All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
页面 (电商AI平台/) - account / team / settings / index / products / projects: 累积迭代 - restraint.css: 设计 token 补充 - login.html / register.html: 新增登录注册页 - _ARCHIVE.md: 归档说明 Next.js 工程骨架 - app/ + components/: 新一代 SPA 雏形 (page / layout / sidebar / topbar / GridBg / Icon) - package.json / package-lock.json / next.config.mjs / tsconfig.json / postcss.config.mjs / next-env.d.ts 历史归档 / 文档 - v1/: 原 V1 静态稿镜像 (含 mockup-A/B/C) - PRD.md / deployment-guide.md / _check.html - ui参考/ / screenshots/ 杂项 - .gitignore 调整 - 删除根 README.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
613 lines
40 KiB
HTML
613 lines
40 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>消费 · 流·Studio</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/restraint.css?v=202605211643">
|
||
<style>
|
||
/* ─── 顶部:左右布局(余额 banner + 快速充值)─── */
|
||
.top-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 16px; margin-bottom: 22px; align-items: stretch; }
|
||
@media (max-width: 980px) { .top-grid { grid-template-columns: 1fr; } }
|
||
|
||
.balance-banner {
|
||
background: var(--accent-black);
|
||
color: var(--accent-white);
|
||
padding: 24px 28px;
|
||
position: relative;
|
||
border: 1px solid var(--accent-black);
|
||
border-radius: var(--r-md);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 22px;
|
||
min-width: 0;
|
||
}
|
||
.balance-banner::before, .balance-banner::after,
|
||
.balance-banner > .corner-tr, .balance-banner > .corner-bl {
|
||
content: ''; position: absolute; width: 14px; height: 14px;
|
||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23e8e8e8'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center;
|
||
background-size: contain; pointer-events: none;
|
||
}
|
||
.balance-banner::before { top: -7px; left: -7px; }
|
||
.balance-banner::after { bottom: -7px; right: -7px; }
|
||
.balance-banner > .corner-tr { top: -7px; right: -7px; }
|
||
.balance-banner > .corner-bl { bottom: -7px; left: -7px; }
|
||
|
||
/* 主余额 · 突出展示 */
|
||
.balance-hero { display: flex; flex-direction: column; gap: 4px; }
|
||
.balance-hero .lbl { font-family: var(--font-mono); font-size: 10.5px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; }
|
||
.balance-hero .v { font-size: 38px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
|
||
.balance-hero .meta { font-size: 11.5px; color: rgba(255,255,255,.5); font-family: var(--font-mono); letter-spacing: .02em; margin-top: 4px; }
|
||
|
||
/* 子统计 · 月限额 / 已用 */
|
||
.balance-sub { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; padding: 14px 0 0; border-top: 1px solid rgba(255,255,255,.1); }
|
||
.balance-sub .col { min-width: 0; }
|
||
.balance-sub .lbl { font-family: var(--font-mono); font-size: 10px; color: rgba(255,255,255,.5); letter-spacing: .06em; text-transform: uppercase; }
|
||
.balance-sub .v { font-size: 18px; font-weight: 700; letter-spacing: -.01em; margin-top: 4px; font-variant-numeric: tabular-nums; }
|
||
.balance-sub .meta { font-size: 10.5px; color: rgba(255,255,255,.42); margin-top: 3px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
|
||
.balance-actions { display: flex; gap: 8px; margin-top: auto; }
|
||
.balance-actions .btn { background: var(--accent-white); color: var(--accent-black); border-color: var(--accent-white); flex: 1; }
|
||
.balance-actions .btn:hover { background: var(--background-base); }
|
||
.balance-actions .btn-ghost { background: transparent; color: var(--accent-white); border: 1px solid rgba(255,255,255,.25); flex: 1; }
|
||
.balance-actions .btn-ghost:hover { background: rgba(255,255,255,.1); color: var(--accent-white); }
|
||
|
||
/* ─── 快速充值 pane(右栏)─── */
|
||
.pane { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 20px; margin-bottom: 16px; }
|
||
.pane h3 { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
|
||
.pane .desc { font-size: 11.5px; color: var(--black-alpha-48); margin-bottom: 14px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
.topup-pane { display: flex; flex-direction: column; padding: 20px 22px; margin-bottom: 0; }
|
||
|
||
.recharge-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||
.recharge-card { border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 14px 16px; text-align: center; cursor: pointer; background: var(--surface); position: relative; transition: background var(--t-base), border-color var(--t-base); }
|
||
.recharge-card:hover { background: var(--background-lighter); }
|
||
.recharge-card.selected { border-color: var(--heat); background: var(--heat-12); }
|
||
.recharge-card.selected::before, .recharge-card.selected::after { content: ''; position: absolute; width: 14px; height: 14px; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23FA5D19'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center; background-size: contain; pointer-events: none; }
|
||
.recharge-card.selected::before { top: -7px; left: -7px; }
|
||
.recharge-card.selected::after { bottom: -7px; right: -7px; }
|
||
.recharge-card .amt { font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
||
.recharge-card .gift { font-size: 10.5px; color: var(--black-alpha-48); margin-top: 2px; font-family: var(--font-mono); }
|
||
.recharge-card .gift.bonus { color: var(--accent-forest); font-weight: 600; }
|
||
.recharge-card .ribbon { position: absolute; top: -8px; right: 8px; font-family: var(--font-mono); font-size: 9.5px; padding: 1px 6px; background: var(--heat); color: var(--accent-white); letter-spacing: .04em; font-weight: 600; border-radius: var(--r-sm); }
|
||
.pay-row { display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 14px; }
|
||
.pay-row .input { width: 100%; box-sizing: border-box; height: 38px; }
|
||
.pay-btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||
.pay-btn-row .btn { width: 100%; }
|
||
|
||
/* ─── Tab strip ─── */
|
||
.billing-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border-faint); margin: 26px 0 18px; padding: 0 2px; }
|
||
.billing-tabs .tab { background: none; border: 0; padding: 10px 16px 11px; font-size: 13px; color: var(--black-alpha-56); font-family: inherit; cursor: pointer; border-bottom: 2px solid transparent; position: relative; top: 1px; letter-spacing: .01em; transition: color var(--t-base); }
|
||
.billing-tabs .tab:hover { color: var(--accent-black); }
|
||
.billing-tabs .tab.active { color: var(--accent-black); font-weight: 600; border-bottom-color: var(--heat); }
|
||
.billing-tabs .tab .mono { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-32); margin-left: 4px; letter-spacing: .02em; font-weight: 400; }
|
||
.billing-tabs .tab.active .mono { color: var(--heat); }
|
||
|
||
.tab-panel { display: none; }
|
||
.tab-panel.active { display: block; }
|
||
|
||
/* ─── 总览 · 趋势 + 阶段分布 ─── */
|
||
.overview-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; align-items: start; }
|
||
|
||
.trend-pane { padding: 18px 20px 14px; }
|
||
.trend-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 14px; }
|
||
.trend-head h3 { margin-bottom: 0; }
|
||
.trend-head .sub { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
.trend-head .spacer { flex: 1; }
|
||
.trend-head .chip { font-family: var(--font-mono); font-size: 10.5px; padding: 3px 8px; border: 1px solid var(--border-faint); border-radius: var(--r-pill); color: var(--black-alpha-56); cursor: pointer; }
|
||
.trend-head .chip.active { background: var(--accent-black); color: var(--accent-white); border-color: var(--accent-black); }
|
||
|
||
.trend-chart { display: grid; grid-template-rows: 1fr auto; gap: 6px; height: 170px; padding: 6px 4px 2px; position: relative; }
|
||
.trend-chart .bars { display: grid; grid-template-columns: repeat(14, 1fr); gap: 5px; align-items: end; height: 100%; }
|
||
.trend-chart .bar { background: var(--background-lighter); border-radius: 2px 2px 0 0; position: relative; transition: background var(--t-base); cursor: pointer; }
|
||
.trend-chart .bar > span { display: block; width: 100%; background: var(--heat); border-radius: 2px 2px 0 0; }
|
||
.trend-chart .bar:hover > span { background: var(--accent-black); }
|
||
.trend-chart .bar.peak > span { background: var(--accent-black); }
|
||
.trend-chart .x-axis { display: grid; grid-template-columns: repeat(14, 1fr); gap: 5px; font-family: var(--font-mono); font-size: 9.5px; color: var(--black-alpha-32); text-align: center; letter-spacing: .02em; }
|
||
.trend-foot { display: flex; gap: 14px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-faint); font-size: 12px; }
|
||
.trend-foot .item { display: flex; align-items: baseline; gap: 6px; }
|
||
.trend-foot .item .k { color: var(--black-alpha-48); font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .02em; }
|
||
.trend-foot .item .v { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--accent-black); }
|
||
.trend-foot .item .v.warn { color: #B45309; }
|
||
|
||
/* ─── 阶段分布 ─── */
|
||
.stage-pane .usage-line { display: flex; justify-content: space-between; padding: 4px 0 4px; font-size: 12.5px; }
|
||
.stage-pane .usage-line .k { color: var(--accent-black); }
|
||
.stage-pane .usage-line .v { font-variant-numeric: tabular-nums; color: var(--accent-black); font-weight: 600; }
|
||
.stage-pane .usage-bar { height: 4px; background: var(--background-lighter); border-radius: 2px; margin: 4px 0 10px; overflow: hidden; }
|
||
.stage-pane .usage-bar > span { display: block; height: 100%; transition: width .3s ease; }
|
||
.stage-pane .total { display: flex; justify-content: space-between; padding-top: 10px; margin-top: 6px; border-top: 1px solid var(--border-faint); font-size: 13px; font-weight: 600; }
|
||
.stage-pane .total .v { font-variant-numeric: tabular-nums; }
|
||
|
||
/* ─── 扣费规则 + 四层预检 ─── */
|
||
.rule-pane .rule-list { font-size: 12.5px; color: var(--black-alpha-56); line-height: 1.7; }
|
||
.rule-pane .rule-list strong { color: var(--accent-black); font-weight: 600; }
|
||
.rule-pane .mono-acc { font-family: var(--font-mono); color: var(--heat); background: var(--heat-12); padding: 1px 5px; font-size: 11.5px; border-radius: var(--r-sm); }
|
||
|
||
.quota-rules { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-faint); }
|
||
.quota-rules .qr-head { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; margin-bottom: 10px; }
|
||
.quota-rules .step { display: grid; grid-template-columns: 22px 1fr; gap: 10px; align-items: baseline; margin-bottom: 6px; font-size: 12.5px; color: var(--accent-black); }
|
||
.quota-rules .step .num { width: 20px; height: 20px; border-radius: 50%; background: var(--heat-12); color: var(--heat); font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; display: grid; place-items: center; }
|
||
.quota-rules .step .formula { font-family: var(--font-mono); font-size: 11.5px; color: var(--heat); background: var(--heat-12); padding: 0 4px; border-radius: var(--r-sm); }
|
||
|
||
/* ─── 表格通用 ─── */
|
||
.billing-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; }
|
||
.billing-table th, .billing-table td { padding: 11px 14px; text-align: left; font-size: 12.5px; border-bottom: 1px solid var(--border-faint); }
|
||
.billing-table thead th { background: var(--background-lighter); font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; }
|
||
.billing-table tbody tr:last-child td { border-bottom: 0; }
|
||
.billing-table tbody tr:hover { background: var(--background-lighter); }
|
||
.billing-table .ts { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
.billing-table .neg { font-variant-numeric: tabular-nums; font-weight: 500; color: var(--accent-black); text-align: right; }
|
||
.billing-table .pos { font-variant-numeric: tabular-nums; font-weight: 500; color: var(--accent-forest); text-align: right; }
|
||
.billing-table .zero { font-variant-numeric: tabular-nums; font-weight: 500; color: var(--black-alpha-32); text-align: right; }
|
||
.billing-table .muted { color: var(--black-alpha-56); font-size: 11.5px; }
|
||
.billing-table .ref { color: var(--black-alpha-48); font-size: 10.5px; font-family: var(--font-mono); }
|
||
.billing-table .who { display: inline-flex; align-items: center; gap: 8px; }
|
||
.billing-table .who .av { width: 24px; height: 24px; border-radius: 50%; background: var(--background-lighter); border: 1px solid var(--border-faint); display: inline-grid; place-items: center; font-size: 11px; font-weight: 600; color: var(--accent-black); }
|
||
.billing-table .role-pill { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: var(--r-pill); font-size: 10.5px; font-weight: 500; }
|
||
.billing-table .role-pill .dot { width: 5px; height: 5px; border-radius: 50%; }
|
||
.billing-table .role-super { background: var(--heat-12); color: var(--heat); }
|
||
.billing-table .role-super .dot { background: var(--heat); }
|
||
.billing-table .role-admin { background: rgba(30,64,175,.1); color: #1E40AF; }
|
||
.billing-table .role-admin .dot { background: #1E40AF; }
|
||
.billing-table .role-member { background: var(--background-lighter); color: var(--black-alpha-56); }
|
||
.billing-table .role-member .dot { background: var(--black-alpha-56); }
|
||
.billing-table .status-tag { font-family: var(--font-mono); font-size: 10px; padding: 1px 6px; border-radius: var(--r-sm); letter-spacing: .04em; }
|
||
.billing-table .status-tag.ok { background: rgba(66,195,102,.12); color: var(--accent-forest); }
|
||
.billing-table .status-tag.wip { background: var(--heat-12); color: var(--heat); }
|
||
.billing-table .status-tag.fail { background: rgba(235,52,36,.10); color: var(--accent-crimson); }
|
||
.billing-table .progress-mini { width: 80px; height: 4px; background: var(--background-lighter); border-radius: 2px; overflow: hidden; display: inline-block; vertical-align: middle; margin-left: 8px; }
|
||
.billing-table .progress-mini > span { display: block; height: 100%; background: var(--heat); }
|
||
|
||
/* ─── 流水筛选条 ─── */
|
||
.filter-bar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
|
||
.filter-bar select, .filter-bar input { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-sm); padding: 6px 10px; font-size: 12.5px; font-family: inherit; color: var(--accent-black); }
|
||
.filter-bar select { padding-right: 24px; }
|
||
.filter-bar .spacer { flex: 1; }
|
||
.filter-bar .ct { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
|
||
/* 充值 modal */
|
||
.topup-modal { width: min(460px, 92vw); }
|
||
.topup-modal .topup-qr { aspect-ratio: 1; max-width: 220px; margin: 4px auto 12px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); display: grid; place-items: center; position: relative; overflow: hidden; }
|
||
.topup-modal .topup-qr::before {
|
||
content: ''; position: absolute; inset: 18px;
|
||
background-image: linear-gradient(45deg, var(--accent-black) 25%, transparent 25%),
|
||
linear-gradient(-45deg, var(--accent-black) 25%, transparent 25%),
|
||
linear-gradient(45deg, transparent 75%, var(--accent-black) 75%),
|
||
linear-gradient(-45deg, transparent 75%, var(--accent-black) 75%);
|
||
background-size: 16px 16px;
|
||
background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
|
||
opacity: .14;
|
||
}
|
||
.topup-modal .topup-qr .center { position: relative; z-index: 1; background: var(--surface); padding: 10px 12px; border-radius: var(--r-sm); font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-56); letter-spacing: .02em; text-align: center; }
|
||
.topup-modal .topup-info { text-align: center; font-size: 13px; color: var(--black-alpha-56); margin-bottom: 6px; }
|
||
.topup-modal .topup-amt { text-align: center; font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--heat); margin-bottom: 6px; }
|
||
.topup-modal .topup-note { text-align: center; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; margin-bottom: 4px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="page">
|
||
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>消费</h1>
|
||
<div class="sub"><span class="mono">// 余额 · 充值 · 4 维消费视图 + 账单流水</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 顶部:余额 banner(左)+ 快速充值(右)左右布局 -->
|
||
<div class="top-grid">
|
||
<!-- 左:余额 banner -->
|
||
<div class="balance-banner">
|
||
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
|
||
<div class="balance-hero">
|
||
<div class="lbl">团队余额</div>
|
||
<div class="v">¥327.40</div>
|
||
<div class="meta">// 充值累加 · 不重置</div>
|
||
</div>
|
||
<div class="balance-sub">
|
||
<div class="col">
|
||
<div class="lbl">本月限额</div>
|
||
<div class="v">¥3,000.00</div>
|
||
<div class="meta">// 按自然月重置</div>
|
||
</div>
|
||
<div class="col">
|
||
<div class="lbl">当月已用</div>
|
||
<div class="v">¥162.60</div>
|
||
<div class="meta">// 占比 5.4% · 健康</div>
|
||
</div>
|
||
</div>
|
||
<div class="balance-actions">
|
||
<button class="btn btn-lg" onclick="openTopup()">充值</button>
|
||
<button class="btn btn-ghost btn-lg" onclick="Shell.toast('提取明细', 'CSV · /billing/export')">导出账单</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右:快速充值 -->
|
||
<div class="pane topup-pane">
|
||
<h3>快速充值</h3>
|
||
<div class="desc">// 充值后立刻到账,可开发票 · 仅超管可操作</div>
|
||
<div class="recharge-row">
|
||
<div class="recharge-card" data-amt="100"><div class="amt">¥100</div><div class="gift">无赠送</div></div>
|
||
<div class="recharge-card selected" data-amt="500"><span class="ribbon">推荐</span><div class="amt">¥500</div><div class="gift bonus">+ ¥30 赠送</div></div>
|
||
<div class="recharge-card" data-amt="1000"><div class="amt">¥1000</div><div class="gift bonus">+ ¥80 赠送</div></div>
|
||
<div class="recharge-card" data-amt="3000"><div class="amt">¥3000</div><div class="gift bonus">+ ¥300 赠送</div></div>
|
||
</div>
|
||
<div class="pay-row">
|
||
<input class="input" id="custom-amt" placeholder="自定义金额(最低 ¥50)">
|
||
<div class="pay-btn-row">
|
||
<button class="btn btn-primary" onclick="openTopup('wechat')">微信支付</button>
|
||
<button class="btn" onclick="openTopup('alipay')">支付宝</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab strip -->
|
||
<div class="billing-tabs" role="tablist">
|
||
<button class="tab active" data-tab="overview" role="tab">总览 <span class="mono">// 当月趋势 + 阶段分布</span></button>
|
||
<button class="tab" data-tab="by-project" role="tab">按项目 <span class="mono">// 8 个</span></button>
|
||
<button class="tab" data-tab="by-member" role="tab">按成员 <span class="mono">// 5 人</span></button>
|
||
<button class="tab" data-tab="bills" role="tab">账单流水 <span class="mono">// 30 天</span></button>
|
||
</div>
|
||
|
||
<!-- ===== Tab 1: 总览 ===== -->
|
||
<div class="tab-panel active" id="panel-overview">
|
||
<div class="overview-grid">
|
||
<div class="pane trend-pane">
|
||
<div class="trend-head">
|
||
<h3>消费趋势</h3>
|
||
<span class="sub">// 近 14 天 · 单位 ¥</span>
|
||
<span class="spacer"></span>
|
||
<button class="chip active">日</button>
|
||
<button class="chip">周</button>
|
||
<button class="chip">月</button>
|
||
</div>
|
||
<div class="trend-chart">
|
||
<div class="bars" id="trend-bars">
|
||
<!-- JS 注入 14 根柱 -->
|
||
</div>
|
||
<div class="x-axis" id="trend-xaxis">
|
||
<!-- JS 注入日期 -->
|
||
</div>
|
||
</div>
|
||
<div class="trend-foot">
|
||
<div class="item"><span class="k">14 天合计</span><span class="v" id="trend-sum">¥0.00</span></div>
|
||
<div class="item"><span class="k">日均</span><span class="v" id="trend-avg">¥0.00</span></div>
|
||
<div class="item"><span class="k">峰值</span><span class="v warn" id="trend-peak">¥0.00</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pane stage-pane">
|
||
<h3>本月按阶段分布</h3>
|
||
<div class="desc">// PRD §5.3.5 扣费规则 · 仅确认后扣</div>
|
||
<div class="usage-line"><span class="k">视频片段(Seedance)</span><span class="v">¥98.40</span></div>
|
||
<div class="usage-bar"><span style="width:60%; background:var(--heat);"></span></div>
|
||
<div class="usage-line"><span class="k">故事板(image-2)</span><span class="v">¥36.00</span></div>
|
||
<div class="usage-bar"><span style="width:22%; background:var(--accent-forest);"></span></div>
|
||
<div class="usage-line"><span class="k">基础资产</span><span class="v">¥21.00</span></div>
|
||
<div class="usage-bar"><span style="width:13%; background:var(--black-alpha-56);"></span></div>
|
||
<div class="usage-line"><span class="k">脚本 LLM</span><span class="v">¥7.20</span></div>
|
||
<div class="usage-bar"><span style="width:5%; background:var(--black-alpha-32);"></span></div>
|
||
<div class="total"><span>合计</span><span class="v">¥162.60</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pane rule-pane" style="margin-top: 16px;">
|
||
<h3>扣费 + 四层额度预检规则</h3>
|
||
<div class="desc">// PRD §5.3.5 + §10.3 · 对接团队请以此页为准</div>
|
||
<div class="rule-list">
|
||
<strong>① 失败不扣</strong>:模型超时 / 内容审核拦截 / 生成异常一律不扣费。<br>
|
||
<strong>② 用户重跑不扣首次</strong>:第一次重跑保留原扣费,第二次起按次结算。<br>
|
||
<strong>③ 仅在你点击 <span class="mono-acc">[ 确认通过 ]</span> 时入账</strong>。<br>
|
||
<strong>④ 导出不再扣费</strong>,所有 token 已在过程中结算。
|
||
</div>
|
||
<div class="quota-rules">
|
||
<div class="qr-head">// 任务确认前 · 四层额度预检(任一不通过即拦截)</div>
|
||
<div class="step"><span class="num">1</span><span><strong>个人日剩余</strong> ≥ 任务预估 × <span class="formula">1.2</span></span></div>
|
||
<div class="step"><span class="num">2</span><span><strong>个人月剩余</strong> ≥ 同上</span></div>
|
||
<div class="step"><span class="num">3</span><span><strong>团队月剩余</strong> ≥ 同上</span></div>
|
||
<div class="step"><span class="num">4</span><span><strong>团队总余额</strong> ≥ 同上</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Tab 2: 按项目 ===== -->
|
||
<div class="tab-panel" id="panel-by-project">
|
||
<div class="filter-bar">
|
||
<select><option>全部状态</option><option>进行中</option><option>已完成</option><option>已归档</option></select>
|
||
<select><option>本月</option><option>近 30 天</option><option>近 90 天</option></select>
|
||
<span class="spacer"></span>
|
||
<span class="ct">共 <b style="color:var(--accent-black);">8</b> 个项目 · 当月消耗 ¥162.60</span>
|
||
</div>
|
||
<table class="billing-table">
|
||
<thead>
|
||
<tr>
|
||
<th>项目</th>
|
||
<th>商品</th>
|
||
<th>所属成员</th>
|
||
<th>当前阶段</th>
|
||
<th>状态</th>
|
||
<th style="text-align:right;">当月消耗</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="proj-body">
|
||
<!-- JS 注入 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ===== Tab 3: 按成员 ===== -->
|
||
<div class="tab-panel" id="panel-by-member">
|
||
<div class="filter-bar">
|
||
<select><option>全部角色</option><option>超管</option><option>团管</option><option>成员</option></select>
|
||
<select><option>本月</option><option>近 30 天</option><option>近 90 天</option></select>
|
||
<span class="spacer"></span>
|
||
<span class="ct">共 <b style="color:var(--accent-black);">5</b> 人 · 当月合计 ¥319.00</span>
|
||
</div>
|
||
<table class="billing-table">
|
||
<thead>
|
||
<tr>
|
||
<th>成员</th>
|
||
<th>角色</th>
|
||
<th>已完成项目</th>
|
||
<th>当月已用 / 月度额度</th>
|
||
<th>最近活跃</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="member-body">
|
||
<!-- JS 注入 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ===== Tab 4: 账单流水 ===== -->
|
||
<div class="tab-panel" id="panel-bills">
|
||
<div class="filter-bar">
|
||
<select><option>全部阶段</option><option>视频片段</option><option>故事板</option><option>基础资产</option><option>脚本 LLM</option><option>充值</option><option>导出</option></select>
|
||
<select><option>全部成员</option><option>小李</option><option>张运营</option><option>王小姐</option><option>陈策划</option></select>
|
||
<select><option>近 30 天</option><option>近 7 天</option><option>本月</option><option>全部</option></select>
|
||
<span class="spacer"></span>
|
||
<span class="ct">共 <b style="color:var(--accent-black);">28</b> 条 · <button class="chip" style="height:24px;font-size:11px;margin-left:4px;" onclick="Shell.toast('导出 CSV', '/billing/export?range=30d')">导出 CSV</button></span>
|
||
</div>
|
||
<table class="billing-table">
|
||
<thead>
|
||
<tr>
|
||
<th>时间</th>
|
||
<th>项目 / 类型</th>
|
||
<th>详情</th>
|
||
<th>成员</th>
|
||
<th>状态</th>
|
||
<th style="text-align:right;">金额</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="bills-body">
|
||
<!-- JS 注入 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 充值 modal -->
|
||
<div class="modal-bg" id="topup-bg" onclick="if(event.target===this)Shell.closeModal('topup-bg')">
|
||
<div class="modal topup-modal">
|
||
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
|
||
<div class="modal-h">
|
||
<div class="ic-m">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
|
||
</div>
|
||
<div class="ti">扫码支付<span id="topup-channel-label">// 微信支付</span></div>
|
||
</div>
|
||
<div class="modal-b">
|
||
<div class="topup-info">支付金额</div>
|
||
<div class="topup-amt" id="topup-amt">¥500.00</div>
|
||
<div class="topup-note" id="topup-bonus">// 含 ¥30 赠送 · 实到账 ¥530</div>
|
||
<div class="topup-qr">
|
||
<div class="center" id="topup-channel-name">微信扫码<br><span style="color:var(--black-alpha-32);">/topup/wx/TX...</span></div>
|
||
</div>
|
||
<div style="text-align:center; font-family:var(--font-mono); font-size:11px; color:var(--black-alpha-48); letter-spacing:.02em;">// 5 分钟内有效 · 到账后自动关闭</div>
|
||
</div>
|
||
<div class="modal-f">
|
||
<button class="btn" type="button" onclick="Shell.closeModal('topup-bg')">取消</button>
|
||
<button class="btn btn-primary" type="button" onclick="topupDone()">已完成支付</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<script src="assets/shell.js?v=202605211643"></script>
|
||
<script>
|
||
Shell.render({ active: 'account', crumbs: [{ label: '工作台', href: 'index.html' }, { label: '消费' }] });
|
||
|
||
/* ============================================================
|
||
Mock 数据
|
||
============================================================ */
|
||
const TREND_DAYS = [
|
||
{ d: '05.08', v: 12.40 }, { d: '05.09', v: 18.80 }, { d: '05.10', v: 6.20 }, { d: '05.11', v: 0 },
|
||
{ d: '05.12', v: 4.50 }, { d: '05.13', v: 22.10 }, { d: '05.14', v: 14.60 }, { d: '05.15', v: 9.30 },
|
||
{ d: '05.16', v: 28.40 }, { d: '05.17', v: 13.80 }, { d: '05.18', v: 8.20 }, { d: '05.19', v: 11.50 },
|
||
{ d: '05.20', v: 19.40 }, { d: '05.21', v: 7.80 },
|
||
];
|
||
|
||
const PROJECTS_BILL = [
|
||
{ name: '补水面膜 · v3', product: '透真补水面膜', owner: '李', role: 'super', stage: 'Stage 3 故事板', stagePct: 60, status: 'wip', statusLabel: '进行中', amount: 48.20 },
|
||
{ name: '透真防晒 · 通勤对比', product: '透真防晒', owner: '李', role: 'super', stage: 'Stage 5 导出', stagePct: 100, status: 'ok', statusLabel: '已完成', amount: 32.60 },
|
||
{ name: '蓝牙耳机 · 开箱测评', product: 'Pro 4 蓝牙耳机', owner: '张', role: 'admin', stage: 'Stage 4 视频', stagePct: 80, status: 'wip', statusLabel: '进行中', amount: 28.40 },
|
||
{ name: '速食面 · 加班场景', product: '熊猫速食面', owner: '陈', role: 'member', stage: 'Stage 5 导出', stagePct: 100, status: 'ok', statusLabel: '已完成', amount: 12.80 },
|
||
{ name: '春日新品 · 立体口红', product: '凝彩立体口红', owner: '李', role: 'super', stage: 'Stage 2 资产', stagePct: 40, status: 'wip', statusLabel: '进行中', amount: 18.30 },
|
||
{ name: '咖啡冻干 · 早八', product: '冷萃咖啡冻干', owner: '王', role: 'member', stage: 'Stage 3 故事板', stagePct: 60, status: 'fail', statusLabel: '失败 · 待重跑', amount: 0 },
|
||
{ name: '瑜伽裤 · 通勤穿搭', product: '透气速干瑜伽裤', owner: '王', role: 'member', stage: 'Stage 5 导出', stagePct: 100, status: 'ok', statusLabel: '已完成', amount: 14.80 },
|
||
{ name: '保温杯 · 户外随行', product: '316 保温杯', owner: '张', role: 'admin', stage: 'Stage 1 脚本', stagePct: 20, status: 'wip', statusLabel: '进行中', amount: 7.50 },
|
||
];
|
||
|
||
const MEMBERS_BILL = [
|
||
{ av: '李', name: '小李', role: 'super', projectsDone: 14, used: 162.60, monthly: 10000, lastActive: '15 分钟前' },
|
||
{ av: '张', name: '张运营', role: 'admin', projectsDone: 8, used: 98.40, monthly: 6000, lastActive: '10 分钟前' },
|
||
{ av: '王', name: '王小姐', role: 'member', projectsDone: 4, used: 45.20, monthly: 2000, lastActive: '28 分钟前' },
|
||
{ av: '陈', name: '陈策划', role: 'member', projectsDone: 1, used: 12.80, monthly: 2000, lastActive: '4 小时前' },
|
||
{ av: '林', name: '林新人', role: 'member', projectsDone: 0, used: 0.00, monthly: 2000, lastActive: '尚未激活' },
|
||
];
|
||
|
||
const BILLS = [
|
||
{ ts: '05.21 14:32', proj: '补水面膜 · v3', type: '视频片段', detail: 'Seedance · 场 1 · 1 镜', who: '李', role: 'super', status: 'ok', statusLabel: '通过', amount: -0.45 },
|
||
{ ts: '05.21 14:08', proj: '补水面膜 · v3', type: '视频片段', detail: 'Seedance · 场 2 · 1 镜', who: '张', role: 'admin', status: 'ok', statusLabel: '通过', amount: -0.45 },
|
||
{ ts: '05.20 21:42', proj: '蓝牙耳机 · 开箱测评', type: '视频片段', detail: 'Seedance · 场 3 · 6 镜', who: '张', role: 'admin', status: 'ok', statusLabel: '通过', amount: -1.20 },
|
||
{ ts: '05.20 18:21', proj: '透真防晒 · 通勤对比', type: '视频片段', detail: 'Seedance · 整段 · 6 镜', who: '李', role: 'super', status: 'ok', statusLabel: '通过', amount: -1.20 },
|
||
{ ts: '05.20 16:00', proj: '蓝牙耳机 · 开箱测评', type: '故事板', detail: 'image-2 · 整张重跑 · 场 2', who: '张', role: 'admin', status: 'ok', statusLabel: '通过', amount: -0.45 },
|
||
{ ts: '05.20 11:02', proj: '充值', type: '充值', detail: '微信支付 · TX2024052011021Z', who: '李', role: 'super', status: 'ok', statusLabel: '到账', amount: 500.00 },
|
||
{ ts: '05.19 18:08', proj: '速食面 · 加班场景', type: '故事板', detail: 'image-2 · 场 1', who: '陈', role: 'member', status: 'ok', statusLabel: '通过', amount: -0.30 },
|
||
{ ts: '05.19 16:08', proj: '补水面膜 · v3', type: '故事板', detail: 'image-2 · 场 1', who: '李', role: 'super', status: 'ok', statusLabel: '通过', amount: -0.45 },
|
||
{ ts: '05.19 14:02', proj: '补水面膜 · v3', type: '脚本 LLM', detail: '2.4k tokens · AI 全生', who: '李', role: 'super', status: 'ok', statusLabel: '通过', amount: -0.04 },
|
||
{ ts: '05.19 13:38', proj: '补水面膜 · v3', type: '基础资产', detail: 'image-2 · 5 张', who: '李', role: 'super', status: 'ok', statusLabel: '通过', amount: -1.05 },
|
||
{ ts: '05.19 11:18', proj: '补水面膜 · v3', type: '故事板', detail: 'image-2 · 场 2', who: '张', role: 'admin', status: 'ok', statusLabel: '通过', amount: -0.45 },
|
||
{ ts: '05.19 11:12', proj: '速食面 · 加班场景', type: '基础资产', detail: 'image-2 · 2 张', who: '陈', role: 'member', status: 'ok', statusLabel: '通过', amount: -0.42 },
|
||
{ ts: '05.18 15:42', proj: '咖啡冻干 · 早八', type: '故事板', detail: 'image-2 · 场 3', who: '王', role: 'member', status: 'fail', statusLabel: '失败不扣', amount: 0 },
|
||
{ ts: '05.18 09:42', proj: '蓝牙耳机 · 开箱测评', type: '基础资产', detail: 'image-2 · 4 张', who: '张', role: 'admin', status: 'ok', statusLabel: '通过', amount: -0.84 },
|
||
{ ts: '05.17 14:38', proj: '蓝牙耳机 · 开箱测评', type: '脚本 LLM', detail: '1.8k tokens · 自带粘贴', who: '张', role: 'admin', status: 'ok', statusLabel: '通过', amount: -0.03 },
|
||
{ ts: '05.17 10:30', proj: '瑜伽裤 · 通勤穿搭', type: '导出', detail: '1080×1920 · 9:16 · 38s', who: '王', role: 'member', status: 'ok', statusLabel: '免费', amount: 0 },
|
||
{ ts: '05.17 10:08', proj: '瑜伽裤 · 通勤穿搭', type: '视频片段', detail: 'Seedance · 整段 · 5 镜', who: '王', role: 'member', status: 'ok', statusLabel: '通过', amount: -3.20 },
|
||
{ ts: '05.16 19:38', proj: '透真防晒 · 通勤对比', type: '视频片段', detail: 'Seedance · 4 镜', who: '王', role: 'member', status: 'ok', statusLabel: '通过', amount: -0.80 },
|
||
{ ts: '05.16 11:42', proj: '透真防晒 · 通勤对比', type: '故事板', detail: 'image-2 · 场 2', who: '王', role: 'member', status: 'ok', statusLabel: '通过', amount: -0.45 },
|
||
{ ts: '05.15 16:08', proj: '透真防晒 · 通勤对比', type: '基础资产', detail: 'image-2 · 3 张', who: '王', role: 'member', status: 'ok', statusLabel: '通过', amount: -0.63 },
|
||
];
|
||
|
||
const ROLE_META = {
|
||
super: { label: '超管', cls: 'role-super' },
|
||
admin: { label: '团管', cls: 'role-admin' },
|
||
member: { label: '成员', cls: 'role-member' },
|
||
};
|
||
|
||
function fmtMoney(n) { return '¥' + Math.abs(n).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
|
||
function amtStr(n) {
|
||
if (n === 0) return '¥0.00';
|
||
return (n > 0 ? '+' : '-') + fmtMoney(n);
|
||
}
|
||
function amtCls(n) { return n > 0 ? 'pos' : (n === 0 ? 'zero' : 'neg'); }
|
||
|
||
/* ─── 趋势柱 ─── */
|
||
function renderTrend() {
|
||
const bars = document.getElementById('trend-bars');
|
||
const xax = document.getElementById('trend-xaxis');
|
||
const max = Math.max(...TREND_DAYS.map(d => d.v));
|
||
bars.innerHTML = TREND_DAYS.map(d => {
|
||
const h = max > 0 ? (d.v / max * 100) : 0;
|
||
const isPeak = d.v === max;
|
||
return `<div class="bar${isPeak ? ' peak' : ''}" title="${d.d} · ${fmtMoney(d.v)}"><span style="height: ${h.toFixed(1)}%"></span></div>`;
|
||
}).join('');
|
||
xax.innerHTML = TREND_DAYS.map((d, i) => i % 2 === 0 ? `<span>${d.d.slice(3)}</span>` : '<span></span>').join('');
|
||
const sum = TREND_DAYS.reduce((s, d) => s + d.v, 0);
|
||
document.getElementById('trend-sum').textContent = fmtMoney(sum);
|
||
document.getElementById('trend-avg').textContent = fmtMoney(sum / TREND_DAYS.length);
|
||
document.getElementById('trend-peak').textContent = fmtMoney(max);
|
||
}
|
||
|
||
/* ─── 按项目 表格 ─── */
|
||
function renderProjects() {
|
||
const tb = document.getElementById('proj-body');
|
||
tb.innerHTML = PROJECTS_BILL.map(p => {
|
||
const r = ROLE_META[p.role];
|
||
return `
|
||
<tr>
|
||
<td><a href="pipeline.html?product=${encodeURIComponent(p.product)}" style="color:var(--accent-black);text-decoration:none;font-weight:500;">${p.name}</a></td>
|
||
<td class="muted">${p.product}</td>
|
||
<td><span class="who"><span class="av">${p.owner}</span><span class="role-pill ${r.cls}"><span class="dot"></span>${r.label}</span></span></td>
|
||
<td><span class="muted">${p.stage}</span><span class="progress-mini"><span style="width:${p.stagePct}%"></span></span></td>
|
||
<td><span class="status-tag ${p.status}">${p.statusLabel}</span></td>
|
||
<td class="${amtCls(-p.amount)}">${p.amount === 0 ? '¥0.00' : '-' + fmtMoney(p.amount)}</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
/* ─── 按成员 表格 ─── */
|
||
function renderMembers() {
|
||
const tb = document.getElementById('member-body');
|
||
tb.innerHTML = MEMBERS_BILL.map(m => {
|
||
const r = ROLE_META[m.role];
|
||
const pct = m.monthly > 0 ? (m.used / m.monthly * 100) : 0;
|
||
return `
|
||
<tr style="cursor:pointer;" onclick="location.href='team.html'">
|
||
<td><span class="who"><span class="av">${m.av}</span><strong style="font-weight:500;">${m.name}</strong></span></td>
|
||
<td><span class="role-pill ${r.cls}"><span class="dot"></span>${r.label}</span></td>
|
||
<td>${m.projectsDone}</td>
|
||
<td>
|
||
<strong style="font-variant-numeric:tabular-nums;font-weight:600;">${fmtMoney(m.used)}</strong>
|
||
<span class="muted"> / ${fmtMoney(m.monthly)} · ${pct.toFixed(1)}%</span>
|
||
<span class="progress-mini"><span style="width:${Math.min(100, pct)}%; background:${pct >= 85 ? '#B45309' : 'var(--heat)'};"></span></span>
|
||
</td>
|
||
<td><span class="ts">${m.lastActive}</span></td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
/* ─── 账单流水 表格 ─── */
|
||
function renderBills() {
|
||
const tb = document.getElementById('bills-body');
|
||
tb.innerHTML = BILLS.map(b => {
|
||
const r = ROLE_META[b.role];
|
||
return `
|
||
<tr>
|
||
<td class="ts">${b.ts}</td>
|
||
<td><strong style="font-weight:500;">${b.proj}</strong><br><span class="muted">${b.type}</span></td>
|
||
<td class="muted">${b.detail}</td>
|
||
<td><span class="who"><span class="av">${b.who}</span></span></td>
|
||
<td><span class="status-tag ${b.status}">${b.statusLabel}</span></td>
|
||
<td class="${amtCls(b.amount)}">${b.amount === 0 ? '¥0.00' : (b.amount > 0 ? '+' + fmtMoney(b.amount) : '-' + fmtMoney(b.amount))}</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
/* ─── Tab 切换 ─── */
|
||
document.querySelectorAll('.billing-tabs .tab').forEach(tab => {
|
||
tab.addEventListener('click', () => {
|
||
document.querySelectorAll('.billing-tabs .tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
document.getElementById('panel-' + tab.dataset.tab).classList.add('active');
|
||
});
|
||
});
|
||
|
||
/* ─── 快速充值卡选择 ─── */
|
||
document.querySelectorAll('.recharge-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
document.querySelectorAll('.recharge-card').forEach(c => c.classList.remove('selected'));
|
||
card.classList.add('selected');
|
||
});
|
||
});
|
||
|
||
/* ─── 充值 modal ─── */
|
||
function openTopup(channel) {
|
||
const selected = document.querySelector('.recharge-card.selected');
|
||
const customRaw = document.getElementById('custom-amt').value.trim();
|
||
const custom = Number(customRaw);
|
||
let amt = 500, bonus = 30;
|
||
if (custom >= 50) { amt = custom; bonus = 0; }
|
||
else if (selected) {
|
||
amt = Number(selected.dataset.amt);
|
||
const bonusEl = selected.querySelector('.gift.bonus');
|
||
bonus = bonusEl ? Number(bonusEl.textContent.replace(/\D/g, '')) : 0;
|
||
}
|
||
document.getElementById('topup-amt').textContent = fmtMoney(amt);
|
||
document.getElementById('topup-bonus').textContent = bonus > 0
|
||
? `// 含 ¥${bonus} 赠送 · 实到账 ¥${(amt + bonus).toFixed(2)}`
|
||
: '// 无赠送';
|
||
const isAlipay = channel === 'alipay';
|
||
document.getElementById('topup-channel-label').textContent = isAlipay ? '// 支付宝' : '// 微信支付';
|
||
document.getElementById('topup-channel-name').innerHTML = (isAlipay ? '支付宝扫码' : '微信扫码') +
|
||
'<br><span style="color:var(--black-alpha-32);">/topup/' + (isAlipay ? 'ali' : 'wx') + '/TX' + Date.now() + '</span>';
|
||
Shell.openModal('topup-bg');
|
||
}
|
||
function topupDone() {
|
||
Shell.closeModal('topup-bg');
|
||
Shell.toast('充值成功', '余额已更新 · 可开发票');
|
||
}
|
||
|
||
/* ─── 初始化 ─── */
|
||
renderTrend();
|
||
renderProjects();
|
||
renderMembers();
|
||
renderBills();
|
||
</script>
|
||
</body>
|
||
</html>
|