|
|
@@ -1,5 +1,5 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
+import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
|
|
import {
|
|
|
ElButton,
|
|
|
ElCard,
|
|
|
@@ -21,9 +21,11 @@ import {
|
|
|
ElSteps,
|
|
|
ElTag,
|
|
|
} from 'element-plus'
|
|
|
-import { ArrowLeft, Camera, Cpu, FileText, Home, MapPin, Plus, User, X } from 'lucide-vue-next'
|
|
|
+import { ArrowLeft, Cpu, Eye, FileText, Home, MapPin, Plus, User, X } from 'lucide-vue-next'
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
import { clientGet, clientPost } from '@/utils/request.ts'
|
|
|
+// 导入docx-preview库
|
|
|
+import { renderAsync } from 'docx-preview'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
|
@@ -31,10 +33,14 @@ const route = useRoute()
|
|
|
// 响应式数据
|
|
|
const showRentalDialog = ref(false)
|
|
|
const showTerminateDialog = ref(false)
|
|
|
+const showDocxPreviewDialog = ref(false) // 新增:DOCX预览对话框
|
|
|
+const docxPreviewLoading = ref(false) // 新增:DOCX加载状态
|
|
|
+const docxPreviewContainer = ref<HTMLElement>() // 新增:DOCX预览容器引用
|
|
|
const currentStep = ref(0)
|
|
|
const submitting = ref(false)
|
|
|
const terminating = ref(false)
|
|
|
const loading = ref(true)
|
|
|
+const MINIO_URL = import.meta.env.VITE_MINIO_BASE_URL
|
|
|
|
|
|
const propertyInfo = ref({
|
|
|
id: '',
|
|
|
@@ -537,14 +543,77 @@ const deviceTypes = computed(() => {
|
|
|
return Array.from(types)
|
|
|
})
|
|
|
|
|
|
-const openARViewing = () => {
|
|
|
- ElMessage.info('正在启动AR看房功能...')
|
|
|
-}
|
|
|
+// const openARViewing = () => {
|
|
|
+// ElMessage.info('正在启动AR看房功能...')
|
|
|
+// }
|
|
|
|
|
|
const filterAssets = () => {
|
|
|
// 筛选逻辑已通过计算属性实现
|
|
|
}
|
|
|
|
|
|
+// 新增:预览DOCX文件的函数
|
|
|
+const previewDocx = async (url: string) => {
|
|
|
+ url = MINIO_URL + url
|
|
|
+ if (!url) {
|
|
|
+ ElMessage.error('合同文件URL不存在')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ showDocxPreviewDialog.value = true
|
|
|
+ docxPreviewLoading.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取DOCX文件
|
|
|
+ const response = await fetch(url)
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('无法获取合同文件')
|
|
|
+ }
|
|
|
+
|
|
|
+ const arrayBuffer = await response.arrayBuffer()
|
|
|
+
|
|
|
+ // 等待DOM更新
|
|
|
+ await nextTick()
|
|
|
+
|
|
|
+ if (docxPreviewContainer.value) {
|
|
|
+ // 清空容器
|
|
|
+ docxPreviewContainer.value.innerHTML = ''
|
|
|
+
|
|
|
+ // 渲染DOCX内容
|
|
|
+ await renderAsync(arrayBuffer, docxPreviewContainer.value, undefined, {
|
|
|
+ className: 'docx-wrapper',
|
|
|
+ inWrapper: true,
|
|
|
+ ignoreWidth: false,
|
|
|
+ ignoreHeight: false,
|
|
|
+ ignoreFonts: false,
|
|
|
+ breakPages: true,
|
|
|
+ ignoreLastRenderedPageBreak: true,
|
|
|
+ experimental: false,
|
|
|
+ trimXmlDeclaration: true,
|
|
|
+ useBase64URL: false,
|
|
|
+ renderHeaders: true,
|
|
|
+ renderFooters: true,
|
|
|
+ renderFootnotes: true,
|
|
|
+ renderEndnotes: true,
|
|
|
+ debug: false,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('预览DOCX文件失败:', error)
|
|
|
+ ElMessage.error('预览合同文件失败,请稍后重试')
|
|
|
+ showDocxPreviewDialog.value = false
|
|
|
+ } finally {
|
|
|
+ docxPreviewLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 新增:关闭DOCX预览对话框
|
|
|
+const closeDocxPreview = () => {
|
|
|
+ showDocxPreviewDialog.value = false
|
|
|
+ if (docxPreviewContainer.value) {
|
|
|
+ docxPreviewContainer.value.innerHTML = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const getValidationFields = () => {
|
|
|
const assetType = propertyInfo.value.assetType
|
|
|
if (assetType === '公租房') {
|
|
|
@@ -778,6 +847,13 @@ const submitRental = async () => {
|
|
|
ElMessage.success(res.msg)
|
|
|
handleCloseRentalDialog()
|
|
|
await getData()
|
|
|
+
|
|
|
+ // 新增:签订合同成功后自动预览DOCX
|
|
|
+ if (contractInfo.value?.contractOriginalUrl) {
|
|
|
+ setTimeout(() => {
|
|
|
+ previewDocx(contractInfo.value!.contractOriginalUrl!)
|
|
|
+ }, 500) // 延迟500ms确保数据更新完成
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
console.error(error)
|
|
|
ElMessage.error('出租失败,请重试')
|
|
|
@@ -928,26 +1004,26 @@ onMounted(() => {
|
|
|
</div>
|
|
|
<div class="text-right flex items-center gap-6">
|
|
|
<div class="relative">
|
|
|
- <button
|
|
|
- @click="openARViewing"
|
|
|
- class="ar-viewing-btn group relative overflow-hidden bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 text-white px-6 py-3 rounded-2xl font-semibold text-sm shadow-2xl transform transition-all duration-300 hover:scale-105 hover:shadow-pink-500/25 active:scale-95"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="absolute inset-0 bg-gradient-to-r from-pink-600 via-purple-600 to-indigo-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
|
|
- ></div>
|
|
|
- <div class="relative flex items-center gap-2">
|
|
|
- <div class="w-5 h-5 relative">
|
|
|
- <div
|
|
|
- class="absolute inset-0 bg-white rounded-full opacity-20 animate-ping"
|
|
|
- ></div>
|
|
|
- <el-icon class="relative z-10"><Camera /></el-icon>
|
|
|
- </div>
|
|
|
- <span class="relative z-10">AR看房</span>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-1000"
|
|
|
- ></div>
|
|
|
- </button>
|
|
|
+ <!-- <button-->
|
|
|
+ <!-- @click="openARViewing"-->
|
|
|
+ <!-- class="ar-viewing-btn group relative overflow-hidden bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 text-white px-6 py-3 rounded-2xl font-semibold text-sm shadow-2xl transform transition-all duration-300 hover:scale-105 hover:shadow-pink-500/25 active:scale-95"-->
|
|
|
+ <!-- >-->
|
|
|
+ <!-- <div-->
|
|
|
+ <!-- class="absolute inset-0 bg-gradient-to-r from-pink-600 via-purple-600 to-indigo-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"-->
|
|
|
+ <!-- ></div>-->
|
|
|
+ <!-- <div class="relative flex items-center gap-2">-->
|
|
|
+ <!-- <div class="w-5 h-5 relative">-->
|
|
|
+ <!-- <div-->
|
|
|
+ <!-- class="absolute inset-0 bg-white rounded-full opacity-20 animate-ping"-->
|
|
|
+ <!-- ></div>-->
|
|
|
+ <!-- <el-icon class="relative z-10"><Camera /></el-icon>-->
|
|
|
+ <!-- </div>-->
|
|
|
+ <!-- <span class="relative z-10">AR看房</span>-->
|
|
|
+ <!-- </div>-->
|
|
|
+ <!-- <div-->
|
|
|
+ <!-- class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-1000"-->
|
|
|
+ <!-- ></div>-->
|
|
|
+ <!-- </button>-->
|
|
|
<div
|
|
|
class="absolute -inset-1 bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 rounded-2xl blur opacity-30 group-hover:opacity-50 transition-opacity duration-300 -z-10"
|
|
|
></div>
|
|
|
@@ -1116,9 +1192,22 @@ onMounted(() => {
|
|
|
class="mb-6 shadow-lg rounded-2xl border-0"
|
|
|
>
|
|
|
<template #header>
|
|
|
- <div class="flex items-center">
|
|
|
- <el-icon class="mr-2 text-purple-500"><FileText /></el-icon>
|
|
|
- <span class="text-lg font-semibold">合同信息</span>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2 text-purple-500"><FileText /></el-icon>
|
|
|
+ <span class="text-lg font-semibold">合同信息</span>
|
|
|
+ </div>
|
|
|
+ <!-- 新增:预览合同按钮 -->
|
|
|
+ <el-button
|
|
|
+ v-if="contractInfo.contractOriginalUrl"
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ @click="previewDocx(contractInfo.contractOriginalUrl)"
|
|
|
+ class="flex items-center gap-1"
|
|
|
+ >
|
|
|
+ <el-icon><Eye /></el-icon>
|
|
|
+ 预览合同
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
<el-row :gutter="24">
|
|
|
@@ -1185,6 +1274,29 @@ onMounted(() => {
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-main>
|
|
|
+
|
|
|
+ <!-- 新增:DOCX预览对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showDocxPreviewDialog"
|
|
|
+ title="合同预览"
|
|
|
+ width="70%"
|
|
|
+ :before-close="closeDocxPreview"
|
|
|
+ class="docx-preview-dialog"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-loading="docxPreviewLoading"
|
|
|
+ element-loading-text="正在加载合同文件..."
|
|
|
+ class="docx-preview-wrapper"
|
|
|
+ >
|
|
|
+ <div ref="docxPreviewContainer" class="docx-container" v-show="!docxPreviewLoading"></div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <el-button @click="closeDocxPreview">关闭</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
<el-dialog
|
|
|
v-model="showRentalDialog"
|
|
|
title="房屋出租"
|
|
|
@@ -1831,4 +1943,74 @@ onMounted(() => {
|
|
|
.animate-ping {
|
|
|
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
|
|
}
|
|
|
+
|
|
|
+/* 新增:DOCX预览相关样式 */
|
|
|
+.docx-preview-dialog {
|
|
|
+ --el-dialog-content-font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-preview-wrapper {
|
|
|
+ min-height: 400px;
|
|
|
+ max-height: 70vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container {
|
|
|
+ padding: 20px;
|
|
|
+ font-family: 'Times New Roman', serif;
|
|
|
+ line-height: 1.6;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+/* DOCX内容样式优化 */
|
|
|
+.docx-container :deep(.docx-wrapper) {
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+ background: white;
|
|
|
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
|
+ padding: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container :deep(p) {
|
|
|
+ margin: 0 0 12px 0;
|
|
|
+ text-align: justify;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container :deep(table) {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ margin: 16px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container :deep(table td),
|
|
|
+.docx-container :deep(table th) {
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ padding: 8px;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container :deep(table th) {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container :deep(h1),
|
|
|
+.docx-container :deep(h2),
|
|
|
+.docx-container :deep(h3),
|
|
|
+.docx-container :deep(h4),
|
|
|
+.docx-container :deep(h5),
|
|
|
+.docx-container :deep(h6) {
|
|
|
+ margin: 20px 0 12px 0;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.docx-container :deep(.page-break) {
|
|
|
+ page-break-before: always;
|
|
|
+ margin-top: 40px;
|
|
|
+ padding-top: 40px;
|
|
|
+ border-top: 1px dashed #ccc;
|
|
|
+}
|
|
|
</style>
|