@szc-ft/mcp-szcd-client 0.25.0 → 0.27.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/agents/opencode-extension/agents/szcd-component-expert.md +160 -40
- package/agents/qwen-extension/agents/szcd-component-expert.md +160 -40
- package/agents/src/szcd-component-expert.md +187 -40
- package/agents/src/tools.json +2 -2
- package/agents/szcd-component-expert.md +160 -40
- package/agents/szcd-component-expert.qoder.md +186 -41
- package/agents/szcd-component-expert.trae.md +185 -40
- package/opencode-extension/agents/szcd-component-expert.md +160 -40
- package/opencode-extension/skills/local-api-tool/SKILL.md +160 -28
- package/opencode-extension/skills/local-api-tool/scripts/extract-swagger.mjs +625 -0
- package/opencode-extension/skills/local-api-tool/scripts/extract_swagger.py +125 -9
- package/opencode-extension/skills/szcd-component-helper/SKILL.md +12 -13
- package/opencode-extension/skills/szcd-design-to-code/SKILL.md +250 -28
- package/package.json +1 -1
- package/qwen-extension/agents/szcd-component-expert.md +160 -40
- package/qwen-extension/qwen-extension.json +1 -1
- package/qwen-extension/skills/local-api-tool/SKILL.md +160 -28
- package/qwen-extension/skills/local-api-tool/scripts/extract-swagger.mjs +625 -0
- package/qwen-extension/skills/local-api-tool/scripts/extract_swagger.py +125 -9
- package/qwen-extension/skills/szcd-component-helper/SKILL.md +12 -13
- package/qwen-extension/skills/szcd-design-to-code/SKILL.md +250 -28
- package/standard-skill/local-api-tool/SKILL.md +160 -28
- package/standard-skill/local-api-tool/scripts/extract-swagger.mjs +625 -0
- package/standard-skill/local-api-tool/scripts/extract_swagger.py +125 -9
- package/standard-skill/szcd-component-helper/SKILL.md +12 -13
- package/standard-skill/szcd-design-to-code/SKILL.md +251 -28
|
@@ -11,8 +11,10 @@ extract_swagger.py — 本地 Swagger 文档提取与解析(与服务端 parse
|
|
|
11
11
|
递归深度 5,两层 definitions(summary + detail),引用树传递闭包裁剪。
|
|
12
12
|
|
|
13
13
|
用法:
|
|
14
|
-
# 方式 A:脚本内部 curl
|
|
14
|
+
# 方式 A:脚本内部 curl 下载(自动缓存到 /tmp/swagger-cache/,TTL 10 分钟)
|
|
15
15
|
extract_swagger.py --url http://10.x.x.x/svc/v2/api-docs [--cookie 'JSESSIONID=xxx']
|
|
16
|
+
extract_swagger.py --url ... --no-cache # 强制重新下载
|
|
17
|
+
extract_swagger.py --url ... --cache-ttl 3600 # 自定义 TTL(秒),0 关闭缓存
|
|
16
18
|
|
|
17
19
|
# 方式 B:读取本地已下载的 JSON 文件
|
|
18
20
|
extract_swagger.py --file /tmp/swagger.json
|
|
@@ -27,18 +29,40 @@ extract_swagger.py — 本地 Swagger 文档提取与解析(与服务端 parse
|
|
|
27
29
|
# 输出到文件而非 stdout
|
|
28
30
|
extract_swagger.py --file /tmp/swagger.json --filter tag --out /tmp/parsed.json
|
|
29
31
|
|
|
32
|
+
容错:
|
|
33
|
+
- 自动修复八进制字面量(如 [011,015] → [11,15]),无需手动预处理
|
|
34
|
+
- JSON 解析失败给出错误位置上下文 + 常见原因提示(HTML 跳转/截断/八进制)
|
|
35
|
+
|
|
36
|
+
跨平台:
|
|
37
|
+
- Linux/macOS:`python3 extract_swagger.py ...`(脚本 shebang 已带 #!/usr/bin/env python3)
|
|
38
|
+
- Windows:`python extract_swagger.py ...`(python3 命令可能不可用)
|
|
39
|
+
|
|
30
40
|
退出码:0 成功;1 参数错误;2 网络/IO 错误;3 解析错误
|
|
31
41
|
"""
|
|
32
42
|
|
|
33
43
|
import argparse
|
|
44
|
+
import hashlib
|
|
34
45
|
import json
|
|
46
|
+
import os
|
|
47
|
+
import re
|
|
35
48
|
import sys
|
|
36
49
|
import subprocess
|
|
50
|
+
import tempfile
|
|
51
|
+
import time
|
|
37
52
|
from pathlib import Path
|
|
38
53
|
|
|
39
54
|
MAX_DEPTH = 5
|
|
40
55
|
METHOD_KEYS = ["post", "get", "put", "delete", "patch"]
|
|
41
56
|
|
|
57
|
+
# 缓存配置(与 Node 版 extract-swagger.mjs 完全一致:共用同一目录 + sha256 key)
|
|
58
|
+
CACHE_DIR = Path(tempfile.gettempdir()) / "swagger-cache"
|
|
59
|
+
DEFAULT_CACHE_TTL = 600 # 10 分钟
|
|
60
|
+
|
|
61
|
+
# 八进制字面量:JSON 标准不允许数字以 0 开头(除 0 本身或小数),
|
|
62
|
+
# 但部分 Java/旧网关序列化会产出 [011,015,022] 这类数组。
|
|
63
|
+
# 匹配 JSON value 位置上的非法前导零数字(不含小数、不含 0 单独存在)。
|
|
64
|
+
OCTAL_LITERAL_RE = re.compile(r'(?<=[\s,\[:])0(\d+)(?=[\s,\]}])')
|
|
65
|
+
|
|
42
66
|
|
|
43
67
|
# ==================== 工具函数 ====================
|
|
44
68
|
|
|
@@ -367,7 +391,69 @@ def get_definition(api_docs, definition_name):
|
|
|
367
391
|
}
|
|
368
392
|
|
|
369
393
|
|
|
370
|
-
# ==================== 输入:curl 下载 / 读文件 ====================
|
|
394
|
+
# ==================== 输入:curl 下载 / 读文件 / 缓存 ====================
|
|
395
|
+
|
|
396
|
+
def _diagnose_json_error(text, err):
|
|
397
|
+
"""根据错误位置给出 ±80 字符上下文 + 常见原因建议。"""
|
|
398
|
+
pos = getattr(err, "pos", None)
|
|
399
|
+
if pos is None:
|
|
400
|
+
# 用 lineno/colno 反推 pos
|
|
401
|
+
lines = text.split("\n")
|
|
402
|
+
pos = sum(len(l) + 1 for l in lines[: err.lineno - 1]) + (err.colno - 1)
|
|
403
|
+
start, end = max(0, pos - 80), min(len(text), pos + 80)
|
|
404
|
+
snippet = text[start:end].replace("\n", "\\n")
|
|
405
|
+
marker = " " * (pos - start) + "^"
|
|
406
|
+
|
|
407
|
+
# 常见原因诊断
|
|
408
|
+
hints = []
|
|
409
|
+
low = text[:500].lstrip()
|
|
410
|
+
if low.startswith("<"):
|
|
411
|
+
hints.append("响应以 '<' 开头,可能是 HTML 错误页/登录跳转页,而非 JSON。检查 cookie/auth 是否过期")
|
|
412
|
+
if OCTAL_LITERAL_RE.search(text):
|
|
413
|
+
hints.append("检测到八进制字面量(如 [011,015]),脚本应已自动修复——若仍报错请提交 issue")
|
|
414
|
+
if len(text) < 1024 and text.rstrip().endswith(("}", "]")) is False:
|
|
415
|
+
hints.append("响应可能被截断(curl 管道/终端缓冲),建议改用 --file 模式直接读本地文件")
|
|
416
|
+
|
|
417
|
+
msg = f"JSON 解析失败 at line {err.lineno} col {err.colno}: {err.msg}\n"
|
|
418
|
+
msg += f" 上下文: ...{snippet}...\n"
|
|
419
|
+
msg += f" {' ' * 11}{marker}\n"
|
|
420
|
+
if hints:
|
|
421
|
+
msg += " 可能原因:\n" + "\n".join(f" - {h}" for h in hints)
|
|
422
|
+
return msg
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _fix_octal_literals(text):
|
|
426
|
+
"""把 [011,015] 这类八进制字面量改成 [11,15](仅在 array/value 位置)。"""
|
|
427
|
+
return OCTAL_LITERAL_RE.sub(lambda m: m.group(1), text)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def load_json_text(text):
|
|
431
|
+
"""解析 JSON;首次失败则尝试八进制修复 fallback,再失败给出诊断信息。"""
|
|
432
|
+
try:
|
|
433
|
+
return json.loads(text)
|
|
434
|
+
except json.JSONDecodeError as e:
|
|
435
|
+
# Fallback: 尝试修复八进制字面量
|
|
436
|
+
if OCTAL_LITERAL_RE.search(text):
|
|
437
|
+
fixed = _fix_octal_literals(text)
|
|
438
|
+
if fixed != text:
|
|
439
|
+
try:
|
|
440
|
+
parsed = json.loads(fixed)
|
|
441
|
+
print(f"[INFO] 自动修复八进制字面量后解析成功", file=sys.stderr)
|
|
442
|
+
return parsed
|
|
443
|
+
except json.JSONDecodeError as e2:
|
|
444
|
+
raise RuntimeError(_diagnose_json_error(fixed, e2)) from None
|
|
445
|
+
raise RuntimeError(_diagnose_json_error(text, e)) from None
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _cache_key(url, cookie, auth):
|
|
449
|
+
h = hashlib.sha256()
|
|
450
|
+
h.update(url.encode("utf-8"))
|
|
451
|
+
if cookie:
|
|
452
|
+
h.update(b"\x00" + cookie.encode("utf-8"))
|
|
453
|
+
if auth:
|
|
454
|
+
h.update(b"\x00" + auth.encode("utf-8"))
|
|
455
|
+
return h.hexdigest()[:16]
|
|
456
|
+
|
|
371
457
|
|
|
372
458
|
def fetch_via_curl(url, cookie=None, auth=None, timeout=30):
|
|
373
459
|
"""脚本内部走 curl 下载 Swagger JSON。失败抛 RuntimeError。"""
|
|
@@ -389,13 +475,36 @@ def fetch_via_curl(url, cookie=None, auth=None, timeout=30):
|
|
|
389
475
|
return r.stdout
|
|
390
476
|
|
|
391
477
|
|
|
392
|
-
def
|
|
478
|
+
def fetch_with_cache(url, cookie=None, auth=None, timeout=30, ttl=DEFAULT_CACHE_TTL, no_cache=False):
|
|
479
|
+
"""带 TTL 缓存的 fetch:缓存命中直接读 /tmp/swagger-cache/<key>.json,否则 curl 拉取并写入。"""
|
|
480
|
+
if no_cache or ttl <= 0:
|
|
481
|
+
return fetch_via_curl(url, cookie=cookie, auth=auth, timeout=timeout)
|
|
482
|
+
|
|
483
|
+
key = _cache_key(url, cookie, auth)
|
|
484
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
485
|
+
cache_file = CACHE_DIR / f"{key}.json"
|
|
486
|
+
meta_file = CACHE_DIR / f"{key}.meta.json"
|
|
487
|
+
|
|
488
|
+
if cache_file.exists() and meta_file.exists():
|
|
489
|
+
try:
|
|
490
|
+
meta = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
491
|
+
age = time.time() - meta.get("cached_at", 0)
|
|
492
|
+
if age < ttl:
|
|
493
|
+
print(f"[CACHE] 命中 {key} (age={int(age)}s, ttl={ttl}s)", file=sys.stderr)
|
|
494
|
+
return cache_file.read_text(encoding="utf-8")
|
|
495
|
+
except Exception:
|
|
496
|
+
pass # 缓存损坏 → 重新拉
|
|
497
|
+
|
|
498
|
+
raw = fetch_via_curl(url, cookie=cookie, auth=auth, timeout=timeout)
|
|
393
499
|
try:
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
500
|
+
cache_file.write_text(raw, encoding="utf-8")
|
|
501
|
+
meta_file.write_text(json.dumps({
|
|
502
|
+
"url": url, "cached_at": time.time(), "ttl": ttl, "size": len(raw),
|
|
503
|
+
}), encoding="utf-8")
|
|
504
|
+
print(f"[CACHE] 已写入 {key} ({len(raw)} bytes)", file=sys.stderr)
|
|
505
|
+
except Exception as e:
|
|
506
|
+
print(f"[WARN] 缓存写入失败: {e}", file=sys.stderr)
|
|
507
|
+
return raw
|
|
399
508
|
|
|
400
509
|
|
|
401
510
|
# ==================== CLI ====================
|
|
@@ -413,6 +522,10 @@ def main():
|
|
|
413
522
|
parser.add_argument("--cookie", help="--url 模式可选,鉴权后的 Cookie 值(如 'JSESSIONID=xxx')")
|
|
414
523
|
parser.add_argument("--auth", help="--url 模式可选,Basic 鉴权 base64(user:pass)")
|
|
415
524
|
parser.add_argument("--timeout", type=int, default=30, help="--url 模式 curl 超时秒数,默认 30")
|
|
525
|
+
parser.add_argument("--cache-ttl", type=int, default=DEFAULT_CACHE_TTL,
|
|
526
|
+
help=f"--url 模式缓存 TTL 秒数,默认 {DEFAULT_CACHE_TTL}(10 分钟),设为 0 关闭")
|
|
527
|
+
parser.add_argument("--no-cache", action="store_true",
|
|
528
|
+
help="--url 模式强制重新下载,忽略缓存")
|
|
416
529
|
|
|
417
530
|
parser.add_argument(
|
|
418
531
|
"--filter", action="append", default=[],
|
|
@@ -433,7 +546,10 @@ def main():
|
|
|
433
546
|
# 1. 读取原始 Swagger JSON
|
|
434
547
|
try:
|
|
435
548
|
if args.url:
|
|
436
|
-
raw_text =
|
|
549
|
+
raw_text = fetch_with_cache(
|
|
550
|
+
args.url, cookie=args.cookie, auth=args.auth, timeout=args.timeout,
|
|
551
|
+
ttl=args.cache_ttl, no_cache=args.no_cache,
|
|
552
|
+
)
|
|
437
553
|
else:
|
|
438
554
|
raw_text = Path(args.file).read_text(encoding="utf-8")
|
|
439
555
|
except Exception as e:
|
|
@@ -61,7 +61,7 @@ compatibility:
|
|
|
61
61
|
AI 助手在处理页面生成需求时,必须按以下流程使用工具:
|
|
62
62
|
|
|
63
63
|
### 步骤1:架构认知 + 需求理解
|
|
64
|
-
- 调用 `get_architecture_overview(detail="
|
|
64
|
+
- 调用 `get_architecture_overview(detail="full")` 获取 7 层架构、模板组合模式、全量组件设计元信息(componentSummary)+ styleMapping + assetExtraction,用于后续步骤的组件推理与样式注入
|
|
65
65
|
- 分析用户需求的页面布局类型、查询字段、操作按钮、表格列等
|
|
66
66
|
|
|
67
67
|
### 步骤2:组件发现(优先语义搜索)
|
|
@@ -93,17 +93,13 @@ AI 助手在处理页面生成需求时,必须按以下流程使用工具:
|
|
|
93
93
|
→ get_component_full_profile(批量) → 编码
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
**Sketch 结构 →
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
| 弹窗/抽屉图层 | `ModelOrDrawer` 组件 | 弹窗交互特征 |
|
|
104
|
-
| 页面标题 + 返回箭头 | `TitleAndBack` 组件 | 标题栏特征 |
|
|
105
|
-
| 左右分区布局 | `TemplateMode(templateTpye="LeftRight")` | 区域分布 |
|
|
106
|
-
| 上下分区布局 | `TemplateMode(templateTpye="TopBottom")` | 区域分布 |
|
|
96
|
+
**Sketch 结构 → 组件映射依据**:直接查阅步骤 1 `get_architecture_overview(detail="full")` 返回的 `componentSummary` 全量元信息,按以下顺序反查组件:
|
|
97
|
+
1. **大区切分** → `componentSummary.other` 中 `layoutRole === '页面定位'`(如 TemplateMode),结合 `variantProps.templateType` 和 `templatePatterns` 选模板
|
|
98
|
+
2. **区域容器** → `layoutRole === '区域容器'` 的组件(LeftTree / Query / TableOrList / ModelOrDrawer / TitleAndBack 等),按 `useCases` 匹配 Sketch 区域语义
|
|
99
|
+
3. **图表** → `componentSummary.echarts[*].sketchSignals` 反查 `chartType`
|
|
100
|
+
4. **变体** → `variantProps` 配合 `useCases` 区分子类型
|
|
101
|
+
|
|
102
|
+
`componentSummary` 由组件库源码动态扫描生成,是唯一可信源;不要依赖任何硬编码 Sketch→组件对照表。
|
|
107
103
|
|
|
108
104
|
**提示**:
|
|
109
105
|
- 大型 .sketch 文件用 `getPageStructure(maxDepth=1-2)` 即可获取布局概况,避免深层递归超时
|
|
@@ -204,7 +200,10 @@ sketch-mcp-server 是社区版 MCP Server(npm: `sketch-mcp-server`),通过
|
|
|
204
200
|
#### 4. get_architecture_overview
|
|
205
201
|
**功能**:查看 7 层架构(AntD→Cover→Wrapper→ProPackage→复合组件→模板)、层级关系和推荐使用顺序。含 LLM 映射修正提示。
|
|
206
202
|
**参数**:
|
|
207
|
-
- `detail` (enum, optional, default: "summary"):
|
|
203
|
+
- `detail` (enum, optional, default: "summary"):
|
|
204
|
+
- `summary`:层级概览 + 模板模式 + LLM 提示(轻量,~2K tokens)
|
|
205
|
+
- `full`:在 summary 基础上附加 `componentSummary`(全量组件清单 + 设计元信息:layoutRole/variantProps/styleInjection/useCases,other/echarts 层另含 compositionSlots/requiredHooks/chartType/sketchSignals)+ `styleMapping`(节点样式映射 + 注入机制 + 选择器表)+ `assetExtraction`(位图/图标/图表色提取规则)。设计稿 → 代码场景必选 full
|
|
206
|
+
- `decisions`:含架构决策记录
|
|
208
207
|
|
|
209
208
|
### Props 深度解析工具
|
|
210
209
|
|
|
@@ -34,12 +34,15 @@ Ant Design → ProComponents → Cover 层 → Wrapper 层 → ProPackages →
|
|
|
34
34
|
|
|
35
35
|
### 步骤1:架构认知(必做)
|
|
36
36
|
|
|
37
|
-
调用 `mcp__szcd-component-helper__get_architecture_overview`(detail="
|
|
37
|
+
调用 `mcp__szcd-component-helper__get_architecture_overview`(detail="full")一次性获取:
|
|
38
38
|
- `templatePatterns`:4种模板模式(TreeQueryTable/QueryTabsTables/LeftRight/UpDown)
|
|
39
39
|
- `llmMappingHints.commonMistakes`:LLM 常见映射错误
|
|
40
40
|
- `recommendedUsageOrder`:推荐使用顺序
|
|
41
|
+
- **`componentSummary`**:全量组件清单(cover/wrappers/proPackages/other/echarts),每个组件附带 `layoutRole`(页面定位/区域容器/可视化等)、`variantProps`(关键 variant 参数)、`styleInjection`(样式注入入口)、`useCases`(典型场景);other/echarts 层还附带 `compositionSlots`、`requiredHooks`、`chartType`、`sketchSignals` 等元信息——LLM 据此推理组件候选
|
|
42
|
+
- **`styleMapping`**:节点样式 → 组件样式的映射规则(nodeStyleRules / injectionMechanisms / selectorMap)
|
|
43
|
+
- **`assetExtraction`**:图标/位图/图表色提取规则(bitmapRules / iconWorkflow / chartColorMapping)
|
|
41
44
|
|
|
42
|
-
根据返回的 `templatePatterns`
|
|
45
|
+
根据返回的 `templatePatterns` 初步判断用户需求匹配哪个模板,并在步骤 3.5 用 `componentSummary` 推理组件候选列表。
|
|
43
46
|
|
|
44
47
|
### 步骤2:理解需求(必做)
|
|
45
48
|
|
|
@@ -86,15 +89,78 @@ queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })
|
|
|
86
89
|
|
|
87
90
|
组件候选列表推理**不**在步骤 2.5 内部完成,**统一在主流程中结合步骤 1 的 `templatePatterns` 推理**(详见主流程步骤 4 前置说明)。
|
|
88
91
|
|
|
92
|
+
### 步骤 2.5 必读:getMultipleNodeInfo 返回字段契约(sketch-mcp-server ≥ v1.6.1)
|
|
93
|
+
|
|
94
|
+
**每个节点的实测返回结构**(与本表不一致即视为版本不匹配,提示用户 `npm i -g @szc-ft/sketch-mcp-server@latest`):
|
|
95
|
+
|
|
96
|
+
| 节点类型 | 必有字段 | 关键样式字段(取值路径) |
|
|
97
|
+
|----------|----------|--------------------------|
|
|
98
|
+
| 所有 | `id`, `name`, `type`, `position:{x,y}`, `size:{width,height}`, `parent:{id,name,type}`, `style:{fills,borders,shadows,innerShadows,blur}`, `isVisible`, `opacity` | `style.fills[i].color.hex` / `style.borders[i].color.hex+thickness` / `style.shadows[i].{color,offsetX,offsetY,blurRadius}` |
|
|
99
|
+
| text | + `text:{content, attributes:[{location,length,attributes:{fontFamily,fontSize,color,alignment,lineHeight,letterSpacing}}]}` | 文字色 `text.attributes[0].attributes.color.hex`、字号 `text.attributes[0].attributes.fontSize`、**字重** = `text.attributes[0].attributes.fontFamily` 末尾(如 `PingFangSC-Semibold` → 600,`-Regular` → 400,`-Medium` → 500) |
|
|
100
|
+
| rectangle/oval | + `shape:{type, cornerRadius}` | `shape.cornerRadius`(圆角);背景 `style.fills[0].color.hex`;描边 `style.borders[0]` |
|
|
101
|
+
| **group/artboard** | + `children:[id]`, **`isVisualContainer:bool`** | **`isVisualContainer === true`** 时:`style.fills[0]`(含 `gradient`)= 容器背景、`style.borders[0]` = 容器边框 ← **渐变盒/卡片样式的唯一来源**;`false` 时只是图层文件夹,无视觉样式 |
|
|
102
|
+
| bitmap | + `image:{imageRef, originalSize}` | 用 `extractBitmaps` 取 base64 |
|
|
103
|
+
| shapePath | + `shape:{path}` | 用 `matchIconFromName` 匹配 iconfont |
|
|
104
|
+
|
|
105
|
+
#### 字段缺失自检(getMultipleNodeInfo 返回后立即跑)
|
|
106
|
+
|
|
107
|
+
抽样 5 个节点(含至少 1 text + 1 group + 1 rectangle)做契约校验:
|
|
108
|
+
|
|
109
|
+
- [ ] 5 个节点都有 `parent.name`(任一为 undefined → 服务端版本旧,提示升级到 ≥ v1.6.1)
|
|
110
|
+
- [ ] 所有 group/artboard 节点都有 `isVisualContainer` 布尔字段
|
|
111
|
+
- [ ] 所有 group 节点都有 `children` 数组(即使为空)
|
|
112
|
+
- [ ] text 节点 `text.attributes[0].attributes.fontFamily` 不为空
|
|
113
|
+
- [ ] 至少 1 个 rectangle 的 `style.fills.length > 0` 或 `style.borders.length > 0`(若全为 0,可能 .sketch 文件本身无视觉样式,或抽样偏差)
|
|
114
|
+
|
|
115
|
+
**任一项 ❌ → 停下来定位原因,禁止带着不完整的数据进入步骤 2.6**。
|
|
116
|
+
|
|
89
117
|
### 步骤2.5.1:图标与位图提取(有 bitmap/shapePath 节点时执行)
|
|
90
118
|
|
|
91
119
|
**输入**:步骤 2.5 产出的 bitmap ID 列表 + shapePath 节点名列表
|
|
92
120
|
|
|
121
|
+
#### 🔒 资源优先级铁律(必读,违反则直接重做本步骤)
|
|
122
|
+
|
|
123
|
+
代码中的每一处图标、背景、装饰资源,必须按以下优先级**依次尝试**,前一级能命中就**不许跳到下一级**:
|
|
124
|
+
|
|
125
|
+
| 优先级 | 来源 | 适用判定 | 渲染产物 |
|
|
126
|
+
|---|---|---|---|
|
|
127
|
+
| **P0** | **sketch 自带 bitmap** | 节点是 `type=bitmap` | `extractBitmaps` 返回的 `siblingsAfterMe + position + nodeSize` 三维判定渲染产物:区域背景图 / 行内图标 / 独立装饰插图(详见下方表 6) |
|
|
128
|
+
| **P1** | **sketch 自带 shapePath(iconfont 命中)** | shapePath/shapeGroup 节点名能在 iconfont 库匹配到 | `matchIconFromName` → `<IconfontWapper type='icon-xxx' />` |
|
|
129
|
+
| **P2** | **sketch 自带 shapePath(SVG 直出)** | 独立 shapePath(parent.type !== 'shapeGroup'),P1 未命中且 viewBox 校验通过 | `getShapePathData` → 内联 SVG |
|
|
130
|
+
| **P3** | **antd 图标语义匹配** | 上面三级都失败:shapeGroup 无法直出、节点名无 iconfont 匹配 | `<XxxOutlined />` 按 contextTable 选语义最近的 antd 图标 |
|
|
131
|
+
| **P4** | **纯 CSS / div 渲染** | 上面四级全部失败,且元素是几何/色块(非图标语义) | `<div style={{...}}>` |
|
|
132
|
+
|
|
133
|
+
**禁止行为(违反 = 重做)**:
|
|
134
|
+
- ❌ sketch 里有 bitmap 节点,却用 `<div style={{backgroundColor}}>` 模拟(必须 P0 提取真图)
|
|
135
|
+
- ❌ shapePath 节点直接降级 antd 图标,没跑过 `matchIconFromName`(跳过 P1)
|
|
136
|
+
- ❌ 凭节点名"猜"语义就选 antd 图标,没看 parent 上下文(contextTable 在 `assetExtraction.iconWorkflow.contextTable`)
|
|
137
|
+
- ❌ 把 P0 提取出的 base64 写成 hardcoded url 而非 inline data URI(资源会丢)
|
|
138
|
+
|
|
139
|
+
**前置自检**:步骤 1 拿到的 `assetExtraction.bitmapRules` + `assetExtraction.iconWorkflow` 是上述铁律的服务端权威定义;本步开始前必须已加载,否则回到步骤 1 调 `get_architecture_overview(detail="full")`。
|
|
140
|
+
|
|
93
141
|
**工具调用**:
|
|
94
142
|
|
|
95
143
|
1. `mcp__sketch-mcp-server__extractBitmaps`(bitmapIds)— 从 .sketch 解压目录提取 bitmap PNG 资源
|
|
96
|
-
-
|
|
97
|
-
-
|
|
144
|
+
- 返回(v1.7.0+):`{ bitmaps: [{ nodeId, name, base64, mimeType, size, fileName, position:{x,y}, nodeSize:{width,height}, parentId, parent:{id,name,type,size}, siblingsBeforeMe:[...], siblingsAfterMe:[...] }] }`
|
|
145
|
+
- **`siblingsAfterMe` 是 z-order 渲染在 bitmap 上方的兄弟节点**(Sketch `layers[]` 排在它之后的项),是判定"区域背景图 vs 独立装饰"的核心信号
|
|
146
|
+
- **不要再为每个 bitmap 单独跑 `getNodeInfo` 拿 position/parent**,这些字段在 extractBitmaps 返回里已有
|
|
147
|
+
|
|
148
|
+
##### 表 6:bitmap 用途判定与代码落地(必做)
|
|
149
|
+
|
|
150
|
+
每个 bitmap 都要按本表归类,**只看 `siblingsAfterMe` + `position` + `nodeSize/parent.size`**,不要凭名字猜。判定后必须落到代码(详见经验 9 的"资源落地校验")。
|
|
151
|
+
|
|
152
|
+
| # | 判定信号(联合判断) | 用途 | 代码落地方式 |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| 1 | `siblingsAfterMe.length > 0` **且** `nodeSize.width / parent.size.width >= 0.7` | **区域背景图**(容器底图,内容元素叠在上面) | `parent` 包一层 wrapper div,bitmap 作为 wrapper 的 `style={{backgroundImage:` `url(data:image/png;base64,${base64})` `, backgroundSize:'cover'}}`;`siblingsAfterMe` 作为 wrapper 子节点正常渲染 |
|
|
155
|
+
| 2 | `nodeSize.width <= 32 && nodeSize.height <= 32` **且** 同 parent 内有 text 兄弟(`siblingsBeforeMe + siblingsAfterMe` 含 text) | **行内图标**(表格行图标、按钮前缀图标等) | `<img src={`data:image/png;base64,${base64}`} style={{width:N,height:N}} />` 替换原本的 `<IconfontWapper>` 占位 |
|
|
156
|
+
| 3 | `siblingsAfterMe.length === 0 && siblingsBeforeMe.length === 0`(独占父容器) | **独立装饰插图**(占位图、空状态图等) | `<img src={`data:image/png;base64,${base64}`} style={{width:nodeSize.width,height:nodeSize.height}} />` 走文档流 |
|
|
157
|
+
| 4 | `siblingsAfterMe.length > 0` **但** 占父宽 < 70% | **嵌入式插图**(区域内某子块的背景) | 同 #1,但 wrapper 是该子块而非整个 parent;定位用 `position:'absolute', top:position.y, left:position.x` |
|
|
158
|
+
|
|
159
|
+
**反例(违反 = 重做)**:
|
|
160
|
+
- ❌ 把 #1 区域背景图渲染为独立 `<img>`(反馈 #14 的 `未编目资源` 493×64 y=52 错例)——丢失"内容叠在底图上"的视觉关系
|
|
161
|
+
- ❌ 把 #2 行内图标用 `backgroundImage` 写在 `<div>` 上——`<div>` 没设宽高就塌缩;用 `<img>` 自带尺寸更稳
|
|
162
|
+
- ❌ 同一 imageRef 多个 nodeId 引用,extractBitmaps 多次调用——只调一次(按 nodeId 列表去重前判断 `fileName`),渲染时复用 base64 引用
|
|
163
|
+
|
|
98
164
|
2. `mcp__sketch-mcp-server__matchIconFromName`(nodeNames, library="newFont")— 匹配 iconfont 图标库(v1.5.0+ 评分制)
|
|
99
165
|
- 输入 shapePath 节点名列表(如 `["表 icon", "新建文件夹", "编组11备份"]`)
|
|
100
166
|
- **评分机制**:compoundIcons 强映射 +100、keyword 精确 +20、keyword 包含 +10、semanticTag +3
|
|
@@ -165,15 +231,20 @@ queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })
|
|
|
165
231
|
- 新做法:直接传入全部 ID(如 200 个)→ 内部自动按 100 个一批处理 → 返回合并结果
|
|
166
232
|
- 返回结构不变:`{ nodes: [...], total, requested, batches }`
|
|
167
233
|
|
|
168
|
-
**经验3
|
|
234
|
+
**经验3:归属判定走 x+y+parent 三轴联合,禁止单轴定论(坐标和 parent 互为纠错器)**
|
|
169
235
|
|
|
170
|
-
|
|
236
|
+
实战教训:
|
|
237
|
+
- 反例 A:`'主题'text(x=272,y=149)` 单看 x 在右侧内容区列,但 `parent.name=leftTreeHeader` 强语义 → 归左树(情形 C:parent 修正坐标)
|
|
238
|
+
- 反例 B(反馈 #11):`'段码含义'text(x=300,y=257)` 坐标落在 `渐变容器(x=240..960, y=189..297)` 内,但 parent 是兄弟编组 `cataMain` → 视觉上属于渐变容器内(情形 B:图层拆开,按坐标渲染)
|
|
171
239
|
|
|
172
240
|
正确做法:
|
|
173
|
-
- 步骤 2.5 第 4 步先 `mode="tree"` 看顶层 group 分区(通常 2-5
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
241
|
+
- 步骤 2.5 第 4 步先 `mode="tree"` 看顶层 group 分区(通常 2-5 个)
|
|
242
|
+
- 步骤 2.6 做双向校验:Pass 1 坐标推假设 → Pass 2 parent 质检 → 四类情形 A/B/C/D 处理
|
|
243
|
+
- 大区归属:`parent.name` 含 `LeftTree/树/PageContent/sidebar/cataMain` 等强语义关键字时优先于 x 分布
|
|
244
|
+
- 容器归属:必须 `x ∈ [容器x, 容器x+w]` **且** `y ∈ [容器y, 容器y+h]`,单轴判定无效
|
|
245
|
+
- 跨分区不比 y;同分区内按 y 升序;同行(y 差<8px)按 x 升序
|
|
246
|
+
|
|
247
|
+
详细决策矩阵见步骤 2.6。
|
|
177
248
|
|
|
178
249
|
**经验4:信息栏/卡片容器必须先取 group 再取子元素**
|
|
179
250
|
|
|
@@ -209,12 +280,146 @@ queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })
|
|
|
209
280
|
|
|
210
281
|
识别出图表后,**图例色块**应归入 ECharts `legend` 配置(不要用 div 硬编码),**柱体色**映射到 `series.itemStyle.color`。
|
|
211
282
|
|
|
283
|
+
**图表组件类型选择**:参考步骤 1 返回的 `componentSummary.echarts`,每项含 `chartType`(line/bar/pie 等)和 `sketchSignals`(典型 Sketch 特征),按 sketchSignals 反查 chartType 即可锁定组件名(如 `Line` / `Bar` / `Pie`)。
|
|
284
|
+
|
|
212
285
|
**经验7:矢量图形用 matchIconFromName 自动匹配,无需手动解压**
|
|
213
286
|
- `shapePath` / `shapeGroup` 在 sketch 节点中占比可能高达 46.8%,但 `renderNodeAsBase64` 不支持渲染
|
|
214
287
|
- **新方案**:`matchIconFromName(shapePathNames)` 自动查询 iconfont 库 → 返回匹配结果
|
|
215
288
|
- bitmap 用 `extractBitmaps(bitmapIds)` 自动提取 PNG 资源
|
|
216
289
|
- 兜底:未匹配的节点用 antd `@ant-design/icons` 同语义图标替代,在代码注释中标注“⚠️ 矢量图标用 antd 图标替代,原始 Sketch 为 shapePath”
|
|
217
290
|
|
|
291
|
+
**经验8:rectangle 的 fill/border 必须以内联样式或精确选择器落到 DOM,禁止借用通用 CSS class**
|
|
292
|
+
|
|
293
|
+
实战教训(反馈 #11):5 个段码视觉块的颜色(#f2f8fd/#f5ffed/#fffbe6/#f5f6f7)完整地从 rectangle 节点拿到了,但代码却用了 `.beforebg{background:#ebf0ff}` 这种与设计稿无对应关系的通用 class,导致 5 个块颜色全错。
|
|
294
|
+
|
|
295
|
+
正确做法:
|
|
296
|
+
- 步骤 2.6 的样式映射表(表 5)必须为每个"颜色块"列出 rectangle 数据 → 内联 style 的对应关系
|
|
297
|
+
- 写代码时直接用 `<div style={{background:'#f2f8fd',border:'1px solid #d9eafb'}}>`
|
|
298
|
+
- 若必须用 CSS class,类名要能反向追到 Sketch 节点(如 `.segmentBlock-blue` 而非 `.beforebg`),且 class 内容必须**精确等于** rectangle 的 fill/border
|
|
299
|
+
- 反例排查:写完代码后 grep 一下 CSS class 名,出现 `.bg1/.box2/.before*` 这类无语义类名、且颜色和设计稿对不上 → 几乎确定是凭空兜底
|
|
300
|
+
|
|
301
|
+
**经验9:bitmap 资源落地校验(每张 PNG 必须对应一处代码引用)**
|
|
302
|
+
|
|
303
|
+
实战教训(反馈 #14,2026-06-15):`未编目资源` bitmap(493×64, y=52)首版被渲染为独立 `<img>` 装饰插图,实际上它是 64px 高统计区域的底图(`siblingsAfterMe` 含 7 个 text/rect 内容元素叠在上面)。9 张 PNG 资源拿到了 base64,但其中 1 张用错位置——这种"提取了但用错"的回退比"忘记提取"更隐蔽。
|
|
304
|
+
|
|
305
|
+
**校验流程(写代码后必跑)**:
|
|
306
|
+
|
|
307
|
+
1. 把 `extractBitmaps` 返回的每个 `bitmap` 按表 6 归类,列出"用途清单"(区域背景 N 个、行内图标 N 个、独立装饰 N 个)
|
|
308
|
+
2. 在生成的代码里 grep `data:image/png;base64`,统计实际引用数 ≥ 用途清单总数(去重后)
|
|
309
|
+
3. 对每个 #1 区域背景图核对:是否真的写在 wrapper div 的 `style.backgroundImage` 上?wrapper 内部是否包含 `siblingsAfterMe` 列出的子节点?
|
|
310
|
+
4. 对每个 #2 行内图标核对:原本在该位置的 `<IconfontWapper>` / antd 图标是否已被 `<img>` 替换?
|
|
311
|
+
5. 对每个 #3 独立装饰核对:是否在 parent 的纵向流中出现?
|
|
312
|
+
|
|
313
|
+
**反例(违反 = 重做)**:
|
|
314
|
+
- ❌ extractBitmaps 拿到 9 张 PNG,代码里只 grep 出 8 个 `data:image/png;base64` 引用——必有 1 张被丢
|
|
315
|
+
- ❌ #1 区域背景图被错放成独立 `<img>`,导致 `siblingsAfterMe` 的内容元素失去底图衬托(这是反馈 #14 的具体回退)
|
|
316
|
+
- ❌ 同 imageRef 多次出现(如 `文件备份2/3/4` 复用同一 PNG),代码生成了 N 份 base64 字符串而非引用同一个常量——文件大小翻倍
|
|
317
|
+
|
|
318
|
+
### 步骤2.6:布局推理(必做,写代码前的最后一道关)
|
|
319
|
+
|
|
320
|
+
⚠️ 这一步专治"数据全拿到却放错位置"。反馈 #11 的 5 处错位全部由跳过此步骤导致。
|
|
321
|
+
|
|
322
|
+
**核心思路**:x+y 坐标先画"视觉假设",parent 字段做质检纠错。两套信号**互为纠错器**,不是单方向"谁优先"。
|
|
323
|
+
|
|
324
|
+
**输入**:步骤 2.5 产出的 `text + rectangle + group` 节点全量数据(含 `position.{x,y}`、`size.{width,height}`、`parent.{id,name,type}`、`style`、`isVisualContainer`)
|
|
325
|
+
|
|
326
|
+
#### Pass 1:坐标推假设(产出 5 张表)
|
|
327
|
+
|
|
328
|
+
每张表都必须输出,即使某类节点为空也要写"无"。
|
|
329
|
+
|
|
330
|
+
##### 表 1:分区识别表(x+y+parent 联合,定大区)
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
| 节点 name | x | y | parent.name | 推断分区 | 决策依据 |
|
|
334
|
+
|----------------|-----|-----|------------------|--------------|-------------------------|
|
|
335
|
+
| 主题(text) | 272 | 149 | leftTreeHeader | 左树标题区 | parent 强语义压过 x 列 |
|
|
336
|
+
| 段码定义(text) | 320 | 200 | cataMain | 右侧内容-顶部 | parent 含 PageContent 类语义 |
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
判定流程:
|
|
340
|
+
1. 按 x 把画板切成 1-3 列(x<300 → 左区、x≥300 → 右区,或按设计稿实际边界)
|
|
341
|
+
2. 用 `parent.name` 校验:含 `LeftTree/树/sidebar/cataMain/PageContent/header/footer` 等关键字时以 parent 为准
|
|
342
|
+
3. 冲突时遵循"经验3"的 A/B/C/D 矩阵(见下方 Pass 2)
|
|
343
|
+
|
|
344
|
+
→ 此表输出**布局类型**:UpDown(单列)/ LeftRight(双列)/ LeftMiddleRight(三列)
|
|
345
|
+
|
|
346
|
+
##### 表 2:纵向顺序表(同分区内按 y)
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
| 分区 | 节点 | y | 推断角色 |
|
|
350
|
+
|------------|---------------|-----|-------------------|
|
|
351
|
+
| 右侧内容区 | 段码定义 | 200 | 卡片 1 |
|
|
352
|
+
| 右侧内容区 | Tabs | 420 | Tabs(type=card) |
|
|
353
|
+
| 右侧内容区 | 配置说明 | 478 | 卡片 2(Tabs 下方) |
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
铁律:**同分区内**渲染顺序按 y 升序。跨分区不比 y(左树第 100 行 y 和右内容区第 1 行 y 没有可比性)。
|
|
357
|
+
|
|
358
|
+
##### 表 3:横向排列表(同行按 x,y 差<8px 视为同行)
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
| 分区/容器 | 节点 | x | y | 推断角色 |
|
|
362
|
+
|-----------------|--------------|-----|-----|-------------------------|
|
|
363
|
+
| Query 操作栏 | 名称输入框 | 320 | 50 | Form.Item col-span=6 |
|
|
364
|
+
| Query 操作栏 | 状态下拉 | 540 | 50 | Form.Item col-span=6 |
|
|
365
|
+
| Query 操作栏 | 查询按钮 | 980 | 50 | 操作按钮(右对齐) |
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
判定流程:
|
|
369
|
+
1. y 差<8px 识别同一行
|
|
370
|
+
2. 同行按 x 升序排
|
|
371
|
+
3. 计算 x 间距推算 antd Col span(24 栅格)或 flex
|
|
372
|
+
4. 最右元素若 `x + width` 接近容器右边界 → 右对齐(justify-content: flex-end)
|
|
373
|
+
|
|
374
|
+
##### 表 4:容器归属表(x+y 双轴边界检测)
|
|
375
|
+
|
|
376
|
+
仅对 `isVisualContainer === true` 的 group 和带 fill/border 的 rectangle 做检测。
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
| 容器 | x..x+w | y..y+h | 落入子元素 |
|
|
380
|
+
|------------------|----------|----------|----------------------------------|
|
|
381
|
+
| 渐变容器(group) | 240..960 | 189..297 | 段码含义(x=300,y=257) ✅ |
|
|
382
|
+
| 段码块-蓝(rect) | 320..420 | 320..360 | (无子节点,仅样式) |
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
判定式:子元素 `x ∈ [容器x, 容器x+w]` **且** `y ∈ [容器y, 容器y+h]` → 渲染在容器内部。**单轴判定无效**。
|
|
386
|
+
|
|
387
|
+
##### 表 5:样式映射表(rectangle/group fill → UI 组件内联样式)
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
| Sketch 节点 | 数据来源 | bg/border | 映射到代码 |
|
|
391
|
+
|----------------------|--------------------------|--------------------------|-----------------------------------------------------------|
|
|
392
|
+
| 段码块-蓝(rectangle) | style.fills[0]/borders[0]| bg #f2f8fd / b #d9eafb | <div style={{background:'#f2f8fd',border:'1px solid #d9eafb'}}> |
|
|
393
|
+
| 渐变容器(group) | style.fills[0].gradient | linear #f3faff→#f8fdff | <div style={{background:'linear-gradient(180deg,#f3faff,#f8fdff)'}}> |
|
|
394
|
+
| Tab 激活态 | rect bg #fff border #0079f7 | bg #fff / b #0079f7 | Tabs type="card" + .segmentTabs .ant-tabs-tab-active 覆盖 |
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
铁律(见经验 8):rectangle 的 fill/border 必须以**内联样式**或精确选择器落到代码,**禁止**用 `.beforebg{background:#ebf0ff}` 这类与设计稿无对应关系的 CSS class。
|
|
398
|
+
|
|
399
|
+
#### Pass 2:parent 质检(按 A/B/C/D 矩阵处理冲突)
|
|
400
|
+
|
|
401
|
+
逐行扫表 1,对照 parent 字段:
|
|
402
|
+
|
|
403
|
+
| 情形 | 坐标判定 | parent 判定 | 处理 |
|
|
404
|
+
|------|----------|-------------|------|
|
|
405
|
+
| A | 在容器 X 内 | parent 链含 X | ✅ 双向一致,直接采用 |
|
|
406
|
+
| B | 在容器 X 内 | parent 是兄弟节点 Y | ⚠️ **视觉容器优先**(设计师把图层拆开了)→ 按坐标渲染到 X 内 |
|
|
407
|
+
| C | 不在容器 X 内 | parent.name 强语义 = X | ⚠️ **parent 优先**(绝对定位/溢出布局)→ 按 parent 归属,加 `position:absolute` 标注 |
|
|
408
|
+
| D | 双向都不落 | parent 是顶层 | 独立元素,按坐标走纵向顺序 |
|
|
409
|
+
|
|
410
|
+
输出**最终归属表**:在表 1 末尾追加 `情形(A/B/C/D)` 列。
|
|
411
|
+
|
|
412
|
+
#### Pass 3:自检清单(写代码前必跑)
|
|
413
|
+
|
|
414
|
+
- [ ] 表 1 是否按 x 分布判定了布局类型(UpDown / LeftRight / LeftMiddleRight)?
|
|
415
|
+
- [ ] 表 2 同分区内是否按 y 排序,没有跨分区比 y?
|
|
416
|
+
- [ ] 表 3 同行元素(y 差<8px)是否按 x 排序,计算了栅格 span 或右对齐?
|
|
417
|
+
- [ ] 表 4 容器归属是否做了 x+y 双轴检测,不是只看一轴?
|
|
418
|
+
- [ ] 表 5 所有"颜色块/卡片/渐变"样式都来自 rectangle/group 的 style.fills/borders,没有出现凭空 CSS class?
|
|
419
|
+
- [ ] Pass 2 冲突行是否标了 A/B/C/D 情形和决策依据?
|
|
420
|
+
|
|
421
|
+
任一项 ❌ → 回 Pass 1 补;禁止跳到步骤 3 前未输出全部 5 张表 + 最终归属表。
|
|
422
|
+
|
|
218
423
|
### 步骤3:分析设计稿图片(有图片时执行)
|
|
219
424
|
|
|
220
425
|
- 主智能体已提供图片描述时,直接使用,跳过此步
|
|
@@ -246,23 +451,14 @@ queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })
|
|
|
246
451
|
- ❌ 在步骤 3 内部推理组件候选列表(应只产出图片描述)
|
|
247
452
|
- ❌ 跳过此环节直接调 `get_component_full_profile`(必须先有候选列表)
|
|
248
453
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
| 中间表头 + 数据行图层 | `TableOrList` |
|
|
258
|
-
| 弹窗/抽屉图层 | `ModelOrDrawer` |
|
|
259
|
-
| 页面标题 + 返回箭头 | `TitleAndBack` |
|
|
260
|
-
| 详情展示区域(只读字段) | `FormItemOrDetailItem(type="detail")` |
|
|
261
|
-
| 卡片列表 | `TableOrList(componentType="ProList")` |
|
|
262
|
-
| 可编辑表格 | `TableOrList(componentType="EditableProTable")` |
|
|
263
|
-
| 折线趋势图 | `Line` |
|
|
264
|
-
| 柱状对比图 | `Bar` |
|
|
265
|
-
| 饼图/环形图 | `Pie` |
|
|
454
|
+
**推理依据**:步骤 1 返回的 `componentSummary` 是组件候选的权威目录,每项含 `layoutRole` / `variantProps` / `useCases` / `sketchSignals`(图表层)。按以下顺序反查:
|
|
455
|
+
|
|
456
|
+
1. **大区切分** → 看 `componentSummary.other` 里 `layoutRole === '页面定位'` 的组件(TemplateMode)匹配 `templateType` 变体
|
|
457
|
+
2. **区域容器** → `layoutRole === '区域容器'` 的组件(LeftTree / Query / TableOrList / ModelOrDrawer 等),按 `useCases` 描述匹配 Sketch 区域语义
|
|
458
|
+
3. **图表组件** → `componentSummary.echarts[*].sketchSignals` 反查 `chartType`,锁定 Line / Bar / Pie / Scatter 等
|
|
459
|
+
4. **变体选择** → 同一组件的子类型(如 TableOrList 的 ProTable / ProList / EditableProTable)查 `variantProps` 配合 useCases
|
|
460
|
+
|
|
461
|
+
不要再依赖任何硬编码映射表——`componentSummary` 由组件库源码动态生成,是唯一可信源。
|
|
266
462
|
|
|
267
463
|
### ⚠️ TemplateMode 槽位传参铁律(高频踩坑)
|
|
268
464
|
|
|
@@ -314,8 +510,20 @@ queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })
|
|
|
314
510
|
### 依赖关系
|
|
315
511
|
| 组件 | 依赖的 szcd 组件 | 依赖的 hooks |
|
|
316
512
|
|------|-----------------|-------------|
|
|
513
|
+
|
|
514
|
+
### 布局顺序(来自步骤 2.6 表 2 压缩,按 y 排序)
|
|
515
|
+
[页面级垂直顺序,如:TitleAndBack → 段码定义卡片 → Tabs → 配置说明卡片]
|
|
516
|
+
|
|
517
|
+
### 关键样式映射(来自步骤 2.6 表 5,仅列非默认样式)
|
|
518
|
+
| UI 元素 | Sketch 数据来源 | 落地方式 |
|
|
519
|
+
|---------|----------------|----------|
|
|
520
|
+
| 段码块×5 | 5 个 rectangle 的 fills[0]/borders[0] | 内联 style |
|
|
521
|
+
| 渐变容器 | group(isVisualContainer) 的 fills[0].gradient | 内联 linear-gradient |
|
|
522
|
+
| Tab 激活态 | rectangle bg #fff border #0079f7 | type="card" + .segmentTabs 覆盖 |
|
|
317
523
|
```
|
|
318
524
|
|
|
525
|
+
用户在确认环节即可发现"段码块颜色没列上"或"Tab 样式漏识别",避免反馈 #11 那样要等代码生成后反复修。
|
|
526
|
+
|
|
319
527
|
### 步骤6:生成代码(必做)
|
|
320
528
|
|
|
321
529
|
遵循规范:
|
|
@@ -326,7 +534,21 @@ queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })
|
|
|
326
534
|
- Hooks 按依赖关系正确引入(useLeftTree、useTable 等)
|
|
327
535
|
- 复合组件配合 TemplateMode 使用
|
|
328
536
|
|
|
329
|
-
|
|
537
|
+
#### 步骤 6 末尾必做:样式落地校验
|
|
538
|
+
|
|
539
|
+
代码生成完成后,**逐行核对**步骤 2.6 表 5(样式映射表)的每一项是否真落到代码里:
|
|
540
|
+
|
|
541
|
+
```
|
|
542
|
+
| 样式映射表行 | 代码中对应位置 | 校验 |
|
|
543
|
+
|---------------------------------------------|-------------------------------------------------------------|------|
|
|
544
|
+
| 段码块-蓝 bg #f2f8fd border #d9eafb | CreateOrEdit.tsx:L120 `<div style={{background:'#f2f8fd',border:'1px solid #d9eafb'}}>` | ✅ |
|
|
545
|
+
| 渐变容器 linear #f3faff→#f8fdff | CreateOrEdit.tsx:L98 `style={{background:'linear-gradient(...)'}}` | ✅ |
|
|
546
|
+
| Tab 激活态 bg #fff border #0079f7 | styles.less:L45 `.segmentTabs .ant-tabs-tab-active{...}` | ✅ |
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
❌ 如果某行找不到对应代码位置 → 直接补到代码里再继续,**禁止说"代码已生成"前留缺口**。这是反馈 #11"5 个段码块全错"的根因——所有颜色都拿到了,但没逐一回查是否落到了代码上。
|
|
550
|
+
|
|
551
|
+
代码生成 + 落地校验都通过后,询问用户:
|
|
330
552
|
> "代码已生成,你可以选择:
|
|
331
553
|
> 1. 调整样式或交互细节
|
|
332
554
|
> 2. 补充表单校验/批量操作等功能
|