| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- <template>
- <div class="dashboard-container">
- <!-- 筛选区域(新增类型选择器) -->
- <div class="filter-panel">
- <div class="filter-item">
- <label class="filter-label">选择年份:</label>
- <el-date-picker
- size="default"
- v-model="searchForm.selectedYear"
- type="year"
- placeholder="选择年份"
- @change="handleYearChange"
- format="YYYY"
- value-format="YYYY"
- />
- </div>
- <div class="filter-item">
- <label class="filter-label">选择月份:</label>
- <el-select
- v-model="searchForm.selectedMonth"
- placeholder="请先选择年份"
- size="default"
- class="w-48!"
- :disabled="!searchForm.selectedYear"
- >
- <el-option
- v-for="month in availableMonths"
- :key="month"
- :label="`${month}月`"
- :value="month"
- />
- </el-select>
- </div>
- <!-- 新增:类型选择器(和收据报表保持一致) -->
- <div class="filter-item">
- <label class="filter-label">选择类型:</label>
- <el-select
- v-model="searchForm.selectedType"
- placeholder="请选择数据类型"
- size="default"
- class="w-48!"
- :disabled="!searchForm.selectedYear"
- >
- <el-option
- v-for="type in availableTypes"
- :key="type.value"
- :label="type.label"
- :value="type.value"
- />
- </el-select>
- </div>
- <div>
- <el-button type="primary" @click="getSelection()" :icon="Search" size="default">
- 搜索
- </el-button>
- </div>
- <div>
- <el-button
- color="#626aef"
- @click="getMonthDetail()"
- :icon="Tickets"
- size="default"
- :disabled="!isMonthDetailEnabled"
- :title="!isMonthDetailEnabled ? '请先选择年份和月份' : ''"
- >
- 月详情
- </el-button>
- </div>
- <div>
- <el-button type="success" @click="printTable" size="default">
- 打印表格
- </el-button>
- </div>
- </div>
- <!-- 数据表格 -->
- <div class="table-container" id="printTarget">
- <el-table
- :data="filteredTableData"
- border
- stripe
- style="width: 100%"
- :loading="tableLoading"
- empty-text="暂无相关收据数据"
- :summary-method="getSummaries"
- show-summary
- :span-method="handleSpanMethod"
- >
- <!-- 1. 序号列 -->
- <el-table-column label="序号" align="center" width="50">
- <template #default="scope">
- {{ scope.$index + 1 }}
- </template>
- </el-table-column>
- <!-- 2. 房间信息(仅详情显示) -->
- <el-table-column
- v-if="showMonthDetailColumns"
- prop="tenant"
- label="房间信息"
- align="center"
- />
- <!-- 3. 退款时间 -->
- <el-table-column
- prop="refundTime"
- label="退款时间"
- align="center"
- />
- <!-- 4. 租金 -->
- <el-table-column
- prop="rent"
- label="租金(元)"
- align="center"
- >
- <template #default="scope">
- {{ scope.row.rent?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00' }}
- </template>
- </el-table-column>
- <!-- 5. 押金 -->
- <el-table-column
- prop="deposit"
- label="押金(元)"
- align="center"
- >
- <template #default="scope">
- {{ scope.row.deposit?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00' }}
- </template>
- </el-table-column>
- <!-- 6. 物业费 -->
- <el-table-column
- prop="propertyFee"
- label="物业费(元)"
- align="center"
- >
- <template #default="scope">
- {{ scope.row.propertyFee?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00' }}
- </template>
- </el-table-column>
- <!-- 7. 经手人(仅详情显示) -->
- <el-table-column
- v-if="showMonthDetailColumns"
- prop="operator"
- label="经手人"
- align="center"
- />
- </el-table>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted, reactive } from 'vue'
- import { clientGet } from "@/utils/request.js";
- import { ElMessage } from "element-plus";
- import { Search, Tickets, Printer } from "lucide-vue-next";
- // 类型定义优化
- interface RefundData {
- deposit: number // 押金
- propertyFee: number // 物业费
- refundTime: string // 退款时间
- rent: number // 租金
- tenant?: string // 房间信息
- operator?: string // 经手人
- // 其他备用字段
- waterFee?: number // 水费
- electricityBill?: number// 电费
- refundCode?: string // 收据号
- payType?: string // 付款方式
- rentPeriod?: string // 收取时段
- }
- // 新增:类型选择器选项类型(和收据报表一致)
- interface TypeOption {
- label: string // 显示文本
- value: string | number // 传递值
- }
- interface ApiResponse<T> {
- code: number
- msg: string
- data: T[]
- }
- // 常量定义
- const availableMonths = ref<number[]>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
- // 新增:类型选择器可选选项(和收据报表完全一致)
- const availableTypes = ref<TypeOption[]>([
- { label: '公租房', value: '公租房' },
- { label: '厂房', value: '厂房' },
- { label: '创新创业基地', value: '创新创业基地' }
- ])
- const API_BASE_URL = '/arefund/refund/stat'
- // 响应式数据
- const tableLoading = ref(false)
- const showMonthDetailColumns = ref(false)
- const rawTableData = ref<RefundData[]>([])
- // 搜索表单(新增selectedType,默认选中公租房)
- const searchForm = reactive({
- selectedYear: '', // YYYY格式
- selectedMonth: '',
- selectedType: '公租房' // 新增:类型筛选值,和收据报表默认值一致
- })
- // 计算属性
- const isMonthDetailEnabled = computed(() => {
- return !!searchForm.selectedYear && !!searchForm.selectedMonth
- })
- const filteredTableData = computed(() => {
- return [...rawTableData.value]
- })
- // 接口请求方法(修改:传递类型参数assetType到后端)
- const fetchRefundData = async (apiPath: string, isDetail = false) => {
- tableLoading.value = true
- try {
- const params = {
- year: searchForm.selectedYear,
- month: searchForm.selectedMonth,
- assetType: searchForm.selectedType // 新增:传递类型参数,和收据报表字段名一致
- }
- const res = await clientGet<ApiResponse<RefundData>>(apiPath, { params })
- if (!res) {
- ElMessage.error('接口返回数据为空')
- rawTableData.value = []
- showMonthDetailColumns.value = false
- return
- }
- if (res.code !== 200) {
- ElMessage.error(res.msg || '数据获取失败')
- rawTableData.value = []
- showMonthDetailColumns.value = false
- return
- }
- rawTableData.value = res.data || []
- if (rawTableData.value.length === 0) {
- ElMessage.warning('暂无收据数据')
- showMonthDetailColumns.value = false
- } else {
- showMonthDetailColumns.value = isDetail
- }
- } catch (error) {
- ElMessage.error('网络异常,数据获取失败')
- console.error('获取退款数据失败:', error)
- rawTableData.value = []
- showMonthDetailColumns.value = false
- } finally {
- tableLoading.value = false
- }
- }
- // 事件处理方法(修改:切换年份时重置类型为默认公租房)
- const handleYearChange = (val: string) => {
- if (!val) {
- searchForm.selectedMonth = ''
- searchForm.selectedType = '公租房' // 新增:重置类型选择
- rawTableData.value = []
- ElMessage.warning('请选择年份进行筛选')
- showMonthDetailColumns.value = false
- return
- }
- searchForm.selectedMonth = ''
- searchForm.selectedType = '公租房' // 新增:重置类型选择
- showMonthDetailColumns.value = false
- fetchRefundData(`${API_BASE_URL}/year`)
- }
- const getSelection = () => {
- if (!searchForm.selectedYear) {
- ElMessage.warning('请先选择年份')
- return
- }
- showMonthDetailColumns.value = false
- const apiPath = !searchForm.selectedMonth
- ? `${API_BASE_URL}/year`
- : `${API_BASE_URL}/month`
- fetchRefundData(apiPath)
- }
- const getMonthDetail = () => {
- if (!isMonthDetailEnabled.value) {
- ElMessage.warning('请先选择年份和月份')
- return
- }
- fetchRefundData(`${API_BASE_URL}/day`, true)
- }
- // 合计行计算(适配当前列结构)
- const getSummaries = (param: { columns: any[], data: RefundData[] }) => {
- const { columns, data } = param
- const sums = Array(columns.length).fill('')
- sums[0] = '合计' // 序号列显示合计
- // 定义合计字段和对应索引
- const summaryConfig = showMonthDetailColumns.value
- ? {
- // 详情模式:3=租金、4=押金、5=物业费
- indexes: [3, 4, 5],
- fields: ['rent', 'deposit', 'propertyFee']
- }
- : {
- // 汇总模式:2=租金、3=押金、4=物业费
- indexes: [2, 3, 4],
- fields: ['rent', 'deposit', 'propertyFee']
- }
- // 计算合计值
- summaryConfig.indexes.forEach((index, fieldIndex) => {
- if (index >= columns.length) return
- const field = summaryConfig.fields[fieldIndex]
- const sum = data.reduce((total, item) => {
- return total + (Number(item[field]) || 0)
- }, 0)
- sums[index] = sum.toLocaleString('zh-CN', { minimumFractionDigits: 2 })
- })
- return sums
- }
- // 合并单元格方法(优化合计行显示)
- const handleSpanMethod = ({ row, column, columnIndex }: {
- row: any,
- column: any,
- columnIndex: number
- }) => {
- // 仅处理合计行
- if (row && row.type === 'footer') {
- // 需要显示的列:序号列 + 数值列(租金、押金、物业费)
- const valueColumns = ['rent', 'deposit', 'propertyFee']
- const isValueColumn = valueColumns.includes(column.prop as string)
- if (columnIndex === 0 || isValueColumn) {
- return { rowspan: 1, colspan: 1 }
- } else {
- return { rowspan: 0, colspan: 0 } // 隐藏非数值列
- }
- }
- return { rowspan: 1, colspan: 1 }
- }
- // 打印功能(修改:新增显示选中的类型,和收据报表保持一致)
- const printTable = () => {
- if (filteredTableData.value.length === 0) {
- ElMessage.warning('暂无收据数据,无法打印')
- return
- }
- try {
- const printWindow = window.open('', '_blank')
- if (!printWindow) {
- ElMessage.error('浏览器弹窗被拦截,请允许弹窗后重试')
- return
- }
- // 新增:获取当前选中类型的显示文本
- const getSelectedTypeLabel = () => {
- const currentType = availableTypes.value.find(item => item.value === searchForm.selectedType)
- return currentType?.label || '公租房'
- }
- // 计算打印用的合计数据
- const calculatePrintSummary = () => {
- return filteredTableData.value.reduce((summary, item) => {
- return {
- rent: summary.rent + (Number(item.rent) || 0),
- deposit: summary.deposit + (Number(item.deposit) || 0),
- propertyFee: summary.propertyFee + (Number(item.propertyFee) || 0)
- }
- }, { rent: 0, deposit: 0, propertyFee: 0 })
- }
- const printSummary = calculatePrintSummary()
- // 构建打印表格HTML
- const buildPrintTable = () => {
- // 定义表头和列宽
- let headers = showMonthDetailColumns.value
- ? [
- { label: '序号', width: '50px' },
- { label: '房间信息', width: '120px' },
- { label: '退款时间', width: '150px' },
- { label: '租金(元)', width: '120px' },
- { label: '押金(元)', width: '120px' },
- { label: '物业费(元)', width: '120px' },
- { label: '经手人', width: '60px' }
- ]
- : [
- { label: '序号', width: '50px' },
- { label: '退款时间', width: '150px' },
- { label: '租金(元)', width: '120px' },
- { label: '押金(元)', width: '120px' },
- { label: '物业费(元)', width: '120px' }
- ]
- // 构建表头行
- const headerHtml = headers.map(h =>
- `<th style="width: ${h.width}; text-align: center; padding: 8px; border: 1px solid #000; background: #f0f0f0;">${h.label}</th>`
- ).join('')
- // 构建数据行
- const rowsHtml = filteredTableData.value.map((item, index) => {
- const cells = showMonthDetailColumns.value
- ? [
- `<td style="width: 50px; text-align: center; padding: 8px; border: 1px solid #000;">${index + 1}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${item.tenant || '-'}</td>`,
- `<td style="width: 150px; text-align: center; padding: 8px; border: 1px solid #000;">${item.refundTime || '-'}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${(item.rent || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${(item.deposit || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${(item.propertyFee || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 60px; text-align: center; padding: 8px; border: 1px solid #000;">${item.operator || '-'}</td>`
- ]
- : [
- `<td style="width: 50px; text-align: center; padding: 8px; border: 1px solid #000;">${index + 1}</td>`,
- `<td style="width: 150px; text-align: center; padding: 8px; border: 1px solid #000;">${item.refundTime || '-'}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${(item.rent || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${(item.deposit || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000;">${(item.propertyFee || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`
- ]
- return `<tr>${cells.join('')}</tr>`
- }).join('')
- // 构建合计行
- const summaryCells = showMonthDetailColumns.value
- ? [
- `<td style="width: 50px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">合计</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; background: #f0f0f0;"></td>`,
- `<td style="width: 150px; text-align: center; padding: 8px; border: 1px solid #000; background: #f0f0f0;"></td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">${printSummary.rent.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">${printSummary.deposit.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">${printSummary.propertyFee.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 60px; text-align: center; padding: 8px; border: 1px solid #000; background: #f0f0f0;"></td>`
- ]
- : [
- `<td style="width: 50px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">合计</td>`,
- `<td style="width: 150px; text-align: center; padding: 8px; border: 1px solid #000; background: #f0f0f0;"></td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">${printSummary.rent.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">${printSummary.deposit.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`,
- `<td style="width: 120px; text-align: center; padding: 8px; border: 1px solid #000; font-weight: bold; background: #f0f0f0;">${printSummary.propertyFee.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}</td>`
- ]
- const summaryHtml = `<tr>${summaryCells.join('')}</tr>`
- // 完整表格
- return `
- <table style="width: 100%; border-collapse: collapse; font-family: 'Microsoft YaHei', sans-serif;">
- <thead><tr>${headerHtml}</tr></thead>
- <tbody>
- ${rowsHtml}
- ${summaryHtml}
- </tbody>
- </table>
- `
- }
- // 构建打印页面内容(修改:标题中加入选中的类型)
- const printContent = `
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>${showMonthDetailColumns.value ? '退款详情' : '退款汇总'}-${searchForm.selectedYear}年${searchForm.selectedMonth ? searchForm.selectedMonth + '月' : ''}-${getSelectedTypeLabel()}</title>
- <style>
- body {
- padding: 20px;
- font-family: 'Microsoft YaHei', sans-serif;
- font-size: 12px;
- }
- h1 {
- text-align: center;
- font-size: 16px;
- margin-bottom: 20px;
- }
- @media print {
- @page {
- margin: 15mm;
- }
- }
- </style>
- </head>
- <body>
- <h1>${showMonthDetailColumns.value ? '退款详情数据' : '退款汇总数据'} - ${searchForm.selectedYear}年${searchForm.selectedMonth ? searchForm.selectedMonth + '月' : ''} - ${getSelectedTypeLabel()}</h1>
- ${buildPrintTable()}
- </body>
- </html>
- `
- printWindow.document.write(printContent)
- printWindow.document.close()
- // 执行打印
- setTimeout(() => {
- printWindow.focus()
- printWindow.print()
- setTimeout(() => printWindow.close(), 500)
- }, 200)
- } catch (error) {
- ElMessage.error('打印失败:' + (error as Error).message)
- console.error('打印错误:', error)
- }
- }
- // 初始化
- onMounted(() => {
- // 默认选中当前年份
- searchForm.selectedYear = new Date().getFullYear().toString()
- fetchRefundData(`${API_BASE_URL}/year`)
- })
- </script>
- <style scoped>
- .dashboard-container {
- margin: 20px auto;
- padding: 0 20px;
- font-family: 'Microsoft YaHei', sans-serif;
- }
- .filter-panel {
- display: flex;
- align-items: center;
- gap: 20px;
- padding: 16px 20px;
- background: #f8f9fa;
- border-radius: 8px;
- margin-bottom: 20px;
- flex-wrap: wrap;
- }
- .filter-item {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .filter-label {
- font-size: 14px;
- color: #333;
- font-weight: 500;
- white-space: nowrap;
- }
- .table-container {
- background: #fff;
- padding: 20px;
- border-radius: 8px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
- }
- /* 响应式适配 */
- @media (max-width: 768px) {
- .filter-panel {
- flex-direction: column;
- align-items: flex-start;
- }
- }
- /* 表格样式优化 */
- :deep(.el-table__cell) {
- padding: 12px 0;
- }
- :deep(.el-select.is-disabled) {
- opacity: 0.7;
- }
- :deep(.el-button.is-disabled) {
- cursor: not-allowed !important;
- }
- /* 合计行样式增强 */
- :deep(.el-table__footer-wrapper) {
- font-weight: bold !important;
- }
- :deep(.el-table__footer-wrapper .el-table__cell) {
- background-color: #f0f0f0 !important;
- color: #333 !important;
- font-size: 14px !important;
- }
- :deep(.el-table__footer) {
- border-top: 2px solid #e6e6e6 !important;
- }
- </style>
|