Bläddra i källkod

fix: 修复注册表单状态清理Bug,防止二次注册时使用旧数据

- 在formSlice中添加clearFormData action用于清空Redux状态
- 在Register组件中实现注册成功后的三重清理(Redux formSlice + Ant Design Form + selectedViews)
- 在Register组件中添加useEffect cleanup,组件卸载时清理表单状态
- 创建详细的Bug分析文档,包含领域模型概念、因果链条和修复方案

修改文件:
- src/states/patient/register/formSlice.ts
- src/pages/patient/register.tsx
- docs/实现/注册表单状态清理-Bug修复.md
sw 6 dagar sedan
förälder
incheckning
f97f5a374f

+ 361 - 0
docs/实现/注册表单状态清理-Bug修复.md

@@ -0,0 +1,361 @@
+# 注册表单状态清理 - Bug分析与修复
+
+## Bug 描述
+
+**问题场景**:
+
+1. 第一次填写完整的患者信息并注册成功
+2. 表单UI清空,但内部状态未清空
+3. 第二次不填写任何信息,直接选择体位后点击注册
+4. ❌ Bug:仍然可以注册成功(应该失败)
+
+---
+
+## 根本原因分析
+
+### 涉及的领域模型概念
+
+这个bug涉及到**两个独立的状态管理域**:
+
+#### 1. Ant Design Form State(组件级状态)
+
+```typescript
+const [form] = Form.useForm();
+// 内部维护表单字段值
+// 生命周期:从组件创建到手动重置
+// 清理方式:form.resetFields()
+```
+
+**特性**:
+
+- 状态持久化:即使UI不显示,内部store仍然保留数据
+- 独立管理:不受Redux状态影响
+- 需要显式清理:必须调用`form.resetFields()`
+
+#### 2. Redux formSlice State(全局状态)
+
+```typescript
+interface FormState {
+  formData: Record<string, any>;
+}
+// 存储表单数据的副本
+// 用于跨组件共享
+```
+
+**特性**:
+
+- 全局可访问
+- 需要通过action更新
+- 需要显式清理action
+
+### 因果链条(清晰明确)
+
+```
+第一次注册成功
+  ↓
+触发事件:registerWork API 返回 code === '0x000000'
+  ↓
+当前行为(Bug原因):
+  - ✅ Redux viewSelection.selectedViews 被清空
+  - ❌ Redux formSlice.formData 未清空
+  - ❌ Ant Design Form 内部状态未清空
+  ↓
+状态不一致:
+  - UI层:表单显示为空(用户视角)
+  - 数据层:form.getFieldsValue() 返回旧数据(系统视角)
+  ↓
+第二次注册
+  ↓
+用户操作:只选择体位,不填表单
+  ↓
+点击"注册"按钮
+  ↓
+handleRegister() 执行:
+  1. let values = form.getFieldsValue();
+     // ❌ 返回第一次的完整数据(用户不知道)
+  2. validateResult = schema.safeParse(values);
+     // ✅ 验证通过(因为values有完整数据)
+  3. 调用 registerWork(registerInfo);
+     // ❌ 使用旧数据注册成功
+  ↓
+Bug结果:用户以为表单是空的,实际用旧数据注册成功
+```
+
+---
+
+## 修复方案
+
+### 方案概述
+
+在两个关键时机清理**双重状态**:
+
+1. **清理时机1**:注册成功后(最重要)
+2. **清理时机3**:组件卸载时(防御性)
+
+### 实现细节
+
+#### 修改1:formSlice 添加清理action
+
+**文件**:`src/states/patient/register/formSlice.ts`
+
+```typescript
+const formSlice = createSlice({
+  name: 'form',
+  initialState,
+  reducers: {
+    setFormData: (state, action) => {
+      state.formData = action.payload;
+    },
+    // ✅ 新增:清空表单数据
+    clearFormData: (state) => {
+      state.formData = {};
+      console.log('Redux formSlice: 表单数据已清空');
+    },
+  },
+});
+
+export const { setFormData, clearFormData } = formSlice.actions;
+```
+
+**领域概念**:
+
+- **Action**:clearFormData - 表达"清空表单"的业务意图
+- **Reducer**:将状态重置为初始值 `{}`
+- **日志**:记录状态变化,便于调试
+
+#### 修改2:注册成功后清理(清理时机1)
+
+**文件**:`src/pages/patient/Register.tsx`
+
+```typescript
+const response = await registerWork(registerInfo);
+if (response.code !== '0x000000') {
+  message.error(`注册失败: ${response.description}`);
+  return { success: false, views: [] };
+}
+
+// ✅ 注册成功后的三重清理
+// 1. 清理 Redux formSlice
+dispatch(clearFormData());
+
+// 2. 清理 Ant Design Form
+form.resetFields();
+
+// 3. selectedViews 已在 viewSelectionSlice 中自动清空
+console.log('注册成功,表单已清空,可以开始新的注册');
+
+return { success: true, data: response.data, views: selectedViews };
+```
+
+**因果关系**:
+
+```
+API返回成功
+  ↓
+触发清理三重奏:
+  1. dispatch(clearFormData())  → Redux formSlice.formData = {}
+  2. form.resetFields()         → Ant Design Form 内部状态清空
+  3. selectedViews自动清空      → 已在viewSelectionSlice实现
+  ↓
+系统恢复初始状态
+  ↓
+用户可以开始新的注册流程 ✅
+```
+
+#### 修改3:组件卸载时清理(清理时机3)
+
+**文件**:`src/pages/patient/Register.tsx`
+
+```typescript
+// 清理时机3:组件卸载时清理表单
+useEffect(() => {
+  return () => {
+    // 组件卸载时清理
+    dispatch(clearFormData());
+    form.resetFields();
+    console.log('注册页面已卸载,表单数据已清空');
+  };
+}, [dispatch, form]);
+```
+
+**领域概念**:
+
+- **React生命周期**:useEffect cleanup函数
+- **防御性编程**:即使用户没有完成注册就离开页面,也确保状态被清理
+- **避免数据泄漏**:用户再次进入注册页面时,不会看到旧数据
+
+---
+
+## 修复后的完整流程
+
+### 正常注册流程
+
+```
+用户进入注册页面
+  ↓
+填写患者信息
+  ↓
+  每次字段变化 → dispatch(setFormData(allValues))
+  ↓
+选择体位
+  ↓
+  点击体位 → dispatch(addSelectedView(view))
+  ↓
+点击"注册"按钮
+  ↓
+handleRegister():
+  1. 获取表单值:form.getFieldsValue()
+  2. 验证表单:schema.safeParse(values)
+  3. 验证通过 → 调用API:registerWork(registerInfo)
+  ↓
+API返回成功
+  ↓
+【三重清理】
+  1. dispatch(clearFormData())
+     → formSlice.formData = {}
+  2. form.resetFields()
+     → Form内部状态清空
+  3. selectedViews自动清空
+     → viewSelection.selectedViews = []
+  ↓
+显示成功消息
+  ↓
+用户可以开始新的注册 ✅
+```
+
+### 用户离开页面流程
+
+```
+用户填写部分信息
+  ↓
+未完成注册,切换到其他页面
+  ↓
+RegisterPage组件卸载
+  ↓
+useEffect cleanup函数执行
+  ↓
+【清理】
+  1. dispatch(clearFormData())
+  2. form.resetFields()
+  ↓
+用户再次进入注册页面
+  ↓
+看到的是干净的空表单 ✅
+```
+
+---
+
+## 状态管理架构图
+
+### 修复前(有Bug)
+
+```
+┌─────────────────────────────────────────────┐
+│          注册成功后的状态                      │
+├─────────────────────────────────────────────┤
+│ Redux viewSelection.selectedViews: []  ✅    │
+│ Redux formSlice.formData: {...旧数据}  ❌    │
+│ Ant Design Form State: {...旧数据}     ❌    │
+└─────────────────────────────────────────────┘
+         ↓
+   状态不一致!
+         ↓
+   第二次注册时Bug
+```
+
+### 修复后(正常)
+
+```
+┌─────────────────────────────────────────────┐
+│          注册成功后的状态                      │
+├─────────────────────────────────────────────┤
+│ Redux viewSelection.selectedViews: []  ✅    │
+│ Redux formSlice.formData: {}           ✅    │
+│ Ant Design Form State: {}              ✅    │
+└─────────────────────────────────────────────┘
+         ↓
+   状态一致!
+         ↓
+   系统恢复初始状态
+```
+
+---
+
+## 关键概念总结
+
+### 1. 双重状态管理
+
+- **Form State**(组件级):负责UI交互
+- **Redux State**(全局级):负责跨组件共享
+
+### 2. 状态同步
+
+- 数据流入:`onValuesChange` → `dispatch(setFormData)`
+- 数据清理:必须同时清理两个状态
+
+### 3. 清理时机
+
+- **时机1**:业务成功后(注册成功)
+- **时机3**:组件卸载时(防御性)
+
+### 4. 因果关系
+
+```
+用户操作 → 状态变化 → 副作用处理 → UI更新
+```
+
+---
+
+## 测试验证要点
+
+### 手动测试
+
+1. **测试场景1**:连续注册两次
+
+   - 第一次:填写完整信息 → 注册成功
+   - 验证:表单已清空(UI和数据层都清空)
+   - 第二次:不填表单 → 点击注册
+   - 预期:提示"必填项未填写"
+
+2. **测试场景2**:填写后离开页面
+   - 填写部分信息
+   - 切换到其他页面
+   - 再次返回注册页面
+   - 预期:看到空表单
+
+### 验证点
+
+- [ ] 注册成功后,`form.getFieldsValue()` 返回空对象
+- [ ] 注册成功后,Redux `formSlice.formData` 为空对象
+- [ ] 注册成功后,Redux `selectedViews` 为空数组
+- [ ] 组件卸载后再进入,表单显示为空
+- [ ] 控制台日志正确显示清理信息
+
+---
+
+## 相关文件清单
+
+### 已修改文件
+
+1. `src/states/patient/register/formSlice.ts`
+
+   - 添加 `clearFormData` action
+
+2. `src/pages/patient/Register.tsx`
+   - 导入 `clearFormData`, `useEffect`
+   - 实现清理时机1:注册成功后
+   - 实现清理时机3:组件卸载时
+
+### 相关参考文件
+
+- `src/states/patient/viewSelection/index.ts`(selectedViews的清理)
+- `src/hooks/useRegisterState.ts`(状态获取)
+- `src/validation/patient/registerSchema.ts`(验证逻辑)
+
+---
+
+## 更新记录
+
+| 日期      | 修改人 | 修改内容                    |
+| --------- | ------ | --------------------------- |
+| 2025/10/8 | -      | Bug分析、修复实现、文档创建 |

+ 24 - 2
src/pages/patient/register.tsx

@@ -1,8 +1,11 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 import { Row, Col, Collapse, Grid, Button, Space, Form, message } from 'antd';
 import { FormattedMessage } from 'react-intl';
 import { useDispatch, useSelector } from 'react-redux';
-import { setFormData } from '@/states/patient/register/formSlice';
+import {
+  setFormData,
+  clearFormData,
+} from '@/states/patient/register/formSlice';
 import BasicInfoForm from './components/register.form';
 import SelectedProtocolList from './components/register.selected.view.list';
 import RegisterAvailableList from './components/register.available.list';
@@ -36,6 +39,16 @@ const RegisterPage: React.FC = () => {
     (state: RootState) => state.product.productName
   );
 
+  // 清理时机3:组件卸载时清理表单
+  useEffect(() => {
+    return () => {
+      // 组件卸载时清理
+      dispatch(clearFormData());
+      form.resetFields();
+      console.log('注册页面已卸载,表单数据已清空');
+    };
+  }, [dispatch, form]);
+
   const handleRegister = async (): Promise<{
     success: boolean;
 
@@ -90,6 +103,15 @@ const RegisterPage: React.FC = () => {
       }
       console.log('Work registered successfully:', response);
       message.info('Work registered successfully');
+
+      // 清理时机1:注册成功后清理表单
+      // 1. 清理 Redux formSlice
+      dispatch(clearFormData());
+      // 2. 清理 Ant Design Form
+      form.resetFields();
+      // 3. selectedViews 会在 Redux 中自动清空(已在 viewSelectionSlice 中实现)
+      console.log('注册成功,表单已清空,可以开始新的注册');
+
       return { success: true, data: response.data, views: selectedViews };
     } catch (error) {
       console.error('Error registering work:', error);

+ 6 - 1
src/states/patient/register/formSlice.ts

@@ -18,9 +18,14 @@ const formSlice = createSlice({
       state.formData = action.payload;
     },
     getFormData: (state) => state, //其实这里用不到
+    // 清空表单数据
+    clearFormData: (state) => {
+      state.formData = {};
+      console.log('Redux formSlice: 表单数据已清空');
+    },
   },
 });
 
-export const { setFormData, getFormData } = formSlice.actions;
+export const { setFormData, getFormData, clearFormData } = formSlice.actions;
 export type { FormState };
 export default formSlice.reducer;