# RTC Web 前端设计规范 > 本文档定义 rtc_web 管理后台的 UI 设计规范。所有新页面和组件必须遵循此规范,确保视觉一致性。 --- ## 1. 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | React | 19.x | UI 框架 | | TypeScript | 5.9+ | 类型安全 | | Ant Design | 5.x | 组件库 | | @ant-design/pro-components | 2.x | ProTable 等高级组件 | | @ant-design/charts | 2.x | 图表 (Pie, Column 等) | | Zustand | 5.x | 状态管理 | | Vite | 7.x | 构建工具 | --- ## 2. 色彩体系 ### 2.1 主题色 主题色通过 `src/theme/tokens.ts` 统一配置,由 Ant Design 的 `ConfigProvider` 全局应用。 | 语义 | 色值 | 用途 | |------|------|------| | **Primary** | `#6366f1` | 主操作按钮、链接、选中态、品牌色 | | Success | `#10b981` | 成功状态、已绑定、完成 | | Warning | `#f59e0b` | 警告、未导入、待处理 | | Error | `#ef4444` | 错误、删除、禁用 | | Info | `#3b82f6` | 信息提示、只读状态 | ### 2.2 统计卡片专用色(`statColors`) 从 `src/theme/tokens.ts` 中导入 `statColors`: ```typescript import { statColors } from '../../theme/tokens'; // 可用颜色: statColors.primary // #6366f1 - 紫蓝 statColors.success // #10b981 - 绿 statColors.warning // #f59e0b - 橙 statColors.error // #ef4444 - 红 statColors.info // #3b82f6 - 蓝 statColors.purple // #8b5cf6 - 紫 statColors.cyan // #06b6d4 - 青 statColors.pink // #ec4899 - 粉 ``` ### 2.3 中性色 | 用途 | 色值 | |------|------| | 正文标题 | `#1f2937` | | 正文内容 | `#374151` | | 辅助文字 | `#6b7280` | | 占位/禁用 | `#9ca3af` | | 边框 | `#e5e7eb` | | 浅背景 | `#f5f5f7` (Layout 背景) | | 白色容器 | `#ffffff` | ### 2.4 CSS 变量 全局 CSS 变量定义在 `src/index.css` 的 `:root` 中,可在任何 CSS 文件中使用: ```css var(--color-primary) /* #6366f1 */ var(--color-primary-light) /* #818cf8 */ var(--color-primary-dark) /* #4f46e5 */ var(--color-bg-base) /* #f5f5f7 */ var(--color-bg-elevated) /* #ffffff */ var(--color-border) /* #e5e7eb */ var(--shadow-sm) /* 微弱阴影 */ var(--shadow-md) /* 中等阴影 */ var(--shadow-lg) /* 强阴影 */ var(--transition-fast) /* 150ms */ var(--transition-base) /* 200ms */ var(--transition-slow) /* 300ms */ ``` --- ## 3. 圆角规范 | 场景 | 值 | 说明 | |------|------|------| | 小组件 (Tag) | `6px` | `borderRadiusSM` | | 标准组件 (Button, Input) | `8px` | `borderRadius` | | 大容器 (Card, Modal) | `12px` | `borderRadiusLG` | | 特殊场景 (登录卡片) | `16-20px` | 仅限全屏独立页面 | | 图标背景 | `12px` | 统计卡片图标容器 | --- ## 4. 间距规范 基于 **8px** 网格系统: | 场景 | 值 | |------|------| | 紧凑间距 | `8px` | | 小间距 | `12px` | | 标准间距 | `16px` | | 中等间距 | `24px` (内容区 padding、页面级 margin) | | 宽松间距 | `32px` | **常见用法:** ```tsx // 页面标题与内容 页面标题 // Row 间距 // Card body padding // 区块间距 ``` --- ## 5. 阴影规范 ``` 轻微: 0 1px 2px rgba(0,0,0,0.03) → 内容区、Card 默认 中等: 0 1px 3px rgba(0,0,0,0.06) → Header 悬停: var(--shadow-md) → Card:hover (全局自动) 强调: 0 25px 50px rgba(99,102,241,0.25) → 登录卡片 ``` --- ## 6. 页面结构模板 ### 6.1 列表页(ProTable 页面) 这是最常见的页面类型,用于设备类型、批次、用户、管理员等管理页面。 ```tsx import React, { useState, useRef } from 'react'; import { Button, message, Modal, Form, Input, Space, Tag, Popconfirm } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; import { ProTable } from '@ant-design/pro-components'; import type { ProColumns, ActionType } from '@ant-design/pro-components'; import { getItems, createItem, updateItem, deleteItem } from '../../api/xxx'; import type { Item } from '../../api/xxx'; const XxxPage: React.FC = () => { const actionRef = useRef(null); const [modalVisible, setModalVisible] = useState(false); const [editingRecord, setEditingRecord] = useState(null); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); // --- CRUD handlers --- const handleAdd = () => { setEditingRecord(null); form.resetFields(); setModalVisible(true); }; const handleEdit = (record: Item) => { setEditingRecord(record); form.setFieldsValue(record); setModalVisible(true); }; const handleDelete = async (id: number) => { try { await deleteItem(id); message.success('删除成功'); actionRef.current?.reload(); } catch (error) { message.error(error instanceof Error ? error.message : '删除失败'); } }; const handleSubmit = async () => { try { const values = await form.validateFields(); setLoading(true); if (editingRecord) { await updateItem(editingRecord.id, values); message.success('更新成功'); } else { await createItem(values); message.success('创建成功'); } setModalVisible(false); form.resetFields(); actionRef.current?.reload(); } catch (error) { if (error instanceof Error) { message.error(error.message); } } finally { setLoading(false); } }; // --- Column definitions --- const columns: ProColumns[] = [ { title: 'ID', dataIndex: 'id', width: 80, search: false, }, { title: '名称', dataIndex: 'name', ellipsis: true, }, { title: '状态', dataIndex: 'is_active', width: 80, search: false, render: (_, record) => ( {record.is_active ? '正常' : '禁用'} ), }, { title: '创建时间', dataIndex: 'created_at', valueType: 'dateTime', width: 180, search: false, }, { title: '操作', width: 150, search: false, render: (_, record) => ( handleDelete(record.id)}> ), }, ]; return (
{/* ✅ 必须加 cardBordered */} headerTitle="Xxx 管理" rowKey="id" actionRef={actionRef} columns={columns} cardBordered request={async (params) => { try { const res = await getItems({ page: params.current, page_size: params.pageSize, }); return { data: res.data.items, total: res.data.total, success: true, }; } catch (error) { message.error('获取数据失败'); return { data: [], total: 0, success: false }; } }} toolBarRender={() => [ , ]} pagination={{ defaultPageSize: 10, showSizeChanger: true, }} /> {/* Modal - 统一 layout="vertical" + marginTop 24 */} setModalVisible(false)} confirmLoading={loading} destroyOnClose >
); }; export default XxxPage; ``` **关键规则:** 1. ProTable 必须添加 `cardBordered` 属性 2. Modal 内 Form 统一 `layout="vertical"` + `style={{ marginTop: 24 }}` 3. 删除操作必须用 `Popconfirm` 确认 4. ID 列:`width: 80, search: false` 5. 时间列:`valueType: 'dateTime', width: 180, search: false` 6. 操作列:`search: false`,按钮用 `type="link" size="small"` 7. 分页统一:`defaultPageSize: 10, showSizeChanger: true` ### 6.2 详情页 ```tsx import { statColors } from '../../theme/tokens'; // 页头:返回按钮 + 标题 详情 - {record.name} // 信息卡片 // 统计数据 // 子表格 ``` ### 6.3 统计卡片 Dashboard 风格的统计卡片模板: ```tsx import { statColors } from '../../theme/tokens';
{/* 图标容器 */}
{/* 数值区 */}
标题
{value.toLocaleString()}
``` **图标背景色规则:** 使用对应 statColor 的 8% 透明度作为背景。 ``` rgba(99, 102, 241, 0.08) → primary 背景 rgba(16, 185, 129, 0.08) → success 背景 rgba(139, 92, 246, 0.08) → purple 背景 rgba(245, 158, 11, 0.08) → warning 背景 rgba(6, 182, 212, 0.08) → cyan 背景 rgba(236, 72, 153, 0.08) → pink 背景 ``` --- ## 7. 图表规范 使用 `@ant-design/charts`,导入方式: ```typescript import { Pie, Column, Line, Area } from '@ant-design/charts'; ``` ### 7.1 饼图 ```tsx const pieConfig = { data: [{ type: '类别', value: 100 }], angleField: 'value', colorField: 'type', radius: 0.85, innerRadius: 0.6, // 环形图 color: [statColors.primary, statColors.success, statColors.warning], label: { text: (d) => `${d.type}\n${d.value}`, style: { fontSize: 12, fontWeight: 500 }, }, legend: { color: { position: 'bottom' as const, layout: { justifyContent: 'center' as const }, }, }, }; ``` ### 7.2 柱状图 ```tsx const columnConfig = { data: [{ name: '类别', value: 100 }], xField: 'name', yField: 'value', style: { radiusTopLeft: 6, radiusTopRight: 6, fill: statColors.primary, fillOpacity: 0.85, }, label: { text: (d) => `${d.value}`, textBaseline: 'bottom' as const, style: { dy: -4, fontSize: 12 }, }, axis: { y: { title: false }, x: { title: false }, }, }; ``` ### 7.3 图表容器 ```tsx
``` - 图表容器高度统一 `320px` - 空数据时显示居中灰色提示文字 - 图表颜色优先使用 `statColors` 中的色值 --- ## 8. Tag 状态映射 ### 8.1 通用状态 ```tsx // 启用/禁用 {record.is_active ? '正常' : '禁用'} // 设备状态 const statusMap = { in_stock: { color: 'default', text: '库存中' }, bound: { color: 'green', text: '已绑定' }, offline: { color: 'red', text: '离线' }, }; // 布尔属性 {value ? '是' : '否'} // 缺失数据 record.field || 未导入 ``` ### 8.2 角色 ```tsx const roleMap = { super_admin: { text: '超级管理员', color: 'red' }, admin: { text: '管理员', color: 'blue' }, operator: { text: '操作员', color: 'default' }, }; ``` --- ## 9. 响应式断点 基于 Ant Design Grid 的断点系统: ```tsx // 统计卡片 (6列网格) // 图表区域 (2列) // 详情页统计 (4列) // Descriptions 响应式列数 ``` --- ## 10. 样式规则 ### 10.1 使用优先级 1. **Ant Design Theme Token** → 优先使用 `theme.useToken()` 获取的值 2. **statColors** → 统计、图表颜色用 `statColors` 3. **CSS 变量** → 阴影、过渡动画用 `var(--xxx)` 4. **内联 style** → 布局、间距等用内联样式 5. **全局 CSS** → 仅用于 Ant Design 组件覆盖(已有的 CSS 文件) ### 10.2 禁止事项 - **不要** 硬编码 `#1890ff`(旧主题色),使用 `themeToken.colorPrimary` 或 `statColors.primary` - **不要** 创建 `.module.css` 文件,保持现有内联样式模式 - **不要** 在页面组件中直接引用 CSS 文件,全局 CSS 已在 `App.tsx` 统一导入 - **不要** 使用 `style={{ color: '#999' }}`,用 `#6b7280` 或 `#9ca3af` 代替 - **不要** 遗漏 ProTable 的 `cardBordered` 属性 ### 10.3 获取主题 Token ```tsx import { theme } from 'antd'; const MyComponent: React.FC = () => { const { token: themeToken } = theme.useToken(); return (
...
); }; ``` --- ## 11. 文件结构 ``` src/ ├── theme/ │ ├── tokens.ts # 主题配置 (themeConfig + statColors) │ └── sidebar.css # 侧边栏样式 ├── styles/ │ └── protable-theme.css # ProTable 全局样式 ├── components/ │ └── Layout/ │ └── index.tsx # 主布局 (侧边栏 + 头部 + Content) ├── pages/ │ ├── Login/index.tsx # 登录页 (独立布局) │ ├── Dashboard/index.tsx │ ├── [Module]/ │ │ ├── index.tsx # 列表页 │ │ └── Detail.tsx # 详情页 (如有) ├── api/ # API 层 ├── store/ # Zustand 状态 ├── routes/index.tsx # 路由配置 ├── App.tsx # 根组件 (ConfigProvider + 主题) └── index.css # CSS 变量 + 全局基础样式 ``` --- ## 12. 新增页面检查清单 新开发一个页面时,逐项检查: - [ ] ProTable 添加了 `cardBordered` - [ ] 使用 `statColors` 而非硬编码颜色 - [ ] Tag 状态映射与已有页面一致 - [ ] Modal Form 使用 `layout="vertical"` + `style={{ marginTop: 24 }}` - [ ] 删除操作使用 `Popconfirm` - [ ] 时间列使用 `valueType: 'dateTime'` - [ ] 页面标题 `` + `marginBottom: 24` - [ ] 响应式布局使用 `Row/Col` + 正确断点 - [ ] 详情页有返回按钮 (`type="text"`) - [ ] 空状态文字颜色使用 `#9ca3af` - [ ] 图表容器高度 `320px` - [ ] 分页配置 `defaultPageSize: 10, showSizeChanger: true` --- ## 13. 新增路由步骤 1. 在 `src/pages/` 下创建页面组件 2. 在 `src/routes/index.tsx` 的 `children` 数组中添加路由 3. 在 `src/components/Layout/index.tsx` 的 `menuItems` 中添加菜单项 4. 在 `src/api/` 下创建对应的 API 文件 ```tsx // routes/index.tsx { path: 'new-page', element: <NewPage />, } // Layout/index.tsx menuItems { key: '/new-page', icon: <SomeOutlined />, label: '新页面', } ```