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