Ver código fonte

feat(zf): 添加合同预览功能

- 集成 docx-preview 库用于渲染 DOCX 文件
- 添加预览对话框和相关功能函数
- 在合同信息卡片中增加预览按钮- 优化 DOCX 预览样式
nahida 10 meses atrás
pai
commit
39b4300308
1 arquivos alterados com 210 adições e 28 exclusões
  1. 210 28
      src/views/zf/fwxq.vue

+ 210 - 28
src/views/zf/fwxq.vue

@@ -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>