refactor(admin): 配置按域收口到「系统配置」页,Usage 只留用量观测

原本模型配置在「模型」页、计费规则与支付渠道却塞在「分析 > 计费&用量」观测页里(该页
~1290 行,首屏全是表单、真正的用量观测在第四屏)。按「域」重组:

- 新建「系统配置」页,两个 Tab:
  · 模型 = 模型登记/激活/连通性测试 + 计费规则(汇率/硬拦截/按模型定价与权重)
  · 支付 = 支付渠道配置(兑换码/积分包/微信参数) + 订单流水(订单流/对账/人工退款)
  配了什么就在同处看它的规则与数据,不用来回跳。三块直接复用原组件,零重写。
- 「计费 & 用量」瘦成纯用量观测(消耗趋势/租户排行/发放积分)。
- 导航「模型」页并入系统配置(12 页 → 11),侧栏图标换齿轮。
- 顺带修残留 bug:数据源页「前往模型管理」原为 href=#/models + onClick 改 hash,
  既指向已废弃路径、又是 hash 写法(早已换 BrowserRouter,点了不跳),改正规 <Link to=/admin/settings>。

tsc + build + vitest 41 过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-20 10:19:32 +08:00
parent d189dd967a
commit c3151d1078
5 changed files with 78 additions and 26 deletions
+6 -9
View File
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { adminDatasources, listModels, migrateKbStorage, type DatasourceKB, type MigrateKbResult } from "../api";
// 数据源 & RAG:真数据(全平台知识库清单,来自 sundynix_kb/sundynix_doc)。
@@ -71,16 +72,12 @@ export function DatasourcesPage() {
<code className="text-xs font-mono font-medium text-gray-800">{activeEmbedding}</code>
</div>
</div>
<a
href="#/models"
onClick={(e) => {
// 如果使用 hash 路由或 react-router,可正常导航。这里提示用户去模型页配置
window.location.hash = "#/models";
}}
className="text-xs text-violet-600 hover:text-violet-700 font-medium hover:underline flex items-center gap-1"
<Link
to="/admin/settings"
className="flex items-center gap-1 text-xs font-medium text-violet-600 hover:text-violet-700 hover:underline"
>
</a>
</Link>
</div>
{/* 运维工具 (RAG 迁移) */}
+64
View File
@@ -0,0 +1,64 @@
import { useState } from "react";
import { ModelsPage } from "./ModelsPage";
import { BillingRules } from "../components/BillingRules";
import { TopupChannels } from "../components/TopupChannels";
import { OrderStream } from "../components/OrderStream";
// 系统配置:按「域」聚合 —— 配了什么,就在同一处看它相关的规则与数据。
// 模型 = 模型登记/激活/连通性测试 + 计费规则(token→积分汇率、硬拦截、按模型定价与权重)
// 支付 = 支付渠道配置(兑换码 / 积分包 / 微信参数)+ 订单流水(订单流、对账、人工退款)
// 「计费 & 用量」页只留纯用量观测(消耗趋势/租户排行/发放积分),不再混配置。
// 提示词有版本历史 + diff + 激活的完整工作流,保留独立页,不塞进 Tab。
type Tab = "models" | "payment";
const TABS: Array<{ key: Tab; label: string; desc: string }> = [
{ key: "models", label: "模型", desc: "模型登记与激活 · 计费规则与按模型定价" },
{ key: "payment", label: "支付", desc: "支付渠道配置 · 订单流水与对账退款" },
];
export function SettingsPage() {
const [tab, setTab] = useState<Tab>("models");
const current = TABS.find((t) => t.key === tab);
return (
<div className="space-y-5">
<div className="flex flex-wrap items-center justify-between gap-4 border-b border-gray-200 pb-4">
<div>
<h3 className="text-base font-semibold text-gray-800"></h3>
<p className="text-xs text-gray-400">{current?.desc}</p>
</div>
<div className="flex rounded-lg border border-gray-200 bg-gray-50/50 p-1">
{TABS.map((t) => (
<button
key={t.key}
onClick={() => setTab(t.key)}
className={`rounded-md px-4 py-1.5 text-xs font-medium transition-all ${
tab === t.key ? "bg-white text-violet-700 shadow-sm" : "text-gray-500 hover:text-gray-700"
}`}
>
{t.label}
</button>
))}
</div>
</div>
{/* 模型域:先登记模型,紧接着配它的计价规则 */}
{tab === "models" && (
<div className="space-y-6">
<ModelsPage />
<BillingRules />
</div>
)}
{/* 支付域:先配渠道,紧接着看这些渠道产生的订单流水与对账 */}
{tab === "payment" && (
<div className="space-y-6">
<TopupChannels />
<OrderStream />
</div>
)}
</div>
);
}
+1 -11
View File
@@ -1,8 +1,5 @@
import { useEffect, useMemo, useState, type ReactNode } from "react";
import { adminUsage, listTenants, type TenantRow, type UsageReport, type UsageTenantSum, type UsageDay } from "../api";
import { BillingRules } from "../components/BillingRules";
import { TopupChannels } from "../components/TopupChannels";
import { OrderStream } from "../components/OrderStream";
import { GrantCreditsModal } from "../components/GrantCreditsModal";
@@ -109,14 +106,7 @@ export function UsagePage() {
{/* 发放积分 Modal */}
<GrantCreditsModal tenant={grantTenant} onClose={() => setGrantTenant(null)} onDone={() => void load()} />
{/* 配置端:计费规则(改规则即对后续任务生效) */}
<BillingRules onSaved={() => void load()} />
{/* 配置端:充值渠道(兑换码生成/台账 + 积分包定价 + 微信配置,P5.1/P5.2 */}
<TopupChannels />
{/* 观测端:充值订单流 + 对账(P5.3) */}
<OrderStream />
{/* 本页只做「用量」观测。模型/计价、支付渠道/订单流水均按域收口到「系统配置」页。 */}
{/* 观测端:用量结果 */}
<div className="flex items-center gap-2 pt-1">
+4 -4
View File
@@ -6,7 +6,7 @@ import { lazy, type ReactNode } from "react";
const DashboardPage = lazy(() => import("./pages/DashboardPage").then((m) => ({ default: m.DashboardPage })));
const UsagePage = lazy(() => import("./pages/UsagePage").then((m) => ({ default: m.UsagePage })));
const ModelsPage = lazy(() => import("./pages/ModelsPage").then((m) => ({ default: m.ModelsPage })));
const SettingsPage = lazy(() => import("./pages/SettingsPage").then((m) => ({ default: m.SettingsPage })));
const DatasourcesPage = lazy(() => import("./pages/DatasourcesPage").then((m) => ({ default: m.DatasourcesPage })));
const StatusPage = lazy(() => import("./pages/StatusPage").then((m) => ({ default: m.StatusPage })));
const TasksPage = lazy(() => import("./pages/TasksPage").then((m) => ({ default: m.TasksPage })));
@@ -41,11 +41,11 @@ export const routes: RouteDef[] = [
element: <UsagePage />,
},
{
path: "models",
label: "模型",
path: "settings",
label: "系统配置",
group: "配置",
ready: true,
element: <ModelsPage />,
element: <SettingsPage />,
},
{
path: "datasources",
+3 -2
View File
@@ -18,9 +18,10 @@ const ICON_MAP: Record<string, React.ReactNode> = {
<path strokeLinecap="round" strokeLinejoin="round" d="M9 8h6m-5 0a3 3 0 110 6m0-6V7a1 1 0 112 0v1m-1 5a1.5 1.5 0 100-3m0 3v1m0-1a1.5 1.5 0 100-3m-3-3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
models: (
settings: (
<svg className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
),
datasources: (