Sfoglia il codice sorgente

docs(architecture): 添加UI包开发规范文档

- 创建ui-package-standards.md文档,基于史诗008经验和现有UI包实现
- 包含包结构规范、RPC客户端实现规范、组件开发规范等
- 更新架构文档索引,在"后端模块包开发规范"后添加"UI包开发规范"
- 修正package.json配置与实际UI包一致(直接指向src)

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 6 giorni fa
parent
commit
e0d2e70675
2 ha cambiato i file con 666 aggiunte e 0 eliminazioni
  1. 12 0
      docs/architecture/index.md
  2. 654 0
      docs/architecture/ui-package-standards.md

+ 12 - 0
docs/architecture/index.md

@@ -55,6 +55,18 @@
     - [开发流程规范](./backend-module-package-standards.md#开发流程规范)
     - [错误处理规范](./backend-module-package-standards.md#错误处理规范)
     - [参考实现](./backend-module-package-standards.md#参考实现)
+  - [UI包开发规范](./ui-package-standards.md)
+    - [包结构规范](./ui-package-standards.md#包结构规范)
+    - [RPC客户端实现规范](./ui-package-standards.md#rpc客户端实现规范)
+    - [组件开发规范](./ui-package-standards.md#组件开发规范)
+    - [类型定义规范](./ui-package-standards.md#类型定义规范)
+    - [状态管理规范](./ui-package-standards.md#状态管理规范)
+    - [测试规范](./ui-package-standards.md#测试规范)
+    - [构建和发布规范](./ui-package-standards.md#构建和发布规范)
+    - [集成规范](./ui-package-standards.md#集成规范)
+    - [错误处理规范](./ui-package-standards.md#错误处理规范)
+    - [开发流程规范](./ui-package-standards.md#开发流程规范)
+    - [参考实现](./ui-package-standards.md#参考实现)
   - [安全集成](./security-integration.md)
     - [现有安全措施](./security-integration.md#现有安全措施)
     - [增强安全要求](./security-integration.md#增强安全要求)

+ 654 - 0
docs/architecture/ui-package-standards.md

@@ -0,0 +1,654 @@
+# UI包开发规范
+
+## 版本信息
+| 版本 | 日期 | 描述 | 作者 |
+|------|------|------|------|
+| 1.0 | 2025-12-03 | 基于史诗008经验创建UI包规范 | Claude Code |
+
+## 概述
+
+UI包是独立的前端模块包,用于封装特定业务功能的React组件、API客户端和状态管理逻辑。每个UI包作为一个独立的npm包发布,可以被主应用或其他UI包引用。
+
+## 包结构规范
+
+### 标准目录结构
+```text
+packages/<module-name>-ui/
+├── package.json                    # 包配置
+├── tsconfig.json                   # TypeScript配置
+├── vite.config.ts                  # Vite构建配置
+├── src/
+│   ├── index.ts                    # 主入口文件
+│   ├── components/                 # React组件
+│   │   ├── <ComponentName>.tsx     # 组件实现
+│   │   ├── <ComponentName>.test.tsx # 组件测试
+│   │   └── index.ts                # 组件导出
+│   ├── api/                        # API客户端
+│   │   ├── <module>Client.ts       # RPC客户端管理器
+│   │   └── index.ts                # API导出
+│   ├── hooks/                      # 自定义Hooks
+│   │   ├── use<HookName>.ts        # Hook实现
+│   │   └── index.ts                # Hook导出
+│   ├── types/                      # TypeScript类型定义
+│   │   ├── index.ts                # 类型导出
+│   │   └── <type>.ts               # 具体类型定义
+│   └── utils/                      # 工具函数
+│       └── index.ts                # 工具导出
+├── __tests__/                      # 测试文件
+│   └── integration/                # 集成测试
+└── README.md                       # 包文档
+```
+
+### package.json配置
+```json
+{
+  "name": "@d8d/<module-name>-ui",
+  "version": "1.0.0",
+  "description": "UI包描述",
+  "type": "module",
+  "main": "src/index.ts",
+  "types": "src/index.ts",
+  "exports": {
+    ".": {
+      "types": "./src/index.ts",
+      "import": "./src/index.ts",
+      "require": "./src/index.ts"
+    },
+    "./components": {
+      "types": "./src/components/index.ts",
+      "import": "./src/components/index.ts",
+      "require": "./src/components/index.ts"
+    },
+    "./hooks": {
+      "types": "./src/hooks/index.ts",
+      "import": "./src/hooks/index.ts",
+      "require": "./src/hooks/index.ts"
+    },
+    "./api": {
+      "types": "./src/api/index.ts",
+      "import": "./src/api/index.ts",
+      "require": "./src/api/index.ts"
+    }
+  },
+  "files": [
+    "src"
+  ],
+  "scripts": {
+    "build": "unbuild",
+    "dev": "tsc --watch",
+    "test": "vitest run",
+    "test:watch": "vitest",
+    "test:coverage": "vitest run --coverage",
+    "lint": "eslint src --ext .ts,.tsx",
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@d8d/shared-types": "workspace:*",
+    "@d8d/shared-ui-components": "workspace:*",
+    "@d8d/<module-name>-module": "workspace:*",
+    "@hookform/resolvers": "^5.2.1",
+    "@tanstack/react-query": "^5.90.9",
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "date-fns": "^4.1.0",
+    "hono": "^4.8.5",
+    "lucide-react": "^0.536.0",
+    "react": "^19.1.0",
+    "react-dom": "^19.1.0",
+    "react-hook-form": "^7.61.1",
+    "sonner": "^2.0.7",
+    "tailwind-merge": "^3.3.1",
+    "zod": "^4.0.15"
+  },
+  "devDependencies": {
+    "@testing-library/jest-dom": "^6.8.0",
+    "@testing-library/react": "^16.3.0",
+    "@testing-library/user-event": "^14.6.1",
+    "@types/node": "^22.10.2",
+    "@types/react": "^19.2.2",
+    "@types/react-dom": "^19.2.3",
+    "@typescript-eslint/eslint-plugin": "^8.18.1",
+    "@typescript-eslint/parser": "^8.18.1",
+    "eslint": "^9.17.0",
+    "jsdom": "^26.0.0",
+    "typescript": "^5.8.3",
+    "unbuild": "^3.4.0",
+    "vitest": "^4.0.9"
+  },
+  "peerDependencies": {
+    "react": "^19.1.0",
+    "react-dom": "^19.1.0"
+  }
+}
+```
+
+## RPC客户端实现规范
+
+### 客户端管理器模式
+每个UI包必须实现一个`ClientManager`类来管理RPC客户端生命周期:
+
+```typescript
+// src/api/<module>Client.ts
+import { <module>Routes } from '@d8d/<module-name>-module';
+import { rpcClient } from '@d8d/shared-ui-components/utils/hc'
+
+export class <Module>ClientManager {
+  private static instance: <Module>ClientManager;
+  private client: ReturnType<typeof rpcClient<typeof <module>Routes>> | null = null;
+
+  private constructor() {}
+
+  public static getInstance(): <Module>ClientManager {
+    if (!<Module>ClientManager.instance) {
+      <Module>ClientManager.instance = new <Module>ClientManager();
+    }
+    return <Module>ClientManager.instance;
+  }
+
+  // 初始化客户端
+  public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof <module>Routes>> {
+    return this.client = rpcClient<typeof <module>Routes>(baseUrl);
+  }
+
+  // 获取客户端实例
+  public get(): ReturnType<typeof rpcClient<typeof <module>Routes>> {
+    if (!this.client) {
+      return this.init()
+    }
+    return this.client;
+  }
+
+  // 重置客户端(用于测试或重新初始化)
+  public reset(): void {
+    this.client = null;
+  }
+}
+
+// 导出单例实例
+const <module>ClientManager = <Module>ClientManager.getInstance();
+
+// 导出默认客户端实例(延迟初始化)
+export const <module>Client = <module>ClientManager.get()
+
+export {
+  <module>ClientManager
+}
+```
+
+### API导出文件
+```typescript
+// src/api/index.ts
+export {
+  <Module>ClientManager,
+  <module>ClientManager,
+  <module>Client
+} from './<module>Client';
+```
+
+## 组件开发规范
+
+### 组件结构
+```typescript
+// src/components/<ComponentName>.tsx
+import React from 'react';
+import { useQuery, useMutation } from '@tanstack/react-query';
+import { <module>ClientManager } from '../api/<module>Client';
+import type { <Module>Response, <Module>SearchParams } from '../types';
+
+interface <ComponentName>Props {
+  // 组件属性定义
+}
+
+export const <ComponentName>: React.FC<<ComponentName>Props> = (props) => {
+  // 使用客户端管理器获取客户端实例
+  const client = <module>ClientManager.get();
+
+  // 数据查询示例
+  const { data, isLoading } = useQuery({
+    queryKey: ['<module>', searchParams],
+    queryFn: async () => {
+      const res = await client.index.$get({
+        query: {
+          page: searchParams.page,
+          pageSize: searchParams.limit,
+          keyword: searchParams.search
+        }
+      });
+      if (res.status !== 200) throw new Error('获取数据失败');
+      return await res.json();
+    }
+  });
+
+  // 数据变更示例
+  const mutation = useMutation({
+    mutationFn: async (data: CreateRequest) => {
+      const res = await client.index.$post({ json: data });
+      if (res.status !== 201) throw new Error('创建失败');
+      return await res.json();
+    },
+    onSuccess: () => {
+      // 成功处理
+    },
+    onError: (error) => {
+      // 错误处理
+    }
+  });
+
+  return (
+    // 组件JSX
+  );
+};
+
+export default <ComponentName>;
+```
+
+### 组件导出
+```typescript
+// src/components/index.ts
+export { <ComponentName> } from './<ComponentName>';
+export type { <ComponentName>Props } from './<ComponentName>';
+```
+
+## 类型定义规范
+
+### 类型文件结构
+```typescript
+// src/types/index.ts
+import type { InferResponseType, InferRequestType } from 'hono';
+import type { <module>Routes } from '@d8d/<module-name>-module';
+import { <module>Client } from '../api/<module>Client';
+
+// 使用导出的client进行类型推导
+export type <Module>Response = InferResponseType<typeof <module>Client.index.$get>;
+export type Create<Module>Request = InferRequestType<typeof <module>Client.index.$post>;
+export type Update<Module>Request = InferRequestType<typeof <module>Client[':id']['$put']>;
+
+// 搜索参数类型
+export interface <Module>SearchParams {
+  page: number;
+  limit: number;
+  search?: string;
+  // 其他搜索参数
+}
+
+// 组件属性类型
+export interface <ComponentName>Props {
+  // 属性定义
+}
+```
+
+## 状态管理规范
+
+### React Query配置
+```typescript
+// 在组件中使用React Query进行状态管理
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+
+// 查询示例
+const { data, isLoading, error, refetch } = useQuery({
+  queryKey: ['<module>', id],
+  queryFn: async () => {
+    const res = await client[':id'].$get({ param: { id } });
+    if (res.status !== 200) throw new Error('获取详情失败');
+    return await res.json();
+  },
+  enabled: !!id, // 条件查询
+});
+
+// 变更示例
+const queryClient = useQueryClient();
+const mutation = useMutation({
+  mutationFn: async (data: UpdateRequest) => {
+    const res = await client[':id']['$put']({
+      param: { id },
+      json: data
+    });
+    if (res.status !== 200) throw new Error('更新失败');
+    return await res.json();
+  },
+  onSuccess: () => {
+    // 使相关查询失效
+    queryClient.invalidateQueries({ queryKey: ['<module>'] });
+    queryClient.invalidateQueries({ queryKey: ['<module>', id] });
+  }
+});
+```
+
+## 测试规范
+
+### 单元测试
+```typescript
+// __tests__/<ComponentName>.test.tsx
+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 { <ComponentName> } from '../src/components/<ComponentName>';
+import { <module>ClientManager } from '../src/api/<module>Client';
+
+// Mock RPC客户端
+vi.mock('../src/api/<module>Client', () => ({
+  <module>ClientManager: {
+    getInstance: vi.fn(() => ({
+      get: vi.fn(() => ({
+        index: {
+          $get: vi.fn(() => Promise.resolve({
+            status: 200,
+            json: () => Promise.resolve({ data: [], pagination: { total: 0 } })
+          }))
+        }
+      }))
+    }))
+  }
+}));
+
+describe('<ComponentName>', () => {
+  const queryClient = new QueryClient({
+    defaultOptions: {
+      queries: {
+        retry: false,
+      },
+    },
+  });
+
+  it('渲染组件', () => {
+    render(
+      <QueryClientProvider client={queryClient}>
+        <<ComponentName> />
+      </QueryClientProvider>
+    );
+    expect(screen.getByText('组件标题')).toBeInTheDocument();
+  });
+});
+```
+
+### 集成测试
+```typescript
+// __tests__/integration/<ComponentName>.integration.test.tsx
+import { describe, it, expect, beforeAll, afterAll } from 'vitest';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { http, HttpResponse } from 'msw';
+import { setupServer } from 'msw/node';
+import { <ComponentName> } from '../../src/components/<ComponentName>';
+
+// 设置Mock Server
+const server = setupServer(
+  http.get('/api/<module>', () => {
+    return HttpResponse.json({
+      data: [{ id: 1, name: '测试数据' }],
+      pagination: { total: 1 }
+    });
+  })
+);
+
+describe('<ComponentName>集成测试', () => {
+  beforeAll(() => server.listen());
+  afterAll(() => server.close());
+  afterEach(() => server.resetHandlers());
+
+  it('从API加载数据并显示', async () => {
+    const queryClient = new QueryClient();
+
+    render(
+      <QueryClientProvider client={queryClient}>
+        <<ComponentName> />
+      </QueryClientProvider>
+    );
+
+    await waitFor(() => {
+      expect(screen.getByText('测试数据')).toBeInTheDocument();
+    });
+  });
+});
+```
+
+## 构建和发布规范
+
+### 构建配置
+UI包在PNPM工作空间中直接使用TypeScript源码,不需要构建步骤。主入口直接指向`src/index.ts`,TypeScript会自动处理类型检查和编译。
+
+### TypeScript配置
+```json
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "lib": ["DOM", "DOM.Iterable", "ES2020"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx",
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "include": ["src"]
+}
+```
+
+### 构建脚本说明
+- `"build": "unbuild"`: 使用unbuild进行构建(可选,用于生产环境发布)
+- `"dev": "tsc --watch"`: 开发模式下监听TypeScript文件变化
+- `"typecheck": "tsc --noEmit"`: 类型检查,不生成输出文件
+
+## 集成规范
+
+### 在主应用中使用
+```typescript
+// 主应用中的使用示例
+import { <ComponentName> } from '@d8d/<module-name>-ui';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+const queryClient = new QueryClient();
+
+function App() {
+  return (
+    <QueryClientProvider client={queryClient}>
+      <<ComponentName> />
+    </QueryClientProvider>
+  );
+}
+```
+
+### 环境配置
+UI包应该支持以下环境变量配置:
+- `VITE_API_BASE_URL`: API基础URL(默认为`/`)
+- `VITE_APP_ENV`: 应用环境(development/production)
+
+## 错误处理规范
+
+### API错误处理
+```typescript
+// 统一的错误处理模式
+try {
+  const res = await client.index.$get({ query: params });
+  if (res.status !== 200) {
+    const errorData = await res.json();
+    throw new Error(errorData.message || '请求失败');
+  }
+  return await res.json();
+} catch (error) {
+  if (error instanceof Error) {
+    toast.error(error.message);
+  } else {
+    toast.error('未知错误');
+  }
+  throw error;
+}
+```
+
+### 组件错误边界
+```typescript
+import React, { Component, ErrorInfo, ReactNode } from 'react';
+
+interface Props {
+  children: ReactNode;
+  fallback?: ReactNode;
+}
+
+interface State {
+  hasError: boolean;
+  error?: Error;
+}
+
+export class ErrorBoundary extends Component<Props, State> {
+  public state: State = {
+    hasError: false
+  };
+
+  public static getDerivedStateFromError(error: Error): State {
+    return { hasError: true, error };
+  }
+
+  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+    console.error('UI组件错误:', error, errorInfo);
+  }
+
+  public render() {
+    if (this.state.hasError) {
+      return this.props.fallback || <div>组件加载失败</div>;
+    }
+
+    return this.props.children;
+  }
+}
+```
+
+## 开发流程规范
+
+### 1. 创建新UI包
+```bash
+# 复制模板
+cp -r packages/template-ui packages/<module-name>-ui
+
+# 更新包名和依赖
+cd packages/<module-name>-ui
+# 修改package.json中的name和dependencies
+```
+
+### 2. 开发组件
+```bash
+# 启动开发模式
+pnpm dev
+
+# 运行测试
+pnpm test
+
+# 类型检查
+pnpm typecheck
+
+# 代码检查
+pnpm lint
+```
+
+### 3. 构建和发布
+```bash
+# 构建包
+pnpm build
+
+# 本地测试
+# 在主应用中引用本地构建的包进行测试
+
+# 发布到npm(由CI/CD流程处理)
+```
+
+## 参考实现
+
+### 现有UI包参考
+- **广告管理UI包**: `packages/advertisement-management-ui`
+  - 组件: `src/components/AdvertisementManagement.tsx`
+  - API客户端: `src/api/advertisementClient.ts`
+  - 类型定义: `src/types/index.ts`
+
+- **区域管理UI包**: `packages/area-management-ui`
+  - 组件: `src/components/AreaManagement.tsx`
+  - API客户端: `src/api/areaClient.ts`
+
+### 关键代码片段
+```typescript
+// RPC客户端管理器实现参考
+import { advertisementRoutes } from '@d8d/advertisements-module';
+import { rpcClient } from '@d8d/shared-ui-components/utils/hc'
+
+export class AdvertisementClientManager {
+  private static instance: AdvertisementClientManager;
+  private client: ReturnType<typeof rpcClient<typeof advertisementRoutes>> | null = null;
+
+  public static getInstance(): AdvertisementClientManager {
+    if (!AdvertisementClientManager.instance) {
+      AdvertisementClientManager.instance = new AdvertisementClientManager();
+    }
+    return AdvertisementClientManager.instance;
+  }
+
+  public get(): ReturnType<typeof rpcClient<typeof advertisementRoutes>> {
+    if (!this.client) {
+      return this.init()
+    }
+    return this.client;
+  }
+}
+```
+
+## 版本管理
+
+### 版本号规则
+- **主版本号**: 不兼容的API变更
+- **次版本号**: 向后兼容的功能性新增
+- **修订号**: 向后兼容的问题修正
+
+### 变更日志
+每个版本更新必须包含变更日志,记录:
+1. 新增功能
+2. 问题修复
+3. 破坏性变更
+4. 依赖更新
+
+## 性能优化
+
+### 代码分割
+```typescript
+// 使用React.lazy进行代码分割
+const LazyComponent = React.lazy(() => import('./HeavyComponent'));
+
+function App() {
+  return (
+    <React.Suspense fallback={<LoadingSpinner />}>
+      <LazyComponent />
+    </React.Suspense>
+  );
+}
+```
+
+### 组件优化
+- 使用`React.memo`避免不必要的重渲染
+- 使用`useMemo`和`useCallback`缓存计算和函数
+- 实现虚拟列表处理大量数据
+
+## 安全规范
+
+### 输入验证
+- 所有用户输入必须在前端进行验证
+- 使用Zod schema进行表单验证
+- 敏感数据不存储在客户端状态中
+
+### XSS防护
+- 使用React的自动转义机制
+- 避免使用`dangerouslySetInnerHTML`
+- 对动态内容进行清理
+
+## 文档要求
+
+每个UI包必须包含:
+1. **README.md**: 包概述、安装、使用示例
+2. **API文档**: 组件Props和API接口说明
+3. **示例代码**: 完整的使用示例
+4. **变更日志**: 版本更新记录
+
+---
+
+*本规范基于史诗008(AllIn UI模块移植)的经验总结,确保UI包开发的一致性和可维护性。*