forked from wangziqi/gongxue-base
fix:修复无法导出账单pdf
This commit is contained in:
110
apps/admin/src/pages/Bills/bill-print.ts
Normal file
110
apps/admin/src/pages/Bills/bill-print.ts
Normal file
@@ -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<string, string> = {
|
||||
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) => `
|
||||
<tr>
|
||||
<td>${escapeHtml(item.expenseType || '')}</td>
|
||||
<td>${escapeHtml(item.description || '')}</td>
|
||||
<td>${Number(item.days || 0)}</td>
|
||||
<td>${Number(item.totalRoomDays || 0)}</td>
|
||||
<td>${Number(item.studentAmount || 0).toFixed(2)}</td>
|
||||
</tr>`,
|
||||
)
|
||||
.join('');
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>账单_${bill.id}</title>
|
||||
<style>
|
||||
@page { size: A4; margin: 50pt; }
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
color: #000;
|
||||
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
h1 { margin: 0; font-size: 20px; font-weight: 600; text-align: center; }
|
||||
h2 { margin: 20px 0 6px; font-size: 14px; text-decoration: underline; }
|
||||
p { margin: 2px 0; }
|
||||
.generated { margin: 6px 0 18px; color: #666; font-size: 10px; text-align: center; }
|
||||
.total { color: #007aff; font-size: 14px; font-weight: 600; }
|
||||
.paid { color: #389e0d; font-size: 11px; }
|
||||
.outstanding { color: ${Number(bill.outstandingAmount || 0) > 0 ? '#ff3b30' : '#389e0d'}; font-size: 14px; font-weight: 600; }
|
||||
table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 9px; }
|
||||
th, td { padding: 4px 0; text-align: left; vertical-align: top; word-break: break-word; }
|
||||
th { color: #333; font-size: 10px; font-weight: 400; border-bottom: 1px solid #ccc; }
|
||||
th:nth-child(1) { width: 24%; }
|
||||
th:nth-child(2) { width: 36%; }
|
||||
th:nth-child(3), th:nth-child(4) { width: 12%; }
|
||||
th:nth-child(5) { width: 16%; }
|
||||
.empty { padding: 12px 0; color: #999; text-align: center; }
|
||||
.footer { margin-top: 34px; color: #999; font-size: 8px; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>恭学教育基地水电费账单</h1>
|
||||
<div class="generated">生成时间: ${escapeHtml(new Date().toLocaleString('zh-CN'))}</div>
|
||||
<p>学生姓名: ${escapeHtml(bill.student?.name || '-')}</p>
|
||||
<p>计费周期: ${escapeHtml(bill.periodStart || '-')} ~ ${escapeHtml(bill.periodEnd || '-')}</p>
|
||||
<p>账单状态: ${escapeHtml(statusLabels[bill.status || ''] || bill.status || '-')}</p>
|
||||
|
||||
<h2>费用汇总</h2>
|
||||
<p>分摊费用: ${money(bill.sharedAmount)}</p>
|
||||
<p>个人费用: ${money(bill.personalAmount)}</p>
|
||||
<p class="total">应付总额: ${money(bill.totalAmount)}</p>
|
||||
<p class="paid">已扣余额: ${money(bill.paidAmount)}</p>
|
||||
<p class="outstanding">待补缴: ${money(bill.outstandingAmount)}</p>
|
||||
|
||||
<h2>费用明细</h2>
|
||||
<table>
|
||||
<thead><tr><th>费用类型</th><th>说明</th><th>天数</th><th>总人天</th><th>金额(元)</th></tr></thead>
|
||||
<tbody>${rows || '<tr><td class="empty" colspan="5">暂无费用明细</td></tr>'}</tbody>
|
||||
</table>
|
||||
<div class="footer">本账单由恭学教育基地管理系统自动生成</div>
|
||||
<script>window.addEventListener('load', function () { setTimeout(function () { window.print(); }, 100); });</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
@@ -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<string, { text: string; color: string }> = {
|
||||
@@ -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('<p style="font-family:sans-serif;padding:24px">正在加载账单...</p>');
|
||||
try {
|
||||
const bill = await api.get<BillPrintData>(`/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(() => [
|
||||
|
||||
Reference in New Issue
Block a user