2
0

epic-008-allin-ui-modules-transplant.md 43 KB

史诗008 - Allin System UI模块移植到独立UI包

史诗目标

在史诗007完成Allin系统后端模块移植的基础上,将allin_system-master/client目录中的UI模块移植到allin-packages目录下,作为独立的UI包,遵循项目现有的UI包结构和命名规范,实现UI模块的现代化重构和标准化管理。

史诗描述

现有系统上下文

  • 当前相关功能:allin_system-master/client是一个基于Next.js + Ant Design的前端系统,根据分析有以下UI模块:

    1. channel管理页面 - 渠道管理界面
    2. company管理页面 - 公司管理界面
    3. disability管理页面 - 残疾人管理界面
    4. disability_person管理页面 - 残疾人个人管理界面
    5. order管理页面 - 订单管理界面
    6. platform管理页面 - 平台管理界面
    7. salary管理页面 - 薪资管理界面 :region管理页面已有@d8d/area-management-ui包提供,直接复用即可,无需移植
  • 技术栈:Next.js、React 19、Ant Design 5、Jotai、Tailwind CSS、自定义fetch API

  • 集成点:通过自定义API客户端与后端API集成,使用Jotai进行状态管理

增强详情

  • 新增/变更内容:将7个现有UI模块从allin_system-master/client移植到allin-packages目录下的独立UI包
  • 集成方式:每个UI模块将重构为独立的npm包,遵循@d8d/allin-命名规范,通过workspace依赖管理
  • 技术栈转换:从Ant Design + Jotai转换为@d8d/shared-ui-components + React Query + Hono RPC
  • 成功标准
    1. 7个UI模块成功移植为独立UI包
    2. 保持原有功能完整性
    3. 遵循现有UI包结构模式
    4. 通过组件测试和集成测试验证
    5. 与史诗007移植的后端模块无缝集成
    6. 区域管理功能复用现有@d8d/area-management-ui

参考文档

执行本史诗时,必须参考以下架构文档:

特别强调:所有UI包开发必须严格遵循UI包开发规范,包括包结构、RPC客户端模式、组件开发、测试规范等。

模块分析结果

1. 模块依赖关系分析

根据对每个模块页面的分析,依赖关系如下:

  1. channel管理页面 (app/admin/dashboard/channel/page.tsx)

    • 依赖:Ant Design组件、自定义API客户端、Jotai状态管理
    • 功能:渠道CRUD管理、搜索、分页
    • 复杂度:中等
  2. company管理页面 (app/admin/dashboard/company/page.tsx)

    • 依赖:Ant Design组件、自定义API客户端、Jotai状态管理
    • 功能:公司CRUD管理、平台关联、搜索、分页
    • 复杂度:中等(依赖platform数据)
  3. disability管理页面 (app/admin/dashboard/disability/page.tsx)

    • 依赖:Ant Design组件、自定义API客户端、Jotai状态管理
    • 功能:残疾人信息管理、搜索、分页
    • 复杂度:中等
  4. disability_person管理页面 (app/admin/dashboard/disability_person/)

    • 依赖:Ant Design组件、自定义API服务、复杂表单
    • 功能:残疾人个人详细信息管理、照片上传、备注管理
    • 复杂度:高(多个子组件、复杂表单)
  5. order管理页面 (app/admin/dashboard/order/)

    • 依赖:Ant Design组件、自定义API服务、复杂业务逻辑
    • 功能:订单管理、人员分配、资产关联
    • 复杂度:高(复杂业务逻辑、多实体关联)
  6. platform管理页面 (app/admin/dashboard/platform/page.tsx)

    • 依赖:Ant Design组件、自定义API客户端、Jotai状态管理
    • 功能:平台CRUD管理、搜索、分页
    • 复杂度:低
  7. region管理页面 (app/admin/dashboard/region/page.tsx)

    • 依赖:Ant Design组件、自定义API客户端、树形结构
    • 功能:区域树形管理、CRUD操作
    • 复杂度:中等(树形结构)
    • 重要说明:已有@d8d/area-management-ui包提供相同功能,直接复用即可
  8. salary管理页面 (app/admin/dashboard/salary/page.tsx)

    • 依赖:Ant Design组件、自定义API客户端、复杂表单验证
    • 功能:薪资水平管理、区域关联、数值计算
    • 复杂度:中等(复杂表单验证)

2. 模块到独立UI包的映射方案

基于现有项目UI包命名规范,并考虑这是Allin系统专属模块,制定以下映射方案:

原页面名 独立UI包名 目录名 对应后端模块 说明
channel @d8d/allin-channel-management-ui channel-management-ui @d8d/allin-channel-module 渠道管理UI
company @d8d/allin-company-management-ui company-management-ui @d8d/allin-company-module 公司管理UI
disability @d8d/allin-disability-management-ui disability-management-ui @d8d/allin-disability-module 残疾人管理UI
disability_person @d8d/allin-disability-person-management-ui disability-person-management-ui @d8d/allin-disability-module 残疾人个人管理UI
order @d8d/allin-order-management-ui order-management-ui @d8d/allin-order-module 订单管理UI
platform @d8d/allin-platform-management-ui platform-management-ui @d8d/allin-platform-module 平台管理UI
region 复用现有@d8d/area-management-ui - @d8d/geo-areas 区域管理UI(已有现成包)
salary @d8d/allin-salary-management-ui salary-management-ui @d8d/allin-salary-module 薪资管理UI

命名原则

  1. 包名前缀:使用@d8d/allin-前缀,明确表明是Allin系统专属包
  2. 包名后缀:使用-management-ui后缀,遵循现有UI包命名规范
  3. 目录名:使用{模块名}-management-ui格式,便于在allin-packages目录中管理
  4. 非多租户:不使用-mt后缀(本次移植为非多租户版本)

3. 目录结构和包规范

目录结构决策

由于这些UI模块是allin_system-master项目独有的业务模块(非通用模块),将在allin-packages目录下创建:

项目根目录/
├── allin-packages/                    # Allin系统专属包目录
│   ├── channel-management-ui/         # 渠道管理UI
│   ├── company-management-ui/         # 公司管理UI
│   ├── disability-management-ui/      # 残疾人管理UI
│   ├── disability-person-management-ui/ # 残疾人个人管理UI
│   ├── order-management-ui/           # 订单管理UI
│   ├── platform-management-ui/        # 平台管理UI
│   # 区域管理UI已有`@d8d/area-management-ui`,无需创建新包
│   ├── salary-management-ui/          # 薪资管理UI
│   ├── channel-module/                # 后端模块(已存在)
│   ├── company-module/                # 后端模块(已存在)
│   ├── disability-module/             # 后端模块(已存在)
│   ├── platform-module/               # 后端模块(已存在)
│   ├── salary-module/                 # 后端模块(已存在)
│   └── enums/                         # 枚举常量包(已存在)
├── packages/                          # 现有通用包目录(保持不变)
└── allin_system-master/               # 原始代码目录(移植源)

UI包结构规范

参考现有advertisement-management-ui结构,每个独立UI包应包含:

allin-packages/{module-name}-management-ui/
├── package.json          # 包配置,workspace依赖管理
├── tsconfig.json        # TypeScript配置
├── vitest.config.ts     # 测试配置
├── src/
│   ├── index.ts         # 主导出文件
│   ├── components/      # React组件
│   │   ├── index.ts     # 组件导出
│   │   ├── {Module}Table.tsx  # 数据表格组件
│   │   ├── {Module}Form.tsx   # 表单组件
│   │   ├── {Module}Modal.tsx  # 模态框组件
│   │   └── {Module}Filter.tsx # 筛选组件
│   ├── hooks/           # 自定义Hook
│   │   ├── index.ts
│   │   ├── use{Module}Query.ts  # 数据查询Hook
│   │   └── use{Module}Mutation.ts # 数据变更Hook
│   ├── api/             # API客户端
│   │   ├── index.ts
│   │   ├── client.ts    # Hono RPC客户端
│   │   └── types.ts     # API类型定义
│   └── utils/           # 工具函数
│       ├── index.ts
│       └── formatters.ts
├── tests/               # 测试文件
│   ├── components/      # 组件测试
│   └── integration/     # 集成测试
└── dist/                # 构建输出

关键配置要求

  1. package.json中设置"type": "module"
  2. 主入口为src/index.ts
  3. 使用workspace依赖:"@d8d/allin-{module}-module": "workspace:*"(对应后端模块)
  4. 包名使用@d8d/allin-前缀,如@d8d/allin-channel-management-ui
  5. 导出必要的组件和Hook

4. 技术栈差异分析与移植方案

技术栈对比分析

方面 当前项目(目标) allin_system-master(源) 差异程度
UI框架 React 19 + @d8d/shared-ui-components React 19 + Ant Design 5 重大
状态管理 React Query + React状态(useState/useContext) Jotai 中等
表单处理 React Hook Form + Zod Ant Design Form 重大
API客户端 Hono RPC (rpcClient + ClientManager模式) 自定义fetch API 重大
样式方案 Tailwind CSS + shadcn/ui Ant Design + Tailwind CSS 中等
组件库 @d8d/shared-ui-components组件 Ant Design组件 重大
构建工具 unbuild Next.js构建 重大
测试框架 Vitest + Testing Library 无测试 新增

关键依赖对比

  • 当前项目@d8d/shared-ui-components@tanstack/react-queryreact-hook-formzodhonoshadcn/ui
  • allin_systemantdjotai、自定义API客户端、dayjs

移植调整方案

A. 架构转换策略
  1. Ant Design组件 → @d8d/shared-ui-components组件:重写所有UI组件,遵循UI包开发规范
  2. Jotai状态 → React Query + React状态(useState/useContext):重构状态管理,遵循UI包开发规范
  3. Ant Design Form → React Hook Form + Zod:转换表单逻辑
  4. 自定义fetch API → Hono RPC (rpcClient + ClientManager模式):重构API客户端,使用项目现有的rpcClient工具和ClientManager单例模式,遵循UI包开发规范
  5. Next.js页面 → React组件库:将页面转换为可复用组件,遵循UI包开发规范
B. 组件层转换示例
// 原Ant Design表格组件
import { Table, Button, Space } from 'antd';

const ChannelTable = ({ data, loading, onEdit, onDelete }) => {
  const columns = [
    { title: '渠道名称', dataIndex: 'channel_name', key: 'channel_name' },
    { title: '操作', key: 'action', render: (_, record) => (
      <Space>
        <Button onClick={() => onEdit(record)}>编辑</Button>
        <Button danger onClick={() => onDelete(record)}>删除</Button>
      </Space>
    )}
  ];

  return <Table dataSource={data} columns={columns} loading={loading} />;
};

// 转换后@d8d/shared-ui-components表格组件(使用rpcClient + ClientManager模式)
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
import { Button } from '@d8d/shared-ui-components/components/ui/button';
import { useQuery } from '@tanstack/react-query';
import { channelClientManager } from './api/channelClient';

const ChannelTable = () => {
  const { data: channels, isLoading } = useQuery({
    queryKey: ['channels'],
    queryFn: async () => {
      const res = await channelClientManager.get().channel.getChannelList.$get();
      if (res.status !== 200) throw new Error('获取渠道列表失败');
      return await res.json();
    }
  });

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>渠道名称</TableHead>
          <TableHead>操作</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {channels?.map((channel) => (
          <TableRow key={channel.id}>
            <TableCell>{channel.channelName}</TableCell>
            <TableCell>
              <Button variant="outline" onClick={() => onEdit(channel)}>编辑</Button>
              <Button variant="destructive" onClick={() => onDelete(channel)}>删除</Button>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
};
C. 表单转换示例
// 原Ant Design表单
import { Form, Input, Button } from 'antd';

const ChannelForm = ({ initialValues, onSubmit }) => {
  const [form] = Form.useForm();

  return (
    <Form form={form} initialValues={initialValues} onFinish={onSubmit}>
      <Form.Item name="channel_name" label="渠道名称" rules={[{ required: true }]}>
        <Input />
      </Form.Item>
      <Form.Item>
        <Button type="primary" htmlType="submit">提交</Button>
      </Form.Item>
    </Form>
  );
};

// 转换后React Hook Form + Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@d8d/shared-ui-components/components/ui/form';
import { Input } from '@d8d/shared-ui-components/components/ui/input';
import { Button } from '@d8d/shared-ui-components/components/ui/button';

const channelSchema = z.object({
  channelName: z.string().min(1, '渠道名称不能为空')
});

const ChannelForm = ({ initialValues, onSubmit }) => {
  const form = useForm({
    resolver: zodResolver(channelSchema),
    defaultValues: initialValues
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField
          control={form.control}
          name="channelName"
          render={({ field }) => (
            <FormItem>
              <FormLabel>渠道名称</FormLabel>
              <FormControl>
                <Input {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">提交</Button>
      </form>
    </Form>
  );
};
D. API客户端转换示例
// 原自定义fetch API
const api = {
  getChannels: async () => {
    const response = await fetch('/api/channel/getChannelList');
    return response.json();
  },
  createChannel: async (data) => {
    const response = await fetch('/api/channel/createChannel', {
      method: 'POST',
      body: JSON.stringify(data)
    });
    return response.json();
  }
};

// 转换后Hono RPC客户端(遵循现有项目模式)
import { channelRoutes } from '@d8d/allin-channel-module';
import { rpcClient } from '@d8d/shared-ui-components/utils/hc'

class ChannelClientManager {
  private static instance: ChannelClientManager;
  private client: ReturnType<typeof rpcClient<typeof channelRoutes>> | null = null;

  private constructor() {}

  public static getInstance(): ChannelClientManager {
    if (!ChannelClientManager.instance) {
      ChannelClientManager.instance = new ChannelClientManager();
    }
    return ChannelClientManager.instance;
  }

  // 初始化客户端
  public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof channelRoutes>> {
    return this.client = rpcClient<typeof channelRoutes>(baseUrl);
  }

  // 获取客户端实例
  public get(): ReturnType<typeof rpcClient<typeof channelRoutes>> {
    if (!this.client) {
      return this.init()
    }
    return this.client;
  }

  // 重置客户端(用于测试或重新初始化)
  public reset(): void {
    this.client = null;
  }
}

// 导出单例实例
const channelClientManager = ChannelClientManager.getInstance();

// 导出默认客户端实例(延迟初始化)
export const channelClient = channelClientManager.get()

// 在React组件中的使用示例
import { channelClientManager } from './api/channelClient';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 类型定义(使用导出的channelClient进行类型推导)
import type { InferResponseType, InferRequestType } from 'hono/client';
import { channelClient } from './api/channelClient';

type ChannelResponse = InferResponseType<typeof channelClient.channel.getChannelList.$get, 200>['data'][0];
type CreateChannelRequest = InferRequestType<typeof channelClient.channel.createChannel.$post>['json'];

// React组件中的实际调用
const ChannelManagement: React.FC = () => {
  const queryClient = useQueryClient();

  // 查询数据 - 使用channelClientManager.get()获取客户端实例
  const { data: channels, isLoading } = useQuery({
    queryKey: ['channels'],
    queryFn: async () => {
      const res = await channelClientManager.get().channel.getChannelList.$get();
      if (res.status !== 200) throw new Error('获取渠道列表失败');
      return await res.json();
    }
  });

  // 创建数据
  const createMutation = useMutation({
    mutationFn: async (data: CreateChannelRequest) => {
      const res = await channelClientManager.get().channel.createChannel.$post({ json: data });
      if (res.status !== 201) throw new Error('创建渠道失败');
      return await res.json();
    },
    onSuccess: () => {
      // 刷新数据
      queryClient.invalidateQueries({ queryKey: ['channels'] });
    }
  });

  // ...组件其他代码
};
E. 状态管理转换
// 原Jotai状态管理
import { atom, useAtom } from 'jotai';

const channelsAtom = atom([]);
const loadingAtom = atom(false);

const useChannels = () => {
  const [channels, setChannels] = useAtom(channelsAtom);
  const [loading, setLoading] = useAtom(loadingAtom);

  const fetchChannels = async () => {
    setLoading(true);
    const data = await api.getChannels();
    setChannels(data);
    setLoading(false);
  };

  return { channels, loading, fetchChannels };
};

// 转换后React Query(使用rpcClient + ClientManager模式)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { channelClientManager } from './api/channelClient';
import type { InferRequestType } from 'hono/client';
import { channelClient } from './api/channelClient';

type CreateChannelRequest = InferRequestType<typeof channelClient.channel.createChannel.$post>['json'];

const useChannels = () => {
  const queryClient = useQueryClient();

  const { data: channels, isLoading } = useQuery({
    queryKey: ['channels'],
    queryFn: async () => {
      const res = await channelClientManager.get().channel.getChannelList.$get();
      if (res.status !== 200) throw new Error('获取渠道列表失败');
      return await res.json();
    }
  });

  const createMutation = useMutation({
    mutationFn: async (data: CreateChannelRequest) => {
      const res = await channelClientManager.get().channel.createChannel.$post({ json: data });
      if (res.status !== 201) throw new Error('创建渠道失败');
      return await res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['channels'] });
    }
  });

  return { channels, isLoading, createChannel: createMutation.mutateAsync };
};

移植顺序策略

建议移植顺序

  1. 基础UI包:先移植简单的CRUD页面(channel、platform)
  2. 中等复杂度:移植有依赖关系的页面(company、salary)
  3. 高复杂度:最后移植复杂页面(disability_person、order)
  4. 特殊处理:region页面已有@d8d/area-management-ui包,直接复用

具体顺序

  1. allin-platform-management-ui(最简单)
  2. allin-channel-management-ui(独立)
  3. allin-company-management-ui(依赖platform)
  4. allin-salary-management-ui(中等复杂度)
  5. allin-disability-management-ui(中等复杂度)
  6. allin-disability-person-management-ui(高复杂度)
  7. allin-order-management-ui(高复杂度) :region管理UI已有@d8d/area-management-ui包,无需移植

兼容性保证

  • 保持:业务逻辑、数据模型、用户交互流程
  • 调整:UI组件库、状态管理、API调用方式
  • 新增:类型安全、测试覆盖、更好的开发体验

故事分解(按模块拆分)

:每个故事对应一个UI模块的完整移植,包括技术栈转换和组件测试。

故事1:移植平台管理UI(platform → @d8d/allin-platform-management-ui)

目标:移植基础平台管理页面

验收标准

  1. 创建allin-packages/platform-management-ui目录结构
  2. 完成组件转换:平台管理组件
  3. 完成API客户端转换:基础CRUD API(使用rpcClient + ClientManager模式)
  4. 完成状态管理转换:基础数据状态
  5. 完成表单转换:简单验证表单
  6. 配置package.json:基础依赖
  7. 编写组件测试:验证基础功能
  8. 通过类型检查和基本测试验证

故事2:移植渠道管理UI(channel → @d8d/allin-channel-management-ui)

目标:将channel管理页面移植为独立UI包,完成技术栈转换并验证功能完整性

验收标准

  1. 创建allin-packages/channel-management-ui目录结构
  2. 完成组件转换:从Ant Design转换为@d8d/shared-ui-components组件
  3. 完成API客户端转换:从自定义fetch API转换为Hono RPC (rpcClient + ClientManager模式) (rpcClient + ClientManager模式)
  4. 完成状态管理转换:从Jotai转换为React Query
  5. 完成表单转换:从Ant Design Form转换为React Hook Form + Zod
  6. 配置package.json:使用@d8d/allin-channel-management-ui包名,workspace依赖
  7. 编写组件测试:覆盖所有组件
  8. 通过类型检查和基本测试验证
  9. @d8d/allin-channel-module后端模块集成验证

组件测试要求

  • 测试文件:tests/components/ChannelTable.test.tsx
  • 测试覆盖:表格组件、表单组件、模态框组件
  • 验证:数据渲染、用户交互、表单验证
  • 遵循现有组件测试模式

故事3:移植公司管理UI(company → @d8d/allin-company-management-ui)

目标:将company管理页面移植为独立UI包,处理对platform数据的依赖

验收标准

  1. 创建allin-packages/company-management-ui目录结构
  2. 完成组件转换:公司管理相关组件
  3. 处理数据依赖:正确集成platform数据选择器
  4. 完成API客户端转换:处理跨模块API调用(使用rpcClient + ClientManager模式)
  5. 完成状态管理转换:复杂数据关联状态
  6. 完成表单转换:包含平台选择的下拉表单
  7. 配置package.json:依赖@d8d/allin-platform-management-ui
  8. 编写组件测试:验证跨模块数据集成
  9. 通过类型检查和基本测试验证

故事4:移植薪资管理UI(salary → @d8d/allin-salary-management-ui)

目标:移植薪资管理页面,包含复杂表单验证

验收标准

  1. 创建allin-packages/salary-management-ui目录结构
  2. 完成组件转换:薪资表格、复杂表单组件
  3. 区域包集成:集成@d8d/area-management-ui的区域选择器组件
  4. 完成API客户端转换:薪资计算API(使用rpcClient + ClientManager模式)
  5. 完成状态管理转换:数值计算状态
  6. 完成表单转换:复杂数值验证
  7. 配置package.json:依赖@d8d/area-management-ui
  8. 编写组件测试:验证数值计算和验证
  9. 通过类型检查和基本测试验证

故事5:移植残疾人管理UI(disability → @d8d/allin-disability-management-ui)

目标:将disability管理页面移植为独立UI包

验收标准

  1. 创建allin-packages/disability-management-ui目录结构
  2. 完成组件转换:残疾人列表管理组件
  3. 区域包集成:集成@d8d/area-management-ui的区域选择器组件用于残疾人区域信息管理
  4. 完成API客户端转换:与disability-module集成
  5. 完成状态管理转换:残疾人数据状态
  6. 完成表单转换:基础残疾人信息表单(包含区域选择)
  7. 配置package.json:依赖@d8d/allin-disability-module@d8d/area-management-ui
  8. 编写组件测试:验证残疾人管理功能(包含区域选择验证)
  9. 通过类型检查和基本测试验证

故事6:移植残疾人个人管理UI(disability_person → @d8d/allin-disability-person-management-ui)

目标:移植最复杂的残疾人个人管理页面,包含照片上传、备注管理等

验收标准

  1. 创建allin-packages/disability-person-management-ui目录结构
  2. 完成组件转换:复杂表单、照片上传、备注管理组件
  3. 文件上传集成:与@d8d/file-management-ui集成
  4. 区域包集成:集成@d8d/area-management-ui的区域选择器组件用于残疾人详细地址管理(省份→城市→区县三级联动)
  5. 枚举常量集成:使用@d8d/allin-enums包中的枚举
  6. 完成API客户端转换:复杂API调用链(使用rpcClient + ClientManager模式)
  7. 完成状态管理转换:多步骤表单状态
  8. 完成表单转换:复杂验证逻辑(包含区域选择验证)
  9. 配置package.json:多依赖管理(包含@d8d/area-management-ui
  10. 编写组件测试:覆盖所有复杂场景(包含区域选择功能测试)
  11. 通过类型检查和基本测试验证

故事7:移植订单管理UI(order → @d8d/allin-order-management-ui)

目标:移植复杂的订单管理页面,包含人员分配、资产关联、考勤打卡等

验收标准

  1. 创建allin-packages/order-management-ui目录结构
  2. 完成组件转换:订单表格、人员选择、资产关联组件
  3. 文件上传集成:资产文件关联组件
  4. 区域包集成:集成@d8d/area-management-ui的区域选择器组件用于订单相关区域信息管理
  5. 枚举常量集成:使用@d8d/allin-enums包中的订单状态枚举
  6. 完成API客户端转换:复杂业务API(使用rpcClient + ClientManager模式)
  7. 完成状态管理转换:订单工作流状态
  8. 完成表单转换:多实体关联表单(包含区域选择)
  9. 考勤打卡功能移植:移植出勤表导出功能
    • 创建AttendanceModal组件,从Ant Design转换为@d8d/shared-ui-components组件
    • 集成到订单管理UI中,保持原有交互流程
    • 完成技术栈转换:Modal、DatePicker、Select等组件转换
    • 保持Excel导出功能(使用xlsx库)
    • 编写组件测试:tests/components/AttendanceModal.test.tsx
  10. 配置package.json:复杂依赖管理(包含@d8d/area-management-uixlsx等)
  11. 编写组件测试:覆盖订单全生命周期(包含区域相关功能测试)
  12. 通过类型检查和基本测试验证

故事8:将Allin系统UI包集成到Web client admin

目标:将史诗008中创建的7个Allin系统UI包集成到Web client admin中,对应史诗007的最后一个任务(故事8:将Allin系统模块集成到packages/server)

验收标准

  1. 依赖配置:在web/package.json中添加所有7个Allin系统UI包的workspace依赖

    • @d8d/allin-channel-management-ui
    • @d8d/allin-company-management-ui
    • @d8d/allin-disability-management-ui
    • @d8d/allin-disability-person-management-ui
    • @d8d/allin-order-management-ui
    • @d8d/allin-platform-management-ui
    • @d8d/allin-salary-management-ui
  2. API客户端初始化:在web/src/client/admin/api_init.ts中添加所有7个Allin系统UI包的API客户端初始化,路径必须与server包中的路由配置完全一致(参考packages/server/src/index.ts:139-144

    • channelClientManager.init('/api/v1/channel') - 对应server中的/api/v1/channel
    • companyClientManager.init('/api/v1/company') - 对应server中的/api/v1/company
    • disabilityClientManager.init('/api/v1/disability') - 对应server中的/api/v1/disability(注意:disability_person模块在server中注册为/api/v1/disability
    • orderClientManager.init('/api/v1/order') - 对应server中的/api/v1/order
    • platformClientManager.init('/api/v1/platform') - 对应server中的/api/v1/platform
    • salaryClientManager.init('/api/v1/salary') - 对应server中的/api/v1/salary
    • 注意:所有路径使用单数形式,没有s后缀,与server包配置保持一致
  3. 路由集成:在web/src/client/admin/routes.tsx中添加所有7个UI包的路由配置

    • /admin/channels@d8d/allin-channel-management-ui
    • /admin/companies@d8d/allin-company-management-ui
    • /admin/disabilities@d8d/allin-disability-management-ui
    • /admin/disability-persons@d8d/allin-disability-person-management-ui
    • /admin/orders@d8d/allin-order-management-ui
    • /admin/platforms@d8d/allin-platform-management-ui
    • /admin/salaries@d8d/allin-salary-management-ui
  4. 菜单集成:在web/src/client/admin/menu.tsx中添加对应的菜单项

    • 渠道管理
    • 公司管理
    • 残疾人管理
    • 残疾人个人管理
    • 订单管理
    • 平台管理
    • 薪资管理
  5. 权限配置:为每个菜单项配置相应的权限(参考现有权限模式)

  6. 图标配置:为每个菜单项配置合适的图标(使用lucide-react图标库)

  7. API路径配置:确保API路径与后端路由对应(参考史诗007的server集成)

  8. 集成验证:验证所有路由正常工作,UI包正确加载,API客户端初始化成功

  9. 测试验证:通过E2E测试验证集成功能

技术要点

  • 遵循现有路由和菜单集成模式
  • 保持权限系统一致性
  • 确保路由路径与后端API路径对应
  • 验证UI包与后端模块的集成(对应史诗007的故事8)

与史诗007的对应关系

  • 本故事对应史诗007的故事8:将Allin系统模块集成到packages/server
  • 确保前端UI包与后端模块的集成时间点对齐
  • 验证前后端集成功能完整性

区域管理UI说明

目标:复用现有@d8d/area-management-ui包,无需重新移植

现状分析

  1. 已有包@d8d/area-management-ui已提供完整的区域管理功能
  2. 技术栈:与目标技术栈一致(@d8d/shared-ui-components + React Query + Hono RPC)
  3. 功能匹配:提供树形区域管理、CRUD操作、搜索等功能
  4. 集成点:与@d8d/geo-areas后端模块已集成

实施策略

  1. 直接复用:在Allin系统中直接使用@d8d/area-management-ui
  2. 依赖配置:在需要区域选择功能的UI包中添加对@d8d/area-management-ui的依赖
  3. 组件集成:使用AreaSelectorAreaTree等现有组件
  4. 无需移植:节省开发工作量,保持技术栈一致性

文件上传集成方案

现状分析

1. allin_system-master中的文件上传

  • 照片上传DisabledPhoto相关页面使用直接文件上传
  • 资产文件上传OrderPersonAsset相关页面使用文件上传
  • 实现方式:直接调用后端上传接口,存储URL到数据库

2. 当前项目的文件上传

  • 文件管理UI包@d8d/file-management-ui
  • 核心功能:文件选择器组件、上传进度、预览功能
  • 集成方式:通过fileId关联业务实体

集成方案

架构设计原则

核心思想:复用现有的@d8d/file-management-ui包,保持统一的文件上传体验

实施步骤

  1. 组件集成

    // 在disability-person-management-ui中集成文件选择器
    import { FileSelector } from '@d8d/file-management-ui';
    
    const PhotoUploadField = ({ value, onChange }) => {
     return (
       <div>
         <Label>照片上传</Label>
         <FileSelector
           accept="image/*"
           onFileSelected={(fileId) => onChange(fileId)}
         />
         {value && <FilePreview fileId={value} />}
       </div>
     );
    };
    
  2. 表单集成

    // 残疾人照片表单
    const disabilityPersonSchema = z.object({
     // ...其他字段
     photoFileId: z.number().optional(), // 文件ID字段
    });
    
    // 提交时只传递fileId
    const onSubmit = (data) => {
     // data.photoFileId 包含文件ID
     await api.createDisabledPerson(data);
    };
    
  3. 数据显示

    // 显示照片时使用文件预览组件
    import { FilePreview } from '@d8d/file-management-ui';
    
    const PhotoDisplay = ({ fileId }) => {
     return <FilePreview fileId={fileId} />;
    };
    

影响的故事

需要调整以下故事的文件上传处理:

  1. 故事6(disability-person-management-ui)

    • 集成文件选择器组件用于照片上传
    • 修改表单接收fileId而非文件内容
    • 使用文件预览组件显示照片
  2. 故事7(order-management-ui)

    • 集成文件选择器组件用于资产文件上传
    • 根据资产类型限制文件类型(图片/视频)
    • 使用文件预览组件显示资产文件

兼容性要求

  • 现有功能保持不变
  • 用户交互流程保持一致
  • 数据模型兼容后端API
  • 遵循现有TypeScript配置和构建模式
  • 性能影响最小化

风险缓解

  • 主要风险:技术栈差异大,移植过程中可能破坏现有交互体验
  • 缓解措施:逐个模块移植,每个模块完成后进行用户验收测试
  • 回滚计划:保留原始allin_system-master/client目录作为备份

完成定义

  • 所有8个故事完成,验收标准满足
    • 故事1:平台管理UI(故事008.001已完成)
    • 故事2:渠道管理UI(故事008.002已完成)
    • 故事3:公司管理UI(故事008.003已完成)
    • 故事4:薪资管理UI(故事008.004已完成)
    • 故事5:残疾人管理UI(故事008.005已完成)
    • 故事6:残疾人个人管理UI(故事008.006已完成)
    • 故事7:订单管理UI(故事008.007已完成)
    • 故事8:将Allin系统UI包集成到Web client admin(对应史诗007故事8)
  • 区域管理功能复用@d8d/area-management-ui
  • 现有功能通过测试验证
  • 集成点正常工作
  • 文档更新适当
  • 现有功能无回归

验证清单

范围验证

  • 史诗可在8个故事内完成
  • 不需要架构文档变更
  • 增强遵循现有模式
  • 集成复杂度可管理

风险评估

  • 对现有系统风险较低
  • 回滚计划可行
  • 测试方法覆盖现有功能
  • 团队对集成点有足够了解

完整性检查

  • 史诗目标清晰可实现
  • 故事范围适当
  • 成功标准可衡量
  • 依赖关系已识别

故事经理交接说明

"UI模块分析和技术栈分析已完成,关键发现:

  1. 依赖关系分析完成:7个UI模块的依赖关系已明确(region复用现有包),新增故事8用于集成到Web client admin
  2. 技术栈差异分析完成:源系统使用Ant Design + Jotai,目标系统使用@d8d/shared-ui-components + React Query,存在重大架构差异
  3. 命名方案确定:使用@d8d/allin-前缀,-management-ui后缀,非多租户版本
  4. 目录结构:在allin-packages/目录下创建UI包,与后端模块保持相同目录,遵循UI包开发规范
  5. 移植顺序建议:从简单到复杂,先移植基础CRUD页面
  6. 技术栈转换方案:已制定从Ant Design到@d8d/shared-ui-components的详细转换策略,遵循UI包开发规范
  7. 故事拆分完成:按模块拆分为7个移植故事+1个集成故事(共8个故事,按执行顺序编号),每个移植故事包含组件测试要求,遵循UI包开发规范

新的故事拆分方案(按执行顺序编号)

  • 故事1:移植平台管理UI(platform → @d8d/allin-platform-management-ui) - 最简单,无依赖
  • 故事2:移植渠道管理UI(channel → @d8d/allin-channel-management-ui) - 独立模块
  • 故事3:移植公司管理UI(company → @d8d/allin-company-management-ui) - 依赖platform数据
  • 故事4:移植薪资管理UI(salary → @d8d/allin-salary-management-ui) - 中等复杂度
  • 故事5:移植残疾人管理UI(disability → @d8d/allin-disability-management-ui) - 中等复杂度
  • 故事6:移植残疾人个人管理UI(disability_person → @d8d/allin-disability-person-management-ui) - 高复杂度,依赖disability
  • 故事7:移植订单管理UI(order → @d8d/allin-order-management-ui) - 高复杂度,可能依赖其他模块
  • 故事8:将Allin系统UI包集成到Web client admin - 对应史诗007故事8,完成前后端集成
  • 区域管理:复用现有@d8d/area-management-ui包(无需移植)

每个故事的关键要求

  1. 技术栈转换:必须完成组件、API客户端、状态管理、表单的完整转换,严格遵循UI包开发规范
  2. 组件测试:必须编写tests/components/测试文件,遵循UI包开发规范
  3. 测试覆盖:必须覆盖所有主要组件,验证用户交互
  4. 遵循现有模式:参考advertisement-management-ui的组件模式,并遵循UI包开发规范
  5. 集成验证:必须与对应的后端模块集成验证

执行顺序: 按故事编号顺序执行即可:故事1 → 故事2 → 故事3 → 故事4 → 故事5 → 故事6 → 故事7 → 故事8

特别说明

  • 故事8必须在所有UI包移植完成后执行,对应史诗007的故事8(将Allin系统模块集成到packages/server)
  • 确保前后端集成时间点对齐,验证完整的Allin系统功能

依赖关系说明

  • 故事3(company-management-ui)依赖platform数据,必须在故事1(platform-management-ui)之后
  • 故事6(disability-person-management-ui)是disability的详细管理,应在故事5(disability-management-ui)之后
  • 故事7(order-management-ui)可能涉及多个实体关联,放在最后

技术栈转换关键点

  • Ant Design组件 → @d8d/shared-ui-components组件:使用现有共享UI组件库
  • Jotai状态 → React Query + React状态(useState/useContext):使用TanStack Query进行服务端数据管理,React状态管理本地状态
  • Ant Design Form → React Hook Form + Zod:使用hook form + zod验证
  • 自定义fetch API → Hono RPC (rpcClient + ClientManager模式):使用类型安全的RPC客户端,遵循项目现有的rpcClient工具和ClientManager单例模式
  • 文件上传:集成@d8d/file-management-ui文件选择器组件

组件测试模板参考: 参考/packages/advertisement-management-ui/tests/components/

  • 使用@testing-library/react进行组件测试
  • 模拟用户交互(点击、输入、表单提交)
  • 验证组件渲染和状态变化
  • 每个主要组件都要有测试

文件上传集成方案: 发现allin_system-master中有文件上传功能(照片、资产文件),需要与现有@d8d/file-management-ui集成。

采用方案:复用现有文件管理UI包

  1. 架构原则:使用统一的文件选择器组件,后端只接收文件ID
  2. 故事4(disability-person-management-ui)
    • 集成FileSelector组件用于照片上传
    • 表单接收fileId参数(而非文件内容)
    • 使用FilePreview组件显示照片
  3. 故事5(order-management-ui)
    • 集成FileSelector组件用于资产文件上传
    • 根据资产类型限制文件类型
    • 使用文件预览组件显示资产
  4. 优势:统一用户体验,复用现有组件,简化后端逻辑

实施要点

  • UI层使用现有文件选择器组件上传文件
  • 后端API只接收文件ID,不处理文件上传
  • 保持API兼容性,制定数据迁移方案
  • 注意组件性能,合理使用memoization

故事8(Web client admin集成)关键要求

  1. 依赖管理:在web/package.json中添加所有7个Allin系统UI包的workspace依赖
  2. API客户端初始化:在web/src/client/admin/api_init.ts中添加所有7个Allin系统UI包的API客户端初始化,API路径必须与server包中的路由配置完全一致(参考packages/server/src/index.ts:139-144
    • 使用单数形式路径:/api/v1/channel/api/v1/company等(没有s后缀)
    • disability_person模块对应/api/v1/disability路径
  3. 路由配置:在web/src/client/admin/routes.tsx中添加7个路由路径,对应每个UI包
  4. 菜单集成:在web/src/client/admin/menu.tsx中添加对应的菜单项,配置权限和图标
  5. API路径对齐必须确保前端API路径与后端路由完全对应(参考史诗007的server集成)
  6. 集成验证:验证所有路由正常工作,API客户端初始化成功,与后端模块集成完整
  7. 对应关系:本故事对应史诗007的故事8,确保前后端集成同步完成

史诗应在保持用户体验一致性的同时实现将UI模块从Ant Design架构移植到@d8d/shared-ui-components架构的标准化独立UI包,每个模块都要有完整的组件测试验证,并完成与现有文件管理UI包的集成,最后通过故事8将所有UI包集成到Web client admin中。"

新增考勤打卡功能说明

  • 发现:原系统中存在考勤打卡功能(出勤表导出),位于订单管理模块
  • 功能位置allin_system-master/client/app/admin/dashboard/order/AttendanceModal.tsx
  • 功能描述:为订单人员生成月度出勤Excel表,支持选择月份和每周出勤天数,模拟出勤数据
  • 移植方案:在故事7(order-management-ui) 中新增考勤打卡功能移植任务
  • 具体任务
    1. 创建AttendanceModal组件,从Ant Design转换为@d8d/shared-ui-components组件
    2. 集成到订单管理UI中,保持原有交互流程
    3. 完成技术栈转换:Modal、DatePicker、Select等组件转换
    4. 保持Excel导出功能(使用xlsx库)
    5. 编写组件测试:tests/components/AttendanceModal.test.tsx
  • 依赖更新:在package.json中添加xlsx依赖
  • 测试要求:验证出勤表导出功能正常工作,Excel文件正确生成"