diff --git a/design/CONTRACT.md b/design/CONTRACT.md new file mode 100644 index 0000000..9be0f8f --- /dev/null +++ b/design/CONTRACT.md @@ -0,0 +1,97 @@ +# 设计契约 — 岩美酒库 Flutter 还原(Phase 1 起:库存列表) + +> design-distill 阶段 1 产物。像素以 `.superpowers/prototype/` 原型为**唯一基准**; +> token 走 codegen 单源(已在 P0 落地);还原由截图 diff 验收,不靠目测。 +> 本文件是**执行真相源**(与 plan `.md` 同性质,机器/diff 引用),随逐屏推进增补。 + +## 阶段 0 · 归一化产物(已就位) + +| 项 | 位置 | 说明 | +|---|---|---| +| token 单源 | `.superpowers/prototype/tokens.css` | 三主题 `[data-theme=a/b/c]`,已 snapshot 到 `client/lib/core/theme/token_source/tokens.css` | +| token codegen | `client/lib/core/theme/tool/gen_tokens.mjs` → `app_tokens.g.dart` | 单源生成,**禁手改**;`kTokensA/B/C` + `appTokensOf(key)` | +| 实现层 | `themes.dart`(buildTheme)+ `context_tokens.dart`(`context.tokens`) | AppTokens ThemeExtension 挂三 ThemeData | +| 原型清单 | `.superpowers/prototype/screens/*.html` | 桌面 + `m-*` 移动;viewport 桌面 1280×860 / 移动 390×844 | +| 设计铁律 | `.superpowers/prototype/index.html`(atoms 注册)+ `tools/check-ds.mjs` 闸 | 颜色只走 token;组件出自已登记 atoms | +| 验收工具 | `~/.claude/skills/design-distill/tools/{shoot-prototype,diff}.mjs` | prototype shoot 已验证三主题可出图 | + +### 主题 +- **a 经典蓝**(浅,**diff 基准**) · **b 琥珀**(深) · **c 酒窖**(暖浅)。结构/密度/字阶三套统一,只换颜色。 + +--- + +## 块 1 · token 映射表(prototype CSS var → Flutter AppTokens) + +颜色字段 P0 已 1:1 落地(同名)。下表为 a 主题取值(来源 `tokens.css`,b/c 见 `app_tokens.g.dart`): + +| 原型 var | a 值 | AppTokens 字段 | 用途 | +|---|---|---|---| +| `--page` | `#E7EBF1` | `page` | 最外层底 | +| `--bg` | `#F5F7FA` | `bg` | 内容区底 | +| `--surface` | `#FFFFFF` | `surface` | 卡片/工具条/表格面 | +| `--border` / `--border-subtle` | `#DCE2EB` / `#ECEFF4` | `border` / `borderSubtle` | 描边 | +| `--text` / `--heading` / `--title` | `#232934` / `#1B2430` / `#1B2430` | `text` / `heading` / `title` | 正文/小标题/大标题 | +| `--muted` / `--faint` | `#6E7888` / `#99A3B3` | `muted` / `faint` | 次要/极弱文字 | +| `--primary` / `--primary-dark` / `--on-primary` | `#2563AC` / `#154072` / `#FFFFFF` | `primary` / `primaryDark` / `onPrimary` | 主操作 | +| `--brand400` / `--brand50` / `--accent` | `#4F86C6` / `#EEF4FB` / `#8B2331` | `brand400` / `brand50` / `accent` | 品牌/强调 | +| `--th-bg` / `--row-hover` / `--shadow` | `#F7F9FC` / `#EEF4FB` | `thBg` / `rowHover` / `shadow` | 表头/悬停/投影 | +| 顶栏 `--top-*` / 侧栏 `--side-*` / 用户菜单 `--menu-user-*` | — | `topBg…/sideBg…/menuUser*` | 外壳 chrome(P0 已迁移) | +| 状态徽章 `.badge.b-在售/预警/缺货` | success/warn/danger 系 | `success`+`okSoft` / `warn`+`warnBg` / `danger`+`dangerBg` | 状态色 | + +### ⚠️ 缺口(鲁棒还原前需补) +AppTokens 目前**仅颜色**。原型还驱动: +- **字阶** `--fs-display 28 / h1 21 / h2 17 / title 15 / body 13 / sm 12 / xs 11` +- **圆角** `--r-sm 4 / md 6 / lg 10 / xl 14 / pill 999` +- **间距** kpis gap 14、toolbar pad 12/14、head mb 18、searchbox 240/180 等 + +→ 还原前需把字阶/圆角(必要时间距)补进 token 单源 + codegen(P1 第一步,**禁页面里写死 px**)。 + +--- + +## 块 2 · 组件清单(原型 atom → Flutter widget) + +| 原型 atom | Flutter | props / 状态 | 现状 | +|---|---|---|---| +| `.top`(顶栏,品牌+门店+主题+通知+用户) | `shell/app_shell.dart` 顶栏 | active 项、主题选择器、通知红点、用户菜单 | 已有,需对齐新视觉 | +| `.side`(分组导航 单据/经营/系统 + 用户页脚) | `shell/app_shell.dart` 侧栏/Drawer | 分组、active、窄屏 Drawer | 已有,需分组化 | +| `.kpi` / `.kpi.alert.click`(KPI 卡 + 图标 + 同比) | **新** `widgets/kpi_card.dart` | icon/title/value/delta(up/down)/onTap/alert | **缺**,需新建 | +| `.toolbar` + `.searchbox` + `.chip`(筛选条) | `widgets/` 复用 + 列头 chip | 搜索/编码/状态 chip/重置 | 部分有 | +| `.table` + `.fh.filtered`(列头漏斗筛选) | `widgets/data_table_card.dart` + `FilterableColumnHeader` | 列头内嵌筛选、可隐藏列、`thBg` 表头 | 已有 | +| `.badge.b-*`(状态徽章 圆点+色) | `widgets/status_badge.dart` | 在售/预警/缺货 → ok/warn/danger | 已有,校色 | +| `.qty.low`(低库存染色 qty≤10) | data row 单元 | qty≤min_stock 染 danger | 需补 | +| `.pager` + `.pgsize-btn`(分页 + 每页条数) | `widgets/` 分页 | 页码/每页 10/20/50/100 | 需对齐 | +| `.ds-fab`(设计期返回球) | — | 仅原型,不还原 | 不实现 | + +--- + +## 块 3 · 页面像素规格 — 库存列表(`screens/inventory.html`,桌面 1280×860) + +数值取自原型源码(`inventory.html` 内联 + `atoms.css`/`tokens.css`),非目测。 + +- **head**:`align-items:flex-end; gap:14; margin-bottom:18`。`h1` `fs-h1(21)/700/title/letter .3px`;`.sub` `fs-sm(12)/muted`。`.actions` 右对齐 gap 10:`列设置`(ghost) `导出`(ghost) `新增入库`(primary)。 +- **kpis**:`grid 4×1fr gap:14 margin-bottom:20`。每卡:左 `.ic`(圆角 `r-xl` 色块 info/ok/blue/alert)+ `.t`(fs-sm muted) + `.v`(大数值) + `.d`(同比 up=success▲ / down=danger▼)。第 4 卡 `alert.click` → 点击筛状态=缺货;第 1 卡 click → 清筛选。 +- **toolbar**:`surface` 面、`border` 无下边、上圆角 `r-lg`、`pad 12/14 gap 10`。`searchbox` 240(商品名/拼音);`searchbox.code` 180(商品编码);`chip` 状态(`.on` 高亮 + 选中值);右侧 `重置`(ghost sm)。 +- **table**(`flush-top` 接 toolbar):表头 `thBg`;列见块 4。行 `click` 进商品抽屉;`qty≤10` 染 `low`(danger);`生产日期` 等宽 `text`、`入库时间` 等宽 `muted`;`status` → `.badge.b-{在售|预警|缺货}`;`act` 列 眼睛图标查看。空态 `.empty「没有匹配的商品 · 试试调整筛选或搜索」`。 +- **pager**:`每页 [10▾] 条`(10/20/50/100 菜单) + `显示 a–b,共 N` + 页码 `‹ 1 2 … ›`,当前页 `.cur`。 +- **移动**(`m-inventory.html`,390×844):表格 → `MobileListCard` 卡片流(复用 `mobile_list_card.dart`);筛选 pill 换行不横滚(原型已修)。 + +### 列定义(顺序 = 还原顺序) +`商品`(name+code 两行,fixed) · `规格` · `系列` · `批次号` · `库存`(num,≤10染色) · `成本价`(num) · `单价`(num) · `生产日期`(mono) · `入库时间`(mono,muted) · `供应商` · `状态`(filter) · `操作`(fixed) +> 真实后端字段对齐:name=品牌 / series=型号 / spec=版本 / code=商品编号 / batch_no / production_date;状态派生 qty vs `products.min_stock`(在售 / 预警 / 缺货)。快照列回退 `COALESCE(p.*, 快照)`。 + +--- + +## 块 4 · 逐屏验收清单(每屏 × 主题;diff 闸) + +| 屏 | 原型 | Flutter | a(基准) | b | c | +|---|---|---|---|---|---| +| 库存列表(桌面) | `inventory.html` | `inventory_list_screen.dart` | ☐ 像素 diff ≤阈 | ☐ 结构 | ☐ 结构 | +| 库存列表(移动) | `m-inventory.html` | 同上窄屏 | ☐ | ☐ | ☐ | + +**验收法(HTML→Flutter 特例)**:HTML(Chromium) 与 Flutter 渲染器不同,**严格 0.1% pixelmatch 不适用跨渲染器**。本项目采用: +1. **原型基准**:`shoot-prototype.mjs .html out.png --width 1280 --height 860 --theme a|b|c`(已验证)。 +2. **Flutter 实现图**:golden / integration_test 截目标屏 → png。 +3. **a 主题**:原型 vs Flutter **并排人工目检** + 关键区域结构 diff(容差放宽);**b/c**:Flutter 三主题自比,确认 chrome/表头/徽章随主题换色、无残留硬编码。 +4. token 残留闸:`grep 0xFF…` 该屏为 0(颜色全走 `context.tokens`)。 + +> 后续屏(商品/单据/往来/财务/系统域)逐个追加块 3 规格 + 块 4 行,一屏一闭环,禁积压。