cardbuddy 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/init.js +0 -2
- package/dist/preview-editor/assets/index-CXDf5tVg.css +1 -0
- package/dist/preview-editor/assets/index-DLQkCPLT.js +337 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/vendored/server/core/browser.js +17 -0
- package/dist/vendored/server/core/cardFiles.js +49 -1
- package/dist/vendored/server/core/cardImage.js +103 -2
- package/dist/vendored/server/core/screenshot.js +94 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +7 -7
- package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +1 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +7 -2
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +14 -0
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +48 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +21 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +48 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +41 -4
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +57 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +17 -3
- package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +8 -1
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +46 -2
- package/dist/vendored/skills/mbank-design-gen/SKILL.md +76 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-apps.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-arrow-right-blue.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-check.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-settings.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-sound.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-voice-switch.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/progress-thumb.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/tag-bg.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/vs-badge.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-chat-options.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-home-scene.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-product-detail.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-rate-table.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/robot.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/references/component-library.md +127 -0
- package/dist/vendored/skills/mbank-design-gen/references/design-spec.md +261 -0
- package/dist/vendored/skills/mbank-design-gen/templates/mockup-template.html +138 -0
- package/dist/vendored/skills/mbank-design-gen/tools/screenshot.mjs +34 -0
- package/dist/vendored/skills/shared/spec-contract.md +10 -1
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +1 -1
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +30 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +25 -3
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +15 -3
- package/dist/vendored/templates/helloCard/schema.json +62 -1
- package/dist/vendored/tools/finmall-env.mjs +1 -18
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-BkbBxDTT.css +0 -1
- package/dist/preview-editor/assets/index-Bro07DTa.js +0 -313
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# MBank 设计规范(design-spec)
|
|
2
|
+
|
|
3
|
+
> 唯一权威 token 源。由两套银行 AI 助手设计稿(贷款助手 + 存款助手,MeaXure 导出,45 个画板)程序化采样提炼。
|
|
4
|
+
> 单位约定:正文数值一律 **pt(@1x 设计值)**,导出/截图按 @2x ×2 换算为 px。画布 375pt 宽。
|
|
5
|
+
> 组件级细节见 `references/component-library.md`;生成工作流见 `SKILL.md`。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 卡片设计规范(圆角 / 阴影 / 边框 / 内边距)
|
|
10
|
+
|
|
11
|
+
### 圆角梯度(radius scale)
|
|
12
|
+
|
|
13
|
+
| Token | 值 | 使用场景 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `radius-xs` | 4pt | 标签底、小色块 |
|
|
16
|
+
| `radius-sm` | 8pt | 进度条、小按钮、行内元素 |
|
|
17
|
+
| `radius-md` | 12pt | **聊天气泡**、列表条目卡、内嵌面板 |
|
|
18
|
+
| `radius-lg` | 20pt | **标准卡片**(白卡/奶油卡/表格卡/选项容器)、首页大卡 |
|
|
19
|
+
| `radius-xl` | 22pt | 特大底部面板/弹层 |
|
|
20
|
+
| `radius-pill` | 100pt | 胶囊按钮、建议气泡、底部输入条 |
|
|
21
|
+
| `radius-circle` | 50% | 圆形图标钮、序号圆点、头像 |
|
|
22
|
+
|
|
23
|
+
### 气泡非对称圆角(对话强特征,必须遵守)
|
|
24
|
+
|
|
25
|
+
- **AI 气泡**(左侧):`12pt 12pt 12pt 0`(左上直角,其余 12)
|
|
26
|
+
- **用户气泡**(右侧):`12pt 0 12pt 12pt`(右上直角,其余 12)
|
|
27
|
+
|
|
28
|
+
### 阴影(轻投影,整体偏「浮」不偏「重」)
|
|
29
|
+
|
|
30
|
+
| Token | 值 | 使用场景 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `shadow-float` | `0 2px 6px rgba(134,155,168,0.5)` | 悬浮卡片、首页大卡 |
|
|
33
|
+
| `shadow-soft` | `0 2px 10px rgba(0,0,0,0.2)` | 弹层、模态 |
|
|
34
|
+
| `shadow-none` | 无 | 常规列表卡、表格卡(默认无阴影) |
|
|
35
|
+
|
|
36
|
+
### 边框
|
|
37
|
+
|
|
38
|
+
- 常规卡片**无边框**,靠背景色差分区。
|
|
39
|
+
- 描边仅两处:建议气泡 `1pt solid #2F86F6`;分隔线 `1pt solid #E9ECF2`(列表内可用 #E9E9E9)。
|
|
40
|
+
|
|
41
|
+
### 内边距
|
|
42
|
+
|
|
43
|
+
| 容器 | padding |
|
|
44
|
+
|---|---|
|
|
45
|
+
| 标准卡片 | 16pt |
|
|
46
|
+
| 聊天气泡 | 12pt 16pt |
|
|
47
|
+
| 列表条目卡 | 14pt 16pt |
|
|
48
|
+
| 选项容器(包裹层) | 12pt 16pt 16pt |
|
|
49
|
+
| 底部输入条 | 12pt 16pt(左右) |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. 尺寸规范(画布 / 卡片 / 组件标准尺寸与断点)
|
|
54
|
+
|
|
55
|
+
### 画布
|
|
56
|
+
|
|
57
|
+
- 基准:**375 × 812pt**(iPhone X),导出 @2x = **750 × 1624px**。
|
|
58
|
+
- 纵向自适应:内容撑高,最小 812pt;底部固定区(输入条 + Home Indicator)不随内容滚动。
|
|
59
|
+
- 单一移动端断点;本规范不定义平板/桌面断点。内容最大宽度 = 画布宽(无居中限宽)。
|
|
60
|
+
|
|
61
|
+
### 页面骨架尺寸
|
|
62
|
+
|
|
63
|
+
| 区域 | 尺寸 |
|
|
64
|
+
|---|---|
|
|
65
|
+
| 状态栏 | 高 44pt |
|
|
66
|
+
| 顶部导航(此刻/对话 tabs + 声音/设置) | 高 44pt |
|
|
67
|
+
| 页面左右安全边距 | 16pt(聊天气泡区 12pt) |
|
|
68
|
+
| 底部输入条 | 输入胶囊高 36pt,圆钮 44×44pt |
|
|
69
|
+
| Home Indicator | 高 34pt,黑色横条宽 ~134pt 居中 |
|
|
70
|
+
|
|
71
|
+
### 组件标准尺寸
|
|
72
|
+
|
|
73
|
+
| 组件 | 尺寸 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| 全宽卡片 | 宽 343pt(375 − 2×16),高按内容 |
|
|
76
|
+
| 首页双列促销卡 | 宽 ~166pt,两列 gap 12pt |
|
|
77
|
+
| 全宽主按钮 | 343 × 48pt,radius 24pt |
|
|
78
|
+
| 胶囊小按钮 | 高 32pt,左右 padding 16pt |
|
|
79
|
+
| 建议气泡 pill | 高 32pt,padding 0 16pt |
|
|
80
|
+
| 选项列表条目 | 高 ≥56pt(单行) |
|
|
81
|
+
| 表格行 | 高 44pt |
|
|
82
|
+
| 进度条 | 高 4pt,圆形 thumb 直径 16pt |
|
|
83
|
+
| 直播徽标 | 高 16pt,radius-pill |
|
|
84
|
+
| 吉祥物头像 | 直径 48–64pt |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. 设计节奏(间距梯度 / 栅格)
|
|
89
|
+
|
|
90
|
+
### 间距梯度(4pt 基数)
|
|
91
|
+
|
|
92
|
+
`4 / 8 / 12 / 16 / 20 / 24 / 32`
|
|
93
|
+
|
|
94
|
+
| 关系 | 值 |
|
|
95
|
+
|---|---|
|
|
96
|
+
| 图标↔文字(行内) | 8pt |
|
|
97
|
+
| 气泡↔气泡(纵向) | 12pt |
|
|
98
|
+
| 卡片↔卡片(纵向) | 12–16pt |
|
|
99
|
+
| 卡内标题↔正文 | 8pt |
|
|
100
|
+
| 卡内正文↔按钮 | 12pt |
|
|
101
|
+
| 区块↔区块(大分组) | 24pt |
|
|
102
|
+
| 页面顶部首个内容 | 16–24pt |
|
|
103
|
+
|
|
104
|
+
### 栅格
|
|
105
|
+
|
|
106
|
+
- **双列网格**:首页促销卡 2 列,列宽相等,gap 12pt,边距 16pt。
|
|
107
|
+
- **单列流**:对话内容与数据卡全为单列流式,宽 343pt(气泡区 351pt)。
|
|
108
|
+
- 对齐基线统一 4pt 网格;所有测量值必须是 4 的倍数(例外:图标 15/17 等特殊尺寸允许)。
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 4. 配色规范
|
|
113
|
+
|
|
114
|
+
### 主色(品牌蓝系)
|
|
115
|
+
|
|
116
|
+
| Token | 值 | 使用场景 |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `color-primary` | `#2F86F6` | 主按钮、选中态、进度条、用户气泡基色 |
|
|
119
|
+
| `color-primary-deep` | `#005DFD` | 用户气泡渐变深端、强调 |
|
|
120
|
+
| `color-primary-bright` | `#3B73FF` | 链接、可点击文本、蓝色图标 |
|
|
121
|
+
| `primary-gradient` | `linear-gradient(90deg,#2F86F6,#005DFD)` | 用户气泡、主 CTA |
|
|
122
|
+
|
|
123
|
+
### 辅助色(营销/情绪)
|
|
124
|
+
|
|
125
|
+
| Token | 值 | 使用场景 |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `color-red` | `#F3464A` | 利率数字、警示、直播徽标、对比卡左方 |
|
|
128
|
+
| `color-orange` | `#F09404` | 活动强调、利息/收益提示 |
|
|
129
|
+
| `color-orange-deep` | `#EE6723` | 橙色 CTA 渐变深端(如 `linear-gradient(90deg,#F09404,#EE6723)`) |
|
|
130
|
+
| `color-green` | `#68C800` | 达成/对勾、筛选条件绿勾 |
|
|
131
|
+
|
|
132
|
+
### 中性色(文字 + 背景 + 线)
|
|
133
|
+
|
|
134
|
+
| Token | 值 | 使用场景 |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `text-1` | `#1F2635` | 主文字(冷调深灰蓝) |
|
|
137
|
+
| `text-1-warm` | `#221715` | 暖调标题(首页问候/营销卡,与暖背景同源) |
|
|
138
|
+
| `text-2` | `#727885` | 次要文字、说明、未激活 tab |
|
|
139
|
+
| `text-3` | `#AAAAAA` | 占位符、脚注、极弱信息 |
|
|
140
|
+
| `bg-page` | `#F2F3F5`(≈`#EFF1F4`) | 页面基底 |
|
|
141
|
+
| `bg-warm-panel` | `#FEFBF7` | AI 气泡(暖白) |
|
|
142
|
+
| `bg-cream` | `#FFF7E8 → #FEF6ED` | 奶油促销卡渐变 |
|
|
143
|
+
| `bg-blue-tint` | `#EDF5FF` | 标题高亮、淡蓝衬底 |
|
|
144
|
+
| `bg-gray-block` | `#F5F6F8` / `#F5F5F5` | 浅灰信息块、对比表中列 |
|
|
145
|
+
| `divider` | `#E9ECF2`(列表内 `#E9E9E9`) | 分隔线 |
|
|
146
|
+
|
|
147
|
+
### 页面氛围渐变(全 App 签名特征,必须还原)
|
|
148
|
+
|
|
149
|
+
顶部氛围光 + 下方渐隐:
|
|
150
|
+
|
|
151
|
+
```css
|
|
152
|
+
background:
|
|
153
|
+
radial-gradient(ellipse 60% 30% at 0% 0%, #DBE3F5 0%, transparent 70%), /* 左上冷蓝 */
|
|
154
|
+
radial-gradient(ellipse 60% 30% at 100% 0%, #EEE6D9 0%, transparent 70%), /* 右上暖奶油 */
|
|
155
|
+
linear-gradient(180deg, #E4E2E4 0%, #EFF1F4 30%, #F7F8FA 70%, #FBFCFC 100%);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
要点:**左冷右暖**的双色晕染只出现在首屏顶部 ~30%,向下收敛为浅灰;对话长页与首页共用。
|
|
159
|
+
|
|
160
|
+
### 语义色使用规则
|
|
161
|
+
|
|
162
|
+
- 金额/利率涨跌与关键数字:**红 = 值得关注/收益**(`#F3464A`),蓝 = 中性数据(品牌蓝)。
|
|
163
|
+
- 成功/达成 = 绿 `#68C800`;警告/提醒 = 橙 `#F09404`;错误/禁止 = 红 `#F3464A`。
|
|
164
|
+
- 禁止把品牌蓝用于语义警示;禁止在暖色营销卡上用蓝色文字(对比冲突)。
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 5. 层级规范(字号 / 字重 / 行高 / 信息层级)
|
|
169
|
+
|
|
170
|
+
### 字体族
|
|
171
|
+
|
|
172
|
+
- 中文/正文:`PingFang SC`(Regular 400 / Medium 500 / Semibold 600)
|
|
173
|
+
- 数字/金额:`DIN Alternate Bold`;数据表数值可用 `SF Pro Text Semibold`
|
|
174
|
+
- 回退栈:`-apple-system, "PingFang SC", "Helvetica Neue", sans-serif`
|
|
175
|
+
|
|
176
|
+
### 字阶(7 级)
|
|
177
|
+
|
|
178
|
+
| Token | 字号/行高 | 字重 | 使用场景 |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| `font-hero` | 24 / 34 | Semibold | 首页问候语、大额金额(¥298.69) |
|
|
181
|
+
| `font-display` | 20 / 28 | Semibold + DIN | 金额、核心数值(¥6,000.00) |
|
|
182
|
+
| `font-title` | 18 / 26 | Semibold | 卡片标题、区块题 |
|
|
183
|
+
| `font-nav` | 17 / 24 | Medium | 导航 tab(激活)、大按钮 |
|
|
184
|
+
| `font-body-em` | 16 / 24 | Medium | 卡片条目标题、表格卡标题 |
|
|
185
|
+
| `font-body` | 15 / 24 | Regular | **正文基准**:气泡正文、表格行、列表 |
|
|
186
|
+
| `font-caption` | 14 / 22 | Regular | 辅文、副标题、按钮小字 |
|
|
187
|
+
| `font-footnote` | 13 / 20 | Regular | 脚注、日期行 |
|
|
188
|
+
| `font-note` | 12 / 18 | Regular | 免责/备注(`text-3`) |
|
|
189
|
+
| `font-badge` | 10 / 14 | Medium | 徽标(直播)、角标 |
|
|
190
|
+
|
|
191
|
+
### 信息层级规则
|
|
192
|
+
|
|
193
|
+
1. **数字大于文字**:金额/利率永远比同区块标题大(20–24pt vs 16–18pt)。
|
|
194
|
+
2. 一屏最多一个 `font-hero`;区块内强调只用字重 + 语义色,不新增字号。
|
|
195
|
+
3. 辅助说明统一降两级(正文 15 → 备注 12–13)并换中性灰,不缩字号凑层级。
|
|
196
|
+
4. 行高:标题 1.3–1.45,正文 1.5–1.6;数字用 DIN 时行高取 1.2 并与基线对齐。
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 6. 布局规范(对齐 / 栅格 / 响应式规则)
|
|
201
|
+
|
|
202
|
+
- **整体轴向**:单列纵向流;无侧栏、无横滑布局(首页双列卡除外)。
|
|
203
|
+
- **对齐**:标题/正文左对齐(默认);数据值(金额/利率)与其标签左对齐成组,或表格行内「标签左、值右」两端对齐;气泡按角色左右贴边分流。
|
|
204
|
+
- **底部固定**:输入条 + Home Indicator 固定底部,内容区从其上方滚动;输入条上方留 12pt 间距。
|
|
205
|
+
- **响应式规则**(内容自适应而非多断点):
|
|
206
|
+
1. 文本气泡宽度 `max-width: 75%`,超出换行,高度自适应。
|
|
207
|
+
2. 卡片高度由内容撑开,不写死高度;同类卡片等宽对齐。
|
|
208
|
+
3. 长页(如产品详情 812→1421pt)只向下增长,骨架区域(导航/输入条)尺寸不变。
|
|
209
|
+
4. 双列网格内容超长时截断省略(`…`),不挤压列宽。
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 7. 风格化(视觉语言 / 质感 / 图标与插画)
|
|
214
|
+
|
|
215
|
+
### 视觉语言一句话
|
|
216
|
+
|
|
217
|
+
**「暖调 AI 管家」**:冷蓝品牌色承载功能与信任,暖奶油/橙承载营销与温度;扁平 UI 面板 + 3D 轻拟物插画混搭,氛围光背景消除生硬感。
|
|
218
|
+
|
|
219
|
+
### 质感
|
|
220
|
+
|
|
221
|
+
- UI 面板全扁平:纯色/线性渐变填充 + 大圆角,**无内阴影、无描边、无噪点**。
|
|
222
|
+
- 插画区轻拟物 3D:柔光、软投影、轻微光泽(glossy),禁止硬边扁平插画与线框风。
|
|
223
|
+
- 氛围:柔和、透气、留白充足;背景氛围光让白卡「浮」在页面上。
|
|
224
|
+
|
|
225
|
+
### 插画风格(3D 金融物件)
|
|
226
|
+
|
|
227
|
+
- 题材:金币、存钱罐、沙漏、银行卡、计算器、进度台座等金融符号。
|
|
228
|
+
- 色域:暖金 `#F6CAA3` / 橙 `#F09404` / 深橙 `#EE6723` 为主,点缀品牌蓝卡片元素。
|
|
229
|
+
- 构图:物件置于卡片右上角或顶部,**允许溢出卡片边缘**(打破边界是设计特征)。
|
|
230
|
+
- 生成关键词(ImageGen 用):`3D render, soft studio light, glossy financial objects, gold coins, warm orange palette, soft shadows, isolated on transparent background, C4D style, octane render`
|
|
231
|
+
|
|
232
|
+
### 吉祥物
|
|
233
|
+
|
|
234
|
+
- 白色圆脸机器人(`assets/robot.png`,168×168 RGBA):极简面部,橙红色圆腮红/耳点,无复杂五官。
|
|
235
|
+
- 用途:首页问候头像、AI 消息头像。保持原始素材,不重绘。
|
|
236
|
+
|
|
237
|
+
### 图标
|
|
238
|
+
|
|
239
|
+
- 导航图标:黑色细线性描边(声音、设置,24pt 网格,2pt 描边)。
|
|
240
|
+
- 功能图标:品牌蓝填充或蓝描边(右箭头、对勾)。
|
|
241
|
+
- 素材直接取 `assets/icons/`(icon-sound / icon-settings / icon-voice-switch / icon-arrow-right-blue / icon-check / icon-apps / vs-badge / tag-bg / progress-thumb),等比缩放使用,不重绘。
|
|
242
|
+
|
|
243
|
+
### 人物照片
|
|
244
|
+
|
|
245
|
+
- 人类顾问/主播用真实照片(圆角 12pt 裁切),与 3D 插画分区使用:真人 = 服务人格化,3D = 内容视觉化。无照片素材时用 ImageGen 生成职业形象照。
|
|
246
|
+
|
|
247
|
+
### 徽标与标签
|
|
248
|
+
|
|
249
|
+
- 直播徽标:红底 `#F3464A` 白字 10pt,radius-pill,左侧圆点。
|
|
250
|
+
- 标签底图:`assets/icons/tag-bg.png`(奶油色异形标签)。
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 附:提炼来源与可信度
|
|
255
|
+
|
|
256
|
+
| 数据 | 来源 | 可信度 |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| 色值/字号/圆角/阴影频次 | 两套 index.html 内联样式正则统计(top 频次取众数) | 高(设计源数据) |
|
|
259
|
+
| 背景渐变/奶油卡色 | 预览 PNG 逐像素采样(#DBE3F5/#EEE6D9/#EFF1F4/#FFF7E8/#FEF6ED) | 高(实测) |
|
|
260
|
+
| 组件尺寸/间距 | 45 画板目测 + MeaXure 数值交叉 | 中高(±2pt) |
|
|
261
|
+
| 字体族 | MeaXure font-family 统计 | 高 |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!-- MBank 高保真稿画布模板。用法:复制本文件,替换 <body> 内 slot 区域。
|
|
3
|
+
画布 375×812pt(@2x 导出 750×1624px)。token 与 references/design-spec.md 一一对应。
|
|
4
|
+
assets 路径按本文件位置相对引用(templates/../assets/);复制到别处时同步调整。 -->
|
|
5
|
+
<html lang="zh-CN">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="UTF-8">
|
|
8
|
+
<title>MBank Mockup</title>
|
|
9
|
+
<style>
|
|
10
|
+
/* ===== Design Tokens(唯一取值处,改样式只改这里) ===== */
|
|
11
|
+
:root {
|
|
12
|
+
/* 主色 */
|
|
13
|
+
--c-primary: #2F86F6;
|
|
14
|
+
--c-primary-deep: #005DFD;
|
|
15
|
+
--c-primary-bright: #3B73FF;
|
|
16
|
+
--grad-primary: linear-gradient(90deg, #2F86F6, #005DFD);
|
|
17
|
+
/* 辅助色 */
|
|
18
|
+
--c-red: #F3464A;
|
|
19
|
+
--c-orange: #F09404;
|
|
20
|
+
--c-orange-deep: #EE6723;
|
|
21
|
+
--grad-cta: linear-gradient(90deg, #F09404, #EE6723);
|
|
22
|
+
--c-green: #68C800;
|
|
23
|
+
/* 中性 */
|
|
24
|
+
--text-1: #1F2635;
|
|
25
|
+
--text-1-warm: #221715;
|
|
26
|
+
--text-2: #727885;
|
|
27
|
+
--text-3: #AAAAAA;
|
|
28
|
+
--bg-page: #F2F3F5;
|
|
29
|
+
--bg-warm-panel: #FEFBF7;
|
|
30
|
+
--bg-blue-tint: #EDF5FF;
|
|
31
|
+
--bg-gray-block: #F5F6F8;
|
|
32
|
+
--divider: #E9ECF2;
|
|
33
|
+
/* 圆角 */
|
|
34
|
+
--r-xs: 4px; --r-sm: 8px; --r-md: 12px; --r-lg: 20px; --r-xl: 22px; --r-pill: 100px;
|
|
35
|
+
/* 阴影 */
|
|
36
|
+
--shadow-float: 0 2px 6px rgba(134,155,168,.5);
|
|
37
|
+
--shadow-soft: 0 2px 10px rgba(0,0,0,.2);
|
|
38
|
+
/* 字体 */
|
|
39
|
+
--font: -apple-system, "PingFang SC", "Helvetica Neue", sans-serif;
|
|
40
|
+
--font-num: "DIN Alternate Bold", "SF Pro Text Semibold", var(--font);
|
|
41
|
+
/* 间距 */
|
|
42
|
+
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px; --sp-6: 24px; --sp-8: 32px;
|
|
43
|
+
}
|
|
44
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
45
|
+
html, body { width: 375px; }
|
|
46
|
+
body {
|
|
47
|
+
font-family: var(--font); color: var(--text-1); font-size: 15px; line-height: 1.6;
|
|
48
|
+
/* 全 App 签名背景:左冷蓝右暖奶油氛围光 + 向下渐隐 */
|
|
49
|
+
background:
|
|
50
|
+
radial-gradient(ellipse 60% 30% at 0% 0%, #DBE3F5 0%, transparent 70%),
|
|
51
|
+
radial-gradient(ellipse 60% 30% at 100% 0%, #EEE6D9 0%, transparent 70%),
|
|
52
|
+
linear-gradient(180deg, #E4E2E4 0%, #EFF1F4 30%, #F7F8FA 70%, #FBFCFC 100%);
|
|
53
|
+
min-height: 812px; position: relative;
|
|
54
|
+
}
|
|
55
|
+
.canvas { min-height: 812px; display: flex; flex-direction: column; }
|
|
56
|
+
|
|
57
|
+
/* ===== 全局框架 ===== */
|
|
58
|
+
.statusbar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; font-size: 15px; font-weight: 600; }
|
|
59
|
+
.navbar { height: 44px; display: flex; align-items: center; padding: 0 16px; }
|
|
60
|
+
.navbar .tabs { display: flex; gap: 24px; }
|
|
61
|
+
.navbar .tab { font-size: 17px; color: var(--text-2); }
|
|
62
|
+
.navbar .tab.active { color: var(--text-1); font-weight: 500; }
|
|
63
|
+
.navbar .actions { margin-left: auto; display: flex; gap: 16px; align-items: center; }
|
|
64
|
+
.navbar .actions img { width: 24px; height: 24px; }
|
|
65
|
+
.content { flex: 1; padding: var(--sp-4) var(--sp-3); }
|
|
66
|
+
.bottombar { position: sticky; bottom: 0; padding: var(--sp-3) var(--sp-4) 0; display: flex; gap: var(--sp-3); align-items: center; }
|
|
67
|
+
.input-pill { flex: 1; height: 36px; background: #fff; border-radius: var(--r-pill); display: flex; align-items: center; padding: 0 16px; gap: var(--sp-2); color: var(--text-3); font-size: 15px; }
|
|
68
|
+
.grid-btn { width: 44px; height: 44px; border-radius: 50%; background: #fff; box-shadow: var(--shadow-float); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
69
|
+
.home-indicator { height: 34px; display: flex; align-items: center; justify-content: center; }
|
|
70
|
+
.home-indicator::after { content: ""; width: 134px; height: 5px; border-radius: 3px; background: #000; }
|
|
71
|
+
|
|
72
|
+
/* ===== 气泡 ===== */
|
|
73
|
+
.bubble { max-width: 75%; padding: 12px 16px; font-size: 15px; line-height: 1.6; margin-bottom: var(--sp-3); }
|
|
74
|
+
.bubble.user { margin-left: auto; background: var(--grad-primary); color: #fff; border-radius: 12px 0 12px 12px; }
|
|
75
|
+
.bubble.ai { background: var(--bg-warm-panel); border-radius: 0 12px 12px 12px; }
|
|
76
|
+
.bubble.ai .note { margin-top: var(--sp-2); font-size: 13px; line-height: 1.55; color: var(--text-2); }
|
|
77
|
+
.bubble.ai .hl { color: var(--c-primary-bright); font-weight: 600; }
|
|
78
|
+
|
|
79
|
+
/* ===== 选项列表卡 ===== */
|
|
80
|
+
.option-group { background: rgba(255,255,255,.7); border-radius: var(--r-lg); padding: var(--sp-3) var(--sp-4) var(--sp-4); margin-bottom: var(--sp-3); }
|
|
81
|
+
.option-group .group-title { font-size: 15px; color: var(--text-2); margin-bottom: var(--sp-3); }
|
|
82
|
+
.option-item { background: #fff; border-radius: var(--r-md); padding: 14px var(--sp-4); display: flex; align-items: center; justify-content: space-between; }
|
|
83
|
+
.option-item + .option-item { margin-top: var(--sp-3); }
|
|
84
|
+
.option-item .t { font-size: 16px; font-weight: 500; }
|
|
85
|
+
.option-item .d { font-size: 14px; color: var(--text-2); margin-top: var(--sp-1); }
|
|
86
|
+
.option-item.selected .t { color: var(--c-primary); }
|
|
87
|
+
.option-item.selected { border: 1px solid var(--c-primary); }
|
|
88
|
+
|
|
89
|
+
/* ===== 建议气泡 ===== */
|
|
90
|
+
.suggestion { padding: 0 var(--sp-1); }
|
|
91
|
+
.suggestion .s-title { font-size: 15px; color: var(--text-2); margin-bottom: var(--sp-3); }
|
|
92
|
+
.suggestion .pill { display: inline-block; height: 32px; line-height: 30px; padding: 0 var(--sp-4); background: #fff; border: 1px solid var(--c-primary); color: var(--c-primary); border-radius: var(--r-pill); font-size: 14px; margin: 0 var(--sp-2) var(--sp-3) 0; }
|
|
93
|
+
|
|
94
|
+
/* ===== 表格卡 ===== */
|
|
95
|
+
.sheet { background: #fff; border-radius: var(--r-lg); padding: var(--sp-4); margin-bottom: var(--sp-3); }
|
|
96
|
+
.sheet .sheet-title { font-size: 16px; font-weight: 500; }
|
|
97
|
+
.sheet .sheet-title mark { background: var(--bg-blue-tint); padding: 2px 4px; }
|
|
98
|
+
.sheet .row { height: 44px; display: flex; align-items: center; justify-content: space-between; font-size: 15px; border-bottom: 1px solid #E9E9E9; }
|
|
99
|
+
.sheet .row:last-of-type { border-bottom: none; }
|
|
100
|
+
.sheet .row .val { color: var(--c-red); font-weight: 600; }
|
|
101
|
+
.sheet .footnote { font-size: 12px; line-height: 1.5; color: var(--text-3); margin-top: var(--sp-2); }
|
|
102
|
+
|
|
103
|
+
/* ===== 主按钮 / 胶囊 ===== */
|
|
104
|
+
.btn-primary { width: 100%; height: 48px; border-radius: 24px; background: var(--c-primary); color: #fff; font-size: 17px; font-weight: 500; display: flex; align-items: center; justify-content: center; }
|
|
105
|
+
.btn-cta { height: 32px; padding: 0 var(--sp-4); border-radius: var(--r-pill); background: var(--grad-cta); color: #fff; font-size: 14px; font-weight: 500; display: inline-flex; align-items: center; }
|
|
106
|
+
|
|
107
|
+
/* ===== 通用卡 ===== */
|
|
108
|
+
.card { background: #fff; border-radius: var(--r-lg); padding: var(--sp-4); margin-bottom: var(--sp-3); }
|
|
109
|
+
.card.float { box-shadow: var(--shadow-float); }
|
|
110
|
+
.card.cream { background: linear-gradient(160deg, #FFF7E8, #FEF6ED); }
|
|
111
|
+
.card .card-title { font-size: 18px; font-weight: 600; color: var(--text-1-warm); }
|
|
112
|
+
|
|
113
|
+
/* ===== slot:在此填充页面内容 ===== */
|
|
114
|
+
</style>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<div class="canvas">
|
|
118
|
+
<div class="statusbar"><span>9:41</span><span>▮▮▮</span></div>
|
|
119
|
+
<div class="navbar">
|
|
120
|
+
<div class="tabs"><span class="tab active">此刻</span><span class="tab">对话</span></div>
|
|
121
|
+
<div class="actions">
|
|
122
|
+
<img src="../assets/icons/icon-sound.png" alt="">
|
|
123
|
+
<img src="../assets/icons/icon-settings.png" alt="">
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="content">
|
|
128
|
+
<!-- SLOT:页面主体。按 component-library.md 拼装气泡/卡片/表格。 -->
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="bottombar">
|
|
132
|
+
<div class="input-pill">欢迎向小易提问~</div>
|
|
133
|
+
<div class="grid-btn"><img src="../assets/icons/icon-apps.png" width="20" height="20" alt=""></div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="home-indicator"></div>
|
|
136
|
+
</div>
|
|
137
|
+
</body>
|
|
138
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// MBank mockup 截图工具:把 375×812 的 mockup HTML 导出为 750×1624 @2x PNG。
|
|
3
|
+
// 用法:node <skill>/tools/screenshot.mjs <input.html> [output.png] [--full]
|
|
4
|
+
// --full 整页截图(长页 mockup 用;默认 812pt 视口截断)
|
|
5
|
+
// 依赖 playwright(仓库根 node_modules 已装;本脚本从自身位置向上解析)。
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { dirname, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const { chromium } = require('playwright');
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const full = args.includes('--full');
|
|
17
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
18
|
+
const input = positional[0];
|
|
19
|
+
if (!input) {
|
|
20
|
+
console.error('usage: node screenshot.mjs <input.html> [output.png] [--full]');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const output = positional[1] || input.replace(/\.html$/i, '') + '.png';
|
|
24
|
+
|
|
25
|
+
const browser = await chromium.launch();
|
|
26
|
+
const page = await browser.newPage({
|
|
27
|
+
viewport: { width: 375, height: 812 },
|
|
28
|
+
deviceScaleFactor: 2, // @2x → 750 宽输出,与设计稿一致
|
|
29
|
+
});
|
|
30
|
+
await page.goto(pathToFileURL(resolve(input)).href, { waitUntil: 'networkidle' });
|
|
31
|
+
await page.waitForTimeout(300); // 字体/图片稳定
|
|
32
|
+
await page.screenshot({ path: output, fullPage: full });
|
|
33
|
+
await browser.close();
|
|
34
|
+
console.log(output);
|
|
@@ -45,12 +45,14 @@
|
|
|
45
45
|
| text_only | 纯文本画板(对话 / 问答 / 提示),整板无卡 | `[]` | 不产 | **不建卡**(上游按类型收口,勿人工绕过) | `classify_artboard`;`test_text_only_artboard.py` 用例 1 |
|
|
46
46
|
| overlay_dialog | 系统弹框 | `[]` | 视觉 scrim 判定类不产;名称兜底判定类产 fallback `card-1.png` | **不建卡** —— 由父卡 alertDialog 调用承载 | 用例 5 |
|
|
47
47
|
| subcard | 视觉 bottom-sheet 子卡(底部抽屉) | **1 张**(面板几何直取,**不走候选检测**) | 产 `card-1.png`(面板 rect + 顶圆角遮罩) | **建独立 Dialog 卡** —— 与普通卡同构:`rect` / `mask.radius` / 卡内 `elements` / `segments` 齐备 | `detect_artboard_cards` sheet 分支;`vision._bottom_sheet_rect`;用例 4 |
|
|
48
|
-
| card | 0 卡 ·
|
|
48
|
+
| card | 0 卡 · 结构丰富:候选被读成控件条 / 无候选(整卡单切图已由兜底提升为 1 张卡,见下注) | `[]` | 产 fallback `card-1.png`(去系统 chrome) | 建卡:以该兜底图 + `elements[]` 为准(**有图无坐标**) | `parse_meaxure.py:645`;`board_tag`(`:1768`);用例 2/6 |
|
|
49
49
|
|
|
50
50
|
> 注:本表首列**刻意不加反引号** —— `spec-contract.test.mjs` 以「首列反引号 + 整行 6 列」识别字段契约表,加反引号会让本表被误当成字段登记。
|
|
51
51
|
|
|
52
52
|
> **`subcard` 产卡(2026-09-24 修)**:此前 `bottom_sheet` 只写 `focusSkipReason` 不产 `cards`,导致 `cluster_states` 承认它是独立逻辑卡(`pageId=card-N`、`isDialog=true`)而 spec 层给不出任何卡级数据。现由 `detect_artboard_cards` 的 sheet 分支把面板几何(`vision._bottom_sheet_rect` 早已算出 rect + 顶圆角)落成 1 张卡;`focusSkipReason` **保持不变**(`classify_artboard` 靠它判 `subcard`,改动会连带打翻 `is_text_only_artboard` / `board_verdict` / trainer 的 `is_popup_case`)。**旧版存量 spec 仍可能是 `subcard` + `cards=[]` → 重跑上游,不要在下游补卡。**
|
|
53
53
|
|
|
54
|
+
> **整卡单切图兜底(2026-09-28)**:设计稿把卡片整体导成一张大切图时(图层树只剩「编组 N」大图),`detect_artboard_cards` 在**常规候选 0 卡**时把该切图提升为 1 张真卡。纯几何判据:宽 ≥ 55%W、高 ≥ 12%H、纵向落在 10%..95%H、区域归属元素只有切图本身(混入任何文本/其他切图标注不兜底)。此前这类画板 `cards=[]` → manifest `trainingEligible=false` → 数据集构建整板跳过,「对话 + 内嵌卡」形态永远进不了训练。兜底后 `cards[0].rect` = 切图几何、卡内 elements 只有该 slice,下游按普通 Image 卡建卡。仅 0 卡时触发,已有卡的画板行为不变。
|
|
55
|
+
>
|
|
54
56
|
> `readiness.mjs` 门 **F**:`cards=[]` 的三类(text_only / overlay_dialog / card)只在 `checks.spec.notes` 记说明,**均不进 `blockers`**;`cards` 非空时只校验 `rect` / `mask` 是否缺失(产卡与否由 Workflow A.5 决定,不是契约缺失)。
|
|
55
57
|
> 门 F 真正拦的只有:`cards` 非空时 `cards[i].rect` / `cards[i].mask` 缺失(旧版产物)、`--card` 序号越界、spec 解析失败。
|
|
56
58
|
|
|
@@ -116,3 +118,10 @@
|
|
|
116
118
|
- 判定口径:字段出现次数 < `cards[]` 总数即存在旧产物。
|
|
117
119
|
- 处置:**重跑上游**(`parse_meaxure.py --src <导出> --draft <稿名> --build`)而非手工补字段。
|
|
118
120
|
- 不要为了兼容旧产物在下游写 `if (!card.mask)` 分支 —— 契约规定 `mask` 必需,兼容分支会让"上游漏产"永远不被发现。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
### upstream-structural-focus-image-card(r264 训练回灌)
|
|
124
|
+
> lesson: `r264-l01` status: `promoted`
|
|
125
|
+
|
|
126
|
+
整卡单切图形态(设计稿把卡片整体导出成一张大切图,图层树只剩该图、卡内无任何文本/按钮标注)此前在解析器永远 0 卡:_candidate_visuals 跳过 is_slice → 候选全被低结构门拒绝 → spec.cards=[] → manifest trainingEligible=false → 数据集构建整板跳过,而 is_text_only_artboard 又承认这种板「有卡」(has_card_like_visual),两处口径互相矛盾。修复三件套:① detect_artboard_cards 在常规候选 0 卡时把合格大切图兜底提升为 1 张真卡(判据纯几何:宽≥55%W、高≥12%H、纵向 10%..95%H、区域归属元素仅切图本身,混入任何其他标注不兜底);② card_focus_metrics 把切片当内容而非壳——面积≥55%卡区的壳层排除规则只适用 shape/symbol,切片无条件计数并新增 sliceAreaRatio 字段;③ 训练器上游结构聚焦门新增图像卡通道 sliceAreaRatio≥0.6。生成器对该形态的忠实还原 = 把切片绑为卡背景图(R31 消毒空格→连字符),像素/结构/色彩门按同图对比正常评分。
|
|
127
|
+
|
|
@@ -159,7 +159,7 @@ python skills/sketch-spec-to-card/scripts/cluster_states.py --out <UI目录> --a
|
|
|
159
159
|
|
|
160
160
|
1. 每个候选必须是一个**完整业务卡边界**:有独立容器、标题/内容主体和业务语义,不是对话气泡、单个按钮、图标、状态条或背景装饰。
|
|
161
161
|
2. 一张画板的多个卡片必须分别落成 `card-1.png`、`card-2.png`;不允许把多张业务卡合成一张截图。
|
|
162
|
-
3. 识别不到完整卡片,或画板只是弹窗、toast、底部抽屉等交互态,输出 `trainingEligible=false`
|
|
162
|
+
3. 识别不到完整卡片,或画板只是弹窗、toast、底部抽屉等交互态,输出 `trainingEligible=false` 并记录跳过原因,不进入普通卡训练。**例外(整卡单切图,2026-09-28)**:设计稿把卡片整体导成一张大切图(图层树只剩该图、卡内无文本/按钮标注)时,`detect_artboard_cards` 在常规候选 0 卡后把合格大切图兜底提升为 1 张真卡(纯几何判据:宽≥55%W、高≥12%H、纵向 10%..95%H、区域归属元素仅切图本身;混入任何其他标注不兜底)——`trainingEligible=true`,下游按图像卡(切片绑背景图)建卡。
|
|
163
163
|
4. 低置信度候选宁可拒绝并进入 `rejectedCards`,不得为了提高召回把按钮或气泡当卡片。
|
|
164
164
|
5. `focus-audit` 必须保留 `acceptedCards/rejectedCards/skippedNoCards` 证据,后续训练只消费通过该门的普通卡。
|
|
165
165
|
|
|
@@ -555,6 +555,36 @@ def detect_artboard_cards(ab_raw):
|
|
|
555
555
|
"reason": reason,
|
|
556
556
|
"metrics": focus_metrics,
|
|
557
557
|
})
|
|
558
|
+
|
|
559
|
+
# 整卡单切图兜底(whole-card-as-one-slice):常规候选全被拒(0 卡)时,若板上存在
|
|
560
|
+
# 卡片级大盘切图(卡片被设计稿整体导成一张图——图层树只剩「编组 N」大图),
|
|
561
|
+
# 把该切图提升为 1 张卡。此前这类板 cards 恒为空 → manifest trainingEligible=false
|
|
562
|
+
# → 数据集构建整板跳过,「对话 + 内嵌卡」形态永远进不了训练;而
|
|
563
|
+
# is_text_only_artboard 早已承认这种板「有卡」(has_card_like_visual),两处口径不一致。
|
|
564
|
+
# 判据(纯几何,与 has_card_like_visual 同口径,禁业务词):
|
|
565
|
+
# · 宽 ≥ 55%W、高 ≥ 12%H,纵向落在 10%..95%H(标题栏之下、输入坞/Home 之上);
|
|
566
|
+
# · 「纯单切图」:区域归属元素只有这张大图本身(无文本/其他切图被归属进来)——
|
|
567
|
+
# 标注里只要还有可建结构,就维持低结构门的保守性,不走图像卡路线;
|
|
568
|
+
# · 仅在 0 卡时触发,已有卡的画板行为不变(冻结锚点安全)。
|
|
569
|
+
if not cards:
|
|
570
|
+
for ve in flattened:
|
|
571
|
+
if not ve.is_slice:
|
|
572
|
+
continue
|
|
573
|
+
sx, sy, sw, sh = ve.rect
|
|
574
|
+
if sw < 0.55 * W or sh < 0.12 * H:
|
|
575
|
+
continue
|
|
576
|
+
if sy < 0.10 * H or (sy + sh) > 0.95 * H:
|
|
577
|
+
continue
|
|
578
|
+
region = (sx, sy, sw, sh)
|
|
579
|
+
owned = vision.elements_in_rect(flattened, region)
|
|
580
|
+
if len(owned) != 1 or owned[0] is not ve:
|
|
581
|
+
continue # 区域里混有任何其他标注 → 不兜底,交给既有裁决
|
|
582
|
+
cards.append((
|
|
583
|
+
sx, sy, sw, sh, owned,
|
|
584
|
+
[(region, list(ve.radius or []))],
|
|
585
|
+
vision.card_focus_metrics(owned, region, W, H),
|
|
586
|
+
))
|
|
587
|
+
break # 兜底只提 1 张:多重叠大切图不做连环提升
|
|
558
588
|
return {
|
|
559
589
|
"W": W, "H": H, "layers": raw_layers,
|
|
560
590
|
"overlay": bool(overlay), "sheet": bool(bottom_sheet),
|
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
1. 纯文本对话页(信用卡返现 01/03 型):气泡里只有文字 → text_only,被过滤;
|
|
8
8
|
2. 对话 + 内嵌矢量卡(理财 06 型):卡壳里是 3 宫格选项,检测器会读成「控件条」→ 保留;
|
|
9
|
-
3. 对话 + 卡片切图(房贷 32 型):卡片被整体导成一张切图 →
|
|
9
|
+
3. 对话 + 卡片切图(房贷 32 型):卡片被整体导成一张切图 → 整卡单切图兜底产 1 张卡
|
|
10
|
+
(2026-09-28 前 cards 恒为 0,训练集永远盖不到该形态);
|
|
10
11
|
4. 底部抽屉(喜豆 04/05/06 型):alertCard 子卡形态 → 保留(subcard),
|
|
11
12
|
且**产 1 张卡**(矩形由 `vision._bottom_sheet_rect` 几何直取,非候选检测);
|
|
12
13
|
5. 全屏遮罩弹框 → 保留(overlay_dialog);
|
|
13
14
|
6. 0 候选的普通文字页:保守保留,交给下游裁决(维持既有行为)。
|
|
14
15
|
|
|
15
16
|
断言分两层:`kind/skip`(画板类型判定,6 例)+ `cards` 数(产出,注意卡片型用例
|
|
16
|
-
允许 0 —— kind 与 cards
|
|
17
|
+
允许 0 —— kind 与 cards 数解耦是上游有意设计;整卡单切图兜底后用例 3 产 1 张)
|
|
18
|
+
+ 抽屉元素归属(边界气泡不得渗入)。
|
|
17
19
|
"""
|
|
18
20
|
import sys
|
|
19
21
|
from pathlib import Path
|
|
@@ -131,7 +133,7 @@ PLAIN_NO_CANDIDATE = ab("03信用卡返现_有信用卡_无返现记录", chrome
|
|
|
131
133
|
CASES = [
|
|
132
134
|
(CHAT_TEXT_ONLY, "text_only", True, 0, "纯文本对话页(只有问答气泡)"),
|
|
133
135
|
(CHAT_WITH_VECTOR_CARD, "card", False, 0, "对话 + 内嵌矢量卡(3 宫格选项)"),
|
|
134
|
-
(CHAT_WITH_SLICED_CARD, "card", False,
|
|
136
|
+
(CHAT_WITH_SLICED_CARD, "card", False, 1, "对话 + 卡片切图(整卡单切图兜底)"),
|
|
135
137
|
(BOTTOM_SHEET, "subcard", False, 1, "底部抽屉(alertCard 子卡)→ 几何直取 1 张卡"),
|
|
136
138
|
(OVERLAY_DIALOG, "overlay_dialog", False, 0, "全屏遮罩弹框(alertDialog)"),
|
|
137
139
|
(PLAIN_NO_CANDIDATE, "card", False, 0, "0 候选的普通文字页(保守保留)"),
|
|
@@ -196,6 +198,26 @@ def main():
|
|
|
196
198
|
assert vision.has_card_like_visual(flat_text_only, 375, 812) is False, "30x30 图标切图不该判为卡片"
|
|
197
199
|
print("[PASS] has_card_like_visual:中段大盘切图算卡,小图标不算")
|
|
198
200
|
|
|
201
|
+
# 整卡单切图兜底(2026-09-28):0 卡画板上的卡片级大盘切图提升为 1 张真卡,
|
|
202
|
+
# 让「对话 + 内嵌卡(整卡导出为一张图)」形态进入训练集;纯单切图才兜底。
|
|
203
|
+
found = pm.detect_artboard_cards(CHAT_WITH_SLICED_CARD)
|
|
204
|
+
assert len(found["cards"]) == 1, f"整卡单切图应兜底为 1 张卡,实际 {len(found['cards'])}"
|
|
205
|
+
fx, fy, fw, fh, f_els, f_shapes, _fm = found["cards"][0]
|
|
206
|
+
assert (fx, fy, fw, fh) == (16, 276, 343, 280), f"兜底卡矩形应取切图几何,实际 {(fx, fy, fw, fh)}"
|
|
207
|
+
assert len(f_els) == 1 and f_els[0].is_slice, "纯单切图兜底:归属元素应只有切图本身"
|
|
208
|
+
# 混入其他标注(气泡文字渗进切图区域)→ 不兜底,维持 0 卡
|
|
209
|
+
MIXED = ab("32房贷利率调整_混合标注", chrome() + bubble(
|
|
210
|
+
196, 100, 163, 42, text("调整下我的房贷利率", 208, 110, 140, 22)) + [
|
|
211
|
+
L("应答气泡", "shape", 16, 156, 343, 108, radius=[0, 12, 12, 12], hexv="#FFFFFFFF"),
|
|
212
|
+
text("您的贷款为深圳地区二套住房贷款……", 30, 166, 315, 88),
|
|
213
|
+
L("编组 4", "slice", 16, 276, 343, 280),
|
|
214
|
+
text("浮层标注", 30, 300, 200, 24),
|
|
215
|
+
])
|
|
216
|
+
found_mixed = pm.detect_artboard_cards(MIXED)
|
|
217
|
+
assert len(found_mixed["cards"]) == 0, \
|
|
218
|
+
f"切图区域内混有文本标注时不得兜底,实际 {len(found_mixed['cards'])} 卡"
|
|
219
|
+
print("[PASS] 整卡单切图兜底:纯切图 → 1 卡(矩形=切图几何);混入标注 → 不兜底")
|
|
220
|
+
|
|
199
221
|
# 同名画板消歧(2026-09-24):Sketch 允许两个画板**完全同名**——实测「地址管理」稿
|
|
200
222
|
# 25 板里两个「11…_失败_再次修改」(375×1232/129 层 vs 375×1252/130 层,两个不同设计)。
|
|
201
223
|
# 目录名直接用 name 时后跑的会整板覆盖先跑的(产物静默丢失),且 manifest 两条 entry
|
|
@@ -899,7 +899,13 @@ def _is_divider(ve):
|
|
|
899
899
|
|
|
900
900
|
|
|
901
901
|
def card_focus_metrics(elements, rect, width, height):
|
|
902
|
-
"""量化候选区域的结构复杂度,供聚焦提取统一过滤。
|
|
902
|
+
"""量化候选区域的结构复杂度,供聚焦提取统一过滤。
|
|
903
|
+
|
|
904
|
+
切片(slice)是**内容**(设计稿导出的真实图像),不是卡壳——「面积 ≥ 55% 卡区
|
|
905
|
+
跳过」的壳层排除规则只适用于 shape/symbol 背景层,不得吞掉切片计数,否则
|
|
906
|
+
「整卡 = 一张大切图」的形态会被统计成零结构(真踩过:slice 兜底卡 rich=0)。
|
|
907
|
+
sliceAreaRatio:切片覆盖卡区的面积比,供下游识别「图像卡」形态。
|
|
908
|
+
"""
|
|
903
909
|
x, y, w, h = rect
|
|
904
910
|
card_area = max(w * h, 1)
|
|
905
911
|
text_count = 0
|
|
@@ -907,6 +913,7 @@ def card_focus_metrics(elements, rect, width, height):
|
|
|
907
913
|
slice_count = 0
|
|
908
914
|
symbol_count = 0
|
|
909
915
|
shape_count = 0
|
|
916
|
+
slice_area = 0.0
|
|
910
917
|
for ve in elements:
|
|
911
918
|
if _intersect_ratio(ve.rect, rect) < 0.35:
|
|
912
919
|
continue
|
|
@@ -914,12 +921,16 @@ def card_focus_metrics(elements, rect, width, height):
|
|
|
914
921
|
if ve.area > 0:
|
|
915
922
|
text_count += 1
|
|
916
923
|
continue
|
|
917
|
-
if _is_divider(ve)
|
|
924
|
+
if _is_divider(ve):
|
|
918
925
|
continue
|
|
919
926
|
if ve.is_slice:
|
|
920
927
|
slice_count += 1
|
|
921
928
|
rich_count += 1
|
|
922
|
-
|
|
929
|
+
slice_area += _intersect_ratio(ve.rect, rect) * ve.area
|
|
930
|
+
continue
|
|
931
|
+
if ve.area >= 0.55 * card_area:
|
|
932
|
+
continue
|
|
933
|
+
if ve.type == "symbol":
|
|
923
934
|
symbol_count += 1
|
|
924
935
|
rich_count += 1
|
|
925
936
|
elif ve.type == "shape" and (ve.fills or ve.border or any(v > 0 for v in _radius_values(ve.radius))):
|
|
@@ -932,6 +943,7 @@ def card_focus_metrics(elements, rect, width, height):
|
|
|
932
943
|
"textCount": text_count,
|
|
933
944
|
"richComponentCount": rich_count,
|
|
934
945
|
"sliceCount": slice_count,
|
|
946
|
+
"sliceAreaRatio": min(1.0, slice_area / card_area),
|
|
935
947
|
"symbolCount": symbol_count,
|
|
936
948
|
"shapeCount": shape_count,
|
|
937
949
|
}
|