@szc-ft/mcp-szcd-client 0.24.0 → 0.26.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.
Files changed (34) hide show
  1. package/agents/build.js +22 -12
  2. package/agents/opencode-extension/agents/szcd-component-expert.md +235 -49
  3. package/agents/qwen-extension/agents/szcd-component-expert.md +235 -49
  4. package/agents/src/szcd-component-expert.md +262 -49
  5. package/agents/src/tools.json +1 -1
  6. package/agents/szcd-component-expert.md +235 -49
  7. package/agents/szcd-component-expert.qoder.md +261 -50
  8. package/agents/szcd-component-expert.trae.md +260 -49
  9. package/opencode-extension/agents/szcd-component-expert.md +235 -49
  10. package/opencode-extension/skills/local-api-tool/SKILL.md +296 -60
  11. package/opencode-extension/skills/local-api-tool/scripts/extract-swagger.mjs +625 -0
  12. package/opencode-extension/skills/local-api-tool/scripts/extract_swagger.py +593 -0
  13. package/opencode-extension/skills/szcd-component-helper/SKILL.md +7 -4
  14. package/opencode-extension/skills/szcd-design-to-code/SKILL.md +267 -25
  15. package/package.json +1 -1
  16. package/qwen-extension/agents/szcd-component-expert.md +235 -49
  17. package/qwen-extension/qwen-extension.json +1 -1
  18. package/qwen-extension/skills/local-api-tool/SKILL.md +296 -60
  19. package/qwen-extension/skills/local-api-tool/scripts/extract-swagger.mjs +625 -0
  20. package/qwen-extension/skills/local-api-tool/scripts/extract_swagger.py +593 -0
  21. package/qwen-extension/skills/szcd-component-helper/SKILL.md +7 -4
  22. package/qwen-extension/skills/szcd-design-to-code/SKILL.md +267 -25
  23. package/scripts/lib/claude-code.js +1 -1
  24. package/scripts/lib/common.js +44 -0
  25. package/scripts/lib/opencode.js +1 -1
  26. package/scripts/lib/qoder.js +1 -1
  27. package/scripts/lib/trae-cli.js +1 -1
  28. package/scripts/lib/trae-ide.js +1 -1
  29. package/scripts/postinstall.js +1 -0
  30. package/standard-skill/local-api-tool/SKILL.md +296 -60
  31. package/standard-skill/local-api-tool/scripts/extract-swagger.mjs +625 -0
  32. package/standard-skill/local-api-tool/scripts/extract_swagger.py +593 -0
  33. package/standard-skill/szcd-component-helper/SKILL.md +7 -4
  34. package/standard-skill/szcd-design-to-code/SKILL.md +267 -25
@@ -57,7 +57,7 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
57
57
  | `get_best_practices` | 获取组件使用最佳实践和常见错误修正,指定scenario可获取组合最佳实践 | 步骤4:组合最佳实践 |
58
58
  | `get_style_injection_guide` | 查询组件样式注入方法、CSS穿透路径、优先级和反模式 | 步骤5:样式注入 |
59
59
  | `read_file` | 读取仓库内文件(组件源码/文档),组件源码无长度限制 | 步骤6:读取源码确认 |
60
- | `get_upload_endpoint` | 获取设计稿图片上传接口地址和curl命令,上传后取upload_id传给analyze_design_image | 步骤3:上传设计稿 |
60
+ | `get_upload_endpoint` | 获取设计稿图片上传接口地址和curl命令,上传后取upload_id传给analyze_design_image。上传最多重试3次,超过后停止并向用户说明失败原因 | 步骤3:上传设计稿 |
61
61
  | `analyze_design_image` | 分析UI设计稿,提取Token配置、CSS覆盖样式、视觉细节,支持pixel_perfect模式 | 步骤3:分析设计稿 |
62
62
  | `map_design_data` | 将设计稿解析结果映射为szcd组件配置,支持sketchData自动推断组件 | 步骤2.5:设计稿映射 |
63
63
  | `assert_page_screenshot` | 对页面截图进行自然语言语义断言,返回pass/fail+原因。用于自动化测试验证布局、内容、功能 | 步骤6.5:浏览器验证结果审查 |
@@ -120,11 +120,11 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
120
120
 
121
121
  ### 步骤2.5:Sketch 文件解析(条件执行)
122
122
 
123
- **当用户提供 .sketch 设计文件时**,使用 `sketch-mcp-server`(独立 MCP Server,stdio 模式)解析,直接提取结构化数据,结合组件库架构推理组件方案,无需经过视觉模型和 `map_design_data`。
123
+ **当用户提供 .sketch 设计文件时**,使用 `sketch-mcp-server`(独立 MCP Server,stdio 模式,**v1.5.0+**)解析,直接提取结构化数据,结合组件库架构推理组件方案,无需经过视觉模型和 `map_design_data`。
124
124
 
125
- **核心原则**:写代码前必须先 `getMultipleNodeInfo` 拉取所有 `text` 和 `rectangle` 节点的完整样式 Token(`style.fills[0].color.hex`、`text.color`、`style.fontSize`),禁止凭节点名猜测颜色和样式。一次批量查询(50-100 ID)比逐个 `getNodeInfo` 10 倍以上,且不会遗漏。
125
+ **核心原则**:写代码前必须先 `getMultipleNodeInfo` 拉取所有 `text`、`rectangle`、`group` 节点的完整样式 Token + 父子归属。`queryNodes.type` 接受数组(一次拿三类)、`getMultipleNodeInfo` 内部自动分片、每个节点自动返回 `parent: {id,name,type}`,禁止凭节点名猜测样式,禁止用 x 坐标判定元素归属。
126
126
 
127
- **工作流(6步深度探查,像素级还原)**:
127
+ **工作流(4 步,sketch-mcp-server v1.5.0+)**:
128
128
 
129
129
  1. **加载文件**:
130
130
  - `loadSketchByPath` 加载 .sketch 文件
@@ -132,42 +132,42 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
132
132
  - `queryNodes`(pageId, type="artboard")获取画板列表
133
133
  - 根据画板名称选择目标页面
134
134
 
135
- 2. **获取完整节点树**:
136
- - `getPageStructure`(pageId)**或** `getDocumentStructure`(includeDetails=true)— 拿到完整节点树 + 所有节点 ID
137
- - 必须 `includeDetails=true`,否则拿不到初始样式
138
- - 拿到所有节点 ID 列表,为下一步分组做准备
139
-
140
- 3. **按类型分组统计**:
141
- - `getPageStructure`(pageId, mode="summary", groupBy="type")— 按类型分组,统计 text/rectangle/group 各多少个
142
- - 识别组件模式:
143
- - `group(rectangle + text)` = 标签组件(如状态徽章)
144
- - `group(text + text + rectangle...)` = 表单项
145
- - `group(text 列表 + 边框)` = 表格行
146
- - 决定下一批 `getMultipleNodeInfo` 的目标节点范围(所有 type=text + type=rectangle)
147
-
148
- 4. **批量拉取样式 Token**:
149
- - `getMultipleNodeInfo`([id1, id2, ...])— 每次 ≤ 100 个节点
135
+ 2. **获取分区结构**:
136
+ - `getPageStructure`(pageId, mode="tree", maxDepth=3)— 看顶层 group 分区(通常 2-5 个,对应 LeftTree区/Query+Table区/信息栏区/标题栏区)
137
+ - 记下每个分区根 group 的 id 和 name,后续按 `parent` 字段归属元素
138
+
139
+ 3. **批量获取目标节点 ID**:
140
+ - 推荐:`queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })` — **type 数组**一次拿三类节点的 ID
141
+ - 备选:`getPageStructure(pageId, mode="ids")` 返回按类型分组的全量 ID `{ text: [...], rectangle: [...], group: [...], bitmap: [...], shapePath: [...] }`
142
+ - 顺手记下 bitmap/shapePath 节点列表,传给步骤 2.5.1
143
+
144
+ 4. **批量拉取样式 Token + 父子归属**:
145
+ - `getMultipleNodeInfo`([id1, id2, ...])— 传入全部 ID,**内部自动按 100 个一批分片**,无需手动拆分
146
+ - **同批必须包含 text + rectangle + group 三类节点 ID**(漏 group 会丢失渐变背景/边框/阴影)
150
147
  - 关键字段提取:
151
- - 背景色:`style.fills[0].color.hex`
148
+ - 背景色:`style.fills[0].color.hex`(含 group 容器的渐变)
152
149
  - 文字色:`text.color`(部分版本在 `style.fills[0].color`)
153
150
  - 字号:`style.fontSize`
154
151
  - 字重:`style.fontWeight`
155
152
  - 边框:`style.borders[0].color.hex` + `thickness`
153
+ - 阴影:`style.shadows`
154
+ - **父子归属**:`parent: { id, name, type }` ← v1.5.0+ 自动返回,元素归属判定唯一可信来源
156
155
  - **禁止**逐个 `getNodeInfo` 探查
157
156
 
158
157
  **步骤 2.5 产出物**(**只产出数据,不做组件映射**):
159
- - Sketch 节点结构 + 全量节点 ID 列表
160
- - text/rectangle 节点的完整样式 Token
158
+ - Sketch 顶层分区结构(步骤 2 tree)
159
+ - text/rectangle/group 节点的完整样式 Token + parent 父子关系
161
160
  - 图表区结构规律识别结果
161
+ - bitmap ID 列表 + shapePath 节点名列表(传给步骤 2.5.1)
162
162
 
163
163
  组件候选列表推理**不**在步骤 2.5 内部完成,**统一在主流程步骤 3.5 中结合步骤 1 的 `templatePatterns` 推理**。
164
164
 
165
- **Sketch → szcd 工作流(结构化直传 + 样式 Token,无需视觉模型)**:
165
+ **Sketch → szcd 工作流(v1.5.0+)**:
166
166
  ```
167
- .sketch → loadSketchByPath → listPages → queryNodes(pageId, type=artboard)
168
- → getPageStructure(includeDetails=true) / getDocumentStructure(includeDetails=true)
169
- getPageStructure(pageId, mode="summary", groupBy="type")
170
- → getMultipleNodeInfo([所有text+rectangle节点ID]) 步骤 2.5 结束
167
+ .sketch → loadSketchByPath → listPages → queryNodes(pageId, type="artboard")
168
+ → getPageStructure(pageId, mode="tree", maxDepth=3) ← 拿分区
169
+ queryNodes(pageId, type=["text","rectangle","group"]) ← 拿全量 ID(数组类型)
170
+ → getMultipleNodeInfo([全部 ID]) 自动分片,含 parent 字段
171
171
  → [进入主流程步骤 3.5 统一推理]
172
172
  ```
173
173
 
@@ -175,16 +175,38 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
175
175
 
176
176
  | 步骤 | 第一次做错的 | 应该做的 |
177
177
  |------|------------|---------|
178
- | 结构获取 | `getDocumentStructure(includeDetails=false)` 只有名称和位置 | `includeDetails=true` `getPageStructure`,拿到初始样式 |
179
- | 样式提取 | 没做 | `getMultipleNodeInfo` 批量拉取所有 type=text 和 type=rectangle 节点 |
180
- | 组件识别 | 凭节点名猜测(如看到 "tag" 节点就写 `<Tag>`) | `getPageStructure(pageId, mode="summary", groupBy="type")` 统计类型分布,识别 text+rectangle 组合 → 标签组件 |
181
- | 颜色提取 | 跳过 | 对每个 rectangle(背景)+ text(文字)取 `style.fills[0].color.hex` + `text.color` |
182
-
183
- **实战经验沉淀(重要,4 条核心收获)**:
178
+ | 分区识别 | 直接拉所有节点详情,靠 x/y 反推分区 | `getPageStructure(mode="tree", maxDepth=3)` 先看顶层 group 分布 |
179
+ | 节点收集 | 分两次调用 queryNodes text 和 rectangle | `queryNodes(type=["text","rectangle","group"])` 一次拿三类 |
180
+ | 样式提取 | 没做 | `getMultipleNodeInfo([全部 ID])` 内部自动分片,**单次调用** |
181
+ | 元素归属 | x/y 坐标判定("x=272 列都是同区域") | `getMultipleNodeInfo` 返回的 `parent.id/name` 字段 |
182
+ | 颜色提取 | 跳过 group 只取 text/rectangle | 同批提交 group ID,从 `style.fills` 取渐变背景 |
183
+
184
+ #### getMultipleNodeInfo 返回字段契约(sketch-mcp-server ≥ v1.6.1)
185
+
186
+ | 字段 | 适用类型 | 用途 |
187
+ |------|----------|------|
188
+ | `position:{x,y}` + `size:{width,height}` | 全部 | 步骤 2.6 表 1/2/3/4 的坐标推理 |
189
+ | `parent:{id,name,type}` | 全部(顶层除外) | 步骤 2.6 表 1 大区归属、Pass 2 质检 |
190
+ | `style.fills[].{color,gradient}` | rectangle / group / text | 表 5 样式映射 |
191
+ | `style.borders[].{color,thickness}` | rectangle / group | 表 5 样式映射 |
192
+ | `style.shadows[]` | rectangle / group | 表 5 样式映射 |
193
+ | `text.attributes[0].attributes.{fontFamily,fontSize,color,lineHeight}` | text | 字号/字重/颜色(fontFamily 编码字重,如 PingFangSC-Semibold → 600) |
194
+ | `children:[id]` | group / artboard | 推断容器子节点集合(与 parent 反向印证) |
195
+ | `isVisualContainer:bool` | group / artboard | true = 该 group 自身带 fills/borders/shadows,必须作为 UI 容器渲染(不是纯图层分组) |
196
+ | `shape.cornerRadius` | rectangle / oval | 圆角 |
197
+
198
+ **字段缺失自检**(任一项失败 → sketch-mcp-server 版本过旧,升级到 ≥ v1.6.1 后重跑):
199
+
200
+ - [ ] 任取一个 text 节点,`parent.id` 非空 ✅
201
+ - [ ] 任取一个 group 节点,`isVisualContainer` 类型是 boolean、`children` 是数组 ✅
202
+ - [ ] 任取一个 rectangle 节点,`style.fills` 数组存在(即使为空也要是 `[]` 不能 undefined) ✅
203
+ - [ ] 任取一个 text 节点,`text.attributes[0].attributes.fontFamily` 可读出(编码字重) ✅
204
+
205
+ **实战经验沉淀(重要)**:
184
206
 
185
207
  **收获 1:首轮 `getMultipleNodeInfo` 必须全量拉取,不能挑**
186
208
  - 错误做法:先查 7 个顶层容器,再根据需要分批补漏(结果补了 7 轮 ≈ 108 个节点)
187
- - 正确做法:`getPageStructure(pageId)` 一次性拿全量 ID → `getMultipleNodeInfo(全部 text + 全部 rectangle 节点 ID)`,≤100 个/批,**2 批搞定**
209
+ - 正确做法:`queryNodes(type=["text","rectangle","group"])` 一次性拿全量 ID → `getMultipleNodeInfo([全部 ID])`,**单次调用**(内部自动分片)
188
210
 
189
211
  **收获 2:text 节点是颜色/DOM 映射的两级索引**
190
212
  每个 text 节点同时关联两种上下文,**必须同批拉取**:
@@ -192,9 +214,33 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
192
214
  - 所属 group 父级 rectangle 样式:`bg`、`border`、`radius`、`shadow`
193
215
 
194
216
  错误做法:先查 text 拿自身样式 → 再根据 parent ID 单独查 rectangle(多走一步)
195
- 正确做法:构造查询列表时,**把同一 group 内的 text + rectangle 节点 ID 合并提交**
217
+ 正确做法:用 `queryNodes(type=["text","rectangle","group"])` 一次拿三类 ID,`getMultipleNodeInfo` 返回的 `parent` 字段直接告诉你归属关系
218
+
219
+ **收获 3:归属判定走 x+y+parent 三轴联合,禁止单轴定论(坐标和 parent 互为纠错器)**
220
+
221
+ 实战教训:
222
+ - 反例 A:`'主题'text(x=272,y=149)` 单看 x 在右侧内容区列,但 `parent.name=leftTreeHeader` 强语义 → 归左树(情形 C:parent 修正坐标)
223
+ - 反例 B(反馈 #11):`'段码含义'text(x=300,y=257)` 坐标落在 `渐变容器(x=240..960, y=189..297)` 内,但 parent 是兄弟编组 `cataMain` → 视觉上属于渐变容器内(情形 B:图层拆开,按坐标渲染)
224
+
225
+ 正确做法:
226
+ - 步骤 2.5 第 4 步先 `mode="tree"` 看顶层 group 分区(通常 2-5 个)
227
+ - 步骤 2.6 做双向校验:Pass 1 坐标推假设 → Pass 2 parent 质检 → 四类情形 A/B/C/D 处理
228
+ - 大区归属:`parent.name` 含 `LeftTree/树/PageContent/sidebar/cataMain` 等强语义关键字时优先于 x 分布
229
+ - 容器归属:必须 `x ∈ [容器x, 容器x+w]` **且** `y ∈ [容器y, 容器y+h]`,单轴判定无效
230
+ - 跨分区不比 y;同分区内按 y 升序;同行(y 差<8px)按 x 升序
231
+
232
+ 详细决策矩阵见步骤 2.6。
233
+
234
+ **收获 4:信息栏/卡片容器必须先取 group 再取子元素**
235
+
236
+ 实战教训:仅提取 text 节点导致信息栏容器样式遗漏(渐变背景 #f3faff→#f8fdff、边框 #e9f5ff、图标编组)。
196
237
 
197
- **收获 3:图表区样式提取有规律可循**
238
+ 正确做法:
239
+ - 看到画板里有 `编组N`/`infoCard`/`statBox` 等 group 节点,必须连 group 自身和内部 rectangle 一起拉 `getMultipleNodeInfo`
240
+ - group 节点的 `style.fills` 才是渐变背景、`style.borders` 才是边框
241
+ - 子图标若为 group(rectangle + shapePath),rectangle 是圆形/方形背景,shapePath 是矢量图标
242
+
243
+ **收获 5:图表区样式提取有规律可循**
198
244
 
199
245
  | 图表子元素 | 对应 Sketch 节点类型 | 关键样式字段 |
200
246
  |---|---|---|
@@ -209,23 +255,149 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
209
255
 
210
256
  识别出图表后,**图例色块**应归入 ECharts `legend` 配置(不要用 div 硬编码),**柱体色**映射到 `series.itemStyle.color`。
211
257
 
212
- **收获 4:矢量图形是工具链盲区(兜底方案)**
213
- - `shapePath` / `shapeGroup` 在 sketch 节点中占比可能高达 46.8%(实战中 434/926),但 `renderNodeAsBase64` 不支持渲染
214
- - 解压 `.sketch`(zip 格式)只能提取 `bitmap` 中内嵌的 PNG/PDF(实战中可能仅 2 张,常是侧边栏 Logo)
215
- - **兜底方案**:① 用解压提取的 PNG/PDF 资源;② 无资源时用 antd `@ant-design/icons` 同语义图标替代;③ 在代码注释中标注"⚠️ 矢量图标用 antd 图标替代,原始 Sketch 为 shapePath"
258
+ **收获 6:矢量图形 + 图标语义优先级(v1.5.0 已内置 compoundIcons)**
259
+ - `shapePath` / `shapeGroup` 在 sketch 节点中占比可能高达 46.8%,`renderNodeAsBase64` 不支持渲染
260
+ - 解压 `.sketch` 只能提取 `bitmap` 中内嵌的 PNG/PDF(常仅几张,通常是 Logo)
261
+ - **`matchIconFromName` v1.5.0+ 评分制**:每个 nodeName 返回 Top-3 候选,complex 词组合(如"文件夹+新增")自动命中 `FolderAddOutlined`(compound score 100+),不会被"新增"单独 +20 分压制
262
+ - 优先看 `bestMatch.source === "compound"`:表示已强映射,直接采用 `antdIcon`
263
+ - `unmatched`(无关键词信号,如"编组11备份")必须看节点 `parent.name` 和兄弟节点上下文兜底
264
+
265
+ **参考表(内置 compoundIcons 已覆盖;列出供 unmatched 兜底参考)**:
266
+
267
+ | Sketch 节点上下文 | 期望 antd 图标 | 反例(不要选) |
268
+ |---|---|---|
269
+ | 文件夹+新增/添加 | `FolderAddOutlined` | PlusOutlined |
270
+ | 文件+新增 | `FileAddOutlined` | PlusOutlined |
271
+ | 用户+新增 | `UserAddOutlined` | PlusOutlined |
272
+ | 文件夹+打开 | `FolderOpenOutlined` | FolderOutlined |
273
+ | 编辑+笔 | `EditOutlined` | FormOutlined |
274
+ | 设置+齿轮 | `SettingOutlined` | ToolOutlined |
275
+
276
+ - **兜底方案**:① 用 `extractBitmaps` 提取的 PNG/PDF 资源;② `matchIconFromName` 的 Top-1(若 source=compound);③ unmatched 时按 parent 上下文 + 上表选 antd `@ant-design/icons` 复合图标;④ 在代码注释中标注"⚠️ 矢量图标用 antd 图标替代,原始 Sketch 为 shapePath"
277
+
278
+ **收获 8:rectangle 的 fill/border 必须以内联样式或精确选择器落到 DOM,禁止借用通用 CSS class**
279
+
280
+ 实战教训(反馈 #11):5 个段码视觉块的颜色(#f2f8fd/#f5ffed/#fffbe6/#f5f6f7)完整地从 rectangle 节点拿到了,但代码却用了 `.beforebg{background:#ebf0ff}` 这种与设计稿无对应关系的通用 class,导致 5 个块颜色全错。
281
+
282
+ 正确做法:
283
+ - 步骤 2.6 的样式映射表(表 5)必须为每个"颜色块"列出 rectangle 数据 → 内联 style 的对应关系
284
+ - 写代码时直接用 `<div style={{background:'#f2f8fd',border:'1px solid #d9eafb'}}>`
285
+ - 若必须用 CSS class,类名要能反向追到 Sketch 节点(如 `.segmentBlock-blue` 而非 `.beforebg`),且 class 内容必须**精确等于** rectangle 的 fill/border
286
+ - 反例排查:写完代码后 grep 一下 CSS class 名,出现 `.bg1/.box2/.before*` 这类无语义类名、且颜色和设计稿对不上 → 几乎确定是凭空兜底
287
+
288
+ ### 步骤2.6:布局推理(必做,写代码前的最后一道关)
289
+
290
+ ⚠️ 这一步专治"数据全拿到却放错位置"。反馈 #11 的 5 处错位全部由跳过此步骤导致。
291
+
292
+ **核心思路**:x+y 坐标先画"视觉假设",parent 字段做质检纠错。两套信号**互为纠错器**,不是单方向"谁优先"。
293
+
294
+ **输入**:步骤 2.5 产出的 `text + rectangle + group` 节点全量数据(含 `position.{x,y}`、`size.{width,height}`、`parent.{id,name,type}`、`style`、`isVisualContainer`)
295
+
296
+ #### Pass 1:坐标推假设(产出 5 张表)
297
+
298
+ 每张表都必须输出,即使某类节点为空也要写"无"。
299
+
300
+ ##### 表 1:分区识别表(x+y+parent 联合,定大区)
301
+
302
+ ```
303
+ | 节点 name | x | y | parent.name | 推断分区 | 决策依据 |
304
+ |----------------|-----|-----|------------------|--------------|-------------------------|
305
+ | 主题(text) | 272 | 149 | leftTreeHeader | 左树标题区 | parent 强语义压过 x 列 |
306
+ | 段码定义(text) | 320 | 200 | cataMain | 右侧内容-顶部 | parent 含 PageContent 类语义 |
307
+ ```
308
+
309
+ 判定流程:
310
+ 1. 按 x 把画板切成 1-3 列(x<300 → 左区、x≥300 → 右区,或按设计稿实际边界)
311
+ 2. 用 `parent.name` 校验:含 `LeftTree/树/sidebar/cataMain/PageContent/header/footer` 等关键字时以 parent 为准
312
+ 3. 冲突时遵循"收获 3"的 A/B/C/D 矩阵(见下方 Pass 2)
313
+
314
+ → 此表输出**布局类型**:UpDown(单列)/ LeftRight(双列)/ LeftMiddleRight(三列)
315
+
316
+ ##### 表 2:纵向顺序表(同分区内按 y)
317
+
318
+ ```
319
+ | 分区 | 节点 | y | 推断角色 |
320
+ |------------|---------------|-----|-------------------|
321
+ | 右侧内容区 | 段码定义 | 200 | 卡片 1 |
322
+ | 右侧内容区 | Tabs | 420 | Tabs(type=card) |
323
+ | 右侧内容区 | 配置说明 | 478 | 卡片 2(Tabs 下方) |
324
+ ```
325
+
326
+ 铁律:**同分区内**渲染顺序按 y 升序。跨分区不比 y(左树第 100 行 y 和右内容区第 1 行 y 没有可比性)。
327
+
328
+ ##### 表 3:横向排列表(同行按 x,y 差<8px 视为同行)
329
+
330
+ ```
331
+ | 分区/容器 | 节点 | x | y | 推断角色 |
332
+ |-----------------|--------------|-----|-----|-------------------------|
333
+ | Query 操作栏 | 名称输入框 | 320 | 50 | Form.Item col-span=6 |
334
+ | Query 操作栏 | 状态下拉 | 540 | 50 | Form.Item col-span=6 |
335
+ | Query 操作栏 | 查询按钮 | 980 | 50 | 操作按钮(右对齐) |
336
+ ```
337
+
338
+ 判定流程:
339
+ 1. y 差<8px 识别同一行
340
+ 2. 同行按 x 升序排
341
+ 3. 计算 x 间距推算 antd Col span(24 栅格)或 flex
342
+ 4. 最右元素若 `x + width` 接近容器右边界 → 右对齐(justify-content: flex-end)
343
+
344
+ ##### 表 4:容器归属表(x+y 双轴边界检测)
345
+
346
+ 仅对 `isVisualContainer === true` 的 group 和带 fill/border 的 rectangle 做检测。
347
+
348
+ ```
349
+ | 容器 | x..x+w | y..y+h | 落入子元素 |
350
+ |------------------|----------|----------|----------------------------------|
351
+ | 渐变容器(group) | 240..960 | 189..297 | 段码含义(x=300,y=257) ✅ |
352
+ | 段码块-蓝(rect) | 320..420 | 320..360 | (无子节点,仅样式) |
353
+ ```
354
+
355
+ 判定式:子元素 `x ∈ [容器x, 容器x+w]` **且** `y ∈ [容器y, 容器y+h]` → 渲染在容器内部。**单轴判定无效**。
356
+
357
+ ##### 表 5:样式映射表(rectangle/group fill → UI 组件内联样式)
358
+
359
+ ```
360
+ | Sketch 节点 | 数据来源 | bg/border | 映射到代码 |
361
+ |----------------------|--------------------------|--------------------------|-----------------------------------------------------------|
362
+ | 段码块-蓝(rectangle) | style.fills[0]/borders[0]| bg #f2f8fd / b #d9eafb | <div style={{background:'#f2f8fd',border:'1px solid #d9eafb'}}> |
363
+ | 渐变容器(group) | style.fills[0].gradient | linear #f3faff→#f8fdff | <div style={{background:'linear-gradient(180deg,#f3faff,#f8fdff)'}}> |
364
+ | Tab 激活态 | rect bg #fff border #0079f7 | bg #fff / b #0079f7 | Tabs type="card" + .segmentTabs .ant-tabs-tab-active 覆盖 |
365
+ ```
366
+
367
+ 铁律(见收获 8):rectangle 的 fill/border 必须以**内联样式**或精确选择器落到代码,**禁止**用 `.beforebg{background:#ebf0ff}` 这类与设计稿无对应关系的 CSS class。
368
+
369
+ #### Pass 2:parent 质检(按 A/B/C/D 矩阵处理冲突)
370
+
371
+ 逐行扫表 1,对照 parent 字段:
372
+
373
+ | 情形 | 坐标判定 | parent 判定 | 处理 |
374
+ |------|----------|-------------|------|
375
+ | A | 在容器 X 内 | parent 链含 X | ✅ 双向一致,直接采用 |
376
+ | B | 在容器 X 内 | parent 是兄弟节点 Y | ⚠️ **视觉容器优先**(设计师把图层拆开了)→ 按坐标渲染到 X 内 |
377
+ | C | 不在容器 X 内 | parent.name 强语义 = X | ⚠️ **parent 优先**(绝对定位/溢出布局)→ 按 parent 归属,加 `position:absolute` 标注 |
378
+ | D | 双向都不落 | parent 是顶层 | 独立元素,按坐标走纵向顺序 |
379
+
380
+ 输出**最终归属表**:在表 1 末尾追加 `情形(A/B/C/D)` 列。
216
381
 
217
- **提示**:
382
+ #### Pass 3:自检清单(写代码前必跑)
383
+
384
+ - [ ] 表 1 是否按 x 分布判定了布局类型(UpDown / LeftRight / LeftMiddleRight)?
385
+ - [ ] 表 2 同分区内是否按 y 排序,没有跨分区比 y?
386
+ - [ ] 表 3 同行元素(y 差<8px)是否按 x 排序,计算了栅格 span 或右对齐?
387
+ - [ ] 表 4 容器归属是否做了 x+y 双轴检测,不是只看一轴?
388
+ - [ ] 表 5 所有"颜色块/卡片/渐变"样式都来自 rectangle/group 的 style.fills/borders,没有出现凭空 CSS class?
389
+ - [ ] Pass 2 冲突行是否标了 A/B/C/D 情形和决策依据?
390
+
391
+ 任一项 ❌ → 回 Pass 1 补;禁止跳到步骤 3 前未输出全部 5 张表 + 最终归属表。
392
+
393
+ **提示(sketch-mcp-server v1.6.1+)**:
218
394
  - 大型 .sketch 文件先 `queryNodes(pageId, type="artboard")` 锁定目标画板,避免一次性加载整个文档
219
- - `getPageStructure(pageId, mode="summary")` token 节省关键:先按类型分组统计,再决定要查哪些节点 ID
220
- - 批量 `getMultipleNodeInfo` 支持任意数量 ID(内部自动分批),无需手动拆分
395
+ - `queryNodes(pageId, type=["text","rectangle","group"])` 一次拿三类节点 ID(type 字段支持数组),跳过 `getPageStructure(mode="ids")` 单独调用
396
+ - `getPageStructure(pageId, mode="tree", maxDepth=2-3)` 用于看分区层级;`mode="summary"` 用于按类型统计概况
397
+ - 批量 `getMultipleNodeInfo` 支持任意数量 ID(内部按 100 一批自动分片),无需手动拆分;每个节点自带 `parent: {id,name,type}` 字段 + group/artboard 额外有 `children` 和 `isVisualContainer` —— 步骤 2.6 的双向校验同时用 parent 和 x/y,不是只挑一个
398
+ - `matchIconFromName` 评分制返回 Top-3 + `bestMatch.source === "compound"` 强映射,shapePath 节点优先走它
221
399
  - 仅当结构化样式数据仍不足(如复杂渐变、阴影)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
222
- - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
223
-
224
- **提示**:
225
- - 大型 .sketch 文件用 `getPageStructure(maxDepth=1-2)` 即可获取布局概况,避免深层递归超时
226
- - `queryNodes(pageId, type="artboard")` 优先于 `getPageStructure`,更轻量
227
- - 仅当结构化数据不足以判断视觉细节(颜色、间距、字体)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
228
- - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
400
+ - 如果 sketch-mcp-server 工具不可用或版本 < v1.6.1(parent / isVisualContainer 字段缺失),提示用户升级:`npm install -g @szc-ft/sketch-mcp-server@latest`
229
401
 
230
402
  ### 步骤3:分析设计稿(条件执行)
231
403
 
@@ -303,6 +475,19 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
303
475
  | 可编辑表格(单元格直接输入) | `TableOrList(componentType="EditableProTable")` | 可编辑表格特征 |
304
476
  | 卡片列表(非表格式数据卡片) | `TableOrList(componentType="ProList")` | 卡片布局特征 |
305
477
 
478
+ #### ⚠️ TemplateMode 槽位传参铁律(高频踩坑)
479
+
480
+ **LeftContent / Query / PageContent 三个槽位都接收单个 ReactNode,禁止传数组**:
481
+
482
+ | ❌ 错误 | ✅ 正确 | 后果 |
483
+ |---|---|---|
484
+ | `LeftContent={[<LeftTree/>]}` | `LeftContent={<LeftTree/>}` | 数组会触发多余 tab 渲染 |
485
+ | `LeftContent={[<LeftTree/>, <Filter/>]}` | `LeftContent={<><LeftTree/><Filter/></>}` 或包一层 div | 同上 |
486
+ | `Query={[<Query/>]}` | `Query={<Query/>}` | 同上 |
487
+ | `PageContent={[<TableOrList/>]}` | `PageContent={<TableOrList/>}` | 同上 |
488
+
489
+ **判定方法**:Sketch 中只要某个分区是"单棵树/单个查询面板/单个表格",对应槽位就传单个组件实例。需要多个并列时用 Fragment `<>...</>` 或容器 div 包裹,**不要用数组**。
490
+
306
491
  **长尾场景走语义搜索**(不进文档,避免膨胀):
307
492
 
308
493
  *数据可视化长尾*:`Radar`(雷达图)、`Gauge`(仪表盘)、`Funnel`(漏斗图)
@@ -383,10 +568,22 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
383
568
  | 组件 | 依赖的 szcd 组件 | 依赖的 hooks |
384
569
  |------|-----------------|-------------|
385
570
 
571
+ ### 布局顺序(来自步骤 2.6 表 2 压缩,按 y 排序)
572
+ [页面级垂直顺序,如:TitleAndBack → 段码定义卡片 → Tabs → 配置说明卡片]
573
+
574
+ ### 关键样式映射(来自步骤 2.6 表 5,仅列非默认样式)
575
+ | UI 元素 | Sketch 数据来源 | 落地方式 |
576
+ |---------|----------------|----------|
577
+ | 段码块×5 | 5 个 rectangle 的 fills[0]/borders[0] | 内联 style |
578
+ | 渐变容器 | group(isVisualContainer) 的 fills[0].gradient | 内联 linear-gradient |
579
+ | Tab 激活态 | rectangle bg #fff border #0079f7 | type="card" + .segmentTabs 覆盖 |
580
+
386
581
  ### 备选方案(如有)
387
582
  [说明]
388
583
  ```
389
584
 
585
+ 用户在确认环节即可发现"段码块颜色没列上"或"Tab 样式漏识别",避免反馈 #11 那样要等代码生成后反复修。
586
+
390
587
  **交互节点**:向用户确认:
391
588
  - "以上组件方案是否合适?是否需要调整?"
392
589
  - 提供选项:同意方案 / 需要调整 / 换一个方案
@@ -407,6 +604,20 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
407
604
  - 复合组件配合 TemplateMode 使用实现标准页面布局
408
605
  - Hooks 按依赖关系正确引入(如 useLeftTree、useTable)
409
606
 
607
+ #### 步骤 6 末尾必做:样式落地校验
608
+
609
+ 代码生成完成后,**逐行核对**步骤 2.6 表 5(样式映射表)的每一项是否真落到代码里:
610
+
611
+ ```
612
+ | 样式映射表行 | 代码中对应位置 | 校验 |
613
+ |---------------------------------------------|-------------------------------------------------------------|------|
614
+ | 段码块-蓝 bg #f2f8fd border #d9eafb | CreateOrEdit.tsx:L120 `<div style={{background:'#f2f8fd',border:'1px solid #d9eafb'}}>` | ✅ |
615
+ | 渐变容器 linear #f3faff→#f8fdff | CreateOrEdit.tsx:L98 `style={{background:'linear-gradient(...)'}}` | ✅ |
616
+ | Tab 激活态 bg #fff border #0079f7 | styles.less:L45 `.segmentTabs .ant-tabs-tab-active{...}` | ✅ |
617
+ ```
618
+
619
+ ❌ 如果某行找不到对应代码位置 → 直接补到代码里再继续,**禁止说"代码已生成"前留缺口**。这是反馈 #11"5 个段码块全错"的根因——所有颜色都拿到了,但没逐一回查是否落到了代码上。
620
+
410
621
 
411
622
  **交互节点**:代码生成后,询问用户下一步:
412
623
  > "代码已生成,你可以选择: