mta-mcp 3.3.0 → 3.5.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.
@@ -1,7 +1,7 @@
1
1
  # Flutter 开发代理
2
2
 
3
3
  > 此 Agent 引导 AI 通过 MCP 工具获取 npm 包中的详细规范
4
- > 版本: v3.3.0 | 最后更新: 2026-01-23
4
+ > 版本: v3.5.0 | 最后更新: 2026-01-23
5
5
 
6
6
  ---
7
7
 
@@ -113,24 +113,70 @@ get_standard_by_id({ ids: ['flutter', 'flutter-ui-system'] })
113
113
 
114
114
  **触发词**:精确还原、深度还原、像素级还原、完美还原
115
115
 
116
- 当用户使用以上触发词时,执行深度还原流程:
116
+ 当用户使用以上触发词时,执行 Flutter 专属深度还原流程:
117
117
 
118
118
  ```
119
119
  1. 检查用户是否已选中 Sketch 画板(未选中则提示)
120
- 2. 调用深度测量脚本获取完整元素树
121
- 3. 分析每个元素的 layoutHints(CENTER/COLUMN/ROW 等)
122
- 4. 计算响应式参数(禁止硬编码尺寸)
123
- 5. 生成适配多设备的代码
120
+ 2. 调用深度测量脚本获取 Layout Intent
121
+ 3. 按照 Flutter 转换规范映射代码
122
+ 4. 使用 Token 系统($c, $t, $s, $r)替代硬编码
123
+ 5. 在多设备尺寸验证
124
124
  ```
125
125
 
126
- > 📖 **完整脚本和流程**:`get_standard_by_id({ id: 'sketch-mcp' })`
126
+ #### Flutter Layout Intent 快速映射
127
+
128
+ | Layout Intent | Flutter 代码 |
129
+ |---------------|-------------|
130
+ | `CENTER` | `Center(child: ...)` |
131
+ | `HORIZONTAL_CENTER` | `Row(mainAxisAlignment: MainAxisAlignment.center)` |
132
+ | `FILL + margin:16` | `Padding(horizontal: 16) + SizedBox(width: double.infinity)` |
133
+ | `COLUMN + gap:16` | `Column(children: [..., Gap(16), ...])` |
134
+ | `ROW + gap:12` | `Row(children: [..., Gap.h(12), ...])` |
135
+
136
+ #### Icon 使用规范(重要)
137
+
138
+ **检测到 icon 时的处理流程**:
139
+
140
+ ```
141
+ 1. 深度测量脚本会标记 `isIcon: true` 并提示导出为 SVG 200h
142
+ 2. 将 SVG 文件放入 assets/icons/ 目录
143
+ 3. 在 pubspec.yaml 中声明 assets
144
+ 4. 使用 flutter_svg 包加载
145
+ ```
146
+
147
+ **Flutter Icon 使用方式**:
148
+
149
+ | 场景 | 代码示例 | 说明 |
150
+ |------|---------|------|
151
+ | **SVG Icon** | `SvgPicture.asset('assets/icons/menu.svg', width: 24, height: 24, colorFilter: ColorFilter.mode($c.iconPrimary, BlendMode.srcIn))` | 推荐,支持颜色替换 |
152
+ | **系统 Icon** | `Icon(Icons.menu, size: 24, color: $c.iconPrimary)` | 优先使用 Material Icons |
153
+ | **固定尺寸** | `SizedBox(width: 24, height: 24, child: icon)` | 确保 icon 不被拉伸 |
154
+
155
+ **⚠️ Icon 禁止规则**:
156
+
157
+ | ❌ 禁止 | ✅ 正确 |
158
+ |--------|--------|
159
+ | `Image.asset('icon.png')` | `SvgPicture.asset('icon.svg')` |
160
+ | `width: 32, height: 28` | 保持 1:1 宽高比或使用原始比例 |
161
+ | 硬编码颜色 | `colorFilter: ColorFilter.mode($c.iconPrimary, ...)` |
162
+
163
+ #### Flutter 禁止规则
164
+
165
+ | ❌ 禁止 | ✅ 正确 |
166
+ |--------|--------|
167
+ | `width: 343` | `MediaQuery.of(context).size.width - 32` |
168
+ | `Color(0xFF...)` | `$c.primary` |
169
+ | `fontSize: 14` | `$t.bodyMd` |
170
+ | `SizedBox(height: 16)` | `Gap(16)` 或 `$s.md` |
171
+
172
+ > 📖 **完整转换规范**:`get_standard_by_id({ id: 'sketch-mcp' })` 查看 "Flutter 深度还原转换规范" 章节
127
173
 
128
174
  ### 核心原则:测量 + 计算
129
175
 
130
- **绝不直接使用测量数据!必须分析计算后再转换为代码参数。**
176
+ **绝不直接使用测量数据!必须分析 Layout Intent 后再转换。**
131
177
 
132
178
  ```
133
- 测量 → 分析(理解设计意图)计算(转换参数)编码
179
+ 测量 → Layout Intent Flutter 映射 Token 替换 → 验证
134
180
  ```
135
181
 
136
182
  ### 配合 Sketch MCP 使用测量模板
@@ -1,7 +1,7 @@
1
1
  # Vue 3 + TypeScript 开发代理
2
2
 
3
3
  > 此 Agent 引导 AI 通过 MCP 工具获取 npm 包中的详细规范
4
- > 版本: v3.0.0 | 最后更新: 2026-01-16
4
+ > 版本: v3.5.0 | 最后更新: 2026-01-23
5
5
 
6
6
  ---
7
7
 
@@ -140,6 +140,80 @@ get_standard_by_id({ ids: ['vue3-composition', 'element-plus', 'i18n'] })
140
140
 
141
141
  > ⚠️ 详细规范请调用:`get_standard_by_id({ id: 'design-restoration' })`
142
142
 
143
+ ### 🎯 深度还原模式(像素级)
144
+
145
+ **触发词**:精确还原、深度还原、像素级还原、完美还原
146
+
147
+ 当用户使用以上触发词时,执行 Vue/CSS 专属深度还原流程:
148
+
149
+ ```
150
+ 1. 检查用户是否已选中 Sketch 画板(未选中则提示)
151
+ 2. 调用深度测量脚本获取 Layout Intent
152
+ 3. 按照 Vue/CSS 转换规范映射代码
153
+ 4. 使用 CSS 变量替代硬编码值
154
+ 5. 在 375px / 1920px 视口验证
155
+ ```
156
+
157
+ #### Vue/CSS Layout Intent 快速映射
158
+
159
+ | Layout Intent | CSS 代码 |
160
+ |---------------|---------|
161
+ | `CENTER` | `display: flex; justify-content: center; align-items: center;` |
162
+ | `HORIZONTAL_CENTER` | `margin: 0 auto;` 或 `justify-content: center;` |
163
+ | `FILL + margin:16` | `width: calc(100% - 32px); margin: 0 16px;` |
164
+ | `COLUMN + gap:16` | `display: flex; flex-direction: column; gap: 16px;` |
165
+ | `ROW + gap:12` | `display: flex; gap: 12px;` |
166
+ | `GRID` | `display: grid; grid-template-columns: repeat(...); gap: 16px;` |
167
+
168
+ #### Icon 使用规范(重要)
169
+
170
+ **检测到 icon 时的处理流程**:
171
+
172
+ ```
173
+ 1. 深度测量脚本会标记 `isIcon: true` 并提示导出为 SVG 200h
174
+ 2. 将 SVG 文件放入 src/assets/icons/ 目录
175
+ 3. 在 Vue 组件中引入使用
176
+ ```
177
+
178
+ **Vue Icon 使用方式**:
179
+
180
+ | 场景 | 代码示例 | 说明 |
181
+ |------|---------|------|
182
+ | **直接引入 SVG** | `<img :src="menuIcon" class="icon" />` + `import menuIcon from '@/assets/icons/menu.svg'` | 推荐,便于维护 |
183
+ | **内联 SVG** | `<svg>...</svg>` 直接复制 SVG 代码 | 支持 CSS 样式控制 |
184
+ | **Icon 组件库** | `<el-icon><Menu /></el-icon>` | Element Plus 项目优先使用 |
185
+ | **SVG Sprite** | `<svg><use xlink:href="#icon-menu"></use></svg>` | 多个 icon 复用时使用 |
186
+
187
+ **⚠️ Icon 禁止规则**:
188
+
189
+ | ❌ 禁止 | ✅ 正确 |
190
+ |--------|--------|
191
+ | `<img src="icon.png">` | `<img src="icon.svg">` 或 SVG sprite |
192
+ | `width: 32px; height: 28px` | 保持 1:1 宽高比 `width: 24px; height: 24px;` |
193
+ | 硬编码颜色 | `fill: var(--color-icon-primary);` |
194
+
195
+ **CSS 样式控制**:
196
+
197
+ ```css
198
+ .icon {
199
+ width: 24px;
200
+ height: 24px;
201
+ fill: var(--color-icon-primary); /* SVG 内联时生效 */
202
+ color: var(--color-icon-primary); /* 部分 icon 组件库使用 */
203
+ }
204
+ ```
205
+
206
+ #### Vue/CSS 禁止规则
207
+
208
+ | ❌ 禁止 | ✅ 正确 |
209
+ |--------|--------|
210
+ | `width: 343px` | `width: calc(100% - 32px)` |
211
+ | `color: #3B82F6` | `color: var(--color-primary)` |
212
+ | `font-size: 14px` | `font-size: var(--font-body-md)` |
213
+ | `margin-bottom: 16px` | `gap: 16px`(在 flex 容器上) |
214
+
215
+ > 📖 **完整转换规范**:`get_standard_by_id({ id: 'sketch-mcp' })` 查看 "Vue 3 / CSS 深度还原转换规范" 章节
216
+
143
217
  ### 快速提示
144
218
 
145
219
  | 关键点 | 说明 |
@@ -1,7 +1,7 @@
1
1
  # 微信小程序开发代理
2
2
 
3
3
  > 此 Agent 引导 AI 通过 MCP 工具获取 npm 包中的详细规范
4
- > 版本: v3.0.0 | 最后更新: 2026-01-16
4
+ > 版本: v3.5.0 | 最后更新: 2026-01-23
5
5
 
6
6
  ---
7
7
 
@@ -89,14 +89,107 @@ miniprogram/
89
89
 
90
90
  > ⚠️ 详细规范请调用:`get_standard_by_id({ id: 'design-restoration' })`
91
91
 
92
+ ### 🎯 深度还原模式(像素级)
93
+
94
+ **触发词**:精确还原、深度还原、像素级还原、完美还原
95
+
96
+ 当用户使用以上触发词时,执行小程序专属深度还原流程:
97
+
98
+ ```
99
+ 1. 检查用户是否已选中 Sketch 画板(未选中则提示)
100
+ 2. 调用深度测量脚本获取 Layout Intent
101
+ 3. 按照小程序转换规范映射代码(注意 rpx)
102
+ 4. 使用 CSS 变量替代硬编码值
103
+ 5. 在微信开发者工具多机型预览验证
104
+ ```
105
+
106
+ #### 小程序 Layout Intent 快速映射
107
+
108
+ | Layout Intent | WXSS 代码 |
109
+ |---------------|----------|
110
+ | `CENTER` | `display: flex; justify-content: center; align-items: center;` |
111
+ | `FILL + margin:16` | `width: calc(100% - 64rpx); margin: 0 32rpx;` |
112
+ | `COLUMN + gap:16` | `display: flex; flex-direction: column;` + 子元素 `margin-bottom: 32rpx;` |
113
+ | `ROW + gap:12` | `display: flex;` + 子元素 `margin-right: 24rpx;` |
114
+
115
+ #### Icon 使用规范(重要)
116
+
117
+ **检测到 icon 时的处理流程**:
118
+
119
+ ```
120
+ 1. 深度测量脚本会标记 `isIcon: true` 并提示导出为 SVG 200h
121
+ 2. 将 SVG 转换为 Base64 或使用图片格式
122
+ 3. 放入 /images/icons/ 目录
123
+ ```
124
+
125
+ **小程序 Icon 使用方式**:
126
+
127
+ | 场景 | 代码示例 | 说明 |
128
+ |------|---------|------|
129
+ | **Image 组件** | `<image src="/images/icons/menu.svg" class="icon" mode="aspectFit" />` | SVG 支持有限,优先 PNG |
130
+ | **字体图标** | `<text class="iconfont icon-menu"></text>` | 推荐,体积小,支持颜色 |
131
+ | **Base64** | `<image src="data:image/svg+xml;base64,..." />` | 小图标可内联 |
132
+
133
+ **⚠️ 小程序 Icon 限制**:
134
+
135
+ | ❌ 禁止 | ✅ 正确 |
136
+ |--------|--------|
137
+ | 直接使用外部 SVG | 转换为 PNG 或使用字体图标 |
138
+ | `width: 32rpx; height: 28rpx` | 保持 1:1 宽高比 `width: 48rpx; height: 48rpx;` |
139
+ | 硬编码颜色 | `color: var(--icon-color-primary);` |
140
+
141
+ **WXSS 样式控制**:
142
+
143
+ ```css
144
+ .icon {
145
+ width: 48rpx;
146
+ height: 48rpx;
147
+ }
148
+
149
+ .iconfont {
150
+ font-size: 48rpx;
151
+ color: var(--icon-color-primary);
152
+ }
153
+ ```
154
+
155
+ **字体图标配置**(推荐):
156
+
157
+ ```css
158
+ /* app.wxss */
159
+ @font-face {
160
+ font-family: 'iconfont';
161
+ src: url('data:font/woff2;base64,...');
162
+ }
163
+ ```
164
+
165
+ #### 小程序特殊规则(重要)
166
+
167
+ | 设计稿 (px) | 小程序 (rpx) | 说明 |
168
+ |------------|-------------|------|
169
+ | 16px | 32rpx | 间距、边距 |
170
+ | 343px | `calc(100% - 64rpx)` | 宽度优先用 calc |
171
+ | 14px (字号) | 28rpx | 字号也需转换 |
172
+
173
+ **⚠️ 小程序不支持 `gap` 属性**,需用 `margin` 模拟,配合 `:last-child { margin: 0; }` 移除最后一个元素的边距。
174
+
175
+ #### 小程序禁止规则
176
+
177
+ | ❌ 禁止 | ✅ 正确 |
178
+ |--------|--------|
179
+ | `width: 686rpx` | `width: calc(100% - 64rpx)` |
180
+ | `color: #3B82F6` | `color: var(--color-primary)` |
181
+ | `gap: 32rpx` | `margin-bottom: 32rpx`(不支持 gap) |
182
+
183
+ > 📖 **完整转换规范**:`get_standard_by_id({ id: 'sketch-mcp' })` 查看 "微信小程序深度还原转换规范" 章节
184
+
92
185
  ### 快速提示
93
186
 
94
187
  | 关键点 | 说明 |
95
188
  |--------|------|
96
189
  | **渐变色** | 检查 `fillType` 后读取 `gradient.stops`,勿直接用 `color` 属性 |
97
190
  | **全局样式** | 还原前先搜索项目现有变量(app.wxss 中的 CSS 变量) |
98
- | **rpx 换算** | 设计稿 px * 2 = rpx(基于 750px 设计稿) |
99
- | **阴影语法** | `box-shadow: 8px 8px 20px rgba(28,43,69,0.15)` |
191
+ | **rpx 换算** | 设计稿 px * 2 = rpx(基于 375px 设计稿) |
192
+ | **阴影语法** | `box-shadow: 16rpx 16rpx 40rpx rgba(28,43,69,0.15)` |
100
193
 
101
194
  ### 相关规范
102
195
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mta-mcp",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "MTA - 智能编码助手 MCP 服务器(规范 + 技能 + 诊断 + 模板 + 记忆 + 思考)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -58,14 +58,27 @@ const alpha = parseInt(colorHex.slice(7, 9), 16) / 255;
58
58
  - "像素级还原 XX 页面"
59
59
  - "完美还原 XX 页面"
60
60
 
61
- ### 模式说明
61
+ ### 架构说明
62
62
 
63
- 深度还原模式用于需要 **100% 精确还原设计稿** 的场景。与普通还原不同,此模式会:
63
+ 深度还原采用 **双层架构**,确保通用性的同时不牺牲各框架的专业性:
64
64
 
65
- 1. **完整提取**:递归获取画板下所有层级的元素数据
66
- 2. **智能分析**:根据元素的实际展示效果推断设计意图
67
- 3. **精确计算**:将绝对坐标转换为相对布局参数
68
- 4. **响应式适配**:禁止硬编码,确保多设备适配
65
+ ```
66
+ ┌─────────────────────────────────────────────────────────┐
67
+ │ 通用分析层(本文档) │
68
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
69
+ │ │ 深度测量脚本 │→ │ 布局意图分析 │→ │ Layout Intent │ │
70
+ │ │ (Sketch API)│ │ (算法识别) │ │ (标准化输出) │ │
71
+ │ └─────────────┘ └─────────────┘ └────────┬────────┘ │
72
+ └─────────────────────────────────────────────┼───────────┘
73
+
74
+ ┌─────────────────────────────────────────────┴───────────┐
75
+ │ 框架专属转换层 │
76
+ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────┐ │
77
+ │ │ Flutter │ │ Vue 3 │ │ 小程序 │ │ React │ │
78
+ │ │ (详见下方) │ │ (详见下方) │ │ (详见下方) │ │ Native│ │
79
+ │ └───────────┘ └───────────┘ └───────────┘ └───────┘ │
80
+ └─────────────────────────────────────────────────────────┘
81
+ ```
69
82
 
70
83
  ### 强制前置检查
71
84
 
@@ -76,14 +89,18 @@ const alpha = parseInt(colorHex.slice(7, 9), 16) / 255;
76
89
  3. 确认选中后再执行深度测量脚本
77
90
  ```
78
91
 
79
- ### 深度测量脚本
92
+ ---
93
+
94
+ ### 第一层:深度测量脚本(通用)
95
+
96
+ 此脚本提取设计稿数据并输出标准化的 **Layout Intent**,供各框架转换层使用。
80
97
 
81
98
  ```javascript
82
99
  const sketch = require('sketch');
83
100
 
84
101
  /**
85
102
  * 深度还原模式 - 完整测量脚本
86
- * 递归提取所有子元素的布局和样式数据
103
+ * 输出标准化的 Layout Intent,供各框架转换使用
87
104
  */
88
105
  function deepMeasureForRestoration() {
89
106
  const doc = sketch.getSelectedDocument();
@@ -107,12 +124,8 @@ function deepMeasureForRestoration() {
107
124
  height: artboard.frame.height,
108
125
  },
109
126
  elements: [],
110
- layoutAnalysis: {
111
- horizontalGroups: [], // 水平排列的元素组
112
- verticalGroups: [], // 垂直排列的元素组
113
- centeredElements: [], // 居中的元素
114
- spacingPatterns: [], // 间距规律
115
- }
127
+ // Layout Intent 汇总(框架转换时使用)
128
+ layoutIntents: [],
116
129
  };
117
130
 
118
131
  // 递归测量所有元素
@@ -132,12 +145,22 @@ function deepMeasureForRestoration() {
132
145
  },
133
146
  // 相对于父容器的位置(关键!)
134
147
  relativePosition: null,
135
- // 布局分析结果
136
- layoutHints: [],
148
+ // Layout Intent(标准化布局意图)
149
+ layoutIntent: {},
150
+ // Icon 检测标记
151
+ isIcon: false,
152
+ iconExportPath: null,
137
153
  children: [],
138
154
  };
139
155
 
140
- // 计算相对位置
156
+ // ========== Icon 智能检测 ==========
157
+ element.isIcon = detectIfIcon(layer);
158
+ if (element.isIcon) {
159
+ element.iconExportPath = `icons/${sanitizeFileName(layer.name)}.svg`;
160
+ console.log(`🎨 检测到 Icon: ${layer.name} → 建议导出为 SVG 200h`);
161
+ }
162
+
163
+ // 计算相对位置并生成 Layout Intent
141
164
  if (parentFrame) {
142
165
  const relLeft = layer.frame.x;
143
166
  const relTop = layer.frame.y;
@@ -151,34 +174,58 @@ function deepMeasureForRestoration() {
151
174
  bottom: Math.round(relBottom),
152
175
  };
153
176
 
154
- // 分析布局意图
177
+ // ========== 生成 Layout Intent ==========
155
178
  const tolerance = 2; // 允许 2px 误差
179
+ const intent = {
180
+ alignment: [],
181
+ sizing: {},
182
+ spacing: {},
183
+ };
156
184
 
157
- // 水平居中检测
158
- if (Math.abs(relLeft - relRight) <= tolerance) {
159
- element.layoutHints.push('HORIZONTAL_CENTER');
160
- }
161
- // 垂直居中检测
162
- if (Math.abs(relTop - relBottom) <= tolerance) {
163
- element.layoutHints.push('VERTICAL_CENTER');
164
- }
165
- // 完全居中
166
- if (element.layoutHints.includes('HORIZONTAL_CENTER') &&
167
- element.layoutHints.includes('VERTICAL_CENTER')) {
168
- element.layoutHints = ['CENTER'];
169
- }
170
- // 左对齐(边距小于 5%)
171
- if (relLeft < parentFrame.width * 0.05 && relRight > relLeft * 3) {
172
- element.layoutHints.push('ALIGN_LEFT');
185
+ // 对齐意图
186
+ const isHCenter = Math.abs(relLeft - relRight) <= tolerance;
187
+ const isVCenter = Math.abs(relTop - relBottom) <= tolerance;
188
+
189
+ if (isHCenter && isVCenter) {
190
+ intent.alignment = ['CENTER'];
191
+ } else {
192
+ if (isHCenter) intent.alignment.push('HORIZONTAL_CENTER');
193
+ if (isVCenter) intent.alignment.push('VERTICAL_CENTER');
194
+ if (relLeft < parentFrame.width * 0.05 && relRight > relLeft * 3) {
195
+ intent.alignment.push('ALIGN_START'); // 通用术语,非 left
196
+ }
197
+ if (relRight < parentFrame.width * 0.05 && relLeft > relRight * 3) {
198
+ intent.alignment.push('ALIGN_END');
199
+ }
173
200
  }
174
- // 右对齐
175
- if (relRight < parentFrame.width * 0.05 && relLeft > relRight * 3) {
176
- element.layoutHints.push('ALIGN_RIGHT');
201
+
202
+ // 尺寸意图
203
+ const widthRatio = layer.frame.width / parentFrame.width;
204
+ const heightRatio = layer.frame.height / parentFrame.height;
205
+
206
+ if (isHCenter && relLeft < 30) {
207
+ intent.sizing.width = { type: 'FILL', margin: relLeft };
208
+ } else if (widthRatio > 0.9) {
209
+ intent.sizing.width = { type: 'MATCH_PARENT' };
210
+ } else {
211
+ intent.sizing.width = { type: 'FIXED', value: layer.frame.width };
177
212
  }
178
- // 宽度占满(左右边距相等且较小)
179
- if (Math.abs(relLeft - relRight) <= tolerance && relLeft < 30) {
180
- element.layoutHints.push('FILL_WIDTH');
213
+
214
+ if (heightRatio > 0.9) {
215
+ intent.sizing.height = { type: 'MATCH_PARENT' };
216
+ } else {
217
+ intent.sizing.height = { type: 'WRAP_CONTENT', minHeight: layer.frame.height };
181
218
  }
219
+
220
+ // 边距意图(用于 padding 计算)
221
+ intent.spacing = {
222
+ paddingStart: relLeft,
223
+ paddingEnd: relRight,
224
+ paddingTop: relTop,
225
+ paddingBottom: relBottom,
226
+ };
227
+
228
+ element.layoutIntent = intent;
182
229
  }
183
230
 
184
231
  // 提取样式
@@ -203,9 +250,9 @@ function deepMeasureForRestoration() {
203
250
  }
204
251
  });
205
252
 
206
- // 分析子元素排列规律
253
+ // 分析子元素排列规律 → 生成容器 Layout Intent
207
254
  if (element.children.length > 1) {
208
- analyzeChildrenLayout(element);
255
+ element.containerIntent = analyzeChildrenLayout(element.children);
209
256
  }
210
257
 
211
258
  return element;
@@ -292,7 +339,7 @@ function deepMeasureForRestoration() {
292
339
  }
293
340
  }
294
341
 
295
- // 计算行高比例
342
+ // 计算行高比例(通用,各框架按需转换)
296
343
  if (layer.style.lineHeight) {
297
344
  info.lineHeight = layer.style.lineHeight;
298
345
  info.lineHeightRatio = (layer.style.lineHeight / layer.style.fontSize).toFixed(2);
@@ -304,7 +351,7 @@ function deepMeasureForRestoration() {
304
351
  return info;
305
352
  }
306
353
 
307
- // 解析字重
354
+ // 解析字重(通用映射)
308
355
  function parseFontWeight(fontName) {
309
356
  const weightMap = {
310
357
  'Thin': 100, 'Ultralight': 200, 'Light': 300,
@@ -317,10 +364,106 @@ function deepMeasureForRestoration() {
317
364
  return 400;
318
365
  }
319
366
 
320
- // 分析子元素排列规律
321
- function analyzeChildrenLayout(element) {
322
- const children = element.children;
367
+ // ========== Icon 智能检测 ==========
368
+ // 根据元素特征判断是否为 icon
369
+ function detectIfIcon(layer) {
370
+ const name = layer.name.toLowerCase();
371
+ const size = Math.max(layer.frame.width, layer.frame.height);
372
+
373
+ // 规则 1: 名称特征
374
+ const iconKeywords = ['icon', 'ico', 'logo', 'symbol', 'arrow', 'close', 'menu', 'search', 'star', 'check'];
375
+ const hasIconKeyword = iconKeywords.some(keyword => name.includes(keyword));
376
+
377
+ // 规则 2: 尺寸特征(icon 通常在 12-64px 之间)
378
+ const isIconSize = size >= 12 && size <= 64;
379
+
380
+ // 规则 3: 形状特征(正方形或近似正方形)
381
+ const aspectRatio = layer.frame.width / layer.frame.height;
382
+ const isSquarish = aspectRatio >= 0.8 && aspectRatio <= 1.2;
383
+
384
+ // 规则 4: 类型特征(Shape Path / Symbol Instance / Group 且子元素少)
385
+ const isVectorType = layer.type === 'ShapePath' || layer.type === 'SymbolInstance' ||
386
+ (layer.type === 'Group' && (layer.layers?.length || 0) <= 5);
387
+
388
+ // 规则 5: 无文本内容
389
+ const hasNoText = layer.type !== 'Text';
390
+
391
+ // 综合判定(满足 3 个条件即认为是 icon)
392
+ const matchCount = [hasIconKeyword, isIconSize, isSquarish, isVectorType, hasNoText]
393
+ .filter(Boolean).length;
394
+
395
+ return matchCount >= 3;
396
+ }
397
+
398
+ // 文件名清理(用于 SVG 导出)
399
+ function sanitizeFileName(name) {
400
+ return name
401
+ .toLowerCase()
402
+ .replace(/\s+/g, '-')
403
+ .replace(/[^a-z0-9-_]/g, '')
404
+ .replace(/-+/g, '-')
405
+ .replace(/^-|-$/g, '');
406
+ }
407
+
408
+ // ========== 子元素对齐检测(优化布局分析)==========
409
+ function detectChildrenAlignment(children) {
410
+ if (children.length < 2) return null;
411
+
412
+ const tolerance = 2; // 允许 2px 误差
413
+ const leftEdges = children.map(c => c.frame.x);
414
+ const rightEdges = children.map(c => c.frame.x + c.frame.width);
415
+ const centerXs = children.map(c => c.frame.x + c.frame.width / 2);
416
+
417
+ // 检测左对齐
418
+ const leftAligned = leftEdges.every(x => Math.abs(x - leftEdges[0]) <= tolerance);
419
+ // 检测右对齐
420
+ const rightAligned = rightEdges.every(x => Math.abs(x - rightEdges[0]) <= tolerance);
421
+ // 检测水平居中对齐
422
+ const centerAligned = centerXs.every(x => Math.abs(x - centerXs[0]) <= tolerance);
423
+
424
+ if (leftAligned) return 'START';
425
+ if (rightAligned) return 'END';
426
+ if (centerAligned) return 'CENTER';
427
+ return 'NONE';
428
+ }
429
+
430
+ // ========== 响应式建议(增强交互准确性)==========
431
+ function generateResponsiveHint(children) {
432
+ if (children.length === 0) return null;
433
+
434
+ const hint = {
435
+ wrapNeeded: false,
436
+ scrollNeeded: false,
437
+ expandableChildren: [],
438
+ };
439
+
440
+ // 检测是否需要换行(子元素宽度总和超过容器)
441
+ const totalChildWidth = children.reduce((sum, c) => sum + c.frame.width, 0);
442
+ const parentWidth = children[0].frame.x; // 假设父容器信息存在
443
+ if (totalChildWidth > parentWidth * 0.9) {
444
+ hint.wrapNeeded = true;
445
+ }
446
+
447
+ // 检测是否需要滚动(子元素高度总和超过容器)
448
+ const totalChildHeight = children.reduce((sum, c) => sum + c.frame.height, 0);
449
+ const parentHeight = children[0].frame.y;
450
+ if (totalChildHeight > parentHeight * 0.9) {
451
+ hint.scrollNeeded = true;
452
+ }
453
+
454
+ // 检测哪些子元素应该可扩展(占据剩余空间)
455
+ children.forEach((child, index) => {
456
+ // 如果是容器且包含大量内容
457
+ if ((child.children?.length || 0) > 3) {
458
+ hint.expandableChildren.push(index);
459
+ }
460
+ });
323
461
 
462
+ return hint;
463
+ }
464
+
465
+ // 分析子元素排列规律 → 生成容器级 Layout Intent
466
+ function analyzeChildrenLayout(children) {
324
467
  // 按 Y 坐标排序检测垂直排列
325
468
  const sortedByY = [...children].sort((a, b) => a.frame.y - b.frame.y);
326
469
  const verticalGaps = [];
@@ -339,34 +482,83 @@ function deepMeasureForRestoration() {
339
482
  horizontalGaps.push(Math.round(gap));
340
483
  }
341
484
 
342
- // 判断主轴方向和间距规律
343
- const avgVGap = verticalGaps.reduce((a, b) => a + b, 0) / verticalGaps.length;
344
- const avgHGap = horizontalGaps.reduce((a, b) => a + b, 0) / horizontalGaps.length;
485
+ // 生成标准化的容器 Layout Intent
486
+ const avgVGap = verticalGaps.length > 0
487
+ ? verticalGaps.reduce((a, b) => a + b, 0) / verticalGaps.length : 0;
488
+ const avgHGap = horizontalGaps.length > 0
489
+ ? horizontalGaps.reduce((a, b) => a + b, 0) / horizontalGaps.length : 0;
490
+
491
+ const containerIntent = {
492
+ direction: null,
493
+ gap: null,
494
+ isGapUniform: true,
495
+ gaps: [],
496
+ // 新增:对齐检测
497
+ alignment: detectChildrenAlignment(children),
498
+ // 新增:响应式建议
499
+ responsiveHint: generateResponsiveHint(children),
500
+ };
345
501
 
346
502
  if (avgVGap > 0 && avgHGap <= 0) {
347
- element.layoutHints.push(`COLUMN(gap: ${Math.round(avgVGap)})`);
348
- // 检测间距是否统一
349
- const isUniform = verticalGaps.every(g => Math.abs(g - avgVGap) <= 2);
350
- if (!isUniform) {
351
- element.layoutHints.push('⚠️ 间距不统一: [' + verticalGaps.join(', ') + ']');
352
- }
503
+ containerIntent.direction = 'COLUMN';
504
+ containerIntent.gap = Math.round(avgVGap);
505
+ containerIntent.gaps = verticalGaps;
506
+ containerIntent.isGapUniform = verticalGaps.every(g => Math.abs(g - avgVGap) <= 2);
353
507
  } else if (avgHGap > 0 && avgVGap <= 0) {
354
- element.layoutHints.push(`ROW(gap: ${Math.round(avgHGap)})`);
355
- const isUniform = horizontalGaps.every(g => Math.abs(g - avgHGap) <= 2);
356
- if (!isUniform) {
357
- element.layoutHints.push('⚠️ 间距不统一: [' + horizontalGaps.join(', ') + ']');
358
- }
508
+ containerIntent.direction = 'ROW';
509
+ containerIntent.gap = Math.round(avgHGap);
510
+ containerIntent.gaps = horizontalGaps;
511
+ containerIntent.isGapUniform = horizontalGaps.every(g => Math.abs(g - avgHGap) <= 2);
512
+ } else if (avgVGap > 0 && avgHGap > 0) {
513
+ // 网格布局
514
+ containerIntent.direction = 'GRID';
515
+ containerIntent.rowGap = Math.round(avgVGap);
516
+ containerIntent.columnGap = Math.round(avgHGap);
359
517
  }
518
+
519
+ return containerIntent;
360
520
  }
361
521
 
362
522
  // 执行测量
363
523
  const rootElement = measureRecursively(artboard, 0, null);
364
524
  result.elements.push(rootElement);
365
525
 
526
+ // 收集所有 icon 元素
527
+ const icons = [];
528
+ function collectIcons(element) {
529
+ if (element.isIcon) {
530
+ icons.push({
531
+ name: element.name,
532
+ id: element.id,
533
+ exportPath: element.iconExportPath,
534
+ size: { width: element.frame.width, height: element.frame.height }
535
+ });
536
+ }
537
+ element.children.forEach(collectIcons);
538
+ }
539
+ collectIcons(rootElement);
540
+
366
541
  // 输出结构化结果
367
- console.log('\n📊 测量完成,数据结构:');
542
+ console.log('\n📊 测量完成,Layout Intent 数据:');
368
543
  console.log(JSON.stringify(result, null, 2));
369
544
 
545
+ // 输出 icon 导出指引
546
+ if (icons.length > 0) {
547
+ console.log('\n🎨 检测到 Icon,建议导出步骤:');
548
+ console.log('1️⃣ 在 Sketch 中选中以下图层:');
549
+ icons.forEach(icon => {
550
+ console.log(` - ${icon.name} (建议尺寸: ${icon.size.width}x${icon.size.height})`);
551
+ });
552
+ console.log('\n2️⃣ 点击右下角"Make Exportable"');
553
+ console.log('3️⃣ 选择格式: SVG');
554
+ console.log('4️⃣ 设置高度: 200h(保持高宽比,高度200px)');
555
+ console.log('5️⃣ 导出位置建议: assets/icons/ 或 src/assets/icons/');
556
+ console.log('\n📝 导出后的使用方式:');
557
+ icons.forEach(icon => {
558
+ console.log(` ${icon.exportPath} → 在代码中引用`);
559
+ });
560
+ }
561
+
370
562
  return result;
371
563
  }
372
564
 
@@ -374,73 +566,132 @@ function deepMeasureForRestoration() {
374
566
  deepMeasureForRestoration();
375
567
  ```
376
568
 
377
- ### 分析与转换流程
569
+ ---
570
+
571
+ ### 第二层:框架专属转换规范
572
+
573
+ **⚠️ 关键设计:各框架有独立的转换规范,确保深度还原效果不因通用化而降低。**
574
+
575
+ 获取 Layout Intent 后,根据目标框架调用对应的转换规范:
576
+
577
+ #### 框架转换索引
578
+
579
+ | 框架 | 规范位置 | 调用方式 |
580
+ |------|---------|---------|
581
+ | **Flutter** | 本文档下方 + flutter.agent.md | `get_standard_by_id({ id: 'sketch-mcp' })` |
582
+ | **Vue 3 / CSS** | vue3.agent.md + design-restoration.md | `get_standard_by_id({ id: 'design-restoration' })` |
583
+ | **微信小程序** | wechat-miniprogram.agent.md | `get_standard_by_id({ id: 'wechat-miniprogram' })` |
584
+ | **React Native** | 参照 Flutter 规范 | Layout Intent 映射类似 |
585
+
586
+ ---
378
587
 
379
- 获取测量数据后,按以下流程分析:
588
+ ### Flutter 深度还原转换规范
380
589
 
381
- #### Step 1: 识别布局结构
590
+ #### Layout Intent → Flutter 代码映射
591
+
592
+ | Layout Intent | Flutter 实现 | 说明 |
593
+ |---------------|-------------|------|
594
+ | `alignment: ['CENTER']` | `Center(child: ...)` 或 `Align(alignment: Alignment.center)` | 完全居中 |
595
+ | `alignment: ['HORIZONTAL_CENTER']` | 外层 `Row` + `mainAxisAlignment: MainAxisAlignment.center` | 水平居中 |
596
+ | `alignment: ['VERTICAL_CENTER']` | 外层 `Column` + `mainAxisAlignment: MainAxisAlignment.center` | 垂直居中 |
597
+ | `alignment: ['ALIGN_START']` | `CrossAxisAlignment.start` 或 `Alignment.centerLeft` | 起始对齐 |
598
+ | `sizing.width: { type: 'FILL', margin: 16 }` | `Padding(padding: EdgeInsets.symmetric(horizontal: 16), child: SizedBox(width: double.infinity))` | 填充宽度 |
599
+ | `sizing.width: { type: 'MATCH_PARENT' }` | `SizedBox(width: double.infinity)` 或 `Expanded` | 匹配父宽 |
600
+ | `containerIntent.direction: 'COLUMN'` | `Column(children: [...])` | 垂直排列 |
601
+ | `containerIntent.direction: 'ROW'` | `Row(children: [...])` | 水平排列 |
602
+ | `containerIntent.gap: 16` | `Gap(16)` 或子元素间加 `SizedBox(height: 16)` | 间距 |
603
+ | `containerIntent.isGapUniform: false` | 根据 `gaps` 数组逐个设置 | 非均匀间距 |
604
+
605
+ #### Flutter 响应式禁止规则
606
+
607
+ | ❌ 禁止 | 原因 | ✅ 正确做法 |
608
+ |--------|------|-----------|
609
+ | `width: 343` | 仅适配设计稿尺寸 | `MediaQuery.of(context).size.width - 32` |
610
+ | `height: 812` | 仅适配特定设备 | `Expanded` / `Flexible` |
611
+ | `left: 47` | 无法适配其他尺寸 | 分析 Layout Intent,用 `Alignment` |
612
+ | `fontSize: 14` | 无响应式 | Token 如 `$t.bodyMd` |
613
+ | `Color(0xFF...)` | 硬编码颜色 | Token 如 `$c.primary` |
614
+
615
+ #### Flutter 验证清单
382
616
 
383
617
  ```
384
- 根据 layoutHints 确定:
385
- - CENTER 使用 Alignment.center
386
- - COLUMN(gap: X) → 使用 Column + Gap(X) 或 SizedBox
387
- - ROW(gap: X) → 使用 Row + Gap(X)
388
- - FILL_WIDTH → 使用 double.infinity 或 Expanded
618
+ 所有尺寸使用相对值或 Token($s.md, $r.lg)
619
+ 间距使用 Gap() SizedBox,值来自 Token
620
+ 颜色使用项目 Token($c.primary)
621
+ 阴影使用 Token($shadow.md)
622
+ 文字样式使用 Token($t.titleLg)
623
+ □ 在 iPhone SE / iPhone 15 Pro Max 测试布局
624
+ □ 与设计稿截图叠加对比
389
625
  ```
390
626
 
391
- #### Step 2: 计算响应式参数
627
+ ---
392
628
 
393
- ```javascript
394
- // ❌ 禁止硬编码绝对值
395
- Container(width: 343) // 错误!
396
-
397
- // ✅ 转换为相对值或约束
398
- // 方式1: 使用屏幕比例
399
- final width = MediaQuery.of(context).size.width - 32; // 左右各 16 边距
400
-
401
- // 方式2: 使用 LayoutBuilder
402
- LayoutBuilder(
403
- builder: (context, constraints) {
404
- final width = constraints.maxWidth - 32;
405
- return Container(width: width, ...);
406
- }
407
- )
629
+ ### Vue 3 / CSS 深度还原转换规范
408
630
 
409
- // 方式3: 使用 Expanded/Flexible
410
- Row(children: [
411
- Expanded(child: TextField(...)), // 自动填充剩余空间
412
- SizedBox(width: 16),
413
- ElevatedButton(...),
414
- ])
415
- ```
631
+ #### Layout Intent → Vue/CSS 代码映射
632
+
633
+ | Layout Intent | CSS / Vue 实现 | 说明 |
634
+ |---------------|---------------|------|
635
+ | `alignment: ['CENTER']` | `display: flex; justify-content: center; align-items: center;` | 完全居中 |
636
+ | `alignment: ['HORIZONTAL_CENTER']` | `margin: 0 auto;` 或 `justify-content: center;` | 水平居中 |
637
+ | `alignment: ['VERTICAL_CENTER']` | `align-items: center;` | 垂直居中 |
638
+ | `sizing.width: { type: 'FILL', margin: 16 }` | `width: calc(100% - 32px); margin: 0 16px;` | 填充宽度 |
639
+ | `sizing.width: { type: 'MATCH_PARENT' }` | `width: 100%;` | 匹配父宽 |
640
+ | `containerIntent.direction: 'COLUMN'` | `display: flex; flex-direction: column;` | 垂直排列 |
641
+ | `containerIntent.direction: 'ROW'` | `display: flex; flex-direction: row;` | 水平排列 |
642
+ | `containerIntent.gap: 16` | `gap: 16px;` | 间距 |
643
+ | `containerIntent.direction: 'GRID'` | `display: grid; grid-template-columns: repeat(...); gap: 16px;` | 网格布局 |
416
644
 
417
- #### Step 3: 边距转换规则
645
+ #### Vue 响应式禁止规则
418
646
 
419
- | 测量数据 | 分析结果 | Flutter 代码 |
420
- |----------|----------|-------------|
421
- | left=16, right=16 | 水平居中+固定边距 | `padding: EdgeInsets.symmetric(horizontal: 16)` |
422
- | left=16, right=不等 | 左对齐 | `padding: EdgeInsets.only(left: 16)` |
423
- | top=bottom | 垂直居中 | `alignment: Alignment.center` |
424
- | 元素宽度=父宽度-32 | 填充 | `Expanded` `width: double.infinity` |
647
+ | 禁止 | 原因 | 正确做法 |
648
+ |--------|------|-----------|
649
+ | `width: 343px` | 仅适配设计稿尺寸 | `width: calc(100% - 32px)` |
650
+ | `height: 812px` | 仅适配特定设备 | `min-height: 100vh` / `flex: 1` |
651
+ | `left: 47px` | 无法适配其他尺寸 | 分析 Layout Intent,用 flex |
652
+ | `font-size: 14px` | 无响应式 | CSS 变量 `var(--font-body-md)` |
653
+ | `color: #3B82F6` | 硬编码颜色 | CSS 变量 `var(--color-primary)` |
425
654
 
426
- #### Step 4: 验证清单
655
+ #### Vue 验证清单
427
656
 
428
657
  ```
429
- 所有尺寸使用相对值或 Token
430
- 间距使用统一的间距系统(如 $s.md, Gap(16))
431
- 颜色使用项目 Token(如 $c.primary)
432
- 在不同屏幕尺寸下测试布局
658
+ 使用 CSS 变量(--spacing-md, --color-primary)
659
+ 使用 flex/grid 布局,避免绝对定位
660
+ 移动端使用 vw/vh 或 calc()
661
+ 在 375px / 1920px 视口测试
433
662
  □ 与设计稿截图叠加对比
434
663
  ```
435
664
 
436
- ### 禁止事项
665
+ ---
437
666
 
438
- | 禁止 | 原因 | 正确做法 |
439
- |------|------|---------|
440
- | `width: 343` | 仅适配设计稿尺寸 | 使用 `MediaQuery` 或 `Expanded` |
441
- | `height: 812` | 仅适配特定设备 | 使用 `Flexible` 或百分比 |
442
- | `left: 47` | 无法适配其他尺寸 | 分析是否居中,用 `Alignment` |
443
- | `fontSize: 14` | 无响应式 | 使用 Token 如 `$t.bodyMd` |
667
+ ### 微信小程序深度还原转换规范
668
+
669
+ #### Layout Intent 小程序代码映射
670
+
671
+ | Layout Intent | WXML / WXSS 实现 | 说明 |
672
+ |---------------|-----------------|------|
673
+ | `alignment: ['CENTER']` | `display: flex; justify-content: center; align-items: center;` | 完全居中 |
674
+ | `sizing.width: { type: 'FILL', margin: 16 }` | `width: calc(100% - 64rpx); margin: 0 32rpx;` | 填充宽度(注意 rpx) |
675
+ | `containerIntent.direction: 'COLUMN'` | `display: flex; flex-direction: column;` | 垂直排列 |
676
+ | `containerIntent.gap: 16` | 小程序不支持 gap,用 `margin-bottom: 32rpx;` | 间距(最后一个用 :last-child 移除) |
677
+
678
+ #### 小程序特殊规则
679
+
680
+ | 设计稿值 (px) | 小程序值 (rpx) | 转换公式 |
681
+ |--------------|---------------|---------|
682
+ | 16px | 32rpx | `rpx = px * 2`(基于 375 设计稿) |
683
+ | 343px | 686rpx 或 `calc(100% - 64rpx)` | 宽度优先用 calc |
684
+ | 14px (字号) | 28rpx | 字号也需转换 |
685
+
686
+ #### 小程序验证清单
687
+
688
+ ```
689
+ □ 所有 px 值已转换为 rpx(×2)
690
+ □ 宽度使用 calc(100% - Xrpx) 而非固定 rpx
691
+ □ 间距用 margin 模拟 gap
692
+ □ 在微信开发者工具多机型预览
693
+ □ 与设计稿截图叠加对比
694
+ ```
444
695
 
445
696
  ---
446
697