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:
@@ -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 迁移) */}
|
||||
|
||||
@@ -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,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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: (
|
||||
|
||||
Reference in New Issue
Block a user