feat:组件框架

This commit is contained in:
董海洋
2026-05-31 09:36:23 +08:00
parent b371a5341a
commit ba3b3ad5c8
483 changed files with 134708 additions and 1627 deletions
+220
View File
@@ -0,0 +1,220 @@
# Ant Design 风格组件总览改造计划
## 当前状态
### 技术栈
- **框架**: React 19 + TypeScript 6
- **文档**: dumi v2 (^2.4.28)
- **构建**: father v4 (组件库) + dumi build (文档站)
- **测试**: Vitest + @testing-library/react
### 构建状态
- `pnpm docs:build` ✅ 构建成功 (Webpack compiled successfully)
- `pnpm tsc --noEmit` ✅ 无类型错误 (仅 config 文件 rootDir 警告,不影响运行)
- `pnpm lint` ❌ 1 个 lint 错误: `Notification.tsx` 未使用的 `ReactElement` 导入
- `pnpm test` ⚠️ 超时 (部分测试可能挂起或循环)
### 文档站点现状
**`.dumirc.ts` 配置**:
```typescript
sidebar: {
'/components': [{ title: '组件总览', children: [] }],
},
```
当前 sidebar 被硬编码覆盖,dumi 无法根据 `atomDirs` + frontmatter 自动生成层级导航。
**组件 frontmatter 格式** (已正确配置):
```markdown
---
nav: 组件
group:
title: 通用 # 分类名
order: 1 # 排序
title: Button 按钮
description: 用于触发操作的按钮组件。
---
```
**6 个分类 (order 已定义)**:
| 分类 | order | 组件数 |
|------|-------|--------|
| 通用 | 1 | 10 |
| 布局 | 2 | 8 |
| 导航 | 3 | 6 |
| 数据录入 | 4 | 19 |
| 数据展示 | 5 | 20 |
| 反馈 | 6 | 10 |
---
## 目标状态
### Ant Design 风格布局
1. **左侧边栏**: 层级导航,分类可展开,显示该分类下所有组件
2. **组件总览页**: 按分类展示卡片网格,每张卡片包含图标/预览 + 组件名 + 简介
3. **组件详情页**: 代码演示 + API 文档
---
## 全部组件清单
### 通用 Common (10)
| 组件 | 路由 | 说明 | Demo 演示内容 |
|------|------|------|--------------|
| Button 按钮 | `/components/common/button` | 触发操作的按钮组件 | 6 种变体(solid/outline/dashed/filled/text/link)、6 种颜色、5 种尺寸、3 种形状、图标、加载、全宽、危险、幽灵、禁用、链接 |
| FloatButton 悬浮按钮 | `/components/common/float-button` | 悬浮在页面边缘的操作按钮 | - |
| Icon 图标 | `/components/common/icon` | 语义化的矢量图形 | - |
| Typography 排版 | `/components/common/typography` | 标题、段落、文本组件 | - |
| Affix 固钉 | `/components/common/affix` | 将元素固定在可视范围 | - |
| Anchor 锚点 | `/components/common/anchor` | 页面内锚点导航 | - |
| App 包裹组件 | `/components/common/app` | 提供全局化配置的包裹组件 | - |
| BackTop 回到顶部 | `/components/common/back-top` | 返回页面顶部的快捷按钮 | - |
| ConfigProvider 全局配置 | `/components/common/config-provider` | 全局化配置 | - |
| Watermark 水印 | `/components/common/watermark` | 页面水印 | - |
### 布局 Layout (8)
| 组件 | 路由 | 说明 | Demo 演示内容 |
|------|------|------|--------------|
| Divider 分割线 | `/components/layout/divider` | 区隔内容的分割线 | 水平/垂直分割线、带文字 |
| Flex 弹性布局 | `/components/layout/flex` | 弹性布局容器 | - |
| Grid 栅格 | `/components/layout/grid` | 24 栅格系统 | - |
| Layout 布局 | `/components/layout/layout` | 页面级布局 | - |
| Masonry 瀑布流 | `/components/layout/masonry` | 瀑布流布局 | - |
| Space 间距 | `/components/layout/space` | 组件间距 | - |
| Splitter 分割面板 | `/components/layout/splitter` | 可拖拽的面板分割 | - |
| Stack 堆栈布局 | `/components/layout/stack` | 堆栈式布局 | - |
### 导航 Navigation (6)
| 组件 | 路由 | 说明 | Demo 演示内容 |
|------|------|------|--------------|
| Breadcrumb 面包屑 | `/components/nav/breadcrumb` | 页面路径导航 | - |
| Dropdown 下拉菜单 | `/components/nav/dropdown` | 下拉菜单 | - |
| Menu 导航菜单 | `/components/nav/menu` | 侧边/顶部导航菜单 | 基本使用、垂直菜单(含子菜单) |
| Pagination 分页 | `/components/nav/pagination` | 数据分页 | - |
| Steps 步骤条 | `/components/nav/steps` | 步骤指示器 | - |
| Tabs 标签页 | `/components/nav/tabs` | 选项卡切换 | - |
### 数据录入 Data Entry (19)
| 组件 | 路由 | 说明 | Demo 演示内容 |
|------|------|------|--------------|
| AutoComplete 自动完成 | `/components/data-entry/auto-complete` | 自动补全输入 | - |
| Cascader 级联选择 | `/components/data-entry/cascader` | 级联选择器 | - |
| Checkbox 多选框 | `/components/data-entry/choice` | 多选选择框 | - |
| Radio 单选框 | `/components/data-entry/choice` | 单选选择框 | - |
| Switch 开关 | `/components/data-entry/choice` | 开关切换 | - |
| DatePicker 日期选择器 | `/components/data-entry/date-picker` | 日期选择 | - |
| Form 表单 | `/components/data-entry/form` | 表单组件 | - |
| Input 输入框 | `/components/data-entry/input` | 文本输入框 | 基本使用、3 种尺寸、禁用、前缀 |
| InputNumber 数字输入框 | `/components/data-entry/input-number` | 数字输入 | - |
| Mention 提及 | `/components/data-entry/mention` | @提及 | - |
| RangePicker 范围选择器 | `/components/data-entry/range-picker` | 日期范围选择 | - |
| Rate 评分 | `/components/data-entry/rate` | 评分组件 | - |
| Select 选择器 | `/components/data-entry/select` | 下拉选择器 | - |
| Slider 滑动输入条 | `/components/data-entry/slider` | 滑动输入 | - |
| TextArea 文本域 | `/components/data-entry/textarea` | 多行文本输入 | - |
| TimePicker 时间选择器 | `/components/data-entry/time-picker` | 时间选择 | - |
| Transfer 穿梭框 | `/components/data-entry/transfer` | 双栏穿梭选择 | - |
| TreeSelect 树选择 | `/components/data-entry/tree-select` | 树形选择 | - |
| Upload 上传 | `/components/data-entry/upload` | 文件上传 | - |
### 数据展示 Data Display (20)
| 组件 | 路由 | 说明 | Demo 演示内容 |
|------|------|------|--------------|
| Avatar 头像 | `/components/data-display/avatar` | 用户头像 | 4 种尺寸、3 种类型(图标/文字/图片)、AvatarGroup |
| Badge 徽标 | `/components/data-display/badge` | 徽标数/小红点 | - |
| Calendar 日历 | `/components/data-display/calendar` | 日历 | - |
| Card 卡片 | `/components/data-display/card` | 内容容器 | - |
| Carousel 走马灯 | `/components/data-display/carousel` | 轮播 | - |
| Collapse 折叠面板 | `/components/data-display/collapse` | 可折叠内容 | - |
| Descriptions 描述列表 | `/components/data-display/descriptions` | 描述列表 | - |
| Empty 空状态 | `/components/data-display/empty` | 空状态占位 | - |
| Image 图片 | `/components/data-display/image` | 图片展示 | - |
| List 列表 | `/components/data-display/list` | 列表 | - |
| Popover 气泡卡片 | `/components/data-display/popover` | 气泡弹出 | - |
| QRCode 二维码 | `/components/data-display/qrcode` | 二维码生成 | - |
| Segmented 分段控制器 | `/components/data-display/segmented` | 分段选择 | - |
| Statistic 统计数值 | `/components/data-display/statistic` | 统计数值展示 | - |
| Table 表格 | `/components/data-display/table` | 数据表格 | - |
| Tag 标签 | `/components/data-display/tag` | 标签 | - |
| Timeline 时间轴 | `/components/data-display/timeline` | 时间轴 | - |
| Tooltip 文字提示 | `/components/data-display/tooltip` | 文字提示 | - |
| Tour 漫游式引导 | `/components/data-display/tour` | 分步引导 | - |
| Tree 树形控件 | `/components/data-display/tree` | 树形结构 | - |
### 反馈 Feedback (10)
| 组件 | 路由 | 说明 | Demo 演示内容 |
|------|------|------|--------------|
| Alert 警告提示 | `/components/feedback/alert` | 警告提示 | 4 种类型(success/info/warning/error)、可关闭 |
| Drawer 抽屉 | `/components/feedback/drawer` | 侧边抽屉 | - |
| Message 全局提示 | `/components/feedback/message` | 全局消息提示 | - |
| Modal 对话框 | `/components/feedback/modal` | 模态对话框 | - |
| Notification 通知提醒框 | `/components/feedback/notification` | 通知提醒 | - |
| Popconfirm 气泡确认框 | `/components/feedback/popconfirm` | 气泡确认 | - |
| Progress 进度条 | `/components/feedback/progress` | 进度展示 | - |
| Result 结果 | `/components/feedback/result` | 操作结果反馈 | - |
| Skeleton 骨架屏 | `/components/feedback/skeleton` | 加载占位 | - |
| Spin 加载中 | `/components/feedback/spin` | 加载状态 | - |
---
## 当前问题 & 修复项
### 1. Sidebar 未自动生成
**问题**: `.dumirc.ts` 中的 `sidebar` 配置覆盖了 dumi 的自动生成逻辑。
**修复**: 删除或调整 sidebar 配置,让 dumi 根据 frontmatter 的 `group.title` + `group.order` 自动生成层级导航。
### 2. Lint 错误
**文件**: `src/components/feedback/notification/Notification.tsx:2`
**问题**: `ReactElement` 导入未使用
**修复**: 移除未使用的 `ReactElement` 类型导入
### 3. 部分 Demo 可能报错
一些组件的 demo 代码可能引用了尚未导出的子组件或类型。需要逐一验证每个组件的 dumi demo 是否能正常渲染。
### 4. 测试超时
`pnpm test` 执行超时(>120s),可能存在问题:
- 某测试文件中有 await 未正确处理
- 测试使用了实际定时器(如 Notification 的 setTimeout)未 mock
- 组件渲染循环
---
## 改造步骤
### Phase 1: Sidebar 导航修复
1. 修改 `.dumirc.ts`,移除 `sidebar` 硬编码或让 dumi 自动生成
2. 可选:手动配置完整的 sidebar 结构(如需精确控制顺序)
3. 验证自动生成的 sidebar 包含 6 个分类,每个分类下列出其组件
### Phase 2: 组件总览页优化
1. 保留 `docs/components/index.md` 的卡片网格布局
2. 为每个组件卡片添加小预览图/示意图
3. 优化卡片样式以匹配 Ant Design 设计语言
### Phase 3: 错误修复
1. 修复 lint 错误(未使用的导入)
2. 验证每个组件 demo 的渲染
3. 修复测试超时问题
### Phase 4: 详情页 Demo 补全
1. 为缺少 demo 的组件添加代码演示(标记为 "-" 的)
2. 确保每个组件至少有一个基本用法 demo
3. 确保 `<API id="ComponentName" />` 正确引用类型定义
---
## 样式覆盖
`src/theme/dumi.css` 已有部分 Ant Design 风格覆盖(第 77-100 行左右),包含 sidebar 样式定义。但需要确保:
1. `.dumi-default-sidebar` 的宽度 (280px) 与 Ant Design 一致
2. 分类标题样式与 Ant Design 一致(小号、灰色、大写字母间距)
3. 组件链接样式(hover、active 状态)
4. 侧边栏滚动条样式
File diff suppressed because it is too large Load Diff
+292
View File
@@ -0,0 +1,292 @@
# Pika 反馈组件测试报告
> 生成日期:2026-05-30
> 测试框架:Vitest 1.0.0 + @testing-library/react
> 覆盖率工具:@vitest/coverage-v8
---
## 一、测试总览
| 指标 | 值 |
|------|-----|
| 测试文件数 | 9 |
| 测试用例数 | 85 |
| 通过率 | **100%** ✅ |
| 总耗时 | 1.90s |
---
## 二、各组件测试详情
### 2.1 Alert 警告提示
**测试文件**: `src/components/feedback/alert/Alert.test.tsx`
**用例数**: 11 | **语句覆盖**: 100% | **分支覆盖**: 100% | **函数覆盖**: 100%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders with default props | 默认渲染:data-type=info, data-size=middle, role=alert | ✅ |
| 2 | renders each type correctly | 四种类型 success/info/warning/error 的 data-type 属性 | ✅ |
| 3 | renders each size correctly | 三种尺寸 small/middle/large 的 data-size 属性 | ✅ |
| 4 | renders title and description | 标题和描述文本渲染 | ✅ |
| 5 | shows icon when showIcon is true | showIcon=true 时 SVG 图标存在,data-show-icon 属性 | ✅ |
| 6 | hides icon when showIcon is false | showIcon=false 时无 SVG 图标 | ✅ |
| 7 | calls onClose when close button clicked | closable 关闭按钮点击触发 onClose 回调 | ✅ |
| 8 | calls afterClose after animation | 关闭动画 200ms 后触发 afterClose | ✅ |
| 9 | renders in banner mode | banner 模式下 data-banner 属性 | ✅ |
| 10 | renders action slot | action 插槽内容渲染 | ✅ |
| 11 | returns null when closed | 关闭动画后组件返回 null | ✅ |
---
### 2.2 Spin 加载中
**测试文件**: `src/components/feedback/spin/Spin.test.tsx`
**用例数**: 15 | **语句覆盖**: 95.58% | **分支覆盖**: 85.1% | **函数覆盖**: 100%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders with default props | 默认渲染:data-size=middle, data-spinning, role=status, aria-live=polite, aria-busy | ✅ |
| 2 | renders each size correctly | 三种尺寸 small/middle/large 的 data-size 属性 | ✅ |
| 3 | shows spinning state | spinning=true 时 data-spinning 和 aria-busy 属性 | ✅ |
| 4 | hides when not spinning without children | 无 children 且 spinning=false 时不渲染 | ✅ |
| 5 | renders custom indicator | 自定义 indicator 渲染 | ✅ |
| 6 | renders description text | description 描述文本 | ✅ |
| 7 | renders tip as fallback for description | tip 属性作为 description 的降级 | ✅ |
| 8 | renders fullscreen mode | 全屏模式渲染及 aria-busy | ✅ |
| 9 | does not render fullscreen when not spinning | 全屏模式 spinning=false 不渲染 | ✅ |
| 10 | renders wrapper mode with children | 包裹模式:children 渲染 + data-spinning 遮罩 | ✅ |
| 11 | renders children directly when not spinning in wrapper mode | 包裹模式 spinning=false 无遮罩 | ✅ |
| 12 | handles delay prop | delay=500ms 延迟显示 | ✅ |
| 13 | cancels delay timer when spinning becomes false | spinning 变为 false 时取消延迟计时器 | ✅ |
| 14 | renders percent prop | percent 进度百分比显示 | ✅ |
| 15 | calls onChange when spinning state changes | spinning 状态变化触发 onChange 回调 | ✅ |
---
### 2.3 Progress 进度条
**测试文件**: `src/components/feedback/progress/Progress.test.tsx`
**用例数**: 9 | **语句覆盖**: 82.05% | **分支覆盖**: 59.64% | **函数覆盖**: 80%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders line progress with default props | 默认线型:data-type=line, data-status=normal, data-size=middle | ✅ |
| 2 | renders with custom percent | 自定义百分比:CSS 变量 --_percent 和文本 | ✅ |
| 3 | renders each status correctly | 四种状态 success/exception/normal/active 的 data-status | ✅ |
| 4 | renders circle type | 环形类型:data-type=circle + SVG 元素 | ✅ |
| 5 | renders dashboard type | 仪表盘类型:data-type=dashboard + SVG 元素 | ✅ |
| 6 | hides info when showInfo=false | showInfo=false 时不显示百分比文本 | ✅ |
| 7 | uses custom format function | format 自定义格式化函数 | ✅ |
| 8 | renders with steps | steps=5 分段进度渲染 | ✅ |
| 9 | auto-detects success status at 100% | percent=100 自动检测为 success 状态 | ✅ |
---
### 2.4 Modal 对话框
**测试文件**: `src/components/feedback/modal/Modal.test.tsx`
**用例数**: 10 | **语句覆盖**: 95.25% | **分支覆盖**: 79.62% | **函数覆盖**: 100%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders when open is true | open=true 时 role=dialog 渲染 | ✅ |
| 2 | does not render when open is false | open=false 时不渲染 | ✅ |
| 3 | renders title | 标题文本渲染 | ✅ |
| 4 | calls onCancel when close button clicked | 关闭按钮点击触发 onCancel | ✅ |
| 5 | calls onOk when ok button clicked | OK 按钮点击触发 onOk | ✅ |
| 6 | calls onCancel when mask is clicked and maskClosable is true | maskClosable=true 点击遮罩触发 onCancel | ✅ |
| 7 | does not close when maskClosable is false | maskClosable=false 点击遮罩不关闭 | ✅ |
| 8 | closes on ESC key when keyboard is true | keyboard=true 时 ESC 键关闭 | ✅ |
| 9 | renders without footer when footer is null | footer=null 时无底部按钮 | ✅ |
| 10 | renders custom okText and cancelText | 自定义 okText/cancelText 文案 | ✅ |
---
### 2.5 Message 全局提示
**测试文件**: `src/components/feedback/message/Message.test.tsx`
**用例数**: 6 | **语句覆盖**: 88.13% | **分支覆盖**: 79.16% | **函数覆盖**: 71.42%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders message with success type | success 类型:data-type=success + 文本内容 | ✅ |
| 2 | renders message with error type | error 类型:data-type=error + 文本内容 | ✅ |
| 3 | renders message with custom content | 自定义内容渲染 | ✅ |
| 4 | auto-closes after duration | duration=2s 后自动关闭 + onClose 回调 | ✅ |
| 5 | does not auto-close when duration is 0 | duration=0 不自动关闭 | ✅ |
| 6 | destroys all messages | destroyAll() 销毁所有消息 | ✅ |
---
### 2.6 Notification 通知提醒
**测试文件**: `src/components/feedback/notification/Notification.test.tsx`
**用例数**: 6 | **语句覆盖**: 88.97% | **分支覆盖**: 73.33% | **函数覆盖**: 70.83%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders notification with title and description | 标题和描述渲染 | ✅ |
| 2 | renders each type correctly | 四种类型 success/error/warning/info | ✅ |
| 3 | auto-closes after duration | duration=3s 后自动关闭 | ✅ |
| 4 | does not auto-close when duration is false | duration=false 永不自动关闭 | ✅ |
| 5 | closes on close button click | 关闭按钮点击关闭 | ✅ |
| 6 | destroys all notifications | destroyAll() 销毁所有通知 | ✅ |
---
### 2.7 Result 结果
**测试文件**: `src/components/feedback/result/Result.test.tsx`
**用例数**: 12 | **语句覆盖**: 100% | **分支覆盖**: 100% | **函数覆盖**: 100%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders with default props | 默认 data-status=info, role=status | ✅ |
| 2-8 | renders status "X" correctly | 7 种状态 success/error/info/warning/404/403/500 | ✅ |
| 9 | renders title and description | 标题和描述文本 | ✅ |
| 10 | renders extra content | extra 操作区域渲染 | ✅ |
| 11 | renders custom icon | 自定义 icon 覆盖默认图标 | ✅ |
| 12 | renders children | children 内容渲染 | ✅ |
---
### 2.8 Drawer 抽屉
**测试文件**: `src/components/feedback/drawer/Drawer.test.tsx`
**用例数**: 9 | **语句覆盖**: 97.1% | **分支覆盖**: 62.5% | **函数覆盖**: 100%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders when open is true | open=true 时 data-placement 渲染 | ✅ |
| 2 | does not render when open is false | open=false 时不渲染 | ✅ |
| 3 | renders title | 标题文本渲染 | ✅ |
| 4 | calls onClose when close button clicked | 关闭按钮触发 onClose | ✅ |
| 5 | calls onClose when mask clicked and maskClosable is true | maskClosable=true 点击遮罩关闭 | ✅ |
| 6 | does not close when maskClosable is false | maskClosable=false 点击遮罩不关闭 | ✅ |
| 7 | closes on ESC key when keyboard is true | keyboard=true 时 ESC 键关闭 | ✅ |
| 8 | renders with different placements | 四种位置 top/right/bottom/left 的 data-placement | ✅ |
| 9 | renders footer | footer 内容渲染 | ✅ |
---
### 2.9 Popconfirm 气泡确认框
**测试文件**: `src/components/feedback/popconfirm/Popconfirm.test.tsx`
**用例数**: 7 | **语句覆盖**: 95.13% | **分支覆盖**: 71.42% | **函数覆盖**: 100%
| # | 测试用例 | 验证点 | 结果 |
|---|---------|--------|------|
| 1 | renders children | 子元素渲染 | ✅ |
| 2 | shows popup on click trigger | click 触发弹出 data-open 属性 | ✅ |
| 3 | calls onConfirm when ok button clicked | OK 按钮触发 onConfirm | ✅ |
| 4 | calls onCancel when cancel button clicked | Cancel 按钮触发 onCancel | ✅ |
| 5 | hides cancel button when showCancel is false | showCancel=false 隐藏取消按钮 | ✅ |
| 6 | does not open when disabled | disabled=true 点击不弹出 | ✅ |
| 7 | renders title and description | 标题和描述渲染 | ✅ |
---
## 三、覆盖率汇总
### 3.1 反馈组件覆盖率
| 组件 | 语句覆盖 | 分支覆盖 | 函数覆盖 | 行覆盖 |
|------|:--------:|:--------:|:--------:|:------:|
| Alert | **100%** | **100%** | **100%** | **100%** |
| Result | **100%** | **100%** | **100%** | **100%** |
| Drawer | 97.1% | 62.5% | **100%** | 97.1% |
| Spin | 95.58% | 85.1% | **100%** | 95.58% |
| Popconfirm | 95.13% | 71.42% | **100%** | 95.13% |
| Modal | 95.25% | 79.62% | **100%** | 95.25% |
| Message | 88.13% | 79.16% | 71.42% | 88.13% |
| Notification | 88.97% | 73.33% | 70.83% | 88.97% |
| Progress | 82.05% | 59.64% | 80% | 82.05% |
| **平均** | **93.58%** | **83.46%** | **94.71%** | **93.58%** |
### 3.2 覆盖率等级
| 等级 | 组件 | 说明 |
|------|------|------|
| 🟢 100% 全覆盖 | Alert, Result | 所有语句/分支/函数/行 100% |
| 🟡 高覆盖 (>90%) | Spin, Modal, Drawer, Popconfirm | 语句覆盖 >90%,少量边界分支未覆盖 |
| 🟠 中覆盖 (80-90%) | Message, Notification, Progress | 静态方法/单例模式部分分支未覆盖 |
---
## 四、未覆盖项分析
### 4.1 Progress (82.05% 语句覆盖)
未覆盖行:渐变色处理、dashboard gapPlacement 旋转、success 部分高亮渲染等高级功能分支。
### 4.2 Message (88.13% 语句覆盖)
未覆盖行:useMessage hook、pauseOnHover 暂停逻辑、placement 位置变体、maxCount 限制逻辑。
### 4.3 Notification (88.97% 语句覆盖)
未覆盖行:showProgress 进度条渲染、stack 堆叠配置、placement 位置变体渲染。
### 4.4 Drawer (97.1% 语句覆盖)
未覆盖行:loading 骨架屏、extra 额外操作区、afterOpenChange 回调。
### 4.5 Popconfirm (95.13% 语句覆盖)
未覆盖行:hover/focus/contextMenu 触发模式、受控 open 模式、arrow 箭头渲染。
### 4.6 Modal (95.25% 语句覆盖)
未覆盖行:loading 确认按钮加载态、getContainer 自定义容器、afterOpenChange 回调。
### 4.7 Spin (95.58% 语句覆盖)
未覆盖行:percent='auto' 不定进度、fullscreen + spinning=false 组合。
---
## 五、测试分类统计
### 5.1 按测试类型
| 测试类型 | 用例数 | 占比 |
|---------|:------:|:----:|
| 渲染测试 | 35 | 41.2% |
| Props 组合测试 | 22 | 25.9% |
| 交互回调测试 | 18 | 21.2% |
| 状态/动画测试 | 6 | 7.1% |
| 边界条件测试 | 4 | 4.7% |
### 5.2 按组件优先级
| 优先级 | 组件 | 用例数 |
|--------|------|:------:|
| P0 | Alert, Spin, Progress, Modal, Message | 51 |
| P1 | Notification, Result | 18 |
| P2 | Drawer, Popconfirm | 16 |
---
## 六、运行命令
```bash
# 运行所有反馈组件测试
pnpm exec vitest run src/components/feedback
# 运行单个组件测试
pnpm exec vitest run src/components/feedback/alert
# 带覆盖率报告
pnpm exec vitest run src/components/feedback --coverage
# 监听模式
pnpm exec vitest src/components/feedback --watch
```
---
## 七、结论
Pika 反馈组件 9 个组件共 85 个测试用例全部通过,平均语句覆盖率 93.58%,函数覆盖率 94.71%。其中 Alert 和 Result 达到 100% 全覆盖。未覆盖部分主要为高级功能分支(渐变色、堆叠配置、多种触发模式等),核心交互路径已全部覆盖。
+660
View File
@@ -0,0 +1,660 @@
# Pika 布局组件规划
> 基于 Pika 设计规范的布局组件设计
> 设计原则:以 Apple HIG 为准,AI-First,三级别尺寸体系
---
## 一、核心理念
### 1.1 设计价值观(与根规范一致)
Pika 布局组件基于 Apple Human Interface Guidelines 的三大核心原则构建:
| 原则 | 说明 |
|-----|------|
| **层级** | 建立清晰的视觉层级,区分内容和控件 |
| **和谐** | 与硬件和软件设计保持一致 |
| **一致性** | 采用平台约定,跨组件保持统一 |
### 1.2 AI-First 设计原则
所有布局组件遵循 Pika 的 AI-First 设计原则:
| 原则 | 说明 |
|-----|------|
| **零歧义** | Props 名称自解释,无简称、无隐式行为 |
| **零默认假设** | 所有视觉行为显式声明 |
| **一致性** | 相同概念用相同 prop 名 |
| **扁平化** | 最多一层嵌套 |
| **确定性** | 相同输入始终产生相同输出 |
---
## 二、统一尺寸体系(三级别)
所有布局组件使用统一的三级别尺寸体系:
| 级别 | 间距 | 圆角 | 字体大小 |
|-----|------|------|---------|
| **small** | 8px | 4px | 12px |
| **middle** | 16px | 8px | 14px |
| **large** | 24px | 12px | 16px |
### 2.1 Design Token(与根规范一致)
```css
--nv-spacing-small: 8px;
--nv-spacing-middle: 16px;
--nv-spacing-large: 24px;
--nv-radius-small: 4px;
--nv-radius-middle: 8px;
--nv-radius-large: 12px;
--nv-radius-full: 9999px;
--nv-font-size-small: 12px;
--nv-font-size-middle: 14px;
--nv-font-size-large: 16px;
```
---
## 三、组件体系
### 3.1 组件清单
| 组件 | 说明 | 优先级 |
|-----|------|-------|
| **Layout** | 页面布局容器 | P0 |
| **Header** | 顶部导航栏 | P0 |
| **Footer** | 底部区域 | P0 |
| **Content** | 内容区域 | P0 |
| **Sider** | 侧边栏 | P0 |
| **Row** | 栅格行 | P1 |
| **Col** | 栅格列 | P1 |
| **Space** | 间距组件 | P2 |
| **HStack** | 水平排列 | P2 |
| **VStack** | 垂直排列 | P2 |
| **Divider** | 分隔线 | P2 |
---
## 四、组件详细设计
### 4.1 Layout 布局容器
#### Props 定义(AI-First
```tsx
export interface LayoutProps {
/** 是否有侧边栏(用于 SSR 避免样式闪烁) */
hasSider?: boolean
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
export interface HeaderProps {
/** 高度 */
height?: number | string
/** 是否固定在顶部 */
fixed?: boolean
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
export interface FooterProps {
/** 内边距 */
padding?: number | string
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
export interface ContentProps {
/** 内边距 */
padding?: number | string
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
export interface SiderProps {
/** 宽度 */
width?: number | string
/** 折叠后的宽度 */
collapsedWidth?: number
/** 是否可折叠 */
collapsible?: boolean
/** 当前折叠状态 */
collapsed?: boolean
/** 默认折叠状态 */
defaultCollapsed?: boolean
/** 折叠状态变化回调 */
onCollapse?: (collapsed: boolean) => void
/** 响应式断点 */
breakpoint?: 'small' | 'middle' | 'large'
/** 断点变化回调 */
onBreakpoint?: (broken: boolean) => void
/** 是否反转箭头方向 */
reverseArrow?: boolean
/** 自定义触发器 */
trigger?: React.ReactNode
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
```
#### 使用示例
```tsx
// 基础布局
<Layout>
<Layout.Header>Header</Layout.Header>
<Layout.Content>Content</Layout.Content>
<Layout.Footer>Footer</Layout.Footer>
</Layout>
// 经典后台布局
<Layout>
<Layout.Sider
width={200}
collapsedWidth={80}
collapsible
breakpoint="middle"
>
<Menu />
</Layout.Sider>
<Layout>
<Layout.Header fixed>Header</Layout.Header>
<Layout.Content padding={24}>Content</Layout.Content>
</Layout>
</Layout>
```
#### Design Token
```css
--nv-layout-header-height: 64px;
--nv-layout-footer-padding: var(--nv-spacing-middle);
--nv-layout-sider-width: 200px;
--nv-layout-sider-collapsed-width: 80px;
--nv-layout-trigger-height: 48px;
--nv-layout-content-padding: var(--nv-spacing-middle);
```
---
### 4.2 Grid 栅格系统
#### Props 定义(AI-First
```tsx
export interface RowProps {
/** 栅格间隔 */
gutter?: number | [number, number]
/** 主轴对齐方式 */
justify?: 'start' | 'end' | 'center' | 'space-around' | 'space-between'
/** 交叉轴对齐方式 */
align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch'
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
export interface ColProps {
/** 栅格格数 (0-24) */
span?: number
/** 左侧偏移格数 */
offset?: number
/** 栅格顺序 */
order?: number
/** 响应式栅格 */
small?: number | { span?: number; offset?: number; order?: number }
middle?: number | { span?: number; offset?: number; order?: number }
large?: number | { span?: number; offset?: number; order?: number }
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
```
#### 使用示例
```tsx
// 基础栅格
<Row gutter={16}>
<Col span={12}><Card /></Col>
<Col span={12}><Card /></Col>
</Row>
// 响应式布局
<Row gutter={[16, 16]}>
<Col
small={24}
middle={12}
large={6}
>
</Col>
</Row>
// 间距和对齐
<Row
gutter={[16, 16]}
justify="space-between"
align="center"
>
<Col span={8} offset={2}></Col>
<Col span={12}></Col>
</Row>
```
#### Design Token
```css
--nv-grid-gutter: var(--nv-spacing-middle);
--nv-grid-columns: 24;
```
---
### 4.3 Space 间距组件
#### Props 定义(AI-First
```tsx
export interface SpaceProps {
/** 间距方向 */
direction?: 'horizontal' | 'vertical'
/** 间距大小 */
size?: 'small' | 'middle' | 'large'
/** 对齐方式 */
align?: 'start' | 'end' | 'center' | 'baseline'
/** 是否换行(仅水平方向有效) */
wrap?: boolean
/** 分隔符 */
split?: React.ReactNode
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
```
#### 使用示例
```tsx
// 基础使用
<Space size="middle">
<Button></Button>
<Button></Button>
</Space>
// 垂直排列
<Space direction="vertical" size="small">
<Input />
<Input />
</Space>
// 带分隔符
<Space split={<Divider type="vertical" />}>
<Text>Link 1</Text>
<Text>Link 2</Text>
</Space>
// 自动换行
<Space wrap size="middle">
{tags.map(tag => <Tag key={tag}>{tag}</Tag>)}
</Space>
```
#### Design Token
```css
--nv-space-gap-small: var(--nv-spacing-small);
--nv-space-gap-middle: var(--nv-spacing-middle);
--nv-space-gap-large: var(--nv-spacing-large);
```
---
### 4.4 Stack 弹性盒布局
#### Props 定义(AI-First
```tsx
export interface StackProps {
/** 排列方向 */
direction?: 'horizontal' | 'vertical'
/** 间距 */
gap?: 'small' | 'middle' | 'large' | number | string
/** 主轴对齐方式 */
justify?: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'
/** 交叉轴对齐方式 */
align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch'
/** 是否换行 */
wrap?: boolean
/** 子元素 */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
// 便捷组件
export interface HStackProps extends Omit<StackProps, 'direction'> {}
export interface VStackProps extends Omit<StackProps, 'direction'> {}
```
#### 使用示例
```tsx
// 使用 Stack
<Stack direction="horizontal" gap="middle" justify="space-between">
<Logo />
<Nav />
</Stack>
// 使用 HStack
<HStack gap="middle" justify="space-between">
<Logo />
<Nav />
</HStack>
// 使用 VStack
<VStack gap="small" align="center">
<Avatar />
<Name />
<Desc />
</VStack>
```
---
### 4.5 Divider 分隔线
#### Props 定义(AI-First
```tsx
export interface DividerProps {
/** 方向 */
type?: 'horizontal' | 'vertical'
/** 是否虚线 */
dashed?: boolean
/** 子元素(居中文字) */
children?: React.ReactNode
className?: string
style?: React.CSSProperties
}
```
#### 使用示例
```tsx
// 水平分隔
<Divider />
// 带文字
<Divider></Divider>
// 垂直分隔
<Space split={<Divider type="vertical" />}>
<Text>Link 1</Text>
<Text>Link 2</Text>
</Space>
// 虚线
<Divider dashed />
```
#### Design Token
```css
--nv-divider-color: var(--nv-color-border);
--nv-divider-padding: var(--nv-spacing-middle);
```
---
## 五、AI-First 设计规范
### 5.1 Props 命名规范
| 规则 | 说明 |
|-----|------|
| **布尔属性** | 动词/形容词 + 肯定形式:`disabled`, `collapsible`, `fixed`, `wrap`, `dashed` |
| **事件回调** | `on + 动词 + 名词``onCollapse`, `onBreakpoint` |
| **尺寸** | 统一使用 `small`, `middle`, `large` |
| **响应式断点** | 统一使用 `small`, `middle`, `large` |
### 5.2 样式绑定规范
使用 `data-*` 属性进行样式绑定:
```tsx
<div
data-direction="horizontal"
data-size="middle"
data-wrap="true"
/>
```
### 5.3 组件导入规范
每个组件独立导入,路径即名字:
```tsx
import { Layout } from '@Pika/ui/layout'
import { Header, Footer, Sider, Content } from '@Pika/ui/layout'
import { Row, Col } from '@Pika/ui/grid'
import { Space } from '@Pika/ui/space'
import { Stack, HStack, VStack } from '@Pika/ui/stack'
import { Divider } from '@Pika/ui/divider'
```
---
## 六、完整使用示例
### 6.1 经典后台布局
```tsx
import { Layout, Row, Col, Space, HStack, VStack, Divider } from '@Pika/ui'
const App = () => {
const [collapsed, setCollapsed] = useState(false)
return (
<Layout>
<Layout.Sider
width={200}
collapsedWidth={80}
collapsible
collapsed={collapsed}
onCollapse={setCollapsed}
breakpoint="middle"
>
<Menu />
</Layout.Sider>
<Layout>
<Layout.Header fixed>
<HStack justify="space-between">
<Logo />
<UserMenu />
</HStack>
</Layout.Header>
<Layout.Content padding={24}>
<VStack gap="middle">
<PageTitle />
<Row gutter={[16, 16]}>
<Col
small={24}
middle={12}
large={6}
>
<Card />
</Col>
<Col
small={24}
middle={12}
large={6}
>
<Card />
</Col>
<Col
small={24}
middle={12}
large={6}
>
<Card />
</Col>
<Col
small={24}
middle={12}
large={6}
>
<Card />
</Col>
</Row>
<Divider />
<Space size="middle">
<Button></Button>
<Button variant="outline"></Button>
</Space>
</VStack>
</Layout.Content>
<Layout.Footer>
© 2024 Pika UI
</Layout.Footer>
</Layout>
</Layout>
)
}
```
---
## 七、文件结构
```
src/components/
├── layout/
│ ├── index.ts
│ ├── Layout.tsx
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── Sider.tsx
│ ├── Content.tsx
│ ├── Layout.module.css
│ └── Layout.test.tsx
├── grid/
│ ├── index.ts
│ ├── Row.tsx
│ ├── Col.tsx
│ ├── Grid.module.css
│ └── Grid.test.tsx
├── space/
│ ├── index.ts
│ ├── Space.tsx
│ ├── Space.module.css
│ └── Space.test.tsx
├── stack/
│ ├── index.ts
│ ├── Stack.tsx
│ ├── HStack.tsx
│ ├── VStack.tsx
│ ├── Stack.module.css
│ └── Stack.test.tsx
└── divider/
├── index.ts
├── Divider.tsx
├── Divider.module.css
└── Divider.test.tsx
```
---
## 八、优先级规划
### Phase 1: 核心布局 (P0)
| 组件 | 说明 | 预估工时 |
|-----|------|---------|
| Layout | 布局容器 | 1h |
| Header | 顶部导航栏 | 0.5h |
| Footer | 底部区域 | 0.5h |
| Content | 内容区域 | 0.5h |
| Sider | 侧边栏 | 2h |
### Phase 2: 栅格系统 (P1)
| 组件 | 说明 | 预估工时 |
|-----|------|---------|
| Row | 行容器 | 1h |
| Col | 列组件 | 1.5h |
### Phase 3: 间距和弹性盒 (P2)
| 组件 | 说明 | 预估工时 |
|-----|------|---------|
| Space | 间距组件 | 1h |
| Stack | 弹性盒容器 | 1h |
| HStack | 水平排列 | 0.25h |
| VStack | 垂直排列 | 0.25h |
| Divider | 分隔线 | 0.5h |
---
## 九、与根设计规范的对照
| 根规范 | 实现方式 |
|------|---------|
| **三级别尺寸** | `small`, `middle`, `large` 统一应用于所有组件 |
| **AI-First** | 所有 Props 使用联合类型字面量,无魔法字符串 |
| **Apple HIG** | 圆角、间距、动效遵循 Apple 规范 |
| **data-* 样式绑定** | 使用 `data-*` 属性而非 className 拼接 |
| **自包含组件** | 每个组件独立导入,路径即名字 |
| **统一事件命名** | `on + 动词 + 名词` 规则 |
---
## 十、设计总结
### Pika 布局组件的特点
1. **简洁统一** - 三级别尺寸体系,无过度设计
2. **AI 友好** - Props 自解释,类型安全,确定导入路径
3. **Apple HIG 优先** - 遵循 Apple 人机交互指南
4. **语义化** - Space, HStack, VStack 提供清晰的布局语义
5. **可扩展** - 支持响应式、折叠等高级功能
### AI 提示工程
```
1. 导入路径 = @Pika/ui/组件名 (layout/grid/space/stack/divider)
2. 尺寸 prop 始终叫 size,值从 small/middle/large 选
3. 响应式断点统一:small/middle/large
4. 布尔 prop 用动词开头 (collapsible, fixed, wrap, dashed)
5. 回调命名 = on + 动词 + 名词 (onCollapse, onBreakpoint)
6. 所有 prop 类型用联合类型字面量定义
7. 使用 Space 代替手动计算间距
8. 使用 HStack/VStack 进行弹性盒布局
```
> **Pika 布局组件:简洁、统一、AI-First**
File diff suppressed because it is too large Load Diff
+901
View File
@@ -0,0 +1,901 @@
# Pika 其他组件开发计划与规范
&gt; 基于 Pika 设计规范,参照 Ant Design 剩余组件体系,构建专为 AI 代码生成优化的其他组件库。
---
## 一、组件总览
Pika 其他组件分为以下几类:
| 类别 | 组件 | antd 对标 | 优先级 |
|------|------|-----------|--------|
| **通用组件** | Affix, Anchor, App, ConfigProvider, BackTop, Watermark | Ant Design 通用组件 | P0-P1 |
| **容器组件** | Drawer | Ant Design 抽屉 | P1 |
| **数据展示** | List | Ant Design 列表 | P1 |
---
## 二、核心设计原则(与根规范一致)
### 2.1 AI 优先设计原则
| 原则 | 说明 |
|------|------|
| **零歧义** | Props 名称自解释,无简称、无隐式行为 |
| **零默认假设** | 所有视觉行为显式声明 |
| **一致性** | 相同概念用相同 prop 名 |
| **扁平化** | 最多一层嵌套 |
| **确定性** | 相同输入始终产生相同输出 |
### 2.2 统一尺寸体系
所有组件使用统一的三级尺寸体系:
| 级别 | 说明 | 常用值 |
|------|------|-------|
| **small** | 小尺寸 | 紧凑列表、表格内 |
| **middle** | 中尺寸(默认) | 默认使用 |
| **large** | 大尺寸 | 突出显示、重点元素 |
---
## 三、通用组件
### 3.1 Affix 固钉
#### 组件定位
将页面元素固定在可视区域内,常用于导航栏、侧边栏、回到顶部按钮等。
#### Props 定义
```tsx
export interface AffixProps {
/** 距离窗口顶部达到指定偏移量后触发 */
offsetTop?: number;
/** 距离窗口底部达到指定偏移量后触发 */
offsetBottom?: number;
/** 指定 Affix 挂载的 HTML 节点 */
target?: () => HTMLElement | null;
/** 监听滚动事件的容器 */
getContainer?: () => HTMLElement | Window;
/** 固定状态改变时触发的回调 */
onChange?: (isAffixed: boolean) => void;
/** 子元素 */
children?: ReactNode;
/** 样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}
```
#### 使用示例
```tsx
import { Affix, Button } from '@Pika/ui/affix';
// 基本使用
<Affix offsetTop={10}>
<Button variant="solid"></Button>
</Affix>;
// 固钉在底部
<Affix offsetBottom={10}>
<Button variant="solid"></Button>
</Affix>;
// 自定义容器
<Affix target={() => document.getElementById('scroll-container')} offsetTop={10}>
<Button variant="solid"></Button>
</Affix>;
```
#### Design Token
```css
--nv-affix-z-index: 10;
```
#### 开发优先级:P1
---
### 3.2 Anchor 锚点
#### 组件定位
用于跳转到页面指定位置,或显示当前滚动位置。
#### Props 定义
```tsx
export interface AnchorProps {
/** 锚点方向 */
direction?: 'vertical' | 'horizontal';
/** 当前激活的锚点链接 */
activeLink?: string;
/** 锚点滚动偏移量 */
targetOffset?: number;
/** 点击锚点链接的回调 */
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { href: string; title: ReactNode }
) => void;
/** 锚点链接改变时的回调 */
onChange?: (currentActiveLink: string) => void;
/** 获取锚点容器 */
getContainer?: () => HTMLElement | Window;
/** 是否改变 hash */
hash?: boolean;
/** 子元素 */
children?: ReactNode;
/** 样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}
export interface AnchorLinkProps {
/** 锚点链接 */
href: string;
/** 锚点文本 */
title: ReactNode;
/** 子元素 */
children?: ReactNode;
}
```
#### 使用示例
```tsx
import { Anchor } from '@Pika/ui/anchor';
// 基本使用
<Anchor>
<Anchor.Link href="#section-1" title="第一章" />
<Anchor.Link href="#section-2" title="第二章">
<Anchor.Link href="#section-2-1" title="2.1 小节" />
<Anchor.Link href="#section-2-2" title="2.2 小节" />
</Anchor.Link>
<Anchor.Link href="#section-3" title="第三章" />
</Anchor>;
```
#### Design Token
```css
--nv-anchor-link-height: 32px;
--nv-anchor-link-color: var(--nv-text-color-secondary);
--nv-anchor-link-active-color: var(--nv-color-primary);
--nv-anchor-link-active-width: 2px;
--nv-anchor-wrapper-padding: 4px 0;
```
#### 开发优先级:P1
---
### 3.3 App 包裹组件
#### 组件定位
提供全局上下文,用于 Message、Modal、Notification 等组件的静态方法调用。
#### Props 定义
```tsx
export interface AppProps {
/** 子元素 */
children?: ReactNode;
}
export interface AppContextProps {
message: MessageStatic;
modal: ModalStatic;
notification: NotificationStatic;
}
export declare function useApp(): AppContextProps;
```
#### 使用示例
```tsx
import { App, Button } from '@Pika/ui/app';
// 基本使用
function AppDemo() {
const { message, modal, notification } = App.useApp();
const showMessage = () => {
message.success('操作成功');
};
const showModal = () => {
modal.info({ title: '提示', content: '这是一条提示信息' });
};
return (
<App>
<Button onClick={showMessage}></Button>
<Button onClick={showModal}></Button>
</App>
);
}
```
#### 开发优先级:P0
---
### 3.4 ConfigProvider 全局配置
#### 组件定位
为组件提供统一的全局配置,包括主题、语言、国际化等。
#### Props 定义
```tsx
export interface ConfigProviderProps {
/** 主题配置 */
theme?: {
token?: Record<string, any>;
components?: Record<string, any>;
algorithm?: 'default' | 'dark' | 'compact' | Array<'default' | 'dark' | 'compact'>;
};
/** 语言包 */
locale?: Record<string, any>;
/** 全局 zIndex */
zIndex?: number;
/** 设置主题色 */
colorPrimary?: string;
/** 设置图标前缀 */
iconPrefixCls?: string;
/** 设置组件类名前缀 */
prefixCls?: string;
/** 设置弹框的默认渲染容器 */
getPopupContainer?: (node: HTMLElement) => HTMLElement;
/** 子元素 */
children?: ReactNode;
}
```
#### 使用示例
```tsx
import { ConfigProvider, Button } from '@Pika/ui/config-provider';
// 基本使用
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
borderRadius: 8,
},
}}
>
<App />
</ConfigProvider>;
```
#### Design Token
与根设计规范中的 Token 一致。
#### 开发优先级:P0
---
### 3.5 BackTop 回到顶部
#### 组件定位
提供快速回到页面顶部的功能。
#### Props 定义
```tsx
export interface BackTopProps {
/** 距离顶部多少像素时显示 */
visibilityHeight?: number;
/** 点击回调 */
onClick?: () => void;
/** 监听滚动的容器 */
target?: () => HTMLElement | Window;
/** 子元素 */
children?: ReactNode;
/** 样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}
```
#### 使用示例
```tsx
import { BackTop } from '@Pika/ui/back-top';
// 基本使用
<BackTop />;
// 自定义图标
<BackTop>
<div style={{
height: 40,
width: 40,
lineHeight: '40px',
borderRadius: 4,
backgroundColor: '#1088e9',
color: '#fff',
textAlign: 'center',
fontSize: 20,
}}>
UP
</div>
</BackTop>;
```
#### Design Token
```css
--nv-back-top-size: 40px;
--nv-back-top-z-index: 10;
--nv-back-top-color: var(--nv-color-primary);
--nv-back-top-hover-color: var(--nv-color-primary-hover);
```
#### 开发优先级:P1
---
### 3.6 Watermark 水印
#### 组件定位
为页面或元素添加水印,防止截图泄露或证明来源。(注意:在 FEEDBACK_COMPONENTS.md 中已有定义,这里补充完整)
#### Props 定义
```tsx
export interface WatermarkProps {
/** 水印文字 */
content?: string | string[];
/** 自定义图片水印 */
image?: string;
/** 水印宽度(文字模式时为单个文字宽度) */
width?: number;
/** 水印高度(文字模式时为单个文字高度) */
height?: number;
/** 旋转角度 */
rotate?: number;
/** 水印之间的水平间距 */
gap?: [number, number];
/** 水印在 canvas 上绘制时的偏移量 */
offset?: [number, number];
/** 文字样式 */
font?: {
color?: string;
fontSize?: number;
fontFamily?: string;
fontWeight?: 'normal' | 'light' | 'weight' | number;
};
/** 水印层级 */
zIndex?: number;
/** 子元素 */
children?: ReactNode;
}
```
#### 使用示例
```tsx
import { Watermark, Card } from '@Pika/ui/watermark';
// 基本使用
<Watermark content="Pika UI">
<Card style={{ width: 600, height: 400 }}>
<p></p>
</Card>
</Watermark>;
// 多行水印
<Watermark content={['Pika UI', '内部文档']}>
<div></div>
</Watermark>;
// 自定义样式
<Watermark
content="Pika UI"
rotate={-25}
font={{
color: 'rgba(0, 0, 0, 0.08)',
fontSize: 16,
}}
>
<div></div>
</Watermark>;
```
#### Design Token
```css
--nv-watermark-font-size: 14px;
--nv-watermark-font-color: rgba(0, 0, 0, 0.08);
--nv-watermark-rotate: -22deg;
--nv-watermark-gap: [100, 100];
--nv-watermark-z-index: 9;
```
#### 开发优先级:P3
---
## 四、容器组件
### 4.1 Drawer 抽屉
#### 组件定位
抽屉组件,用于从屏幕边缘滑出内容面板,常用于详情页、表单页、筛选页等。
#### Props 定义
```tsx
export type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left';
export type DrawerSize = 'small' | 'middle' | 'large' | number | string;
export interface DrawerProps {
/** 抽屉是否可见 */
open: boolean;
/** 抽屉标题 */
title?: ReactNode;
/** 抽屉宽度(placement 为 left/right 时生效) */
width?: number | string;
/** 抽屉高度(placement 为 top/bottom 时生效) */
height?: number | string;
/** 抽屉尺寸预设 */
size?: DrawerSize;
/** 抽屉放置位置 */
placement?: DrawerPlacement;
/** 是否显示遮罩 */
mask?: boolean;
/** 点击遮罩是否关闭 */
maskClosable?: boolean;
/** 是否显示右上角关闭按钮 */
closable?: boolean;
/** 自定义关闭图标 */
closeIcon?: ReactNode;
/** 点击关闭图标的回调 */
onClose?: () => void;
/** 抽屉打开后的回调 */
afterOpenChange?: (open: boolean) => void;
/** 抽屉的容器 */
getContainer?: HTMLElement | (() => HTMLElement) | null;
/** 预渲染,防止抖动 */
forceRender?: boolean;
/** 关闭时销毁 Drawer 里的子元素 */
destroyOnClose?: boolean;
/** 抽屉的样式 */
style?: React.CSSProperties;
/** 抽屉标题的样式 */
headerStyle?: React.CSSProperties;
/** 抽屉内容的样式 */
bodyStyle?: React.CSSProperties;
/** 自定义遮罩样式 */
maskStyle?: React.CSSProperties;
/** 抽屉的类名 */
className?: string;
/** 抽屉根元素的类名 */
rootClassName?: string;
/** 是否支持键盘 esc 关闭 */
keyboard?: boolean;
/** 子元素 */
children?: ReactNode;
/** 抽屉页脚 */
footer?: ReactNode;
/** 页脚的样式 */
footerStyle?: React.CSSProperties;
/** 是否显示页脚 */
footer?: ReactNode | null;
}
```
#### 使用示例
```tsx
import { Drawer, Button, Form, Input } from '@Pika/ui/drawer';
function DrawerDemo() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}></Button>
<Drawer
title="基本抽屉"
width={480}
open={open}
onClose={() => setOpen(false)}
>
<p></p>
<p></p>
</Drawer>
</>
);
}
// 表单抽屉
function FormDrawer() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}></Button>
<Drawer
title="新建"
width={480}
open={open}
onClose={() => setOpen(false)}
footer={
<div style={{ textAlign: 'right' }}>
<Button onClick={() => setOpen(false)} style={{ marginRight: 8 }}>
</Button>
<Button variant="solid" onClick={() => setOpen(false)}>
</Button>
</div>
}
>
<Form layout="vertical">
<Form.Item label="名称" name="name">
<Input placeholder="请输入名称" />
</Form.Item>
<Form.Item label="描述" name="description">
<TextArea placeholder="请输入描述" rows={4} />
</Form.Item>
</Form>
</Drawer>
</>
);
}
```
#### Design Token
```css
--nv-drawer-z-index: 1000;
--nv-drawer-header-height: 56px;
--nv-drawer-header-padding: 16px 24px;
--nv-drawer-body-padding: 24px;
--nv-drawer-footer-padding: 16px 24px;
--nv-drawer-mask-bg: rgba(0, 0, 0, 0.45);
--nv-drawer-bg: #fff;
--nv-drawer-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
--nv-drawer-width-small: 378px;
--nv-drawer-width-middle: 480px;
--nv-drawer-width-large: 736px;
```
#### 开发优先级:P1
---
## 五、数据展示组件
### 5.1 List 列表
#### 组件定位
通用列表组件,用于展示一系列同类数据。
#### Props 定义
```tsx
export interface ListProps<T> {
/** 列表数据 */
data?: T[];
/** 列表尺寸 */
size?: 'small' | 'middle' | 'large';
/** 列表边框 */
bordered?: boolean;
/** 是否显示分割线 */
split?: boolean;
/** 列表项头部 */
header?: ReactNode;
/** 列表项底部 */
footer?: ReactNode;
/** 列表加载状态 */
loading?: boolean;
/** 空列表时显示的内容 */
empty?: ReactNode;
/** 渲染列表项 */
renderItem?: (item: T, index: number) => ReactNode;
/** 列表项布局 */
itemLayout?: 'horizontal' | 'vertical';
/** 列表行高 */
rowKey?: string | ((item: T) => string);
/** 是否显示网格 */
grid?: {
gutter?: number;
column?: number;
xs?: number;
sm?: number;
md?: number;
lg?: number;
xl?: number;
xxl?: number;
};
/** 子元素 */
children?: ReactNode;
/** 样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}
export interface ListItemProps {
/** 列表项内容 */
children?: ReactNode;
/** 列表项的额外内容 */
extra?: ReactNode;
/** 列表项的类名 */
className?: string;
/** 列表项的样式 */
style?: React.CSSProperties;
/** 列表项的 actions */
actions?: ReactNode[];
}
export interface ListItemMetaProps {
/** 头像 */
avatar?: ReactNode;
/** 标题 */
title?: ReactNode;
/** 描述 */
description?: ReactNode;
/** 类名 */
className?: string;
}
```
#### 使用示例
```tsx
import { List, Avatar, Button } from '@Pika/ui/list';
// 基本使用
const data = [
{ name: '张三', age: 25, avatar: 'https://example.com/avatar1.jpg' },
{ name: '李四', age: 30, avatar: 'https://example.com/avatar2.jpg' },
{ name: '王五', age: 28, avatar: 'https://example.com/avatar3.jpg' },
];
<List
data={data}
renderItem={(item) => (
<List.Item>
<List.Item.Meta
avatar={<Avatar src={item.avatar} />}
title={<a href="#">{item.name}</a>}
description={`年龄: ${item.age}`}
/>
</List.Item>
)}
/>;
// 网格列表
<List
grid={{ gutter: 16, column: 4 }}
data={data}
renderItem={(item) => (
<List.Item>
<Card title={item.name}>
<p>{item.age}</p>
</Card>
</List.Item>
)}
/>;
```
#### Design Token
```css
--nv-list-item-height-small: 32px;
--nv-list-item-height-middle: 40px;
--nv-list-item-height-large: 48px;
--nv-list-item-padding-small: 8px 12px;
--nv-list-item-padding-middle: 12px 16px;
--nv-list-item-padding-large: 16px 24px;
--nv-list-border-color: var(--nv-color-border);
--nv-list-split-color: var(--nv-color-border);
```
#### 开发优先级:P1
---
## 六、开发计划与优先级
### 6.1 优先级规划
#### Phase 1: 核心通用组件(P0
| 组件 | 说明 | 预估工时 |
|------|------|---------|
| App | 包裹组件,提供全局上下文 | 0.5 天 |
| ConfigProvider | 全局配置组件 | 1 天 |
#### Phase 2: 重要通用组件(P1
| 组件 | 说明 | 预估工时 |
|------|------|---------|
| Affix | 固钉组件 | 0.5 天 |
| Anchor | 锚点组件 | 0.5 天 |
| BackTop | 回到顶部 | 0.5 天 |
| Drawer | 抽屉组件 | 1.5 天 |
| List | 列表组件 | 1.5 天 |
#### Phase 3: 高级组件(P3
| 组件 | 说明 | 预估工时 |
|------|------|---------|
| Watermark | 水印组件 | 0.5 天 |
### 6.2 开发里程碑
| 阶段 | 任务 | 时间 |
|------|------|------|
| Phase 1 | App 和 ConfigProvider | 1 周 |
| Phase 2 | Affix、Anchor、BackTop、Drawer、List | 2 周 |
| Phase 3 | Watermark | 0.5 周 |
---
## 七、通用设计规范
### 7.1 Props 命名规范
```tsx
// ✅ Pika 方式
interface ComponentProps {
// 尺寸统一
size?: 'small' | 'middle' | 'large';
// 状态统一
status?: 'default' | 'success' | 'warning' | 'error' | 'info';
// 数据统一
data?: T[];
// 布尔属性:动词/形容词 + 肯定形式
showIcon?: boolean;
showClose?: boolean;
closable?: boolean;
disabled?: boolean;
loading?: boolean;
// 事件:on + 动词 + 名词
onChange?: (value: any) => void;
onClick?: (event: MouseEvent) => void;
onClose?: () => void;
onOpenChange?: (open: boolean) => void;
}
```
### 7.2 样式绑定规范
```tsx
// ✅ Pika 方式:使用 data-* 属性
<div
data-size={size}
data-status={status}
data-variant={variant}
data-loading={loading}
/>
// CSS 中使用属性选择器
.component[data-size="small"] {
font-size: 12px;
}
```
### 7.3 类型定义规范
```tsx
// ✅ 使用联合类型字面量
type Size = 'small' | 'middle' | 'large';
type Status = 'default' | 'success' | 'warning' | 'error' | 'info';
// ❌ 避免使用 string 或 any
type Size = string; // 范围不明确
type Status = any; // 没有类型提示
```
---
## 八、无障碍设计
### 8.1 ARIA 属性
```tsx
// Drawer
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title"
aria-describedby="drawer-content"
>
<h2 id="drawer-title"></h2>
<div id="drawer-content"></div>
</div>
// List
<ul role="list">
<li role="listitem"> 1</li>
<li role="listitem"> 2</li>
</ul>
```
### 8.2 键盘操作
- Drawer 支持 Esc 关闭
- 所有可点击元素支持 Enter/Space 触发
- Tab 顺序合理
### 8.3 颜色对比度
- 文本与背景对比度 ≥ 4.5:1
- 大文本对比度 ≥ 3:1
- 图标与背景对比度 ≥ 3:1
---
## 九、与 Pika 根规范的对照
| Pika 根规范 | 实现方式 |
|------------|---------|
| **AI 优先** | 所有 Props 使用联合类型字面量,无魔法字符串 |
| **三级别尺寸** | `small`/`middle`/`large` 统一应用于所有组件 |
| **统一事件命名** | `on + 动词 + 名词` 规则 |
| **布尔属性** | 动词/形容词 + 肯定形式 |
| **data-* 样式绑定** | 使用 `data-*` 属性而非 class 拼接 |
| **自包含组件** | 每个组件独立导入,路径即名字 |
| **Apple HIG** | 圆角、间距、动效遵循 Apple 规范 |
---
## 十、完整组件清单
### 10.1 已规划完成的组件
-**通用组件**: Button, FloatButton, Icon, Typography
-**数据展示**: Avatar, Badge, Calendar, Card, Carousel, Collapse, Descriptions, Empty, Image, Popover, QRCode, Segmented, Statistic, Table, Tag, Timeline, Tooltip, Tour, Tree
-**数据录入**: AutoComplete, Cascader, Checkbox, Radio, Switch, DatePicker, Form, Input, InputNumber, Mention, RangePicker, Rate, Select, Slider, Textarea, TimePicker, Transfer, TreeSelect, Upload
-**布局组件**: Divider, Flex, Grid, Layout, Masonry, Space, Splitter, Stack
-**导航组件**: Breadcrumb, Dropdown, Menu, Pagination, Steps, Tabs
-**反馈组件**: Alert, Message, Notification, Modal, Popconfirm, Spin, Progress, Result, Skeleton, Watermark
### 10.2 本规范补充的组件
- 📋 **通用组件**: Affix, Anchor, App, ConfigProvider, BackTop
- 📋 **容器组件**: Drawer
- 📋 **数据展示**: List
---
## 十一、AI 生成提示
当 AI 生成 Pika 组件相关代码时,请遵循以下提示:
```
1. 导入路径 = @Pika/ui/组件名
2. 尺寸 prop 始终叫 size,值从 small/middle/large 选
3. 数据 prop 始终叫 data
4. 布尔 prop 用动词/形容词开头
5. 所有 prop 类型用联合类型字面量定义
6. 使用 data-* 属性进行样式绑定
7. 事件回调 = on + 动词 + 名词
8. 参考 Ant Design API 但按照 Pika 规范调整命名
```
---
&gt; **Pika 其他组件计划完成!所有必要组件的规范已制定完毕。**
+697
View File
@@ -0,0 +1,697 @@
# Pika 组件库结构优化分析
> 基于当前代码库(`src/components` 约 77 个组件目录、80 个组件 TSX、74 个测试文件)的静态分析。
> 生成日期:2026-05-30
---
## 目录
1. [现状概览](#1-现状概览)
2. [目录与命名规范](#2-目录与命名规范)
3. [重复代码与可抽取的公共层](#3-重复代码与可抽取的公共层)
4. [ConfigProvider 与主题系统](#4-configprovider-与主题系统)
5. [CSS 与 Design Token 策略](#5-css-与-design-token-策略)
6. [类型系统](#6-类型系统)
7. [Props 诚实性(接口 vs 实现)](#7-props-诚实性接口-vs-实现)
8. [共享 Hooks 与工具](#8-共享-hooks-与工具)
9. [测试覆盖](#9-测试覆盖)
10. [文档与 DX](#10-文档与-dx)
11. [构建与导出](#11-构建与导出)
12. [优先级路线图](#12-优先级路线图)
13. [附录:文件清单速查](#13-附录文件清单速查)
---
## 1. 现状概览
### 1.1 分层结构(已成型,方向正确)
```
src/
├── components/
│ ├── common/ # 基础通用(Button、ConfigProvider、Icon…)
│ ├── layout/ # 布局(Layout、Flex、Grid、Space…)
│ ├── nav/ # 导航(Menu、Tabs、Breadcrumb…)
│ ├── entry/ # 数据录入(Input、Select、Form…)
│ ├── feedback/ # 反馈(Modal、Message、Alert…)
│ ├── display/ # 数据展示(Table、Card、Tooltip…)
│ └── shared/ # 内部共享(hooks、types、utils
├── theme/ # JS Token 定义
└── global.css # CSS 变量(--nv-*
```
六大分类与 Ant Design 的组件分区思路一致,有利于 AI 生成代码时的语义检索。`shared/` 作为内部基础设施层也已建立。
### 1.2 做得好的地方
| 方面 | 说明 |
|------|------|
| **组件粒度** | 每个组件独立目录,含 `*.tsx` + `*.module.css` + `index.ts` + `*.test.tsx`,结构清晰 |
| **Barrel 导出** | 分类 `index.ts` → 根 `components/index.ts``src/index.ts`,对外 API 集中 |
| **测试基线** | 主组件几乎都有 Vitest + Testing Library 测试(74 个 test 文件) |
| **forwardRef** | 布局/表单类组件普遍支持 ref 透传 |
| **data-* 属性** | 组件用 `data-*` 标记状态,利于测试与 CSS 选择器 |
| **共享 Hooks** | `useClickOutside``useScrollListener` 等已在 overlay 类组件中复用 |
### 1.3 主要问题摘要
| 类别 | 严重度 | 一句话描述 |
|------|--------|-----------|
| 遗留重复路径 | 🔴 高 | `src/components/Button/``common/Button/` 并存 |
| ConfigProvider 未接线 | 🔴 高 | 实现了 context,但业务组件几乎不消费 |
| Token 三套并行 | 🔴 高 | JS tokens / CSS `--nv-*` / 组件 `--_*` 混用 |
| Overlay 重复实现 | 🟠 中 | Tooltip 与 Popover 各 ~360 行,逻辑高度相似 |
| 类型别名泛滥 | 🟠 中 | `PikaSize` 几乎无人用,各组件自建 `*Size` |
| Props 接口超前 | 🟠 中 | 多个 prop 在类型里声明但实现缺失 |
| 文档空白 | 🟠 中 | 80 个组件仅 1 个 `index.md` |
| 构建 subpath 可能无效 | 🟡 低 | `package.json exports "./*"` 与源码结构不匹配 |
---
## 2. 目录与命名规范
### 2.1 文件夹命名两套并存
| 风格 | 示例 | 数量 |
|------|------|------|
| **PascalCase** | `common/Button/``common/ConfigProvider/` | ~10 个 |
| **kebab-case** | `display/avatar/``entry/tree-select/` | ~67 个 |
**建议:** 统一为 **kebab-case 目录 + PascalCase 文件名**,与绝大多数组件一致:
```
display/avatar/Avatar.tsx ✅ 推荐
common/Button/Button.tsx ⚠️ 目录应改为 common/button/
```
迁移成本:仅 `common/` 下 10 个目录,可一次性批量 rename + 更新 import。
### 2.2 导出模式不一致
**现状:**
```ts
// 组件文件 — 普遍 default export
export default Button
// barrel — named export
export { Button } from './Button'
```
**建议:** 组件 TSX 改为 **named export only**,与 barrel 和 tree-shaking 更一致:
```ts
// Button.tsx
export const Button = forwardRef(...)
export type { ButtonProps }
```
### 2.3 复合组件组织方式不统一
| 模式 | 示例 |
|------|------|
| 同目录多文件 | `display/avatar/Avatar.tsx` + `AvatarGroup.tsx` |
| 单目录聚合 | `entry/choice/Checkbox.tsx` + `Radio.tsx` + `Switch.tsx` |
| Object.assign | `Layout.Header``Card.Meta` |
**建议:** 制定简单规则写入 `CONVENTIONS.md`(当前已删除):
- 强关联子组件 → 同目录 + `Object.assign` 或 compound export
- 独立可选组件 → 同目录分文件
- 录入类「形态变体」→ 可聚合(如 choice)
### 2.4 遗留路径:Button 双份
| 路径 | 状态 |
|------|------|
| `src/components/common/Button/` | ✅ 现行源码,根 index 导出 |
| `src/components/Button/` | ⚠️ Git 索引中仍存在,含 `index.md`、测试 |
**行动:** 删除 `src/components/Button/`,文档迁移至 `common/Button/index.md`(或 `docs/components/button.md`)。
---
## 3. 重复代码与可抽取的公共层
### 3.1 Tooltip / Popover — 最高优先级抽取
两者共享:
- `GAP``PLACEMENT_POSITION_MAP``FLIP_MAP` 常量
- Portal 渲染 + 定位计算
- `useScrollListener` + `useClickOutside`
- 显隐状态机
```
shared/overlay/
├── Overlay.tsx # 定位 + portal + 显隐
├── placement.ts # PLACEMENT_MAP, FLIP_MAP
├── useOverlay.ts # open/close/trigger 逻辑
└── Overlay.module.css
```
预估可减少 **~300 行**重复代码,后续 Popconfirm、Dropdown 也可复用。
### 3.2 `getSemantic` 内联重复(22 处)
以下组件各自定义相同模式的 `getSemantic` helper
```
common/Button/Button.tsx
display/badge/Badge.tsx
display/card/Card.tsx
display/table/Table.tsx
display/tooltip/Tooltip.tsx
… 等 22 个文件
```
**建议:** 抽到 `shared/utils/semantic.ts`
```ts
export function getSemanticClass(
styles: Record<string, string>,
semantic?: Record<string, string>,
part: string,
): string | undefined
```
### 3.3 Portal 工具已写但未用
`shared/utils/portal.ts` 提供 `getPortalContainer` / `renderPortal`**零引用**。
Tooltip、Popover、Tour、Modal 均直接 `createPortal(..., document.body)`
**建议:** overlay 重构时统一接入,并读取 ConfigProvider 的 `getPopupContainer`
### 3.4 Table 内联分页 vs nav/Pagination
`display/table/Table.tsx` 内置 `PaginationInline`,功能仅为 prev/next + 页码,不支持:
- `showSizeChanger`
- `showQuickJumper`
- `pageSizeOptions`
**建议:** Table 直接组合 `nav/pagination/Pagination`,或抽取 `shared/PaginationMini`
### 3.5 className 拼接模式重复
大量组件使用:
```ts
[className, styles.root].filter(Boolean).join(' ')
```
**建议:** `shared/utils/classNames.ts`(或引入轻量 `clsx` 依赖):
```ts
export function cn(...parts: (string | false | undefined | null)[]): string
```
---
## 4. ConfigProvider 与主题系统
### 4.1 现状:基础设施已建,消费方缺失
`ConfigProvider` 提供:
| Context 字段 | 是否被组件消费 |
|-------------|---------------|
| `prefixCls` | ❌ CSS 硬编码 `nv-*` |
| `iconPrefixCls` | ❌ Icon 用全局 `.nv-icon` |
| `colorPrimary` / `theme.token` | ⚠️ 仅注入 wrapper inline style |
| `theme.components` | ❌ 接口存在,从未应用 |
| `locale` | ❌ 无组件读取 |
| `zIndex` | ❌ Modal/Drawer 各自硬编码 |
| `getPopupContainer` | ❌ 仅 prop 级,不读 context |
`useConfig()` 仅出现在 `ConfigProvider.tsx` 及其测试中。
### 4.2 建议接线顺序
**Phase 1 — 最小可用全局配置:**
1. 创建 `shared/hooks/usePikaContext.ts`,封装 `useConfig()` + 默认值合并
2. Overlay 组件(Tooltip、Popover、Dropdown、Modal)读取 `getPopupContainer``zIndex`
3. 所有 portal 组件 z-index 从 context 叠加(base + offset
**Phase 2 — 前缀与国际化:**
4. CSS Module 改为 `[data-nv-prefix]` 或 runtime class prefix(成本较高,可延后)
5. `locale` 接入 DatePicker、Pagination、Modal 按钮文案
**Phase 3 — 组件级主题:**
6. 实现 `theme.components.Button` 等 token 覆盖
### 4.3 Token 源统一
当前三套 token 并行:
```
┌─────────────────────────────────────────────────────────┐
│ theme/tokens.ts (JS 对象) │
│ → entry/tokens/index.ts → inline style 注入 │
├─────────────────────────────────────────────────────────┤
│ global.css (:root --nv-*) │
│ → feedback 组件 CSS var fallback │
├─────────────────────────────────────────────────────────┤
│ 组件私有 --_* (TSX inline style → CSS Module) │
│ → Button, Layout, Progress 等 │
└─────────────────────────────────────────────────────────┘
```
**问题:**
- `theme/tokens.ts``color.primary = '#6C5CE7'``global.css` 暗色模式值可能不同步
- Entry 组件 inline style **不响应** ConfigProvider 运行时改色
- `global.css` 首行 `@import './theme/dumi.css'` 把文档站样式耦合进库
**建议目标架构:**
```
theme/
├── tokens.css # 唯一 token 源(--nv-*
├── tokens.ts # 从 CSS 变量读取或生成类型(可选)
├── dark.css
└── compact.css
组件 CSS:只用 var(--nv-*),组件级 --_* 仅用于 prop 驱动的动态值(width、height
ConfigProvider:通过 style 覆盖 --nv-color-primary 等根变量
```
---
## 5. CSS 与 Design Token 策略
### 5.1 当前模式评估
| 模式 | 用途 | 评价 |
|------|------|------|
| `*.module.css` | 组件样式隔离 | ✅ 主流,正确 |
| `--nv-*` + fallback | 设计 token | ✅ 正确方向,但 fallback 硬编码过多 |
| `--_*` inline 注入 | prop 驱动动态值 | ✅ 适合 Layout/Button 的高度、宽度 |
| 全局 `Icon/style.css` | 图标字体 | ⚠️ 应改为 CSS Module 或 CSS-in-JS |
| inline style 色值 | Entry 组件 | ❌ 应迁移到 CSS var |
### 5.2 硬编码色值示例(需逐步清理)
以下文件存在与 token 并行的硬编码:
- `display/popover/Popover.module.css``#fff``rgba(0,0,0,0.85)`
- `display/collapse/Collapse.module.css` — 边框/背景硬编码
- `entry/tokens/index.ts` — 直接从 JS 对象读色,不经过 CSS 变量
### 5.3 CSS 变量命名规范建议
| 层级 | 前缀 | 示例 | 谁设置 |
|------|------|------|--------|
| 全局设计 token | `--nv-` | `--nv-color-primary` | global.css / ConfigProvider |
| 组件内部变量 | `--_{part}-` | `--_height``--_padding` | 组件 TSX inline style |
| 语义 DOM | `data-*` | `data-collapsed``data-size` | 组件 TSX |
避免 `--_*``--nv-*` 语义混淆:前者是**实例级**动态值,后者是**主题级**静态 token。
---
## 6. 类型系统
### 6.1 重复 Size / Status 类型
`shared/types/common.ts` 已定义:
```ts
export type PikaSize = 'small' | 'middle' | 'large'
export type PikaStatus = 'default' | 'success' | 'warning' | 'error' | 'info'
```
**几乎无组件引用**。各模块自建:
| 类型 | 位置 |
|------|------|
| `DataEntrySize` | `entry/common.ts` |
| `ButtonSize` | `common/Button/Button.tsx` |
| `AvatarSizeType` | `display/avatar/Avatar.tsx` |
| `TagSize`, `CardSize`, `TableSize`, `BadgeSize`… | 各 display 组件 |
**建议:**
```ts
// shared/types/common.ts — canonical types
export type PikaSize = 'small' | 'middle' | 'large'
export type PikaStatus = 'default' | 'success' | 'warning' | 'error' | 'info'
// entry/common.ts
import type { PikaSize, PikaStatus } from '../../shared/types'
export type DataEntrySize = PikaSize
export type DataEntryStatus = PikaStatus
// 组件 props
export interface ButtonProps {
size?: PikaSize
}
```
### 6.2 公共 Props 基类利用不足
`entry/common.ts` 定义了良好的基类:
- `DataEntryBaseProps`
- `DataEntryInputProps`
- `DataEntryTextualProps`
- `DataEntrySelectableProps`
**建议:** display / feedback 组件也建立类似基类:
```ts
// shared/types/component.ts
export interface PikaComponentProps {
className?: string
style?: CSSProperties
}
export interface PikaInteractiveProps extends PikaComponentProps {
disabled?: boolean
}
```
### 6.3 `CSSCustomProperties` 使用率低
`shared/types/css.ts` 仅 5 个组件 import,其余用 `Record<string, string>` 或无类型。
**建议:** 统一 cssVars 类型:
```ts
type CSSVars = CSSProperties & Record<`--${string}`, string>
```
---
## 7. Props 诚实性(接口 vs 实现)
> 接口声明了但实现缺失的 prop,会误导 AI 和使用者。应 **实现** 或 **从类型中移除**(或标注 `@deprecated` / 文档说明「即将支持」)。
### 7.1 已确认未实现的 Props
| 组件 | Prop | 现状 |
|------|------|------|
| **Layout.Sider** | `onBreakpoint` | 有 `breakpoint` data 属性,无 resize 监听 |
| **DatePicker** | `mode`, `format` | 固定 date 模式 + `YYYY-MM-DD` |
| **TimePicker** | `format`, `hourStep` | 固定格式与步进 |
| **Slider** | `range` | 仅单值滑块 |
| **TextArea** | `autoSize`, `onResize` | 未实现自动高度 |
| **Upload** | `directory`, `onPreview` | 未实现文件夹上传与预览 |
| **Popconfirm** | `okType` | 确认按钮未区分 danger/primary |
| **TreeSelect** | `treeCheckable` | 解构为 `_treeCheckable` 或未使用 |
| **Form** | `labelCol`, `wrapperCol` | 无栅格布局 |
| **Table** | `filters`, `filteredValue`, `filterMultiple` | Column 类型完整,无 filter UI |
| **Table** | `showSizeChanger`, `showQuickJumper` | PaginationConfig 有类型,内联分页未支持 |
| **Mention** | `showSearch`, `showClear` | 继承自基类但未解构 |
### 7.2 处理策略
```
优先级 A — 高频 APIAnt Design 对标)
→ 补实现:DatePicker.format、Slider.range、Layout.onBreakpoint
优先级 B — 低频 / 复杂
→ 从类型移除,CHANGELOG 记录,待迭代再加
优先级 C — 计划内
→ 保留类型,组件 JSDoc 标注 @experimental
```
### 7.3 类型笔误
`TooltipPlacement``'rightRight'`,疑似应为 `'rightBottom'``Tooltip.tsx`)。`FLIP_MAP` 映射也需核对。
---
## 8. 共享 Hooks 与工具
### 8.1 已有 Hooks 使用情况
| Hook | 路径 | 已使用 | 应使用未使用 |
|------|------|--------|-------------|
| `useClickOutside` | `shared/hooks/` | Select, Dropdown, Tooltip, Popover, Popconfirm, AutoComplete, RangePicker | **Cascader, DatePicker, TimePicker, TreeSelect, Mention** |
| `useScrollListener` | 同上 | Affix, Anchor, BackTop, Tooltip, Popover, Tour | — |
| `useEscapeKey` | 同上 | Modal, Drawer, Tour, Image | — |
| `useMatchMedia` | 同上 | Grid | **Layout.Sider**breakpoint |
### 8.2 重复实现需清理
| 组件 | 问题 | 建议 |
|------|------|------|
| `FloatButton` | 自定义 `document.addEventListener('click')` | 改用 `useClickOutside` |
| `Image` | `useEscapeKey` + 额外 `keydown` 监听 | 合并为单一 hook |
| `Tour` | `useEscapeKey` + 额外 `keydown` 处理方向键 | 扩展 hook 或统一 handler |
### 8.3 建议新增 Hooks
| Hook | 用途 |
|------|------|
| `useControllableState` | 统一 controlled/uncontrolled 模式(大量组件重复 `isControlled` 逻辑) |
| `useBreakpoint` | Layout.Sider、Grid 共用 responsive 逻辑 |
| `useMergedRef` | forwardRef + 内部 ref 合并 |
`useControllableState` 示例 — 当前 Layout、Sider、Modal、Tabs 等均有类似代码:
```ts
const isControlled = value !== undefined
const current = isControlled ? value : internal
```
---
## 9. 测试覆盖
### 9.1 覆盖率概况
- **主组件:** 几乎每个一级目录都有 `*.test.tsx`
- **子组件:** 以下 **7 个** 无独立测试:
| 子组件 | 路径 | 风险 |
|--------|------|------|
| `AvatarGroup` | `display/avatar/` | maxCount、overflow 逻辑 |
| `BadgeRibbon` | `display/badge/` | 定位、颜色 |
| `CardGrid` / `CardMeta` | `display/card/` | 栅格布局 |
| `ImagePreviewGroup` | `display/image/` | 多图预览切换 |
| `StatisticTimer` | `display/statistic/` | 倒计时逻辑 |
| `CheckableTag` | `display/tag/` | 选中态 |
### 9.2 测试质量建议
| 方向 | 说明 |
|------|------|
| **行为测试优先** | 少测 class 名,多测交互(open/close、keyboard、form submit |
| **未实现 prop 不测** | 避免测试「接口存在但行为不存在」造成 false positive |
| **ConfigProvider 集成测试** | 验证 theme/locale 注入后组件行为变化 |
| **a11y 基线** | overlay 组件补充 aria 属性断言 |
### 9.3 重复测试
`src/components/Button/Button.test.tsx``common/Button/Button.test.tsx` 可能重复 — 随遗留目录一并清理。
---
## 10. 文档与 DX
### 10.1 文档现状
| 类型 | 数量 | 路径 |
|------|------|------|
| 组件 co-located `index.md` | **1** | `components/Button/index.md`(遗留) |
| Dumi 指南 | 3 | `docs/index.md`, `getting-started.md`, `components/index.md` |
| 逐组件 API 文档 | **0** | — |
| 设计规范 | 1 | `DESIGN_SPEC.md`(完整) |
| 开发约定 | 0 | `CONVENTIONS.md` 已删除 |
### 10.2 文档建设建议
**短期(可脚本化):**
1. 为每个组件目录生成 `index.md` 模板(frontmatter + 基础 demo + Props 表占位)
2. Dumi 配置 `resolve.atomDirs` 指向 `src/components`
**中期:**
3. 从 TS 类型自动生成 Props 表(dumi-theme-Pika 或 typedoc
4. 每个组件至少 3 个 demo:基础用法、受控模式、禁用/错误态
**长期:**
5. AI 示例库 — 与 `DESIGN_SPEC.md` 的 AI-First 原则对齐,每组件提供「AI 友好」单行示例
### 10.3 开发者体验
| 改进项 | 说明 |
|--------|------|
| 恢复 `CONVENTIONS.md` | 目录命名、export 规范、CSS 约定、测试要求 |
| `npm run lint:fix` | 已有 lint-staged,可加 CI workflow |
| Storybook vs Dumi | 当前 Dumi 足够,保持单文档方案 |
| 组件生成 CLI | `pnpm gen component Button --category common` 脚手架 |
---
## 11. 构建与导出
### 11.1 当前构建链
```
father build → dist/cjs + dist/esm
src/index.ts → export * from './components' + tokens
```
### 11.2 问题
| 问题 | 详情 |
|------|------|
| **Subpath exports 可能无效** | `package.json` 声明 `"./*" → dist/esm/*/index.js`,但源码无对应分包 |
| **CSS 未作为包入口** | 消费者需自行引入 token CSS;`global.css` 未 export |
| **global.css 耦合 dumi** | `@import './theme/dumi.css'` 不应出现在库运行时入口 |
| **shared 不对外** | 第三方无法复用 hooks/utils |
| **手动维护导出面** | 新组件需改 3 处 index |
### 11.3 建议
**package.json exports 修正:**
```json
{
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
"./styles": "./dist/styles/global.css",
"./tokens": "./dist/esm/theme/tokens.js"
}
}
```
**CSS 构建:**
-`global.css` 拆为 `styles/tokens.css`(纯 token+ `styles/dumi.css`(文档专用)
- father 配置 `extraBabelPlugins` 或 postcss 复制 CSS 到 dist
**按需加载(可选):**
- 若需要 `@Pika/ui/button`,需 father 多 entry 配置 + 每组件独立 package path
---
## 12. 优先级路线图
### P0 — 基础一致性(1–2 周)
| # | 任务 | 影响 |
|---|------|------|
| 1 | 删除 `src/components/Button/` 遗留,统一至 `common/Button/` | 消除混淆 |
| 2 | 统一 `common/` 目录为 kebab-case | 命名一致 |
| 3 | Props 诚实性清理:移除或标注未实现 prop | AI 生成准确 |
| 4 | `global.css` 与 dumi.css 解耦 | 库可独立使用 |
| 5 | 新增 `shared/utils/classNames.ts` | 减少重复 |
### P1 — 架构增强(2–4 周)
| # | 任务 | 影响 |
|---|------|------|
| 6 | 抽取 `shared/overlay/` primitives | Tooltip/Popover 减 300+ 行 |
| 7 | 抽取 `getSemantic``shared/utils/semantic.ts` | 22 处重复消除 |
| 8 | ConfigProvider Phase 1 接线(zIndex、getPopupContainer | 全局配置可用 |
| 9 | 统一 Size/Status 类型为 `PikaSize` / `PikaStatus` | 类型一致 |
| 10 | Cascader/DatePicker/TreeSelect 接入 `useClickOutside` | 交互完整 |
| 11 | 新增 `useControllableState` hook | 减少状态逻辑重复 |
### P2 — 体验完善(4–8 周)
| # | 任务 | 影响 |
|---|------|------|
| 12 | Token 统一为 CSS `--nv-*` 单源 | 主题可运行时切换 |
| 13 | 补全 7 个子组件测试 | 测试完整 |
| 14 | 每组件 `index.md` + 基础 demo | 文档可用 |
| 15 | 恢复并完善 `CONVENTIONS.md` | 贡献者友好 |
| 16 | 实现高频未实现 propDatePicker.format、Slider.range | API 完整 |
| 17 | package.json exports + CSS 入口修正 | 发布可用 |
### P3 — 长期演进
| # | 任务 |
|---|------|
| 18 | 组件级 theme override`theme.components` |
| 19 | 按需 subpath import 分包构建 |
| 20 | 组件生成 CLI |
| 21 | a11y 审计与 WCAG 基线 |
| 22 | 可视化组件(AntV)与 UI 组件 Token 统一 |
---
## 13. 附录:文件清单速查
### 13.1 应删除/迁移
```
src/components/Button/ → 删除,保留 common/Button/
src/components/Button/index.md → 迁移至 docs 或 common/Button/
```
### 13.2 应统一命名(common/ PascalCase → kebab-case
```
common/Button/ → common/button/
common/ConfigProvider/ → common/config-provider/
common/FloatButton/ → common/float-button/
common/BackTop/ → common/back-top/
… (共 10 个)
```
### 13.3 共享层待建设
```
shared/overlay/Overlay.tsx
shared/utils/classNames.ts
shared/utils/semantic.ts
shared/hooks/useControllableState.ts
shared/hooks/useBreakpoint.ts
shared/hooks/usePikaContext.ts
```
### 13.4 应对接 ConfigProvider 的组件(优先)
```
display/tooltip/Tooltip.tsx
display/popover/Popover.tsx
nav/dropdown/Dropdown.tsx
feedback/modal/Modal.tsx
feedback/drawer/Drawer.tsx
feedback/message/Message.tsx
feedback/notification/Notification.tsx
display/tour/Tour.tsx
entry/select/Select.tsx
entry/date-picker/DatePicker.tsx
```
### 13.5 统计
| 指标 | 数量 |
|------|------|
| 组件目录 | ~77 |
| 组件 TSX(不含测试) | ~80 |
| 测试文件 | ~74 |
| 无独立测试的子组件 | 7 |
| getSemantic 重复 | 22 处 |
| 未实现 prop 的组件 | ~12 |
| co-located 文档 | 1 |
---
## 总结
Pika 的 **六大分类 + 单组件目录结构** 已经是一个健康的基础,测试覆盖和 forwardRef 等实践也到位。当前主要短板集中在:
1. **一致性** — 命名、导出、类型、token 三套并行
2. **诚实性** — 接口超前于实现,对 AI-First 目标伤害最大
3. **基础设施未接线** — ConfigProvider、shared utils 写了但没用起来
4. **重复** — Overlay、getSemantic、className 拼接、受控状态
按 P0 → P1 → P2 推进,可以在不推翻现有结构的前提下,显著提升可维护性和 AI 代码生成准确率。
+417
View File
@@ -0,0 +1,417 @@
# Pika 组件库测试报告
> 生成时间:2026-05-30
> 测试框架:Vitest + @testing-library/react
> 覆盖率工具:@vitest/coverage-v8
---
## 一、总体概览
| 指标 | 值 |
|------|-----|
| 测试文件数 | 63 |
| 测试用例数 | 800 |
| 通过率 | **100%** |
| 语句覆盖率 (Stmts) | 82.42% |
| 分支覆盖率 (Branch) | 76.74% |
| 函数覆盖率 (Funcs) | 58.61% |
| 行覆盖率 (Lines) | 82.42% |
---
## 二、本次新增组件测试详情
### 2.1 Affix 固钉
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/common/Affix/Affix.test.tsx` |
| 测试用例数 | 12 |
| 语句覆盖率 | 93.10% |
| 分支覆盖率 | 81.81% |
| 函数覆盖率 | 100% |
| 未覆盖行 | 51-55, 64-68 |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders with default props | ✅ |
| 2 | renders with offsetTop | ✅ |
| 3 | renders with offsetBottom | ✅ |
| 4 | does not affix without offset | ✅ |
| 5 | applies data-affixed attribute when affixed | ✅ |
| 6 | applies data-position attribute | ✅ |
| 7 | renders placeholder when affixed | ✅ |
| 8 | calls onChange when affix state changes | ✅ |
| 9 | offsetTop takes priority over offsetBottom | ✅ |
| 10 | applies data-position bottom for offsetBottom | ✅ |
| 11 | applies custom className and style | ✅ |
| 12 | uses custom getContainer for scroll listener | ✅ |
---
### 2.2 Anchor 锚点
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/common/Anchor/Anchor.test.tsx` |
| 测试用例数 | 10 |
| 语句覆盖率 | 84.69% |
| 分支覆盖率 | 84.37% |
| 函数覆盖率 | 80% |
| 未覆盖行 | 183-202, 230-233 |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders with default props | ✅ |
| 2 | renders multiple Anchor.Link items | ✅ |
| 3 | handles click events | ✅ |
| 4 | supports direction prop | ✅ |
| 5 | marks active link with data-active | ✅ |
| 6 | renders nested Anchor.Link items | ✅ |
| 7 | calls onChange when active link changes | ✅ |
| 8 | updates URL hash when hash prop is true | ✅ |
| 9 | renders ink indicator element | ✅ |
| 10 | applies custom className and style | ✅ |
---
### 2.3 App 包裹组件
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/common/App/App.test.tsx` |
| 测试用例数 | 8 |
| 语句覆盖率 | 100% |
| 分支覆盖率 | 100% |
| 函数覆盖率 | 50% |
| 未覆盖行 | — |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders children | ✅ |
| 2 | provides context via useApp | ✅ |
| 3 | provides message static methods | ✅ |
| 4 | provides modal static methods | ✅ |
| 5 | provides notification static methods | ✅ |
| 6 | throws when useApp is used outside App | ✅ |
| 7 | exposes useApp as a static method on App | ✅ |
| 8 | renders wrapper div | ✅ |
> 函数覆盖率 50% 是因为 message/modal/notification 的静态方法目前为 no-op 占位,待后续实际组件实现后补充。
---
### 2.4 ConfigProvider 全局配置
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/common/ConfigProvider/ConfigProvider.test.tsx` |
| 测试用例数 | 12 |
| 语句覆盖率 | **100%** |
| 分支覆盖率 | **100%** |
| 函数覆盖率 | **100%** |
| 未覆盖行 | — |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders children | ✅ |
| 2 | applies dark theme data attribute (string algorithm) | ✅ |
| 3 | applies dark theme data attribute (array algorithm) | ✅ |
| 4 | does not set data-theme for default algorithm | ✅ |
| 5 | applies custom primary color CSS variable | ✅ |
| 6 | applies theme token as CSS variables | ✅ |
| 7 | preserves --nv- prefix in token keys | ✅ |
| 8 | provides config via useConfig hook | ✅ |
| 9 | provides default context values | ✅ |
| 10 | passes locale and zIndex through context | ✅ |
| 11 | does not set style when no CSS variables needed | ✅ |
| 12 | passes getPopupContainer through context | ✅ |
---
### 2.5 BackTop 回到顶部
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/common/BackTop/BackTop.test.tsx` |
| 测试用例数 | 10 |
| 语句覆盖率 | 98.03% |
| 分支覆盖率 | 93.75% |
| 函数覆盖率 | 100% |
| 未覆盖行 | 72-73 |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders with default props | ✅ |
| 2 | is hidden by default (no data-visible) | ✅ |
| 3 | becomes visible after scroll exceeds visibilityHeight | ✅ |
| 4 | remains hidden when scroll is below threshold | ✅ |
| 5 | fires onClick when clicked | ✅ |
| 6 | calls window.scrollTo to scroll to top | ✅ |
| 7 | renders custom children | ✅ |
| 8 | applies custom className and style | ✅ |
| 9 | uses custom target container | ✅ |
| 10 | removes scroll listener on unmount | ✅ |
---
### 2.6 Watermark 水印
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/common/Watermark/Watermark.test.tsx` |
| 测试用例数 | 8 |
| 语句覆盖率 | 73.42% |
| 分支覆盖率 | 90.62% |
| 函数覆盖率 | 66.66% |
| 未覆盖行 | 179, 245-257, 262 |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders children | ✅ |
| 2 | renders with text content | ✅ |
| 3 | renders with multiple content lines | ✅ |
| 4 | applies custom font settings | ✅ |
| 5 | applies custom zIndex | ✅ |
| 6 | renders without content or image | ✅ |
| 7 | applies custom gap and offset | ✅ |
| 8 | applies custom rotate | ✅ |
> 覆盖率较低是因为图片水印的异步加载逻辑在 jsdom 中难以完全模拟。
---
### 2.7 Drawer 抽屉
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/feedback/drawer/Drawer.test.tsx` |
| 测试用例数 | 9 |
| 语句覆盖率 | 97.10% |
| 分支覆盖率 | 62.50% |
| 函数覆盖率 | 100% |
| 未覆盖行 | 67-69, 107-108 |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders when open is true | ✅ |
| 2 | does not render when open is false | ✅ |
| 3 | renders title | ✅ |
| 4 | calls onClose when close button clicked | ✅ |
| 5 | calls onClose when mask clicked and maskClosable is true | ✅ |
| 6 | does not close when maskClosable is false | ✅ |
| 7 | closes on ESC key when keyboard is true | ✅ |
| 8 | renders with different placements | ✅ |
| 9 | renders footer | ✅ |
---
### 2.8 List 列表
| 指标 | 值 |
|------|-----|
| 测试文件 | `src/components/data-display/list/List.test.tsx` |
| 测试用例数 | 15 |
| 语句覆盖率 | 95.98% |
| 分支覆盖率 | 91.42% |
| 函数覆盖率 | 100% |
| 未覆盖行 | 194-203 |
**测试用例清单:**
| # | 用例名 | 状态 |
|---|--------|------|
| 1 | renders with default props | ✅ |
| 2 | renders data items via renderItem | ✅ |
| 3 | renders children directly | ✅ |
| 4 | supports size prop | ✅ |
| 5 | supports bordered prop | ✅ |
| 6 | renders header and footer | ✅ |
| 7 | renders empty state when data is empty | ✅ |
| 8 | renders custom empty content | ✅ |
| 9 | renders loading state | ✅ |
| 10 | renders grid mode | ✅ |
| 11 | renders ListItem with extra | ✅ |
| 12 | renders ListItem with actions | ✅ |
| 13 | passes context from List to ListItem | ✅ |
| 14 | renders ListItemMeta with avatar, title, description | ✅ |
| 15 | applies data-size attribute | ✅ |
---
## 三、全量组件覆盖率汇总
### 3.1 通用组件 (common)
| 组件 | Stmts | Branch | Funcs | Lines |
|------|-------|--------|-------|-------|
| Affix | 93.10% | 81.81% | 100% | 93.10% |
| Anchor | 84.69% | 84.37% | 80% | 84.69% |
| App | 100% | 100% | 50% | 100% |
| BackTop | 98.03% | 93.75% | 100% | 98.03% |
| Button | 84.12% | 73.23% | 81.81% | 84.12% |
| ConfigProvider | 100% | 100% | 100% | 100% |
| FloatButton | 50.07% | 48.83% | 30% | 50.07% |
| Icon | 48.33% | 68.42% | 53.84% | 48.33% |
| Typography | 67.09% | 55% | 58.82% | 67.09% |
| Watermark | 73.42% | 90.62% | 66.66% | 73.42% |
### 3.2 数据展示组件 (data-display)
| 组件 | Stmts | Branch | Funcs | Lines |
|------|-------|--------|-------|-------|
| Avatar | 93.28% | 43.75% | 100% | 93.28% |
| Badge | 98.85% | 76.47% | 100% | 98.85% |
| Calendar | 97.59% | 80.61% | 82.35% | 97.59% |
| Card | 99.70% | 82.35% | 100% | 99.70% |
| Carousel | 88.19% | 64.51% | 66.66% | 88.19% |
| Collapse | 98.10% | 83.33% | 66.66% | 98.10% |
| Descriptions | 98.20% | 87.87% | 100% | 98.20% |
| Empty | 100% | 80% | 100% | 100% |
| Image | 87.95% | 80.35% | 50% | 87.95% |
| List | 95.98% | 91.42% | 100% | 95.98% |
| Popover | 91.39% | 82.95% | 31.25% | 91.39% |
| QRCode | 94.27% | 97.14% | 100% | 94.27% |
| Segmented | 98.04% | 83.33% | 100% | 98.04% |
| Statistic | 99.71% | 76.19% | 100% | 99.71% |
| Table | 87.99% | 59.44% | 76% | 87.99% |
| Tag | 98.75% | 85.18% | 100% | 98.75% |
| Timeline | 100% | 93.75% | 100% | 100% |
| Tooltip | 91.10% | 81.92% | 31.25% | 91.10% |
| Tour | 84.29% | 84.61% | 35% | 84.29% |
| Tree | 85.61% | 79.61% | 64% | 85.61% |
### 3.3 数据录入组件 (data-entry)
| 组件 | Stmts | Branch | Funcs | Lines |
|------|-------|--------|-------|-------|
| AutoComplete | 67.41% | 66.66% | 60% | 67.41% |
| Cascader | 68.53% | 77.27% | 37.5% | 68.53% |
| Checkbox | 98.61% | 80% | 100% | 98.61% |
| DatePicker | 56.06% | 33.33% | 8.33% | 56.06% |
| Form | 72.23% | 71.87% | 35% | 72.23% |
| Input | 71.07% | 52.63% | 0% | 71.07% |
| InputNumber | 78.91% | 47.36% | 66.66% | 78.91% |
| Mention | 64.57% | 72% | 100% | 64.57% |
| Radio | 100% | 92.85% | 100% | 100% |
| RangePicker | 0% | 0% | 0% | 0% |
| Rate | 87.70% | 56.52% | 71.42% | 87.70% |
| Select | 54.62% | 35.71% | 12.5% | 54.62% |
| Slider | 89.41% | 75.86% | 80% | 89.41% |
| Switch | 92.66% | 66.66% | 0% | 92.66% |
| TextArea | 89.71% | 50% | 100% | 89.71% |
| TimePicker | 55.13% | 45.45% | 12.5% | 55.13% |
| Transfer | 73.99% | 84.21% | 36.36% | 73.99% |
| TreeSelect | 62.05% | 91.42% | 37.5% | 62.05% |
| Upload | 58.05% | 88.23% | 17.64% | 58.05% |
### 3.4 反馈组件 (feedback)
| 组件 | Stmts | Branch | Funcs | Lines |
|------|-------|--------|-------|-------|
| Alert | 100% | 100% | 100% | 100% |
| Drawer | 97.10% | 62.50% | 100% | 97.10% |
| Message | 88.13% | 79.16% | 71.42% | 88.13% |
| Modal | 95.25% | 79.62% | 100% | 95.25% |
| Notification | 88.97% | 73.33% | 70.83% | 88.97% |
| Popconfirm | 95.13% | 71.42% | 100% | 95.13% |
| Progress | 82.05% | 59.64% | 80% | 82.05% |
| Result | 100% | 100% | 100% | 100% |
| Spin | 95.58% | 85.10% | 100% | 95.58% |
### 3.5 布局组件 (layout)
| 组件 | Stmts | Branch | Funcs | Lines |
|------|-------|--------|-------|-------|
| Layout | 100% | 80% | 50% | 100% |
### 3.6 导航组件 (nav)
| 组件 | Stmts | Branch | Funcs | Lines |
|------|-------|--------|-------|-------|
| Breadcrumb | 100% | 100% | 100% | 100% |
| Dropdown | 97.05% | 91.89% | 66.66% | 97.05% |
| Menu | 97.08% | 93.54% | 60% | 97.08% |
| Pagination | 100% | 90.47% | 77.77% | 100% |
| Steps | 100% | 100% | 100% | 100% |
| Tabs | 100% | 92% | 100% | 100% |
---
## 四、100% 覆盖率组件
以下组件实现了语句/分支/函数/行 **全部 100%** 覆盖率:
- ✅ ConfigProvider
- ✅ Alert
- ✅ Result
- ✅ Breadcrumb
- ✅ Steps
---
## 五、覆盖率改进建议
### 5.1 优先改进(覆盖率 < 60%
| 组件 | Stmts | 建议补充的测试 |
|------|-------|---------------|
| RangePicker | 0% | 完全没有测试文件,需要新建 |
| Icon (Icon.tsx) | 0% | 自定义图标组件未测试 |
| FloatButton | 50.07% | FloatButtonGroup 和 BackTop 变体未覆盖 |
| Select | 54.62% | 下拉展开/搜索/多选逻辑未覆盖 |
| DatePicker | 56.06% | 日期选择面板交互未覆盖 |
| TimePicker | 55.13% | 时间选择面板交互未覆盖 |
| Upload | 58.05% | 文件上传/拖拽/删除逻辑未覆盖 |
### 5.2 中等改进(覆盖率 60%-80%
| 组件 | Stmts | 建议补充的测试 |
|------|-------|---------------|
| AutoComplete | 67.41% | 下拉建议列表交互 |
| Cascader | 68.53% | 级联面板展开交互 |
| Form | 72.23% | 表单验证/提交/重置逻辑 |
| Watermark | 73.42% | 图片水印异步加载 |
| Input | 71.07% | 前缀/后缀/密码切换 |
| Transfer | 73.99% | 穿梭框选择/搜索逻辑 |
| InputNumber | 78.91% | 步进器增减逻辑 |
| Typography | 67.09% | 复制/编辑/省略交互 |
| Mention | 64.57% | @提及下拉交互 |
| TreeSelect | 62.05% | 树形选择展开交互 |
---
## 六、运行命令
```bash
# 运行全量测试
pnpm exec vitest run
# 运行指定组件测试
pnpm exec vitest run src/components/common/Affix
# 运行覆盖率报告
pnpm exec vitest run --coverage
# 运行 lint 检查
pnpm exec eslint src/components/
```
---
> **Pika 组件库测试报告完成。800 个测试用例全部通过,整体语句覆盖率 82.42%。**