Pārlūkot izejas kodu

feat(zfxxgl): 添加已租房屋的租户和合同信息功能

- 新增租户信息和合同信息的接口调用- 实现租户信息和合同信息的展示功能
- 添加合同文档的预览和下载功能- 优化房屋状态的显示逻辑,根据状态显示不同颜色
nahida 10 mēneši atpakaļ
vecāks
revīzija
fe674aa7e0
2 mainītis faili ar 453 papildinājumiem un 14 dzēšanām
  1. 14 8
      src/views/zf/zflb.vue
  2. 439 6
      src/views/zfgl/zfxxgl.vue

+ 14 - 8
src/views/zf/zflb.vue

@@ -390,7 +390,13 @@ onMounted(() => {
                 </el-col>
                 <el-col :xs="24" :sm="12" :md="6" style="display: flex">
                   <div class="mb-4 flex items-end">
-                    <el-button type="primary" @click="applyFilters" class="mr-2" :loading="loading" size="default">
+                    <el-button
+                      type="primary"
+                      @click="applyFilters"
+                      class="mr-2"
+                      :loading="loading"
+                      size="default"
+                    >
                       <el-icon class="mr-1"><Search /></el-icon>
                       搜索
                     </el-button>
@@ -410,13 +416,13 @@ onMounted(() => {
               共找到
               <span class="font-semibold text-blue-600">{{ total }}</span> 个结果
             </div>
-<!--            <el-select v-model="sortBy" placeholder="排序方式" class="w-48" size="default">-->
-<!--              <el-option label="默认排序" value="default"></el-option>-->
-<!--              <el-option label="租金从低到高" value="price-asc"></el-option>-->
-<!--              <el-option label="租金从高到低" value="price-desc"></el-option>-->
-<!--              <el-option label="面积从小到大" value="area-asc"></el-option>-->
-<!--              <el-option label="面积从大到小" value="area-desc"></el-option>-->
-<!--            </el-select>-->
+            <el-select v-model="sortBy" placeholder="排序方式" style="width: 20%" size="default">
+              <el-option label="默认排序" value="default"></el-option>
+              <el-option label="租金从低到高" value="price-asc"></el-option>
+              <el-option label="租金从高到低" value="price-desc"></el-option>
+              <el-option label="面积从小到大" value="area-asc"></el-option>
+              <el-option label="面积从大到小" value="area-desc"></el-option>
+            </el-select>
           </div>
 
           <!-- 加载状态 -->

+ 439 - 6
src/views/zfgl/zfxxgl.vue

@@ -1,19 +1,23 @@
 <script setup lang="ts">
-import { onMounted, reactive, ref } from 'vue'
+import { computed, onMounted, reactive, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import {
   Delete,
+  Download,
   Edit,
   Eye,
+  FileText,
   Home,
   Info,
   Package,
   Plus,
   Search,
   Settings,
+  Users,
   Wrench,
 } from 'lucide-vue-next'
 import { clientGet, clientPost } from '@/utils/request.ts'
+import { renderAsync } from 'docx-preview'
 
 interface ASimplifiedHouseInfo {
   id: string
@@ -155,12 +159,45 @@ interface UpdateAMaintenanceRecords {
   maintenanceStatus: string
 }
 
+interface ATenantInfo {
+  id: string
+  simplifiedHouseId: string
+  tenantName: string
+  tenantNumber: string
+  tenantIdCard: string
+  tenantInDate: string
+  tenantTime: string
+  tenantRent: number
+}
+
+interface ATenantInfoResponse extends BaseResponse {
+  data: ATenantInfo
+}
+
+interface AContractInfo {
+  id: string
+  simplifiedHouseId: string
+  contractNumber: string
+  contractDate: string
+  contractTime: string
+  contractExpirationDate: string
+  contractDeposit: number
+  contractStatus: string
+  originalContractUrl: string
+  signContractUrl: string
+}
+
+interface AContractInfoResponse extends BaseResponse {
+  data: AContractInfo
+}
+
 // 响应式数据
 const loading = ref(false)
 const tableData = ref<ASimplifiedHouseInfo[]>([])
 const total = ref(0)
 const currentPage = ref(1)
 const pageSize = ref(10)
+const MINIO_URL = import.meta.env.VITE_MINIO_BASE_URL
 
 // 搜索表单
 const searchForm = reactive({
@@ -176,6 +213,7 @@ const detailDialogVisible = ref(false)
 const deviceDialogVisible = ref(false)
 const maintenanceDialogVisible = ref(false)
 const maintenanceRecordDialogVisible = ref(false)
+const contractPreviewDialogVisible = ref(false)
 const isEdit = ref(false)
 const isDeviceEdit = ref(false)
 const isMaintenanceEdit = ref(false)
@@ -229,9 +267,26 @@ const maintenanceForm = reactive<AddAMaintenanceRecords & { id?: string }>({
 const selectedRows = ref<ASimplifiedHouseInfo[]>([])
 const currentHouseId = ref('')
 const currentHouseName = ref('')
+const currentHouseStatus = ref('')
 const deviceList = ref<ADeviceInfo[]>([])
 const deviceLoading = ref(false)
 
+// 租户和合同信息
+const tenantInfo = ref<ATenantInfo | null>(null)
+const contractInfo = ref<AContractInfo | null>(null)
+const tenantLoading = ref(false)
+const contractLoading = ref(false)
+
+// 合同预览相关
+const previewLoading = ref(false)
+const previewTitle = ref('')
+const previewContent = ref<HTMLElement | null>(null)
+
+// 计算属性:是否显示租户和合同标签页
+const showTenantAndContractTabs = computed(() => {
+  return currentHouseStatus.value === '已租'
+})
+
 // API方法
 const addRentingHouse = async (info: AddASimplifiedHouseInfo) => {
   const res = await clientPost<AddASimplifiedHouseInfo, BaseResponse>(
@@ -427,6 +482,112 @@ const getDeviceMaintainRecord = async (deviceId: string) => {
   return res.data
 }
 
+// 根据房屋ID获取租户信息
+const getTenantInfo = async (simplifiedHouseId: string) => {
+  tenantLoading.value = true
+  try {
+    const res = await clientGet<
+      {
+        simplifiedHouseId: string
+      },
+      ATenantInfoResponse
+    >('/atenantInfo/getBySimplifiedHouseId', {
+      params: {
+        simplifiedHouseId: simplifiedHouseId,
+      },
+    })
+    if (res.code !== 200) {
+      ElMessage.error(res.msg)
+      tenantInfo.value = null
+      return
+    }
+    tenantInfo.value = res.data
+  } finally {
+    tenantLoading.value = false
+  }
+}
+
+// 根据房屋ID获取合同信息
+const getContractInfo = async (simplifiedHouseId: string) => {
+  contractLoading.value = true
+  try {
+    const res = await clientGet<
+      {
+        simplifiedHouseId: string
+      },
+      AContractInfoResponse
+    >('/acontractInfo/getBySimplifiedHouseId', {
+      params: {
+        simplifiedHouseId: simplifiedHouseId,
+      },
+    })
+    if (res.code !== 200) {
+      ElMessage.error(res.msg)
+      contractInfo.value = null
+      return
+    }
+    contractInfo.value = res.data
+  } finally {
+    contractLoading.value = false
+  }
+}
+
+// 预览docx文件
+const previewDocx = async (url: string, title: string) => {
+  url = MINIO_URL + url
+  if (!url) {
+    ElMessage.warning('文档地址为空')
+    return
+  }
+
+  previewLoading.value = true
+  previewTitle.value = title
+
+  try {
+    // 获取文档内容
+    const response = await fetch(url)
+    if (!response.ok) {
+      throw new Error('文档加载失败')
+    }
+
+    const arrayBuffer = await response.arrayBuffer()
+
+    // 创建预览容器
+    const container = document.createElement('div')
+    container.style.padding = '20px'
+    container.style.backgroundColor = '#fff'
+    container.style.minHeight = '400px'
+
+    // 使用docx-preview渲染文档
+    await renderAsync(arrayBuffer, container)
+
+    previewContent.value = container
+    contractPreviewDialogVisible.value = true
+  } catch (error) {
+    console.error('预览文档失败:', error)
+    ElMessage.error('预览文档失败,请检查文档地址是否正确')
+  } finally {
+    previewLoading.value = false
+  }
+}
+
+// 下载文档
+const downloadDocument = (url: string, filename: string) => {
+  url = MINIO_URL + url
+  if (!url) {
+    ElMessage.warning('文档地址为空')
+    return
+  }
+
+  const link = document.createElement('a')
+  link.href = url
+  link.download = filename
+  link.target = '_blank'
+  document.body.appendChild(link)
+  link.click()
+  document.body.removeChild(link)
+}
+
 // 页面方法
 const handleSearch = () => {
   currentPage.value = 1
@@ -506,6 +667,7 @@ const handleBatchDelete = async () => {
 const handleViewDetail = async (row: ASimplifiedHouseInfo) => {
   currentHouseId.value = row.id
   currentHouseName.value = row.houseName || ''
+  currentHouseStatus.value = row.status || ''
   activeTab.value = 'detail'
 
   // 获取房屋详细信息
@@ -525,6 +687,12 @@ const handleViewDetail = async (row: ASimplifiedHouseInfo) => {
   // 获取设备列表
   await loadDeviceList(row.id)
 
+  // 如果是已租房屋,获取租户和合同信息
+  if (row.status === '已租') {
+    await getTenantInfo(row.id)
+    await getContractInfo(row.id)
+  }
+
   detailDialogVisible.value = true
 }
 
@@ -721,8 +889,16 @@ const getAssetTypeTag = (assetType: string | null | undefined): string => {
   if (!assetType) return 'default'
   if (assetType === '公租房') return 'primary'
   if (assetType === '厂房') return 'success'
-  if (assetType === '创新创业基地') return 'danger' // 新增类型
-  return 'warning' // 默认颜色
+  if (assetType === '创新创业基地') return 'danger'
+  return 'warning'
+}
+
+const getContractStatusColor = (status: string) => {
+  const colorMap: Record<string, string> = {
+    有效: 'success',
+    已终止: 'info',
+  }
+  return colorMap[status] || ''
 }
 
 onMounted(() => {
@@ -894,8 +1070,14 @@ onMounted(() => {
         <el-form-item label="房间名称" required>
           <el-input v-model="houseForm.houseName" placeholder="请输入房间名称" />
         </el-form-item>
-        <el-form-item label="状态" required>
-          <el-select v-model="houseForm.status" placeholder="请选择状态" class="w-full">
+        <el-form-item label="状态">
+          <el-select
+            v-model="houseForm.status"
+            placeholder="请选择状态"
+            class="w-full"
+            default-first-option
+            disabled
+          >
             <el-option label="空闲" value="空闲" />
             <el-option label="已租" value="已租" />
           </el-select>
@@ -924,7 +1106,7 @@ onMounted(() => {
     <el-dialog
       v-model="detailDialogVisible"
       :title="`${currentHouseName} - 详细信息管理`"
-      width="1000px"
+      width="1200px"
       :close-on-click-modal="false"
       class="detail-dialog"
     >
@@ -1044,6 +1226,192 @@ onMounted(() => {
             </div>
           </div>
         </el-tab-pane>
+
+        <!-- 租户信息标签页 (仅已租房屋显示) -->
+        <el-tab-pane v-if="showTenantAndContractTabs" label="租户信息" name="tenant">
+          <template #label>
+            <div class="flex items-center gap-2">
+              <Users class="w-4 h-4" />
+              <span>租户信息</span>
+            </div>
+          </template>
+          <div class="p-4" v-loading="tenantLoading">
+            <div v-if="tenantInfo" class="bg-gray-50 rounded-lg p-6">
+              <div class="grid grid-cols-2 gap-6">
+                <div class="space-y-4">
+                  <div class="flex items-center gap-3">
+                    <span class="text-gray-600 font-medium w-20">租户姓名:</span>
+                    <span class="text-gray-900">{{ tenantInfo.tenantName }}</span>
+                  </div>
+                  <div class="flex items-center gap-3">
+                    <span class="text-gray-600 font-medium w-20">联系电话:</span>
+                    <span class="text-gray-900">{{ tenantInfo.tenantNumber }}</span>
+                  </div>
+                  <div class="flex items-center gap-3">
+                    <span class="text-gray-600 font-medium w-20">身份证号:</span>
+                    <span class="text-gray-900">{{ tenantInfo.tenantIdCard }}</span>
+                  </div>
+                </div>
+                <div class="space-y-4">
+                  <div class="flex items-center gap-3">
+                    <span class="text-gray-600 font-medium w-20">入住时间:</span>
+                    <span class="text-gray-900">{{ tenantInfo.tenantInDate }}</span>
+                  </div>
+                  <div class="flex items-center gap-3">
+                    <span class="text-gray-600 font-medium w-20">租期:</span>
+                    <span class="text-gray-900">{{ tenantInfo.tenantTime }}</span>
+                  </div>
+                  <div class="flex items-center gap-3">
+                    <span class="text-gray-600 font-medium w-20">月租金:</span>
+                    <span class="text-green-600 font-semibold">¥{{ tenantInfo.tenantRent }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div v-else class="text-center py-12">
+              <Users class="w-16 h-16 text-gray-400 mx-auto mb-4" />
+              <p class="text-gray-500 text-lg">暂无租户信息</p>
+            </div>
+          </div>
+        </el-tab-pane>
+
+        <!-- 合同信息标签页 (仅已租房屋显示) -->
+        <el-tab-pane v-if="showTenantAndContractTabs" label="合同信息" name="contract">
+          <template #label>
+            <div class="flex items-center gap-2">
+              <FileText class="w-4 h-4" />
+              <span>合同信息</span>
+            </div>
+          </template>
+          <div class="p-4" v-loading="contractLoading">
+            <div v-if="contractInfo" class="space-y-6">
+              <!-- 合同基本信息 -->
+              <div class="bg-gray-50 rounded-lg p-6">
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">合同基本信息</h3>
+                <div class="grid grid-cols-2 gap-6">
+                  <div class="space-y-4">
+                    <div class="flex items-center gap-3">
+                      <span class="text-gray-600 font-medium w-24">合同编号:</span>
+                      <span class="text-gray-900">{{ contractInfo.contractNumber }}</span>
+                    </div>
+                    <div class="flex items-center gap-3">
+                      <span class="text-gray-600 font-medium w-24">签约日期:</span>
+                      <span class="text-gray-900">{{ contractInfo.contractDate }}</span>
+                    </div>
+                    <div class="flex items-center gap-3">
+                      <span class="text-gray-600 font-medium w-24">合同期限:</span>
+                      <span class="text-gray-900">{{ contractInfo.contractTime }}</span>
+                    </div>
+                  </div>
+                  <div class="space-y-4">
+                    <div class="flex items-center gap-3">
+                      <span class="text-gray-600 font-medium w-24">到期日期:</span>
+                      <span class="text-gray-900">{{ contractInfo.contractExpirationDate }}</span>
+                    </div>
+                    <div class="flex items-center gap-3">
+                      <span class="text-gray-600 font-medium w-24">押金:</span>
+                      <span class="text-green-600 font-semibold"
+                        >¥{{ contractInfo.contractDeposit }}</span
+                      >
+                    </div>
+                    <div class="flex items-center gap-3">
+                      <span class="text-gray-600 font-medium w-24">合同状态:</span>
+                      <el-tag :type="getContractStatusColor(contractInfo.contractStatus)">
+                        {{ contractInfo.contractStatus }}
+                      </el-tag>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 合同文档 -->
+              <div class="bg-gray-50 rounded-lg p-6">
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">合同文档</h3>
+                <div class="space-y-4">
+                  <!-- 原合同 -->
+                  <div class="flex items-center justify-between p-4 bg-white rounded-lg border">
+                    <div class="flex items-center gap-3">
+                      <FileText class="w-6 h-6 text-blue-600" />
+                      <div>
+                        <p class="font-medium text-gray-900">原合同文档</p>
+                        <p class="text-sm text-gray-500">原始合同文件</p>
+                      </div>
+                    </div>
+                    <div class="flex gap-2">
+                      <el-button
+                        type="primary"
+                        size="small"
+                        @click="previewDocx(contractInfo.originalContractUrl, '原合同文档')"
+                        :loading="previewLoading"
+                        :icon="Eye"
+                      >
+                        预览
+                      </el-button>
+                      <el-button
+                        type="success"
+                        size="small"
+                        @click="
+                          downloadDocument(contractInfo.originalContractUrl, '原合同文档.docx')
+                        "
+                        :icon="Download"
+                      >
+                        下载
+                      </el-button>
+                    </div>
+                  </div>
+
+                  <!-- 签订后合同 -->
+                  <div
+                    v-if="contractInfo.signContractUrl"
+                    class="flex items-center justify-between p-4 bg-white rounded-lg border"
+                  >
+                    <div class="flex items-center gap-3">
+                      <FileText class="w-6 h-6 text-green-600" />
+                      <div>
+                        <p class="font-medium text-gray-900">签订后合同</p>
+                        <p class="text-sm text-gray-500">已签署的合同文件</p>
+                      </div>
+                    </div>
+                    <div class="flex gap-2">
+                      <el-button
+                        type="primary"
+                        size="small"
+                        @click="previewDocx(contractInfo.signContractUrl, '签订后合同')"
+                        :loading="previewLoading"
+                        :icon="Eye"
+                      >
+                        预览
+                      </el-button>
+                      <el-button
+                        type="success"
+                        size="small"
+                        @click="downloadDocument(contractInfo.signContractUrl, '签订后合同.docx')"
+                        :icon="Download"
+                      >
+                        下载
+                      </el-button>
+                    </div>
+                  </div>
+
+                  <!-- 签订后合同为空的提示 -->
+                  <div
+                    v-else
+                    class="flex items-center justify-center p-8 bg-white rounded-lg border border-dashed border-gray-300"
+                  >
+                    <div class="text-center">
+                      <FileText class="w-12 h-12 text-gray-400 mx-auto mb-2" />
+                      <p class="text-gray-500">签订后合同暂未上传</p>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div v-else class="text-center py-12">
+              <FileText class="w-16 h-16 text-gray-400 mx-auto mb-4" />
+              <p class="text-gray-500 text-lg">暂无合同信息</p>
+            </div>
+          </div>
+        </el-tab-pane>
       </el-tabs>
 
       <template #footer>
@@ -1053,6 +1421,29 @@ onMounted(() => {
       </template>
     </el-dialog>
 
+    <!-- 合同文档预览对话框 -->
+    <el-dialog
+      v-model="contractPreviewDialogVisible"
+      :title="previewTitle"
+      width="60%"
+      :close-on-click-modal="false"
+      class="preview-dialog"
+    >
+      <div
+        v-if="previewContent"
+        v-html="previewContent.innerHTML"
+        class="preview-content max-h-[70vh] overflow-y-auto"
+      ></div>
+      <div v-else class="text-center py-12">
+        <p class="text-gray-500">文档加载中...</p>
+      </div>
+      <template #footer>
+        <div class="flex justify-end">
+          <el-button @click="contractPreviewDialogVisible = false">关闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
     <!-- 设备新增/编辑对话框 -->
     <el-dialog
       v-model="deviceDialogVisible"
@@ -1289,4 +1680,46 @@ onMounted(() => {
   --el-badge-size: 16px;
   --el-badge-padding: 4px;
 }
+
+.preview-dialog .el-dialog__body {
+  padding: 20px;
+}
+
+.preview-content {
+  background: white;
+  border-radius: 8px;
+  padding: 20px;
+  border: 1px solid #e5e7eb;
+}
+
+.preview-content p {
+  margin-bottom: 12px;
+  line-height: 1.6;
+}
+
+.preview-content h1,
+.preview-content h2,
+.preview-content h3 {
+  margin-bottom: 16px;
+  margin-top: 24px;
+  font-weight: 600;
+}
+
+.preview-content table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 16px 0;
+}
+
+.preview-content table th,
+.preview-content table td {
+  border: 1px solid #d1d5db;
+  padding: 8px 12px;
+  text-align: left;
+}
+
+.preview-content table th {
+  background-color: #f9fafb;
+  font-weight: 600;
+}
 </style>