feat: 接入运维真实数据与三角色权限链路

- 新增 model_deploy 与 model_operations 双库查询,支持模型列表、模型详情、单月监控结果和运维工作台真实接口。

- 关闭运维模块生产 Mock 数据,补充缺表/空数据降级、工作台空状态和首条纵向链路测试。

- 按业务团队、模型团队、管理员权限矩阵接入菜单、页面、操作按钮和后端接口权限校验,支持 business_team 角色。

- 更新工作台布局、深色欢迎卡片、全宽页面适配、顶部回退,以及权限分组展示。

- 新增架构实现基线、周目标完成情况和角色权限矩阵初始化 SQL 文档。
This commit is contained in:
郑龙捷
2026-09-02 15:02:47 +08:00
parent 6d044784ec
commit 74dd97428f
51 changed files with 2142 additions and 1062 deletions
@@ -26,15 +26,15 @@ export default function UsageStatsPage() {
const model = USAGE_RECORDS.filter((record) => record.team === "模型团队");
const sum = (records: UsageRecord[], key: "logins" | "requests" | "reportsRead" | "reportsDownloaded") => records.reduce((total, record) => total + record[key], 0);
const metrics = [
{ label: "业务团队登录", value: sum(business, "logins"), detail: `${business.length} 人 · 人均 ${(sum(business, "logins") / business.length).toFixed(1)}`, Icon: LogIn },
{ label: "模型团队登录", value: sum(model, "logins"), detail: `${model.length} 人 · 人均 ${(sum(model, "logins") / model.length).toFixed(1)}`, Icon: LogIn },
{ label: "业务团队登录", value: sum(business, "logins"), detail: business.length ? `${business.length} 人 · 人均 ${(sum(business, "logins") / business.length).toFixed(1)}` : "暂无数据", Icon: LogIn },
{ label: "模型团队登录", value: sum(model, "logins"), detail: model.length ? `${model.length} 人 · 人均 ${(sum(model, "logins") / model.length).toFixed(1)}` : "暂无数据", Icon: LogIn },
{ label: "提交需求", value: sum(USAGE_RECORDS, "requests"), detail: "累计业务团队发起", Icon: Send },
{ label: "报告下载", value: sum(USAGE_RECORDS, "reportsDownloaded"), detail: "累计两方合计", Icon: FileDown },
];
return (
<section className="h-full overflow-auto bg-bg p-6">
<div className="mx-auto flex max-w-screen-2xl flex-col gap-6 pb-8">
<div className="flex w-full flex-col gap-6 pb-8">
<OperationsPageHeader
title="平台使用统计"
description="查看业务团队与模型团队的平台使用情况,只做人员维度汇总,不展示操作内容明细。"
@@ -56,10 +56,10 @@ export default function UsageStatsPage() {
<CardContent className="px-0 pt-0">
<Table>
<TableHeader><TableRow><TableHead></TableHead><TableHead></TableHead><TableHead></TableHead><TableHead></TableHead><TableHead></TableHead><TableHead></TableHead><TableHead></TableHead><TableHead></TableHead></TableRow></TableHeader>
<TableBody>{rows.map((record) => {
<TableBody>{rows.length ? rows.map((record) => {
const activity = record.logins >= 40 ? ["高", "bg-success-soft text-success-strong"] : record.logins >= 15 ? ["中", "bg-brand-soft text-primary"] : ["低", "bg-warning-soft text-warning"];
return <TableRow key={`${record.team}-${record.person}`}><TableCell className="font-medium text-foreground">{record.person}</TableCell><TableCell>{record.team}</TableCell><TableCell className="tabular-nums">{record.logins}</TableCell><TableCell className="tabular-nums">{record.requests}</TableCell><TableCell className="tabular-nums">{record.reportsRead}</TableCell><TableCell className="tabular-nums">{record.reportsDownloaded}</TableCell><TableCell className="font-mono text-xs">{record.lastLoginAt}</TableCell><TableCell><span className={`rounded-full px-2.5 py-1 text-xs font-medium ${activity[1]}`}>{activity[0]}</span></TableCell></TableRow>;
})}</TableBody>
}) : <TableRow><TableCell colSpan={8} className="h-32 text-center text-muted-foreground">使</TableCell></TableRow>}</TableBody>
</Table>
</CardContent>
</Card>