大量修改UI

This commit is contained in:
Azmat@qq.com
2026-08-19 17:44:56 +08:00
parent 18b3927817
commit a343f47f0c
76 changed files with 11655 additions and 6104 deletions
+35 -30
View File
@@ -1,8 +1,9 @@
import { useEffect, useMemo, useState } from "react";
import { CreditCard, X } from "lucide-react";
import { ArrowLeft, CreditCard, X } from "lucide-react";
import { createPortal } from "react-dom";
import { api } from "../api";
import type { BillingSummary, BillingTrend, Ledger, Project, Team, TeamMember } from "../types";
import type { NavigateFn } from "./route-config";
import { money, pts, stageMeta, yuan } from "./stage-config";
import { pageWindow } from "../components/pager";
import { useBodyScrollLock, useOverlayTransition } from "../components/overlays";
@@ -14,9 +15,9 @@ const ROLE_PILL: Record<string, string> = { owner: "role-super", admin: "role-ad
type TrendRange = "day" | "week" | "month";
const RANGE_META: Record<TrendRange, { chip: string; sub: string; totalLabel: string; avgLabel: string }> = {
day: { chip: "日", sub: "// 近 14 天 · 单位 积分", totalLabel: "14 天合计", avgLabel: "日均" },
week: { chip: "周", sub: "// 近 8 周 · 单位 积分", totalLabel: "8 周合计", avgLabel: "周均" },
month: { chip: "月", sub: "// 近 6 个月 · 单位 积分", totalLabel: "6 月合计", avgLabel: "月均" }
day: { chip: "日", sub: "近 14 天 · 单位 积分", totalLabel: "14 天合计", avgLabel: "日均" },
week: { chip: "周", sub: "近 8 周 · 单位 积分", totalLabel: "8 周合计", avgLabel: "周均" },
month: { chip: "月", sub: "近 6 个月 · 单位 积分", totalLabel: "6 月合计", avgLabel: "月均" }
};
type Tab = "overview" | "by-project" | "by-member" | "bills";
@@ -63,7 +64,7 @@ const RECHARGE: Array<{ amt: number; gift: string; bonus: boolean; bonusAmt: num
const MIN_RECHARGE_AMOUNT = 50;
const STAGES: Array<{ k: string; color: string; bucket: keyof BillingTrend["by_stage"] }> = [
{ k: "视频片段(Seedance)", color: "var(--heat)", bucket: "video" },
{ k: "视频片段(Seedance)", color: "var(--klein)", bucket: "video" },
{ k: "故事板(image-2)", color: "var(--accent-forest)", bucket: "storyboard" },
{ k: "基础资产", color: "var(--black-alpha-56)", bucket: "base" },
{ k: "脚本 LLM", color: "var(--black-alpha-32)", bucket: "script" }
@@ -92,18 +93,18 @@ function TopupModal({ open, channel, amount, bonus, rate, close, onDone }: {
<span className="corner-tr">+</span><span className="corner-bl">+</span>
<div className="modal-h">
<div className="ic-m"><CreditCard size={16} /></div>
<div className="ti"><span>// {channelLabel}</span></div>
<div className="ti"><span>{channelLabel}</span></div>
<button className="x modal-x" type="button" onClick={close} aria-label="关闭"><X size={14} /></button>
</div>
<div className="modal-b">
<div className="topup-info"></div>
<div className="topup-amt">{yuan(amount)}</div>
<div className="topup-note">{`// 到账 ${money(Math.round(amount * rate + bonus))}`}{bonus > 0 ? `(含 ${bonus} 积分赠送)` : ""}</div>
<div className="topup-note">{`到账 ${money(Math.round(amount * rate + bonus))}`}{bonus > 0 ? `(含 ${bonus} 积分赠送)` : ""}</div>
<div className="topup-qr" aria-label="二维码占位">
<div className="center">{scanLabel}<br /><span className="ref">{txRef}</span></div>
</div>
<div className="topup-valid">// 5 分钟内有效 · 到账后自动关闭</div>
<div className="topup-valid">// 积分不可提现、不可转让 · 长期有效 · 发票按实际支付金额开具</div>
<div className="topup-valid">5 · </div>
<div className="topup-valid"> · · </div>
</div>
<div className="modal-f">
<button className="btn" type="button" onClick={close}></button>
@@ -115,11 +116,12 @@ function TopupModal({ open, channel, amount, bonus, rate, close, onDone }: {
);
}
export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
export function AccountPage({ billing, projects, team, navigate, onRecharge, onNotify }: {
billing: BillingSummary | null;
projects: Project[];
// team prop 用于读取团队级月限额(与团队管理页保持同一来源 · #14)
team?: Team | null;
navigate: NavigateFn;
onRecharge: (amount: number, bonus: number) => void | Promise<unknown>;
onNotify: (type: "success" | "error" | "info", text: string) => void;
}) {
@@ -281,9 +283,12 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
return (
<section className="account-page">
<div className="page-head">
<button className="ac-back" type="button" onClick={() => navigate("dashboard")} aria-label="返回">
<ArrowLeft size={18} strokeWidth={1.75} />
</button>
<div>
<h1></h1>
<div className="sub"><span className="mono">// 余额 · 充值 · 4 维消费视图 + 账单流水</span></div>
<h1></h1>
<div className="sub">{money(balance)} · {money(used)} · </div>
</div>
</div>
@@ -293,18 +298,18 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className="balance-hero">
<div className="lbl"></div>
<div className="v">{money(balance)}</div>
<div className="meta">// 充值累加 · 不重置</div>
<div className="meta"> · </div>
</div>
<div className="balance-sub">
<div className="col">
<div className="lbl"></div>
<div className="v">{money(limit)}</div>
<div className="meta">// 按自然月重置</div>
<div className="meta"></div>
</div>
<div className="col">
<div className="lbl"></div>
<div className="v">{money(used)}</div>
<div className="meta">// 占比 {pct.toFixed(1)}% · {pct >= 80 ? "注意" : "健康"}</div>
<div className="meta"> {pct.toFixed(1)}% · {pct >= 80 ? "注意" : "健康"}</div>
</div>
</div>
<div className="balance-foot">
@@ -320,7 +325,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className="topup-head">
<div>
<h3></h3>
<div className="desc">// 充值后立刻到账,可开发票 · 仅超管可操作</div>
<div className="desc">, · </div>
</div>
<div className="topup-selected"> ¥{effectiveAmount}( {Math.round(effectiveAmount * pointsRate + effectiveBonus)} ){effectiveBonus > 0 ? ` · 含 ${effectiveBonus} 积分赠送` : ""}</div>
</div>
@@ -377,10 +382,10 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
</div>
<div className="billing-tabs" role="tablist">
<button className={`tab ${tab === "overview" ? "active" : ""}`} type="button" onClick={() => setTab("overview")}></button>
<button className={`tab ${tab === "by-project" ? "active" : ""}`} type="button" onClick={() => setTab("by-project")}> <span className="count">{projects.length}</span></button>
<button className={`tab ${tab === "by-member" ? "active" : ""}`} type="button" onClick={() => setTab("by-member")}> <span className="count">{teamMembers.length}</span></button>
<button className={`tab ${tab === "bills" ? "active" : ""}`} type="button" onClick={() => setTab("bills")}> <span className="count">{ledgerCount}</span></button>
<button className={`ac-tab ${tab === "overview" ? "active" : ""}`} type="button" onClick={() => setTab("overview")}></button>
<button className={`ac-tab ${tab === "by-project" ? "active" : ""}`} type="button" onClick={() => setTab("by-project")}> <span className="count">{projects.length}</span></button>
<button className={`ac-tab ${tab === "by-member" ? "active" : ""}`} type="button" onClick={() => setTab("by-member")}> <span className="count">{teamMembers.length}</span></button>
<button className={`ac-tab ${tab === "bills" ? "active" : ""}`} type="button" onClick={() => setTab("bills")}> <span className="count">{ledgerCount}</span></button>
</div>
<div className={`tab-panel ${tab === "overview" ? "active" : ""}`}>
@@ -391,7 +396,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<span className="sub">{meta.sub}</span>
<span className="spacer"></span>
{(["day", "week", "month"] as TrendRange[]).map((r) => (
<button key={r} className={`chip${range === r ? " active" : ""}`} type="button" onClick={() => setRange(r)}>{RANGE_META[r].chip}</button>
<button key={r} className={`ac-seg${range === r ? " active" : ""}`} type="button" onClick={() => setRange(r)}>{RANGE_META[r].chip}</button>
))}
</div>
<div className="trend-chart">
@@ -422,7 +427,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className="pane stage-pane">
<h3></h3>
<div className="desc">// PRD §5.3.5 扣费规则 · 仅确认后扣</div>
<div className="desc">PRD §5.3.5 · </div>
{STAGES.map((s) => {
const amt = Number(trend?.by_stage[s.bucket] || 0);
const w = stageTotal > 0 ? Math.min(100, (amt / stageTotal) * 100) : 0;
@@ -439,7 +444,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className="pane rule-pane" style={{ marginTop: 16 }}>
<h3> + </h3>
<div className="desc">// PRD §5.3.5 + §10.3 · 对接团队请以此页为准</div>
<div className="desc">PRD §5.3.5 + §10.3 · </div>
<div className="rule-list">
<strong> </strong>: / / <br />
<strong> </strong>:,<br />
@@ -447,7 +452,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<strong> </strong>, token
</div>
<div className="quota-rules">
<div className="qr-head">// 任务确认前 · 四层额度预检(任一不通过即拦截)</div>
<div className="qr-head"> · ()</div>
<div className="step"><span className="num">1</span><span><strong></strong> × <span className="formula">1.2</span></span></div>
<div className="step"><span className="num">2</span><span><strong></strong> </span></div>
<div className="step"><span className="num">3</span><span><strong></strong> </span></div>
@@ -482,9 +487,9 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<thead><tr><th></th><th> / </th><th></th><th></th><th></th><th style={{ textAlign: "right" }}></th></tr></thead>
<tbody>
{ledgersLoading && ledgerRows.length === 0 ? (
<tr><td colSpan={6} className="muted" style={{ textAlign: "center", padding: "24px 0" }}>// 加载中…</td></tr>
<tr><td colSpan={6} className="muted" style={{ textAlign: "center", padding: "24px 0" }}></td></tr>
) : ledgerRows.length === 0 ? (
<tr><td colSpan={6} className="muted" style={{ textAlign: "center", padding: "24px 0" }}>{billFiltered ? "// 无匹配筛选条件的账单" : "// 暂无账单流水"}</td></tr>
<tr><td colSpan={6} className="muted" style={{ textAlign: "center", padding: "24px 0" }}>{billFiltered ? "无匹配筛选条件的账单" : "暂无账单流水"}</td></tr>
) : ledgerRows.map((l) => (
<tr key={l.id}>
<td className="ts">{new Date(l.created_at).toLocaleString("zh-CN")}</td>
@@ -502,7 +507,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
</div>
{ledgerCount > BILLS_PER_PAGE && (
<div className="bill-pager">
<span className="total">// 共 {ledgerCount} 条 · 第 {safeBillPage} / {billTotalPages} 页</span>
<span className="total"> {ledgerCount} · {safeBillPage} / {billTotalPages} </span>
<div className="pages">
<button type="button" disabled={safeBillPage <= 1} onClick={() => setBillPage(safeBillPage - 1)}></button>
{pageWindow(safeBillPage, billTotalPages).map((p, i) => (
@@ -548,7 +553,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<thead><tr><th></th><th></th><th></th><th></th><th style={{ textAlign: "right" }}></th></tr></thead>
<tbody>
{filteredProjects.length === 0 ? (
<tr><td colSpan={5} className="muted" style={{ textAlign: "center", padding: "24px 0" }}>// 当前筛选条件下没有项目</td></tr>
<tr><td colSpan={5} className="muted" style={{ textAlign: "center", padding: "24px 0" }}></td></tr>
) : filteredProjects.map((p) => {
const spend = projectSpend(p.id);
const stg = projStage(p);
@@ -587,7 +592,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<thead><tr><th></th><th></th><th> / </th><th></th></tr></thead>
<tbody>
{filteredMembers.length === 0 ? (
<tr><td colSpan={4} className="muted" style={{ textAlign: "center", padding: "24px 0" }}>// 当前筛选条件下没有成员</td></tr>
<tr><td colSpan={4} className="muted" style={{ textAlign: "center", padding: "24px 0" }}></td></tr>
) : filteredMembers.map((m) => {
const monthly = Number(m.monthly_credit_limit || 0);
const monthUsed = Number(m.month_charged || 0);
@@ -600,7 +605,7 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<td className="quota">
<span className="used">{money(monthUsed)}</span> <span className="lim">/ {monthly > 0 ? money(monthly) : "不限"}{monthly > 0 ? ` · ${usedPct.toFixed(1)}%` : ""}</span>
{monthly > 0 && (
<span className="progress-mini"><span style={{ width: `${usedPct}%`, background: usedPct >= 85 ? "var(--accent-honey)" : "var(--heat)" }} /></span>
<span className="progress-mini"><span style={{ width: `${usedPct}%`, background: usedPct >= 85 ? "var(--accent-honey)" : "var(--klein)" }} /></span>
)}
</td>
<td>{STATUS_LABEL[m.status] || m.status}</td>