feat(admin): 用量 & 计费页 —— SaaS P2 计量观测面(读 /admin/usage)
管理端「分析」组新增页:全平台 / 单租户的 token · 积分 · 成本口径。 - api.adminUsage():拉 /admin/usage(含 trend / totals / tenants 排行 / 单租户余额); 微单位(×10⁻⁶)前端 ÷1e6 展示。 - UsagePage:租户下拉 + 近7/30天切换;KPI 卡(积分/Token/成本/余额或活跃租户); 积分消耗按天条形趋势(补零连续);全平台口径下各租户排行表(点名下钻单租户)。 - routes 注册 /usage(HashRouter)。 live 验证(preview):全平台两租户排行 + KPI + 趋势条渲染正确;点租户下钻→ 单租户口径 + 当前余额卡;数值与后端自洽;无 console 错误;tsc + 41 vitest 全过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -365,3 +365,43 @@ export async function deactivatePrompt(key: string): Promise<void> {
|
||||
throw new Error(d.error ?? `deactivate failed: ${res.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
// —— 用量 / 计费(SaaS P2 计量:token / 积分 / 成本,按租户)——
|
||||
// 金额与积分均为「微单位」(×10⁻⁶):展示时 ÷1e6。走 /admin/usage(系统级,跨租户)。
|
||||
export interface UsageDay {
|
||||
day: string; // YYYYMMDD
|
||||
total_tok: number;
|
||||
credits_micro: number;
|
||||
cost_micros: number;
|
||||
task_count: number;
|
||||
}
|
||||
export interface UsageTenantSum {
|
||||
tenant_id: string;
|
||||
name: string;
|
||||
total_tok: number;
|
||||
credits_micro: number;
|
||||
cost_micros: number;
|
||||
task_count: number;
|
||||
balance_micro: number;
|
||||
}
|
||||
export interface UsageReport {
|
||||
from: string;
|
||||
to: string;
|
||||
tenant: string; // 空 = 全平台口径
|
||||
trend: UsageDay[];
|
||||
totals: { total_tok: number; credits_micro: number; cost_micros: number; task_count: number };
|
||||
balance_micro?: number; // 仅单租户口径
|
||||
tenants?: UsageTenantSum[]; // 仅全平台口径:各租户排行
|
||||
}
|
||||
|
||||
export async function adminUsage(params?: { tenant?: string; from?: string; to?: string }): Promise<UsageReport> {
|
||||
const q = new URLSearchParams();
|
||||
if (params?.tenant) q.set("tenant", params.tenant);
|
||||
if (params?.from) q.set("from", params.from);
|
||||
if (params?.to) q.set("to", params.to);
|
||||
const qs = q.toString();
|
||||
const res = guard(await fetch(`${ADMIN}/usage${qs ? "?" + qs : ""}`, { headers: authHeaders() }));
|
||||
if (!res.ok) throw new Error(`usage failed: ${res.status}`);
|
||||
const d = (await res.json()) as UsageReport;
|
||||
return { ...d, trend: d.trend ?? [], tenants: d.tenants ?? [] }; // Go 空切片可能序列化为 null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user