浏览代码

✨ feat(platform-management): 优化平台管理表单邮箱字段处理并更新相关文档

- 将表单类型从外部模块DTO切换为本地API类型定义,提升类型安全性和维护性
- 优化联系邮箱输入组件,使用显式属性绑定替代展开运算符,增强表单控制
- 更新集成测试,添加邮箱字段验证测试用例,包括空值、有效格式和无效格式场景
- 更新故事文档状态为进行中,并新增公司创建优化故事文档

📝 docs(stories): 新增公司创建优化故事文档

- 创建故事009.008,描述公司创建表单优化需求
- 定义验收标准:仅公司名称为必填项
- 详细列出开发任务、技术约束和测试要求
- 提供参考实现模式和文件位置信息
yourname 2 周之前
父节点
当前提交
32d2714c52

+ 25 - 9
allin-packages/platform-management-ui/src/components/PlatformManagement.tsx

@@ -15,7 +15,7 @@ import { toast } from 'sonner';
 import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
 import { platformClientManager } from '../api/platformClient';
 import { CreatePlatformSchema, UpdatePlatformSchema } from '@d8d/allin-platform-module/schemas';
-import type { CreatePlatformDto, UpdatePlatformDto } from '@d8d/allin-platform-module/schemas';
+import type { CreatePlatformRequest, UpdatePlatformRequest } from '../api/types';
 
 interface PlatformSearchParams {
   page: number;
@@ -33,7 +33,7 @@ const PlatformManagement: React.FC = () => {
   const [platformToDelete, setPlatformToDelete] = useState<number | null>(null);
 
   // 表单实例
-  const createForm = useForm<CreatePlatformDto>({
+  const createForm = useForm<CreatePlatformRequest>({
     resolver: zodResolver(CreatePlatformSchema),
     defaultValues: {
       platformName: '',
@@ -43,7 +43,7 @@ const PlatformManagement: React.FC = () => {
     }
   });
 
-  const updateForm = useForm<UpdatePlatformDto>({
+  const updateForm = useForm<UpdatePlatformRequest>({
     resolver: zodResolver(UpdatePlatformSchema),
     defaultValues: {}
   });
@@ -69,7 +69,7 @@ const PlatformManagement: React.FC = () => {
 
   // 创建平台
   const createMutation = useMutation({
-    mutationFn: async (data: CreatePlatformDto) => {
+    mutationFn: async (data: CreatePlatformRequest) => {
       const res = await platformClientManager.get().createPlatform.$post({ json: data });
       if (res.status !== 200) throw new Error('创建平台失败');
       return await res.json();
@@ -87,7 +87,7 @@ const PlatformManagement: React.FC = () => {
 
   // 更新平台
   const updateMutation = useMutation({
-    mutationFn: async (data: UpdatePlatformDto) => {
+    mutationFn: async (data: UpdatePlatformRequest) => {
       const res = await platformClientManager.get().updatePlatform.$post({
         json: data
       });
@@ -187,7 +187,7 @@ const PlatformManagement: React.FC = () => {
   };
 
   // 处理创建表单提交
-  const handleCreateSubmit = async (data: CreatePlatformDto) => {
+  const handleCreateSubmit = async (data: CreatePlatformRequest) => {
     try {
       await createMutation.mutateAsync(data);
     } catch (error) {
@@ -196,7 +196,7 @@ const PlatformManagement: React.FC = () => {
   };
 
   // 处理编辑表单提交
-  const handleUpdateSubmit = async (data: UpdatePlatformDto) => {
+  const handleUpdateSubmit = async (data: UpdatePlatformRequest) => {
     try {
       await updateMutation.mutateAsync(data);
     } catch (error) {
@@ -398,7 +398,15 @@ const PlatformManagement: React.FC = () => {
                     <FormItem>
                       <FormLabel>联系邮箱</FormLabel>
                       <FormControl>
-                        <Input placeholder="请输入联系邮箱" {...field} data-testid="contact-email-input" />
+                        <Input
+                          placeholder="请输入联系邮箱"
+                          value={field.value || ''}
+                          onChange={field.onChange}
+                          onBlur={field.onBlur}
+                          name={field.name}
+                          ref={field.ref}
+                          data-testid="contact-email-input"
+                        />
                       </FormControl>
                       <FormMessage />
                     </FormItem>
@@ -469,7 +477,15 @@ const PlatformManagement: React.FC = () => {
                     <FormItem>
                       <FormLabel>联系邮箱</FormLabel>
                       <FormControl>
-                        <Input placeholder="请输入联系邮箱" {...field} data-testid="contact-email-input" />
+                        <Input
+                          placeholder="请输入联系邮箱"
+                          value={field.value || ''}
+                          onChange={field.onChange}
+                          onBlur={field.onBlur}
+                          name={field.name}
+                          ref={field.ref}
+                          data-testid="contact-email-input"
+                        />
                       </FormControl>
                       <FormMessage />
                     </FormItem>

+ 150 - 0
allin-packages/platform-management-ui/tests/integration/platform-management.integration.test.tsx

@@ -332,4 +332,154 @@ describe('PlatformManagement 集成测试', () => {
       expect(screen.getByText('平台管理')).toBeInTheDocument();
     });
   });
+
+  // 邮箱字段优化测试
+  it('应该允许邮箱字段为空创建平台', async () => {
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('测试平台')).toBeInTheDocument();
+    });
+
+    // 打开创建模态框
+    const createButton = screen.getByTestId('create-platform-button');
+    fireEvent.click(createButton);
+
+    // 填写表单,不填写邮箱字段
+    const platformNameInput = screen.getByTestId('platform-name-input');
+    const contactPersonInput = screen.getByTestId('contact-person-input');
+    const contactPhoneInput = screen.getByTestId('contact-phone-input');
+
+    fireEvent.change(platformNameInput, { target: { value: '新平台(无邮箱)' } });
+    fireEvent.change(contactPersonInput, { target: { value: '李四' } });
+    fireEvent.change(contactPhoneInput, { target: { value: '13900139000' } });
+
+    // 提交表单
+    const submitButton = screen.getByTestId('create-submit-button');
+    fireEvent.click(submitButton);
+
+    // 验证API调用,邮箱字段应为undefined
+    await waitFor(() => {
+      expect(platformClientManager.get().createPlatform.$post).toHaveBeenCalledWith({
+        json: {
+          platformName: '新平台(无邮箱)',
+          contactPerson: '李四',
+          contactPhone: '13900139000',
+          contactEmail: undefined
+        }
+      });
+    });
+  });
+
+  it('应该允许邮箱字段为有效邮箱格式创建平台', async () => {
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('测试平台')).toBeInTheDocument();
+    });
+
+    // 打开创建模态框
+    const createButton = screen.getByTestId('create-platform-button');
+    fireEvent.click(createButton);
+
+    // 填写表单,包含有效邮箱
+    const platformNameInput = screen.getByTestId('platform-name-input');
+    const contactPersonInput = screen.getByTestId('contact-person-input');
+    const contactPhoneInput = screen.getByTestId('contact-phone-input');
+    const contactEmailInput = screen.getByTestId('contact-email-input');
+
+    fireEvent.change(platformNameInput, { target: { value: '新平台(有邮箱)' } });
+    fireEvent.change(contactPersonInput, { target: { value: '王五' } });
+    fireEvent.change(contactPhoneInput, { target: { value: '13700137000' } });
+    fireEvent.change(contactEmailInput, { target: { value: 'wangwu@example.com' } });
+
+    // 提交表单
+    const submitButton = screen.getByTestId('create-submit-button');
+    fireEvent.click(submitButton);
+
+    // 验证API调用
+    await waitFor(() => {
+      expect(platformClientManager.get().createPlatform.$post).toHaveBeenCalledWith({
+        json: {
+          platformName: '新平台(有邮箱)',
+          contactPerson: '王五',
+          contactPhone: '13700137000',
+          contactEmail: 'wangwu@example.com'
+        }
+      });
+    });
+  });
+
+  it('应该显示无效邮箱格式的错误提示', async () => {
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('测试平台')).toBeInTheDocument();
+    });
+
+    // 打开创建模态框
+    const createButton = screen.getByTestId('create-platform-button');
+    fireEvent.click(createButton);
+
+    // 填写表单,包含无效邮箱
+    const platformNameInput = screen.getByTestId('platform-name-input');
+    const contactEmailInput = screen.getByTestId('contact-email-input');
+
+    fireEvent.change(platformNameInput, { target: { value: '测试平台' } });
+    fireEvent.change(contactEmailInput, { target: { value: '无效邮箱' } });
+
+    // 提交表单
+    const submitButton = screen.getByTestId('create-submit-button');
+    fireEvent.click(submitButton);
+
+    // 验证显示中文错误提示
+    await waitFor(() => {
+      expect(screen.getByText('请输入有效的邮箱地址')).toBeInTheDocument();
+    });
+
+    // 验证API没有被调用
+    expect(platformClientManager.get().createPlatform.$post).not.toHaveBeenCalled();
+  });
+
+  it('应该允许邮箱字段为空更新平台', async () => {
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('测试平台')).toBeInTheDocument();
+    });
+
+    // 打开编辑模态框
+    const editButton = screen.getByTestId('edit-button-1');
+    fireEvent.click(editButton);
+
+    // 等待编辑模态框打开
+    await waitFor(() => {
+      expect(screen.getByTestId('edit-platform-dialog-title')).toBeInTheDocument();
+    });
+
+    // 清空邮箱字段
+    const contactEmailInput = screen.getByTestId('contact-email-input');
+    fireEvent.change(contactEmailInput, { target: { value: '' } });
+
+    // 提交表单
+    const submitButton = screen.getByTestId('update-submit-button');
+    fireEvent.click(submitButton);
+
+    // 验证API调用,邮箱字段应为undefined
+    await waitFor(() => {
+      expect(platformClientManager.get().updatePlatform.$post).toHaveBeenCalledWith({
+        json: {
+          id: 1,
+          platformName: '测试平台',
+          contactPerson: '张三',
+          contactPhone: '13800138000',
+          contactEmail: undefined
+        }
+      });
+    });
+  });
 });

+ 1 - 1
docs/stories/009.002.story.md

@@ -1,7 +1,7 @@
 # Story 009.002: 邮箱输入优化
 
 ## Status
-Draft
+In Progress
 
 ## Story
 **As a** 平台管理员

+ 125 - 0
docs/stories/009.008.story.md

@@ -0,0 +1,125 @@
+# Story 009.008: 公司创建优化
+
+## Status
+Draft
+
+## Story
+**As a** 系统管理员
+**I want** 创建公司时仅"公司名字"为必填项
+**so that** 快速创建公司信息
+
+## Acceptance Criteria
+1. 创建公司表单中仅"公司名字"为必填
+2. 其他所有字段(含平台)均设为非必填
+3. 表单验证规则更新
+
+## Tasks / Subtasks
+- [ ] 修改后端公司模块schema验证规则 (AC: 1, 2, 3)
+  - [ ] 修改`allin-packages/company-module/src/schemas/company.schema.ts`中的`CreateCompanySchema`
+  - [ ] 将`platformId`、`contactPerson`、`contactPhone`字段从必填改为可选
+  - [ ] 添加中文错误提示(参考平台管理模块的schema模式)
+  - [ ] 确保空字符串自动转换为undefined(使用Zod transform)
+- [ ] 更新前端公司管理UI表单配置 (AC: 1, 2, 3)
+  - [ ] 修改`allin-packages/company-management-ui/src/components/CompanyManagement.tsx`中的表单默认值
+  - [ ] 更新FormLabel显示,移除非必填字段的红色星号标记
+  - [ ] 验证表单提交时可选字段正确处理
+- [ ] 更新前端表单验证规则 (AC: 3)
+  - [ ] 确保前端使用更新后的schema进行验证
+  - [ ] 验证可选字段的默认值处理
+- [ ] 更新集成测试 (AC: 1, 2, 3)
+  - [ ] 修改`allin-packages/company-management-ui/tests/integration/company.integration.test.tsx`
+  - [ ] 添加测试用例验证仅公司名称为必填
+  - [ ] 添加测试用例验证可选字段可以留空
+  - [ ] 确保所有现有测试通过
+- [ ] 运行测试验证修改 (AC: 1, 2, 3)
+  - [ ] 运行公司管理UI包测试:`pnpm test`
+  - [ ] 运行公司模块测试:`pnpm test`
+  - [ ] 验证所有测试通过
+
+## Dev Notes
+
+### 技术栈信息 [Source: architecture/tech-stack.md]
+- 前端框架:React 19.1.0
+- 表单验证:Zod 4.0.15 + react-hook-form 7.61.1
+- 构建工具:Vite 7.0.0
+- 测试框架:Vitest 4.0.9 + Testing Library
+
+### 项目结构信息 [Source: architecture/source-tree.md]
+- 公司管理UI包路径:`allin-packages/company-management-ui/`
+- 公司模块路径:`allin-packages/company-module/`
+- 测试文件位置:与源码并列的`tests/`文件夹
+
+### 编码标准和测试策略 [Source: architecture/coding-standards.md]
+- 测试框架:使用Vitest + Testing Library
+- 测试位置:`tests/`文件夹与源码并列
+- 测试类型:单元测试、集成测试
+- 覆盖率目标:核心业务逻辑 > 80%
+
+### UI包开发规范 [Source: architecture/ui-package-standards.md]
+- **表单组件模式规范**:必须使用条件渲染两个独立的Form组件(创建和编辑)
+- **类型推断最佳实践**:必须使用RPC推断类型,而不是直接导入schema类型
+- **测试选择器优化**:必须为关键交互元素添加`data-testid`属性
+- **API调用一致性**:必须根据实际路由名称修正API调用
+
+### 当前schema状态分析
+根据`allin-packages/company-module/src/schemas/company.schema.ts`:
+- `CreateCompanySchema`当前必填字段:`platformId`、`companyName`、`contactPerson`、`contactPhone`
+- 可选字段:`contactEmail`、`address`
+- 需要修改:仅保留`companyName`为必填,其他字段改为可选
+
+### 参考实现模式
+参考平台管理模块的schema优化模式(故事009.002):
+- 使用Zod transform将空字符串转换为undefined
+- 添加中文错误提示
+- 统一创建和更新DTO的验证规则
+
+### 前端表单当前状态
+根据`allin-packages/company-management-ui/src/components/CompanyManagement.tsx`:
+- 创建表单默认值:所有字段都有默认值(包括必填字段)
+- FormLabel显示:部分字段标记为"(可选)"
+- 需要更新:修改默认值,更新FormLabel,确保验证规则一致
+
+### 文件位置
+- 后端schema文件:`allin-packages/company-module/src/schemas/company.schema.ts`
+- 前端组件文件:`allin-packages/company-management-ui/src/components/CompanyManagement.tsx`
+- 前端测试文件:`allin-packages/company-management-ui/tests/integration/company.integration.test.tsx`
+
+### 技术约束
+1. 数据库兼容性:现有数据不需要迁移
+2. API向后兼容:修改schema不影响现有API调用
+3. 前端默认值:需要与后端schema验证规则保持一致
+4. 空字符串处理:使用Zod transform统一处理
+
+### Testing
+#### 测试标准 [Source: architecture/coding-standards.md]
+- 测试文件位置:`tests/integration/`文件夹
+- 测试框架:Vitest + Testing Library
+- Mock模式:使用标准mock响应函数
+
+#### 具体测试要求
+1. **集成测试**:验证创建公司表单仅公司名称为必填
+2. **边界测试**:测试可选字段留空、空字符串处理
+3. **错误处理测试**:验证必填字段缺失时的错误提示
+4. **兼容性测试**:确保现有功能不受影响
+
+#### 测试选择器规范 [Source: architecture/ui-package-standards.md#测试选择器优化规范]
+- 必须为表单元素添加`data-testid`属性
+- 命名约定:kebab-case格式 `{action}-{element}-{purpose}`
+- 示例:`data-testid="create-company-name-input"`
+
+## Change Log
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2025-12-10 | 1.0 | 初始故事创建 | Scrum Master Bob |
+
+## Dev Agent Record
+
+### Agent Model Used
+
+### Debug Log References
+
+### Completion Notes List
+
+### File List
+
+## QA Results