|
|
@@ -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包开发的一致性和可维护性。*
|