This commit is contained in:
2026-08-04 17:57:01 +08:00
parent 768fd4a3a9
commit f77034edd7
641 changed files with 26137 additions and 0 deletions
+257
View File
@@ -0,0 +1,257 @@
# DCalc UI 统一迁移与实现标准
本文档用于在其他 Vue 项目中复用当前 `frontend-ui` 的组件库迁移方法。目标不是机械复制页面,而是在保持业务功能的前提下,统一交互底座、视觉语言、主题能力、响应式策略和验收流程。
## 1. 标准目标
- 以现有业务实现为功能基线,允许调整业务代码以适配新的组件组合方式。
- 以 `design` 的组件结构、状态、动效、素材和交互为视觉事实来源。
- 使用 Reka UI 承担无障碍、键盘、焦点、受控状态和 Portal 等交互语义。
- 使用 Tailwind CSS 处理通用布局,关闭 Preflight,避免迁移期全局样式突变。
- 使用 CSS Variables 建立 DCalc Token 和语义 Token,组件只消费语义 Token。
- 使用 CSS/SCSS 实现游戏质感、像素素材、复杂渐变、边框和状态效果。
- 参考 shadcn-vue 的目录、组合、表单和 variants 规范,不要求引入整套默认视觉。
- 使用 Oxc 统一格式化和静态检查。
## 2. 事实来源与优先级
发生冲突时按以下顺序判断:
1. 业务功能、数据边界和用户可完成的操作不能丢失。
2. `design` 中的组件结构、状态矩阵、动效和真实素材决定视觉结果。
3. Reka UI 的可访问性与交互语义决定组件的基础 DOM 和状态模型。
4. 组件 API、目录和组合方式遵循本标准。
5. 仅在不影响以上约束时使用通用 Tailwind 工具类简化布局。
不要以静态截图代替业务与交互核对。设计中未展示的状态仍需覆盖 hover、pressed、focus-visible、disabled、open/closed、checked/unchecked 和 reduced-motion。
## 3. 推荐技术栈
| 层级 | 标准职责 |
| ------------------------ | ----------------------------------------------------------- |
| Vue 3 + TypeScript | 使用 Composition API、`<script setup>` 和类型化 Props/Emits |
| Vue Router + Pinia | 保留现有路由和业务状态边界 |
| Reka UI | Primitive、焦点管理、键盘交互、受控状态、Portal |
| Tailwind CSS | 栅格、Flex、尺寸约束和简单响应式布局 |
| CSS Variables | Primitive Token、Semantic Token、主题映射 |
| CSS/SCSS | 游戏纹理、分层渐变、图片边框、复杂动效 |
| class-variance-authority | Button、Badge 等离散 variants |
| clsx + tailwind-merge | 条件 class 合并,统一由 `cn()` 暴露 |
| oxlint + oxfmt | Lint、格式化和 CI 检查 |
## 4. 推荐目录
```text
src/
├── components/
│ ├── ui/ # 无业务语义的基础组件
│ │ └── button/
│ │ ├── UiButton.vue
│ │ ├── buttonVariants.ts
│ │ └── index.ts
│ ├── domain/ # 领域组合组件
│ └── hooks/ # Dialog 等跨组件编排
├── composables/
│ └── useTheme.ts
├── lib/
│ └── utils.ts # cn()
├── pages/
│ └── ComponentCatalog/ # 全组件状态与交互目录
└── style/
├── tokens/primitives.css
├── themes/dcalc.css
├── themes/<skin>.css
├── components/
├── tailwind.css
└── index.scss
```
基础组件不得直接请求接口、读取业务 Store 或绑定具体页面路由。业务行为放在页面、领域组件、Store 或 composable 中。
## 5. Token 与换肤标准
Token 分成两层:
1. Primitive Token 保存 DCalc 的原始色值、品质颜色和基础素材色。
2. Semantic Token 表达组件用途,如 `--ui-background`、`--ui-primary`、`--ui-border`、`--ui-ring`。
组件只使用 `--ui-*` 语义变量;主题文件在 `[data-theme='<name>']` 下完成映射。新增皮肤时:
1. 新建 `style/themes/<name>.css`。
2. 完整实现现有 `--ui-*` 契约。
3. 在 Tailwind 入口中导入主题文件。
4. 在 `useTheme.ts` 注册主题名。
5. 使用组件目录页逐状态验证,不修改组件内部代码。
禁止在组件中以 `data-theme` 分支写特定皮肤逻辑。确需固定的源素材颜色时,保留物理色值,并在代码注释或 QA 文档中注明它属于素材还原而非主题语义。
## 6. 单组件迁移流程
### 6.1 盘点功能
- 找出旧组件的所有调用点、Props、Emits、插槽、v-model、快捷键和禁用条件。
- 沿调用链确认业务副作用,避免只复制组件外观。
- 记录必须保持的功能和允许调整的旧 API。
### 6.2 拆解 design
- 对照组件结构、真实尺寸、边框层、渐变、素材、文本和间距。
- 建立完整状态矩阵:rest、hover、pressed、focus、disabled、selected/open。
- 动效需记录帧数、间隔、缓动、焦点条件和 reduced-motion 行为。
- 优先复用原始 PNG、字体、图标和 rarity frame,不用近似图标替代。
### 6.3 选择 Reka Primitive
- 先使用 Reka 已有 Primitive,再包装视觉层。
- 使用 Reka 提供的 `data-state`、`data-disabled`、ARIA 和键盘模型。
- 不重复实现已有的焦点陷阱、Escape 关闭、方向键选择和焦点恢复。
- 只有 design 明确增加的行为才在包装层补充,例如 NumberField 快速步进和 Tabs 移动标记。
### 6.4 实现包装组件
标准模式:
```ts
const props = defineProps<RekaProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<RekaEmits>()
const delegatedProps = computed(() => {
const { class: _class, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
```
- 从转发对象中排除视觉包装专属 Props。
- 通过 `cn(baseClass, props.class)` 合并调用方 class。
- variants 较少且稳定时使用 CVA;动态几何或连续数值使用 computed/CSS Variable。
- 保留清晰的 `Ui<Component>` 命名和目录级 `index.ts` 导出。
### 6.5 迁移业务调用
- 将旧组件 API 映射到新组件的受控状态和事件。
- 允许调整页面或业务组件,使其符合 Reka 的组合方式。
- 每次迁移一个可验证的业务切片,旧实现只在调用点迁完后删除。
- 不在通用组件中加入仅为兼容单个旧页面的隐式逻辑。
## 7. 关键实现规则
### 7.1 Portal、Teleport 与层级
Reka 的 Select、Tooltip、DropdownMenu、Popover 等通常经过 Portal 和 Popper 包装层。Vue scoped CSS 可能只落到包装节点,而没有落到实际 Content 节点。
- 对 Portal Content 使用全局唯一的 BEM class,并采用非 scoped 样式或 `:global()`。
- 同时给 Content 和 `[data-reka-popper-content-wrapper]:has(> .<content-class>)` 设置明确层级。
- 配置 `collisionPadding`,验证视口四边的避让。
- 不能只看 DOM 中存在 listbox/tooltip;必须检查背景、边框、尺寸和最上层命中元素。
推荐层级:普通浮层 80,Tooltip 90,Dialog/Overlay 按页面统一规划,禁止无上限叠加 z-index。
### 7.2 Slider 坐标一致性
`SliderRoot` 的宽度就是 Reka 计算 Range 和 Thumb 的坐标系。数值标签、单位和标题不得放进这个坐标容器并参与其宽度。
```text
.slider-shell
├── SliderRoot.slider-control
│ ├── SliderTrack
│ │ └── SliderRange
│ └── SliderThumb
└── .slider-value
```
验收时检查 `rangeWidth / trackWidth` 是否等于当前百分比,并确认 Thumb 中心与 Range 终点一致。
### 7.3 Tabs 动态标记
- 标记归 TabsList 所有,不绑定在某个 Trigger 的伪元素上。
- 根据 active Trigger 与 List 的实际矩形计算中心位置。
- 监听 `data-state`、容器 Resize 和字体加载完成事件。
- 初次定位不播放位移动画,后续选择变化才动画。
- 在 `prefers-reduced-motion: reduce` 下移除过渡。
### 7.4 Dialog 标题动效
- 焦点态使用 design 提供的完整帧序列,不用单个渐变假装动画。
- 使用确定的数字顺序加载帧,避免字符串排序造成 `10` 排在 `2` 前。
- 仅在 focused 状态推进和展示动效;unfocused 状态使用静态标题栏。
- 组件卸载时清理计时器,reduced-motion 下隐藏或静态化动效。
### 7.5 NumberField 扩展操作
- 基础加减由 Reka NumberField 处理。
- 快速步进作为包装层显式按钮,遵守 min/max 边界并发出标准 `update:modelValue`。
- 所有图形按钮必须有可读的 `aria-label`。
### 7.6 页面滚动契约
- 从应用根到页面滚动容器的 Flex/Grid 链路必须包含 `min-height: 0`。
- 页面只能有一个主要纵向滚动所有者。
- 若 `html/body` 被旧应用设为 `overflow:hidden`,路由视图容器必须明确 `height:100%`、`min-height:0` 和 `overflow-y:auto`。
- Portal 不得渲染到会裁剪内容的局部 `overflow:hidden` 容器。
## 8. 响应式标准
区分“必须像素对齐的组件原子”和“必须流式变化的页面布局”:
- 保留 px:1px 边框、位图原始尺寸、30px 控件高度、像素箭头、帧动画和精细纹理。
- 使用流式单位:页面 gutter、列宽、面板间距、内容最大宽度和字号层级。
- 推荐 `clamp()`、`minmax(0, 1fr)`、`auto-fit`、Flex wrap 和少量语义断点。
- 宽表格和完整状态矩阵允许组件内部横向滚动,但页面本身不得产生横向溢出。
- 至少验证 1920、1280、768、390 CSS px;同时检查 200% 缩放与键盘访问。
不要为了“去除 px”而破坏游戏素材的像素精度。响应式的重点是容器、组合与内容流,而不是把所有物理尺寸换成 rem。
## 9. 组件目录页要求
每引入一个组件,都必须同步加入 `/components` 或等价目录页,并展示:
- 默认、hover、pressed、focus、disabled 等状态。
- 受控值变化和可见的结果反馈。
- 键盘操作、Escape、焦点恢复和 Portal 展示。
- 主题切换后的同一状态。
- 窄屏组合和必要的内部滚动。
- 真实业务素材,而非临时占位图。
目录页既是文档,也是运行态回归面。静态状态展示不能替代真实可交互实例。
## 10. 验证流程
### 10.1 静态检查
```sh
pnpm run format:check
pnpm run lint
pnpm run type-check
pnpm run build-only
git diff --check
```
### 10.2 浏览器检查
逐个组件验证:
1. 默认视觉和 design 对比。
2. 鼠标 hover/pressed 和键盘 focus-visible。
3. v-model、禁用态和边界值。
4. Select、Tooltip、DropdownMenu、Dialog 的层级、避让和关闭行为。
5. Slider、Tabs 等动态几何的实际端点。
6. 页面顶部到底部可滚动,且无意外横向滚动。
7. 主题切换后组件无需修改即可工作。
8. 浏览器控制台无新增 error/warning。
构建通过不是浏览器交互通过。涉及滚动、焦点、Portal、拖拽、动效或响应式的问题必须保留运行态证据。
## 11. 完成定义
一个组件只有同时满足以下条件才算迁移完成:
- 旧调用点所需功能已覆盖,业务链路可完成。
- design 的结构、状态、动效和素材已经逐项核对。
- Reka 的键盘、ARIA、焦点和受控状态没有被视觉包装破坏。
- 组件只消费语义 Token,并通过至少两个主题验证。
- 组件目录页包含真实交互和状态矩阵。
- 桌面、平板、手机宽度无页面级溢出。
- format、lint、type-check、build 和浏览器 QA 均通过。
- 尚未迁移或有意保留的差异在 QA 文档中明确记录。