Ready for Review
As a 开发者, I want 从单租户文件管理UI复制并改造创建统一文件管理UI包(unified-file-management-ui), so that 统一广告管理UI可以使用无租户隔离的文件选择器组件,确保架构一致性。
packages/unified-file-management-ui 包(使用 cp -r 命令直接复制 packages/file-management-ui 整个文件夹)@d8d/file-management-ui 改为 @d8d/unified-file-management-ui,从 @d8d/file-module 改为 @d8d/unified-file-module)/api/v1/admin/unified-files)[x] 任务1: 复制文件管理UI包创建统一文件管理UI包 (AC: 1)
cp -r packages/file-management-ui packages/unified-file-management-ui 命令直接复制整个文件夹[x] 任务2: 修改包配置文件 (AC: 2)
package.json 包名:@d8d/file-management-ui → @d8d/unified-file-management-uipackage.json 描述:添加"unified"相关描述@d8d/file-module → @d8d/unified-file-module[x] 任务3: 修改 API 客户端 (AC: 2, 3)
src/api/fileClient.ts 文件名为 src/api/unifiedFileClient.tssrc/api/fileClient.ts 中的路由导入:fileRoutes → unifiedFileRoutessrc/api/index.ts 导出[x] 任务4: 修改类型定义 (AC: 2)
[x] 任务5: 修改组件 (AC: 4, 5)
[x] 任务6: 编写组件测试 (AC: 6)
[x] 任务7: 类型检查和代码质量 (AC: 7)
pnpm typecheck 确保无TypeScript类型错误pnpm test 确保所有测试通过pnpm test:coverage 确保测试覆盖率达到70%以上史诗010的统一设计模式:
tenantAuthMiddleware/api/v1/admin/unified-files(管理员路由)tenant_id 字段/api/v1 前缀)路由规范 [Source: docs/prd/epic-010-unified-ad-management.md, docs/architecture/backend-module-package-standards.md]:
/ 和 /:id),不包含 /api/v1 前缀request.params 必须明确定义,使用 z.coerce.number<number>() 进行类型转换架构不一致性 [Source: docs/prd/epic-010-unified-ad-management.md]:
unified-advertisement-management-ui) 当前使用 @d8d/file-management-ui-mt 的 FileSelector 组件(多租户)FileSelector 组件关联的是多租户文件模块(有 tenant_id 字段)包结构 [Source: packages/file-management-ui/]:
packages/file-management-ui/
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── build.config.ts
├── src/
│ ├── index.ts
│ ├── api/
│ │ ├── fileClient.ts
│ │ └── index.ts
│ ├── components/
│ │ ├── FileManagement.tsx
│ │ ├── FileSelector.tsx
│ │ ├── MinioUploader.tsx
│ │ └── index.ts
│ ├── hooks/
│ │ ├── useFileManagement.ts
│ │ ├── useFileSelector.ts
│ │ └── index.ts
│ ├── types/
│ │ └── file.ts
│ └── utils/
│ ├── cn.ts
│ ├── minio.ts
│ └── index.ts
└── tests/
├── setup.ts
├── components/
│ ├── FileManagement.test.tsx
│ └── FileSelector.test.tsx
├── hooks/
│ └── useFileManagement.test.tsx
└── utils/
└── index.test.ts
包名: @d8d/file-management-ui [Source: packages/file-management-ui/package.json]
依赖 [Source: packages/file-management-ui/package.json]:
{
"dependencies": {
"@d8d/shared-types": "workspace:*",
"@d8d/shared-ui-components": "workspace:*",
"@d8d/file-module": "workspace:*",
"@hookform/resolvers": "^5.2.1",
"@tanstack/react-query": "^5.90.9",
"axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"dayjs": "^1.11.13",
"hono": "^4.8.5",
"lucide-react": "^0.536.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.61.1",
"react-router": "^7.1.3",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"zod": "^4.0.15"
}
}
API 客户端模式 [Source: packages/file-management-ui/src/api/fileClient.ts]:
// RPC 客户端管理器模式(参考)
import { fileRoutes } from '@d8d/file-module';
import { rpcClient } from '@d8d/shared-ui-components/utils/hc';
export class FileClientManager {
private static instance: FileClientManager;
private client: ReturnType<typeof rpcClient<typeof fileRoutes>> | null = null;
public static getInstance(): FileClientManager {
if (!FileClientManager.instance) {
FileClientManager.instance = new FileClientManager();
}
return FileClientManager.instance;
}
public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof fileRoutes>> {
return this.client = rpcClient<typeof fileRoutes>(baseUrl);
}
public get(): ReturnType<typeof rpcClient<typeof fileRoutes>> {
if (!this.client) {
return this.init()
}
return this.client;
}
public reset(): void {
this.client = null;
}
}
const fileClientManager = FileClientManager.getInstance();
export const fileClient = fileClientManager.get();
export { FileClientManager, fileClientManager };
⚠️ 依赖变更说明:
@d8d/file-module → @d8d/unified-file-modulefileRoutes → unifiedFileRoutes⚠️ 重要:使用 CP 命令直接复制
根据用户明确要求,必须使用以下命令直接复制整个文件管理UI文件夹:
cp -r packages/file-management-ui packages/unified-file-management-ui
为什么使用 cp 命令直接复制:
复制后的修改清单:
@d8d/file-management-ui → @d8d/unified-file-management-ui@d8d/file-module → @d8d/unified-file-modulefileClient.ts → unifiedFileClient.tsfileRoutes → unifiedFileRoutesFileClientManager → UnifiedFileClientManagerfileClient → unifiedFileClient包结构规范 [Source: docs/architecture/ui-package-standards.md]:
packages/<module-name>-ui/
├── package.json # 包配置
├── tsconfig.json # TypeScript配置
├── vite.config.ts # Vite构建配置
├── src/
│ ├── index.ts # 主入口文件
│ ├── components/ # React组件
│ ├── api/ # RPC客户端管理器
│ ├── hooks/ # 自定义Hooks
│ ├── types/ # TypeScript类型定义
│ └── utils/ # 工具函数
└── tests/ # 测试文件
└── integration/ # 集成测试
RPC客户端实现规范 [Source: docs/architecture/ui-package-standards.md]:
类型推断最佳实践 [Source: docs/architecture/coding-standards.md]:
测试规范 [Source: docs/architecture/ui-package-standards.md]:
@testing-library/react 进行组件测试新UI包位置: packages/unified-file-management-ui/ [Source: docs/architecture/source-tree.md]
测试标准: Vitest [Source: docs/architecture/tech-stack.md]
测试文件位置 [Source: docs/architecture/ui-package-standards.md]:
packages/unified-file-management-ui/tests/components/packages/unified-file-management-ui/tests/hooks/packages/unified-file-management-ui/tests/utils/测试框架:
测试配置 [Source: packages/file-management-ui/vitest.config.ts]:
// vitest.config.ts
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['./tests/setup.ts']
}
});
测试覆盖要求:
测试命令:
# 进入UI包目录
cd packages/unified-file-management-ui
# 运行所有测试
pnpm test
# 运行测试并监听变化
pnpm test:watch
# 生成覆盖率报告
pnpm test:coverage
# 类型检查
pnpm typecheck
测试 Setup 配置 [Source: docs/architecture/ui-package-standards.md]:
// 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();
// Mock pointer events for Radix UI Select component
Element.prototype.hasPointerCapture = vi.fn(() => true) as any;
Element.prototype.releasePointerCapture = vi.fn() as any;
Element.prototype.setPointerCapture = vi.fn() as any;
// Mock ResizeObserver (必须使用 class 模式)
global.ResizeObserver = class MockResizeObserver {
constructor(callback: ResizeObserverCallback) {
(this as any).callback = callback;
}
observe() {}
unobserve() {}
disconnect() {}
};
| Date | Version | Description | Author |
|---|---|---|---|
| 2026-01-04 | 1.1 | 批准故事 | Bob (Scrum Master) |
| 2026-01-04 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
d8d-model (claude-opus-4-5-20251101)
无需要记录的调试问题
cp -r 命令成功复制 packages/file-management-ui 到 packages/unified-file-management-uipackage.json 包名和依赖(@d8d/unified-file-module)fileClient.ts → unifiedFileClient.ts,fileClientManager → unifiedFileClientManagerUpdateFileDto → UpdateUnifiedFileDto 导入问题uploadUser → uploadUserId 属性问题(统一文件模块删除了 uploadUser 字段)新增文件:
packages/unified-file-management-ui/package.jsonpackages/unified-file-management-ui/src/api/unifiedFileClient.tspackages/unified-file-management-ui/src/components/FileManagement.tsxpackages/unified-file-management-ui/src/components/FileSelector.tsxpackages/unified-file-management-ui/src/components/MinioUploader.tsxpackages/unified-file-management-ui/src/hooks/useFileManagement.tspackages/unified-file-management-ui/src/hooks/useFileSelector.tspackages/unified-file-management-ui/src/types/file.tspackages/unified-file-management-ui/src/utils/minio.tspackages/unified-file-management-ui/tests/components/FileManagement.test.tsxpackages/unified-file-management-ui/tests/components/FileSelector.test.tsxpackages/unified-file-management-ui/tests/hooks/useFileManagement.test.tsx修改文件:
docs/stories/010.010.story.md (更新状态和任务复选框)QA代理待填写