forked from wangziqi/gongxue-base
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
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>`;
|
|
};
|