|
|
@@ -0,0 +1,817 @@
|
|
|
+<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"
|
|
|
+ :disabled-text="!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="receiptCode"
|
|
|
+ label="收据号"
|
|
|
+ align="center"
|
|
|
+ width="150"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 3. 房间信息(仅详情显示) -->
|
|
|
+ <el-table-column
|
|
|
+ v-if="showMonthDetailColumns"
|
|
|
+ prop="payer"
|
|
|
+ label="房间信息"
|
|
|
+ align="center"
|
|
|
+ width="120"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 4. 付款时间(始终显示) -->
|
|
|
+ <el-table-column prop="receiptTime" label="付款时间" align="center"/>
|
|
|
+
|
|
|
+ <!-- 5. 租金(始终显示) -->
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <!-- 6. 押金(始终显示) -->
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <!-- 7. 水费(始终显示) -->
|
|
|
+ <el-table-column prop="waterFee" label="水费(元)" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.waterFee?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00' }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <!-- 8. 电费(始终显示) -->
|
|
|
+ <el-table-column prop="electricityBill" label="电费(元)" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ {{
|
|
|
+ scope.row.electricityBill?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'
|
|
|
+ }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <!-- 9. 物业费(始终显示) -->
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <!-- 10. 收取时段(仅详情显示) -->
|
|
|
+ <el-table-column
|
|
|
+ v-if="showMonthDetailColumns"
|
|
|
+ prop="rentPeriod"
|
|
|
+ label="收取时段"
|
|
|
+ align="center"
|
|
|
+ width="200"
|
|
|
+ />
|
|
|
+ <!-- 11. 经手人(仅详情显示) -->
|
|
|
+ <el-table-column
|
|
|
+ v-if="showMonthDetailColumns"
|
|
|
+ prop="operator"
|
|
|
+ label="经手人"
|
|
|
+ align="center"
|
|
|
+ width="60"
|
|
|
+ />
|
|
|
+ <!-- 12. 付款方式(仅详情显示) -->
|
|
|
+ <el-table-column
|
|
|
+ v-if="showMonthDetailColumns"
|
|
|
+ prop="payType"
|
|
|
+ label="付款方式"
|
|
|
+ align="center"
|
|
|
+ width="70"
|
|
|
+ />
|
|
|
+ </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 BaseResponse {
|
|
|
+ code: number
|
|
|
+ msg: string
|
|
|
+}
|
|
|
+
|
|
|
+// 扩展收据数据类型接口(匹配页面显示的月详情字段)
|
|
|
+interface ReceiptData {
|
|
|
+ deposit: number // 押金
|
|
|
+ electricityBill: number // 电费
|
|
|
+ propertyFee: number // 物业费
|
|
|
+ receiptTime: string // 付款时间(原收款时间)
|
|
|
+ rent: number // 租金
|
|
|
+ waterFee: number // 水费
|
|
|
+ // 月详情专属字段(和页面表格列一一对应)
|
|
|
+ receiptCode?: string // 收据号
|
|
|
+ operator?: string // 经手人
|
|
|
+ payType?: string // 付款方式
|
|
|
+ payer?: string // 房间信息
|
|
|
+ rentPeriod?: string // 收取时段
|
|
|
+}
|
|
|
+
|
|
|
+// 新增:类型选择器选项类型
|
|
|
+interface TypeOption {
|
|
|
+ label: string // 显示文本
|
|
|
+ value: string | number // 传递值
|
|
|
+}
|
|
|
+
|
|
|
+// 接口响应类型
|
|
|
+interface ReceiptDataResponse extends BaseResponse {
|
|
|
+ data: ReceiptData[]
|
|
|
+}
|
|
|
+
|
|
|
+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 tableLoading = ref(false)
|
|
|
+
|
|
|
+// 搜索表单(新增selectedType)
|
|
|
+const searchForm = reactive({
|
|
|
+ selectedYear: '', // 选中的年份 YYYY格式
|
|
|
+ selectedMonth: '',
|
|
|
+ selectedType: '公租房' // 新增:默认选中"全部收据"
|
|
|
+})
|
|
|
+
|
|
|
+// 核心优化:控制月详情列是否显示
|
|
|
+const showMonthDetailColumns = ref(false)
|
|
|
+
|
|
|
+// 核心优化:计算属性 - 判断月详情按钮是否启用(年份和月份都必须选中)
|
|
|
+const isMonthDetailEnabled = computed(() => {
|
|
|
+ return !!searchForm.selectedYear && !!searchForm.selectedMonth
|
|
|
+})
|
|
|
+
|
|
|
+// 原始收据数据
|
|
|
+const rawTableData = ref<ReceiptData[]>([])
|
|
|
+
|
|
|
+// 筛选后的表格数据(仅保留年份筛选)
|
|
|
+const filteredTableData = computed(() => {
|
|
|
+ let data = [...rawTableData.value]
|
|
|
+ return data
|
|
|
+})
|
|
|
+
|
|
|
+// 合并后的通用数据获取方法(新增:传递类型参数到接口)
|
|
|
+const fetchReceiptData = async (apiPath: string, isDetail = false) => {
|
|
|
+ tableLoading.value = true
|
|
|
+ try {
|
|
|
+ // 构建请求参数(新增:selectedType)
|
|
|
+ const params = {
|
|
|
+ year: searchForm.selectedYear,
|
|
|
+ month: searchForm.selectedMonth,
|
|
|
+ assetType: searchForm.selectedType // 新增:类型参数,传递到后端接口
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await clientGet<any, ReceiptDataResponse>(
|
|
|
+ apiPath,
|
|
|
+ {params},
|
|
|
+ )
|
|
|
+
|
|
|
+ console.log('收据接口返回数据:', res)
|
|
|
+
|
|
|
+ // 接口返回校验
|
|
|
+ if (!res) {
|
|
|
+ ElMessage.error('接口返回数据为空')
|
|
|
+ rawTableData.value = []
|
|
|
+ if (isDetail) showMonthDetailColumns.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg || '数据获取失败')
|
|
|
+ rawTableData.value = []
|
|
|
+ if (isDetail) showMonthDetailColumns.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 赋值接口返回数据
|
|
|
+ rawTableData.value = res.data || []
|
|
|
+ if (rawTableData.value.length === 0) {
|
|
|
+ ElMessage.warning('暂无收据数据')
|
|
|
+ if (isDetail) showMonthDetailColumns.value = false
|
|
|
+ } else {
|
|
|
+ if (isDetail) showMonthDetailColumns.value = true
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('网络异常,数据获取失败')
|
|
|
+ console.error('获取收据数据失败:', error)
|
|
|
+ rawTableData.value = []
|
|
|
+ if (isDetail) 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
|
|
|
+ // 调用通用方法获取年度数据
|
|
|
+ fetchReceiptData('/AReceiptInfo/receipt/stat/year')
|
|
|
+}
|
|
|
+
|
|
|
+const getSelection = () => {
|
|
|
+ // 新增:校验年份选择
|
|
|
+ if (!searchForm.selectedYear) {
|
|
|
+ ElMessage.warning('请先选择年份')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 搜索时关闭详情列(恢复基础表头)
|
|
|
+ showMonthDetailColumns.value = false
|
|
|
+
|
|
|
+ if (!searchForm.selectedMonth) {
|
|
|
+ console.log('获取当前年份数据')
|
|
|
+ // 调用通用方法获取年度数据
|
|
|
+ fetchReceiptData('/AReceiptInfo/receipt/stat/year')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ console.log('获取指定月份数据')
|
|
|
+ // 调用通用方法获取月度数据
|
|
|
+ fetchReceiptData('/AReceiptInfo/receipt/stat/month')
|
|
|
+}
|
|
|
+
|
|
|
+const getMonthDetail = () => {
|
|
|
+ // 双重保险:即使按钮禁用,仍保留逻辑校验(防止非常规操作)
|
|
|
+ if (!isMonthDetailEnabled.value) {
|
|
|
+ ElMessage.warning('请先选择年份和月份')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('获取指定年月的日维度数据')
|
|
|
+ // 传入isDetail=true标识这是详情请求
|
|
|
+ fetchReceiptData('/AReceiptInfo/receipt/stat/day', true)
|
|
|
+}
|
|
|
+
|
|
|
+// 核心优化:动态合计行计算方法 - 根据展示模式切换合计列索引
|
|
|
+const getSummaries = (param: { columns: any[], data: ReceiptData[] }) => {
|
|
|
+ const { columns, data } = param;
|
|
|
+ const sums = [];
|
|
|
+
|
|
|
+ columns.forEach(() => sums.push(''));
|
|
|
+ sums[0] = '合计'; // 序号列显示"合计"
|
|
|
+
|
|
|
+ // 定义不同模式下的合计配置
|
|
|
+ let summaryIndexs: number[] = [];
|
|
|
+ const summaryFields = ['rent', 'deposit', 'waterFee', 'electricityBill', 'propertyFee'];
|
|
|
+
|
|
|
+ // 判断当前展示模式:
|
|
|
+ // showMonthDetailColumns为true → 月详情模式(索引4,5,6,7,8)
|
|
|
+ // showMonthDetailColumns为false → 年份/月份汇总模式(索引2,3,4,5,6)
|
|
|
+ if (showMonthDetailColumns.value) {
|
|
|
+ // 月详情模式:4=租金、5=押金、6=水费、7=电费、8=物业费
|
|
|
+ summaryIndexs = [4, 5, 6, 7, 8];
|
|
|
+ } else {
|
|
|
+ // 年份/月份汇总模式:2=租金、3=押金、4=水费、5=电费、6=物业费
|
|
|
+ summaryIndexs = [2, 3, 4, 5, 6];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行合计计算
|
|
|
+ summaryIndexs.forEach((index, fieldIndex) => {
|
|
|
+ // 防止索引越界
|
|
|
+ if (index >= columns.length) return;
|
|
|
+
|
|
|
+ const field = summaryFields[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, rowIndex, columnIndex }: {
|
|
|
+ row: any,
|
|
|
+ column: any,
|
|
|
+ rowIndex: number,
|
|
|
+ columnIndex: number
|
|
|
+}) => {
|
|
|
+ // 1. 识别合计行(Element Plus的footer行有type标识)
|
|
|
+ if (row && row.type === 'footer') {
|
|
|
+ // 定义需要显示的数值列
|
|
|
+ const keepProps = ['rent', 'deposit', 'waterFee', 'electricityBill', 'propertyFee'];
|
|
|
+ const prop = column.prop as string;
|
|
|
+
|
|
|
+ // 2. 保留序号列(index=0)和数值列,其他列隐藏
|
|
|
+ if (columnIndex === 0 || (prop && keepProps.includes(prop))) {
|
|
|
+ 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 calculatePrintSummary = () => {
|
|
|
+ const summary = {
|
|
|
+ rent: 0,
|
|
|
+ deposit: 0,
|
|
|
+ waterFee: 0,
|
|
|
+ electricityBill: 0,
|
|
|
+ propertyFee: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ filteredTableData.value.forEach(item => {
|
|
|
+ summary.rent += Number(item.rent) || 0;
|
|
|
+ summary.deposit += Number(item.deposit) || 0;
|
|
|
+ summary.waterFee += Number(item.waterFee) || 0;
|
|
|
+ summary.electricityBill += Number(item.electricityBill) || 0;
|
|
|
+ summary.propertyFee += Number(item.propertyFee) || 0;
|
|
|
+ });
|
|
|
+
|
|
|
+ return summary;
|
|
|
+ };
|
|
|
+
|
|
|
+ const printSummary = calculatePrintSummary();
|
|
|
+
|
|
|
+ // 新增:获取当前选中类型的显示文本
|
|
|
+ const getSelectedTypeLabel = () => {
|
|
|
+ const currentType = availableTypes.value.find(item => item.value === searchForm.selectedType)
|
|
|
+ return currentType?.label || '全部收据'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建标准化的表格HTML(按新列顺序)
|
|
|
+ const buildTableHtml = () => {
|
|
|
+ // 基础表头配置(按新顺序:序号、付款时间、租金、押金、水费、电费、物业费)
|
|
|
+ let headers = [
|
|
|
+ { label: '序号', align: 'center', width: '50px' },
|
|
|
+ { label: '付款时间', align: 'center', width: '120px' },
|
|
|
+ { label: '租金(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '押金(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '水费(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '电费(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '物业费(元)', align: 'center', width: '120px' },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 基础数据单元格模板(基础列)
|
|
|
+ let baseCellsTemplate = (item: ReceiptData, index: number) => `
|
|
|
+ <td style="text-align: center; width: 50px; vertical-align: top;">${index + 1}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.receiptTime || ''}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.rent?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.deposit?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.waterFee?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.electricityBill?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.propertyFee?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 如果显示详情列,插入完整列顺序
|
|
|
+ if (showMonthDetailColumns.value) {
|
|
|
+ headers = [
|
|
|
+ { label: '序号', align: 'center', width: '50px' },
|
|
|
+ { label: '收据号', align: 'center', width: '150px' },
|
|
|
+ { label: '房间信息', align: 'center', width: '120px' },
|
|
|
+ { label: '付款时间', align: 'center', width: '120px' },
|
|
|
+ { label: '租金(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '押金(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '水费(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '电费(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '物业费(元)', align: 'center', width: '120px' },
|
|
|
+ { label: '收取时段', align: 'center', width: '200px' },
|
|
|
+ { label: '经手人', align: 'center', width: '60px' },
|
|
|
+ { label: '付款方式', align: 'center', width: '70px' },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 扩展单元格模板(完整列顺序)
|
|
|
+ baseCellsTemplate = (item: ReceiptData, index: number) => `
|
|
|
+ <td style="text-align: center; width: 50px; vertical-align: top;">${index + 1}</td>
|
|
|
+ <td style="text-align: center; width: 150px; vertical-align: top;">${item.receiptCode || '-'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.payer || '-'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.receiptTime || ''}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.rent?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.deposit?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.waterFee?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.electricityBill?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 120px; vertical-align: top;">${item.propertyFee?.toLocaleString('zh-CN', {minimumFractionDigits: 2}) || '0.00'}</td>
|
|
|
+ <td style="text-align: center; width: 200px; vertical-align: top; word-break: break-all;">${item.rentPeriod || '-'}</td>
|
|
|
+ <td style="text-align: center; width: 60px; vertical-align: top;">${item.operator || '-'}</td>
|
|
|
+ <td style="text-align: center; width: 70px; vertical-align: top;">${item.payType || '-'}</td>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建表头行
|
|
|
+ const headerRow = `
|
|
|
+ <tr>
|
|
|
+ ${headers.map(header => `
|
|
|
+ <th style="text-align: ${header.align}; ${header.width ? `width: ${header.width};` : ''}; vertical-align: middle;">${header.label}</th>
|
|
|
+ `).join('')}
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 构建数据行
|
|
|
+ const dataRows = filteredTableData.value.map((item, index) => `
|
|
|
+ <tr>${baseCellsTemplate(item, index)}</tr>
|
|
|
+ `).join('');
|
|
|
+
|
|
|
+ // 生成合计行(适配动态列顺序)
|
|
|
+ let summaryRow = '';
|
|
|
+ if (printSummary) {
|
|
|
+ let summaryCells = [];
|
|
|
+ if (showMonthDetailColumns.value) {
|
|
|
+ // 月详情模式下的合计行
|
|
|
+ summaryCells = [
|
|
|
+ '<td style="text-align: center; width: 50px; font-weight: bold; vertical-align: middle;">合计</td>',
|
|
|
+ '<td style="text-align: center; width: 150px;"></td>',
|
|
|
+ '<td style="text-align: center; width: 120px;"></td>',
|
|
|
+ '<td style="text-align: center; width: 120px;"></td>',
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.rent.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.deposit.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.waterFee.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.electricityBill.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.propertyFee.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ '<td style="text-align: center; width: 200px;"></td>',
|
|
|
+ '<td style="text-align: center; width: 60px;"></td>',
|
|
|
+ '<td style="text-align: center; width: 70px;"></td>'
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ // 年份/月份汇总模式下的合计行
|
|
|
+ summaryCells = [
|
|
|
+ '<td style="text-align: center; width: 50px; font-weight: bold; vertical-align: middle;">合计</td>',
|
|
|
+ '<td style="text-align: center; width: 120px;"></td>',
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.rent.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.deposit.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.waterFee.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.electricityBill.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ `<td style="text-align: center; width: 120px; font-weight: bold; vertical-align: middle;">${printSummary.propertyFee.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</td>`,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ summaryRow = `
|
|
|
+ <tr style="background-color: #f0f0f0; font-weight: bold;">
|
|
|
+ ${summaryCells.join('')}
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 完整表格HTML
|
|
|
+ return `
|
|
|
+ <table class="print-table">
|
|
|
+ <thead>${headerRow}</thead>
|
|
|
+ <tbody>
|
|
|
+ ${dataRows}
|
|
|
+ ${summaryRow}
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ `;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 构建打印内容(新增:显示选中的类型)
|
|
|
+ printWindow.document.write(`
|
|
|
+ <!DOCTYPE html>
|
|
|
+ <html>
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <title>${showMonthDetailColumns.value ? '收据详情' : '收据汇总'}-${searchForm.selectedYear}年${searchForm.selectedMonth ? searchForm.selectedMonth + '月' : ''}-${getSelectedTypeLabel()}</title>
|
|
|
+ <style>
|
|
|
+ /* 基础样式重置 */
|
|
|
+ * {
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ box-sizing: border-box !important;
|
|
|
+ font-family: 'Microsoft YaHei', sans-serif !important;
|
|
|
+ line-height: 1.4 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ padding: 10px !important;
|
|
|
+ width: 100% !important;
|
|
|
+ height: 100% !important;
|
|
|
+ font-size: 12px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 标题样式 */
|
|
|
+ .print-title {
|
|
|
+ text-align: center !important;
|
|
|
+ font-size: 18px !important;
|
|
|
+ font-weight: bold !important;
|
|
|
+ margin-bottom: 15px !important;
|
|
|
+ width: 100% !important;
|
|
|
+ page-break-after: avoid !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 核心:标准化打印表格 */
|
|
|
+ .print-table {
|
|
|
+ width: 100% !important;
|
|
|
+ border-collapse: collapse !important;
|
|
|
+ border-spacing: 0 !important;
|
|
|
+ table-layout: fixed !important;
|
|
|
+ page-break-inside: auto !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .print-table th {
|
|
|
+ border: 1px solid #000 !important;
|
|
|
+ padding: 8px !important;
|
|
|
+ vertical-align: middle !important;
|
|
|
+ font-size: 12px !important;
|
|
|
+ font-weight: bold !important;
|
|
|
+ background-color: #f0f0f0 !important;
|
|
|
+ white-space: nowrap !important;
|
|
|
+ text-align: center !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .print-table td {
|
|
|
+ border: 1px solid #000 !important;
|
|
|
+ padding: 8px !important;
|
|
|
+ vertical-align: top !important;
|
|
|
+ font-size: 12px !important;
|
|
|
+ white-space: normal !important;
|
|
|
+ word-wrap: break-word !important;
|
|
|
+ word-break: break-all !important;
|
|
|
+ text-align: center !important;
|
|
|
+ min-height: 30px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 合计行样式 */
|
|
|
+ .print-table tr:last-child td {
|
|
|
+ vertical-align: middle !important;
|
|
|
+ background-color: #f0f0f0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 打印核心样式 */
|
|
|
+ @media print {
|
|
|
+ @page {
|
|
|
+ margin: 5mm !important;
|
|
|
+ size: auto !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .print-table tr {
|
|
|
+ page-break-inside: avoid !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .print-table td {
|
|
|
+ white-space: normal !important;
|
|
|
+ word-wrap: break-word !important;
|
|
|
+ word-break: break-all !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ <div class="print-container">
|
|
|
+ <div class="print-title">
|
|
|
+ ${showMonthDetailColumns.value ? '收据详情数据' : '收据汇总数据'} - ${searchForm.selectedYear}年${searchForm.selectedMonth ? searchForm.selectedMonth + '月' : ''} - ${getSelectedTypeLabel()}
|
|
|
+ </div>
|
|
|
+ ${buildTableHtml()}
|
|
|
+ </div>
|
|
|
+ </body>
|
|
|
+ </html>
|
|
|
+ `);
|
|
|
+
|
|
|
+ // 完成文档写入
|
|
|
+ printWindow.document.close()
|
|
|
+
|
|
|
+ // 优化打印执行逻辑
|
|
|
+ const executePrint = () => {
|
|
|
+ try {
|
|
|
+ printWindow.focus()
|
|
|
+ setTimeout(() => {
|
|
|
+ printWindow.print()
|
|
|
+ setTimeout(() => {
|
|
|
+ printWindow.close()
|
|
|
+ }, 500)
|
|
|
+ }, 200)
|
|
|
+ } catch (e) {
|
|
|
+ ElMessage.error('打印执行失败:' + (e as Error).message)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 兼容不同浏览器的加载状态
|
|
|
+ if (printWindow.document.readyState === 'complete') {
|
|
|
+ executePrint()
|
|
|
+ } else {
|
|
|
+ printWindow.addEventListener('load', executePrint)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('打印失败:' + (error as Error).message)
|
|
|
+ console.error('打印错误:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化
|
|
|
+onMounted(() => {
|
|
|
+ // 默认选中当前年
|
|
|
+ const currentYear = new Date().getFullYear().toString()
|
|
|
+ searchForm.selectedYear = currentYear
|
|
|
+ // 调用通用方法获取年度数据
|
|
|
+ fetchReceiptData('/AReceiptInfo/receipt/stat/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>
|