# 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) => (
}
onClick={() => handleEdit(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={() => [
} onClick={handleAdd}>
新增
,
]}
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';
// 页头:返回按钮 + 标题
} onClick={() => navigate('/xxx')}
type="text" style={{ color: '#6b7280' }}>
返回
详情 - {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: ,
}
// Layout/index.tsx menuItems
{
key: '/new-page',
icon: ,
label: '新页面',
}
```