汇总近期业务改动:催缴菜单与后端、样本信息快捷登记与进度、运费关联、客户档案扩展、协议优惠/期次与押金划转,并同步 SQL 迁移与管理端页面收敛。 Co-authored-by: Cursor <cursoragent@cursor.com>
965 lines
31 KiB
HTML
965 lines
31 KiB
HTML
<!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>
|