fix: 测试清单一轮bug修复(商品库/视频项目/设置/消费/平台套图)

- 商品库编辑删图假成功+悬停无删除图标;商品三视图错显角色三视图
- 视频项目演员删除入口;角色三视图防呆;故事板审核失败原因透出
- 设置:管理团队死按钮/通知未接入项屏蔽/邮箱验证链路
- 消费:账单流水切换类型重置分页+独立总页数
- 资产库上传按钮隐藏;月限额两处对齐
- 平台套图:提示词框放大/选模型胶囊内嵌/未读任务角标/工作台记录持久化
- 新建商品独立添加卖点按钮;商品库超1920自适应

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-30 19:46:35 +08:00
co-authored by Claude Opus 4.8
parent b17a716c06
commit 22268d608b
14 changed files with 261 additions and 107 deletions
+24 -15
View File
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import { CreditCard, X } from "lucide-react";
import { createPortal } from "react-dom";
import { api } from "../api";
import type { BillingSummary, BillingTrend, Ledger, Project, TeamMember } from "../types";
import type { BillingSummary, BillingTrend, Ledger, Project, Team, TeamMember } from "../types";
import { money, stageMeta } from "./stage-config";
import { pageWindow } from "../components/pager";
import { useBodyScrollLock, useOverlayTransition } from "../components/overlays";
@@ -110,9 +110,11 @@ function TopupModal({ open, channel, amount, bonus, close, onDone }: {
);
}
export function AccountPage({ billing, projects, onRecharge }: {
export function AccountPage({ billing, projects, team, onRecharge }: {
billing: BillingSummary | null;
projects: Project[];
// team prop 用于读取团队级月限额(与团队管理页保持同一来源 · #14)
team?: Team | null;
onRecharge: (amount: number, bonus: number) => void | Promise<unknown>;
}) {
const [tab, setTab] = useState<Tab>("overview");
@@ -150,15 +152,8 @@ export function AccountPage({ billing, projects, onRecharge }: {
}).catch(() => {}).finally(() => { if (alive) setLedgersLoading(false); });
return () => { alive = false; };
}, [billPage, reloadFlag]);
const billTotalPages = Math.max(1, Math.ceil(ledgerCount / BILLS_PER_PAGE));
const safeBillPage = Math.min(billPage, billTotalPages);
// row40:跳页输入框 —— 输入页码回车/点「跳转」即钳到 [1,总页数] 并翻页
const [billJump, setBillJump] = useState("");
function gotoBillPage() {
const n = parseInt(billJump, 10);
if (!Number.isNaN(n)) setBillPage(Math.min(billTotalPages, Math.max(1, n)));
setBillJump("");
}
const selectedCard = RECHARGE.find((item) => item.amt === recharge);
const effectiveAmount = Number(customAmt) > 0 ? Number(customAmt) : recharge;
@@ -175,8 +170,11 @@ export function AccountPage({ billing, projects, onRecharge }: {
const balance = Number(billing?.account.balance || 0);
const used = Number(billing?.charged_total || 0);
const memberLimit = teamMembers.reduce((sum, m) => sum + Math.max(0, Number(m.monthly_credit_limit || 0)), 0);
const limit = memberLimit || balance;
// 月限额:与团队管理页保持同一来源(team.monthly_credit_limit) · #14
// 优先用团队级月限额;-1 = 不限(显示余额);0/null = 未设置 → 用成员额度累加,再 fallback 余额
const savedMonthlyLimit = team?.monthly_credit_limit == null ? 0 : Number(team.monthly_credit_limit);
const memberLimitSum = teamMembers.reduce((sum, m) => sum + Math.max(0, Number(m.monthly_credit_limit || 0)), 0);
const limit = savedMonthlyLimit === -1 ? balance : (savedMonthlyLimit > 0 ? savedMonthlyLimit : (memberLimitSum || balance));
const left = Math.max(0, limit - used);
const pct = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;
@@ -219,7 +217,8 @@ export function AccountPage({ billing, projects, onRecharge }: {
const memFiltered = memRole !== "all";
// ── 账单流水筛选(类型 + 成员)── 后端为服务端分页且无过滤参数(api.ledgers 仅收 page/pageSize),
// 故只能对「当前页」已载入的 10 条做客户端过滤;计数标注为本页范围,避免误导成全量过滤
// 故只能对「当前页」已载入的 10 条做客户端过滤。
// 切换筛选条件时跳回第 1 页(#9a);过滤后按实际可见行数重算总页数(#9b)。
const [billType, setBillType] = useState<string>("all");
const [billMember, setBillMember] = useState<string>("all");
const billMemberOptions = useMemo(() => {
@@ -235,6 +234,16 @@ export function AccountPage({ billing, projects, onRecharge }: {
[ledgerRows, billType, billMember]
);
const billFiltered = billType !== "all" || billMember !== "all";
// 有筛选时按当前页已过滤的可见行数重算总页数,避免总页数用全量致翻到空页(#9b)
const billTotalPages = billFiltered
? Math.max(1, Math.ceil(visibleLedgers.length / BILLS_PER_PAGE))
: Math.max(1, Math.ceil(ledgerCount / BILLS_PER_PAGE));
const safeBillPage = Math.min(billPage, billTotalPages);
function gotoBillPage() {
const n = parseInt(billJump, 10);
if (!Number.isNaN(n)) setBillPage(Math.min(billTotalPages, Math.max(1, n)));
setBillJump("");
}
return (
<section className="account-page">
@@ -398,7 +407,7 @@ export function AccountPage({ billing, projects, onRecharge }: {
<div className={`tab-panel ${tab === "bills" ? "active" : ""}`}>
<div className="filter-bar">
<select value={billType} onChange={(e) => setBillType(e.target.value)} aria-label="按类型筛选">
<select value={billType} onChange={(e) => { setBillType(e.target.value); setBillPage(1); }} aria-label="按类型筛选">
<option value="all"></option>
<option value="charge"></option>
<option value="recharge"></option>
@@ -407,12 +416,12 @@ export function AccountPage({ billing, projects, onRecharge }: {
<option value="adjustment"></option>
<option value="refund">退</option>
</select>
<select value={billMember} onChange={(e) => setBillMember(e.target.value)} aria-label="按成员筛选">
<select value={billMember} onChange={(e) => { setBillMember(e.target.value); setBillPage(1); }} aria-label="按成员筛选">
<option value="all"></option>
{billMemberOptions.map((m) => <option key={m} value={m}>{m}</option>)}
</select>
{billFiltered && (
<button className="filter-reset" type="button" onClick={() => { setBillType("all"); setBillMember("all"); }}></button>
<button className="filter-reset" type="button" onClick={() => { setBillType("all"); setBillMember("all"); setBillPage(1); }}></button>
)}
<span className="spacer"></span>
<span className="ct"> <b>{visibleLedgers.length}</b> · {ledgerCount} </span>