From 2beedc22af58eda923c7745a80bb3482d80bb785 Mon Sep 17 00:00:00 2001 From: xiong Date: Thu, 16 Jul 2026 11:25:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E8=B4=A6=E5=8D=95pdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/src/pages/Bills/bill-print.ts | 110 +++++++++++++++++++++++ apps/admin/src/pages/Bills/index.tsx | 22 ++++- 2 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 apps/admin/src/pages/Bills/bill-print.ts diff --git a/apps/admin/src/pages/Bills/bill-print.ts b/apps/admin/src/pages/Bills/bill-print.ts new file mode 100644 index 0000000..a4cff31 --- /dev/null +++ b/apps/admin/src/pages/Bills/bill-print.ts @@ -0,0 +1,110 @@ +interface BillPrintItem { + expenseType?: string | null; + description?: string | null; + days?: number | null; + totalRoomDays?: number | null; + studentAmount?: number | string | null; +} + +export interface BillPrintData { + id: number; + student?: { name?: string | null } | null; + periodStart?: string | null; + periodEnd?: string | null; + status?: string | null; + sharedAmount?: number | string | null; + personalAmount?: number | string | null; + totalAmount?: number | string | null; + paidAmount?: number | string | null; + outstandingAmount?: number | string | null; + items?: BillPrintItem[] | null; +} + +const statusLabels: Record = { + unpaid: '待支付', + partially_paid: '部分支付', + paid: '已结清', + cancelled: '已取消', +}; + +const escapeHtml = (value: unknown) => + String(value ?? '') + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + +const money = (value: unknown) => `¥${Number(value || 0).toFixed(2)}`; + +export const buildBillPrintHtml = (bill: BillPrintData) => { + const rows = (bill.items ?? []) + .map( + (item) => ` + + ${escapeHtml(item.expenseType || '')} + ${escapeHtml(item.description || '')} + ${Number(item.days || 0)} + ${Number(item.totalRoomDays || 0)} + ${Number(item.studentAmount || 0).toFixed(2)} + `, + ) + .join(''); + + return ` + + + + 账单_${bill.id} + + + +

恭学教育基地水电费账单

+
生成时间: ${escapeHtml(new Date().toLocaleString('zh-CN'))}
+

学生姓名: ${escapeHtml(bill.student?.name || '-')}

+

计费周期: ${escapeHtml(bill.periodStart || '-')} ~ ${escapeHtml(bill.periodEnd || '-')}

+

账单状态: ${escapeHtml(statusLabels[bill.status || ''] || bill.status || '-')}

+ +

费用汇总

+

分摊费用: ${money(bill.sharedAmount)}

+

个人费用: ${money(bill.personalAmount)}

+

应付总额: ${money(bill.totalAmount)}

+ +

待补缴: ${money(bill.outstandingAmount)}

+ +

费用明细

+ + + ${rows || ''} +
费用类型说明天数总人天金额(元)
暂无费用明细
+ + + +`; +}; diff --git a/apps/admin/src/pages/Bills/index.tsx b/apps/admin/src/pages/Bills/index.tsx index a6bc550..7eb1a69 100644 --- a/apps/admin/src/pages/Bills/index.tsx +++ b/apps/admin/src/pages/Bills/index.tsx @@ -24,6 +24,7 @@ import api from '../../api'; import PermissionButton from '../../components/PermissionButton'; import { downloadBlob } from '../../utils/download'; import { message } from '../../ui/app-message'; +import { buildBillPrintHtml, type BillPrintData } from './bill-print'; const statusMap: Record = { @@ -165,10 +166,23 @@ const BillsPage: React.FC = () => { ); }; - const handleExportPdf = (billId: number) => { - downloadBlob(`/bills/export/pdf/${billId}`, `账单_${billId}.pdf`).catch(() => - message.error('导出失败'), - ); + const handleExportPdf = async (billId: number) => { + const printWindow = window.open('', '_blank'); + if (!printWindow) { + message.error('浏览器阻止了打印窗口,请允许弹出窗口后重试'); + return; + } + + printWindow.document.write('

正在加载账单...

'); + try { + const bill = await api.get(`/bills/${billId}`); + printWindow.document.open(); + printWindow.document.write(buildBillPrintHtml(bill)); + printWindow.document.close(); + } catch (error: any) { + printWindow.close(); + message.error(error?.message || '账单加载失败'); + } }; const columns = useMemo(() => [