Per user request: split the development process into per-topic docs under docs/dev-log/. Each entry follows the same structure: What was asked → Problems → How solved Files: - README.md index - 01-initial-scope-and-design.md - 02-ui-primitives-api-design.md - 03-monorepo-scaffold.md - 04-ui-primitive-impl.md - 05-drag-and-drop.md - 06-three-games.md - 07-bugfixes-round-1.md - lessons-learned.md all gotchas consolidated - decisions.md locked decisions with dates Total: 1071 lines across 10 files.
2.7 KiB
2.7 KiB
04 · UI 原语实现
做什么
实现 <Board> <Zone> <Card> <Token> <ActionButton> 五个组件,让它们按 game.ui schema 驱动渲染。
遇到什么问题
问题 1:Board 怎么知道 zone 持有什么实体?
zone 定义是 UI 层的(位置、类型),但哪些 card/tokens 在哪个 zone 是数据层的。
解决:引入 collection 字段约定:
{ id: 'p0-deck', collection: 'p0Deck', ... }
Board 调 resolveZoneEntities(zone, G) → 读 G[collection](ID 数组)+ G[entityField ?? 'drawPile'](ID → 实体映射)。
问题 2:手牌区怎么扇形展示?
spacing 和 width 谁先决定?人手少时扇形,人多时网格?
解决:fan type 内部用三角函数算 offset:
const fanAngle = layout?.fan ?? 30;
const spacing = layout?.spacing ?? 30;
const half = (entities.length - 1) / 2;
const offset = (i - half) * spacing;
const rot = (i - half) * (fanAngle / Math.max(half, 1));
问题 3:pile / discard 渲染策略不同
pile(牌堆):只显示顶牌 + 数量徽章discard(弃牌堆):同 pile,但可能面朝上area(区域):所有牌铺开hand(手牌):扇形展开
解决:在 Zone 组件按 type 分支渲染。
问题 4:SVG 在 React 里直接渲染
用 dangerouslySetInnerHTML 注入 SVG 字符串(来自 template.front(card))。
风险:v1 接受,因为游戏定义是可信方写的;如果 v2 接入第三方游戏,需要 SVG 沙箱。
怎么解决
组件分工
<Board>
├─ 遍历 game.ui.zones
│ └─ <Zone def={...} entities={resolveZoneEntities(def, G)}>
│ ├─ <Card> / <Token> 渲染
│ └─ <ZoneDebugLabel>(debug 模式下)
├─ children(自定义 UI 覆盖层)
└─ 点击空白区域(清空选择)
完整文件
packages/protocol/src/index.ts— 共享类型 +resolveZoneEntitieshelperpackages/ui/src/Board.tsx— 顶层容器packages/ui/src/Zone.tsx— 区域容器(6 种 type)packages/ui/src/Card.tsx— 单卡(SVG 渲染)packages/ui/src/Token.tsx— 单 tokenpackages/ui/src/ActionButton.tsx— 触发 move
验证
pnpm -r ts:6 个包全部通过pnpm --filter @tts-like/web build:成功(283 KB / 90 KB gzip)pnpm --filter @tts-like/web dev:HTTP 200
成果
- Commit
b2d6236 feat(ui): render zones/cards/actions from game.ui schema - War 游戏的 4 个 pile zone + 1 个翻牌按钮都能在浏览器里看到
- 切到拖拽前的最后状态
关联
- 05-drag-and-drop.md — 接下来加拖拽
- 06-three-games.md — 三个游戏靠这些原语 + 拖拽