Bladeren bron

feat(story-008.001): 完成平台选择器组件实现

- 创建PlatformSelector.tsx组件,基于广告类型选择器模式
- 创建平台选择器集成测试,覆盖7个测试场景
- 更新组件导出配置,支持其他模块导入使用
- 更新故事文件,标记任务完成并更新状态为Ready for Review

组件特性:
- 支持value/onChange标准props
- 支持自定义占位符和禁用状态
- 自动处理加载和错误状态
- 使用React Query获取平台列表
- 使用@d8d/shared-ui-components的Select组件

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 2 weken geleden
bovenliggende
commit
61f0bf4b94

+ 78 - 0
allin-packages/platform-management-ui/src/components/PlatformSelector.tsx

@@ -0,0 +1,78 @@
+import React from 'react';
+import { useQuery } from '@tanstack/react-query';
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from '@d8d/shared-ui-components/components/ui/select';
+import { platformClientManager } from '../api/platformClient';
+
+interface PlatformSelectorProps {
+  value?: number;
+  onChange?: (value: number) => void;
+  placeholder?: string;
+  disabled?: boolean;
+  className?: string;
+  testId?: string;
+}
+
+export const PlatformSelector: React.FC<PlatformSelectorProps> = ({
+  value,
+  onChange,
+  placeholder = "请选择平台",
+  disabled = false,
+  className,
+  testId,
+}) => {
+  const {
+    data: platforms,
+    isLoading,
+    isError,
+  } = useQuery({
+    queryKey: ['platforms'],
+    queryFn: async () => {
+      const client = platformClientManager.get()
+      const res = await client.getAllPlatforms.$get({
+        query: {
+          skip: 0,
+          take: 100
+        }
+      });
+      if (res.status !== 200) throw new Error('获取平台列表失败');
+      return await res.json();
+    },
+  });
+
+  if (isError) {
+    return (
+      <div className="text-sm text-destructive">
+        加载平台列表失败
+      </div>
+    );
+  }
+
+  const platformList = platforms?.data || [];
+
+  return (
+    <Select
+      value={value?.toString()}
+      onValueChange={(val) => onChange?.(parseInt(val))}
+      disabled={disabled || isLoading || platformList.length === 0}
+    >
+      <SelectTrigger className={className} data-testid={testId}>
+        <SelectValue placeholder={isLoading ? '加载中...' : placeholder} />
+      </SelectTrigger>
+      <SelectContent>
+        {platformList.map((platform) => (
+          <SelectItem key={platform.id} value={platform.id.toString()}>
+            {platform.platformName}
+          </SelectItem>
+        ))}
+      </SelectContent>
+    </Select>
+  );
+};
+
+export default PlatformSelector;

+ 2 - 1
allin-packages/platform-management-ui/src/components/index.ts

@@ -1,2 +1,3 @@
 // 组件导出文件
-export { default as PlatformManagement } from './PlatformManagement';
+export { default as PlatformManagement } from './PlatformManagement';
+export { default as PlatformSelector } from './PlatformSelector';

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

@@ -0,0 +1,214 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { PlatformSelector } from '../../src/components/PlatformSelector'
+import { platformClient } from '../../src/api/platformClient'
+
+// Mock the platform client
+vi.mock('../../src/api/platformClient', () => {
+  const platformClient = {
+    getAllPlatforms: {
+      $get: vi.fn(),
+    },
+  }
+  return {
+    platformClient,
+    platformClientManager: {
+      get: vi.fn(() => platformClient),
+    },
+  }
+})
+
+const mockPlatforms = {
+  data: [
+    { id: 1, platformName: '微信平台', contactPerson: '张三', contactPhone: '13800138000', status: 1, createTime: '2024-01-01T00:00:00Z', updateTime: '2024-01-01T00:00:00Z' },
+    { id: 2, platformName: '抖音平台', contactPerson: '李四', contactPhone: '13900139000', status: 1, createTime: '2024-01-01T00:00:00Z', updateTime: '2024-01-01T00:00:00Z' },
+    { id: 3, platformName: '快手平台', contactPerson: '王五', contactPhone: '13700137000', status: 0, createTime: '2024-01-01T00:00:00Z', updateTime: '2024-01-01T00:00:00Z' },
+  ],
+  pagination: {
+    total: 3,
+    page: 1,
+    pageSize: 10,
+    totalPages: 1,
+  },
+}
+
+const TestWrapper = ({ children }: { children: React.ReactNode }) => {
+  const queryClient = new QueryClient({
+    defaultOptions: {
+      queries: {
+        retry: false,
+      },
+    },
+  })
+
+  return (
+    <QueryClientProvider client={queryClient}>
+      {children as any}
+    </QueryClientProvider>
+  )
+}
+
+describe('PlatformSelector 集成测试', () => {
+  beforeEach(() => {
+    vi.clearAllMocks()
+  })
+
+  it('应该正确渲染平台选择器', async () => {
+    (platformClient.getAllPlatforms.$get as any).mockResolvedValue({
+      status: 200,
+      json: async () => mockPlatforms,
+    })
+
+    render(
+      <TestWrapper>
+        <PlatformSelector testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 验证加载状态
+    expect(screen.getByTestId('platform-selector')).toBeInTheDocument()
+    expect(screen.getByText('加载中...')).toBeInTheDocument()
+
+    // 等待数据加载完成
+    await waitFor(() => {
+      expect(screen.getByText('请选择平台')).toBeInTheDocument()
+    })
+
+    // 点击选择器打开下拉菜单
+    const selectTrigger = screen.getByTestId('platform-selector')
+    fireEvent.click(selectTrigger)
+
+    // 验证下拉菜单中的选项
+    await waitFor(() => {
+      expect(screen.getByText('微信平台')).toBeInTheDocument()
+      expect(screen.getByText('抖音平台')).toBeInTheDocument()
+      expect(screen.getByText('快手平台')).toBeInTheDocument()
+    })
+  })
+
+  it('应该处理加载状态', () => {
+    // Mock 延迟响应
+    (platformClient.getAllPlatforms.$get as any).mockImplementation(
+      () => new Promise(() => {}) // 永不解析的Promise
+    )
+
+    render(
+      <TestWrapper>
+        <PlatformSelector testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 验证加载状态
+    expect(screen.getByTestId('platform-selector')).toBeInTheDocument()
+    expect(screen.getByText('加载中...')).toBeInTheDocument()
+  })
+
+  it('应该处理错误状态', async () => {
+    (platformClient.getAllPlatforms.$get as any).mockRejectedValue(new Error('API错误'))
+
+    render(
+      <TestWrapper>
+        <PlatformSelector testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 等待错误状态
+    await waitFor(() => {
+      expect(screen.getByText('加载平台列表失败')).toBeInTheDocument()
+    })
+  })
+
+  it('应该处理选择器值变化', async () => {
+    (platformClient.getAllPlatforms.$get as any).mockResolvedValue({
+      status: 200,
+      json: async () => mockPlatforms,
+    })
+
+    const mockOnChange = vi.fn()
+
+    render(
+      <TestWrapper>
+        <PlatformSelector onChange={mockOnChange} testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 等待数据加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('platform-selector')).toBeEnabled()
+    })
+
+    // 点击选择器打开下拉菜单
+    const selectTrigger = screen.getByTestId('platform-selector')
+    fireEvent.click(selectTrigger)
+
+    // 选择第一个选项
+    await waitFor(() => {
+      const firstOption = screen.getByText('微信平台')
+      fireEvent.click(firstOption)
+    })
+
+    // 验证onChange被调用
+    expect(mockOnChange).toHaveBeenCalledWith(1)
+  })
+
+  it('应该支持自定义占位符', async () => {
+    (platformClient.getAllPlatforms.$get as any).mockResolvedValue({
+      status: 200,
+      json: async () => mockPlatforms,
+    })
+
+    render(
+      <TestWrapper>
+        <PlatformSelector placeholder="选择平台" testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 等待数据加载完成
+    await waitFor(() => {
+      expect(screen.getByText('选择平台')).toBeInTheDocument()
+    })
+  })
+
+  it('应该支持禁用状态', async () => {
+    (platformClient.getAllPlatforms.$get as any).mockResolvedValue({
+      status: 200,
+      json: async () => mockPlatforms,
+    })
+
+    render(
+      <TestWrapper>
+        <PlatformSelector disabled={true} testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 等待数据加载完成
+    await waitFor(() => {
+      const selectTrigger = screen.getByTestId('platform-selector')
+      expect(selectTrigger).toBeDisabled()
+    })
+  })
+
+  it('应该支持预选值', async () => {
+    (platformClient.getAllPlatforms.$get as any).mockResolvedValue({
+      status: 200,
+      json: async () => mockPlatforms,
+    })
+
+    render(
+      <TestWrapper>
+        <PlatformSelector value={2} testId="platform-selector" />
+      </TestWrapper>
+    )
+
+    // 等待数据加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('platform-selector')).toBeEnabled()
+    })
+
+    // 验证预选值已正确设置
+    // 在Radix UI Select中,预选值会显示在选择器触发器中
+    const selectTrigger = screen.getByTestId('platform-selector')
+    expect(selectTrigger).toHaveTextContent('抖音平台')
+  })
+})

+ 11 - 5
docs/stories/008.001.transplant-platform-management-ui.story.md

@@ -1,7 +1,7 @@
 # Story 008.001: 移植平台管理UI(platform → @d8d/allin-platform-management-ui)
 
 ## Status
-Ready for Development
+Ready for Review
 
 ## Story
 **As a** 开发者,
@@ -126,16 +126,16 @@ Ready for Development
     - **验证**: 修复了Schema兼容性问题,UpdatePlatformSchema现在包含id字段
     - **经验**: 必须查看后端模块的集成测试和路由定义来确保Schema设计正确
 
-- [ ] 创建平台选择器组件 (新增任务)
-  - [ ] 创建`src/components/PlatformSelector.tsx`组件
+- [x] 创建平台选择器组件 (新增任务)
+  - [x] 创建`src/components/PlatformSelector.tsx`组件
     - **参考文件**: `packages/advertisement-type-management-ui/src/components/AdvertisementTypeSelector.tsx`
     - **架构**: 使用React Query获取平台列表,使用@d8d/shared-ui-components的Select组件
     - **功能**: 平台选择器,支持value/onChange等标准props,显示平台名称
     - **用途**: 作为可复用组件供其他UI包使用(如广告管理、内容管理等需要选择平台的场景)
-  - [ ] 创建平台选择器集成测试
+  - [x] 创建平台选择器集成测试
     - **参考文件**: `packages/advertisement-type-management-ui/tests/integration/advertisement-type-selector.integration.test.tsx`
     - **测试内容**: 数据加载、选择功能、错误处理、禁用状态
-  - [ ] 更新package.json导出配置
+  - [x] 更新package.json导出配置
     - **导出**: 在`src/index.ts`中导出PlatformSelector组件
     - **依赖**: 确保组件可被其他模块导入使用
 
@@ -309,6 +309,9 @@ Claude Code (d8d-model)
 4. ✅ 修复了Schema和API路径一致性问题
 5. ✅ 优化了测试精度和可维护性
 6. ✅ 遵循了现有UI包的最佳实践
+7. ✅ 创建平台选择器组件,作为可复用组件供其他UI包使用
+8. ✅ 平台选择器组件集成测试通过(7个测试)
+9. ✅ 更新组件导出配置,支持其他模块导入使用
 
 ### File List
 **创建的文件**:
@@ -317,13 +320,16 @@ Claude Code (d8d-model)
 - `allin-packages/platform-management-ui/tsconfig.json`
 - `allin-packages/platform-management-ui/vitest.config.ts`
 - `allin-packages/platform-management-ui/src/components/PlatformManagement.tsx`
+- `allin-packages/platform-management-ui/src/components/PlatformSelector.tsx` (新增)
 - `allin-packages/platform-management-ui/src/api/platformClient.ts`
 - `allin-packages/platform-management-ui/src/api/types.ts`
 - `allin-packages/platform-management-ui/src/index.ts`
 - `allin-packages/platform-management-ui/tests/integration/platform-management.integration.test.tsx`
+- `allin-packages/platform-management-ui/tests/integration/platform-selector.integration.test.tsx` (新增)
 
 **修改的文件**:
 - `allin-packages/platform-module/src/schemas/platform.schema.ts` (修复UpdatePlatformSchema,添加id字段)
+- `allin-packages/platform-management-ui/src/components/index.ts` (新增PlatformSelector导出)
 - `docs/stories/008.001.transplant-platform-management-ui.story.md` (更新开发记录)
 - `docs/prd/epic-008-allin-ui-modules-transplant.md` (更新史诗状态)