Bläddra i källkod

feat(zfgl): 添加租房合同管理功能

- 新增租房合同管理页面,包括合同列表、搜索、分页等功能
- 实现合同文档预览和下载功能- 添加合同状态标签和押金金额格式化显示
nahida 10 månader sedan
förälder
incheckning
6ddc426e55
2 ändrade filer med 438 tillägg och 0 borttagningar
  1. 6 0
      src/router/route.ts
  2. 432 0
      src/views/zfgl/zfhtgl.vue

+ 6 - 0
src/router/route.ts

@@ -92,6 +92,12 @@ export const routeList:RouterType[] = [
     icon: 'Location',
     addr: 'zfgl/zfxxgl',
   },
+  {
+    path: 'zfgl/zfhtgl',
+    name: '租房合同管理',
+    icon: 'Location',
+    addr: 'zfgl/zfhtgl',
+  },
   // {
   //   path: 'gzfxx',
   //   name: '公租房信息',

+ 432 - 0
src/views/zfgl/zfhtgl.vue

@@ -0,0 +1,432 @@
+<script setup lang="ts">
+import { nextTick, onMounted, reactive, ref } from 'vue'
+import {
+  ElButton,
+  ElCard,
+  ElDialog,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElMessage,
+  ElPagination,
+  ElSpace,
+  ElTable,
+  ElTableColumn,
+  ElTag,
+} from 'element-plus'
+import { Download, Eye, FileText, RefreshCwIcon, Search } from 'lucide-vue-next'
+import { renderAsync } from 'docx-preview'
+import { clientGet } from '@/utils/request.ts'
+
+// 接口定义
+export interface AContractInfo {
+  id: string
+  simplifiedHouseId: string
+  contractNumber: string
+  contractDate: string
+  contractTime: string
+  contractExpirationDate: string
+  contractDeposit: number
+  contractStatus: string
+  originalContractUrl: string
+  signContractUrl: string
+  createTime: string
+  updateTime: string
+}
+
+interface AContractInfoListResponse extends BaseResponse {
+  data: PageType<AContractInfo>
+}
+
+interface BaseResponse {
+  code: number
+  msg: string
+  data?: object
+}
+
+interface PageType<T> {
+  countId?: object
+  current?: number
+  pages: number
+  records: T[]
+  size: number
+  total: number
+}
+
+// 响应式数据
+const loading = ref(false)
+const tableData = ref<AContractInfo[]>([])
+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({
+  contractNumber: '',
+})
+
+// 预览相关
+const previewVisible = ref(false)
+const previewLoading = ref(false)
+const previewTitle = ref('')
+const previewContainer = ref<HTMLElement>()
+
+// 获取合同状态标签类型
+const getStatusType = (status: string) => {
+  const statusMap: Record<string, string> = {
+    有效: 'success',
+    已退租: 'warning',
+  }
+  return statusMap[status] || 'info'
+}
+
+// 获取列表数据
+const getList = async () => {
+  loading.value = true
+  try {
+    const response = await clientGet<
+      {
+        pageNum: number
+        pageSize: number
+        contractNumber: string
+      },
+      AContractInfoListResponse
+    >('/acontractInfo/findByPage', {
+      params: {
+        pageNum: currentPage.value,
+        pageSize: pageSize.value,
+        contractNumber: searchForm.contractNumber,
+      },
+    })
+
+    if (response.code === 200 && response.data) {
+      tableData.value = response.data.records
+      total.value = response.data.total
+      currentPage.value = response.data.current || 1
+    } else {
+      ElMessage.error(response.msg || '获取数据失败')
+    }
+  } catch (error) {
+    ElMessage.error('网络请求失败')
+    console.error('获取合同列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 搜索
+const handleSearch = () => {
+  currentPage.value = 1
+  getList()
+}
+
+// 重置搜索
+const handleReset = () => {
+  searchForm.contractNumber = ''
+  currentPage.value = 1
+  getList()
+}
+
+// 分页变化
+const handleCurrentChange = (page: number) => {
+  currentPage.value = page
+  getList()
+}
+
+const handleSizeChange = (size: number) => {
+  pageSize.value = size
+  currentPage.value = 1
+  getList()
+}
+
+// 预览文档
+const previewDocument = async (url: string, title: string) => {
+  url = MINIO_URL + url
+  if (!url) {
+    ElMessage.warning('文档地址为空,无法预览')
+    return
+  }
+
+  previewTitle.value = title
+  previewVisible.value = true
+  previewLoading.value = true
+
+  await nextTick()
+
+  try {
+    // 模拟获取docx文件
+    const response = await fetch(url)
+    if (!response.ok) {
+      throw new Error('文档加载失败')
+    }
+
+    const arrayBuffer = await response.arrayBuffer()
+
+    if (previewContainer.value) {
+      // 清空容器
+      previewContainer.value.innerHTML = ''
+
+      // 使用docx-preview渲染文档
+      await renderAsync(arrayBuffer, previewContainer.value, undefined, {
+        className: 'docx-preview',
+        inWrapper: true,
+        ignoreWidth: false,
+        ignoreHeight: false,
+        ignoreFonts: false,
+        breakPages: true,
+        ignoreLastRenderedPageBreak: true,
+        experimental: false,
+        trimXmlDeclaration: true,
+        useBase64URL: false,
+        renderChanges: false,
+        renderComments: false,
+        renderEndnotes: true,
+        renderFootnotes: true,
+        renderFooters: true,
+        renderHeaders: true,
+      })
+    }
+  } catch (error) {
+    ElMessage.error('文档预览失败: ' + (error as Error).message)
+    console.error('文档预览失败:', error)
+  } finally {
+    previewLoading.value = false
+  }
+}
+
+// 下载文档
+const downloadDocument = (url: string, filename: string) => {
+  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)
+}
+
+// 初始化
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div class="contract-management p-6 bg-gray-50 min-h-screen">
+    <!-- 页面标题 -->
+    <div class="mb-6">
+      <h1 class="text-2xl font-bold text-gray-800 mb-2">租房合同管理</h1>
+      <p class="text-gray-600">管理和查看所有租房合同信息</p>
+    </div>
+
+    <!-- 搜索区域 -->
+    <ElCard class="mb-6" shadow="never">
+      <ElForm :model="searchForm" inline class="search-form">
+        <ElFormItem label="合同编号">
+          <ElInput
+            v-model="searchForm.contractNumber"
+            placeholder="请输入合同编号"
+            clearable
+            class="w-200px"
+            @keyup.enter="handleSearch"
+          />
+        </ElFormItem>
+
+        <ElFormItem>
+          <ElSpace>
+            <ElButton type="primary" :icon="Search" @click="handleSearch"> 搜索 </ElButton>
+            <ElButton :icon="RefreshCwIcon" @click="handleReset"> 重置 </ElButton>
+          </ElSpace>
+        </ElFormItem>
+      </ElForm>
+    </ElCard>
+
+    <!-- 表格区域 -->
+    <ElCard shadow="never">
+      <ElTable
+        :data="tableData"
+        :loading="loading"
+        stripe
+        border
+        style="width: 100%"
+        empty-text="暂无数据"
+      >
+        <ElTableColumn prop="contractNumber" label="合同编号" width="160" />
+        <ElTableColumn prop="contractDate" label="签约日期" width="140" />
+        <ElTableColumn prop="contractTime" label="合同期限" width="120" />
+        <ElTableColumn prop="contractExpirationDate" label="到期日期" width="140" />
+        <ElTableColumn prop="contractDeposit" label="押金(元)" width="140">
+          <template #default="{ row }">
+            <span class="text-orange-600 font-medium"
+              >¥{{ row.contractDeposit.toLocaleString() }}</span
+            >
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="contractStatus" label="合同状态" width="120">
+          <template #default="{ row }">
+            <ElTag :type="getStatusType(row.contractStatus)" size="small">
+              {{ row.contractStatus }}
+            </ElTag>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn label="合同文档" min-width="240">
+          <template #default="{ row }">
+            <ElSpace direction="vertical" size="small">
+              <div v-if="row.originalContractUrl" class="flex items-center gap-2">
+                <FileText class="w-4 h-4 text-blue-500" />
+                <span class="text-sm text-gray-600">原合同:</span>
+                <ElButton
+                  type="primary"
+                  size="small"
+                  text
+                  :icon="Eye"
+                  @click="
+                    previewDocument(row.originalContractUrl, `${row.contractNumber} - 原合同`)
+                  "
+                >
+                  预览
+                </ElButton>
+                <ElButton
+                  type="success"
+                  size="small"
+                  text
+                  :icon="Download"
+                  @click="
+                    downloadDocument(row.originalContractUrl, `${row.contractNumber}_原合同.docx`)
+                  "
+                >
+                  下载
+                </ElButton>
+              </div>
+              <div v-else class="text-sm text-gray-400">原合同: 暂无</div>
+
+              <div v-if="row.signContractUrl" class="flex items-center gap-2">
+                <FileText class="w-4 h-4 text-green-500" />
+                <span class="text-sm text-gray-600">签订合同:</span>
+                <ElButton
+                  type="primary"
+                  size="small"
+                  text
+                  :icon="Eye"
+                  @click="previewDocument(row.signContractUrl, `${row.contractNumber} - 签订合同`)"
+                >
+                  预览
+                </ElButton>
+                <ElButton
+                  type="success"
+                  size="small"
+                  text
+                  :icon="Download"
+                  @click="
+                    downloadDocument(row.signContractUrl, `${row.contractNumber}_签订合同.docx`)
+                  "
+                >
+                  下载
+                </ElButton>
+              </div>
+              <div v-else class="text-sm text-gray-400">签订合同: 暂无</div>
+            </ElSpace>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn prop="createTime" label="创建时间" width="180" />
+        <ElTableColumn prop="updateTime" label="更新时间" width="180" />
+      </ElTable>
+
+      <!-- 分页 -->
+      <div class="flex justify-center mt-6">
+        <ElPagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </ElCard>
+
+    <!-- 文档预览对话框 -->
+    <ElDialog v-model="previewVisible" :title="previewTitle" width="60%" top="5vh" destroy-on-close>
+      <div
+        v-loading="previewLoading"
+        element-loading-text="正在加载文档..."
+        class="preview-container"
+        style="height: 70vh; overflow-y: auto"
+      >
+        <div ref="previewContainer" class="docx-container"></div>
+      </div>
+    </ElDialog>
+  </div>
+</template>
+
+<style scoped>
+.contract-management {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+.search-form {
+  background: #fafafa;
+  padding: 20px;
+  border-radius: 8px;
+}
+
+:deep(.el-table) {
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+:deep(.el-table th) {
+  background-color: #f8f9fa;
+  color: #495057;
+  font-weight: 600;
+}
+
+:deep(.el-table td) {
+  padding: 12px 0;
+}
+
+:deep(.el-pagination) {
+  margin-top: 20px;
+}
+
+.preview-container {
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  background: white;
+}
+
+:deep(.docx-container) {
+  padding: 20px;
+  line-height: 1.6;
+}
+
+:deep(.docx-container p) {
+  margin: 8px 0;
+}
+
+:deep(.docx-container table) {
+  border-collapse: collapse;
+  width: 100%;
+  margin: 16px 0;
+}
+
+:deep(.docx-container table td),
+:deep(.docx-container table th) {
+  border: 1px solid #ddd;
+  padding: 8px;
+  text-align: left;
+}
+
+:deep(.docx-container table th) {
+  background-color: #f2f2f2;
+  font-weight: bold;
+}
+</style>