`,适合时间序列数据。支持多系列(选择多个数值列)。
- 图表容器高度 `h-[360px]`,空状态显示 `BarChart3` 图标 + "请选择数据列生成图表" 提示。
- 图表配置面板:X 轴选择、Y 轴选择(可多选)、图表标题输入。
---
#### 5.2.3 侧边栏导航优化
**Schema 树搜索过滤**
Schema 侧边栏顶部增加搜索输入框(`Input size="sm"` + `Search` 图标),实时过滤数据库/表/字段名。过滤逻辑:
- 匹配数据库名:展示该数据库下所有表。
- 匹配表名:展示该表及其所在数据库(其他数据库折叠)。
- 匹配字段名:展示该字段及其所属表和数据库。
使用 `fuse.js` 实现模糊搜索,配置 `threshold: 0.3, keys: ['name']`。
**表节点增强**
每个表节点左侧显示 `Table2` 图标(`w-4 h-4 text-muted-foreground`),右侧显示行数 badge(`rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground`)。行数从 `INFORMATION_SCHEMA` 或后端缓存获取,显示 "~" 前缀表示近似值。
**收藏表功能**
表节点 hover 时显示星标按钮(`Star` 图标,`size="sm" variant="ghost"`),点击切换收藏状态。已收藏的表显示实心 `Star` 图标(`fill: hsl(var(--amber-400))`)。收藏表固定在侧边栏顶部"收藏"分组下,分组标题使用 `text-xs font-semibold text-muted-foreground uppercase tracking-wider`。
**最近访问表**
在"收藏"分组下方增加"最近访问"分组,最多展示 10 个最近点击过的表,按访问时间倒序。每个表项显示访问时间相对文本(`2分钟前`)。使用 `lru-cache` 或简单的 `Map` 实现客户端缓存,持久化到 `localStorage`。
**拖拽插入 SQL**
实现从 Schema 树拖拽表名/字段名到编辑器:
- 拖拽开始:设置 `dataTransfer.setData('text/plain', tableName)`,拖拽图像为半透明标签显示表名。
- 拖拽经过编辑器:显示插入位置指示线。
- 拖拽释放:根据拖拽对象类型插入不同内容:
- 表名:插入 `` `tableName` ``
- 字段名:插入 `` `tableName`.`columnName` ``
- 数据库名:插入 `` `databaseName` ``
使用 CodeMirror 6 的 `dropCursor` 扩展显示插入位置。
---
#### 5.2.4 历史记录优化
**分类标签页**
将历史记录抽屉中的列表改为 `shadcn/ui Tabs` 分类:
- 全部(`History` 图标):所有查询记录。
- 成功(`CheckCircle` 图标,绿色):执行成功的查询。
- 失败(`XCircle` 图标,红色):执行失败的查询。
- 收藏(`Star` 图标,琥珀色):用户收藏的查询。
每个标签显示记录数量 badge。标签切换使用 `AnimatePresence` 实现内容淡入淡出。
**记录项增强**
每条历史记录显示:
- 左侧:状态圆点(成功 `bg-green-500`、失败 `bg-red-500`),直径 8px。
- 标题行:查询前 40 个字符 + 执行时间 badge(`rounded-md bg-muted px-1.5 py-0.5 text-xs`,如 "23ms"、"1.2s")。
- 展开/折叠:点击记录行展开 SQL 预览(`ChevronDown` / `ChevronRight` 图标指示),展开的 SQL 使用只读 CodeMirror 实例(高度 `max-h-[120px]`)或 `` 标签 + 语法高亮。
- 操作按钮组(hover 显示):"运行"(`Play` 图标,将 SQL 插入编辑器并执行)、"插入"(`CornerDownLeft` 图标,仅插入编辑器)、"收藏"(`Star` 图标)、"删除"(`Trash2` 图标,`variant="ghost"` 且 hover 变红)。
**一键对比功能**
在历史记录操作菜单中增加"对比"选项。选择两条记录后,弹出对比 `Dialog`(宽 `max-w-5xl`),使用左右分栏展示两条 SQL 的差异。差异高亮使用 `diff-match-patch` 库或自定义实现:
- 相同行:正常显示。
- 删除行:背景 `bg-red-50`,左侧红色竖线标记。
- 新增行:背景 `bg-green-50`,左侧绿色竖线标记。
- 修改行:字符级差异高亮,删除字符 `bg-red-200 line-through`,新增字符 `bg-green-200`。
**文件夹/标签组织**
允许用户创建自定义文件夹("日常巡检"、"业务报表"等)来组织历史查询。文件夹使用侧边栏树形结构展示,支持:
- 新建文件夹:输入名称,使用 `Dialog` + `Input`。
- 拖拽排序:文件夹和历史记录均支持拖拽重新排序。
- 颜色标签:每个文件夹可选颜色标签(8 种预设色),用于快速视觉识别。
- 搜索:文件夹和历史记录支持全文搜索(SQL 内容 + 备注)。
---
### 5.3 系统设置页面优化
当前设置页面(SettingsPage.tsx 仅 56 行)结构过于简单,PlatformConfigPage.tsx 和 AppearanceSettings.tsx 也存在分类不清晰、交互不一致的问题。
#### 5.3.1 设置分类重组
**新分类结构**
将原有分类重新组织为 7 个一级分类,每个分类分配独立路由(`/settings/:category`):
| 分类 | 路由 | 包含内容 | 图标 |
|------|------|----------|------|
| 外观设置 | `/settings/appearance` | 主题/语言/密度/字体/圆角/自定义CSS | `Palette` |
| 安全设置 | `/settings/security` | 密码策略/登录限制/2FA/IP白名单 | `Shield` |
| 通知设置 | `/settings/notifications` | 渠道配置/模板管理/订阅规则 | `Bell` |
| 执行设置 | `/settings/execution` | 超时/LIMIT/并发/执行器配置 | `PlayCircle` |
| 审计设置 | `/settings/audit` | 保留策略/敏感词/审计级别 | `ClipboardList` |
| 集成设置 | `/settings/integrations` | LDAP/Webhook/API密钥 | `Plug` |
| 高级设置 | `/settings/advanced` | 清理/备份/日志/导入导出 | `Cog` |
**导航布局**
设置页面采用左右布局:左侧导航栏宽 `w-64`(固定),右侧内容区 `flex-1`。左侧导航使用 `shadcn/ui` 的 `ScrollArea` 包裹,每个导航项包含图标(`w-4 h-4`)+ 分类名 + 设置项数量 badge(如"12 项")。当前激活项使用 `bg-primary/10 text-primary border-r-2 border-primary`。
导航栏底部放置"危险区域"入口(`AlertTriangle` 图标,红色文字),包含恢复默认设置、清除所有数据等操作。
---
#### 5.3.2 表单交互优化
**统一表单架构**
所有设置表单统一使用 `shadcn/ui Form` + `react-hook-form` + `zod` 验证。每个设置项包裹在卡片组件中:
```
┌─────────────────────────────────────┐
│ 设置项标题 │ ← text-base font-medium
│ 设置项描述,说明此配置的作用和影响范围 │ ← text-sm text-muted-foreground
│ │
│ [输入控件] [重置] │ ← 控件右对齐,重置按钮变灰
└─────────────────────────────────────┘
```
卡片样式:`rounded-xl border border-border/60 bg-card p-5 shadow-sm`,hover 时 `shadow-md transition-shadow duration-200`。卡片间距 `space-y-4`。
**设置项搜索**
页面顶部增加全局搜索栏(`Input` + `Search` 图标 + `Command+K` 快捷键),支持跨分类搜索设置项。搜索范围包括:设置项标题、描述、关键词标签。搜索结果按分类分组展示,点击后滚动定位到对应设置项并高亮(`bg-primary/10` 闪烁 2 秒后消退)。使用 `fuse.js` 实现,`threshold: 0.25`。
**变更指示与保存**
- 变更指示:每个设置卡片在值发生变化时,底部显示 "已修改" 文字(`text-xs text-amber-600`)+ "撤销" 链接,同时卡片顶部边框变为 `border-t-2 border-amber-400`。
- 保存策略:每个分类页右下角固定悬浮保存栏(`sticky bottom-6`),包含"保存更改"(`variant="default"`)和"重置"(`variant="outline"`)按钮。保存栏使用 `bg-card border shadow-lg rounded-xl px-6 py-3`。
- 自动保存:可选开启,每 5 秒自动保存已变更的设置(防抖处理)。
- 保存成功:顶部显示 `Toast`("设置已保存",`CheckCircle` 图标),保存按钮短暂变为成功状态(文字 "已保存" + `Check` 图标,2 秒后恢复)。
**变更历史**
在设置页面右上角增加"变更历史"按钮(`History` 图标),点击后弹出 `Sheet`(右侧滑出,宽 `w-[480px]`)。历史记录展示最近 50 条设置变更,每条包含:
- 时间(相对格式,如"3 分钟前")
- 分类名
- 设置项标题
- 旧值 → 新值(差异高亮)
- 操作人 Avatar + 用户名
历史记录数据由后端提供,前端使用 `useSWR` 轮询获取(间隔 30 秒)。
**危险设置二次确认**
对于高风险设置项(如"允许删除生产环境数据"、"关闭审计日志"),使用以下交互设计:
- 卡片边框:`border-red-200`。
- 卡片顶部:`bg-red-50 px-5 py-2 -mx-5 -mt-5 mb-3 rounded-t-xl border-b border-red-100`,内含 `AlertTriangle` 图标 + "危险设置" 警告文字。
- 开关/输入控件旁增加确认 `Dialog`,用户点击变更时弹出,要求输入操作密码或二次确认文字(如"请输入 DELETE 确认")。
**设置导入/导出**
在高级设置分类中增加导入/导出功能:
- 导出:将所有设置序列化为 JSON 文件下载,包含元数据(导出时间、版本、操作人)。
- 导入:拖拽或选择 JSON 文件,预览变更内容(使用 diff 视图展示与当前设置的差异),确认后应用。导入前自动创建备份点。
---
#### 5.3.3 安全设置增强
**密码策略配置**
新增密码策略卡片,包含以下设置项:
| 设置项 | 控件 | 默认值 | 验证规则 |
|--------|------|--------|----------|
| 最小密码长度 | `Slider` (6-32) + `Input` | 8 | zod `min(6).max(32)` |
| 必须包含大写字母 | `Switch` | true | — |
| 必须包含小写字母 | `Switch` | true | — |
| 必须包含数字 | `Switch` | true | — |
| 必须包含特殊字符 | `Switch` | false | — |
| 密码过期时间(天) | `Input` (数字) | 90 | `min(1).max(365)` |
| 历史密码禁止 reuse 次数 | `Slider` (0-10) | 3 | `min(0).max(10)` |
密码强度实时预览:下方显示密码示例框,输入测试密码后动态显示强度条(红/黄/绿三段)和文字评价("弱"/"中"/"强")。
**登录失败锁定策略**
- 启用开关:`Switch`,默认开启。
- 最大尝试次数:`Slider` (3-10),默认 5 次。
- 锁定时间:`Select`(5分钟/15分钟/30分钟/1小时/手动解锁),默认 15 分钟。
- 通知管理员:`Switch`,锁定事件发生时通知管理员。
**IP 白名单配置**
使用 `Textarea` 输入,每行一个 IP 或 CIDR 段(如 `192.168.1.0/24`)。实时验证输入格式,非法格式行显示红色下划线 + `Tooltip` 说明错误原因。支持从文件导入(`.txt` 或 `.csv` 格式)。
白名单列表以标签云形式展示,每个 IP 标签可单独删除(`X` 图标)。空状态时显示 `Globe` 图标 + "未配置 IP 白名单,允许所有 IP 访问" 提示。
**Session 超时设置**
- 超时时间:`Select`(15分钟/30分钟/1小时/2小时/4小时/8小时/永不),默认 30 分钟。
- 超时前警告:`Switch`,在超时前 2 分钟弹出警告 `Dialog`,提供"保持登录"按钮。
- 多设备登录限制:`Select`(不限制/1台/2台/3台),默认不限制。
**操作审计级别配置**
使用 `RadioGroup` 三选一:
- 基础:仅记录登录/登出和审批操作。
- 标准(默认):额外记录查询执行和设置变更。
- 完整:记录所有操作包括 Schema 浏览和导出行为。
每个级别旁显示预估日志存储量(基于当前使用量计算)。
---
#### 5.3.4 外观设置增强
**主题实时预览**
主题模式切换(深色/浅色/跟随系统)改为实时预览:用户点击选项后立即应用主题(`document.documentElement.classList`),但此时不保存到后端。页面显示"主题已临时切换,保存以永久生效"的提示条(`bg-primary/10 border border-primary/20 rounded-lg px-4 py-2 mb-4`),包含"保存"和"恢复"按钮。用户刷新页面后未保存的主题恢复为已保存值。
**界面密度选择**
新增三档密度选择,使用 `ToggleGroup` 展示:
| 密度 | 效果 | 适用场景 |
|------|------|----------|
| 紧凑 (`compact`) | 表格行高 32px,卡片内边距 16px,字体 13px | 数据密集型用户 |
| 默认 (`default`) | 表格行高 40px,卡片内边距 20px,字体 14px | 平衡 |
| 舒适 (`comfortable`) | 表格行高 48px,卡片内边距 24px,字体 14px | 触控设备或视力辅助 |
选择后立即生效,通过 CSS 变量控制:`--density-factor: 0.8 | 1.0 | 1.2`,各组件使用 `calc()` 基于该变量计算尺寸。
**侧边栏宽度可调**
在"布局"子分类中增加侧边栏宽度设置:
- 控件:`Slider` (200px-400px),默认 280px。
- 实时预览:拖动滑块时侧边栏宽度实时变化。
- 重置按钮:点击恢复默认 280px。
**字体大小设置**
- 基础字体大小:`Select`(12px/13px/14px/15px/16px),默认 14px。
- 代码字体大小:`Select`(12px/13px/14px/15px/16px),默认 14px。
- 代码字体族:`Select`('JetBrains Mono', 'Fira Code', 'Source Code Pro', 'monospace'),默认 'JetBrains Mono'。
字体族选择时,预览文字实时展示该字体的渲染效果。
**圆角风格设置**
使用三个可视化选项卡片:
| 风格 | CSS 变量 | 视觉描述 |
|------|----------|----------|
| 锐利 (`sharp`) | `--radius: 0px` | 直角,无圆角 |
| 默认 (`default`) | `--radius: 0.5rem` | 中等圆角,卡片 8px |
| 圆润 (`rounded`) | `--radius: 0.75rem` | 大圆角,卡片 12px |
每个选项卡片展示缩略图:一个矩形 + 一个按钮 + 一个 badge,使用对应圆角值渲染。选中状态 `ring-2 ring-primary`。
**自定义 CSS(高级用户)**
在"高级"子分类中增加自定义 CSS 输入区:
- 使用 CodeMirror 6 编辑器(只开启基础高亮,无语法检查),高度 `min-h-[200px]`。
- 占位符:"/* 在此输入自定义 CSS,将覆盖默认样式 */\n\n.custom-sidebar {\n /* your styles */\n}"。
- 实时预览开关:`Switch`,开启时 CSS 立即应用到页面。
- 安全提示:输入区上方显示 `Info` 图标 + "自定义 CSS 仅影响当前用户,错误样式可能导致页面显示异常" 警告。
- CSS 作用域:通过 Shadow DOM 或 `style` 标签 + 特定 class 前缀隔离,避免影响全局样式。
**登录页风格选择**
将原有的 5 个缩略图卡片优化为:
- 卡片尺寸:`aspect-[16/10] rounded-lg overflow-hidden border-2`。
- 选中状态:`border-primary ring-2 ring-primary/20`。
- 缩略图内容:使用实际登录页的静态截图或实时 iframe 预览。
- 卡片底部:风格名称 + 简短描述。
- 新增"上传自定义背景"按钮:支持上传图片(JPG/PNG,最大 5MB),上传后显示预览和"删除"按钮。
---
以上优化方案覆盖了审批变更、查询数据、系统设置三大核心模块的全部关键页面。每个方案均提供了具体的组件选型、布局参数、交互细节和视觉规范,可直接转化为前端开发任务。建议按照"查询数据(高频使用)→ 审批变更(核心流程)→ 系统设置(低频但影响全局)"的优先级顺序逐步实施。
## 6. 菜单体系全面优化
菜单导航是SQL-Platform最核心的交互骨架,直接影响用户的工作流效率和平台整体体验。当前菜单体系存在分组逻辑不够清晰、图标体系不统一、权限控制粒度较粗、缺少面包屑导航等结构性问题。本章将从菜单结构重组、图标规范化、权限映射优化和面包屑导航四个维度进行系统性重构。
---
### 6.1 菜单结构重组方案
#### 6.1.1 重组设计思路
当前菜单的六个分组存在三个核心问题:其一,"配置平台"分组仅包含"系统设置"一项,空间利用率极低;其二,"自动任务"归类于"系统配置"不够合理,它与运维监控的关联度远高于系统配置;其三,"工单管理"的命名偏向内部管理视角,对实际执行数据操作的开发者而言不够直观。
重组方案遵循"以用户角色为中心、以任务流为导向"的原则,将原有6个分组精简为6个更具语义清晰度的分组。核心调整包括:将"概览"升级为"工作台"并增加快捷入口;将"工单管理"重命名为"数据操作";将"自动任务"移至"运维中心";将孤立的"系统设置"与新增"个人中心"组成"平台设置"。
#### 6.1.2 优化后的完整菜单定义
```typescript
// /frontend/src/constants/menu.ts
import type { LucideIcon } from 'lucide-react';
/** 权限码类型 —— 替代粗粒度的 roles 数组 */
export type PermissionCode =
| 'dashboard:view'
| 'todo:view'
| 'query:quick_access'
| 'ticket:view'
| 'ticket:create'
| 'query:view'
| 'sql_assistant:use'
| 'datasource:view'
| 'datasource:manage'
| 'data_dict:view'
| 'user:view'
| 'user:manage'
| 'role:view'
| 'role:manage'
| 'perm_group:view'
| 'perm_group:manage'
| 'workflow:view'
| 'workflow:manage'
| 'audit_log:view'
| 'auto_task:view'
| 'auto_task:manage'
| 'announcement:view'
| 'settings:system'
| 'settings:personal';
/** 菜单分组定义 */
export interface MenuSection {
id: string;
label: string;
icon?: LucideIcon;
order: number;
}
export const menuSections: MenuSection[] = [
{ id: 'workspace', label: '工作台', order: 10 },
{ id: 'dataops', label: '数据操作', order: 20 },
{ id: 'datasource', label: '数据源', order: 30 },
{ id: 'permission', label: '权限中心', order: 40 },
{ id: 'ops', label: '运维中心', order: 50 },
{ id: 'platform', label: '平台设置', order: 60 },
];
/** Badge 数据源类型 */
export type BadgeSource = 'pending_count' | 'unread_count' | 'task_fail_count' | 'announcement_count' | none;
/** 菜单项定义 */
export interface MenuItem {
id: string;
label: string;
icon: string; // lucide-react 图标名称
section: string; // 对应 menuSections.id
permissions: PermissionCode[]; // 细粒度权限码(满足任一即显示)
badge?: BadgeSource; // 动态角标数据源
shortcut?: string; // 快捷键(如 Ctrl+1)
order: number; // 组内排序
}
export const menuItems: MenuItem[] = [
// ─── 工作台 ───
{
id: 'dashboard',
label: '仪表盘',
icon: 'LayoutDashboard',
section: 'workspace',
permissions: ['dashboard:view'],
order: 10,
},
{
id: 'my-todo',
label: '我的待办',
icon: 'CheckCircle2',
section: 'workspace',
permissions: ['ticket:view', 'workflow:view'],
badge: 'pending_count',
order: 20,
},
{
id: 'quick-queries',
label: '快速查询',
icon: 'Zap',
section: 'workspace',
permissions: ['query:view', 'query:quick_access'],
order: 30,
},
// ─── 数据操作 ───
{
id: 'tickets',
label: '数据变更',
icon: 'ClipboardList',
section: 'dataops',
permissions: ['ticket:view'],
badge: 'pending_count',
order: 10,
},
{
id: 'queries',
label: '查询数据',
icon: 'Search',
section: 'dataops',
permissions: ['query:view'],
order: 20,
},
{
id: 'sql-assistant',
label: 'SQL助手',
icon: 'Wand2',
section: 'dataops',
permissions: ['sql_assistant:use', 'query:view'],
order: 30,
},
// ─── 数据源 ───
{
id: 'instances',
label: '数据源管理',
icon: 'Database',
section: 'datasource',
permissions: ['datasource:view'],
order: 10,
},
{
id: 'data-dict',
label: '数据字典',
icon: 'BookOpen',
section: 'datasource',
permissions: ['data_dict:view', 'datasource:view'],
order: 20,
},
// ─── 权限中心 ───
{
id: 'users',
label: '用户管理',
icon: 'Users',
section: 'permission',
permissions: ['user:view'],
order: 10,
},
{
id: 'roles',
label: '角色权限',
icon: 'UserCog',
section: 'permission',
permissions: ['role:view'],
order: 20,
},
{
id: 'instance-groups',
label: '权限组',
icon: 'Shield',
section: 'permission',
permissions: ['perm_group:view'],
order: 30,
},
{
id: 'workflow',
label: '审批流程',
icon: 'GitBranch',
section: 'permission',
permissions: ['workflow:view'],
order: 40,
},
// ─── 运维中心 ───
{
id: 'audit-logs',
label: '审计日志',
icon: 'FileText',
section: 'ops',
permissions: ['audit_log:view'],
order: 10,
},
{
id: 'auto-task',
label: '自动任务',
icon: 'Clock',
section: 'ops',
permissions: ['auto_task:view'],
badge: 'task_fail_count',
order: 20,
},
{
id: 'announcements',
label: '系统公告',
icon: 'Megaphone',
section: 'ops',
permissions: ['announcement:view'],
badge: 'announcement_count',
order: 30,
},
// ─── 平台设置 ───
{
id: 'settings',
label: '系统设置',
icon: 'Settings',
section: 'platform',
permissions: ['settings:system'],
order: 10,
},
{
id: 'profile',
label: '个人中心',
icon: 'UserCircle',
section: 'platform',
permissions: ['settings:personal'],
order: 20,
},
];
/** 根据分组 ID 获取菜单项 */
export const getMenuItemsBySection = (sectionId: string): MenuItem[] => {
return menuItems
.filter((item) => item.section === sectionId)
.sort((a, b) => a.order - b.order);
};
/** 根据权限码过滤可见菜单项 */
export const filterMenuByPermissions = (
items: MenuItem[],
userPermissions: PermissionCode[]
): MenuItem[] => {
return items.filter((item) =>
item.permissions.some((perm) => userPermissions.includes(perm))
);
};
```
#### 6.1.3 重组收益
| 维度 | 调整前 | 调整后 | 收益 |
|------|--------|--------|------|
| 分组数量 | 6个(含1个单项目分组) | 6个(每个分组2-4项) | 分布均匀,视觉平衡 |
| 语义清晰度 | "工单管理"偏管理视角 | "数据操作"以操作为导向 | 降低认知成本 |
| 功能关联性 | 自动任务与系统配置弱关联 | 自动任务归入运维中心 | 逻辑一致性提升 |
| 快捷入口 | 无 | 工作台聚合待办+快速查询 | 减少用户操作路径 |
| 可扩展性 | 新增功能无处安放 | 各分组预留扩展空间 | 支持未来功能迭代 |
---
### 6.2 图标体系规范化
#### 6.2.1 现状问题分析
当前系统存在两套图标体系并行的问题:侧边栏使用 `lucide-react` 矢量图标,而设置页面使用 emoji 图标(🎨⚙️🛡️🔔🔐)。这种不一致带来三个负面影响:视觉风格割裂,emoji 在不同操作系统渲染差异大;专业感下降,emoji 偏向社交/休闲场景;无障碍支持差,emoji 的屏幕阅读器朗读行为不可控。
#### 6.2.2 图标映射表
以下是重组后全部菜单项的图标映射,统一使用 `lucide-react` 图标库:
| 菜单项 ID | 菜单标签 | 图标名称 (Lucide) | 选用理由 |
|-----------|----------|-------------------|----------|
| `dashboard` | 仪表盘 | `LayoutDashboard` | 网格布局隐喻,dashboard 标准语义 |
| `my-todo` | 我的待办 | `CheckCircle2` | 待办完成状态的圆形勾选隐喻 |
| `quick-queries` | 快速查询 | `Zap` | 闪电符号表达"快速"语义 |
| `tickets` | 数据变更 | `ClipboardList` | 工单列表的标准图标,保持不变 |
| `queries` | 查询数据 | `Search` | 搜索查询的标准语义 |
| `sql-assistant` | SQL助手 | `Wand2` | 魔杖隐喻"智能辅助/魔法般的体验" |
| `instances` | 数据源管理 | `Database` | 数据库标准图标,保持不变 |
| `data-dict` | 数据字典 | `BookOpen` | 打开的书籍隐喻"字典/参考" |
| `users` | 用户管理 | `Users` | 用户群像标准图标,保持不变 |
| `roles` | 角色权限 | `UserCog` | 用户+齿轮表达"用户配置" |
| `instance-groups` | 权限组 | `Shield` | 盾牌隐喻"权限保护",替代原 Lock |
| `workflow` | 审批流程 | `GitBranch` | 分支流程图标,保持不变 |
| `audit-logs` | 审计日志 | `FileText` | 文件记录标准图标,保持不变 |
| `auto-task` | 自动任务 | `Clock` | 时钟隐喻"定时/自动化",保持不变 |
| `announcements` | 系统公告 | `Megaphone` | 喇叭隐喻"广播/公告" |
| `settings` | 系统设置 | `Settings` | 齿轮标准图标,保持不变 |
| `profile` | 个人中心 | `UserCircle` | 圆形用户头像隐喻"个人信息" |
#### 6.2.3 图标动态导入实现
为避免一次性导入全部图标导致的包体积膨胀,采用字符串名称 + 动态导入方案:
```typescript
// /frontend/src/constants/menu.ts
import * as Icons from 'lucide-react';
import type { LucideProps } from 'lucide-react';
import React from 'react';
/** 图标名称到组件的动态映射 */
export const getMenuIcon = (
iconName: string
): React.ComponentType | null => {
const Icon = (Icons as Record>)[iconName];
return Icon || null;
};
/** 在 SidebarMenuItem 组件中的使用方式 */
// const IconComponent = getMenuIcon(item.icon);
// {IconComponent && }
```
#### 6.2.4 图标颜色与尺寸规范
```css
/* /frontend/src/styles/menu-icons.css */
/* ─── 基础尺寸 ─── */
.menu-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
transition: color 150ms ease, transform 150ms ease;
}
/* ─── 颜色状态 ─── */
.menu-icon--default {
color: var(--dg-text-secondary); /* #9ca3af */
}
.menu-item:hover .menu-icon--default {
color: var(--dg-text-primary); /* #e5e7eb */
transform: scale(1.05);
}
.menu-icon--active {
color: rgb(96 165 250); /* text-blue-400 */
}
/* ─── 分组标题图标(如使用)─── */
.menu-section-icon {
color: var(--dg-text-muted); /* #6b7280 */
width: 16px;
height: 16px;
}
/* ─── 角标容器 ─── */
.menu-badge {
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
font-size: 11px;
font-weight: 600;
background-color: rgb(239 68 68); /* red-500 */
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
animation: badge-pulse 2s ease-in-out infinite;
}
@keyframes badge-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
```
#### 6.2.5 设置页 emoji 替换方案
原设置页面中使用了以下 emoji 图标,需全部替换为 lucide-react 对应图标:
| 设置项 | 原 emoji | 替换为 Lucide 图标 | 图标名称 |
|--------|----------|-------------------|----------|
| 外观主题 | 🎨 | `Palette` | Palette |
| 平台设置 | ⚙️ | `SlidersHorizontal` | SlidersHorizontal |
| 安全设置 | 🛡️ | `ShieldCheck` | ShieldCheck |
| 通知设置 | 🔔 | `Bell` | Bell |
| 密码修改 | 🔐 | `KeyRound` | KeyRound |
替换后设置页面的图标导入:
```typescript
// /frontend/src/pages/settings/SettingsPage.tsx
import {
Palette,
SlidersHorizontal,
ShieldCheck,
Bell,
KeyRound,
} from 'lucide-react';
const settingsGroups = [
{ id: 'appearance', label: '外观主题', icon: Palette },
{ id: 'platform', label: '平台设置', icon: SlidersHorizontal },
{ id: 'security', label: '安全设置', icon: ShieldCheck },
{ id: 'notification', label: '通知设置', icon: Bell },
{ id: 'password', label: '密码修改', icon: KeyRound },
];
```
---
### 6.3 权限映射优化
#### 6.3.1 从角色到权限的范式迁移
原方案使用 `roles: ['admin', 'dba', 'developer', 'auditor']` 这种粗粒度的角色数组控制菜单可见性,存在三个结构性缺陷:角色名称硬编码,新增角色需修改多处代码;同一角色内不同用户的菜单可见性无法进一步细分;无法支持基于RBAC的动态权限变更。
新方案引入 `permissions: PermissionCode[]` 数组,每个菜单项声明所需的细粒度权限码。用户登录时后端返回该用户的权限码列表,前端通过权限交集运算判断菜单可见性。此设计遵循"后端控制权限,前端控制展示"的分层原则。
#### 6.3.2 权限码命名规范
权限码采用 `resource:action` 的标准格式:
```typescript
// /frontend/src/constants/permissions.ts
/** 权限码完整定义 */
export const PERMISSIONS = {
// 工作台
DASHBOARD_VIEW: 'dashboard:view' as const,
TODO_VIEW: 'todo:view' as const,
QUERY_QUICK_ACCESS:'query:quick_access' as const,
// 数据操作
TICKET_VIEW: 'ticket:view' as const,
TICKET_CREATE: 'ticket:create' as const,
TICKET_APPROVE: 'ticket:approve' as const,
QUERY_VIEW: 'query:view' as const,
QUERY_CREATE: 'query:create' as const,
SQL_ASSISTANT_USE: 'sql_assistant:use' as const,
// 数据源
DATASOURCE_VIEW: 'datasource:view' as const,
DATASOURCE_MANAGE: 'datasource:manage' as const,
DATA_DICT_VIEW: 'data_dict:view' as const,
// 权限中心
USER_VIEW: 'user:view' as const,
USER_MANAGE: 'user:manage' as const,
ROLE_VIEW: 'role:view' as const,
ROLE_MANAGE: 'role:manage' as const,
PERM_GROUP_VIEW: 'perm_group:view' as const,
PERM_GROUP_MANAGE: 'perm_group:manage' as const,
WORKFLOW_VIEW: 'workflow:view' as const,
WORKFLOW_MANAGE: 'workflow:manage' as const,
// 运维中心
AUDIT_LOG_VIEW: 'audit_log:view' as const,
AUTO_TASK_VIEW: 'auto_task:view' as const,
AUTO_TASK_MANAGE: 'auto_task:manage' as const,
ANNOUNCEMENT_VIEW: 'announcement:view' as const,
// 平台设置
SETTINGS_SYSTEM: 'settings:system' as const,
SETTINGS_PERSONAL: 'settings:personal' as const,
} as const;
/** 权限码联合类型 —— 从常量对象推导 */
export type PermissionCode =
typeof PERMISSIONS[keyof typeof PERMISSIONS];
/** 角色到权限的映射模板 —— 后端可动态调整 */
export const ROLE_PERMISSION_TEMPLATES: Record = {
admin: Object.values(PERMISSIONS),
dba: [
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.TODO_VIEW,
PERMISSIONS.TICKET_VIEW,
PERMISSIONS.TICKET_APPROVE,
PERMISSIONS.QUERY_VIEW,
PERMISSIONS.QUERY_CREATE,
PERMISSIONS.SQL_ASSISTANT_USE,
PERMISSIONS.DATASOURCE_VIEW,
PERMISSIONS.DATASOURCE_MANAGE,
PERMISSIONS.DATA_DICT_VIEW,
PERMISSIONS.WORKFLOW_VIEW,
PERMISSIONS.AUDIT_LOG_VIEW,
PERMISSIONS.AUTO_TASK_VIEW,
PERMISSIONS.ANNOUNCEMENT_VIEW,
PERMISSIONS.SETTINGS_PERSONAL,
],
developer: [
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.TICKET_VIEW,
PERMISSIONS.TICKET_CREATE,
PERMISSIONS.QUERY_VIEW,
PERMISSIONS.QUERY_CREATE,
PERMISSIONS.SQL_ASSISTANT_USE,
PERMISSIONS.DATASOURCE_VIEW,
PERMISSIONS.DATA_DICT_VIEW,
PERMISSIONS.WORKFLOW_VIEW,
PERMISSIONS.ANNOUNCEMENT_VIEW,
PERMISSIONS.SETTINGS_PERSONAL,
],
auditor: [
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.TICKET_VIEW,
PERMISSIONS.QUERY_VIEW,
PERMISSIONS.AUDIT_LOG_VIEW,
PERMISSIONS.ANNOUNCEMENT_VIEW,
PERMISSIONS.SETTINGS_PERSONAL,
],
};
```
#### 6.3.3 菜单可见性判断 Hook
```typescript
// /frontend/src/hooks/useMenuPermissions.ts
import { useMemo } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import {
menuSections,
menuItems,
filterMenuByPermissions,
getMenuItemsBySection,
} from '@/constants/menu';
import type { MenuItem, MenuSection } from '@/constants/menu';
export interface VisibleMenuGroup {
section: MenuSection;
items: MenuItem[];
}
/** 获取当前用户可见的菜单结构 */
export const useVisibleMenu = (): VisibleMenuGroup[] => {
const { userPermissions } = useAuth(); // 从 AuthContext 获取用户权限码数组
return useMemo(() => {
// 按 order 排序分组
const sortedSections = [...menuSections].sort((a, b) => a.order - b.order);
return sortedSections
.map((section) => {
const sectionItems = getMenuItemsBySection(section.id);
const visibleItems = filterMenuByPermissions(sectionItems, userPermissions);
return { section, items: visibleItems };
})
.filter((group) => group.items.length > 0); // 过滤空分组
}, [userPermissions]);
};
/** 判断用户是否拥有某个菜单项的访问权限 */
export const useHasMenuPermission = (menuItemId: string): boolean => {
const { userPermissions } = useAuth();
const item = menuItems.find((m) => m.id === menuItemId);
if (!item) return false;
return item.permissions.some((perm) => userPermissions.includes(perm));
};
/** 获取动态角标数据 */
export const useMenuBadges = (): Record => {
const { badgeData } = useAuth(); // 从 AuthContext 获取角标数据
return useMemo(() => {
const result: Record = {};
menuItems.forEach((item) => {
if (item.badge && badgeData[item.badge] !== undefined) {
result[item.id] = badgeData[item.badge];
}
});
return result;
}, [badgeData]);
};
```
#### 6.3.4 动态角标设计
菜单角标实时展示待处理事项数量,提醒用户关注。角标数据通过 WebSocket 或轮询接口从后端获取:
```typescript
// /frontend/src/types/badge.ts
/** 角标数据源类型 */
export interface BadgeData {
pending_count: number; // 待审批工单数
unread_count: number; // 未读消息数
task_fail_count: number; // 失败自动任务数
announcement_count: number; // 未读公告数
}
// /frontend/src/hooks/useBadgePolling.ts
import { useQuery } from '@tanstack/react-query';
import type { BadgeData } from '@/types/badge';
const POLLING_INTERVAL = 60_000; // 60秒轮询
export const useBadgePolling = () => {
return useQuery({
queryKey: ['menu-badges'],
queryFn: async () => {
const res = await fetch('/api/v1/badges');
if (!res.ok) throw new Error('Failed to fetch badges');
return res.json();
},
refetchInterval: POLLING_INTERVAL,
staleTime: 30_000,
});
};
```
角标在侧边栏的渲染逻辑:
```tsx
// 角标渲染(嵌入 SidebarMenuItem 组件中)
{badgeCount !== undefined && badgeCount > 0 && (
99 ? 'bg-red-600' : 'bg-red-500'}
text-white
animate-[badge-pulse_2s_ease-in-out_infinite]
`}>
{badgeCount > 99 ? '99+' : badgeCount}
)}
```
#### 6.3.5 权限迁移路径
| 阶段 | 操作 | 时间窗口 |
|------|------|----------|
| 第一阶段(并行期) | 同时保留 `roles` 和 `permissions` 字段,以 roles 为 fallback | 1-2 个迭代 |
| 第二阶段(切换期) | 后端全面返回 permissions,前端以 permissions 为主 | 1 个迭代 |
| 第三阶段(清理期) | 移除 roles 字段,权限判断完全基于 permissions | 1 个迭代 |
---
### 6.4 面包屑导航补充
#### 6.4.1 设计目标与定位
面包屑导航填补当前平台在"位置感知"维度的空白。当用户通过深层链接(如审批流程详情页)进入页面时,面包屑提供清晰的路径回溯能力。设计遵循三个原则:路径语义化(显示可读标签而非路由路径)、可交互(支持点击跳转)、与侧边栏激活状态联动。
#### 6.4.2 路径映射规则
面包屑的数据源不是路由配置本身,而是一个独立的路径映射表。这种解耦设计使面包屑标签与 URL 路径分离,便于国际化和文案调整。
```typescript
// /frontend/src/constants/breadcrumb.ts
/** 面包屑路径映射项 */
export interface BreadcrumbMapping {
path: string; // 路由路径(支持参数,如 /tickets/:id)
label: string; // 面包屑显示文本
parent?: string; // 父级路径(用于构建完整面包屑链)
icon?: string; // 可选的 lucide 图标名称
}
/** 面包屑路径映射表 —— 按深度从浅到深排列 */
export const breadcrumbMappings: BreadcrumbMapping[] = [
// 工作台
{ path: '/dashboard', label: '仪表盘', icon: 'LayoutDashboard' },
{ path: '/my-todo', label: '我的待办', icon: 'CheckCircle2' },
{ path: '/quick-queries',label: '快速查询', icon: 'Zap' },
// 数据操作
{ path: '/tickets', label: '数据变更', icon: 'ClipboardList' },
{ path: '/tickets/:id', label: '工单详情', parent: '/tickets' },
{ path: '/queries', label: '查询数据', icon: 'Search' },
{ path: '/queries/:id', label: '查询详情', parent: '/queries' },
{ path: '/sql-assistant',label: 'SQL助手', icon: 'Wand2' },
// 数据源
{ path: '/instances', label: '数据源管理', icon: 'Database' },
{ path: '/instances/:id',label: '数据源详情', parent: '/instances' },
{ path: '/data-dict', label: '数据字典', icon: 'BookOpen' },
// 权限中心
{ path: '/users', label: '用户管理', icon: 'Users' },
{ path: '/users/:id', label: '用户详情', parent: '/users' },
{ path: '/roles', label: '角色权限', icon: 'UserCog' },
{ path: '/instance-groups', label: '权限组', icon: 'Shield' },
{ path: '/workflow', label: '审批流程', icon: 'GitBranch' },
// 运维中心
{ path: '/audit-logs', label: '审计日志', icon: 'FileText' },
{ path: '/auto-task', label: '自动任务', icon: 'Clock' },
{ path: '/announcements',label: '系统公告', icon: 'Megaphone' },
// 平台设置
{ path: '/settings', label: '系统设置', icon: 'Settings' },
{ path: '/profile', label: '个人中心', icon: 'UserCircle' },
];
```
#### 6.4.3 面包屑构建工具函数
```typescript
// /frontend/src/utils/breadcrumb.ts
import { breadcrumbMappings } from '@/constants/breadcrumb';
import type { BreadcrumbMapping } from '@/constants/breadcrumb';
export interface BreadcrumbItem {
label: string;
path: string;
icon?: string;
isActive: boolean; // 是否为当前页(不可点击)
}
/** 将路径模板匹配到实际路径 */
const matchPath = (template: string, actualPath: string): boolean => {
const templateParts = template.split('/');
const actualParts = actualPath.split('/');
if (templateParts.length !== actualParts.length) return false;
return templateParts.every((part, i) => {
if (part.startsWith(':')) return true; // 参数段通配
return part === actualParts[i];
});
};
/** 根据当前路径构建面包屑链 */
export const buildBreadcrumbs = (currentPath: string): BreadcrumbItem[] => {
const chain: BreadcrumbItem[] = [];
const visited = new Set();
// 查找当前路径的映射
let currentMapping = breadcrumbMappings.find((m) =>
matchPath(m.path, currentPath)
);
// 逐层向上追溯父级
while (currentMapping && !visited.has(currentMapping.path)) {
visited.add(currentMapping.path);
chain.unshift({
label: currentMapping.label,
path: currentPath, // 当前页使用实际路径
icon: currentMapping.icon,
isActive: chain.length === 0,
});
if (currentMapping.parent) {
currentMapping = breadcrumbMappings.find(
(m) => m.path === currentMapping!.parent
);
} else {
break;
}
}
return chain;
};
/** 获取面包屑项的目标路径(处理参数化路径) */
export const resolveBreadcrumbPath = (
mapping: BreadcrumbMapping,
currentPath: string
): string => {
if (!mapping.path.includes(':')) return mapping.path;
// 从当前路径提取参数值
const templateParts = mapping.path.split('/');
const currentParts = currentPath.split('/');
const resolvedParts = templateParts.map((part, i) => {
if (part.startsWith(':')) return currentParts[i] || part;
return part;
});
return resolvedParts.join('/');
};
```
#### 6.4.4 面包屑 React 组件
```tsx
// /frontend/src/components/layout/Breadcrumb.tsx
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { ChevronRight, Home } from 'lucide-react';
import { buildBreadcrumbs, resolveBreadcrumbPath } from '@/utils/breadcrumb';
import { breadcrumbMappings } from '@/constants/breadcrumb';
import { getMenuIcon } from '@/constants/menu';
export const Breadcrumb: React.FC = () => {
const location = useLocation();
const items = buildBreadcrumbs(location.pathname);
if (items.length <= 1) {
// 顶层页面不显示面包屑
return null;
}
return (
);
};
```
#### 6.4.5 面包屑与布局的集成
```tsx
// /frontend/src/components/layout/AppLayout.tsx —— 集成面包屑后的布局
import React from 'react';
import { Sidebar } from './Sidebar';
import { Breadcrumb } from './Breadcrumb';
import { TopBar } from './TopBar';
export const AppLayout: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
return (
{/* 侧边栏 */}
{/* 主内容区 */}
{/* 顶部栏 */}
{/* 面包屑导航 */}
{/* 页面内容 */}
{children}
);
};
```
#### 6.4.6 面包屑设计规范
| 设计要素 | 规范值 | 说明 |
|----------|--------|------|
| 字体大小 | `text-sm` (14px) | 小于页面标题,大于辅助文本 |
| 字体颜色(默认) | `var(--dg-text-secondary)` | 非当前页 |
| 字体颜色(当前页) | `var(--dg-text-primary)` | 加粗高亮 |
| 图标尺寸 | 14px | 小于侧边栏图标,弱化层级 |
| 分隔符 | `ChevronRight` 14px | 统一右箭头 |
| 垂直间距 | `py-3` (12px) | 紧凑布局 |
| 水平间距 | `px-6` (24px) | 与页面内容对齐 |
| 最大显示层级 | 4 级 | 超过则折叠中间层级 |
| 悬停交互 | 下划线 + 颜色过渡 | `transition-colors duration-150` |
面包屑的显示逻辑遵循"至少两级才展示"原则——当用户位于仪表盘等顶层页面时面包屑自动隐藏,避免冗余信息干扰。对于超过四级的深层路径,中间层级折叠为"..."下拉菜单,保持导航栏的简洁可读性。
---
### 6.5 菜单 Hover/Active 动效设计
#### 6.5.1 动画参数规范
```css
/* /frontend/src/styles/menu-animations.css */
/* ─── 菜单项基础 ─── */
.menu-item {
position: relative;
display: flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
margin: 2px 8px;
border-radius: 8px;
font-size: 13px;
font-weight: 450;
color: var(--dg-text-secondary);
cursor: pointer;
transition:
background-color 180ms cubic-bezier(0.4, 0, 0.2, 1),
color 150ms cubic-bezier(0.4, 0, 0.2, 1),
transform 120ms cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
}
/* ─── Hover 状态 ─── */
.menu-item:hover {
background-color: rgba(255, 255, 255, 0.04);
color: var(--dg-text-primary);
transform: translateX(2px);
}
/* ─── Active 状态 ─── */
.menu-item--active {
background-color: rgb(59 130 246 / 0.10); /* blue-500/10 */
color: rgb(96 165 250); /* blue-400 */
font-weight: 550;
}
/* 左侧指示条 */
.menu-item--active::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 18px;
background-color: rgb(96 165 250); /* blue-400 */
border-radius: 0 2px 2px 0;
animation: indicator-slide-in 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes indicator-slide-in {
from {
height: 0;
opacity: 0;
}
to {
height: 18px;
opacity: 1;
}
}
/* ─── 点击反馈 ─── */
.menu-item:active {
transform: scale(0.98);
transition-duration: 80ms;
}
/* ─── 菜单分组标题 ─── */
.menu-section-title {
padding: 16px 14px 6px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--dg-text-muted);
user-select: none;
}
```
#### 6.5.2 动画设计决策说明
| 动画 | 时长 | 缓动函数 | 设计意图 |
|------|------|----------|----------|
| Hover 背景色 | 180ms | `cubic-bezier(0.4, 0, 0.2, 1)` | Material Design 标准缓动,自然流畅 |
| Hover 位移 | 120ms | 同上 | 轻微右移引导视觉向右,暗示可点击 |
| 文字颜色 | 150ms | 同上 | 略快于背景色,形成层次 |
| 指示条展开 | 200ms | 同上 | 从0到18px高度,带有生长感 |
| 点击缩放 | 80ms | 线性 | 快速反馈,确认操作被接收 |
所有动画均采用 GPU 加速属性(`transform`、`opacity`、`background-color`),确保在 60fps 下流畅运行,不会造成布局抖动。
---
### 6.6 收藏/快捷访问功能
#### 6.6.1 功能设计
"快速查询"菜单项(位于工作台分组)承载收藏/快捷访问功能。用户可在任何页面点击顶部栏的星形图标将当前页面加入收藏,收藏项会出现在快速查询的下拉面板中。
#### 6.6.2 数据模型与存储
```typescript
// /frontend/src/types/favorite.ts
export interface FavoriteItem {
id: string; // 唯一标识
label: string; // 显示名称(用户可编辑)
path: string; // 路由路径
icon: string; // lucide 图标名称
createdAt: string; // ISO 时间戳
order: number; // 自定义排序
}
/** 收藏数据持久化 —— localStorage + 云端同步 */
const FAVORITES_STORAGE_KEY = 'sql_platform_favorites';
export const favoriteStorage = {
getAll(): FavoriteItem[] {
try {
const raw = localStorage.getItem(FAVORITES_STORAGE_KEY);
return raw ? JSON.parse(raw) : [];
} catch {
return [];
}
},
save(items: FavoriteItem[]): void {
localStorage.setItem(FAVORITES_STORAGE_KEY, JSON.stringify(items));
},
add(item: Omit): FavoriteItem[] {
const items = this.getAll();
if (items.some((f) => f.path === item.path)) return items; // 去重
const newItem: FavoriteItem = {
...item,
order: items.length,
createdAt: new Date().toISOString(),
};
const updated = [...items, newItem];
this.save(updated);
return updated;
},
remove(id: string): FavoriteItem[] {
const items = this.getAll().filter((f) => f.id !== id);
this.save(items);
return items;
},
reorder(id: string, newOrder: number): FavoriteItem[] {
const items = this.getAll();
const item = items.find((f) => f.id === id);
if (!item) return items;
item.order = newOrder;
this.save(items.sort((a, b) => a.order - b.order));
return items;
},
};
```
---
### 6.7 实施优先级与验收标准
#### 6.7.1 实施优先级
| 优先级 | 模块 | 预估工时 | 依赖项 |
|--------|------|----------|--------|
| P0 | 6.1 菜单结构重组 | 4h | 无 |
| P0 | 6.2 图标体系规范化 | 3h | 6.1 |
| P1 | 6.3 权限映射优化 | 6h | 后端接口配合 |
| P1 | 6.4 面包屑导航 | 4h | 6.1 |
| P2 | 6.5 菜单动效 | 2h | 6.1, 6.2 |
| P2 | 6.6 收藏功能 | 4h | 无 |
#### 6.7.2 验收标准
- [ ] 侧边栏展示重组后的6个分组,无空分组显示
- [ ] 全部菜单项使用 lucide-react 图标,系统无 emoji 图标残留
- [ ] 菜单可见性基于 permissions 数组判断,roles 字段已移除
- [ ] 面包屑导航在二级及以上页面正确显示路径链
- [ ] 菜单项 Hover/Active 动效流畅,无掉帧
- [ ] 动态角标正确显示待处理数量,超过99显示"99+"
- [ ] 设置页面 emoji 已全部替换为 lucide 图标
---
### 6.8 本章小结
本章从菜单结构重组、图标规范化、权限映射优化和面包屑导航四个维度对 SQL-Platform 的导航体系进行了系统性重构。核心改进包括:将原有6个分组重组为语义更清晰的工作台/数据操作/数据源/权限中心/运维中心/平台设置分组;建立了统一的 lucide-react 图标体系,彻底移除了 emoji 图标;引入 `PermissionCode` 细粒度权限码替代粗粒度的角色数组;补充了面包屑导航完善位置感知能力。这些改进使导航体系从"可用"迈向"好用",为平台的长期功能扩展奠定了坚实的交互基础。
## 7. 设计体系统一方案
### 7.1 色彩体系规范
**主色板定义**
| 角色 | 色值 | 用途 |
|------|------|------|
| Primary | `#58a6ff` (--dg-blue) | 主要操作、链接、高亮 |
| Secondary | `#8957e5` (--dg-purple) | 次级强调、标签、分类 |
| Accent | `#39c5cf` (--dg-cyan) | 提示点、新功能标记、数据高亮 |
**功能色定义**
| 角色 | 色值 | 使用场景 |
|------|------|----------|
| Success | `#3fb950` | 成功状态、通过标识、正向数据 |
| Warning | `#f0883e` | 警告提示、需谨慎操作 |
| Error | `#f85149` | 错误状态、删除操作、阻断性提示 |
| Info | `#58a6ff` | 信息提示、帮助引导 |
**中性色(Gray Scale)**
以现有变量为锚点扩展完整灰阶:`#f0f6fc` (50) → `#c9d1d9` (200) → `#8b949e` (400) → `#6e7681` (500) → `#484f58` (700) → `#30363d` (800) → `#21262d` (850) → `#161b22` (900) → `#0d1117` (950)。分别对应主文本、次级文本、弱化文本、悬浮背景、边框、深边框、卡片背景、次级背景、主背景。
**渐变规范**
- 品牌渐变(`.dg-gradient-bg`):`linear-gradient(135deg, #2563eb, #9333ea)`,用于Hero区域、登录页背景、Banner
- 文字渐变(`.dg-gradient-text`):`linear-gradient(90deg, #60a5fa, #c084fc)`,仅用于大标题强调
- 边框渐变(`.dg-gradient-br`):`linear-gradient(135deg, #2563eb, #9333ea)` 作为 border-image,用于特色卡片边框
- 使用规则:同一页面渐变不超过2种,文字渐变仅限H1/H2级别
**Tailwind配置扩展**
```js
colors: {
dg: {
bg: { primary: '#0d1117', secondary: '#161b22', tertiary: '#21262d', hover: '#30363d' },
border: { DEFAULT: '#30363d', light: '#21262d' },
text: { primary: '#f0f6fc', secondary: '#8b949e', muted: '#6e7681' },
blue: '#58a6ff', green: '#3fb950', red: '#f85149',
orange: '#f0883e', purple: '#8957e5', cyan: '#39c5cf', yellow: '#d29922',
}
}
```
### 7.2 字体排版规范
**字体栈**:`font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;` 系统字体优先,中文回退依次为Noto Sans SC、PingFang SC、Microsoft YaHei。
**字号Scale**(基于1.25倍率):
| Token | 尺寸 | 行高 | 用途 |
|-------|------|------|------|
| text-xs | 12px | 18px | 辅助文字、时间戳、徽章内文字 |
| text-sm | 14px | 20px | 表单标签、表格内容、按钮文字 |
| text-base | 16px | 24px | 正文、菜单项、对话框内容 |
| text-lg | 18px | 28px | 卡片标题、区块小标题 |
| text-xl | 20px | 30px | 页面次级标题 |
| text-2xl | 24px | 32px | 页面标题 |
| text-3xl | 30px | 38px | 模块大标题 |
| text-4xl | 36px | 44px | Hero标题、登录页品牌名 |
**字重规范**:normal (400) 用于正文,medium (500) 用于菜单和按钮,semibold (600) 用于标题,bold (700) 用于强调数据。H1-H6统一semibold,避免过粗导致的中文笔画粘连。
**等宽字体**:代码块与数据展示使用 `font-family: "JetBrains Mono", "Fira Code", "SF Mono", monospace;`,字号14px,行高1.6。
### 7.3 间距体系规范
**基于4px的Spacing Scale**:0(0px)、1(4px)、2(8px)、3(12px)、4(16px)、5(20px)、6(24px)、8(32px)、10(40px)、12(48px)、16(64px)、20(80px)、24(96px)。
**页面内边距**:内容区水平 `px-6`(24px),最大宽度1440px居中;垂直方向页面级 `py-8`(32px),模块级 `py-6`(24px)。
**卡片内边距**:标准卡片 `p-6`(24px),紧凑卡片 `p-4`(16px),数据卡片 `px-5 py-4`(20px/16px)。
**表单间距**:标签与输入框间距 `mb-1.5`(6px),表单项之间 `mb-5`(20px),表单组之间 `mb-8`(32px)。
**表格间距**:表头内边距 `px-4 py-3`(16px/12px),行内边距 `px-4 py-3`(16px/12px),行间距0(以边框分隔)。
**常见布局模板**:页面标题区(mb-8)→ 统计卡片区(grid gap-4, mb-8)→ 主内容卡片区(gap-6)。
### 7.4 组件交互规范
**按钮体系**
| 变体 | 背景 | 文字 | 圆角 | 内边距 | 使用场景 |
|------|------|------|------|--------|----------|
| Primary | `#58a6ff` | `#0d1117` | 6px | px-4 py-2 | 主要提交、确认操作 |
| Secondary | `#21262d` | `#f0f6fc` | 6px | px-4 py-2 | 取消、返回操作 |
| Ghost | transparent | `#8b949e` | 6px | px-4 py-2 | 次要操作、工具栏按钮 |
| Danger | `#f85149` | `#fff` | 6px | px-4 py-2 | 删除、阻断性操作 |
| Link | transparent | `#58a6ff` | 0 | px-2 py-1 | 导航跳转、文字链接 |
尺寸:sm (h-8, px-3, text-xs),md (h-10, px-4, text-sm),lg (h-12, px-6, text-base)。圆角统一6px,hover时亮度提升10%,transition 150ms。
**输入框体系**:默认边框`#30363d`,focus边框`#58a6ff`并附加`ring-2 ring-blue-500/20`,error边框`#f85149`带红色ring,disabled背景`#161b22`文字`#6e7681`。统一圆角6px,内边距px-3 py-2,高度40px。
**卡片体系**:默认卡片(bg `#161b22` border `#30363d` 圆角8px),可悬浮卡片(hover时border-color过渡为`#484f58`、translateY(-1px)、shadow-lg),可选中卡片(选中态border `#58a6ff`、bg `#58a6ff/5%`)。
**表格体系**:表头bg `#161b22` 文字`#8b949e` font-semibold,行hover bg `#21262d`,操作列按钮间距gap-2。边框1px `#21262d`。
**弹窗体系**:确认弹窗(宽400px),表单弹窗(宽560px),抽屉(宽480px/640px两档)。统一圆角12px,阴影`shadow-2xl`,入场动画scaleIn 200ms。
**徽章体系**:状态徽章(圆角全角,px-2 py-0.5,text-xs),标签徽章(圆角4px,带边框),计数徽章(圆形,min-w-5,h-5)。
### 7.5 动效体系规范
**缓动函数定义**
| 名称 | 值 | 用途 |
|------|-----|------|
| ease-default | cubic-bezier(0.4, 0, 0.2, 1) | 常规过渡 |
| ease-in | cubic-bezier(0.4, 0, 1, 1) | 元素退出 |
| ease-out | cubic-bezier(0, 0, 0.2, 1) | 元素入场 |
| ease-spring | cubic-bezier(0.34, 1.56, 0.64, 1) | 弹性交互 |
**时长Scale**:fast 150ms(hover、focus环、颜色过渡),normal 250ms(弹窗、展开/收起),slow 350ms(页面转场、大型组件)。
**入场动画**:fadeIn(opacity 0→1, 200ms ease-out)、slideUp(translateY 8px→0 + opacity, 250ms ease-out)、scaleIn(scale 0.95→1 + opacity, 200ms ease-out)。弹窗统一scaleIn,列表项使用slideUp带stagger 30ms。
**交互动画**:hover过渡统一150ms,按钮active时scale 0.98,卡片hover时translateY(-1px) + border-color过渡,输入框focus时ring 150ms。所有交互元素必须有hover反馈。
**Loading动画**:旋转器(border-2 border-t-blue-500,animate-spin,20px),骨架屏(shimmer动画,bg-gradient横向扫光,1.5s linear infinite)。
**CSS Keyframes**
```css
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
@keyframes slideUp { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: translateY(0) } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.95) } to { opacity: 1; transform: scale(1) } }
```
---
## 8. 实施路线图
### 8.1 优先级划分
| 优先级 | 范围 | 目标 |
|--------|------|------|
| P0 关键 | 登录页改造、设计体系统一 | 解决当前最大用户体验短板,建立可复用的设计基础 |
| P1 重要 | 主题系统扩展、核心功能优化 | 提升产品专业感,优化高频使用路径 |
| P2 优化 | 菜单体系、动效体系 | 增强导航效率与交互质感 |
| P3 增强 | 高级功能 | 数据可视化、个性化等增值能力 |
### 8.2 阶段一:登录页改造(2周)
**Week 1:组件重构**
- Day 1-2:开发粒子动画背景组件(Canvas实现,可配置粒子密度与连线颜色)
- Day 3:品牌展示区(渐变文字、系统名称、副标题、版本号)
- Day 4:登录表单重构(统一输入框规范、密码可见性切换、记住登录态)
- Day 5:安全提示区块(SSL标识、多因子认证引导、安全公告轮播)
**Week 2:动效与联调**
- Day 1-2:入场动效串联(粒子→标题→表单依次fadeIn+slideUp,stagger 150ms)
- Day 3:表单验证增强(实时校验、错误动画抖动、聚焦引导)
- Day 4-5:联调测试、响应式适配、浏览器兼容性验证
交付物:全新登录页、可复用粒子背景组件、登录表单组件。验收标准:Lighthouse性能分≥90,首屏加载<1.5s,动画帧率≥55fps。
### 8.3 阶段二:主题系统扩展(1周)
- Day 1-2:CSS变量体系扩展(将7.1节色彩规范完整植入,新增主题变量层 `--theme-*`)
- Day 3-4:主题切换UI(设置面板主题选择器、实时预览、本地持久化)
- Day 5:测试优化(变量回退机制、主题切换无闪烁、50+ shadcn/ui组件全覆盖)
交付物:扩展后的CSS变量体系、主题切换组件、Light/Dark/High-Contrast三主题。验收标准:主题切换<100ms无闪烁,所有组件颜色100%变量驱动。
### 8.4 阶段三:核心功能优化(3周)
**Week 1:审批变更工单系统**
- 工单列表卡片化重构(统一.dg-card规范)
- 审批流可视化(状态徽章体系、进度条组件)
- 批量操作工具栏(按钮体系统一)
**Week 2:查询数据页面**
- SQL编辑器区域优化(等宽字体规范、行号样式)
- 结果表格重构(表格体系统一、列宽记忆)
- 查询历史面板(间距体系应用、空状态设计)
**Week 3:系统设置页面**
- 设置项卡片分组(间距模板应用)
- 表单交互优化(输入框规范、保存反馈动效)
- 权限矩阵表格重构(表格规范全面落地)
交付物:三个核心页面重构完成、对应组件更新。验收标准:无硬编码颜色,间距100%符合scale,交互动效完整。
### 8.5 阶段四:菜单与体系统一(1周)
- Day 1-2:菜单结构重组(按用户角色分组、图标体系统一、激活态样式)
- Day 3-4:全局设计体系统一(替换所有硬编码颜色为CSS变量,按钮/卡片/输入框标准化)
- Day 5:动效体系实现(全局加载动画、页面转场、交互反馈动效)
交付物:重组后的菜单组件、全局样式清理报告、动效配置文件。验收标准:代码库零硬编码色值(除渐变色定义外),全局交互动效一致性100%。
### 8.6 预期效果
**定性效果**:登录页从功能型升级为品牌展示窗口;核心操作流程视觉层级清晰,用户认知负担降低;全产品交互反馈一致,专业感显著提升。
**定量效果**:
| 指标 | 当前预估 | 目标 |
|------|----------|------|
| 首屏加载时间 | ~2.5s | <1.5s |
| Lighthouse性能分 | ~70 | ≥90 |
| 全局样式不一致点 | 35+处 | 0处 |
| CSS变量覆盖率 | ~60% | 100% |
**风险控制**:每阶段独立分支开发,P0/P1阶段完成后可独立上线;所有样式变更基于现有变量扩展,保持向后兼容;关键页面保留回滚开关,确保24小时内可回退至原版本。