"use client"; import { useEffect, useMemo, useRef, useState, type FormEvent, type KeyboardEvent, type ReactNode } from "react"; import { ArrowUpRight, Banknote, Building2, Check, ChevronRight, CircleDollarSign, Landmark, Loader2, Pencil, Plus, ReceiptText, RefreshCw, Settings2, UsersRound, WalletCards, X } from "lucide-react"; import { billingUnitLabel, formatBillingAmount } from "@/lib/billing"; import { pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion"; import type { BillingAccountConfig, BillingParameterDimension, BillingParameterTier, BillingPriceRule, OrganizationWallet } from "@/lib/types"; type BillingPayload = { organization: { id: string; name: string }; billingAccount: BillingAccountConfig; wallet: OrganizationWallet; ledger: LedgerEntry[]; summary: LedgerSummary; personal: LedgerSummary; }; type LedgerEntry = { id: string; organizationId: string; accountId?: string; jobId?: string; kind: "recharge" | "charge" | "refund" | "adjustment"; deltaFen: number; balanceAfterFen: number; description: string; metadata: Record; createdAt: string; }; type LedgerSummary = { rechargeFen: number; chargedFen: number; refundedFen: number; netConsumedFen: number; }; type AdminMember = { id: string; displayName: string; phone: string; role: "super_admin" | "organization_admin" | "user"; organizationId: string | null; status: "active" | "disabled"; }; type AdminPayload = { billingAccount: BillingAccountConfig; organizations: Array<{ id: string; name: string; status: string; wallet: OrganizationWallet; }>; members: AdminMember[]; ledger: LedgerEntry[]; priceRules: BillingPriceRule[]; }; type BillingTabId = "overview" | "ledger" | "pricing" | "balance" | "account"; type BillingTabDefinition = { id: BillingTabId; label: string; }; type AdjustmentDraft = { organizationId: string; direction: "credit" | "debit"; amountYuan: string; note: string; }; type PriceEditTarget = { dimensionKey?: string; tierValue?: string; label: string; markupMultiplier: number; }; type PriceEditState = { rule: BillingPriceRule; target: PriceEditTarget; }; const emptyAccountDraft: BillingAccountConfig = { accountName: "", bankName: "", accountNumber: "", contact: "" }; const emptyAdjustmentDraft: AdjustmentDraft = { organizationId: "", direction: "credit", amountYuan: "", note: "" }; export function BillingManager({ isSuperAdmin }: { isSuperAdmin: boolean }) { const [billing, setBilling] = useState(null); const [admin, setAdmin] = useState(null); const [accountDraft, setAccountDraft] = useState(emptyAccountDraft); const [adjustmentDraft, setAdjustmentDraft] = useState(emptyAdjustmentDraft); const [activeTab, setActiveTab] = useState("overview"); const [editingAccount, setEditingAccount] = useState(false); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [priceEditState, setPriceEditState] = useState(null); const managerRef = useRef(null); const feedbackRef = useRef(null); const tabs = useMemo(() => isSuperAdmin ? [ { id: "overview", label: "概览" }, { id: "pricing", label: "价格与计费" }, { id: "balance", label: "余额与上账" }, { id: "account", label: "收款设置" } ] : [ { id: "overview", label: "概览" }, { id: "ledger", label: "账务流水" } ], [isSuperAdmin]); const selectedAdjustmentOrganization = useMemo( () => admin?.organizations.find((organization) => organization.id === adjustmentDraft.organizationId), [admin?.organizations, adjustmentDraft.organizationId] ); useEffect(() => { void load(); setActiveTab("overview"); }, [isSuperAdmin]); useEffect(() => runScopedMotion(managerRef, (scope) => revealChildren(scope)), []); useEffect(() => { pulseFeedback(feedbackRef.current); }, [error, notice]); async function load() { setLoading(true); setError(null); try { if (isSuperAdmin) { const adminResponse = await fetch("/api/admin/billing", { cache: "no-store" }); const adminPayload = await readApiPayload(adminResponse); if (!adminResponse.ok) throw new Error(adminPayload.error || "读取超管计费数据失败"); setAdmin(adminPayload); setBilling(buildAdminBillingPayload(adminPayload)); setAccountDraft({ ...emptyAccountDraft, ...adminPayload.billingAccount }); setAdjustmentDraft((current) => ({ ...current, organizationId: current.organizationId || adminPayload.organizations[0]?.id || "" })); } else { const billingResponse = await fetch("/api/billing", { cache: "no-store" }); const billingPayload = await readApiPayload(billingResponse); if (!billingResponse.ok) throw new Error(billingPayload.error || "读取计费数据失败"); setBilling(billingPayload); } } catch (nextError) { setError(nextError instanceof Error ? nextError.message : String(nextError)); } finally { setLoading(false); } } async function saveBillingAccount(event: FormEvent) { event.preventDefault(); setSaving(true); setError(null); setNotice(null); try { const response = await fetch("/api/admin/billing/account", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(accountDraft) }); const payload = await readApiPayload<{ billingAccount?: BillingAccountConfig }>(response); if (!response.ok) throw new Error(payload.error || "保存收款账户失败"); setAccountDraft({ ...emptyAccountDraft, ...(payload.billingAccount || accountDraft) }); setEditingAccount(false); setNotice("对公收款账户已更新,成员将看到最新信息。"); await load(); } catch (nextError) { setError(nextError instanceof Error ? nextError.message : String(nextError)); } finally { setSaving(false); } } async function submitAdjustment(event: FormEvent) { event.preventDefault(); setSaving(true); setError(null); setNotice(null); try { const response = await fetch("/api/admin/billing/adjustments", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ organizationId: adjustmentDraft.organizationId, direction: adjustmentDraft.direction, amountYuan: adjustmentDraft.amountYuan, note: adjustmentDraft.note }) }); const payload = await readApiPayload<{ wallet?: OrganizationWallet }>(response); if (!response.ok) throw new Error(payload.error || "余额调整失败"); setAdjustmentDraft((current) => ({ ...current, amountYuan: "", note: "" })); setNotice(`${adjustmentDraft.direction === "credit" ? "上账" : "扣减"}已完成,${selectedAdjustmentOrganization?.name || "组织"}余额为 ${formatBillingAmount(payload.wallet?.balanceFen || 0)}。`); await load(); } catch (nextError) { setError(nextError instanceof Error ? nextError.message : String(nextError)); } finally { setSaving(false); } } async function savePriceMultiplier(markupMultiplier: string) { if (!priceEditState) return; const { rule, target } = priceEditState; const saved = await mutate(`/api/admin/billing/prices/${encodeURIComponent(rule.id)}`, { markupMultiplier, ...(target.dimensionKey && target.tierValue !== undefined ? { dimensionKey: target.dimensionKey, tierValue: target.tierValue } : {}) }, `${target.label}倍率已更新。`); if (saved) setPriceEditState(null); } async function mutate(url: string, body: Record, successMessage: string, method = "PATCH"): Promise { setSaving(true); setError(null); setNotice(null); try { const response = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const payload = await readApiPayload>(response); if (!response.ok) throw new Error(payload.error || "保存失败"); setNotice(successMessage); await load(); return true; } catch (nextError) { setError(nextError instanceof Error ? nextError.message : String(nextError)); return false; } finally { setSaving(false); } } return (

计费中心

{isSuperAdmin ? "超级管理员" : "成员视图"}
{error ?
{error}
: null} {notice ?
{notice}
: null}
{billing ? : loading ? : null} {billing ? : null} {!isSuperAdmin && billing ? ( <> {activeTab === "overview" ? <> setActiveTab("ledger")} />} /> : null} {activeTab === "ledger" ? 最近 {Math.min(billing.ledger.length, 100)} 条} /> : null} ) : null} {isSuperAdmin && admin && billing ? ( <> {activeTab === "overview" ? : null} {activeTab === "pricing" ? setPriceEditState({ rule, target })} /> : null} {activeTab === "balance" ? : null} {activeTab === "account" ? setEditingAccount(true)} onCancel={() => { setEditingAccount(false); setAccountDraft({ ...emptyAccountDraft, ...admin.billingAccount }); }} onChange={(key, value) => setAccountDraft((current) => ({ ...current, [key]: value }))} onSubmit={saveBillingAccount} /> : null} ) : null} {priceEditState ? setPriceEditState(null)} onSubmit={savePriceMultiplier} /> : null}
); } async function readApiPayload>(response: Response): Promise { const text = await response.text(); if (!text.trim()) { return { error: response.status >= 500 ? "计费服务暂时不可用,请检查服务端日志和 PostgreSQL 迁移状态。" : "服务器未返回有效内容。" } as T & { error?: string }; } try { return JSON.parse(text) as T & { error?: string }; } catch { return { error: response.status >= 500 ? "计费服务返回了服务器错误,请检查服务端日志,并确认已执行 npm run db:migrate。" : `服务器返回了无效响应(HTTP ${response.status})。` } as T & { error?: string }; } } function BillingTabs({ tabs, activeTab, onChange }: { tabs: BillingTabDefinition[]; activeTab: BillingTabId; onChange: (id: BillingTabId) => void }) { const tabRefs = useRef>([]); function moveTab(event: KeyboardEvent, index: number) { if (!(["ArrowLeft", "ArrowRight", "Home", "End"] as string[]).includes(event.key)) return; event.preventDefault(); const nextIndex = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length; const next = tabs[nextIndex]; if (!next) return; onChange(next.id); window.requestAnimationFrame(() => tabRefs.current[nextIndex]?.focus()); } return ; } function buildAdminBillingPayload(admin: AdminPayload): BillingPayload { const wallet = admin.organizations.reduce((total, organization) => ({ organizationId: "platform", balanceFen: total.balanceFen + organization.wallet.balanceFen, totalRechargedFen: total.totalRechargedFen + organization.wallet.totalRechargedFen, totalChargedFen: total.totalChargedFen + organization.wallet.totalChargedFen, updatedAt: total.updatedAt > organization.wallet.updatedAt ? total.updatedAt : organization.wallet.updatedAt }), { organizationId: "platform", balanceFen: 0, totalRechargedFen: 0, totalChargedFen: 0, updatedAt: new Date(0).toISOString() }); return { organization: { id: "platform", name: "全平台组织" }, billingAccount: admin.billingAccount, wallet, ledger: admin.ledger, summary: summarizeLedger(admin.ledger), personal: emptyLedgerSummary() }; } function summarizeLedger(entries: LedgerEntry[]): LedgerSummary { const rechargeFen = entries .filter((entry) => entry.kind === "recharge" || entry.kind === "adjustment" && entry.deltaFen > 0) .reduce((sum, entry) => sum + Math.max(0, entry.deltaFen), 0); const chargedFen = entries .filter((entry) => entry.kind === "charge") .reduce((sum, entry) => sum + Math.max(0, -entry.deltaFen), 0); const refundedFen = entries .filter((entry) => entry.kind === "refund") .reduce((sum, entry) => sum + Math.max(0, entry.deltaFen), 0); return { rechargeFen, chargedFen, refundedFen, netConsumedFen: Math.max(0, chargedFen - refundedFen) }; } function emptyLedgerSummary(): LedgerSummary { return { rechargeFen: 0, chargedFen: 0, refundedFen: 0, netConsumedFen: 0 }; } function AdminOverview({ billing, admin, onChangeTab }: { billing: BillingPayload; admin: AdminPayload; onChangeTab: (id: BillingTabId) => void }) { const enabledRules = admin.priceRules.filter((rule) => rule.enabled).length; return
组织流水} />
; } function PriceManagement({ admin, saving, onEdit }: { admin: AdminPayload; saving: boolean; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void }) { return

价格目录

标准成本×倍率=用户价
; } function BalanceManagement({ admin, adjustmentDraft, setAdjustmentDraft, selectedOrganization, saving, onSubmit }: { admin: AdminPayload; adjustmentDraft: AdjustmentDraft; setAdjustmentDraft: (draft: AdjustmentDraft) => void; selectedOrganization?: AdminPayload["organizations"][number]; saving: boolean; onSubmit: (event: FormEvent) => void }) { return
{admin.organizations.length} 个组织} />

组织钱包

组织成员用量

余额和上账都归组织所有,成员数据仅用于查看各自的额度消耗。

; } function AdjustmentForm({ draft, selectedOrganization, organizations, saving, onChange, onSubmit }: { draft: AdjustmentDraft; selectedOrganization?: AdminPayload["organizations"][number]; organizations: AdminPayload["organizations"]; saving: boolean; onChange: (draft: AdjustmentDraft) => void; onSubmit: (event: FormEvent) => void }) { return
手工上账 / 余额调整
组织当前余额 {formatBillingAmount(selectedOrganization?.wallet.balanceFen || 0)}
onChange({ ...draft, organizationId: value })} options={organizations.map((organization) => ({ value: organization.id, label: `${organization.name}${organization.status === "active" ? "" : "(已停用)"}` }))} /> onChange({ ...draft, direction: value as AdjustmentDraft["direction"] })} options={[{ value: "credit", label: "上账增加余额" }, { value: "debit", label: "扣减余额" }]} /> onChange({ ...draft, amountYuan: value })} placeholder="例如 1000" required inputMode="decimal" />
onChange({ ...draft, note: value })} placeholder="例如:2026 年度服务预存" required />
本次变动全部计入组织额度,管理员与员工共同使用
; } function AccountSettings({ account, draft, editing, saving, onEdit, onCancel, onChange, onSubmit }: { account: BillingAccountConfig; draft: BillingAccountConfig; editing: boolean; saving: boolean; onEdit: () => void; onCancel: () => void; onChange: (key: keyof BillingAccountConfig, value: string) => void; onSubmit: (event: FormEvent) => void }) { return
编辑账户 : null} />
{editing ?
配置对公账户
onChange("accountName", value)} placeholder="公司或平台对公账户名称" /> onChange("bankName", value)} placeholder="例如:中国银行北京分行" /> onChange("accountNumber", value)} placeholder="对公银行账号" /> onChange("contact", value)} placeholder="联系人、电话或邮箱" />
留空字段会在成员端显示为未配置
: null}
; } function BillingOverview({ billing, isSuperAdmin }: { billing: BillingPayload; isSuperAdmin: boolean }) { return
组织可用余额{billing.organization.name}
共享账本
{formatBillingAmount(billing.wallet.balanceFen)}
累计充值 {formatBillingAmount(billing.summary.rechargeFen)}累计扣费 {formatBillingAmount(billing.summary.chargedFen)}{isSuperAdmin ? "账本状态" : "我的净消耗"} {isSuperAdmin ? "正常" : formatBillingAmount(billing.personal.netConsumedFen)}
我的净消耗{formatBillingAmount(billing.personal.netConsumedFen)}
组织净消耗{formatBillingAmount(billing.summary.netConsumedFen)}
; } function LedgerSection({ entries, title, limit = 100, action }: { entries: LedgerEntry[]; title: string; limit?: number; action?: ReactNode }) { return
; } function TabLink({ label, onClick }: { label: string; onClick: () => void }) { return ; } function BillingLoading() { return
; } function SectionHeading({ title, action }: { title: string; action?: ReactNode }) { return

{title}

{action ?
{action}
: null}
; } function BillingField({ label, value, onChange, placeholder, required = false, inputMode }: { label: string; value: string; onChange: (value: string) => void; placeholder?: string; required?: boolean; inputMode?: "decimal" | "tel" | "text" }) { return ; } function BillingSelect({ label, value, onChange, options }: { label: string; value: string; onChange: (value: string) => void; options: Array<{ value: string; label: string }> }) { return ; } function BillingAccountCard({ account, admin = false }: { account: BillingAccountConfig; admin?: boolean }) { const configured = Boolean(account.accountName || account.bankName || account.accountNumber); return

对公收款账户

{configured ? "已配置" : "待配置"}
{configured ?
:

超级管理员尚未配置对公收款账户信息,请先在收款设置中补充。

}
; } function AccountField({ label, value }: { label: string; value?: string }) { return
{label}{value || "—"}
; } function LedgerTable({ entries }: { entries: LedgerEntry[] }) { return
事项变动 / 余额
{entries.length ? entries.map((entry) =>
{entry.description}{entry.kind !== "recharge" && entry.kind !== "adjustment" && entry.metadata.accountName ? ` · ${String(entry.metadata.accountName)}` : entry.jobId ? ` · 任务 ${entry.jobId.slice(0, 10)}` : ""}
{entry.deltaFen < 0 ? "−" : "+"}{formatBillingAmount(Math.abs(entry.deltaFen))}余额 {formatBillingAmount(entry.balanceAfterFen)}
) :
暂无账务流水
}
; } function PriceMultiplierDialog({ state, saving, onClose, onSubmit }: { state: PriceEditState; saving: boolean; onClose: () => void; onSubmit: (value: string) => Promise; }) { const [value, setValue] = useState(String(state.target.markupMultiplier)); const inputRef = useRef(null); const standardUnitPriceFen = priceEditStandardUnitPriceFen(state.rule, state.target); const parsedValue = Number(value); const valid = value.trim() !== "" && Number.isFinite(parsedValue) && parsedValue >= 1 && parsedValue <= 1000; const currentCustomerPrice = customerUnitPriceFen(standardUnitPriceFen, state.target.markupMultiplier); const nextCustomerPrice = valid ? customerUnitPriceFen(standardUnitPriceFen, parsedValue) : null; useEffect(() => { setValue(String(state.target.markupMultiplier)); window.requestAnimationFrame(() => inputRef.current?.focus()); }, [state.rule.id, state.target.dimensionKey, state.target.tierValue, state.target.markupMultiplier]); useEffect(() => { function handleKeyDown(event: globalThis.KeyboardEvent) { if (event.key === "Escape" && !saving) onClose(); } document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [onClose, saving]); function submit(event: FormEvent) { event.preventDefault(); if (!valid || saving) return; void onSubmit(value.trim()); } return (
{ if (event.target === event.currentTarget && !saving) onClose(); }}>
event.stopPropagation()}>

调整上浮倍率

{state.target.label}

标准成本{formatBillingAmount(standardUnitPriceFen)} / {billingUnitLabel(state.rule.unit)}
当前用户价{formatBillingAmount(currentCustomerPrice)}
调整后用户价{nextCustomerPrice === null ? "—" : formatBillingAmount(nextCustomerPrice)}
); } function priceEditStandardUnitPriceFen(rule: BillingPriceRule, target: PriceEditTarget): number { if (!target.dimensionKey || target.tierValue === undefined) return rule.standardUnitPriceFen; const dimension = rule.parameterDimensions?.find((item) => item.key === target.dimensionKey); const tier = dimension?.tiers.find((item) => String(item.value) === target.tierValue); return Math.ceil(rule.standardUnitPriceFen * Number(tier?.standardFactor || 1)); } function PriceCatalog({ rules, onEdit, disabled }: { rules: BillingPriceRule[]; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void; disabled: boolean }) { const groups = groupPriceRules(rules); return
{groups.length ? groups.map(([key, group]) => group.length === 1 ? : ) :
暂无平台价格标准。
}
; } function groupPriceRules(rules: BillingPriceRule[]): Array<[string, BillingPriceRule[]]> { const groups = new Map(); for (const rule of rules) { const key = [rule.provider, rule.capability, rule.reqKey || ""].join("\u0000"); const group = groups.get(key) || []; group.push(rule); groups.set(key, group); } return [...groups.entries()]; } function PriceServiceCard({ rule, onEdit, disabled }: { rule: BillingPriceRule; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void; disabled: boolean }) { const dimensions = rule.parameterDimensions?.filter((dimension) => dimension.tiers.length) || []; return
{providerName(rule.provider).slice(0, 2)}
{providerName(rule.provider)} · {rule.capability === "video.generate" ? "视频生成" : "图片生成"}{rule.reqKey || "默认服务"}{rule.variantKey ? ` · ${rule.variantKey}` : ""}
基准成本{formatBillingAmount(rule.standardUnitPriceFen)}/{billingUnitLabel(rule.unit)}
{rule.source?.url ? 查看价格来源 : 平台标准目录} {rule.note || "平台标准目录"}
{dimensions.length ?
{dimensions.map((dimension) => )}
: }
; } function PriceServiceGroup({ rules, onEdit, disabled }: { rules: BillingPriceRule[]; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void; disabled: boolean }) { const rule = rules[0]; return
{providerName(rule.provider).slice(0, 2)}
{providerName(rule.provider)} · {rule.capability === "video.generate" ? "视频生成" : "图片生成"}{rule.reqKey || "默认服务"}
平台参数档位{rules.length} 个
{rule.source?.url ? 查看价格来源 : 平台标准目录} 平台标准目录按参数档位列出,用户选择后自动匹配。
参数档位不同标准费率独立计费,倍率仅影响用户价
参数档位标准成本用户价倍率操作
{rules.map((item) => )}
; } function PriceDimension({ rule, dimension, onEdit, disabled }: { rule: BillingPriceRule; dimension: BillingParameterDimension; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void; disabled: boolean }) { const baseline = dimension.tiers.find((tier) => String(tier.value).toLowerCase() === String(dimension.baselineValue).toLowerCase()); return
{dimension.label}基准档位:{baseline?.label || String(dimension.baselineValue)} · 组合报价按实际选择自动计算
参数档位标准成本用户价倍率操作
{dimension.tiers.map((tier) => )}
; } function PriceTierRow({ rule, dimension, tier, onEdit, disabled }: { rule: BillingPriceRule; dimension: BillingParameterDimension; tier: BillingParameterTier; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void; disabled: boolean }) { const standardUnitPriceFen = Math.ceil(rule.standardUnitPriceFen * tier.standardFactor); return
{tier.label}{String(tier.value)}{tier.note ? ` · ${tier.note}` : ""}
{formatBillingAmount(standardUnitPriceFen)}/{billingUnitLabel(rule.unit)}
{formatBillingAmount(customerUnitPriceFen(standardUnitPriceFen, tier.markupMultiplier))}/{billingUnitLabel(rule.unit)}
{tier.markupMultiplier.toFixed(2)}×
; } function LegacyPriceRow({ rule, onEdit, disabled }: { rule: BillingPriceRule; onEdit: (rule: BillingPriceRule, target: PriceEditTarget) => void; disabled: boolean }) { return
{rule.variantKey || (describeRuleConditions(rule) || "默认参数")}{rule.enabled ? "平台标准档位" : "已停用"}
{formatBillingAmount(rule.standardUnitPriceFen)}/{billingUnitLabel(rule.unit)}
{formatBillingAmount(customerUnitPriceFen(rule.standardUnitPriceFen, rule.markupMultiplier))}/{billingUnitLabel(rule.unit)}
{rule.markupMultiplier.toFixed(2)}×
; } function WalletTable({ wallets }: { wallets: AdminPayload["organizations"] }) { return
组织余额 / 累计扣费
{wallets.length ? wallets.map((item) =>
{item.name}{item.status === "active" ? "正常" : "已停用"}
{formatBillingAmount(item.wallet.balanceFen)}累计扣费 {formatBillingAmount(item.wallet.totalChargedFen)}
) :
暂无组织
}
; } function MemberBalanceTable({ members, entries }: { members: AdminMember[]; entries: LedgerEntry[] }) { const organizationMembers = members.filter((member) => member.organizationId); return
成员净消耗
{organizationMembers.length ? organizationMembers.map((member) => { const usage = memberUsage(entries, member.id); return
{member.displayName}{member.phone}{member.status === "disabled" ? " · 已停用" : ""}
{formatBillingAmount(usage.netConsumedFen)}扣费 {formatBillingAmount(usage.chargedFen)} · 退款 {formatBillingAmount(usage.refundedFen)}
; }) :
暂无组织成员
}
; } function memberUsage(entries: LedgerEntry[], accountId: string) { const personal = entries.filter((entry) => entry.accountId === accountId); const chargedFen = personal.filter((entry) => entry.kind === "charge").reduce((sum, entry) => sum + Math.max(0, -entry.deltaFen), 0); const refundedFen = personal.filter((entry) => entry.kind === "refund").reduce((sum, entry) => sum + Math.max(0, entry.deltaFen), 0); return { chargedFen, refundedFen, netConsumedFen: Math.max(0, chargedFen - refundedFen) }; } function customerUnitPriceFen(standardUnitPriceFen: number, markupMultiplier: number): number { return Math.ceil(standardUnitPriceFen * markupMultiplier); } function describeRuleConditions(rule: BillingPriceRule): string { const entries = Object.entries(rule.conditions || {}); if (!entries.length) return ""; return entries.map(([key, value]) => `${conditionName(key)}=${conditionValueLabel(value)}`).join(" · "); } function conditionName(key: string) { const labels: Record = { resolution: "分辨率", size: "尺寸", aspectRatio: "比例", quality: "质量", duration: "时长", imageCount: "张数", referenceImageCount: "参考图", model: "模型" }; return labels[key] || key; } function conditionValueLabel(value: unknown): string { if (typeof value === "object" && value !== null && !Array.isArray(value)) { const item = value as { min?: number; max?: number; values?: unknown[] }; if (item.values?.length) return item.values.join("/"); if (item.min !== undefined || item.max !== undefined) return `${item.min ?? "-∞"}~${item.max ?? "+∞"}`; } return String(value); } function providerName(provider: string) { if (provider === "volcengine-visual") return "即梦"; if (provider === "evolink") return "EvoLink"; if (provider === "seedance") return "Seedance"; if (provider === "bailian") return "百炼"; return provider; } function formatTime(value: string) { return new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }).format(new Date(value)); }