# UI包开发规范

## 版本信息
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 1.1 | 2025-12-04 | 添加Radix UI组件测试环境修复规范（基于故事008.007经验） | James |
| 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>;
```

### 表单组件模式规范（基于史诗008经验）

#### 1. 条件渲染独立Form组件
**规范**：当组件需要支持创建和编辑两种表单模式时，必须使用条件渲染两个独立的Form组件，避免在单个Form组件上动态切换props。

```typescript
// ✅ 正确：条件渲染两个独立的Form组件
{isCreateForm ? (
  <Form {...createForm}>
    {/* 创建表单内容 */}
  </Form>
) : (
  <Form {...updateForm}>
    {/* 编辑表单内容 */}
  </Form>
)}

// ❌ 错误：在单个Form组件上动态切换props（可能导致类型不兼容）
<Form {...(isCreateForm ? createForm : updateForm)}>
  {/* 表单内容 */}
</Form>
```

#### 2. 参考现有模式
**规范**：参考PlatformManagement.tsx的表单处理模式，确保一致性。

#### 3. 表单状态管理
**规范**：创建表单和编辑表单分别使用独立的useForm实例，避免状态混淆。

```typescript
const createForm = useForm({
  resolver: zodResolver(CreateSchema),
  defaultValues: createDefaultValues
});

const updateForm = useForm({
  resolver: zodResolver(UpdateSchema),
  defaultValues: updateDefaultValues
});
```

### 组件导出
```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 {
  // 属性定义
}
```

### 类型推断最佳实践（基于史诗008经验）

#### 1. 使用RPC推断类型
**规范**：必须使用RPC推断类型，而不是直接导入schema类型，避免Date/string类型不匹配问题。

```typescript
// ✅ 正确：使用RPC推断类型（推荐）
export type <Module>ListItem = <Module>ListResponse['data'][0];

// ❌ 错误：直接导入schema类型（可能导致Date/string不匹配）
import type { <Module> } from '@d8d/<module-name>-module/schemas';
```

#### 2. 参考现有UI包模式
**规范**：参考现有UI包（如广告管理UI）的类型定义模式，确保一致性。

```typescript
// 广告管理UI模式参考
export type AdvertisementResponse = InferResponseType<typeof advertisementClient.index.$get, 200>['data'][0];
```

#### 3. 处理混合路由模式
**规范**：当模块使用自定义路由与CRUD路由混合时，必须通过查看后端模块集成测试确认正确的路由结构。

```typescript
// 示例：渠道模块的getChannel路由结构
// 根据后台集成测试，路由结构是 getChannel[':id'].$get
export type ChannelDetailResponse = InferResponseType<typeof channelClient.getChannel[':id']['$get'], 200>;
```

#### 4. 避免复杂的条件类型
**规范**：使用简单的类型索引而不是复杂的条件类型，提高代码可读性。

```typescript
// ✅ 正确：简单类型索引
export type <Module>ListItem = <Module>ListResponse['data'][0];

// ❌ 避免：复杂的条件类型
export type <Module>Item = <Module>ListResponse extends { data: infer T } ? T extends Array<infer U> ? U : never : never;
```

## 状态管理规范

### 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] });
  }
});
```

## 测试规范

### 测试文件结构
```text
packages/<module-name>-ui/
├── tests/
│   ├── integration/                    # 集成测试
│   │   └── <component-name>.integration.test.tsx
│   ├── unit/                          # 单元测试
│   │   └── <hook-name>.test.tsx
│   └── components/                    # 组件测试
│       └── <ComponentName>.test.tsx
```

### Mock响应工具函数
```typescript
// 在测试文件中使用的标准mock响应函数
const createMockResponse = (status: number, data?: any) => ({
  status,
  ok: status >= 200 && status < 300,
  body: null,
  bodyUsed: false,
  statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
  headers: new Headers(),
  url: '',
  redirected: false,
  type: 'basic' as ResponseType,
  json: async () => data || {},
  text: async () => '',
  blob: async () => new Blob(),
  arrayBuffer: async () => new ArrayBuffer(0),
  formData: async () => new FormData(),
  clone: function() { return this; }
});
```

### 测试选择器优化规范（基于史诗008经验）

#### 1. 优先使用test ID
**规范**：必须为关键交互元素添加`data-testid`属性，避免使用文本查找导致的测试冲突。

```typescript
// 在组件中添加test ID
<DialogTitle data-testid="create-<module>-modal-title">创建<Module></DialogTitle>
<Button data-testid="search-button">搜索</Button>

// 在测试中使用test ID
const modalTitle = screen.getByTestId('create-<module>-modal-title');
const searchButton = screen.getByTestId('search-button');
```

#### 2. 避免文本选择器冲突
**规范**：当页面中有多个相同文本元素时，必须使用test ID代替`getByText()`。

```typescript
// ❌ 错误：可能找到错误的元素
const createButton = screen.getByText('创建');

// ✅ 正确：使用唯一的test ID
const createButton = screen.getByTestId('create-<module>-button');
```

#### 3. 命名约定
**规范**：test ID命名使用kebab-case格式：`{action}-{element}-{purpose}`。

```typescript
// 示例命名
data-testid="create-channel-modal-title"
data-testid="edit-channel-button-1"
data-testid="delete-confirm-dialog-title"
```

#### 4. Radix UI组件测试环境修复（基于故事008.007经验）
**规范**：在测试环境中使用Radix UI组件（特别是Select、DropdownMenu等）时，必须添加必要的DOM API mock。

**问题**：Radix UI组件在测试环境中可能缺少某些DOM API（如`scrollIntoView`），导致测试失败。

**解决方案**：在测试setup文件中添加必要的mock。

```typescript
// tests/setup.ts
import '@testing-library/jest-dom';
import { vi } from 'vitest';

// Mock sonner
vi.mock('sonner', () => ({
  toast: {
    success: vi.fn(),
    error: vi.fn(),
    warning: vi.fn(),
    info: vi.fn()
  }
}));

// Mock scrollIntoView for Radix UI components
Element.prototype.scrollIntoView = vi.fn();
```

**Select组件test ID规范**：为Radix UI Select组件的选项添加test ID，避免文本查找冲突。

```typescript
// 在组件中为SelectItem添加test ID
<SelectContent>
  <SelectItem value="all" data-testid="order-status-option-all">全部状态</SelectItem>
  <SelectItem value={OrderStatus.DRAFT} data-testid="order-status-option-draft">草稿</SelectItem>
  <SelectItem value={OrderStatus.CONFIRMED} data-testid="order-status-option-confirmed">已确认</SelectItem>
</SelectContent>

// 在测试中使用test ID查找Select选项
expect(screen.getByTestId('order-status-option-all')).toBeInTheDocument();
expect(screen.getByTestId('order-status-option-draft')).toBeInTheDocument();
expect(screen.getByTestId('order-status-option-confirmed')).toBeInTheDocument();
```

### 组件集成测试
```typescript
// tests/integration/<component-name>.integration.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, <module>Client } from '../../src/api/<module>Client';

// Mock RPC客户端
vi.mock('../../src/api/<module>Client', () => {
  const mock<Module>Client = {
    index: {
      $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
        data: [
          {
            id: 1,
            name: '测试数据',
            // 其他字段
          }
        ],
        pagination: {
          page: 1,
          pageSize: 10,
          total: 1,
          totalPages: 1
        }
      }))),
      $post: vi.fn(() => Promise.resolve(createMockResponse(201, {
        id: 2,
        name: '新建数据'
      }))),
    },
    ':id': {
      $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
        id: 1,
        name: '测试数据详情'
      }))),
      $put: vi.fn(() => Promise.resolve(createMockResponse(200, {
        id: 1,
        name: '更新后的数据'
      }))),
      $delete: vi.fn(() => Promise.resolve(createMockResponse(204))),
    },
  };

  const mock<Module>ClientManager = {
    get: vi.fn(() => mock<Module>Client),
  };

  return {
    <module>ClientManager: mock<Module>ClientManager,
    <module>Client: mock<Module>Client,
  };
});

// Mock其他依赖
vi.mock('sonner', () => ({
  toast: {
    success: vi.fn(),
    error: vi.fn(),
    info: vi.fn(),
    warning: vi.fn(),
  }
}));

describe('<ComponentName>集成测试', () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });

  beforeEach(() => {
    vi.clearAllMocks();
    queryClient.clear();
  });

  it('渲染组件并加载数据', async () => {
    render(
      <QueryClientProvider client={queryClient}>
        <<ComponentName> />
      </QueryClientProvider>
    );

    await waitFor(() => {
      expect(screen.getByText('测试数据')).toBeInTheDocument();
    });
  });

  it('创建新数据', async () => {
    render(
      <QueryClientProvider client={queryClient}>
        <<ComponentName> />
      </QueryClientProvider>
    );

    const createButton = screen.getByText('新建');
    fireEvent.click(createButton);

    const nameInput = screen.getByLabelText('名称');
    fireEvent.change(nameInput, { target: { value: '新数据' } });

    const submitButton = screen.getByText('提交');
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(<module>Client.index.$post).toHaveBeenCalledWith({
        json: expect.objectContaining({ name: '新数据' })
      });
    });
  });
});
```

### Hook单元测试
```typescript
// tests/unit/use<HookName>.test.tsx
import React from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { use<HookName> } from '../../src/hooks/use<HookName>';
import { <module>Client } from '../../src/api/<module>Client';

// Mock RPC客户端
vi.mock('../../src/api/<module>Client', () => {
  const mock<Module>Client = {
    index: {
      $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
        data: [{ id: 1, name: '测试数据' }],
        pagination: { total: 1 }
      }))),
    },
  };

  return {
    <module>Client: mock<Module>Client,
  };
});

describe('use<HookName>', () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });

  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );

  beforeEach(() => {
    vi.clearAllMocks();
    queryClient.clear();
  });

  it('加载数据', async () => {
    const { result } = renderHook(() => use<HookName>({ page: 1, limit: 10 }), { wrapper });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.data).toEqual([
      { id: 1, name: '测试数据' }
    ]);
    expect(<module>Client.index.$get).toHaveBeenCalledWith({
      query: { page: 1, pageSize: 10 }
    });
  });
});
```

### 组件单元测试
```typescript
// tests/components/<ComponentName>.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { <ComponentName> } from '../../src/components/<ComponentName>';

// Mock子组件
vi.mock('../ChildComponent', () => ({
  ChildComponent: () => <div>Mock子组件</div>
}));

describe('<ComponentName>', () => {
  it('渲染组件', () => {
    render(<<ComponentName> />);
    expect(screen.getByText('组件标题')).toBeInTheDocument();
  });

  it('显示传入的属性', () => {
    render(<<ComponentName> name="测试名称" />);
    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. 开发前检查清单（基于史诗008经验）
在开始UI包开发前，必须完成以下检查：

#### 1.1 API路径映射验证
**规范**：必须验证故事中的API路径映射与实际后端路由定义的一致性。

```bash
# 检查后端模块的路由定义
cat allin-packages/<module-name>-module/src/routes/*.routes.ts

# 查看后端集成测试确认路由结构
cat allin-packages/<module-name>-module/tests/integration/*.test.ts
```

#### 1.2 路由结构确认
**规范**：必须通过查看后台模块集成测试确认正确的路由结构，特别是混合路由模式。

#### 1.3 参考现有UI包
**规范**：必须参考现有UI包（如广告管理UI、平台管理UI）的实现模式。

### 2. API调用一致性规范
**规范**：必须根据实际路由名称修正API调用，确保前端API调用与后端路由定义完全一致。

```typescript
// ❌ 错误：使用故事中描述但实际不存在的路由
const res = await client.index.$get(...);
const res = await client.channels.$get(...);

// ✅ 正确：使用实际路由名称
const res = await client.getAll<Module>s.$get(...);
const res = await client.search<Module>s.$get(...);
```

### 3. 创建新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包开发的一致性和可维护性。*