# 站点信息 - 布局与组件结构描述 ## 1️⃣ 页面概述 站点信息页面用于配置和管理医学影像系统的 DICOM 站点基本信息,包括站点名称、AE Title、端口、机构信息等核心参数。 ## 2️⃣ 布局结构(使用约定术语) ### Page Layout - **Layout Type**: Vertical Stack Layout(垂直堆叠布局) - **Direction**: Vertical(垂直方向) - **Spacing**: 24px between major sections - **Padding**: 24px all around ### Component Hierarchy(层级结构) ``` Page (页面) └── Main Container (主容器) ├── Header Section (头部区域) │ └── Title Component (标题组件) │ └── Typography.Title (H3) │ ├── Form Section (表单区域) │ └── Form Container (表单容器) │ ├── Form.Item (站点名称) │ │ ├── Label (标签) │ │ └── Input (输入框) │ │ │ ├── Form.Item (AE Title) │ │ ├── Label │ │ └── Input │ │ │ ├── Form.Item (端口) │ │ ├── Label │ │ └── InputNumber │ │ │ ├── Form.Item (机构名称) │ │ ├── Label │ │ └── Input │ │ │ ├── Form.Item (站点编码) │ │ ├── Label │ │ └── Input │ │ │ └── Form.Item (描述) │ ├── Label │ └── TextArea │ └── Footer Section (底部区域) └── Button Group (按钮组) ├── Button (保存) └── Button (重置) ``` ### Layout Properties(布局属性) #### Main Container - **Type**: Flexbox Layout - **Direction**: Column - **Padding**: 24px - **Gap**: 24px - **Background**: Token.colorBgContainer - **Border-radius**: 8px #### Form Section - **Label Width**: 120px - **Label Align**: Right - **Layout**: Horizontal - **Size**: Large #### Footer Section - **Alignment**: Right - **Gap**: 12px - **Margin-top**: 24px ## 3️⃣ Ant Design 组件选择 ### 使用的组件及理由 | 组件 | 用途 | 选择理由 | |------|------|---------| | **Typography.Title** | 页面标题 | - 提供标准的标题样式
- 自动适配主题
- 支持多级标题 | | **Form** | 表单容器 | - 提供完整的表单验证
- 自动处理表单布局
- 内置错误显示
- 支持受控/非受控模式 | | **Form.Item** | 表单项包装器 | - 统一标签和输入框布局
- 提供验证反馈UI
- 自动处理错误信息显示 | | **Input** | 文本输入 | - 标准文本输入组件
- 支持前缀/后缀图标
- 提供清空功能
- 自动trim空格 | | **InputNumber** | 数字输入 | - 专门用于数字输入
- 内置数字验证
- 支持步进器
- 可限制最大最小值 | | **Input.TextArea** | 多行文本 | - 适合长文本输入
- 支持自动调整高度
- 可限制最大长度
- 显示字符计数 | | **Button** | 操作按钮 | - 提供多种按钮类型
- 支持加载状态
- 自动防抖保护
- 主题一致性 | | **Space** | 间距容器 | - 统一管理组件间距
- 响应式间距
- 自动换行支持 | ### 表单布局选择理由 选择 **Horizontal Layout(水平布局)** 的原因: - ✅ 标签在左,输入框在右,符合常见表单设计模式 - ✅ 充分利用横向空间,避免页面过长 - ✅ 标签和输入框对齐整齐,视觉效果好 - ✅ 适合桌面端显示(站点信息主要在桌面端配置) ## 4️⃣ 功能清单 - [x] **数据展示** - [ ] 加载并显示当前站点配置信息 - [ ] 处理加载状态(骨架屏) - [ ] 处理加载错误 - [x] **数据编辑** - [ ] 编辑站点名称 - [ ] 编辑 AE Title(DICOM 应用实体标题) - [ ] 编辑端口号 - [ ] 编辑机构名称 - [ ] 编辑站点编码 - [ ] 编辑描述信息 - [x] **数据验证** - [ ] 站点名称:必填,长度限制 - [ ] AE Title:必填,符合 DICOM 规范(16字符以内,大写字母数字) - [ ] 端口:必填,有效端口范围(1-65535) - [ ] 机构名称:可选 - [ ] 站点编码:可选,唯一性校验 - [ ] 描述:可选,长度限制 - [x] **数据保存** - [ ] 提交表单数据到后端 - [ ] 显示保存进度 - [ ] 保存成功反馈 - [ ] 保存失败处理 - [x] **表单操作** - [ ] 重置表单到初始状态 - [ ] 取消编辑 - [ ] 表单脏数据检测 - [ ] 离开页面确认 ## 5️⃣ 功能需求与思考 ### 功能1:加载站点信息 **需求描述**: - 页面打开时自动从后端加载当前站点配置 - 显示加载状态,避免用户操作空白表单 - 处理加载失败的情况 **交互流程**: 1. 组件挂载时触发数据加载 2. 显示骨架屏或 Spin 组件 3. 数据加载成功后填充表单 4. 加载失败显示错误提示,提供重试按钮 **验证规则**: - API 返回数据结构验证 - 字段类型校验 **边界情况**: - 首次配置(无数据) - 网络超时 - 服务器错误 - 数据格式错误 ### 功能2:AE Title 验证 **需求描述**: - AE Title 是 DICOM 标准的重要标识符 - 必须符合 DICOM 规范要求 **交互流程**: 1. 用户输入 AE Title 2. 实时验证格式(可选) 3. 失焦时完整验证 4. 显示错误信息 **验证规则**: - 必填字段 - 长度:1-16个字符 - 字符集:大写字母、数字、下划线 - 不能包含空格和特殊字符 **边界情况**: - 输入中文字符 - 输入小写字母(可自动转大写) - 超长输入 - 特殊字符输入 ### 功能3:端口号验证 **需求描述**: - 端口号用于 DICOM 通信 - 必须是有效的端口范围 **交互流程**: 1. 使用 InputNumber 组件 2. 限制输入范围 3. 自动过滤非数字输入 **验证规则**: - 必填字段 - 范围:1-65535 - 常用端口:104(DICOM 默认) - 避免系统保留端口(<1024需管理员权限提示) **边界情况**: - 输入0或负数 - 输入超过65535 - 端口被占用(需后端验证) ### 功能4:保存配置 **需求描述**: - 将表单数据保存到后端 - 提供清晰的保存反馈 **交互流程**: 1. 点击保存按钮 2. 触发表单验证 3. 验证通过后提交数据 4. 按钮显示加载状态 5. 保存成功:显示成功消息,可能需要重启服务 6. 保存失败:显示错误信息 **验证规则**: - 前端:表单所有验证规则 - 后端:业务逻辑验证 **边界情况**: - 网络中断 - 服务器繁忙 - 配置冲突 - 需要重启服务生效 ### 功能5:重置表单 **需求描述**: - 允许用户放弃编辑,恢复到初始状态 **交互流程**: 1. 点击重置按钮 2. 如有未保存修改,弹出确认对话框 3. 确认后恢复表单初始值 **边界情况**: - 表单未修改(直接重置) - 表单已修改(需确认) ## 6️⃣ 后续实现建议 ### 状态管理 #### Redux Slice 设计 ```typescript // states/siteInfoSlice.ts interface SiteInfoState { // 站点信息数据 data: { siteName: string; aeTitle: string; port: number; institutionName: string; siteCode: string; description: string; } | null; // 加载状态 loading: boolean; // 保存状态 saving: boolean; // 错误信息 error: string | null; // 是否有未保存的修改 isDirty: boolean; } // 异步 Thunks export const fetchSiteInfo = createAsyncThunk(...); export const saveSiteInfo = createAsyncThunk(...); ``` ### API 接口设计 ```typescript // API/siteInfo.ts // 获取站点信息 export const getSiteInfo = (): Promise => { return request.get('/api/system/site-info'); }; // 保存站点信息 export const updateSiteInfo = (data: SiteInfoData): Promise => { return request.put('/api/system/site-info', data); }; // 验证 AE Title 唯一性(可选) export const validateAETitle = (aeTitle: string): Promise => { return request.post('/api/system/site-info/validate-ae-title', { aeTitle }); }; // 检查端口可用性(可选) export const checkPortAvailability = (port: number): Promise => { return request.post('/api/system/site-info/check-port', { port }); }; ``` ### 表单验证规则 ```typescript // validation/siteInfoRules.ts import { z } from 'zod'; export const siteInfoSchema = z.object({ siteName: z.string() .min(1, '站点名称不能为空') .max(50, '站点名称最多50个字符'), aeTitle: z.string() .min(1, 'AE Title 不能为空') .max(16, 'AE Title 最多16个字符') .regex(/^[A-Z0-9_]+$/, 'AE Title 只能包含大写字母、数字和下划线'), port: z.number() .int('端口必须是整数') .min(1, '端口号最小为1') .max(65535, '端口号最大为65535'), institutionName: z.string() .max(100, '机构名称最多100个字符') .optional(), siteCode: z.string() .max(20, '站点编码最多20个字符') .optional(), description: z.string() .max(500, '描述最多500个字符') .optional(), }); ``` ### 组件实现骨架 ```typescript // sections/SystemHome/SiteInfo.tsx import React, { useEffect } from 'react'; import { Form, Input, InputNumber, Button, message, Spin } from 'antd'; import { useAppDispatch, useAppSelector } from '@/states/store'; import { fetchSiteInfo, saveSiteInfo } from '@/states/siteInfoSlice'; const SiteInfo: React.FC = () => { const [form] = Form.useForm(); const dispatch = useAppDispatch(); const { data, loading, saving } = useAppSelector(state => state.siteInfo); // 加载数据 useEffect(() => { dispatch(fetchSiteInfo()); }, [dispatch]); // 数据加载后填充表单 useEffect(() => { if (data) { form.setFieldsValue(data); } }, [data, form]); // 保存处理 const handleSave = async () => { try { const values = await form.validateFields(); await dispatch(saveSiteInfo(values)).unwrap(); message.success('保存成功'); } catch (error) { message.error('保存失败'); } }; // 重置处理 const handleReset = () => { form.resetFields(); }; if (loading) { return ; } return (
站点信息
{/* 表单项... */}
); }; export default SiteInfo; ``` ### 交互流程图 ``` 用户打开页面 ↓ 加载站点信息 (fetchSiteInfo) ↓ 显示加载状态 ↓ 数据加载成功 → 填充表单 ↓ 用户编辑表单 ↓ 实时验证 (可选) ↓ 点击保存按钮 ↓ 前端验证 ↓ 验证失败 → 显示错误 ↓ 验证成功 → 提交到后端 ↓ 显示保存状态 ↓ 保存成功 → 显示成功消息 ↓ 保存失败 → 显示错误 + 重试选项 ``` ### 国际化支持 ```typescript // assets/i18n/zh-CN.json { "systemSettings": { "siteInfo": { "title": "站点信息", "siteName": "站点名称", "aeTitle": "AE Title", "port": "端口", "institutionName": "机构名称", "siteCode": "站点编码", "description": "描述", "save": "保存", "reset": "重置", "saveSuccess": "保存成功", "saveFailed": "保存失败" } } } ``` ## 7️⃣ 测试要点 ### 单元测试 - 表单验证规则测试 - Redux slice 逻辑测试 - API 接口调用测试 ### 集成测试 - 完整的数据加载-编辑-保存流程 - 错误处理测试 - 边界值测试 ### E2E 测试 - 用户操作流程测试 - 跨浏览器兼容性测试 ## 8️⃣ 性能优化建议 1. **防抖处理**:保存按钮添加防抖,避免重复提交 2. **数据缓存**:考虑缓存站点信息,避免重复加载 3. **懒加载**:如果站点信息较多,考虑分步加载 4. **错误边界**:添加 Error Boundary 捕获组件错误 ## 9️⃣ 安全性考虑 1. **输入过滤**:防止 XSS 注入 2. **数据验证**:前后端双重验证 3. **权限控制**:只有管理员可以修改站点信息 4. **操作日志**:记录站点信息的修改历史