mta-mcp 3.16.0 → 3.16.2

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.
@@ -36,23 +36,25 @@
36
36
  5. 只有当 compact 无法解释局部布局/样式时,再补用 measure/style
37
37
  ```
38
38
 
39
- ### 图标资产强制规则(v4.7.0 新增)
39
+ ### 图标资产强制规则(v4.9.0 更新)
40
40
 
41
- - 默认流程改为:**先在 `assets/icons/` 查找用户手工导出的图标 PNG**,文件名优先匹配设计稿图层名对应的 `*_icon@4x.png`,找到即直接复用。
41
+ - 默认流程改为:**先在项目现有素材目录查找用户手工导出的图标 PNG**,文件名优先匹配设计稿图层名主体;`xxx@4x.png` 里的 `@4x` 只是倍率后缀,不是主体语义,找到即直接复用。
42
42
  - 先看测量结果里的图层类型:`icon/svg` 才允许落地为 SVG;如果 Sketch 返回的是 `Image`,禁止伪造 SVG。
43
- - `Image` 类型的 icon/button/arrow/help/customer-service 图层,优先做 **精确文件名查找**,不要先做语义联想或 canonical 合并。
43
+ - `Image` 类型的 icon/button/arrow/help/customer-service 图层,优先按 **文件名主体查找**,不要先做语义联想或 canonical 合并。
44
44
  - `Help Button`、`Customer Service Button`、`Customer Service Icon` 不能只因为位置相近就合并成同一个语义;先按 Sketch 图层名区分资产,再决定是否复用。
45
- - 只有当 `assets/icons/` 中不存在对应的手工导出资源时,才允许提示用户补导;不要再优先产出 page-local PNG 副本。
46
- - 手工 `*_icon@4x.png` 约定下,默认不再强制要求补齐 `2.0x/3.0x`;只有旧位图流程才继续要求多倍率变体。
45
+ - 如果 `restorationContract.blockOrder` 中的顶部入口测得是 `Image`,回复里必须明确写出:测得的真实块名、最终采用的 assetPath、以及它来自 `bitmapLookupCandidates` 还是 canonical 副本;未确认前禁止继续猜图标。
46
+ - 只有当项目素材目录中不存在对应的手工导出资源时,才允许提示用户补导;不要再优先产出 page-local PNG 副本。
47
+ - 手工高倍率文件(如 `xxx@4x.png`)约定下,默认不再强制要求补齐 `2.0x/3.0x`;只有旧位图流程才继续要求多倍率变体。
47
48
  - 已存在但内容损坏的 SVG 视为无效资产,必须修复或替换;禁止为了绕过损坏文件再平行创建第二个语义相同的新图标。
48
- - 设计源是 `Image` 时,回复里必须明确说明“来源优先是 assets/icons 下的手工导出 PNG,不是设计稿原生 SVG”,避免误报为高清 SVG。
49
+ - 设计源是 `Image` 时,回复里必须明确说明“来源优先是项目素材目录中的手工导出 PNG,不是设计稿原生 SVG”,避免误报为高清 SVG。
50
+ - 若 `bitmapLookupCandidates` 与代码实际资产名都未命中,必须停止并说明“设计资产未确认”,而不是回退到 help/customer-service 等语义相近资源。
49
51
 
50
52
  **注意:**
51
53
  - 不要手动读取 artboard-measure.js(已废弃,合并进 sketch-tools.js)
52
54
  - 不要手动拼接 _SKETCH_CMD(由 sketch_measure skill 自动处理)
53
55
  - 测量结果中所有颜色均包含 `flutterColor: "Color(0xAARRGGBB)"` 格式,可直接使用
54
56
  - 设计稿还原时,restorationContract 优先于现有业务组件、路由标题、i18n 文案和常见 CTA 习惯
55
- - 图标资产落地前,必须先做一次 `assets/icons/` 精确搜索;先看设计稿图层名对应的 `*_icon@4x.png`,再考虑其他复用路径
57
+ - 图标资产落地前,必须先做一次项目素材目录搜索;先看设计稿图层名主体对应的现有文件,再考虑其他复用路径
56
58
 
57
59
  ### 测量数据关键字段(v4.6.0 新增带★标记)
58
60
 
@@ -470,7 +472,7 @@ get_standard_by_id({ id: 'sketch-pitfalls' })
470
472
  ```
471
473
  1. 测量插件标记 `isIcon: true` 并输出 iconContentBounds
472
474
  2. 读取 containerSize(Group 尺寸)和 contentSize(实际路径尺寸)
473
- 3. 将 SVG 文件放入 assets/icons/ 目录
475
+ 3. 将 SVG 文件放入项目约定的素材目录
474
476
  4. 在 pubspec.yaml 中声明 assets
475
477
  5. 使用 flutter_svg 包加载,宽高用 contentSize
476
478
  6. 外层用 SizedBox(containerSize) + Center 包裹
package/dist/index.js CHANGED
@@ -6295,7 +6295,7 @@ var OUTPUT_GUIDE = {
6295
6295
  "- **\u5173\u952E\u89C4\u5219**: \u5F53 colorStrategy=preserve\uFF0C\u6216 SVG \u672C\u8EAB\u5E26 fill/fill-opacity/\u6E10\u53D8\u65F6\uFF0C**\u7981\u6B62**\u518D\u5957 ColorFilter\uFF0C\u5426\u5219\u4F1A\u51FA\u73B0\u989C\u8272\u53D1\u7070\u3001\u900F\u660E\u5EA6\u88AB\u4E8C\u6B21\u5904\u7406\u7684\u95EE\u9898",
6296
6296
  "- **flutterSvgCode**: \u53EF\u76F4\u63A5\u7C98\u8D34\u7684 SvgPicture.string(...) \u4EE3\u7801\uFF1B\u53EA\u6709\u5355\u8272\u56FE\u6807\u624D\u4F1A\u81EA\u52A8\u9644\u5E26 ColorFilter",
6297
6297
  "- **iconContentBounds**: \u56FE\u6807\u5185\u5BB9\u5B9E\u9645\u8FB9\u754C\uFF08vs \u5BB9\u5668\u5C3A\u5BF8\uFF09\uFF0C\u7528\u4E8E SizedBox \u7EA6\u675F",
6298
- "- **iconExportPath/iconSvgFileName**: \u82E5\u9700\u8981\u843D\u76D8\u8D44\u6E90\uFF0C\u4F7F\u7528\u63D2\u4EF6\u5EFA\u8BAE\u6587\u4EF6\u540D\u4FDD\u5B58\u5230 assets/icons/",
6298
+ "- **iconExportPath/iconSvgFileName**: \u82E5\u9700\u8981\u843D\u76D8\u8D44\u6E90\uFF0C\u4F7F\u7528\u63D2\u4EF6\u5EFA\u8BAE\u6587\u4EF6\u540D\u4FDD\u5B58\u5230\u9879\u76EE\u73B0\u6709\u7D20\u6750\u76EE\u5F55\uFF0C\u76EE\u5F55\u540D\u9075\u5FAA\u9879\u76EE\u7EA6\u5B9A\u800C\u4E0D\u662F\u56FA\u5B9A assets/icons/",
6299
6299
  "- **\u9875\u9762\u5C40\u90E8\u4FEE\u590D\u89C4\u5219**: \u82E5\u95EE\u9898\u53EA\u51FA\u5728\u67D0\u4E2A chevron/flag/badge/icon\uFF0C\u4F18\u5148\u91CD\u65B0\u9009\u4E2D\u8BE5\u56FE\u5C42\u6267\u884C cmd=svg \u6216\u5C40\u90E8\u5BFC\u51FA\uFF1B\u4E0D\u8981\u76F4\u63A5\u590D\u7528\u5176\u5B83\u9875\u9762\u7684\u540C\u540D\u5168\u5C40\u8D44\u4EA7",
6300
6300
  "- **\u8D44\u6E90\u590D\u7528\u6821\u9A8C**: \u65E7\u8D44\u4EA7\u5728\u590D\u7528\u524D\uFF0C\u5FC5\u987B\u6838\u5BF9 viewBox\u3001containerSize/contentSize\u3001fill-opacity \u548C\u5F53\u524D\u9875\u9762\u56FE\u5C42\u6765\u6E90\u662F\u5426\u4E00\u81F4",
6301
6301
  "- \u8986\u76D6\u4FEE\u6B63: \u56DB\u8FB9\u8DDD\u22642pt \u6216\u5BBD\u9AD8\u2265\u7236\u5BB9\u566888% \u7684 ShapePath \u662F\u80CC\u666F\u5C42\uFF0C\u5DF2\u81EA\u52A8\u6392\u9664",
@@ -6406,7 +6406,7 @@ var OUTPUT_GUIDE = {
6406
6406
  "- **rp**: [left,top,right,bottom] \u76F8\u5BF9\u7236\u5BB9\u5668\u8FB9\u8DDD",
6407
6407
  "- **vf**: [x,y,w,h] \u89C6\u89C9\u8FB9\u754C\uFF1B\u5F53\u80CC\u666F\u5C42/\u9634\u5F71\u8D85\u51FA\u7236 Group frame \u65F6\uFF0C\u5BB9\u5668\u5C3A\u5BF8\u548C\u89C6\u89C9\u5E95\u8FB9\u8DDD\u4F18\u5148\u770B vf",
6408
6408
  "- **icon/svg/cs/xs**: \u56FE\u6807\u6807\u8BC6/\u771F\u5B9E SvgPicture.string(...) \u4EE3\u7801/containerSize/contentSize",
6409
- "- **image/img/bitmapPath/bitmapScales/bitmapLookupCandidates**: \u4F4D\u56FE\u56FE\u5C42\u6807\u8BC6/Image.asset \u4EE3\u7801/\u5EFA\u8BAE\u843D\u76D8\u8DEF\u5F84/\u5BFC\u51FA\u7EA6\u5B9A/\u4F18\u5148\u641C\u7D22\u7684\u624B\u5DE5\u56FE\u6807\u6587\u4EF6\u540D",
6409
+ "- **image/img/bitmapPath/bitmapScales/bitmapLookupCandidates/bitmapReuseMode**: \u4F4D\u56FE\u56FE\u5C42\u6807\u8BC6/Image.asset \u4EE3\u7801/\u5EFA\u8BAE\u843D\u76D8\u8DEF\u5F84/\u5BFC\u51FA\u7EA6\u5B9A/\u4F18\u5148\u641C\u7D22\u7684\u624B\u5DE5\u56FE\u6807\u6587\u4EF6\u540D/\u662F\u5426 shared-canonical",
6410
6410
  "- **svgMode/svgReason/svgFile**: preserve|tint / \u989C\u8272\u7B56\u7565\u539F\u56E0 / \u5EFA\u8BAE\u843D\u76D8\u6587\u4EF6\u540D",
6411
6411
  "- **text/style/family/textHeight**: \u6587\u672C\u5185\u5BB9/\u5B8C\u6574 TextStyle/\u5B57\u4F53\u65CF/\u884C\u9AD8\u6BD4",
6412
6412
  "- **lh/strut**: strutHeight / \u5B8C\u6574 StrutStyle(...)",
@@ -6423,7 +6423,8 @@ var OUTPUT_GUIDE = {
6423
6423
  "- \u56FE\u6807\u662F\u5426\u8BEF\u7528\u65E7\u9875\u9762\u8D44\u4EA7\uFF1A\u540C\u540D\u7BAD\u5934/\u56FE\u6807\u9ED8\u8BA4\u4E0D\u53EF\u4FE1\uFF0C\u5148\u6838\u5BF9\u6765\u6E90\u56FE\u5C42",
6424
6424
  "- SVG \u662F\u5426\u8BEF\u52A0 ColorFilter\uFF1A\u4FDD\u7559\u539F\u8272\u7684 SVG \u4E0D\u5F97\u4E8C\u6B21\u67D3\u8272",
6425
6425
  "- \u56FE\u6807\u662F\u5426\u7528\u9519\u5C3A\u5BF8\uFF1A16x16 \u69FD\u4F4D\u4E0D\u7B49\u4E8E 16x16 \u56FE\u5F62\uFF0C\u6309 xs/cs \u5206\u79BB\u5360\u4F4D\u4E0E\u6E32\u67D3",
6426
- "- Bitmap \u662F\u5426\u8BEF\u5F53 SVG\uFF1ASketch Image \u56FE\u5C42\u5FC5\u987B\u4F18\u5148\u5728 assets/icons/ \u4E2D\u67E5\u627E\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png\uFF0C\u547D\u4E2D\u540E\u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u518D\u4F2A\u9020 SVG",
6426
+ "- Bitmap \u662F\u5426\u8BEF\u5F53 SVG\uFF1ASketch Image \u56FE\u5C42\u5FC5\u987B\u4F18\u5148\u5728\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u6309\u8BBE\u8BA1\u539F\u540D\u6216 bitmapLookupCandidates \u7684\u6587\u4EF6\u540D\u4E3B\u4F53\u67E5\u627E\u4F4D\u56FE\uFF0C\u547D\u4E2D\u540E\u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u518D\u4F2A\u9020 SVG",
6427
+ "- \u9876\u90E8 Help/Customer Service/Support Entry \u82E5\u5728 blockOrder \u4E2D\u662F Image\uFF0C\u5FC5\u987B\u5148\u6309\u771F\u5B9E\u5757\u540D\u548C bitmapLookupCandidates \u590D\u7528\uFF0C\u4E0D\u5F97\u7528\u8BED\u4E49\u76F8\u8FD1\u6309\u94AE\u66FF\u4EE3",
6427
6428
  "- \u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding\uFF0C\u7981\u6B62\u628A\u6574\u9875\u9501\u6210 390 \u753B\u677F\u58F3\u5B50",
6428
6429
  "- Sketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u4E0D\u5C5E\u4E8E\u9875\u9762\u4E1A\u52A1 UI\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762",
6429
6430
  "- \u82E5\u8282\u70B9\u540C\u65F6\u7ED9\u51FA f \u4E0E vf\uFF0C\u8BF4\u660E\u80CC\u666F\u5C42/\u9634\u5F71\u8D85\u51FA\u903B\u8F91 frame\uFF1B\u5BB9\u5668\u5C3A\u5BF8\u548C\u5E95\u90E8\u89C6\u89C9\u95F4\u8DDD\u4F18\u5148\u4F7F\u7528 vf",
@@ -6527,14 +6528,15 @@ async function sketchMeasure(args) {
6527
6528
  scriptPath: tempScriptPath,
6528
6529
  scriptLength: fullScript.length,
6529
6530
  pluginInstall: installStatus,
6530
- instruction: "\u5C06 loaderScript \u4F20\u5165 mcp_sketch_run_code({ code: loaderScript }) \u6267\u884C\u3002\u8BBE\u8BA1\u7A3F\u8FD8\u539F\u9996\u8F6E\u4F18\u5148\u4F7F\u7528 cmd=compact\uFF1B\u53EA\u6709\u5F53 compact contract \u4E0D\u8DB3\u4EE5\u89E3\u91CA\u5C40\u90E8\u5E03\u5C40\u6216\u6837\u5F0F\u65F6\uFF0C\u518D\u8865\u7528 measure/style\u3002\u82E5\u504F\u5DEE\u96C6\u4E2D\u5728\u5355\u4E2A icon/svg/bitmap\uFF0C\u8BF7\u5148\u5728 assets/icons/ \u4E2D\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u67E5\u627E\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png\uFF0C\u5E76\u590D\u67E5 colorStrategy\u3001viewBox\u3001containerSize/contentSize\u3001vf \u4E0E bitmapLookupCandidates\uFF0C\u907F\u514D\u7EE7\u7EED\u63A8\u65AD\u65E7\u9875\u9762\u8D44\u4EA7\u6216\u4F2A\u9020 SVG\u3002\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding \u9002\u914D\uFF1BSketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762\u3002compact \u82E5\u68C0\u6D4B\u5230\u4F60\u9009\u4E2D\u7684\u662F\u5185\u5C42 glyph\uFF0C\u4F1A\u81EA\u52A8\u63D0\u5347\u5230\u6240\u5C5E\u753B\u677F\u5E76\u901A\u8FC7 selectionContext \u56DE\u62A5\u3002\u8BF7\u5148\u5728 Sketch \u4E2D\u9009\u4E2D\u76EE\u6807\u753B\u677F/\u56FE\u5C42\u3002",
6531
+ instruction: "\u5C06 loaderScript \u4F20\u5165 mcp_sketch_run_code({ code: loaderScript }) \u6267\u884C\u3002\u8BBE\u8BA1\u7A3F\u8FD8\u539F\u9996\u8F6E\u4F18\u5148\u4F7F\u7528 cmd=compact\uFF1B\u53EA\u6709\u5F53 compact contract \u4E0D\u8DB3\u4EE5\u89E3\u91CA\u5C40\u90E8\u5E03\u5C40\u6216\u6837\u5F0F\u65F6\uFF0C\u518D\u8865\u7528 measure/style\u3002\u82E5\u504F\u5DEE\u96C6\u4E2D\u5728\u5355\u4E2A icon/svg/bitmap\uFF0C\u8BF7\u5148\u5728\u9879\u76EE\u73B0\u6709\u7D20\u6750\u76EE\u5F55\u4E2D\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u4E3B\u4F53\u3001bitmapLookupCandidates \u548C bitmapReuseMode \u67E5\u627E\u771F\u5B9E\u4F4D\u56FE\u6216\u89C4\u8303\u5316\u526F\u672C\uFF0C\u907F\u514D\u7EE7\u7EED\u63A8\u65AD\u65E7\u9875\u9762\u8D44\u4EA7\u6216\u4F2A\u9020 SVG\u3002\u6587\u4EF6\u540D\u5339\u914D\u91CD\u70B9\u770B xxx \u4E3B\u4F53\uFF0C@4x \u53EA\u662F\u500D\u7387\u540E\u7F00\u3002\u9876\u90E8 Help/Customer Service/Support Entry \u82E5 blockOrder \u6807\u8BB0\u4E3A Image\uFF0C\u5FC5\u987B\u5148\u590D\u7528\u5BF9\u5E94\u4F4D\u56FE\uFF0C\u672A\u547D\u4E2D\u65F6\u5E94\u505C\u6B62\u731C\u6D4B\u3002\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding \u9002\u914D\uFF1BSketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762\u3002compact \u82E5\u68C0\u6D4B\u5230\u4F60\u9009\u4E2D\u7684\u662F\u5185\u5C42 glyph\uFF0C\u4F1A\u81EA\u52A8\u63D0\u5347\u5230\u6240\u5C5E\u753B\u677F\u5E76\u901A\u8FC7 selectionContext \u56DE\u62A5\u3002\u8BF7\u5148\u5728 Sketch \u4E2D\u9009\u4E2D\u76EE\u6807\u753B\u677F/\u56FE\u5C42\u3002",
6531
6532
  measureOutputPath,
6532
6533
  restoreChecklist: [
6533
6534
  "\u5148\u6309 restorationContract \u9501\u5B9A\u9875\u9762\u9AA8\u67B6\uFF0C\u518D\u5904\u7406\u5C40\u90E8\u6837\u5F0F",
6534
- "\u5355\u4E2A\u56FE\u6807\u504F\u5DEE\u5148\u67E5 assets/icons/ \u4E2D\u8BBE\u8BA1\u7A3F\u540C\u540D *_icon@4x.png\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u8865\u5BFC",
6535
+ "\u5355\u4E2A\u56FE\u6807\u504F\u5DEE\u5148\u67E5\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u8BBE\u8BA1\u7A3F\u540C\u540D\u3001bitmapLookupCandidates \u5217\u51FA\u7684\u540C\u4E3B\u4F53\u4F4D\u56FE\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u8865\u5BFC",
6535
6536
  "preserve \u6A21\u5F0F SVG \u7981\u6B62\u8FFD\u52A0 ColorFilter",
6536
6537
  "16x16 \u69FD\u4F4D\u4E0E 6x8/12x12 \u56FE\u5F62\u8981\u5206\u5F00\u5904\u7406",
6537
- "Sketch Image \u56FE\u5C42\u4F18\u5148\u590D\u7528 assets/icons/*_icon@4x.png\uFF0C\u4E0D\u8981\u4F2A\u88C5\u6210 SVG\uFF1B\u53EA\u6709\u65E7\u4F4D\u56FE\u6D41\u7A0B\u624D\u8865 2.0x/3.0x",
6538
+ "Sketch Image \u56FE\u5C42\u4F18\u5148\u590D\u7528\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u6309\u56FE\u5C42\u539F\u540D\u6216\u540C\u4E3B\u4F53\u547D\u4E2D\u7684\u4F4D\u56FE\uFF0C\u4E0D\u8981\u4F2A\u88C5\u6210 SVG\uFF1B\u53EA\u6709\u65E7\u4F4D\u56FE\u6D41\u7A0B\u624D\u8865 2.0x/3.0x",
6539
+ "\u9876\u90E8 Help/Customer Service/Support Entry \u82E5\u6D4B\u5F97\u4E3A Image\uFF0C\u7981\u6B62\u7528\u8BED\u4E49\u76F8\u8FD1\u7684\u5176\u4ED6\u6309\u94AE\u4F4D\u56FE\u6216 help glyph \u66FF\u4EE3",
6538
6540
  "\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding\uFF0C\u53EA\u6709\u660E\u786E\u6D4B\u5F97\u7684\u5C40\u90E8\u6D6E\u5C42/\u5361\u7247\u5BB9\u5668\u624D\u505A clamp",
6539
6541
  "Sketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u4E0D\u5C5E\u4E8E\u9875\u9762\u4E1A\u52A1 UI\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762",
6540
6542
  "\u82E5\u8282\u70B9\u540C\u65F6\u51FA\u73B0 f \u4E0E vf\uFF0C\u5BB9\u5668\u5C3A\u5BF8\u548C\u5E95\u90E8\u89C6\u89C9\u95F4\u8DDD\u4F18\u5148\u4F7F\u7528 vf\uFF0C\u4E0D\u8981\u53EA\u6284\u903B\u8F91 frame",
@@ -6630,6 +6632,18 @@ function loadMeasurePayload(args) {
6630
6632
  }
6631
6633
  return parsed;
6632
6634
  }
6635
+ function normalizeSemanticKey(value) {
6636
+ return value.replace(/[\s_-]+/g, "").toLowerCase();
6637
+ }
6638
+ function toPosixAssetPath(value) {
6639
+ return value.replace(/\\/g, "/").replace(/[?#].*$/, "");
6640
+ }
6641
+ function getAssetBaseName(value) {
6642
+ return path20.posix.basename(toPosixAssetPath(value));
6643
+ }
6644
+ function normalizeAssetMatchKey(value) {
6645
+ return getAssetBaseName(value).replace(/\.[^.]+$/, "").replace(/@\d+(?:\.\d+)?x$/i, "").replace(/[\s_-]+/g, "").toLowerCase();
6646
+ }
6633
6647
  function hasAnyPattern(source, patterns) {
6634
6648
  return patterns.some((pattern) => pattern.test(source));
6635
6649
  }
@@ -6640,7 +6654,7 @@ function collectAssetContracts(node, bucket = []) {
6640
6654
  if (typeof node.svgFile === "string") {
6641
6655
  bucket.push({
6642
6656
  sketchName: node.iconSketchName || node.n || node.svgFile,
6643
- fileName: path20.posix.basename(node.svgFile),
6657
+ fileName: getAssetBaseName(node.svgFile),
6644
6658
  mode: node.svgMode,
6645
6659
  sourceType: "vector",
6646
6660
  containerSize: node.cs,
@@ -6650,9 +6664,10 @@ function collectAssetContracts(node, bucket = []) {
6650
6664
  if (node.t === "Image") {
6651
6665
  bucket.push({
6652
6666
  sketchName: node.n || "Image",
6653
- fileName: typeof node.bitmapFile === "string" ? path20.posix.basename(node.bitmapFile) : typeof node.bitmapPath === "string" ? path20.posix.basename(node.bitmapPath) : void 0,
6667
+ fileName: typeof node.bitmapFile === "string" ? getAssetBaseName(node.bitmapFile) : typeof node.bitmapPath === "string" ? getAssetBaseName(node.bitmapPath) : void 0,
6654
6668
  sourceType: "bitmap",
6655
6669
  scaleVariants: node.bitmapScales,
6670
+ lookupCandidates: node.bitmapLookupCandidates,
6656
6671
  reuseMode: node.bitmapReuseMode,
6657
6672
  reuseKey: node.bitmapReuseKey
6658
6673
  });
@@ -6663,11 +6678,52 @@ function collectAssetContracts(node, bucket = []) {
6663
6678
  return bucket;
6664
6679
  }
6665
6680
  function extractCodeAssets(codeContent) {
6666
- const matches = codeContent.match(/assets\/[^'")]+\.(svg|png|jpg|jpeg|webp)/g);
6667
- return matches ? [...new Set(matches)] : [];
6681
+ const matches = [...codeContent.matchAll(/['"`]([^'"`\n]+\.(?:svg|png|jpg|jpeg|webp))(?:\?[^'"`\n]*)?['"`]/gi)].map(([, assetPath]) => toPosixAssetPath(assetPath.trim())).filter((assetPath) => !/^(https?:\/\/|data:|<svg)/i.test(assetPath));
6682
+ return [...new Set(matches)];
6683
+ }
6684
+ function hasExplicitBitmapScaleSuffix(assetPath) {
6685
+ return /@\d+(?:\.\d+)?x\.(png|jpg|jpeg|webp)$/i.test(getAssetBaseName(assetPath));
6686
+ }
6687
+ function isCriticalBitmapEntryName(name) {
6688
+ return /help[_ -]?button|customer[_ -]?service(?:[_ -]?(button|icon))?|support(?:[_ -]?entry)?|帮助|客服/i.test(name);
6689
+ }
6690
+ function buildBitmapCandidateSet(contract) {
6691
+ const candidates = /* @__PURE__ */ new Set();
6692
+ if (contract.fileName) {
6693
+ candidates.add(getAssetBaseName(contract.fileName).toLowerCase());
6694
+ }
6695
+ for (const candidate of contract.lookupCandidates || []) {
6696
+ candidates.add(getAssetBaseName(candidate).toLowerCase());
6697
+ }
6698
+ return candidates;
6668
6699
  }
6669
- function isManualExport4xBitmapAsset(assetPath) {
6670
- return /_icon@4x\.(png|jpg|jpeg|webp)$/i.test(path20.posix.basename(assetPath));
6700
+ function buildBitmapMatchKeySet(contract) {
6701
+ const matchKeys = /* @__PURE__ */ new Set();
6702
+ if (contract.fileName) {
6703
+ matchKeys.add(normalizeAssetMatchKey(contract.fileName));
6704
+ }
6705
+ for (const candidate of contract.lookupCandidates || []) {
6706
+ matchKeys.add(normalizeAssetMatchKey(candidate));
6707
+ }
6708
+ return matchKeys;
6709
+ }
6710
+ function collectCriticalBitmapContracts(payload, assetContracts) {
6711
+ var _a;
6712
+ const criticalBitmapKeys = new Set(
6713
+ (((_a = payload.restorationContract) == null ? void 0 : _a.blockOrder) || []).flatMap((block) => {
6714
+ const blockIdentity = `${block.name || ""} ${block.label || ""}`;
6715
+ if (block.type !== "Image" || !isCriticalBitmapEntryName(blockIdentity)) {
6716
+ return [];
6717
+ }
6718
+ return [block.name, block.label].filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => normalizeSemanticKey(value));
6719
+ })
6720
+ );
6721
+ if (criticalBitmapKeys.size === 0) {
6722
+ return [];
6723
+ }
6724
+ return assetContracts.filter(
6725
+ (contract) => contract.sourceType === "bitmap" && criticalBitmapKeys.has(normalizeSemanticKey(contract.sketchName))
6726
+ );
6671
6727
  }
6672
6728
  function hasCodePropertyLiteral(codeContent, property, value) {
6673
6729
  const escapedValue = String(value).replace(".", "\\.");
@@ -6740,7 +6796,8 @@ function extractRouteChunk(routeContent, routeIndex) {
6740
6796
  return routeContent.slice(start, nextBlockStart);
6741
6797
  }
6742
6798
  function resolveProjectAssetPath(projectPath, assetPath) {
6743
- return path20.join(projectPath, assetPath);
6799
+ const normalizedPath = toPosixAssetPath(assetPath).replace(/^\.\//, "").replace(/^@\//, "src/");
6800
+ return path20.join(projectPath, normalizedPath.replace(/^\/+/, ""));
6744
6801
  }
6745
6802
  function getBitmapScaleVariantPaths(projectPath, assetPath) {
6746
6803
  const relativeDir = path20.posix.dirname(assetPath);
@@ -6867,14 +6924,21 @@ function buildContractValidator(payload, codeContent) {
6867
6924
  function buildAssetValidator(payload, codeContent, args) {
6868
6925
  const findings = [];
6869
6926
  const assetContracts = collectAssetContracts(payload.tree);
6927
+ const criticalBitmapContracts = collectCriticalBitmapContracts(payload, assetContracts);
6870
6928
  const expectedVectorFiles = new Set(
6871
6929
  assetContracts.filter((item) => item.sourceType === "vector" && item.fileName).map((item) => item.fileName.toLowerCase())
6872
6930
  );
6873
6931
  const expectedBitmapCount = assetContracts.filter((item) => item.sourceType === "bitmap").length;
6874
6932
  const codeAssets = extractCodeAssets(codeContent);
6875
- const codeVectorAssets = codeAssets.filter((asset) => asset.endsWith(".svg"));
6876
- const codeBitmapAssets = codeAssets.filter((asset) => /\.(png|jpg|jpeg|webp)$/.test(asset));
6877
- const unexpectedVectorAssets = codeVectorAssets.filter((asset) => !expectedVectorFiles.has(path20.posix.basename(asset).toLowerCase()));
6933
+ const codeVectorAssets = codeAssets.filter((asset) => asset.toLowerCase().endsWith(".svg"));
6934
+ const codeBitmapAssets = codeAssets.filter((asset) => /\.(png|jpg|jpeg|webp)$/i.test(asset));
6935
+ const codeBitmapFileNames = new Set(
6936
+ codeBitmapAssets.map((asset) => getAssetBaseName(asset).toLowerCase())
6937
+ );
6938
+ const codeBitmapMatchKeys = new Set(
6939
+ codeBitmapAssets.map((asset) => normalizeAssetMatchKey(asset))
6940
+ );
6941
+ const unexpectedVectorAssets = codeVectorAssets.filter((asset) => !expectedVectorFiles.has(getAssetBaseName(asset).toLowerCase()));
6878
6942
  if (unexpectedVectorAssets.length > 0) {
6879
6943
  findings.push({
6880
6944
  id: "unexpected_vector_assets",
@@ -6887,35 +6951,50 @@ function buildAssetValidator(payload, codeContent, args) {
6887
6951
  if (expectedBitmapCount > 0 && codeBitmapAssets.length === 0) {
6888
6952
  findings.push({
6889
6953
  id: "missing_bitmap_assets",
6890
- severity: "warning",
6891
- message: `\u5F53\u524D\u8BBE\u8BA1\u7A3F\u5305\u542B ${expectedBitmapCount} \u4E2A Image \u56FE\u5C42\uFF0C\u4F46\u4EE3\u7801\u91CC\u6CA1\u6709\u4EFB\u4F55\u4F4D\u56FE\u8D44\u6E90\u5F15\u7528\u3002`,
6892
- suggestion: "\u68C0\u67E5\u662F\u5426\u628A Sketch Image \u56FE\u5C42\u9519\u8BEF\u5F53\u6210 SVG \u6216\u4EE3\u7801\u7ED8\u5236\u4E86\u3002"
6954
+ severity: criticalBitmapContracts.length > 0 ? "error" : "warning",
6955
+ message: criticalBitmapContracts.length > 0 ? `\u5F53\u524D\u8BBE\u8BA1\u7A3F\u5305\u542B ${expectedBitmapCount} \u4E2A Image \u56FE\u5C42\uFF0C\u4E14\u5173\u952E\u5165\u53E3\u4F4D\u56FE\u4E0D\u80FD\u88AB\u8BED\u4E49\u76F8\u8FD1\u7684 SVG/\u81EA\u7ED8\u5B9E\u73B0\u66FF\u4EE3\uFF0C\u4F46\u4EE3\u7801\u91CC\u6CA1\u6709\u4EFB\u4F55\u4F4D\u56FE\u8D44\u6E90\u5F15\u7528\u3002` : `\u5F53\u524D\u8BBE\u8BA1\u7A3F\u5305\u542B ${expectedBitmapCount} \u4E2A Image \u56FE\u5C42\uFF0C\u4F46\u4EE3\u7801\u91CC\u6CA1\u6709\u4EFB\u4F55\u4F4D\u56FE\u8D44\u6E90\u5F15\u7528\u3002`,
6956
+ suggestion: criticalBitmapContracts.length > 0 ? "\u5173\u952E\u5165\u53E3\u7C7B Image block \u5FC5\u987B\u5148\u6309\u8BBE\u8BA1\u56FE\u5C42\u540D\u548C bitmapLookupCandidates \u590D\u7528\u771F\u5B9E\u4F4D\u56FE\uFF1B\u4E0D\u8981\u518D\u628A Help / Customer Service / Support Entry \u753B\u6210\u81EA\u5B9A\u4E49 SVG \u6216\u58F3\u5B50\u7EC4\u4EF6\u3002" : "\u68C0\u67E5\u662F\u5426\u628A Sketch Image \u56FE\u5C42\u9519\u8BEF\u5F53\u6210 SVG \u6216\u4EE3\u7801\u7ED8\u5236\u4E86\u3002"
6893
6957
  });
6894
6958
  }
6959
+ if (criticalBitmapContracts.length > 0 && codeBitmapAssets.length > 0) {
6960
+ const mismatchedCriticalBitmapEntries = criticalBitmapContracts.map((contract) => {
6961
+ const candidateSet = buildBitmapCandidateSet(contract);
6962
+ const candidateMatchKeys = buildBitmapMatchKeySet(contract);
6963
+ const matched = [...codeBitmapMatchKeys].some((matchKey) => candidateMatchKeys.has(matchKey));
6964
+ return matched ? null : `${contract.sketchName} -> expected asset stem matching one of ${[...candidateSet].join(", ")}`;
6965
+ }).filter((item) => item !== null);
6966
+ if (mismatchedCriticalBitmapEntries.length > 0) {
6967
+ findings.push({
6968
+ id: "critical_bitmap_entry_not_reused",
6969
+ severity: "error",
6970
+ message: "\u5173\u952E Image \u5165\u53E3\u6CA1\u6709\u590D\u7528\u8BBE\u8BA1\u6E90\u4F4D\u56FE\uFF0C\u5B58\u5728\u628A Help / Customer Service \u7B49\u8BED\u4E49\u76F8\u8FD1\u56FE\u6807\u4E92\u6362\u7684\u98CE\u9669\u3002",
6971
+ evidence: mismatchedCriticalBitmapEntries,
6972
+ suggestion: "\u5148\u5BF9\u7167 restorationContract.blockOrder \u4E2D\u7684\u771F\u5B9E\u5757\u540D\uFF0C\u518D\u6309 bitmapLookupCandidates \u6216 canonical \u6587\u4EF6\u540D\u590D\u7528\u4F4D\u56FE\uFF1B\u672A\u547D\u4E2D\u65F6\u5E94\u505C\u6B62\u731C\u6D4B\u5E76\u8981\u6C42\u91CD\u65B0\u786E\u8BA4\u8BBE\u8BA1\u5BFC\u51FA\u3002"
6973
+ });
6974
+ }
6975
+ }
6895
6976
  if (codeBitmapAssets.length > 0) {
6896
6977
  findings.push({
6897
6978
  id: "bitmap_asset_source_check_required",
6898
6979
  severity: "info",
6899
6980
  message: "\u4EE3\u7801\u4E2D\u5B58\u5728\u4F4D\u56FE\u8D44\u6E90\uFF0C\u9700\u4EBA\u5DE5\u786E\u8BA4\u5B83\u4EEC\u6765\u81EA\u5F53\u524D\u753B\u677F\uFF0C\u6216\u5DF2\u6B63\u786E\u590D\u7528\u5F53\u524D\u8BBE\u8BA1\u7EA6\u5B9A\u7684 canonical \u5171\u4EAB\u8D44\u4EA7\u3002",
6900
6981
  evidence: codeBitmapAssets,
6901
- suggestion: "\u4F18\u5148\u5148\u5728 assets/icons/ \u4E2D\u67E5\u627E\u7528\u6237\u624B\u5DE5\u5BFC\u51FA\u7684\u8BBE\u8BA1\u540C\u540D *_icon@4x.png\uFF1B\u53EA\u6709\u7F3A\u5931\u65F6\uFF0C\u624D\u6309 contract \u6307\u5B9A\u65B9\u5F0F\u8865\u5BFC\u6216\u590D\u7528\u5171\u4EAB\u8D44\u4EA7\u3002"
6982
+ suggestion: "\u4F18\u5148\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u4E3B\u4F53\u4E0E bitmapLookupCandidates \u5728\u9879\u76EE\u73B0\u6709\u7D20\u6750\u76EE\u5F55\u4E2D\u67E5\u627E\u8D44\u6E90\uFF1B\u6587\u4EF6\u540D\u5339\u914D\u91CD\u70B9\u770B\u4E3B\u4F53\uFF0C\u4F8B\u5982 xxx / xxx@4x\uFF0C\u53EA\u6709\u7F3A\u5931\u65F6\u624D\u6309 contract \u6307\u5B9A\u65B9\u5F0F\u8865\u5BFC\u6216\u590D\u7528\u5171\u4EAB\u8D44\u4EA7\u3002"
6902
6983
  });
6903
6984
  }
6904
- const sharedBitmapFiles = new Set(
6905
- assetContracts.filter((item) => item.sourceType === "bitmap" && item.reuseMode === "shared-canonical" && item.fileName).map((item) => item.fileName.toLowerCase())
6906
- );
6907
- if (sharedBitmapFiles.size > 0) {
6908
- const usedBitmapFiles = new Set(
6909
- codeBitmapAssets.map((asset) => path20.posix.basename(asset).toLowerCase())
6910
- );
6911
- const missingSharedBitmapFiles = [...sharedBitmapFiles].filter((fileName) => !usedBitmapFiles.has(fileName));
6985
+ const sharedBitmapFiles = assetContracts.filter((item) => item.sourceType === "bitmap" && item.reuseMode === "shared-canonical" && item.fileName).map((item) => ({
6986
+ fileName: item.fileName,
6987
+ matchKey: normalizeAssetMatchKey(item.fileName)
6988
+ }));
6989
+ if (sharedBitmapFiles.length > 0) {
6990
+ const missingSharedBitmapFiles = sharedBitmapFiles.filter((item) => !codeBitmapMatchKeys.has(item.matchKey)).map((item) => item.fileName);
6912
6991
  if (missingSharedBitmapFiles.length > 0) {
6913
6992
  findings.push({
6914
6993
  id: "shared_bitmap_asset_not_reused",
6915
6994
  severity: "warning",
6916
- message: "\u8BBE\u8BA1\u7A3F\u5DF2\u6807\u8BB0\u8BE5\u4F4D\u56FE\u4E3A\u5171\u4EAB canonical \u8D44\u4EA7\uFF0C\u4F46\u4EE3\u7801\u6CA1\u6709\u590D\u7528\u5BF9\u5E94\u6587\u4EF6\u540D\uFF0C\u5B58\u5728\u91CD\u590D\u7F13\u5B58\u6216\u628A Help/Customer Service \u8BED\u4E49\u8BEF\u5408\u5E76\u7684\u98CE\u9669\u3002",
6995
+ message: "\u8BBE\u8BA1\u7A3F\u5DF2\u6807\u8BB0\u8BE5\u4F4D\u56FE\u4E3A\u5171\u4EAB canonical \u8D44\u4EA7\uFF0C\u4F46\u4EE3\u7801\u6CA1\u6709\u590D\u7528\u5BF9\u5E94\u6587\u4EF6\u540D\u4E3B\u4F53\uFF0C\u5B58\u5728\u91CD\u590D\u7F13\u5B58\u6216\u628A Help/Customer Service \u8BED\u4E49\u8BEF\u5408\u5E76\u7684\u98CE\u9669\u3002",
6917
6996
  evidence: missingSharedBitmapFiles,
6918
- suggestion: "\u4F18\u5148\u590D\u7528 contract \u8FD4\u56DE\u7684\u5171\u4EAB bitmap \u6587\u4EF6\u540D\uFF1BHelp Button \u4E0E Customer Service Button \u5FC5\u987B\u6309\u5404\u81EA canonical \u6587\u4EF6\u72EC\u7ACB\u590D\u7528\uFF0C\u4E0D\u80FD\u518D\u5408\u5E76\u6210\u540C\u4E00\u4E2A\u540D\u5B57\u3002"
6997
+ suggestion: "\u4F18\u5148\u590D\u7528 contract \u8FD4\u56DE\u7684\u5171\u4EAB bitmap \u6587\u4EF6\u540D\u4E3B\u4F53\uFF1B\u76EE\u5F55\u548C @4x \u53EA\u662F\u9879\u76EE\u5B9E\u73B0\u7EC6\u8282\uFF0CHelp Button \u4E0E Customer Service Button \u5FC5\u987B\u6309\u5404\u81EA\u4E3B\u4F53\u72EC\u7ACB\u590D\u7528\uFF0C\u4E0D\u80FD\u518D\u5408\u5E76\u6210\u540C\u4E00\u4E2A\u540D\u5B57\u3002"
6919
6998
  });
6920
6999
  }
6921
7000
  }
@@ -6923,13 +7002,13 @@ function buildAssetValidator(payload, codeContent, args) {
6923
7002
  (item) => item.sourceType === "bitmap" && item.reuseMode !== "shared-canonical"
6924
7003
  );
6925
7004
  if (expectedBitmapCount > 0 && codeBitmapAssets.length > 0 && requiresPageLocalBitmap) {
6926
- const hasManualExport4xBitmap = codeBitmapAssets.some((asset) => isManualExport4xBitmapAsset(asset));
6927
- if (!hasManualExport4xBitmap) {
7005
+ const hasScaledBitmapExport = codeBitmapAssets.some((asset) => hasExplicitBitmapScaleSuffix(asset));
7006
+ if (!hasScaledBitmapExport) {
6928
7007
  const pageClassName = extractPageClassName(codeContent) || "";
6929
7008
  const normalizedStem = pageClassName.replace(/Page$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
6930
7009
  if (normalizedStem) {
6931
7010
  const hasPageLocalBitmap = codeBitmapAssets.some(
6932
- (asset) => path20.posix.basename(asset).toLowerCase().includes(normalizedStem)
7011
+ (asset) => getAssetBaseName(asset).toLowerCase().includes(normalizedStem)
6933
7012
  );
6934
7013
  if (!hasPageLocalBitmap) {
6935
7014
  findings.push({
@@ -6937,7 +7016,7 @@ function buildAssetValidator(payload, codeContent, args) {
6937
7016
  severity: "warning",
6938
7017
  message: "\u5F53\u524D\u9875\u9762\u4F4D\u56FE\u8D44\u6E90\u770B\u8D77\u6765\u4E0D\u662F page-local \u547D\u540D\uFF0C\u5B58\u5728\u7EE7\u7EED\u590D\u7528\u65E7\u9875\u9762 PNG \u7684\u98CE\u9669\u3002",
6939
7018
  evidence: codeBitmapAssets,
6940
- suggestion: "\u4F4D\u56FE\u8D44\u6E90\u4F18\u5148\u6309\u5F53\u524D\u9875\u9762\u547D\u540D\u5BFC\u51FA\uFF0C\u4F8B\u5982\u5E26\u4E0A\u9875\u9762 stem\uFF1B\u82E5\u9879\u76EE\u5DF2\u6539\u4E3A\u624B\u5DE5 *_icon@4x.png \u7EA6\u5B9A\uFF0C\u5219\u65E0\u9700\u518D\u8FFD\u52A0 page-local stem\u3002"
7019
+ suggestion: "\u4F4D\u56FE\u8D44\u6E90\u4F18\u5148\u6309\u5F53\u524D\u9875\u9762\u547D\u540D\u5BFC\u51FA\uFF0C\u4F8B\u5982\u5E26\u4E0A\u9875\u9762 stem\uFF1B\u82E5\u9879\u76EE\u5DF2\u91C7\u7528\u663E\u5F0F\u500D\u7387\u6587\u4EF6\u540D\uFF08\u5982 xxx@4x.png\uFF09\uFF0C\u5219\u65E0\u9700\u518D\u8FFD\u52A0 page-local stem\u3002"
6941
7020
  });
6942
7021
  }
6943
7022
  }
@@ -6951,7 +7030,7 @@ function buildAssetValidator(payload, codeContent, args) {
6951
7030
  if (requiresRetinaVariants) {
6952
7031
  const missingRetinaEvidence = [];
6953
7032
  for (const asset of codeBitmapAssets) {
6954
- if (isManualExport4xBitmapAsset(asset)) {
7033
+ if (hasExplicitBitmapScaleSuffix(asset)) {
6955
7034
  continue;
6956
7035
  }
6957
7036
  const assetOnDisk = resolveProjectAssetPath(args.projectPath, asset);
@@ -6969,9 +7048,9 @@ function buildAssetValidator(payload, codeContent, args) {
6969
7048
  findings.push({
6970
7049
  id: "bitmap_retina_variants_missing",
6971
7050
  severity: "error",
6972
- message: "\u4F4D\u56FE\u8D44\u6E90\u65E2\u6CA1\u6709\u4F7F\u7528\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png \u7EA6\u5B9A\uFF0C\u4E5F\u7F3A\u5C11 2.0x/3.0x \u53D8\u4F53\uFF0C\u9AD8\u5206\u5C4F\u4E0B\u5BB9\u6613\u53D1\u7CCA\u3002",
7051
+ message: "\u4F4D\u56FE\u8D44\u6E90\u65E2\u6CA1\u6709\u4F7F\u7528\u663E\u5F0F\u500D\u7387\u547D\u540D\uFF08\u4F8B\u5982 xxx@4x.png\uFF09\uFF0C\u4E5F\u7F3A\u5C11 2.0x/3.0x \u53D8\u4F53\uFF0C\u9AD8\u5206\u5C4F\u4E0B\u5BB9\u6613\u53D1\u7CCA\u3002",
6973
7052
  evidence: missingRetinaEvidence,
6974
- suggestion: "\u4F18\u5148\u6539\u4E3A assets/icons/*_icon@4x.png \u7684\u624B\u5DE5\u5BFC\u51FA\u7EA6\u5B9A\uFF1B\u5982\u679C\u4ECD\u8D70\u65E7\u4F4D\u56FE\u6D41\u7A0B\uFF0C\u518D\u8865\u9F50 assets/.../2.0x \u4E0E 3.0x \u53D8\u4F53\u3002"
7053
+ suggestion: "\u4F18\u5148\u4F7F\u7528\u9879\u76EE\u73B0\u6709\u7684\u9AD8\u500D\u7387\u624B\u5DE5\u5BFC\u51FA\u547D\u540D\uFF08\u5982 xxx@4x.png\uFF09\uFF1B\u5982\u679C\u4ECD\u8D70\u591A\u500D\u7387\u76EE\u5F55\u6D41\u7A0B\uFF0C\u518D\u8865\u9F50\u540C\u76EE\u5F55\u7684 2.0x \u4E0E 3.0x \u53D8\u4F53\u3002"
6975
7054
  });
6976
7055
  }
6977
7056
  }