Files
zkxw/docs/demos/2026-10-08-payment-info-excel-list-demo.html
T
zlwuandCursor db31990ebe feat: 细胞存储缴费催缴、统一样本信息与协议支付能力增强
汇总近期业务改动:催缴菜单与后端、样本信息快捷登记与进度、运费关联、客户档案扩展、协议优惠/期次与押金划转,并同步 SQL 迁移与管理端页面收敛。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:20:37 +08:00

965 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>缴费信息 · 单列表(Excel 表头)Demo</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--bg-page: #f8fafc;
--panel: #ffffff;
--text: #0f172a;
--text-secondary: #334155;
--text-muted: #64748b;
--border: #d9e8e2;
--table-head: #f8fafc;
--row-alt: #fafafa;
--row-hover: #f1f5f9;
--radius: 10px;
--radius-sm: 8px;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
--warning: #d97706;
--warning-bg: #fffbeb;
--danger: #dc2626;
--p1: #dbeafe;
--p2: #ffedd5;
--p3: #fef9c3;
--p4: #dcfce7;
--p5: #ffedd5;
--p6: #d1fae5;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg-page);
}
.page {
max-width: 100%;
margin: 0 auto;
padding: 24px 20px 80px;
}
.hero {
max-width: 960px;
margin-bottom: 20px;
}
.hero h1 {
margin: 0 0 8px;
font-size: 22px;
font-weight: 700;
letter-spacing: -0.02em;
}
.hero h1::before {
content: "";
display: inline-block;
width: 4px;
height: 1.1em;
margin-right: 10px;
vertical-align: -0.15em;
border-radius: 2px;
background: var(--primary);
}
.hero p {
margin: 0 0 10px;
color: var(--text-muted);
}
.context-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.pill {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
background: var(--primary-bg);
color: var(--primary-dark);
border: 1px solid #a7f3d0;
}
.pill.warn {
background: var(--warning-bg);
color: var(--warning);
border-color: rgba(217, 119, 6, 0.25);
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px 16px;
margin: 16px 0 0;
font-size: 12px;
color: var(--text-muted);
}
.legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.swatch {
width: 14px;
height: 14px;
border-radius: 3px;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
}
.panel-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
background: #fff;
}
.panel-top h2 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.panel-top h2::before {
content: "";
display: inline-block;
width: 3px;
height: 1em;
margin-right: 8px;
vertical-align: -0.1em;
border-radius: 2px;
background: var(--primary);
}
.hint {
font-size: 12px;
color: var(--text-muted);
}
.filter-block {
padding: 14px 18px 8px;
border-bottom: 1px solid var(--border);
}
.filter-grid {
display: grid;
grid-template-columns: repeat(4, minmax(160px, 1fr));
gap: 12px;
}
.filter-item label {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 4px;
}
.filter-item input,
.filter-item select {
width: 100%;
height: 36px;
padding: 0 10px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: #fff;
font: inherit;
color: var(--text);
}
.filter-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 12px;
padding-bottom: 6px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 14px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: #fff;
color: var(--text-secondary);
font: inherit;
font-weight: 500;
cursor: pointer;
}
.btn:hover { background: var(--row-hover); }
.btn-main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.btn-main:hover { background: var(--primary-dark); }
.table-scroll {
overflow: auto;
max-height: calc(100vh - 280px);
}
table.list-table {
border-collapse: separate;
border-spacing: 0;
width: max-content;
min-width: 100%;
font-size: 13px;
}
.list-table th,
.list-table td {
border-bottom: 1px solid var(--border);
border-right: 1px solid #eef2f0;
padding: 8px 10px;
text-align: center;
white-space: nowrap;
vertical-align: middle;
}
.list-table th {
position: sticky;
top: 0;
z-index: 3;
background: var(--table-head);
color: var(--text-muted);
font-weight: 600;
font-size: 12px;
}
.list-table thead tr.group-row th {
top: 0;
z-index: 4;
font-size: 12px;
letter-spacing: 0.02em;
}
.list-table thead tr.sub-row th {
top: 34px;
z-index: 3;
}
.list-table tbody tr:nth-child(even) td { background: var(--row-alt); }
.list-table tbody tr:hover td { background: var(--row-hover); }
.sticky-left {
position: sticky;
left: 0;
z-index: 2;
background: #fff !important;
box-shadow: 2px 0 0 rgba(15, 23, 42, 0.04);
}
.list-table tbody tr:nth-child(even) .sticky-left { background: var(--row-alt) !important; }
.list-table tbody tr:hover .sticky-left { background: var(--row-hover) !important; }
.list-table thead .sticky-left {
z-index: 5;
background: var(--table-head) !important;
}
.g-base { background: #f1f5f9 !important; color: var(--text-secondary); }
.g-sum { background: #ecfdf5 !important; color: var(--primary-dark); }
.g-p1 { background: var(--p1) !important; color: #1e40af; }
.g-p2 { background: var(--p2) !important; color: #9a3412; }
.g-p3 { background: var(--p3) !important; color: #854d0e; }
.g-p4 { background: var(--p4) !important; color: #166534; }
.g-p5 { background: var(--p5) !important; color: #9a3412; }
.g-p6 { background: var(--p6) !important; color: #065f46; }
.sub-p1 { background: #eff6ff !important; }
.sub-p2 { background: #fff7ed !important; }
.sub-p3 { background: #fefce8 !important; }
.sub-p4 { background: #f0fdf4 !important; }
.sub-p5 { background: #fff7ed !important; }
.sub-p6 { background: #ecfdf5 !important; }
.empty-period {
color: #cbd5e1;
}
.amount-link {
color: var(--primary-dark);
font-weight: 600;
text-decoration: none;
border-bottom: 1px dashed rgba(5, 150, 105, 0.45);
cursor: pointer;
}
.amount-link:hover {
color: var(--primary);
border-bottom-color: var(--primary);
}
.tag {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
}
.tag-success { background: var(--primary-bg); color: var(--primary-dark); }
.tag-warning { background: var(--warning-bg); color: var(--warning); }
.tag-info { background: #f1f5f9; color: #64748b; }
.tag-primary { background: #eff6ff; color: #1d4ed8; }
.tag-danger { background: #fef2f2; color: var(--danger); }
.muted { color: var(--text-muted); }
.customer-cell { text-align: left; min-width: 100px; }
.customer-cell .sub { font-size: 12px; color: var(--text-muted); }
.pager {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
padding: 12px 18px;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 13px;
}
.notes {
max-width: 960px;
margin-top: 20px;
padding: 16px 18px;
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius);
}
.notes h3 {
margin: 0 0 8px;
font-size: 14px;
}
.notes ul {
margin: 0;
padding-left: 18px;
color: var(--text-muted);
font-size: 13px;
}
.notes li { margin: 4px 0; }
/* Modal */
.backdrop {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.35);
display: none;
align-items: center;
justify-content: center;
z-index: 50;
padding: 24px;
backdrop-filter: none;
}
.backdrop.is-open { display: flex; }
.dialog {
width: min(720px, 100%);
max-height: calc(100vh - 48px);
background: #fff;
border-radius: var(--radius);
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.18);
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
}
.dialog-header h3 {
margin: 0;
font-size: 16px;
}
.dialog-close {
border: none;
background: transparent;
font-size: 20px;
line-height: 1;
color: var(--text-muted);
cursor: pointer;
}
.dialog-body {
padding: 16px 18px;
overflow: auto;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 16px;
}
.summary-item {
background: var(--table-head);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 12px;
}
.summary-item label {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 2px;
}
.summary-item strong {
font-size: 14px;
font-weight: 600;
}
.section-title {
margin: 0 0 10px;
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
}
.transfer-card {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 12px;
margin-bottom: 10px;
background: #fff;
}
.transfer-card:last-child { margin-bottom: 0; }
.transfer-top {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 12px;
margin-bottom: 8px;
}
.transfer-meta {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 6px 12px;
font-size: 13px;
color: var(--text-secondary);
}
.transfer-meta span { color: var(--text-muted); }
.attach-row {
margin-top: 8px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.attach-chip {
display: inline-flex;
align-items: center;
height: 26px;
padding: 0 10px;
border-radius: 6px;
background: var(--primary-bg);
color: var(--primary-dark);
font-size: 12px;
border: 1px dashed #a7f3d0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 18px;
border-top: 1px solid var(--border);
background: var(--table-head);
}
.audit-timeline {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--border);
font-size: 12px;
color: var(--text-muted);
}
.audit-timeline div { margin: 2px 0; }
</style>
</head>
<body>
<div class="page">
<div class="hero">
<h1>缴费信息 · 单列表 Demo(对齐 Excel 表头)</h1>
<p>
去掉「已缴费 / 确认中 / 成功 / 按样本」四个 Tab,改为一行一样本:左侧基础信息 + 右侧第 1~6 期缴费块。
趸交 / 分改趸仅第 1 期有值;分期填满 1~6 期(无数据期显示 —)。
<strong>点击各期「实缴金额」</strong>打开弹窗,查看该期单笔/多笔转账与财务审核。
</p>
<div class="context-bar">
<span class="pill">一行 = 一个样本缴费方案</span>
<span class="pill">期块固定 6 列组(对齐 Excel)</span>
<span class="pill warn">实缴金额可点击 → 转账明细弹窗</span>
</div>
<div class="legend">
<span class="legend-item"><i class="swatch" style="background:var(--p1)"></i>第1期</span>
<span class="legend-item"><i class="swatch" style="background:var(--p2)"></i>第2期</span>
<span class="legend-item"><i class="swatch" style="background:var(--p3)"></i>第3期</span>
<span class="legend-item"><i class="swatch" style="background:var(--p4)"></i>第4期</span>
<span class="legend-item"><i class="swatch" style="background:var(--p5)"></i>第5期</span>
<span class="legend-item"><i class="swatch" style="background:var(--p6)"></i>第6期</span>
</div>
</div>
<section class="panel">
<div class="panel-top">
<h2>缴费信息</h2>
<div class="hint">横向滚动查看各期 · 姓名列左侧冻结</div>
</div>
<div class="filter-block">
<div class="filter-grid">
<div class="filter-item">
<label>关键词</label>
<input placeholder="姓名 / 手机 / 合同编号" />
</div>
<div class="filter-item">
<label>缴费方式</label>
<select>
<option>全部</option>
<option>趸交</option>
<option>分期</option>
<option>分改趸</option>
</select>
</div>
<div class="filter-item">
<label>到款状态</label>
<select>
<option>全部</option>
<option>待缴费</option>
<option>部分已缴</option>
<option>缴费确认中</option>
<option>已缴清</option>
</select>
</div>
<div class="filter-item">
<label>签署日期</label>
<input placeholder="开始 — 结束" />
</div>
</div>
<div class="filter-actions">
<button type="button" class="btn">重置</button>
<button type="button" class="btn btn-main">搜索</button>
</div>
</div>
<div class="table-scroll">
<table class="list-table" id="mainTable">
<thead>
<tr class="group-row">
<th class="g-base sticky-left" colspan="11">基础信息</th>
<th class="g-p1" colspan="5">第一期</th>
<th class="g-p2" colspan="5">第二期</th>
<th class="g-p3" colspan="5">第三期</th>
<th class="g-p4" colspan="5">第四期</th>
<th class="g-p5" colspan="5">第五期</th>
<th class="g-p6" colspan="5">第六期</th>
<th class="g-sum" colspan="3">汇总</th>
</tr>
<tr class="sub-row">
<th class="sticky-left">序号</th>
<th>姓名</th>
<th>手机号</th>
<th>合同编号</th>
<th>合同签署日期</th>
<th>存储年限</th>
<th>存储项目</th>
<th>合同总额</th>
<th>缴费方式</th>
<th>优惠金额</th>
<th>应收总额</th>
<!-- period headers x6 -->
<th class="sub-p1">应缴金额</th>
<th class="sub-p1">实缴金额</th>
<th class="sub-p1">缴费日期</th>
<th class="sub-p1">支付方式</th>
<th class="sub-p1">财务审核</th>
<th class="sub-p2">应缴金额</th>
<th class="sub-p2">实缴金额</th>
<th class="sub-p2">缴费日期</th>
<th class="sub-p2">支付方式</th>
<th class="sub-p2">财务审核</th>
<th class="sub-p3">应缴金额</th>
<th class="sub-p3">实缴金额</th>
<th class="sub-p3">缴费日期</th>
<th class="sub-p3">支付方式</th>
<th class="sub-p3">财务审核</th>
<th class="sub-p4">应缴金额</th>
<th class="sub-p4">实缴金额</th>
<th class="sub-p4">缴费日期</th>
<th class="sub-p4">支付方式</th>
<th class="sub-p4">财务审核</th>
<th class="sub-p5">应缴金额</th>
<th class="sub-p5">实缴金额</th>
<th class="sub-p5">缴费日期</th>
<th class="sub-p5">支付方式</th>
<th class="sub-p5">财务审核</th>
<th class="sub-p6">应缴金额</th>
<th class="sub-p6">实缴金额</th>
<th class="sub-p6">缴费日期</th>
<th class="sub-p6">支付方式</th>
<th class="sub-p6">财务审核</th>
<th>累计实缴</th>
<th>待缴金额</th>
<th>备注</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pager">共 3 条 · 演示数据</div>
</section>
<div class="notes">
<h3>与现有系统字段映射(Demo 说明)</h3>
<ul>
<li><strong>一行粒度</strong>:样本缴费方案(与现「按样本」一致),不是按转账笔、也不是按协议整单。</li>
<li><strong>趸交 / 分改趸</strong>:仅第 1 期有 schedule;第 2~6 期显示 —。</li>
<li><strong>分期</strong>:按实际期数填 1~N;本 Demo 固定展示 6 列组,未用到的期显示 —(产品若确认最多 6 期则与 Excel 一致)。</li>
<li><strong>期块「实缴金额」</strong>:该期全部转账金额合计(含确认中);点击后弹窗列出每笔转账与 pay_status / 财务审核时间线。</li>
<li><strong>期块「缴费日期 / 支付方式 / 财务审核」</strong>:多笔时 Demo 取「最近一笔成功/确认中」摘要;完整明细以弹窗为准。</li>
<li><strong>原流程三 Tab</strong>:待缴费/确认中/成功可由筛选「到款状态」或行内 Tag 替代,不再分 Tab。</li>
</ul>
</div>
</div>
<div class="backdrop" id="backdrop" role="dialog" aria-modal="true">
<div class="dialog">
<div class="dialog-header">
<h3 id="dlgTitle">第 N 期 · 转账记录</h3>
<button type="button" class="dialog-close" id="dlgClose" aria-label="关闭">×</button>
</div>
<div class="dialog-body" id="dlgBody"></div>
<div class="dialog-footer">
<button type="button" class="btn" id="dlgCloseBtn">关闭</button>
<button type="button" class="btn btn-main" id="dlgConfirmBtn">缴费确认</button>
</div>
</div>
</div>
<script>
const money = (n) =>
n == null || n === '' || Number.isNaN(Number(n))
? '—'
: Number(n).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const payModeTag = (m) => {
if (m === 'dj') return '<span class="tag tag-success">趸交</span>'
if (m === 'fgd') return '<span class="tag tag-warning">分改趸</span>'
if (m === 'fq') return '<span class="tag tag-primary">分期</span>'
return '<span class="tag tag-info">—</span>'
}
const auditTag = (s) => {
const map = {
yjf: ['tag-success', '已通过'],
jfqrz: ['tag-warning', '确认中'],
dfj: ['tag-info', '待缴费'],
partial: ['tag-primary', '部分到账'],
none: ['tag-info', '—']
}
const [cls, label] = map[s] || map.none
return `<span class="tag ${cls}">${label}</span>`
}
/** Demo rows: base + schedules[0..5] each with transfers[] */
const rows = [
{
id: 'r1',
name: '张敏',
phone: '138****6210',
protocolNo: 'XY-2026-0312',
signDate: '2026-03-12',
years: '20年',
project: '脐带血干细胞',
contractAmount: 22880,
payMode: 'dj',
discount: 880,
receivable: 22000,
remark: '趸交一次结清',
schedules: [
{
due: 22000,
paid: 22000,
payDate: '2026-03-15',
payType: '银行转账',
audit: 'yjf',
transfers: [
{
id: 't1',
amount: 22000,
type: '银行转账',
time: '2026-03-15 14:22',
status: 'yjf',
voucher: '回单-0315.pdf',
auditLog: [
'03-15 14:30 提交财务确认',
'03-15 16:02 财务审核通过'
]
}
]
},
null, null, null, null, null
]
},
{
id: 'r2',
name: '李芳',
phone: '159****8831',
protocolNo: 'XY-2026-0418',
signDate: '2026-04-18',
years: '20年',
project: '脐带间充质',
contractAmount: 29800,
payMode: 'fq',
discount: 0,
receivable: 29800,
remark: '六期分期;第1期有两笔转账',
schedules: [
{
due: 5000,
paid: 5000,
payDate: '2026-04-20',
payType: '多笔',
audit: 'yjf',
transfers: [
{
id: 't2a',
amount: 3000,
type: '微信',
time: '2026-04-19 10:11',
status: 'yjf',
voucher: 'wx-0419.jpg',
auditLog: ['04-19 提交', '04-19 财务通过']
},
{
id: 't2b',
amount: 2000,
type: '银行转账',
time: '2026-04-20 09:40',
status: 'yjf',
voucher: '银行回单.pdf',
auditLog: ['04-20 提交', '04-20 财务通过']
}
]
},
{
due: 4960,
paid: 4960,
payDate: '2026-05-18',
payType: '银行转账',
audit: 'yjf',
transfers: [
{
id: 't3',
amount: 4960,
type: '银行转账',
time: '2026-05-18 11:05',
status: 'yjf',
voucher: '回单-0518.pdf',
auditLog: ['05-18 提交', '05-18 财务通过']
}
]
},
{
due: 4960,
paid: 2000,
payDate: '2026-06-18',
payType: 'POS',
audit: 'jfqrz',
transfers: [
{
id: 't4',
amount: 2000,
type: 'POS',
time: '2026-06-18 15:20',
status: 'jfqrz',
voucher: 'pos小票.jpg',
auditLog: ['06-18 16:01 已提交财务,待审核']
}
]
},
{
due: 4960,
paid: 0,
payDate: null,
payType: null,
audit: 'dfj',
transfers: []
},
{
due: 4960,
paid: 0,
payDate: null,
payType: null,
audit: 'dfj',
transfers: []
},
{
due: 4960,
paid: 0,
payDate: null,
payType: null,
audit: 'dfj',
transfers: []
}
]
},
{
id: 'r3',
name: '王丽',
phone: '186****2209',
protocolNo: 'XY-2025-1120',
signDate: '2025-11-20',
years: '20年',
project: '脐带血干细胞',
contractAmount: 26800,
payMode: 'fgd',
discount: 800,
receivable: 26000,
remark: '原分期后改为趸交结清',
schedules: [
{
due: 26000,
paid: 26000,
payDate: '2026-01-08',
payType: '银行转账',
audit: 'yjf',
transfers: [
{
id: 't5',
amount: 10000,
type: '银行转账',
time: '2025-12-01 09:00',
status: 'yjf',
voucher: '分期首付.pdf',
auditLog: ['已通过(历史分期首付)']
},
{
id: 't6',
amount: 16000,
type: '银行转账',
time: '2026-01-08 13:40',
status: 'yjf',
voucher: '分改趸补缴.pdf',
auditLog: ['01-08 提交分改趸补缴', '01-08 财务通过']
}
]
},
null, null, null, null, null
]
}
]
function periodCells(row, sch, periodIndex) {
if (!sch) {
return Array(5)
.fill('<td class="empty-period">—</td>')
.join('')
}
const paidClickable =
(sch.transfers && sch.transfers.length > 0) || Number(sch.paid) > 0
const paidHtml = paidClickable
? `<a class="amount-link" href="javascript:void(0)" data-row="${row.id}" data-period="${periodIndex}">${money(sch.paid)}</a>`
: `<span class="muted">${money(sch.paid)}</span>`
return [
`<td>${money(sch.due)}</td>`,
`<td>${paidHtml}</td>`,
`<td>${sch.payDate || '—'}</td>`,
`<td>${sch.payType || '—'}</td>`,
`<td>${auditTag(sch.audit)}</td>`
].join('')
}
function renderRows() {
const tbody = document.getElementById('tbody')
tbody.innerHTML = rows
.map((row, idx) => {
const paidSum = row.schedules.reduce(
(s, sch) => s + (sch && sch.paid ? Number(sch.paid) : 0),
0
)
const dueRemain = Math.max(0, Number(row.receivable) - paidSum)
const periods = row.schedules
.map((sch, i) => periodCells(row, sch, i))
.join('')
return `<tr>
<td class="sticky-left">${idx + 1}</td>
<td class="customer-cell"><div>${row.name}</div></td>
<td>${row.phone}</td>
<td>${row.protocolNo}</td>
<td>${row.signDate}</td>
<td>${row.years}</td>
<td>${row.project}</td>
<td>${money(row.contractAmount)}</td>
<td>${payModeTag(row.payMode)}</td>
<td>${money(row.discount)}</td>
<td>${money(row.receivable)}</td>
${periods}
<td><strong style="color:var(--primary-dark)">${money(paidSum)}</strong></td>
<td>${money(dueRemain)}</td>
<td class="muted">${row.remark || '—'}</td>
</tr>`
})
.join('')
}
function openDialog(rowId, periodIndex) {
const row = rows.find((r) => r.id === rowId)
if (!row) return
const sch = row.schedules[periodIndex]
if (!sch) return
const title = document.getElementById('dlgTitle')
title.textContent = `${row.name} · ${row.protocolNo} · 第 ${periodIndex + 1} 期转账记录`
const transfers = sch.transfers || []
const body = document.getElementById('dlgBody')
body.innerHTML = `
<div class="summary-grid">
<div class="summary-item"><label>应缴金额</label><strong>${money(sch.due)}</strong></div>
<div class="summary-item"><label>实缴合计</label><strong style="color:var(--primary-dark)">${money(sch.paid)}</strong></div>
<div class="summary-item"><label>欠缴</label><strong>${money(Math.max(0, Number(sch.due) - Number(sch.paid)))}</strong></div>
<div class="summary-item"><label>缴费方式</label><strong>${row.payMode === 'dj' ? '趸交' : row.payMode === 'fgd' ? '分改趸' : '分期'}</strong></div>
<div class="summary-item"><label>本期财务审核</label><strong>${auditTag(sch.audit)}</strong></div>
<div class="summary-item"><label>转账笔数</label><strong>${transfers.length} 笔</strong></div>
</div>
<div class="section-title">转账明细(含财务审核)</div>
${
transfers.length
? transfers
.map(
(tr, i) => `
<div class="transfer-card">
<div class="transfer-top">
<strong>第 ${i + 1} 笔</strong>
${auditTag(tr.status)}
<span style="color:var(--primary-dark);font-weight:600">${money(tr.amount)}</span>
</div>
<div class="transfer-meta">
<div><span>支付方式</span> · ${tr.type}</div>
<div><span>转账时间</span> · ${tr.time}</div>
</div>
<div class="attach-row"><span class="attach-chip">${tr.voucher || '无凭证'}</span></div>
<div class="audit-timeline">
${(tr.auditLog || []).map((line) => `<div>• ${line}</div>`).join('') || '<div>• 暂无审核记录</div>'}
</div>
</div>`
)
.join('')
: '<div class="muted">暂无转账笔。可在此发起「缴费确认」创建第一笔。</div>'
}
`
const confirmBtn = document.getElementById('dlgConfirmBtn')
confirmBtn.style.display =
sch.audit === 'dfj' || (Number(sch.due) > Number(sch.paid) && sch.audit !== 'jfqrz')
? 'inline-flex'
: 'none'
document.getElementById('backdrop').classList.add('is-open')
}
function closeDialog() {
document.getElementById('backdrop').classList.remove('is-open')
}
document.getElementById('tbody').addEventListener('click', (e) => {
const a = e.target.closest('.amount-link')
if (!a) return
openDialog(a.dataset.row, Number(a.dataset.period))
})
document.getElementById('dlgClose').addEventListener('click', closeDialog)
document.getElementById('dlgCloseBtn').addEventListener('click', closeDialog)
document.getElementById('backdrop').addEventListener('click', (e) => {
if (e.target.id === 'backdrop') closeDialog()
})
document.getElementById('dlgConfirmBtn').addEventListener('click', () => {
alert('Demo:此处复用现有 PaymentReceiptConfirmDialog(上传凭证 / 仅保存 / 提交财务确认)')
})
renderRows()
</script>
</body>
</html>