|
@@ -0,0 +1,405 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { onMounted, reactive, ref } from 'vue'
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
+import { Download, RefreshCw, Search, Trash2 } from 'lucide-vue-next'
|
|
|
|
|
+import { clientGet, clientPost } from '@/utils/request.ts'
|
|
|
|
|
+
|
|
|
|
|
+interface AReceiptInfo {
|
|
|
|
|
+ id?: string
|
|
|
|
|
+ contractId?: string
|
|
|
|
|
+ receiptNumber?: string
|
|
|
|
|
+ payer?: string
|
|
|
|
|
+ paymentMethod?: string
|
|
|
|
|
+ totalAmount?: number
|
|
|
|
|
+ rent?: number
|
|
|
|
|
+ propertyFee?: number
|
|
|
|
|
+ deposit?: number
|
|
|
|
|
+ waterFee?: number
|
|
|
|
|
+ receiptReason?: string
|
|
|
|
|
+ attachmentUrl?: string
|
|
|
|
|
+ accountingDate?: string
|
|
|
|
|
+ generationDate?: string
|
|
|
|
|
+ createTime?: string
|
|
|
|
|
+ updateTime?: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface AReceiptInfoResponse extends BaseResponse {
|
|
|
|
|
+ data: PageType<AReceiptInfo>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 响应式数据
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const tableData = ref<AReceiptInfo[]>([])
|
|
|
|
|
+const selectedRows = ref<AReceiptInfo[]>([])
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+
|
|
|
|
|
+const MINIO_URL = import.meta.env.VITE_MINIO_BASE_URL
|
|
|
|
|
+
|
|
|
|
|
+// 搜索表单
|
|
|
|
|
+const searchForm = reactive({
|
|
|
|
|
+ receiptNumber: '',
|
|
|
|
|
+ payer: '',
|
|
|
|
|
+ paymentMethod: '',
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 收款方式选项
|
|
|
|
|
+const paymentMethods = [
|
|
|
|
|
+ { label: '转账', value: '转账' },
|
|
|
|
|
+ { label: '扫码', value: '扫码' },
|
|
|
|
|
+ { label: '其他', value: '其他' },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+// 获取列表数据
|
|
|
|
|
+const getList = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await clientGet<
|
|
|
|
|
+ {
|
|
|
|
|
+ receiptNumber: string
|
|
|
|
|
+ payer: string
|
|
|
|
|
+ paymentMethod: string
|
|
|
|
|
+ },
|
|
|
|
|
+ AReceiptInfoResponse
|
|
|
|
|
+ >('/AReceiptInfo/findByPage', {
|
|
|
|
|
+ params: {
|
|
|
|
|
+ pageNum: searchForm.pageNum,
|
|
|
|
|
+ pageSize: searchForm.pageSize,
|
|
|
|
|
+ receiptNumber: searchForm.receiptNumber,
|
|
|
|
|
+ payer: searchForm.payer,
|
|
|
|
|
+ paymentMethod: searchForm.paymentMethod,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ tableData.value = res.data.records
|
|
|
|
|
+ total.value = res.data.total
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ ElMessage.error('获取数据失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 搜索
|
|
|
|
|
+const handleSearch = () => {
|
|
|
|
|
+ searchForm.pageNum = 1
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置搜索
|
|
|
|
|
+const handleReset = () => {
|
|
|
|
|
+ searchForm.receiptNumber = ''
|
|
|
|
|
+ searchForm.payer = ''
|
|
|
|
|
+ searchForm.paymentMethod = ''
|
|
|
|
|
+ searchForm.pageNum = 1
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 批量删除
|
|
|
|
|
+const handleBatchDelete = async () => {
|
|
|
|
|
+ if (selectedRows.value.length === 0) {
|
|
|
|
|
+ ElMessage.warning('请选择要删除的数据')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm(
|
|
|
|
|
+ `确定要删除选中的 ${selectedRows.value.length} 条记录吗?`,
|
|
|
|
|
+ '确认删除',
|
|
|
|
|
+ {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ const ids = selectedRows.value.map((row) => row.id!).filter(Boolean)
|
|
|
|
|
+ const res = await clientPost<string[], BaseResponse>('/AReceiptInfo/deleteBatch', ids)
|
|
|
|
|
+
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success(res.msg)
|
|
|
|
|
+ selectedRows.value = []
|
|
|
|
|
+ getList()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ // 用户取消删除
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 表格选择变化
|
|
|
|
|
+const handleSelectionChange = (selection: AReceiptInfo[]) => {
|
|
|
|
|
+ selectedRows.value = selection
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 分页变化
|
|
|
|
|
+const handlePageChange = (page: number) => {
|
|
|
|
|
+ searchForm.pageNum = page
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 每页条数变化
|
|
|
|
|
+const handleSizeChange = (size: number) => {
|
|
|
|
|
+ searchForm.pageSize = size
|
|
|
|
|
+ searchForm.pageNum = 1
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 格式化金额
|
|
|
|
|
+const formatAmount = (amount: number | undefined) => {
|
|
|
|
|
+ return amount ? `¥${amount.toLocaleString()}` : '-'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 格式化日期
|
|
|
|
|
+const formatDate = (date: string | undefined) => {
|
|
|
|
|
+ return date ? new Date(date).toLocaleDateString() : '-'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 下载附件
|
|
|
|
|
+const handleDownloadAttachment = async (row: AReceiptInfo) => {
|
|
|
|
|
+ if (!row.attachmentUrl) {
|
|
|
|
|
+ ElMessage.warning('该记录没有附件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 如果attachmentUrl是完整的URL,直接下载
|
|
|
|
|
+ if (row.attachmentUrl.startsWith('http')) {
|
|
|
|
|
+ // 创建一个临时的a标签进行下载
|
|
|
|
|
+ const link = document.createElement('a')
|
|
|
|
|
+ link.href = row.attachmentUrl
|
|
|
|
|
+ link.download = `收据附件_${row.receiptNumber || row.id}.${getFileExtension(row.attachmentUrl)}`
|
|
|
|
|
+ link.target = '_blank'
|
|
|
|
|
+ document.body.appendChild(link)
|
|
|
|
|
+ link.click()
|
|
|
|
|
+ document.body.removeChild(link)
|
|
|
|
|
+ ElMessage.success('开始下载附件')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果是相对路径则拼上MINIO_URL下载
|
|
|
|
|
+ const url = MINIO_URL + row.attachmentUrl
|
|
|
|
|
+ // 创建一个临时的a标签进行下载
|
|
|
|
|
+ const link = document.createElement('a')
|
|
|
|
|
+ link.href = url
|
|
|
|
|
+ link.download = `收据附件_${row.receiptNumber || row.id}.${getFileExtension(url)}`
|
|
|
|
|
+ link.target = '_blank'
|
|
|
|
|
+ document.body.appendChild(link)
|
|
|
|
|
+ link.click()
|
|
|
|
|
+ document.body.removeChild(link)
|
|
|
|
|
+ ElMessage.success('开始下载附件')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('下载附件失败:', error)
|
|
|
|
|
+ ElMessage.error('下载附件失败,请稍后重试')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取文件扩展名
|
|
|
|
|
+const getFileExtension = (url: string): string => {
|
|
|
|
|
+ const parts = url.split('.')
|
|
|
|
|
+ return parts.length > 1 ? parts[parts.length - 1] : 'pdf'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getList()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="p-6 bg-gray-50 min-h-screen">
|
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm">
|
|
|
|
|
+ <!-- 页面标题 -->
|
|
|
|
|
+ <div class="px-6 py-4 border-b border-gray-200">
|
|
|
|
|
+ <h1 class="text-xl font-semibold text-gray-900">合同收据管理</h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索表单 -->
|
|
|
|
|
+ <div class="p-6 border-b border-gray-200">
|
|
|
|
|
+ <el-form :model="searchForm" inline class="search-form">
|
|
|
|
|
+ <el-form-item label="收据单号">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchForm.receiptNumber"
|
|
|
|
|
+ placeholder="请输入收据单号"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="w-48"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="交款单位">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchForm.payer"
|
|
|
|
|
+ placeholder="请输入交款单位"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="w-48"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="收款方式">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.paymentMethod"
|
|
|
|
|
+ placeholder="请选择收款方式"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 12rem"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in paymentMethods"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="handleSearch" :loading="loading">
|
|
|
|
|
+ <Search class="w-4 h-4 mr-1" v-if="!loading" />
|
|
|
|
|
+ 搜索
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button @click="handleReset">
|
|
|
|
|
+ <RefreshCw class="w-4 h-4 mr-1" />
|
|
|
|
|
+ 重置
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 操作按钮 -->
|
|
|
|
|
+ <div class="px-6 py-4 border-b border-gray-200">
|
|
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
|
|
+ <div class="flex gap-2">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ :disabled="selectedRows.length === 0"
|
|
|
|
|
+ @click="handleBatchDelete"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Trash2 class="w-4 h-4 mr-1" />
|
|
|
|
|
+ 批量删除 ({{ selectedRows.length }})
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="text-sm text-gray-500">共 {{ total }} 条记录</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 数据表格 -->
|
|
|
|
|
+ <div class="px-6">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :data="tableData"
|
|
|
|
|
+ v-loading="loading"
|
|
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
|
|
+ stripe
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column type="selection" width="55" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="receiptNumber" label="收据单号" width="180" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="payer" label="交款单位" min-width="150" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="paymentMethod" label="收款方式" width="100" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="totalAmount" label="合计金额" width="120" align="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="font-medium text-green-600">
|
|
|
|
|
+ {{ formatAmount(row.totalAmount) }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="rent" label="租金" width="100" align="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatAmount(row.rent) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="propertyFee" label="物业费" width="100" align="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatAmount(row.propertyFee) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="deposit" label="押金" width="100" align="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatAmount(row.deposit) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="waterFee" label="水费" width="100" align="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatAmount(row.waterFee) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="receiptReason" label="收款事由" min-width="150" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- <el-table-column prop="accountingDate" label="入账日期" width="100">-->
|
|
|
|
|
+ <!-- <template #default="{ row }">-->
|
|
|
|
|
+ <!-- {{ formatDate(row.accountingDate) }}-->
|
|
|
|
|
+ <!-- </template>-->
|
|
|
|
|
+ <!-- </el-table-column>-->
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column prop="generationDate" label="生成日期" width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatDate(row.generationDate) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ link
|
|
|
|
|
+ v-if="row.attachmentUrl"
|
|
|
|
|
+ @click="handleDownloadAttachment(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Download class="w-3 h-3 mr-1" />
|
|
|
|
|
+ 下载附件
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <span v-else class="text-gray-400 text-xs">无附件</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
|
+ <div class="px-6 py-4 border-t border-gray-200">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="searchForm.pageNum"
|
|
|
|
|
+ v-model:page-size="searchForm.pageSize"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handlePageChange"
|
|
|
|
|
+ class="justify-end"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+:deep(.el-table) {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-pagination) {
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.search-form .el-form-item) {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|