diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 7c21177f0..f4b050513 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,6 +5,16 @@ description: 当前项目已实现功能的手工验证清单与未确认边界 # 待测试 +## 管理后台轻边界与高密度改造(第一轮) + +- [x] 公共后台外壳统一浅深色表面、弱化重复外框,保留表格行分隔、输入边界和键盘焦点;导航按监控排查、模型资源、用户运营、平台配置组织。表格默认紧凑模式,支持舒适模式,偏好按账号保存。 +- [x] 请求明细增加排障、计费、全部字段三种视图,保留详情抽屉、媒体、完整计费与导出入口;总览压缩为六项指标及紧凑趋势区,系统性能压缩装饰留白。统计和请求加载失败明确提示,性能刷新失败标记旧快照,首次采集失败不展示假健康指标。 +- [x] 36 项专项测试通过(330 个断言),覆盖密度偏好、请求状态、账务状态、后台样式合同、渠道工作区、统计财务及主题;前端生产构建通过,仍有部分 chunk 超过 500 kB 的提示。 +- [x] 独立浏览器在真实应用路由使用明确的测试夹具验证,未写入真实业务数据:1440 × 900 下请求紧凑行高 36 px、首屏 18 条完整记录,舒适行高 44 px,切换后刷新保留偏好;排障及计费视图无需横向滚动,全部字段保留横向滚动;375 / 768 px 下页面不横向溢出,表格内部可滚动。浅深主题、键盘进入详情、总览筛选展开收起与 98.6% 精度显示已检查。 +- [x] 测试夹具覆盖统计读取失败、请求读取失败、性能刷新失败及首次采集失败;缓存清理仅检查确认弹窗与取消,未执行清理。 +- [ ] 使用真实管理员账号验收实际筛选与分页、CSV 导出、媒体预览下载、请求详情及真实数据权限。浏览器夹具检查不等于后端联调通过。 +- [ ] 复核渠道、模型、用户、运营及其他设置页在公共样式变化后的长内容、固定列、浮层、滚动及不同站点皮肤;本轮并未逐页重做或完成全后台验收。 + ## 批量生图、刷新恢复与画布节点一致性 - [x] 专项回归 127 项通过,覆盖同步编辑命令、按节点结果提交、批次汇总、资源槽位、空间索引、相邻媒体逻辑、revision 合并与存储清理;前端类型检查通过。 @@ -541,6 +551,11 @@ PR #507 核心能力已按主分支实现保留,并补齐小视口尺寸约束 - [ ] 亮/暗切换只改变后台冷灰台阶,不套用当前产品皮肤的圆角、主色或动效。 - [ ] 抽屉、下拉、确认框仍可操作;关闭积分/前台模型仍有确认。用户详情、支付订单、提示词版本、模型编辑器不再使用产品 `AppDrawer`/`AppModal`。 +本轮继续收敛后台的信息密度与运维扫描路径:用户管理操作区收敛为「详情 / 停用 / 更多」,注册时间改为紧凑展示并保留完整时间 title;用户列表读取失败会清空旧数据,在表格内显示错误与「重试」,避免把过期列表当成最新数据。系统渠道页压缩渠道列表、模型管理和重定价区域的间距,让首屏能看到更多模型配置;功能开放页去除功能域外层重边框,改用层级背景和行级必要分隔。真实管理员权限、用户 CRUD、渠道保存/模型编辑仍待验收:本轮只用隔离 fixture 验证真实路由渲染与失败态,不代表真实后端写入已通过。 + +- [x] 后台专项测试 5 个文件共 36 项通过,覆盖密度偏好、请求明细、分析计费、主题隔离、用户/渠道回归;前端 TypeScript 检查与生产构建通过(仍有既有大分包提示)。 +- [ ] 使用真实管理员账号复验用户筛选、分页、详情、停用/删除权限与失败重试;验证真实渠道保存、模型编辑/重定价和功能开关写入,不用 fixture 结果替代服务端验收。 + ## 模型选择器、画布摘要与首页视频入库 - 全站 `ModelPicker` 统一为先选品牌、再选模型的左右二级菜单(左品牌轨 + 右模型列表),含 Agent 对话、审批卡、章节提取、分镜脚本节点、项目设置。窗口窄于 720px 时才收成单栏。 diff --git a/web/src/pages/admin/admin-route-pages.tsx b/web/src/pages/admin/admin-route-pages.tsx index dda948eb0..52f13b558 100644 --- a/web/src/pages/admin/admin-route-pages.tsx +++ b/web/src/pages/admin/admin-route-pages.tsx @@ -19,7 +19,7 @@ const AgentLessonsPanel = lazy(() => import("./components/agent-lessons-panel")) export function AnalyticsPage() { const { references } = useAdminContext(); return ( - + ); diff --git a/web/src/pages/admin/channels/channels-page.css b/web/src/pages/admin/channels/channels-page.css index 985d58907..fff46235c 100644 --- a/web/src/pages/admin/channels/channels-page.css +++ b/web/src/pages/admin/channels/channels-page.css @@ -23,8 +23,8 @@ display: flex; flex: none; flex-direction: column; - gap: 10px; - padding: 16px; + gap: 8px; + padding: 12px; border-bottom: 1px solid var(--admin-divider); } @@ -40,8 +40,8 @@ display: flex; width: 100%; flex-direction: column; - gap: 10px; - padding: 12px; + gap: 7px; + padding: 9px 10px; margin-bottom: 4px; border: 0; border-radius: var(--admin-radius); @@ -136,7 +136,7 @@ .admin-channel-sidebar-footer { flex: none; - padding: 12px 16px; + padding: 10px 12px; border-top: 1px solid var(--admin-divider); } @@ -150,7 +150,7 @@ flex-wrap: wrap; align-items: center; gap: 12px; - padding: 16px 20px; + padding: 12px 16px; border-bottom: 1px solid var(--admin-divider); } @@ -176,7 +176,7 @@ min-width: 0; min-height: 0; flex-direction: column; - padding: 16px; + padding: 12px; } .admin-channel-model-toolbar { @@ -185,8 +185,8 @@ flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 12px; - margin-bottom: 12px; + gap: 8px; + margin-bottom: 8px; } .admin-channel-model-heading { @@ -223,7 +223,7 @@ flex-direction: column; flex: 1; min-height: 0; - gap: 16px; + gap: 12px; padding-block: 8px; } @@ -235,8 +235,8 @@ display: flex; align-items: flex-end; flex-wrap: wrap; - gap: 24px; - padding: 24px; + gap: 16px; + padding: 16px; border-radius: var(--admin-radius); background: var(--admin-surface-2); } diff --git a/web/src/pages/admin/components/admin-density.tsx b/web/src/pages/admin/components/admin-density.tsx new file mode 100644 index 000000000..425b9a4ba --- /dev/null +++ b/web/src/pages/admin/components/admin-density.tsx @@ -0,0 +1,42 @@ +import { createContext, useContext, useState, type ReactNode } from "react"; + +export type AdminDensity = "compact" | "comfortable"; + +export function normalizeAdminDensity(value: string | null): AdminDensity { + return value === "comfortable" ? "comfortable" : "compact"; +} + +export function adminDensityStorageKey(userId: string) { + return `admin-console:${encodeURIComponent(userId)}:density`; +} + +const AdminDensityContext = createContext<{ density: AdminDensity; toggleDensity: () => void }>({ density: "compact", toggleDensity: () => {} }); + +export function AdminDensityProvider({ userId, children }: { userId?: string; children: ReactNode }) { + const [density, setDensity] = useState(() => { + if (!userId || typeof window === "undefined") return "compact"; + try { + return normalizeAdminDensity(window.localStorage.getItem(adminDensityStorageKey(userId))); + } catch (error) { + console.warn("无法读取后台密度偏好,使用紧凑布局", error); + return "compact"; + } + }); + + const toggleDensity = () => { + const next = density === "compact" ? "comfortable" : "compact"; + setDensity(next); + if (!userId) return; + try { + window.localStorage.setItem(adminDensityStorageKey(userId), next); + } catch (error) { + console.warn("后台密度已切换,但无法保存偏好", error); + } + }; + + return {children}; +} + +export function useAdminDensity() { + return useContext(AdminDensityContext); +} diff --git a/web/src/pages/admin/components/admin-shell.tsx b/web/src/pages/admin/components/admin-shell.tsx index 6c91c56d4..be642595f 100644 --- a/web/src/pages/admin/components/admin-shell.tsx +++ b/web/src/pages/admin/components/admin-shell.tsx @@ -27,6 +27,7 @@ import { PlugZap, RadioTower, RefreshCw, + Rows3, Sparkles, Settings2, ShieldAlert, @@ -48,6 +49,7 @@ import { useUserStore } from "@/stores/use-user-store"; import { useAppearanceStore } from "@/stores/use-appearance-store"; import { getIsolatedAdminAntTheme } from "../theme/admin-ant-theme"; import { AdminTooltip } from "../ui/controls"; +import { AdminDensityProvider, useAdminDensity } from "./admin-density"; import "@/styles/admin-ui.css"; import "../theme/admin-tokens.css"; import "../theme/admin-chrome.css"; @@ -62,40 +64,42 @@ type AdminNavigationItem = { const adminNavigation: Array<{ label: string; items: AdminNavigationItem[] }> = [ { - label: "概览", - items: [{ path: "/admin", label: "数据概览", description: "活跃、调用与成本趋势", icon: }], + label: "监控与排查", + items: [ + { path: "/admin", label: "运行总览", description: "请求质量、队列与成本趋势", icon: }, + { path: "/admin/logs", label: "请求明细", description: "上游调用与费用", icon: }, + { path: "/admin/settings/system-performance", label: "系统性能", description: "主机、数据库与缓存状态", icon: }, + ], }, { - label: "平台资源", + label: "模型与资源", items: [ - { path: "/admin/users", label: "用户管理", description: "账号、角色与状态", icon: }, { path: "/admin/channels", label: "系统渠道", description: "渠道、模型与售价", icon: }, { path: "/admin/models", label: "前台模型", description: "展示、线路与用户价格", icon: , requireFeature: "frontendModelsEnabled" }, - { path: "/admin/plugins", label: "插件管理", description: "平台可用性、上传与卸载", icon: }, { path: "/admin/prompt-templates", label: "提示词模板", description: "平台创作策略版本", icon: }, { path: "/admin/resources", label: "存储资源", description: "资源列表、容量与预览", icon: }, ], }, { - label: "运营", + label: "用户与运营", items: [ + { path: "/admin/users", label: "用户管理", description: "账号、角色与状态", icon: }, { path: "/admin/announcements", label: "系统公告", description: "发布、关闭与历史公告", icon: }, { path: "/admin/banner-announcements", label: "常驻通知", description: "首页顶部常驻滚动通知", icon: }, { path: "/admin/agent-lessons", label: "Agent 记忆", description: "按用户查看个人记忆", icon: }, { path: "/admin/payments", label: "支付充值", description: "支付渠道、订单与对账", icon: }, { path: "/admin/credit-operations", label: "积分运营", description: "人工调账与异常计费", icon: }, { path: "/admin/redemption-codes", label: "兑换码", description: "生成与查看兑换码批次", icon: }, - { path: "/admin/logs", label: "请求明细", description: "上游调用与费用", icon: }, ], }, { - label: "系统配置", + label: "平台配置", items: [ + { path: "/admin/plugins", label: "插件管理", description: "平台可用性、上传与卸载", icon: }, { path: "/admin/settings/appearance", label: "站点及外观", description: "品牌、SEO、备案与皮肤", icon: }, { path: "/admin/settings/features", label: "功能开放", description: "工作台、插件与模型能力", icon: }, { path: "/admin/settings/drawing-engine", label: "绘图工具", description: "画布绘图节点默认引擎", icon: }, { path: "/admin/settings/runtime-policy", label: "资源与策略", description: "配额、并发、频控与超时", icon: }, - { path: "/admin/settings/system-performance", label: "系统性能", description: "主机、数据库与缓存状态", icon: }, { path: "/admin/settings/access", label: "登录与注册", description: "账号创建与第三方登录", icon: }, { path: "/admin/settings/email", label: "邮件服务", description: "注册验证码与 SMTP", icon: }, { path: "/admin/settings/storage", label: "存储服务", description: "对象存储与资源存储", icon: }, @@ -119,6 +123,12 @@ function adminPopupContainer(node?: HTMLElement) { } export function AdminShell() { + const userId = useUserStore((state) => state.user?.id); + return ; +} + +function AdminShellLayout() { + const { density } = useAdminDensity(); const appearance = useAppearanceStore((state) => state.appearance); const [collapsed, setCollapsed] = useState(readWorkspaceSidebarCollapsed); const dark = useThemeStore((state) => state.theme === "dark"); @@ -141,7 +151,7 @@ export function AdminShell() { return ( adminPopupContainer(node)}> -
+