From 15f0d9aba54e8e58aa318f0837c36ff5e67700b6 Mon Sep 17 00:00:00 2001 From: chenhao Date: Mon, 10 Nov 2025 15:49:56 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20ConfirmDialog=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 ConfirmDialog 组件说明文档 -详细描述 delete、batchDelete、warning、confirm 四种方法 - 提供各方法的参数 --- oms_web/docs/DESIGN_COOKBOOK.md | 607 +++++++++++++++++++++ oms_web/docs/components/ConfirmDialog.md | 330 +++++++++++ oms_web/docs/components/DetailDrawer.md | 469 ++++++++++++++++ oms_web/docs/components/InfoPanel.md | 305 +++++++++++ oms_web/docs/components/ListActionBar.md | 249 +++++++++ oms_web/docs/components/ListTable.md | 385 +++++++++++++ oms_web/docs/components/PageTitleBar.md | 131 +++++ oms_web/docs/components/README.md | 149 +++++ oms_web/docs/components/Toast.md | 398 ++++++++++++++ oms_web/docs/components/TreeFilterPanel.md | 297 ++++++++++ oms_web/docs/pages/main-layout.md | 495 +++++++++++++++++ 11 files changed, 3815 insertions(+) create mode 100644 oms_web/docs/DESIGN_COOKBOOK.md create mode 100644 oms_web/docs/components/ConfirmDialog.md create mode 100644 oms_web/docs/components/DetailDrawer.md create mode 100644 oms_web/docs/components/InfoPanel.md create mode 100644 oms_web/docs/components/ListActionBar.md create mode 100644 oms_web/docs/components/ListTable.md create mode 100644 oms_web/docs/components/PageTitleBar.md create mode 100644 oms_web/docs/components/README.md create mode 100644 oms_web/docs/components/Toast.md create mode 100644 oms_web/docs/components/TreeFilterPanel.md create mode 100644 oms_web/docs/pages/main-layout.md diff --git a/oms_web/docs/DESIGN_COOKBOOK.md b/oms_web/docs/DESIGN_COOKBOOK.md new file mode 100644 index 00000000..231a221c --- /dev/null +++ b/oms_web/docs/DESIGN_COOKBOOK.md @@ -0,0 +1,607 @@ +# Nex Design 设计规范指南 + +> 面向 React + Ant Design + Tailwind CSS 的前端设计语言规范 + +## 目录 + +- [概述](#概述) +- [设计原则](#设计原则) +- [颜色系统](#颜色系统) +- [排版规范](#排版规范) +- [间距系统](#间距系统) +- [组件规范](#组件规范) +- [布局规范](#布局规范) +- [交互规范](#交互规范) +- [响应式设计](#响应式设计) +- [页面模板](#页面模板) + +--- + +## 概述 + +Nex Design 是为开发团队打造的一套标准化设计语言系统,旨在提供统一、高效、易维护的前端设计规范。 + +### 技术栈 + +- **框架**: React 18+ +- **组件库**: Ant Design 5.x +- **样式方案**: Tailwind CSS 3.x +- **包管理**: Yarn +- **运行时**: Node.js 16+ + +### 设计目标 + +1. **一致性**: 确保所有页面和组件保持视觉与交互的一致性 +2. **高效性**: 提供可复用的设计模式,加快开发速度 +3. **可维护性**: 建立清晰的规范体系,降低维护成本 +4. **用户体验**: 优先考虑用户体验,打造直观易用的界面 + +--- + +## 设计原则 + +### 1. 清晰明确 +界面设计应当清晰直观,避免歧义,让用户能够快速理解和操作。 + +### 2. 一致性优先 +保持视觉、交互、用词的一致性,减少用户学习成本。 + +### 3. 效率至上 +优化操作流程,减少用户的操作步骤,提高工作效率。 + +### 4. 反馈及时 +对用户的每一个操作都应给予明确的反馈,包括成功、失败、加载等状态。 + +### 5. 容错友好 +预防用户错误,在错误发生时提供清晰的提示和解决方案。 + +--- + +## 颜色系统 + +### 主色调 + +```css +/* 品牌主色 - Primary (基于 NEX Logo 紫红色) */ +--primary-50: #fce7f6; +--primary-100: #f5bae6; +--primary-200: #ee8dd6; +--primary-300: #e760c6; +--primary-400: #e033b6; +--primary-500: #b8178d; /* 主色 - 匹配 NEX Logo */ +--primary-600: #9c1477; +--primary-700: #801161; +--primary-800: #640d4b; +--primary-900: #480a35; +``` + +### 辅助色系 + +```css +/* 蓝色 - Blue (用于信息提示) */ +--blue-50: #e6f4ff; +--blue-100: #bae0ff; +--blue-200: #91caff; +--blue-300: #69b1ff; +--blue-400: #4096ff; +--blue-500: #1677ff; /* 信息色 */ +--blue-600: #0958d9; +--blue-700: #003eb3; +--blue-800: #002c8c; +--blue-900: #001d66; +``` + +### 功能色 + +```css +/* 成功 - Success */ +--success: #52c41a; +--success-bg: #f6ffed; +--success-border: #b7eb8f; + +/* 警告 - Warning */ +--warning: #faad14; +--warning-bg: #fffbe6; +--warning-border: #ffe58f; + +/* 错误 - Error */ +--error: #ff4d4f; +--error-bg: #fff2f0; +--error-border: #ffccc7; + +/* 信息 - Info */ +--info: #1677ff; +--info-bg: #e6f4ff; +--info-border: #91caff; +``` + +### 中性色 + +```css +/* 文本颜色 */ +--text-primary: rgba(0, 0, 0, 0.88); /* 主要文本 */ +--text-secondary: rgba(0, 0, 0, 0.65); /* 次要文本 */ +--text-tertiary: rgba(0, 0, 0, 0.45); /* 辅助文本 */ +--text-disabled: rgba(0, 0, 0, 0.25); /* 禁用文本 */ + +/* 背景颜色 */ +--bg-primary: #ffffff; +--bg-secondary: #fafafa; +--bg-tertiary: #f5f5f5; +--bg-disabled: #f0f0f0; + +/* 边框颜色 */ +--border-primary: #d9d9d9; +--border-secondary: #f0f0f0; +``` + +### 颜色使用规范 + +1. **主色使用**: 主要用于关键操作按钮、重要信息高亮、链接等 +2. **功能色使用**: 严格按照语义使用,不可混淆 +3. **中性色使用**: 用于文本、背景、边框等基础元素 +4. **对比度**: 确保文本与背景的对比度符合 WCAG 2.0 标准(至少 4.5:1) + +--- + +## 排版规范 + +### 字体家族 + +```css +/* 默认字体栈 */ +font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + +/* 等宽字体(代码、数字) */ +font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', + 'Fira Mono', 'Droid Sans Mono', 'Source Code Pro', monospace; +``` + +### 字号规范 + +| 用途 | 字号 | 行高 | Tailwind Class | 使用场景 | +|------|------|------|----------------|----------| +| 特大标题 | 32px | 1.35 | `text-4xl` | 页面主标题 | +| 大标题 | 24px | 1.35 | `text-2xl` | 区块标题 | +| 中标题 | 20px | 1.4 | `text-xl` | 卡片标题 | +| 小标题 | 16px | 1.5 | `text-base` | 表单标签 | +| 正文 | 14px | 1.5714 | `text-sm` | 正文内容 | +| 辅助文字 | 12px | 1.6667 | `text-xs` | 说明文字 | + +### 字重规范 + +- **Regular (400)**: 正文内容 +- **Medium (500)**: 表单标签、列表项 +- **Semibold (600)**: 小标题、强调文本 +- **Bold (700)**: 标题、重要信息 + +### 文本颜色使用 + +```jsx +// 主要文本 +

主要内容

+ +// 次要文本 +

次要内容

+ +// 辅助文本 +

辅助说明

+ +// 禁用文本 +

禁用状态

+``` + +--- + +## 间距系统 + +### 基础间距单位 + +基于 **8px** 网格系统,所有间距应为 8 的倍数。 + +| 名称 | 数值 | Tailwind Class | 用途 | +|------|------|----------------|------| +| xs | 4px | `space-1` / `p-1` / `m-1` | 紧凑间距 | +| sm | 8px | `space-2` / `p-2` / `m-2` | 小间距 | +| md | 16px | `space-4` / `p-4` / `m-4` | 标准间距 | +| lg | 24px | `space-6` / `p-6` / `m-6` | 大间距 | +| xl | 32px | `space-8` / `p-8` / `m-8` | 超大间距 | +| 2xl | 48px | `space-12` / `p-12` / `m-12` | 区块间距 | + +### 间距使用场景 + +1. **组件内边距**: 通常使用 12px (p-3) 或 16px (p-4) +2. **组件外边距**: 标准使用 16px (m-4) 或 24px (m-6) +3. **区块间距**: 使用 32px (mb-8) 或 48px (mb-12) +4. **栅格间距**: 标准使用 16px 或 24px + +--- + +## 组件规范 + +### 按钮 (Button) + +#### 类型与场景 + +```jsx +import { Button } from 'antd'; + +// 主要按钮 - 用于主要操作 + + +// 次要按钮 - 用于次要操作 + + +// 文本按钮 - 用于辅助操作 + + +// 链接按钮 - 用于跳转 + + +// 危险按钮 - 用于删除等危险操作 + +``` + +#### 尺寸规范 + +- **Large**: 高度 40px,用于页面主要操作 +- **Middle**: 高度 32px,默认尺寸 +- **Small**: 高度 24px,用于紧凑场景 + +#### 使用规范 + +1. 一个操作区域最多只有一个主要按钮 +2. 按钮文字应简洁明确,建议不超过 4 个字 +3. 危险操作必须使用二次确认 +4. 按钮组内按钮间距为 8px + +### 表单 (Form) + +#### 布局规范 + +```jsx +import { Form, Input } from 'antd'; + +
+ + + +
+``` + +#### 表单规范 + +1. **标签对齐**: 默认使用垂直布局 (vertical) +2. **必填标识**: 使用红色星号 (*) 标识 +3. **字段宽度**: 根据内容长度设置合理宽度 +4. **错误提示**: 实时验证,错误信息显示在字段下方 +5. **表单间距**: 表单项之间间距 24px + +### 表格 (Table) + +#### 基础配置 + +```jsx +import { Table } from 'antd'; + + `共 ${total} 条`, + }} + scroll={{ x: 1200 }} +/> +``` + +#### 表格规范 + +1. **分页**: 默认每页 10 条,提供分页器 +2. **行高**: 标准行高 54px (middle 模式) +3. **操作列**: 固定在右侧,宽度根据操作数量调整 +4. **空状态**: 使用统一的空状态提示 +5. **加载状态**: 使用 loading 属性显示加载状态 + +### 卡片 (Card) + +```jsx +import { Card } from 'antd'; + +更多} + className="mb-4" +> +

卡片内容

+
+``` + +#### 卡片规范 + +1. **内边距**: 标准 24px +2. **圆角**: 8px (rounded-lg) +3. **阴影**: 默认使用 `shadow-sm` +4. **间距**: 卡片之间间距 16px + +--- + +## 布局规范 + +### 页面布局结构 + +``` +┌─────────────────────────────────────┐ +│ Header (64px) │ +├─────────────────────────────────────┤ +│ Sider │ Content Area │ +│ (200px)│ │ +│ │ │ +│ │ │ +└─────────────────────────────────────┘ +``` + +### 栅格系统 + +采用 24 栏栅格系统,基于 Ant Design Grid 组件。 + +```jsx +import { Row, Col } from 'antd'; + + + + 内容 + + +``` + +### 布局规范 + +1. **页面内边距**: 24px +2. **内容最大宽度**: 1200px (根据实际需求调整) +3. **侧边栏宽度**: 200px (收起后 64px) +4. **顶部导航高度**: 64px +5. **栅格间距**: 水平 16px,垂直 16px + +--- + +## 交互规范 + +### 反馈 + +#### 全局提示 (Message) + +```jsx +import { message } from 'antd'; + +// 成功提示 +message.success('操作成功'); + +// 错误提示 +message.error('操作失败,请重试'); + +// 警告提示 +message.warning('请注意数据安全'); + +// 加载提示 +const hide = message.loading('加载中...'); +``` + +#### 通知提醒 (Notification) + +```jsx +import { notification } from 'antd'; + +notification.open({ + message: '系统通知', + description: '您有新的消息,请及时查看', + duration: 4.5, +}); +``` + +#### 模态对话框 (Modal) + +```jsx +import { Modal } from 'antd'; + +// 确认对话框 +Modal.confirm({ + title: '确认删除', + content: '删除后数据无法恢复,确定要删除吗?', + okText: '确定', + cancelText: '取消', + onOk() { + // 处理确认 + }, +}); +``` + +### 加载状态 + +1. **局部加载**: 使用 Spin 组件 +2. **按钮加载**: 设置 loading 属性 +3. **页面加载**: 骨架屏 (Skeleton) +4. **表格加载**: Table 的 loading 属性 + +### 动画效果 + +1. **过渡时间**: 标准 300ms +2. **缓动函数**: ease-in-out +3. **使用场景**: 展开/收起、显示/隐藏、页面切换 + +--- + +## 响应式设计 + +### 断点定义 + +| 断点 | 屏幕宽度 | 设备类型 | Tailwind 前缀 | +|------|----------|----------|---------------| +| xs | < 576px | 手机竖屏 | - | +| sm | ≥ 576px | 手机横屏 | `sm:` | +| md | ≥ 768px | 平板 | `md:` | +| lg | ≥ 992px | 桌面显示器 | `lg:` | +| xl | ≥ 1200px | 大桌面显示器 | `xl:` | +| 2xl | ≥ 1600px | 超大显示器 | `2xl:` | + +### 响应式策略 + +1. **移动优先**: 先设计移动端,再扩展到桌面端 +2. **弹性布局**: 使用 Flexbox 和 Grid +3. **自适应图片**: 使用相对单位和 max-width +4. **触摸友好**: 移动端可点击区域不小于 44x44px + +--- + +## 页面模板 + +### 通用页面结构 + +```jsx +import { Layout, Breadcrumb } from 'antd'; +const { Content } = Layout; + +function PageTemplate() { + return ( + + {/* 面包屑导航 */} + + 首页 + 当前页面 + + + {/* 页面标题区 */} +
+

页面标题

+

页面描述信息

+
+ + {/* 主要内容区 */} +
+ {/* 页面内容 */} +
+
+ ); +} +``` + +### 已实现的页面模板 + +#### 1. 主框架页面 ✓ + +完整的应用框架布局,包含: +- 侧边菜单栏(支持收起/展开,两级菜单) +- 顶部导航栏(搜索、消息、用户信息) +- 内容区域(可滚动) + +**详细文档**: [主框架页面设计规范](../docs/pages/main-layout.md) + +**主要特性**: +- 基于 NEX Logo 的品牌配色 (#b8178d) +- 菜单数据 JSON 配置 +- 响应式布局 +- 徽章系统(HOT/NEW) +- 用户下拉菜单 + +**使用示例**: +```jsx +import MainLayout from './components/MainLayout' + +function App() { + return ( + + {/* 页面内容 */} + + ) +} +``` + +#### 2. 概览页 (Dashboard) ✓ + +数据概览页面,展示: +- 统计卡片(证书总量、资产数量等) +- 环形进度图表 +- 数据走势图 + +**主要组件**: +- 响应式栅格布局 (Row/Col) +- 统计卡片 (Statistic) +- 进度条 (Progress) +- 图表区域 + +### 待完善的页面模板 + +后续将添加以下页面模板: + +- [ ] **列表页**: 数据表格、筛选、操作 +- [ ] **详情页**: 信息展示、关联数据 +- [ ] **表单页**: 数据录入、验证、提交 +- [ ] **设置页**: 配置管理、偏好设置 + +--- + +## 开发规范 + +### 代码组织 + +``` +src/ +├── components/ # 公共组件 +│ ├── Button/ +│ ├── Card/ +│ └── ... +├── pages/ # 页面组件 +│ ├── Dashboard/ +│ ├── List/ +│ └── ... +├── styles/ # 样式文件 +│ ├── globals.css +│ └── variables.css +├── utils/ # 工具函数 +└── constants/ # 常量定义 +``` + +### 命名规范 + +1. **组件命名**: 大驼峰 (PascalCase),如 `UserList` +2. **文件命名**: 与组件同名,如 `UserList.jsx` +3. **样式类命名**: 小写短横线 (kebab-case) +4. **常量命名**: 全大写下划线 (UPPER_SNAKE_CASE) + +### CSS 使用规范 + +1. **优先使用 Tailwind**: 布局、间距、颜色等 +2. **Ant Design 定制**: 通过主题配置实现 +3. **自定义样式**: 仅在必要时使用,放在组件目录下 + +--- + +## 版本记录 + +| 版本 | 日期 | 说明 | +|------|------|------| +| 1.0.0 | 2024-11-04 | 初始版本,建立基础设计规范 | +| 1.1.0 | 2024-11-04 | 更新品牌配色,完成主框架页面和概览页 | + +--- + +## 参考资源 + +- [Ant Design 官方文档](https://ant.design/) +- [Tailwind CSS 官方文档](https://tailwindcss.com/) +- [React 官方文档](https://react.dev/) +- [Material Design 设计指南](https://material.io/design) + +--- + +**维护者**: Nex Design Team +**最后更新**: 2024-11-04 diff --git a/oms_web/docs/components/ConfirmDialog.md b/oms_web/docs/components/ConfirmDialog.md new file mode 100644 index 00000000..ed85f439 --- /dev/null +++ b/oms_web/docs/components/ConfirmDialog.md @@ -0,0 +1,330 @@ +# ConfirmDialog 组件 + +## 组件说明 + +确认对话框组件,基于 Ant Design Modal 封装,提供统一的确认对话框样式和交互。支持单个删除、批量删除、警告确认和通用确认等多种场景。 + +## 组件位置 + +``` +src/components/ConfirmDialog/ConfirmDialog.jsx +``` + +## API 方法 + +组件以静态方法的形式提供,无需实例化,直接调用即可。 + +### ConfirmDialog.delete() + +显示单个项目删除确认对话框。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| title | string | 否 | '确认删除' | 对话框标题 | +| itemName | string | 是 | - | 要删除的项目名称 | +| itemInfo | string | 否 | - | 项目附加信息 | +| onOk | function | 否 | - | 确认回调,支持返回 Promise | +| onCancel | function | 否 | - | 取消回调 | + +#### 使用示例 + +```jsx +import ConfirmDialog from '../components/ConfirmDialog/ConfirmDialog' + +const handleDeleteUser = (record) => { + ConfirmDialog.delete({ + itemName: `用户名:${record.userName}`, + itemInfo: `姓名:${record.name}`, + onOk() { + return new Promise((resolve) => { + setTimeout(() => { + // 执行删除操作 + deleteUser(record.id) + resolve() + Toast.success('删除成功', `用户 "${record.userName}" 已成功删除`) + }, 1000) + }) + }, + }) +} +``` + +### ConfirmDialog.batchDelete() + +显示批量删除确认对话框。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| count | number | 是 | - | 要删除的项目数量 | +| items | Array | 是 | - | 项目列表 | +| onOk | function | 否 | - | 确认回调,支持返回 Promise | +| onCancel | function | 否 | - | 取消回调 | + +##### ItemInfo 对象 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| name | string | 是 | 项目名称 | +| info | string | 否 | 项目附加信息 | + +#### 使用示例 + +```jsx +const handleBatchDelete = () => { + const selectedUsers = filteredUsers.filter((u) => selectedRowKeys.includes(u.id)) + const items = selectedUsers.map((user) => ({ + name: user.userName, + info: user.name, + })) + + ConfirmDialog.batchDelete({ + count: selectedRowKeys.length, + items, + onOk() { + return new Promise((resolve) => { + setTimeout(() => { + const count = selectedRowKeys.length + const newUsers = filteredUsers.filter((u) => !selectedRowKeys.includes(u.id)) + setFilteredUsers(newUsers) + setSelectedRowKeys([]) + resolve() + Toast.success('批量删除成功', `已成功删除 ${count} 个用户`) + }, 1000) + }) + }, + }) +} +``` + +### ConfirmDialog.warning() + +显示警告确认对话框。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| title | string | 是 | - | 对话框标题 | +| content | string\|ReactNode | 是 | - | 对话框内容 | +| okText | string | 否 | '确定' | 确认按钮文字 | +| cancelText | string | 否 | '取消' | 取消按钮文字 | +| onOk | function | 否 | - | 确认回调 | +| onCancel | function | 否 | - | 取消回调 | + +#### 使用示例 + +```jsx +const handleRiskyOperation = () => { + ConfirmDialog.warning({ + title: '操作警告', + content: '此操作可能影响系统稳定性,确定要继续吗?', + okText: '继续操作', + onOk() { + // 执行危险操作 + performRiskyOperation() + }, + }) +} +``` + +### ConfirmDialog.confirm() + +显示通用确认对话框。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| title | string | 是 | - | 对话框标题 | +| content | string\|ReactNode | 是 | - | 对话框内容 | +| okText | string | 否 | '确定' | 确认按钮文字 | +| cancelText | string | 否 | '取消' | 取消按钮文字 | +| okType | string | 否 | 'primary' | 确认按钮类型(primary/danger) | +| onOk | function | 否 | - | 确认回调 | +| onCancel | function | 否 | - | 取消回调 | + +#### 使用示例 + +```jsx +const handleSubmit = () => { + ConfirmDialog.confirm({ + title: '提交确认', + content: '确定要提交当前表单吗?', + okText: '提交', + onOk() { + // 执行提交操作 + submitForm() + }, + }) +} +``` + +## 完整使用示例 + +### 单个删除 + +```jsx +import ConfirmDialog from '../components/ConfirmDialog/ConfirmDialog' +import Toast from '../components/Toast/Toast' + +function UserListPage() { + const handleDeleteUser = (record) => { + ConfirmDialog.delete({ + itemName: `用户名:${record.userName}`, + itemInfo: `姓名:${record.name}`, + onOk() { + return new Promise((resolve) => { + setTimeout(() => { + const newUsers = filteredUsers.filter((u) => u.id !== record.id) + setFilteredUsers(newUsers) + resolve() + Toast.success('删除成功', `用户 "${record.userName}" 已成功删除`) + }, 1000) + }) + }, + }) + } + + return ( + + ) +} +``` + +### 批量删除 + +```jsx +function UserListPage() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + + const handleBatchDelete = () => { + const selectedUsers = filteredUsers.filter((u) => selectedRowKeys.includes(u.id)) + const items = selectedUsers.map((user) => ({ + name: user.userName, + info: user.name, + })) + + ConfirmDialog.batchDelete({ + count: selectedRowKeys.length, + items, + onOk() { + return new Promise((resolve) => { + setTimeout(() => { + const count = selectedRowKeys.length + const newUsers = filteredUsers.filter((u) => !selectedRowKeys.includes(u.id)) + setFilteredUsers(newUsers) + setSelectedRowKeys([]) + resolve() + Toast.success('批量删除成功', `已成功删除 ${count} 个用户`) + }, 1000) + }) + }, + }) + } + + return ( + + ) +} +``` + +### 自定义内容 + +```jsx +ConfirmDialog.confirm({ + title: '重置密码', + content: ( +
+

确定要重置用户 {user.userName} 的密码吗?

+

新密码将发送到用户邮箱

+
+ ), + okText: '确认重置', + okType: 'primary', + onOk() { + resetPassword(user.id) + }, +}) +``` + +### 异步操作处理 + +```jsx +const handleDeleteUser = (record) => { + ConfirmDialog.delete({ + itemName: `用户名:${record.userName}`, + itemInfo: `姓名:${record.name}`, + async onOk() { + try { + // 调用 API 删除用户 + await api.deleteUser(record.id) + + // 更新本地数据 + const newUsers = filteredUsers.filter((u) => u.id !== record.id) + setFilteredUsers(newUsers) + + Toast.success('删除成功', `用户 "${record.userName}" 已成功删除`) + } catch (error) { + Toast.error('删除失败', error.message) + throw error // 阻止对话框关闭 + } + }, + }) +} +``` + +## 特性说明 + +### 删除确认样式 + +- 红色危险图标 +- 高亮显示要删除的项目信息 +- 红色警告提示"此操作不可恢复,请谨慎操作!" +- 危险样式的确认按钮 + +### 批量删除列表 + +- 最多显示 200px 高度的滚动列表 +- 每个项目显示名称和附加信息 +- 项目间用分隔线隔开 + +### Promise 支持 + +- `onOk` 回调支持返回 Promise +- 异步操作进行时,确认按钮显示 loading 状态 +- Promise reject 时,对话框不会关闭 +- 适合调用 API 等异步操作 + +### 居中显示 + +- 所有对话框默认垂直居中显示(`centered: true`) + +## 使用场景 + +1. **删除确认** - 删除用户、设备、订单等数据前的确认 +2. **批量删除** - 批量删除多条数据前的确认 +3. **危险操作警告** - 执行可能影响系统的操作前的警告 +4. **通用确认** - 提交表单、保存设置等操作的确认 +5. **重要操作二次确认** - 任何需要用户明确确认的操作 + +## 注意事项 + +1. 删除操作统一使用红色危险样式,提醒用户谨慎操作 +2. `onOk` 回调支持同步和异步(返回 Promise)两种方式 +3. 批量删除时,`items` 数组会在对话框中完整展示,注意数量控制 +4. 对话框内容支持字符串和 React 节点,可以自定义复杂内容 +5. 确认按钮文字建议明确操作类型,如"确认删除"、"确认提交"等 +6. 配合 Toast 组件使用,提供操作结果反馈 +7. 异步操作失败时,throw error 可以阻止对话框关闭 diff --git a/oms_web/docs/components/DetailDrawer.md b/oms_web/docs/components/DetailDrawer.md new file mode 100644 index 00000000..76636668 --- /dev/null +++ b/oms_web/docs/components/DetailDrawer.md @@ -0,0 +1,469 @@ +# DetailDrawer 组件 + +## 组件说明 + +详情抽屉组件,用于从页面右侧滑出显示详细信息。支持自定义标题、操作按钮、标签页等功能,内容区域可滚动,顶部标题栏固定。 + +## 组件位置 + +``` +src/components/DetailDrawer/DetailDrawer.jsx +src/components/DetailDrawer/DetailDrawer.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| visible | boolean | 是 | - | 是否显示抽屉 | +| onClose | function | 是 | - | 关闭抽屉回调 | +| title | TitleConfig | 否 | - | 标题配置对象 | +| headerActions | Array | 否 | [] | 顶部操作按钮数组 | +| width | number | 否 | 1080 | 抽屉宽度(像素) | +| children | ReactNode | 否 | - | 主要内容区域 | +| tabs | Array | 否 | - | 标签页配置数组 | + +### 1. 小型抽屉 (Small) - 480px +**适用场景:** +- 简单的信息展示 +- 少量字段的表单(1-3个字段) +- 快速操作面板 +- 通知详情 + +**示例:** +```jsx + +``` + +### 2. 中型抽屉 (Medium) - 720px +**适用场景:** +- 详细信息展示(如主机详情) +- 中等复杂度的表单(4-10个字段) +- 数据编辑面板 +- 配置设置 + +**示例:** +```jsx + +``` +**当前主机列表页面使用此宽度模式** + +### 3. 大型抽屉 (Large) - 1080px +**适用场景:** +- 复杂的多步骤表单 +- 需要并排展示多列信息 +- 包含图表或复杂可视化内容 +- 嵌套子表格或列表 + +**示例:** +```jsx + +``` + +### TitleConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| text | string | 是 | 标题文本 | +| badge | ReactNode | 否 | 状态徽标(如 Tag、Badge 组件) | +| icon | ReactNode | 否 | 标题图标 | + +### ActionConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 按钮唯一标识 | +| label | string | 是 | 按钮文本 | +| icon | ReactNode | 否 | 按钮图标 | +| type | string | 否 | 按钮类型(primary/default/dashed/text/link) | +| danger | boolean | 否 | 是否为危险按钮 | +| disabled | boolean | 否 | 是否禁用 | +| onClick | function | 否 | 点击回调函数 | + +### TabConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 标签页唯一标识 | +| label | ReactNode | 是 | 标签页标题(支持图标+文字) | +| content | ReactNode | 是 | 标签页内容 | + +## 使用示例 + +### 基础用法 + +```jsx +import { useState } from 'react' +import DetailDrawer from '../components/DetailDrawer/DetailDrawer' + +function MyPage() { + const [showDrawer, setShowDrawer] = useState(false) + const [selectedItem, setSelectedItem] = useState(null) + + return ( + <> + + + setShowDrawer(false)} + title={{ + text: selectedItem?.name || '详情', + }} + > +
+

详情内容

+
+
+ + ) +} +``` + +### 带状态徽标 + +```jsx +import { Tag, Badge } from 'antd' + + setShowDetailDrawer(false)} + title={{ + text: selectedUser?.userName || '', + badge: ( + + {selectedUser?.status === 'enabled' ? '启用' : '停用'} + + ), + }} +> + {/* 内容 */} + +``` + +### 带操作按钮 + +```jsx +import { EditOutlined, DeleteOutlined } from '@ant-design/icons' + + setShowDetailDrawer(false)} + title={{ + text: selectedUser?.userName || '', + }} + headerActions={[ + { + key: 'edit', + label: '编辑', + icon: , + onClick: () => { + setEditMode('edit') + setShowEditDrawer(true) + setShowDetailDrawer(false) + }, + }, + { + key: 'delete', + label: '删除', + icon: , + danger: true, + onClick: () => { + setShowDetailDrawer(false) + handleDelete(selectedUser) + }, + }, + ]} +> + {/* 内容 */} + +``` + +### 使用 InfoPanel 显示信息 + +```jsx +import DetailDrawer from '../components/DetailDrawer/DetailDrawer' +import InfoPanel from '../components/InfoPanel/InfoPanel' + +const userFields = [ + { key: 'userName', label: '用户名', span: 6 }, + { key: 'group', label: '用户分组', span: 6 }, + { key: 'name', label: '姓名', span: 6 }, + { key: 'userType', label: '用户类型', span: 6 }, + { + key: 'status', + label: '状态', + span: 6, + render: (value) => ( + + {value === 'enabled' ? '启用' : '停用'} + + ), + }, +] + + setShowDetailDrawer(false)} + title={{ + text: selectedUser?.userName || '', + }} +> + console.log('重置密码') }, + { key: 'disable', label: '停用', onClick: () => console.log('停用') }, + ]} + /> + +``` + +### 带标签页 + +```jsx +import { DatabaseOutlined, UserOutlined } from '@ant-design/icons' + + setShowDetailDrawer(false)} + title={{ + text: selectedHost?.name || '', + badge: ( + + ), + }} + headerActions={[ + { + key: 'edit', + label: '编辑', + icon: , + onClick: handleEdit, + }, + ]} + width={1080} + tabs={[ + { + key: 'images', + label: ( + + + 终端镜像 + + ), + content: ( +
+ {/* 镜像列表内容 */} +
+ ), + }, + { + key: 'users', + label: ( + + + 终端用户 + + ), + content: ( +
+ {/* 用户列表内容 */} +
+ ), + }, + ]} +> + +
+``` + +### 完整示例 + +```jsx +import { useState } from 'react' +import DetailDrawer from '../components/DetailDrawer/DetailDrawer' +import InfoPanel from '../components/InfoPanel/InfoPanel' +import { Tag, Badge, Card } from 'antd' +import { EditOutlined, DeleteOutlined, DatabaseOutlined, UserOutlined } from '@ant-design/icons' + +function UserListPage() { + const [showDetailDrawer, setShowDetailDrawer] = useState(false) + const [selectedUser, setSelectedUser] = useState(null) + + const userFields = [ + { key: 'userName', label: '用户名', span: 6 }, + { key: 'group', label: '用户分组', span: 6 }, + { key: 'name', label: '姓名', span: 6 }, + { key: 'grantedImages', label: '授权镜像', span: 6 }, + { key: 'userType', label: '用户类型', span: 6 }, + { key: 'grantedTerminals', label: '授权终端', span: 6 }, + { + key: 'status', + label: '启停用', + span: 6, + render: (value) => ( + + {value === 'enabled' ? '启用' : '停用'} + + ), + }, + ] + + const detailTabs = [ + { + key: 'terminals', + label: ( + + + 授权终端 + + ), + content: ( +
+ {selectedUser?.terminals?.map((terminal) => ( + +

{terminal.name}

+

IP: {terminal.ip}

+
+ ))} +
+ ), + }, + { + key: 'images', + label: ( + + + 授权镜像 + + ), + content: ( +
+ {selectedUser?.images?.map((image) => ( + +

{image.name}

+

系统: {image.os}

+
+ ))} +
+ ), + }, + ] + + return ( + <> + {/* 列表页面 */} + { + setSelectedUser(record) + setShowDetailDrawer(true) + }} + /> + + {/* 详情抽屉 */} + setShowDetailDrawer(false)} + title={{ + text: selectedUser?.userName || '', + badge: ( + + {selectedUser?.status === 'enabled' ? '启用' : '停用'} + + ), + }} + headerActions={[ + { + key: 'edit', + label: '编辑', + icon: , + onClick: () => { + setShowDetailDrawer(false) + handleEdit(selectedUser) + }, + }, + { + key: 'delete', + label: '删除', + icon: , + danger: true, + onClick: () => { + setShowDetailDrawer(false) + handleDelete(selectedUser) + }, + }, + ]} + width={1080} + tabs={detailTabs} + > + console.log('转移分组') }, + { key: 'reset', label: '重置密码', onClick: () => console.log('重置密码') }, + { key: 'disable', label: '停用', onClick: () => console.log('停用') }, + ]} + /> + + + ) +} +``` + +## 布局结构 + +抽屉采用固定头部、可滚动内容的布局: + +``` +┌─────────────────────────────────────┐ +│ 标题栏(固定,不滚动) │ +│ [关闭] [标题] [徽标] [操作按钮] │ +├─────────────────────────────────────┤ +│ │ +│ 内容区域(可滚动) │ +│ - children 主要内容 │ +│ - tabs 标签页(可选) │ +│ │ +└─────────────────────────────────────┘ +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.detail-drawer-content` - 抽屉内容容器 +- `.detail-drawer-header` - 顶部标题栏 +- `.detail-drawer-header-left` - 标题栏左侧区域 +- `.detail-drawer-header-right` - 标题栏右侧区域 +- `.detail-drawer-close-button` - 关闭按钮 +- `.detail-drawer-header-info` - 标题信息容器 +- `.detail-drawer-title-icon` - 标题图标 +- `.detail-drawer-title` - 标题文本 +- `.detail-drawer-badge` - 徽标容器 +- `.detail-drawer-scrollable-content` - 可滚动内容区域 +- `.detail-drawer-tabs` - 标签页容器 +- `.detail-drawer-tab-content` - 标签页内容 + +## 使用场景 + +1. **查看详细信息** - 点击列表行显示详细信息 +2. **多标签页详情** - 在详情中展示不同类型的关联数据 +3. **带快捷操作的详情** - 在详情顶部提供编辑、删除等操作 +4. **复杂数据展示** - 配合 InfoPanel、Card 等组件展示复杂信息 + +## 注意事项 + +1. 抽屉宽度默认 1080px,可根据内容调整,建议取值范围:720-1200px +2. 标题栏固定在顶部,不随内容滚动,确保操作按钮始终可见 +3. `children` 内容区域会自动应用内边距,`tabs` 内容需要自行控制样式 +4. 操作按钮数量不宜过多,建议不超过 3 个 +5. 使用 `tabs` 时,第一个标签页默认激活 +6. 关闭抽屉时建议清空选中状态,避免下次打开时显示旧数据 +7. 配合 InfoPanel 使用时,InfoPanel 会自动处理内边距 diff --git a/oms_web/docs/components/InfoPanel.md b/oms_web/docs/components/InfoPanel.md new file mode 100644 index 00000000..9c928d66 --- /dev/null +++ b/oms_web/docs/components/InfoPanel.md @@ -0,0 +1,305 @@ +# InfoPanel 组件 + +## 组件说明 + +信息展示面板组件,用于以网格布局展示数据的字段信息,支持自定义字段渲染和操作按钮。常用于详情页面或抽屉中展示结构化数据。 + +## 组件位置 + +``` +src/components/InfoPanel/InfoPanel.jsx +src/components/InfoPanel/InfoPanel.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| data | Object | 是 | - | 数据源对象 | +| fields | Array | 否 | [] | 字段配置数组 | +| actions | Array | 否 | [] | 操作按钮配置数组 | +| gutter | Array | 否 | [24, 16] | Grid 网格间距 [水平, 垂直] | + +### FieldConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 数据字段名 | +| label | string | 是 | 字段显示标签 | +| span | number | 否 | 网格占位份数(24栅格系统),默认 6 | +| render | function(value, data) | 否 | 自定义渲染函数 | + +### ActionConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 按钮唯一标识 | +| label | string | 是 | 按钮文本 | +| icon | ReactNode | 否 | 按钮图标 | +| type | string | 否 | 按钮类型(primary/default/dashed/text/link) | +| danger | boolean | 否 | 是否为危险按钮 | +| disabled | boolean | 否 | 是否禁用 | +| onClick | function | 否 | 点击回调函数 | + +## 使用示例 + +### 基础用法 + +```jsx +import InfoPanel from '../components/InfoPanel/InfoPanel' + +function UserDetail() { + const userData = { + userName: 'admin', + name: '系统管理员', + group: '管理员组', + userType: '管理员', + status: 'enabled', + grantedTerminals: 8, + grantedImages: 21, + } + + const userFields = [ + { key: 'userName', label: '用户名', span: 6 }, + { key: 'name', label: '姓名', span: 6 }, + { key: 'group', label: '用户分组', span: 6 }, + { key: 'userType', label: '用户类型', span: 6 }, + { key: 'grantedTerminals', label: '授权终端', span: 6 }, + { key: 'grantedImages', label: '授权镜像', span: 6 }, + ] + + return +} +``` + +### 自定义字段渲染 + +```jsx +import { Tag } from 'antd' + +const userFields = [ + { key: 'userName', label: '用户名', span: 6 }, + { key: 'name', label: '姓名', span: 6 }, + { + key: 'status', + label: '状态', + span: 6, + render: (value) => ( + + {value === 'enabled' ? '启用' : '停用'} + + ), + }, + { + key: 'description', + label: '描述', + span: 18, + render: (value) => value || '--', // 空值显示默认占位符 + }, +] + + +``` + +### 带操作按钮 + +```jsx +import { LockOutlined } from '@ant-design/icons' + + console.log('转移分组'), + }, + { + key: 'blacklist', + label: '加入黑名单', + onClick: () => console.log('加入黑名单'), + }, + { + key: 'reset', + label: '重置密码', + onClick: () => console.log('重置密码'), + }, + { + key: 'disable', + label: '停用', + icon: , + onClick: () => console.log('停用'), + }, + ]} +/> +``` + +### 配合 DetailDrawer 使用 + +```jsx +import DetailDrawer from '../components/DetailDrawer/DetailDrawer' +import InfoPanel from '../components/InfoPanel/InfoPanel' +import { Tag } from 'antd' + +function UserListPage() { + const [showDetailDrawer, setShowDetailDrawer] = useState(false) + const [selectedUser, setSelectedUser] = useState(null) + + const userFields = [ + { key: 'userName', label: '用户名', span: 6 }, + { key: 'group', label: '用户分组', span: 6 }, + { key: 'name', label: '姓名', span: 6 }, + { key: 'grantedImages', label: '授权镜像', span: 6 }, + { key: 'userType', label: '用户类型', span: 6 }, + { key: 'grantedTerminals', label: '授权终端', span: 6 }, + { + key: 'status', + label: '启停用', + span: 6, + render: (value) => ( + + {value === 'enabled' ? '启用' : '停用'} + + ), + }, + { key: 'description', label: '描述', span: 18, render: () => '--' }, + ] + + return ( + setShowDetailDrawer(false)} + title={{ + text: selectedUser?.userName || '', + }} + > + console.log('转移分组') }, + { key: 'blacklist', label: '加入黑名单', onClick: () => console.log('加入黑名单') }, + { key: 'reset', label: '重置密码', onClick: () => console.log('重置密码') }, + { key: 'disable', label: '停用', onClick: () => console.log('停用') }, + ]} + /> + + ) +} +``` + +### 不同 span 值的布局 + +```jsx +// 24栅格系统,一行总共24份 +const fields = [ + { key: 'field1', label: '字段1', span: 6 }, // 占1/4宽度 + { key: 'field2', label: '字段2', span: 6 }, // 占1/4宽度 + { key: 'field3', label: '字段3', span: 6 }, // 占1/4宽度 + { key: 'field4', label: '字段4', span: 6 }, // 占1/4宽度,满一行 + { key: 'field5', label: '字段5', span: 8 }, // 占1/3宽度 + { key: 'field6', label: '字段6', span: 8 }, // 占1/3宽度 + { key: 'field7', label: '字段7', span: 8 }, // 占1/3宽度,满一行 + { key: 'field8', label: '字段8', span: 12 }, // 占1/2宽度 + { key: 'field9', label: '字段9', span: 12 }, // 占1/2宽度,满一行 + { key: 'description', label: '描述', span: 24 }, // 占满整行 +] + + +``` + +### 访问完整数据对象 + +```jsx +const fields = [ + { key: 'userName', label: '用户名', span: 6 }, + { + key: 'status', + label: '状态信息', + span: 18, + // render 函数的第二个参数是完整的数据对象 + render: (value, data) => ( +
+ + {value === 'enabled' ? '启用' : '停用'} + + + 授权终端:{data.grantedTerminals} 台 + +
+ ), + }, +] + + +``` + +### 自定义网格间距 + +```jsx +// 默认间距 [24, 16] + + +// 紧凑布局 + +``` + +## 布局说明 + +组件使用 Ant Design 的 24 栅格系统: + +``` +┌─────────────────────────────────────────────────┐ +│ span=6 span=6 span=6 span=6 │ 一行4列 +├─────────────────────────────────────────────────┤ +│ span=8 span=8 span=8 │ 一行3列 +├─────────────────────────────────────────────────┤ +│ span=12 span=12 │ 一行2列 +├─────────────────────────────────────────────────┤ +│ span=24 │ 占满一行 +└─────────────────────────────────────────────────┘ +``` + +常用 span 值: +- `span=6` - 一行 4 列 +- `span=8` - 一行 3 列 +- `span=12` - 一行 2 列 +- `span=24` - 占满整行(适合描述、备注等长文本字段) + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.info-panel` - 组件根容器 +- `.info-panel-item` - 单个字段容器 +- `.info-panel-label` - 字段标签 +- `.info-panel-value` - 字段值 +- `.info-panel-actions` - 操作按钮区域 + +## 使用场景 + +1. **详情页信息展示** - 在详情抽屉或页面中展示对象的属性信息 +2. **用户信息展示** - 展示用户的基本信息和状态 +3. **设备信息展示** - 展示设备的配置和参数 +4. **订单信息展示** - 展示订单的详细信息 +5. **任何结构化数据展示** - 以标签-值形式展示的数据 + +## 注意事项 + +1. `data` 为 `null` 或 `undefined` 时组件不渲染任何内容 +2. 字段 `span` 值总和建议为 24 的倍数以保持布局整齐 +3. 长文本字段(如描述、备注)建议使用 `span=24` 或 `span=18` +4. `render` 函数可以返回任何 React 节点,包括组件、文本、HTML 等 +5. 操作按钮会显示在所有字段下方,建议不超过 6 个按钮 +6. 使用 `render` 函数时,第一个参数是字段值,第二个参数是完整数据对象 +7. 网格间距 `gutter` 的第一个值是水平间距,第二个值是垂直间距 diff --git a/oms_web/docs/components/ListActionBar.md b/oms_web/docs/components/ListActionBar.md new file mode 100644 index 00000000..97318465 --- /dev/null +++ b/oms_web/docs/components/ListActionBar.md @@ -0,0 +1,249 @@ +# ListActionBar 组件 + +## 组件说明 + +列表操作栏组件,提供统一的操作按钮区、搜索框、高级筛选和刷新功能。常用于列表页面的顶部操作区域。 + +## 组件位置 + +``` +src/components/ListActionBar/ListActionBar.jsx +src/components/ListActionBar/ListActionBar.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| actions | Array | 否 | [] | 左侧操作按钮配置数组 | +| search | SearchConfig | 否 | - | 搜索框配置对象 | +| filter | FilterConfig | 否 | - | 高级筛选配置对象 | +| showRefresh | boolean | 否 | false | 是否显示刷新按钮 | +| onRefresh | function | 否 | - | 刷新按钮点击回调 | + +### ActionConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 按钮唯一标识 | +| label | string | 是 | 按钮文本 | +| icon | ReactNode | 否 | 按钮图标 | +| type | string | 否 | 按钮类型(primary/default/dashed/text/link) | +| disabled | boolean | 否 | 是否禁用 | +| danger | boolean | 否 | 是否为危险按钮 | +| onClick | function | 否 | 点击回调函数 | + +### SearchConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| placeholder | string | 否 | 搜索框占位文本 | +| width | number | 否 | 搜索框宽度,默认 280 | +| value | string | 否 | 搜索框值(受控模式) | +| onSearch | function(value: string) | 否 | 搜索回调 | +| onChange | function(value: string) | 否 | 输入变化回调 | + +### FilterConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| content | ReactNode | 是 | 筛选面板内容 | +| title | string | 否 | 筛选面板标题 | +| visible | boolean | 否 | 控制面板显示/隐藏 | +| onVisibleChange | function(visible: boolean) | 否 | 显示状态变化回调 | +| selectedLabel | string | 否 | 筛选按钮显示的标签文本 | +| isActive | boolean | 否 | 是否处于激活状态(高亮显示) | + +## 使用示例 + +### 基础用法(仅操作按钮) + +```jsx +import ListActionBar from '../components/ListActionBar/ListActionBar' +import { PlusOutlined, DeleteOutlined } from '@ant-design/icons' + +, + type: 'primary', + onClick: () => console.log('新增'), + }, + { + key: 'delete', + label: '批量删除', + icon: , + danger: true, + disabled: selectedRowKeys.length === 0, + onClick: handleBatchDelete, + }, + ]} +/> +``` + +### 带搜索功能 + +```jsx +import { useState } from 'react' + +function MyPage() { + const [searchKeyword, setSearchKeyword] = useState('') + + return ( + , + type: 'primary', + onClick: () => console.log('新增'), + }, + ]} + search={{ + placeholder: '搜索用户名或姓名', + value: searchKeyword, + onSearch: (value) => { + console.log('搜索:', value) + setSearchKeyword(value) + }, + onChange: (value) => setSearchKeyword(value), + }} + /> + ) +} +``` + +### 带高级筛选 + +```jsx +import { useState } from 'react' +import TreeFilterPanel from '../components/TreeFilterPanel/TreeFilterPanel' + +function MyPage() { + const [showFilterPopover, setShowFilterPopover] = useState(false) + const [selectedGroup, setSelectedGroup] = useState(null) + const [selectedGroupName, setSelectedGroupName] = useState('') + + return ( + , + type: 'primary', + onClick: () => console.log('新增'), + }, + ]} + search={{ + placeholder: '搜索关键词', + onSearch: handleSearch, + }} + filter={{ + content: ( + + ), + title: '高级筛选', + visible: showFilterPopover, + onVisibleChange: setShowFilterPopover, + selectedLabel: selectedGroupName, + isActive: !!selectedGroup, + }} + /> + ) +} +``` + +### 完整示例(所有功能) + +```jsx +import { useState } from 'react' +import ListActionBar from '../components/ListActionBar/ListActionBar' +import TreeFilterPanel from '../components/TreeFilterPanel/TreeFilterPanel' +import { PlusOutlined, DeleteOutlined } from '@ant-design/icons' + +function UserListPage() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [searchKeyword, setSearchKeyword] = useState('') + const [showFilterPopover, setShowFilterPopover] = useState(false) + const [selectedGroup, setSelectedGroup] = useState(null) + const [selectedGroupName, setSelectedGroupName] = useState('') + + return ( + , + type: 'primary', + onClick: handleAdd, + }, + { + key: 'batchDelete', + label: '批量删除', + icon: , + danger: true, + disabled: selectedRowKeys.length === 0, + onClick: handleBatchDelete, + }, + ]} + search={{ + placeholder: '搜索用户名或姓名', + value: searchKeyword, + onSearch: handleSearch, + onChange: handleSearch, + }} + filter={{ + content: ( + + ), + title: '高级筛选', + visible: showFilterPopover, + onVisibleChange: setShowFilterPopover, + selectedLabel: selectedGroupName, + isActive: !!selectedGroup, + }} + showRefresh + onRefresh={() => console.log('刷新')} + /> + ) +} +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.list-action-bar` - 组件根容器 +- `.list-action-bar-left` - 左侧操作按钮区域 +- `.list-action-bar-right` - 右侧搜索筛选区域 +- `.filter-popover` - 筛选弹出层容器 + +## 使用场景 + +1. **列表页面顶部操作区** - 提供新增、批量操作等功能 +2. **带搜索的列表** - 快速搜索列表内容 +3. **带分组筛选的列表** - 通过树形结构筛选数据 +4. **需要刷新的列表** - 提供手动刷新功能 + +## 注意事项 + +1. `actions` 数组中的每个按钮必须提供唯一的 `key` 值 +2. 搜索框支持受控和非受控两种模式,推荐使用受控模式以便更好地管理状态 +3. 筛选功能的 `content` 可以是任意 React 组件,常配合 `TreeFilterPanel` 使用 +4. 当筛选激活时(`isActive: true`),筛选按钮会显示为 primary 类型以提示用户 +5. 左侧操作按钮不宜过多,建议不超过 5 个,过多的操作可以通过下拉菜单组织 diff --git a/oms_web/docs/components/ListTable.md b/oms_web/docs/components/ListTable.md new file mode 100644 index 00000000..6903e5a4 --- /dev/null +++ b/oms_web/docs/components/ListTable.md @@ -0,0 +1,385 @@ +# ListTable 组件 + +## 组件说明 + +列表表格组件,基于 Ant Design Table 组件封装,提供统一的表格样式、行选择、分页、滚动和行点击等功能。 + +## 组件位置 + +``` +src/components/ListTable/ListTable.jsx +src/components/ListTable/ListTable.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| columns | Array | 是 | - | 表格列配置数组 | +| dataSource | Array | 是 | - | 表格数据源 | +| rowKey | string | 否 | 'id' | 行数据的唯一标识字段名 | +| selectedRowKeys | Array | 否 | [] | 选中行的 key 数组 | +| onSelectionChange | function(keys: Array) | 否 | - | 行选择变化回调 | +| pagination | Object\|false | 否 | 默认配置 | 分页配置,false 表示不分页 | +| scroll | Object | 否 | { x: 1200 } | 表格滚动配置 | +| onRowClick | function(record: Object) | 否 | - | 行点击回调 | +| selectedRow | Object | 否 | - | 当前选中的行数据对象 | +| loading | boolean | 否 | false | 表格加载状态 | +| className | string | 否 | '' | 自定义类名 | + +### ColumnConfig 列配置 + +继承自 Ant Design Table 的列配置,常用属性: + +| 属性名 | 类型 | 说明 | +|--------|------|------| +| title | string\|ReactNode | 列标题 | +| dataIndex | string | 数据字段名 | +| key | string | 列唯一标识 | +| width | number | 列宽度 | +| align | 'left'\|'center'\|'right' | 对齐方式 | +| fixed | 'left'\|'right' | 固定列 | +| render | function(value, record, index) | 自定义渲染函数 | + +### 默认分页配置 + +```javascript +{ + pageSize: 10, + showSizeChanger: true, + showQuickJumper: true, + showTotal: (total) => `共 ${total} 条`, +} +``` + +## 使用示例 + +### 基础用法 + +```jsx +import ListTable from '../components/ListTable/ListTable' + +function MyPage() { + const columns = [ + { + title: '序号', + dataIndex: 'id', + key: 'id', + width: 80, + align: 'center', + }, + { + title: '用户名', + dataIndex: 'userName', + key: 'userName', + width: 150, + }, + { + title: '姓名', + dataIndex: 'name', + key: 'name', + width: 120, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: (status) => ( + + {status === 'enabled' ? '启用' : '停用'} + + ), + }, + ] + + const dataSource = [ + { id: 1, userName: 'admin', name: '管理员', status: 'enabled' }, + { id: 2, userName: 'user', name: '张三', status: 'disabled' }, + ] + + return ( + + ) +} +``` + +### 带行选择 + +```jsx +import { useState } from 'react' +import ListTable from '../components/ListTable/ListTable' + +function UserListPage() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + + return ( +
+ {/* 显示选中的数量 */} +
已选择 {selectedRowKeys.length} 项
+ + +
+ ) +} +``` + +### 带行点击和高亮 + +```jsx +import { useState } from 'react' +import ListTable from '../components/ListTable/ListTable' + +function UserListPage() { + const [selectedUser, setSelectedUser] = useState(null) + + const handleRowClick = (record) => { + setSelectedUser(record) + // 打开详情抽屉等操作 + setShowDetailDrawer(true) + } + + return ( + + ) +} +``` + +### 自定义分页 + +```jsx + `总计 ${total} 条记录`, + pageSizeOptions: ['10', '20', '50', '100'], + }} +/> +``` + +### 禁用分页 + +```jsx + +``` + +### 带加载状态 + +```jsx +import { useState, useEffect } from 'react' + +function UserListPage() { + const [loading, setLoading] = useState(false) + const [dataSource, setDataSource] = useState([]) + + const fetchData = async () => { + setLoading(true) + try { + const data = await api.fetchUsers() + setDataSource(data) + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchData() + }, []) + + return ( + + ) +} +``` + +### 横向滚动(列较多时) + +```jsx + +``` + +### 固定列 + +```jsx +const columns = [ + { + title: '序号', + dataIndex: 'id', + key: 'id', + width: 80, + fixed: 'left', // 固定在左侧 + }, + // ... 其他列 + { + title: '操作', + key: 'action', + width: 200, + fixed: 'right', // 固定在右侧 + render: (_, record) => ( + + + + + ), + }, +] + + +``` + +### 完整示例 + +```jsx +import { useState } from 'react' +import ListTable from '../components/ListTable/ListTable' +import { Tag, Button, Space } from 'antd' +import { EditOutlined, DeleteOutlined } from '@ant-design/icons' + +function UserListPage() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [selectedUser, setSelectedUser] = useState(null) + const [showDetailDrawer, setShowDetailDrawer] = useState(false) + + const columns = [ + { + title: '序号', + dataIndex: 'id', + key: 'id', + width: 80, + align: 'center', + }, + { + title: '用户名', + dataIndex: 'userName', + key: 'userName', + width: 150, + }, + { + title: '姓名', + dataIndex: 'name', + key: 'name', + width: 120, + }, + { + title: '用户分组', + dataIndex: 'group', + key: 'group', + width: 150, + render: (text) => {text}, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + align: 'center', + render: (status) => ( + + {status === 'enabled' ? '启用' : '停用'} + + ), + }, + { + title: '操作', + key: 'action', + width: 180, + fixed: 'right', + render: (_, record) => ( + e.stopPropagation()}> + + + + ), + }, + ] + + const handleRowClick = (record) => { + setSelectedUser(record) + setShowDetailDrawer(true) + } + + return ( + + ) +} +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.list-table-container` - 表格容器 +- `.row-selected` - 选中行的类名 + +## 使用场景 + +1. **用户列表** - 显示和管理用户数据 +2. **设备列表** - 显示和管理设备信息 +3. **订单列表** - 显示订单数据 +4. **任何需要表格展示的数据列表** + +## 注意事项 + +1. `columns` 配置中的 `key` 必须唯一 +2. `dataSource` 中的每条数据必须有 `rowKey` 指定的唯一标识字段(默认为 `id`) +3. 操作列中的点击事件需要使用 `e.stopPropagation()` 阻止事件冒泡,避免触发行点击 +4. 当列数较多时,建议设置合适的 `scroll.x` 值并固定首尾列 +5. `selectedRow` 用于高亮显示,`selectedRowKeys` 用于多选 +6. 分页的 `total` 值会自动根据 `dataSource.length` 计算 +7. 使用 `render` 函数时,要注意性能,避免在渲染函数中进行复杂计算 diff --git a/oms_web/docs/components/PageTitleBar.md b/oms_web/docs/components/PageTitleBar.md new file mode 100644 index 00000000..9e2585d7 --- /dev/null +++ b/oms_web/docs/components/PageTitleBar.md @@ -0,0 +1,131 @@ +# PageTitleBar 组件 + +## 组件说明 + +页面标题栏组件,用于显示页面的标题、描述信息、操作按钮和可选的展开/收起控制按钮。 + +## 组件位置 + +``` +src/components/PageTitleBar/PageTitleBar.jsx +src/components/PageTitleBar/PageTitleBar.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| title | string | 是 | - | 页面标题文本 | +| badge | ReactNode | 否 | - | 标题右侧的徽章内容(如状态标签等) | +| description | string | 否 | - | 页面描述文本,显示在标题下方 | +| actions | ReactNode | 否 | - | 右侧操作按钮区域内容 | +| showToggle | boolean | 否 | false | 是否显示展开/收起按钮 | +| onToggle | function(expanded: boolean) | 否 | - | 展开/收起状态变化时的回调函数 | +| defaultExpanded | boolean | 否 | true | 默认展开状态 | + +## 使用示例 + +### 基础用法 + +```jsx +import PageTitleBar from '../components/PageTitleBar/PageTitleBar' + +function MyPage() { + return ( +
+ + {/* 页面内容 */} +
+ ) +} +``` + +### 带徽章的标题 + +```jsx +import { Tag } from 'antd' + +在线} + description="查看主机的详细信息和运行状态" +/> +``` + +### 带展开/收起功能 + +```jsx +import { useState } from 'react' +import PageTitleBar from '../components/PageTitleBar/PageTitleBar' + +function MyPage() { + const [showStatsPanel, setShowStatsPanel] = useState(true) + + return ( +
+ setShowStatsPanel(expanded)} + /> + + {/* 可展开/收起的内容区域 */} + {showStatsPanel && ( +
+ {/* 统计面板内容 */} +
+ )} +
+ ) +} +``` + +### 带操作按钮 + +```jsx +import { Button } from 'antd' +import { PlusOutlined } from '@ant-design/icons' + +}> + 新增用户 + + } +/> +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.page-title-bar` - 组件根容器 +- `.title-bar-content` - 内容容器 +- `.title-bar-left` - 左侧内容区域 +- `.title-bar-right` - 右侧内容区域 +- `.title-group` - 标题和徽章组合 +- `.page-title` - 标题文本 +- `.title-badge` - 徽章容器 +- `.page-description` - 描述文本 +- `.title-actions` - 操作按钮区域 +- `.toggle-button` - 展开/收起按钮 + +## 使用场景 + +1. **列表页面** - 显示列表页面的标题和描述 +2. **详情页面** - 显示详情页的标题和状态标签 +3. **带统计面板的页面** - 配合展开/收起功能控制统计信息的显示 +4. **需要快捷操作的页面** - 通过 actions 参数在标题栏添加常用操作按钮 + +## 注意事项 + +1. `title` 参数为必填项,建议简洁明了 +2. 当使用 `showToggle` 时,建议同时提供 `onToggle` 回调以响应状态变化 +3. `badge` 参数支持任何 React 节点,常用的如 Ant Design 的 Tag、Badge 组件 +4. `actions` 区域不建议放置过多按钮,以保持界面简洁 diff --git a/oms_web/docs/components/README.md b/oms_web/docs/components/README.md new file mode 100644 index 00000000..9854f056 --- /dev/null +++ b/oms_web/docs/components/README.md @@ -0,0 +1,149 @@ +# 组件文档目录 + +## 概述 + +本目录包含 Nex Design 系统所有主要组件的详细文档,包括组件说明、参数配置、使用示例等。 + +## 组件列表 + +### 页面布局组件 + +1. **[PageTitleBar](./PageTitleBar.md)** - 页面标题栏组件 + - 显示页面标题、描述和操作按钮 + - 支持展开/收起功能 + - 适用于所有页面的顶部区域 + +### 列表相关组件 + +2. **[ListActionBar](./ListActionBar.md)** - 列表操作栏组件 + - 提供操作按钮、搜索、筛选功能 + - 适用于列表页面的顶部操作区 + +3. **[TreeFilterPanel](./TreeFilterPanel.md)** - 树形筛选面板组件 + - 树形结构的数据筛选 + - 支持搜索和多级展开 + - 配合 ListActionBar 使用 + +4. **[ListTable](./ListTable.md)** - 列表表格组件 + - 统一的表格样式和交互 + - 支持行选择、分页、排序 + - 适用于所有列表页面 + +### 详情展示组件 + +5. **[DetailDrawer](./DetailDrawer.md)** - 详情抽屉组件 + - 从右侧滑出的详情面板 + - 支持标签页和操作按钮 + - 固定头部,内容可滚动 + +6. **[InfoPanel](./InfoPanel.md)** - 信息展示面板组件 + - 网格布局展示结构化数据 + - 支持自定义字段渲染 + - 配合 DetailDrawer 使用 + +### 交互反馈组件 + +7. **[ConfirmDialog](./ConfirmDialog.md)** - 确认对话框组件 + - 提供统一的确认对话框样式 + - 支持删除、警告、通用确认等场景 + - 支持异步操作 + +8. **[Toast](./Toast.md)** - 通知反馈组件 + - 操作完成后的提示信息 + - 支持成功、错误、警告、信息四种类型 + - 从右上角滑出,自动消失 + +## 组件关系图 + +``` +页面结构层次: + +┌─────────────────────────────────────────┐ +│ PageTitleBar (页面标题栏) │ +├─────────────────────────────────────────┤ +│ ListActionBar (操作栏) │ +│ ├─ 操作按钮 │ +│ ├─ 搜索框 │ +│ └─ TreeFilterPanel (筛选面板) │ +├─────────────────────────────────────────┤ +│ ListTable (数据表格) │ +│ └─ 点击行 → DetailDrawer │ +├─────────────────────────────────────────┤ +│ DetailDrawer (详情抽屉) │ +│ ├─ InfoPanel (基本信息) │ +│ └─ Tabs (关联数据标签页) │ +└─────────────────────────────────────────┘ + +交互反馈: + 操作 → ConfirmDialog (确认) → Toast (结果反馈) +``` + +## 组件组合示例 + +### 标准列表页面 + +```jsx + + + + }} +/> + + + + + + +``` + +### 删除操作流程 + +```jsx +// 1. 点击删除按钮 + + +// 2. 显示确认对话框 +ConfirmDialog.delete({ + itemName: record.name, + onOk: async () => { + // 3. 执行删除 + await api.delete(record.id) + // 4. 显示结果反馈 + Toast.success('删除成功') + } +}) +``` + +## 使用指南 + +### 开始使用 + +1. 查看对应组件的详细文档 +2. 了解组件的参数配置 +3. 参考示例代码 +4. 根据实际需求调整参数 + +### 设计原则 + +- **一致性** - 所有组件使用统一的设计语言 +- **可复用** - 组件高度封装,易于复用 +- **可配置** - 提供丰富的配置选项 +- **易用性** - API 设计简洁直观 + +### 技术栈 + +- React 18 +- Ant Design 5.x +- CSS Modules + +## 更新记录 + +- 2025-11-04: 初始版本,包含 8 个核心组件文档 diff --git a/oms_web/docs/components/Toast.md b/oms_web/docs/components/Toast.md new file mode 100644 index 00000000..1e295b27 --- /dev/null +++ b/oms_web/docs/components/Toast.md @@ -0,0 +1,398 @@ +# Toast 组件 + +## 组件说明 + +通知反馈组件,基于 Ant Design notification 封装,用于操作完成后向用户展示反馈信息。通知从右上角滑出,默认 3 秒后自动消失,最多同时显示 3 条通知。 + +## 组件位置 + +``` +src/components/Toast/Toast.jsx +``` + +## 全局配置 + +```javascript +notification.config({ + placement: 'topRight', // 通知位置:右上角 + top: 24, // 距离顶部 24px + duration: 3, // 默认显示 3 秒 + maxCount: 3, // 最多同时显示 3 条 +}) +``` + +## API 方法 + +组件以静态方法的形式提供,无需实例化,直接调用即可。 + +### Toast.success() + +显示成功通知(绿色图标)。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| message | string | 是 | - | 主要消息内容 | +| description | string | 否 | '' | 详细描述 | +| duration | number | 否 | 3 | 显示时长(秒),0 表示不自动关闭 | + +#### 使用示例 + +```jsx +import Toast from '../components/Toast/Toast' + +// 简单成功提示 +Toast.success('操作成功') + +// 带详细描述 +Toast.success('删除成功', '用户 "admin" 已成功删除') + +// 自定义显示时长(5秒) +Toast.success('保存成功', '您的设置已保存', 5) + +// 不自动关闭 +Toast.success('操作成功', '请注意后续操作', 0) +``` + +### Toast.error() + +显示错误通知(红色图标)。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| message | string | 是 | - | 主要消息内容 | +| description | string | 否 | '' | 详细描述 | +| duration | number | 否 | 3 | 显示时长(秒) | + +#### 使用示例 + +```jsx +// 简单错误提示 +Toast.error('操作失败') + +// 带错误详情 +Toast.error('删除失败', '该用户下还有关联数据,无法删除') + +// API 错误处理 +try { + await api.deleteUser(userId) + Toast.success('删除成功') +} catch (error) { + Toast.error('删除失败', error.message) +} +``` + +### Toast.warning() + +显示警告通知(橙色图标)。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| message | string | 是 | - | 主要消息内容 | +| description | string | 否 | '' | 详细描述 | +| duration | number | 否 | 3 | 显示时长(秒) | + +#### 使用示例 + +```jsx +// 警告提示 +Toast.warning('操作警告', '此操作可能影响系统稳定性') + +// 权限警告 +Toast.warning('权限不足', '您没有执行此操作的权限') + +// 数据警告 +Toast.warning('数据异常', '检测到部分数据可能不完整') +``` + +### Toast.info() + +显示信息通知(蓝色图标)。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| message | string | 是 | - | 主要消息内容 | +| description | string | 否 | '' | 详细描述 | +| duration | number | 否 | 3 | 显示时长(秒) | + +#### 使用示例 + +```jsx +// 信息提示 +Toast.info('系统提示', '系统将在5分钟后进行维护') + +// 操作提示 +Toast.info('导入中', '正在导入数据,请稍候...') + +// 功能提示 +Toast.info('新功能上线', '我们上线了新的数据导出功能') +``` + +### Toast.custom() + +显示自定义通知,支持所有 Ant Design notification 配置。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| config | Object | 是 | 完整的 notification 配置对象 | + +#### 使用示例 + +```jsx +import { Button } from 'antd' + +// 带操作按钮的通知 +Toast.custom({ + message: '新版本可用', + description: '发现新版本 v2.0.0,是否立即更新?', + duration: 0, + btn: ( + + ), + key: 'update-key', +}) + +// 自定义图标和样式 +Toast.custom({ + message: '自定义通知', + description: '这是一个自定义样式的通知', + icon: , + style: { + background: '#fffbe6', + }, +}) +``` + +## 完整使用示例 + +### 配合 ConfirmDialog 使用 + +```jsx +import ConfirmDialog from '../components/ConfirmDialog/ConfirmDialog' +import Toast from '../components/Toast/Toast' + +const handleDeleteUser = (record) => { + ConfirmDialog.delete({ + itemName: `用户名:${record.userName}`, + itemInfo: `姓名:${record.name}`, + onOk() { + return new Promise((resolve) => { + setTimeout(() => { + const newUsers = filteredUsers.filter((u) => u.id !== record.id) + setFilteredUsers(newUsers) + resolve() + Toast.success('删除成功', `用户 "${record.userName}" 已成功删除`) + }, 1000) + }) + }, + }) +} +``` + +### 批量操作反馈 + +```jsx +const handleBatchDelete = () => { + ConfirmDialog.batchDelete({ + count: selectedRowKeys.length, + items: selectedUsers, + onOk() { + return new Promise((resolve) => { + setTimeout(() => { + const count = selectedRowKeys.length + const newUsers = filteredUsers.filter((u) => !selectedRowKeys.includes(u.id)) + setFilteredUsers(newUsers) + setSelectedRowKeys([]) + resolve() + Toast.success('批量删除成功', `已成功删除 ${count} 个用户`) + }, 1000) + }) + }, + }) +} +``` + +### 异步操作反馈 + +```jsx +const handleSaveUser = async (userData) => { + try { + setLoading(true) + await api.updateUser(userData) + setShowEditDrawer(false) + Toast.success('保存成功', '用户信息已更新') + fetchUsers() // 重新加载列表 + } catch (error) { + Toast.error('保存失败', error.message) + } finally { + setLoading(false) + } +} +``` + +### 多步骤操作反馈 + +```jsx +const handleImportData = async () => { + try { + Toast.info('导入开始', '正在验证数据格式...') + + await api.validateData() + Toast.info('验证通过', '正在导入数据...') + + const result = await api.importData() + Toast.success('导入完成', `成功导入 ${result.count} 条数据`) + + } catch (error) { + if (error.type === 'validation') { + Toast.warning('验证失败', error.message) + } else { + Toast.error('导入失败', error.message) + } + } +} +``` + +### 表单提交反馈 + +```jsx +const handleSubmit = async (values) => { + try { + await api.createUser(values) + Toast.success('创建成功', `用户 "${values.userName}" 已成功创建`) + setShowEditDrawer(false) + fetchUsers() + } catch (error) { + if (error.code === 'DUPLICATE') { + Toast.warning('用户名已存在', '请使用其他用户名') + } else { + Toast.error('创建失败', error.message) + } + } +} +``` + +### 带按钮的持久通知 + +```jsx +const showUpdateNotification = () => { + const key = `update-${Date.now()}` + + Toast.custom({ + message: '发现新版本', + description: '系统发现新版本 v2.0.0,建议立即更新', + duration: 0, // 不自动关闭 + key, + btn: ( + + + + + ), + }) +} +``` + +## 使用场景 + +1. **操作成功反馈** - 增删改操作成功后的提示 +2. **操作失败反馈** - 操作失败时显示错误信息 +3. **警告提示** - 权限不足、数据异常等警告 +4. **信息通知** - 系统公告、功能提示等 +5. **进度通知** - 多步骤操作的进度反馈 +6. **版本更新通知** - 带操作按钮的持久通知 + +## 最佳实践 + +### 消息文案 + +```jsx +// 好的做法:简洁明了 +Toast.success('删除成功', '用户已删除') + +// 避免:冗余重复 +Toast.success('删除成功', '删除用户成功') // ❌ +``` + +### 显示时长 + +```jsx +// 简单提示:3秒(默认) +Toast.success('保存成功') + +// 重要信息:5秒 +Toast.warning('权限不足', '请联系管理员', 5) + +// 需要用户操作:不自动关闭 +Toast.custom({ + message: '需要您的确认', + description: '...', + duration: 0, + btn: , +}) +``` + +### 类型选择 + +```jsx +// 成功:操作完成 +Toast.success('保存成功') + +// 错误:操作失败、异常 +Toast.error('保存失败', error.message) + +// 警告:权限、数据问题 +Toast.warning('权限不足') + +// 信息:提示、公告 +Toast.info('系统维护通知') +``` + +## 样式特性 + +- **圆角设计**:8px 圆角,现代化视觉效果 +- **阴影效果**:0 4px 12px rgba(0, 0, 0, 0.15) +- **彩色图标**: + - 成功:绿色 (#52c41a) + - 错误:红色 (#ff4d4f) + - 警告:橙色 (#faad14) + - 信息:蓝色 (#1677ff) + +## 注意事项 + +1. 通知从右上角滑出,默认 3 秒后自动消失 +2. 最多同时显示 3 条通知,超出的会排队等待 +3. `duration: 0` 表示通知不自动关闭,需手动关闭或调用 API 关闭 +4. 通知内容不宜过长,建议 message 不超过 20 字,description 不超过 50 字 +5. 频繁操作时避免连续显示大量通知,可以考虑合并提示 +6. 配合 ConfirmDialog 使用时,在 `onOk` 回调中显示操作结果 +7. 错误通知建议显示具体错误信息,帮助用户排查问题 diff --git a/oms_web/docs/components/TreeFilterPanel.md b/oms_web/docs/components/TreeFilterPanel.md new file mode 100644 index 00000000..676e01ae --- /dev/null +++ b/oms_web/docs/components/TreeFilterPanel.md @@ -0,0 +1,297 @@ +# TreeFilterPanel 组件 + +## 组件说明 + +树形筛选面板组件,用于在弹出层中展示树形结构的筛选选项,支持树形选择、搜索、确认和清除操作。常配合 ListActionBar 组件使用。 + +## 组件位置 + +``` +src/components/TreeFilterPanel/TreeFilterPanel.jsx +src/components/TreeFilterPanel/TreeFilterPanel.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| treeData | Array | 是 | - | 树形数据数组 | +| selectedKey | string | 否 | - | 当前选中的节点 key(已确认的选择) | +| tempSelectedKey | string | 否 | - | 临时选中的节点 key(未确认) | +| treeTitle | string | 否 | '分组筛选' | 树形选择器的标题 | +| onSelect | function(key: string) | 否 | - | 节点选择变化回调 | +| onConfirm | function | 否 | - | 确认筛选按钮回调 | +| onClear | function | 否 | - | 清除筛选按钮回调 | +| placeholder | string | 否 | '请选择分组进行筛选' | 未选择时的占位提示文本 | + +### TreeNode 数据结构 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 节点唯一标识 | +| title | string | 是 | 节点显示文本 | +| children | Array | 否 | 子节点数组 | + +## 使用示例 + +### 基础用法 + +```jsx +import { useState } from 'react' +import TreeFilterPanel from '../components/TreeFilterPanel/TreeFilterPanel' + +function MyPage() { + const [selectedGroup, setSelectedGroup] = useState(null) + const [tempSelectedGroup, setTempSelectedGroup] = useState(null) + + const treeData = [ + { + key: '1', + title: '全部用户', + children: [ + { + key: '1-1', + title: '管理员组', + children: [ + { key: '1-1-1', title: '系统管理员' }, + { key: '1-1-2', title: '安全管理员' }, + ], + }, + { + key: '1-2', + title: '部门用户', + children: [ + { key: '1-2-1', title: '研发部' }, + { key: '1-2-2', title: '产品部' }, + ], + }, + ], + }, + ] + + const handleConfirm = () => { + setSelectedGroup(tempSelectedGroup) + // 执行筛选逻辑 + filterData(tempSelectedGroup) + } + + const handleClear = () => { + setTempSelectedGroup(null) + setSelectedGroup(null) + // 清除筛选 + filterData(null) + } + + return ( + + ) +} +``` + +### 配合 ListActionBar 使用 + +```jsx +import { useState } from 'react' +import ListActionBar from '../components/ListActionBar/ListActionBar' +import TreeFilterPanel from '../components/TreeFilterPanel/TreeFilterPanel' + +function UserListPage() { + const [showFilterPopover, setShowFilterPopover] = useState(false) + const [selectedGroup, setSelectedGroup] = useState(null) + const [tempSelectedGroup, setTempSelectedGroup] = useState(null) + const [selectedGroupName, setSelectedGroupName] = useState('') + + // 将原始数据转换为树形数据格式 + const convertTreeData = (nodes) => { + return nodes.map((node) => ({ + title: node.name, + key: node.id, + children: node.children ? convertTreeData(node.children) : undefined, + })) + } + + const treeData = convertTreeData(userData.userGroups) + + const handleConfirmFilter = () => { + setSelectedGroup(tempSelectedGroup) + + // 查找节点名称 + const findGroupName = (nodes, id) => { + for (const node of nodes) { + if (node.id === id) return node.name + if (node.children) { + const found = findGroupName(node.children, id) + if (found) return found + } + } + return null + } + + const groupName = tempSelectedGroup + ? findGroupName(userData.userGroups, tempSelectedGroup) + : '' + setSelectedGroupName(groupName) + + // 执行筛选 + filterUsers(searchKeyword, tempSelectedGroup) + setShowFilterPopover(false) + } + + const handleClearFilter = () => { + setTempSelectedGroup(null) + setSelectedGroup(null) + setSelectedGroupName('') + filterUsers(searchKeyword, null) + setShowFilterPopover(false) + } + + return ( + + ), + title: '高级筛选', + visible: showFilterPopover, + onVisibleChange: (visible) => { + setShowFilterPopover(visible) + if (visible) { + // 打开时同步临时选择 + setTempSelectedGroup(selectedGroup) + } + }, + selectedLabel: selectedGroupName, + isActive: !!selectedGroup, + }} + /> + ) +} +``` + +### 从 JSON 数据转换树形结构 + +```jsx +// userData.json 中的数据格式 +{ + "userGroups": [ + { + "id": "1", + "name": "全部用户", + "children": [ + { + "id": "1-1", + "name": "管理员组", + "children": [ + { "id": "1-1-1", "name": "系统管理员" }, + { "id": "1-1-2", "name": "安全管理员" } + ] + } + ] + } + ] +} + +// 转换函数 +const convertTreeData = (nodes) => { + return nodes.map((node) => ({ + title: node.name, // 映射 name 到 title + key: node.id, // 映射 id 到 key + children: node.children ? convertTreeData(node.children) : undefined, + })) +} + +const treeData = convertTreeData(userData.userGroups) +``` + +## 组件特性 + +### 自动展开所有节点 + +组件会在数据加载时自动展开所有树节点,方便用户查看完整的层级结构。 + +```javascript +// 组件内部实现 +useEffect(() => { + if (treeData && treeData.length > 0) { + setExpandedKeys(getAllKeys(treeData)) + } +}, [treeData]) +``` + +### 显示当前选择 + +组件顶部会显示当前选中的节点,并提供关闭按钮快速清除选择: + +```jsx +{tempSelectedKey ? ( +
+ 已选择分组: + onSelect?.(null)}> + {findNodeName(treeData, tempSelectedKey)} + +
+) : ( +
+ {placeholder} +
+)} +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.tree-filter-panel` - 组件根容器 +- `.tree-filter-selected` - 已选择区域 +- `.tree-filter-tag` - 选中标签容器 +- `.tree-filter-label` - 标签文本 +- `.tree-filter-placeholder` - 占位提示 +- `.tree-filter-container` - 树形选择器容器 +- `.tree-filter-header` - 树形选择器标题 +- `.tree-filter-actions` - 操作按钮区域 + +## 使用场景 + +1. **用户分组筛选** - 按用户组织架构筛选用户列表 +2. **终端分组筛选** - 按终端分组筛选设备列表 +3. **部门筛选** - 按部门层级筛选数据 +4. **分类筛选** - 任何需要树形层级筛选的场景 + +## 注意事项 + +1. `treeData` 必须符合 Ant Design Tree 组件的数据格式要求 +2. 需要区分 `selectedKey`(已确认)和 `tempSelectedKey`(临时选择),点击确认后才更新 `selectedKey` +3. 建议在打开筛选面板时,将 `selectedKey` 同步到 `tempSelectedKey`,以便用户看到当前的选择状态 +4. 点击清除按钮会同时清除临时选择和已确认选择 +5. 组件会自动展开所有节点,如果数据量很大,可能需要考虑性能优化 +6. 树节点的 `key` 必须唯一,通常使用 ID 字段 diff --git a/oms_web/docs/pages/main-layout.md b/oms_web/docs/pages/main-layout.md new file mode 100644 index 00000000..15607923 --- /dev/null +++ b/oms_web/docs/pages/main-layout.md @@ -0,0 +1,495 @@ +# 主框架页面设计规范 + +> Nex Design 主框架布局设计文档 + +## 概述 + +主框架页面是应用的基础布局结构,包含侧边菜单栏、顶部导航栏和内容区域。本文档详细说明了主框架的设计规范、交互逻辑和实现要点。 + +--- + +## 页面结构 + +``` +┌─────────────────────────────────────────────────────┐ +│ 顶部导航栏 (64px) │ +├──────────┬──────────────────────────────────────────┤ +│ │ │ +│ 侧边栏 │ 内容区域 │ +│ (200px) │ (可向下滚动) │ +│ │ │ +│ │ │ +└──────────┴──────────────────────────────────────────┘ +``` + +--- + +## 1. 侧边栏 (Sider) + +### 1.1 基础规范 + +| 属性 | 展开状态 | 收起状态 | +|------|---------|---------| +| 宽度 | 200px | 64px | +| 背景色 | #001529 (深色) | #001529 | +| 位置 | 固定左侧 | 固定左侧 | +| 层级 | z-index: 10 | z-index: 10 | + +### 1.2 Logo 区域 + +**设计规范**: +- 高度:64px +- 背景:rgba(255, 255, 255, 0.05) +- 底部边框:1px solid rgba(255, 255, 255, 0.05) +- 内边距:12px 16px +- 对齐:居中 + +**展开状态**: +- 显示完整 Logo(logo-full.png) +- Logo 高度:40px,宽度自适应 + +**收起状态**: +- 显示方形 Logo(logo-small.png) +- Logo 尺寸:40px × 40px +- 圆角:8px + +### 1.3 菜单系统 + +#### 菜单层级 + +支持**两级菜单**结构: +- **一级菜单**:带图标,可展开/收起 +- **二级菜单**:文字列表,可带徽章标识 + +#### 菜单数据格式 + +```json +[ + { + "key": "overview", + "label": "概览", + "icon": "DashboardOutlined", + "path": "/overview" + }, + { + "key": "certificate", + "label": "证书管理", + "icon": "SafetyCertificateOutlined", + "children": [ + { + "key": "ssl-cert", + "label": "SSL证书管理", + "path": "/certificate/ssl" + } + ] + } +] +``` + +#### 菜单状态 + +**正常状态**: +- 背景色:透明 +- 文字颜色:rgba(255, 255, 255, 0.65) + +**悬停状态**: +- 背景色:rgba(184, 23, 141, 0.2) +- 文字颜色:#fff + +**选中状态**: +- 背景色:#b8178d (品牌主色) +- 文字颜色:#fff +- 左侧边框:3px solid #b8178d + +**展开状态**: +- 二级菜单背景:rgba(0, 0, 0, 0.15) +- 二级菜单内边距:左侧 48px + +#### 徽章系统 + +支持在菜单项上显示徽章标识: + +- **HOT 徽章**: + - 背景色:#ff4d4f (红色) + - 文字:白色 + - 尺寸:18px 高度,圆角 9px + +- **NEW 徽章**: + - 背景色:#52c41a (绿色) + - 文字:白色 + - 尺寸:18px 高度,圆角 9px + +**注意**:收起状态下徽章自动隐藏。 + +#### 收起状态行为 + +- 仅显示一级菜单图标 +- 鼠标悬停时不展开子菜单 +- 点击跳转到该分类的默认页面 + +### 1.4 滚动条样式 + +```css +宽度:6px +轨道:透明 +滑块:rgba(255, 255, 255, 0.2) +滑块悬停:rgba(255, 255, 255, 0.3) +圆角:3px +``` + +--- + +## 2. 顶部导航栏 (Header) + +### 2.1 基础规范 + +| 属性 | 值 | +|------|---| +| 高度 | 64px | +| 背景色 | #ffffff | +| 阴影 | 0 1px 4px rgba(0, 21, 41, 0.08) | +| 位置 | sticky,top: 0 | +| 层级 | z-index: 9 | +| 内边距 | 0 24px | + +### 2.2 左侧区域 + +**折叠按钮**: +- 图标尺寸:18px +- 颜色:rgba(0, 0, 0, 0.65) +- 悬停色:#b8178d (品牌主色) +- 悬停背景:rgba(0, 0, 0, 0.03) +- 内边距:8px +- 圆角:4px +- 功能:切换侧边栏展开/收起状态 + +**工作台标识**: +- 字号:14px +- 字重:500 (Medium) +- 颜色:rgba(0, 0, 0, 0.88) +- 左侧间距:16px + +### 2.3 右侧区域 + +从左到右依次包含: + +1. **搜索框** + - 宽度:200px + - 高度:32px + - 圆角:16px (胶囊形) + - 占位文字:"搜索..." + - 前缀图标:SearchOutlined + +2. **帮助图标** + - 图标:QuestionCircleOutlined + - 尺寸:16px + - 颜色:rgba(0, 0, 0, 0.65) + - 悬停色:#b8178d + +3. **功能链接**(ICP 备案、企业、支持) + - 字号:14px + - 颜色:rgba(0, 0, 0, 0.65) + - 悬停色:#b8178d + - 悬停背景:rgba(0, 0, 0, 0.03) + - 内边距:4px 8px + - 圆角:4px + +4. **工单图标** + - 图标:SettingOutlined + - 样式同帮助图标 + +5. **消息中心** + - 图标:BellOutlined + - 带徽章:Badge count={5} + - 徽章位置:右上角,offset: [-3, 3] + - 徽章尺寸:small + +6. **用户信息** + - 头像:32px × 32px 圆形 + - 用户名:14px,Medium 字重 + - 颜色:rgba(0, 0, 0, 0.88) + - 整体内边距:4px 8px + - 悬停背景:rgba(0, 0, 0, 0.03) + - 点击显示下拉菜单 + +**间距**:各元素之间间距 16-20px + +--- + +## 3. 内容区域 (Content) + +### 3.1 基础规范 + +| 属性 | 值 | +|------|---| +| 背景色 | #f5f5f5 | +| 内边距 | 24px | +| 高度 | calc(100vh - 64px) | +| 滚动 | overflow-y: auto | + +### 3.2 内容容器 + +- 最大宽度:根据业务需求,建议 1200-1600px +- 内边距:24px +- 背景色:根据内容类型,卡片为 #fff + +### 3.3 滚动行为 + +- 仅内容区域可滚动 +- 顶部导航栏和侧边栏保持固定 +- 滚动条样式与全局一致 + +--- + +## 4. 响应式适配 + +### 4.1 断点规则 + +| 断点 | 行为 | +|------|------| +| < 768px | 侧边栏默认收起,通过遮罩层展开 | +| ≥ 768px | 侧边栏可正常展开/收起 | +| ≥ 1200px | 建议默认展开侧边栏 | + +### 4.2 移动端优化 + +- 侧边栏改为抽屉模式 (Drawer) +- 顶部搜索框宽度减小或移至下拉菜单 +- 功能链接收起至"更多"菜单 +- 用户信息简化显示 + +--- + +## 5. 主题配色 + +### 5.1 品牌主色 + +基于 NEX Logo 的紫红色: + +```css +--primary-color: #b8178d; +--primary-hover: #9c1477; +--primary-active: #801161; +``` + +### 5.2 辅助色 + +- **蓝色**(信息色):#1677ff +- **绿色**(成功):#52c41a +- **红色**(错误/警告):#ff4d4f +- **橙色**(警告):#faad14 + +### 5.3 中性色 + +```css +--text-primary: rgba(0, 0, 0, 0.88); +--text-secondary: rgba(0, 0, 0, 0.65); +--text-tertiary: rgba(0, 0, 0, 0.45); +--bg-primary: #ffffff; +--bg-secondary: #fafafa; +--bg-tertiary: #f5f5f5; +--border-color: #d9d9d9; +``` + +--- + +## 6. 交互规范 + +### 6.1 侧边栏折叠 + +**触发方式**: +- 点击顶部折叠按钮 +- 可选:在设置中保存用户偏好 + +**动画**: +- 过渡时间:200ms +- 缓动函数:ease-in-out +- 影响元素:侧边栏宽度、Logo、菜单文字 + +**状态保持**: +- 使用 localStorage 保存用户折叠状态 +- 页面刷新后保持用户选择 + +### 6.2 菜单导航 + +**展开逻辑**: +- 点击一级菜单展开/收起二级菜单 +- 默认展开当前路由所在的菜单组 +- 支持手风琴模式(可选) + +**高亮逻辑**: +- 根据当前路由自动高亮对应菜单项 +- 二级菜单选中时,一级菜单也显示激活状态 + +**跳转方式**: +- 使用 React Router 进行路由跳转 +- 支持浏览器前进/后退 + +### 6.3 用户下拉菜单 + +**菜单项**: +- 个人中心 +- 账户设置 +- 分割线 +- 退出登录 + +**交互**: +- 点击用户信息区域展开 +- 点击菜单项执行对应操作 +- 点击外部区域关闭 + +--- + +## 7. 代码实现 + +### 7.1 组件结构 + +``` +MainLayout/ +├── MainLayout.jsx # 主布局组件 +├── MainLayout.css # 布局样式 +├── AppSider.jsx # 侧边栏组件 +├── AppSider.css # 侧边栏样式 +├── AppHeader.jsx # 顶部栏组件 +├── AppHeader.css # 顶部栏样式 +└── index.js # 导出文件 +``` + +### 7.2 菜单数据配置 + +菜单数据独立维护在 `src/constants/menuData.json`,便于更新和管理。 + +### 7.3 关键技术点 + +1. **状态管理**: + - collapsed 状态通过 props 传递 + - 菜单展开状态 (openKeys) 在 AppSider 内部管理 + +2. **路由集成**: + - 使用 useNavigate 进行路由跳转 + - 使用 useLocation 获取当前路由 + +3. **图标映射**: + - 通过 iconMap 对象将字符串转换为图标组件 + +4. **主题定制**: + - 在 src/main.jsx 中配置 Ant Design 主题 + - 使用 ConfigProvider 包裹应用 + +--- + +## 8. 示例页面 + +### 8.1 概览页 (Overview) + +作为主框架的示例页面,展示了: +- 统计卡片布局 +- 图表展示 +- 数据可视化 +- 响应式栅格系统 + +详细设计见:[概览页设计文档](./overview.md) + +--- + +## 9. 可访问性 + +### 9.1 键盘导航 + +- 支持 Tab 键在可交互元素间切换 +- 支持 Enter 键激活菜单项 +- 支持方向键在菜单间导航 + +### 9.2 语义化标签 + +- 使用 nav 标签包裹导航菜单 +- 使用 header 标签包裹顶部栏 +- 使用 main 标签包裹主内容区 + +### 9.3 对比度 + +- 所有文本与背景对比度 ≥ 4.5:1 +- 图标与背景对比度 ≥ 3:1 + +--- + +## 10. 性能优化 + +### 10.1 懒加载 + +- 页面组件使用 React.lazy 懒加载 +- 减少首屏加载时间 + +### 10.2 防抖优化 + +- 搜索框输入使用防抖处理 +- 窗口大小变化使用节流处理 + +### 10.3 虚拟滚动 + +- 菜单项较多时考虑虚拟滚动 +- 长列表使用虚拟化技术 + +--- + +## 11. 开发指南 + +### 11.1 添加新菜单 + +1. 编辑 `src/constants/menuData.json` +2. 添加菜单项配置 +3. 如需新图标,在 `AppSider.jsx` 的 iconMap 中添加映射 +4. 创建对应的页面组件 +5. 在 `App.jsx` 中添加路由 + +### 11.2 自定义主题 + +1. 编辑 `src/main.jsx` 中的 theme 配置 +2. 修改 `tailwind.config.js` 中的颜色系统 +3. 更新 `src/styles/globals.css` 中的 CSS 变量 + +### 11.3 扩展功能 + +- 添加面包屑导航 +- 添加页签 (Tabs) 功能 +- 添加全局设置抽屉 +- 添加主题切换(亮色/暗色) + +--- + +## 12. 常见问题 + +### Q1: 如何修改侧边栏默认展开状态? + +在 `MainLayout.jsx` 中修改 `collapsed` 的初始值: + +```jsx +const [collapsed, setCollapsed] = useState(false) // false 为展开 +``` + +### Q2: 如何添加三级菜单? + +当前设计仅支持两级菜单。如需三级菜单,需要: +1. 修改 menuData.json 数据结构 +2. 修改 AppSider.jsx 中的 getMenuItems 函数 +3. 考虑 UI 空间和用户体验 + +### Q3: 如何实现菜单权限控制? + +建议: +1. 在菜单数据中添加 `roles` 或 `permissions` 字段 +2. 在渲染菜单前根据用户权限过滤 +3. 在路由层面也要做权限校验 + +--- + +## 版本记录 + +| 版本 | 日期 | 说明 | +|------|------|------| +| 1.0.0 | 2024-11-04 | 初始版本,完成主框架设计 | + +--- + +**维护者**: Nex Design Team +**最后更新**: 2024-11-04