mta-mcp 2.17.0 → 3.0.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/flutter.agent.md +23 -64
- package/agents/mta.agent.md +226 -0
- package/agents/vue3.agent.md +24 -0
- package/agents/wechat-miniprogram.agent.md +23 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
- package/standards/mcp-tools/sketch-mcp.md +497 -0
- package/standards/patterns/design-system-restoration.md +570 -0
- package/standards/syntax-mapping.md +256 -0
- package/standards/workflows/design-restoration.md +401 -0
- package/templates/README.md +14 -0
- package/templates/design-measurement/README.md +81 -0
- package/templates/design-measurement/component-measurement.js +52 -0
- package/templates/design-measurement/gap-measurement.js +79 -0
- package/templates/design-measurement/style-extraction.js +109 -0
- package/common/i18n.md +0 -385
- package/common/typescript-strict.md +0 -186
- package/troubleshooting/README.md +0 -368
- package/troubleshooting/USAGE_GUIDE.md +0 -289
- package/troubleshooting/flutter/clip-/351/230/264/345/275/261/350/243/201/345/211/252.md +0 -244
- package/troubleshooting/flutter/input-/345/255/227/346/256/265/347/274/272/345/244/261.md +0 -240
- package/troubleshooting/flutter/input-/350/276/271/346/241/206/351/227/256/351/242/230.md +0 -236
- package/troubleshooting/flutter/layout-/345/260/272/345/257/270/344/270/215/345/214/271/351/205/215.md +0 -214
- package/troubleshooting/flutter/shadow-/351/200/217/345/207/272/351/227/256/351/242/230.md +0 -172
- package/troubleshooting/flutter/sketch-/345/210/227/350/241/250item/345/214/272/345/237/237.md +0 -212
- package/troubleshooting/flutter/sketch-/345/233/276/346/240/207/345/260/272/345/257/270.md +0 -135
- package/troubleshooting/flutter/sketch-/345/256/214/346/225/264/346/217/220/345/217/226.md +0 -201
- package/troubleshooting/flutter/sketch-/345/261/236/346/200/247/346/234/252/344/275/277/347/224/250.md +0 -139
- package/troubleshooting/flutter/sketch-/350/203/214/346/231/257/345/261/202/351/253/230/345/272/246.md +0 -264
- package/troubleshooting/flutter/svg-/346/234/252/345/261/205/344/270/255.md +0 -120
- package/troubleshooting/flutter/svg-/351/242/234/350/211/262/345/274/202/345/270/270.md +0 -117
- package/troubleshooting/flutter/tabbar-/345/212/250/347/224/273/345/220/214/346/255/245.md +0 -107
- package/troubleshooting/flutter/withopacity-/345/274/203/347/224/250.md +0 -81
- package/troubleshooting/vue3/cascader-/350/257/257/346/233/277/346/215/242.md +0 -130
- package/troubleshooting/vue3/drawer-input-/346/240/267/345/274/217.md +0 -181
- package/troubleshooting/vue3/table-/347/274/226/350/276/221/345/217/226/346/266/210.md +0 -148
- package/troubleshooting/vue3/table-/350/276/271/346/241/206/351/227/256/351/242/230.md +0 -178
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mta-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MTA -
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "MTA - 智能编码助手 MCP 服务器(规范 + 技能 + 诊断 + 模板)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
"dist/",
|
|
21
21
|
"standards/",
|
|
22
22
|
"agents/",
|
|
23
|
-
"common/",
|
|
24
23
|
"templates/",
|
|
25
|
-
"troubleshooting/",
|
|
26
24
|
"README.md"
|
|
27
25
|
],
|
|
28
26
|
"scripts": {
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# Sketch MCP 最佳实践
|
|
2
|
+
|
|
3
|
+
> 使用 Sketch MCP 工具准确测量设计稿的规范指南
|
|
4
|
+
|
|
5
|
+
## 📋 概述
|
|
6
|
+
|
|
7
|
+
Sketch MCP 是用于从 Sketch 设计文件中提取 UI 参数的工具。本文档记录了使用过程中发现的问题和最佳实践。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⚠️ 已知问题与解决方案
|
|
12
|
+
|
|
13
|
+
### 问题 1:渐变色被读取为单色
|
|
14
|
+
|
|
15
|
+
**严重程度**: 🔴 高
|
|
16
|
+
|
|
17
|
+
**现象**:
|
|
18
|
+
```javascript
|
|
19
|
+
layer.style.fills[0].color // 返回 "#4a60b2ff"
|
|
20
|
+
layer.style.fills[0].fillType // 返回 "Gradient"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
当 `fillType` 是 `Gradient` 时,`color` 属性返回的是一个"代表色",而非实际的渐变颜色。
|
|
24
|
+
|
|
25
|
+
**解决方案**:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
function getLayerColor(layer) {
|
|
29
|
+
const fills = layer.style?.fills?.filter(f => f.enabled);
|
|
30
|
+
if (!fills || fills.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
const fill = fills[0];
|
|
33
|
+
|
|
34
|
+
// 关键:先判断 fillType
|
|
35
|
+
if (fill.fillType === 'Gradient' && fill.gradient) {
|
|
36
|
+
const g = fill.gradient;
|
|
37
|
+
return {
|
|
38
|
+
type: 'gradient',
|
|
39
|
+
gradientType: g.gradientType, // Linear | Radial | Angular
|
|
40
|
+
direction: { from: g.from, to: g.to },
|
|
41
|
+
colors: g.stops.map(s => ({
|
|
42
|
+
position: s.position,
|
|
43
|
+
color: s.color
|
|
44
|
+
}))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
type: 'solid',
|
|
50
|
+
color: fill.color
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 问题 2:fontWeight 数值不准确(重要)
|
|
56
|
+
|
|
57
|
+
**严重程度**: 🔴 高
|
|
58
|
+
|
|
59
|
+
**现象**:`layer.style.fontWeight` 返回的数值(如 8)不能直接映射到 Flutter/CSS 字重。
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// ❌ 错误方式 - fontWeight 值不可靠
|
|
63
|
+
layer.style.fontWeight // 返回 8,但实际可能是 Semibold(w600) 而非 ExtraBold(w800)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**原因**:Sketch API 的 `fontWeight` 属性返回的是内部权重值,不是标准的 100-900 字重值。
|
|
67
|
+
|
|
68
|
+
**解决方案**:通过 `sketchObject.font()` 获取真实的 PostScript 字体名称:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
function getTextFontInfo(textLayer) {
|
|
72
|
+
const result = {
|
|
73
|
+
fontSize: textLayer.style.fontSize,
|
|
74
|
+
fontFamily: textLayer.style.fontFamily,
|
|
75
|
+
fontWeight: textLayer.style.fontWeight, // 仅供参考
|
|
76
|
+
fontName: null, // 关键:真实字体名称
|
|
77
|
+
displayName: null
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 获取真实字体名称
|
|
81
|
+
if (textLayer.sketchObject && textLayer.sketchObject.font) {
|
|
82
|
+
const font = textLayer.sketchObject.font();
|
|
83
|
+
if (font) {
|
|
84
|
+
result.fontName = String(font.fontName()); // 如 "PingFangSC-Semibold"
|
|
85
|
+
result.displayName = String(font.displayName()); // 如 "苹方-简 中粗体"
|
|
86
|
+
result.familyName = String(font.familyName()); // 如 "PingFang SC"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**PostScript 字体名到 Flutter FontWeight 映射**:
|
|
95
|
+
|
|
96
|
+
| PostScript 后缀 | 中文名 | Flutter | CSS |
|
|
97
|
+
|----------------|--------|---------|-----|
|
|
98
|
+
| `-Thin` | 极细 | w100 | 100 |
|
|
99
|
+
| `-Ultralight` | 纤细 | w200 | 200 |
|
|
100
|
+
| `-Light` | 细体 | w300 | 300 |
|
|
101
|
+
| `-Regular` | 常规 | w400 | 400 |
|
|
102
|
+
| `-Medium` | 中等 | w500 | 500 |
|
|
103
|
+
| `-Semibold` | 中粗体 | w600 | 600 |
|
|
104
|
+
| `-Bold` | 粗体 | w700 | 700 |
|
|
105
|
+
| `-Heavy` | 特粗 | w800 | 800 |
|
|
106
|
+
| `-Black` | 黑体 | w900 | 900 |
|
|
107
|
+
|
|
108
|
+
### 问题 3:图标必须从设计稿导出
|
|
109
|
+
|
|
110
|
+
**严重程度**: 🔴 高
|
|
111
|
+
|
|
112
|
+
**现象**:AI 可能会自己生成图标 SVG,而不是从设计稿中提取。
|
|
113
|
+
|
|
114
|
+
**正确做法**:使用 `sketch.export` API 导出设计稿中的图标:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
const sketch = require('sketch');
|
|
118
|
+
|
|
119
|
+
function exportIconAsSVG(layer) {
|
|
120
|
+
// 导出为 Buffer
|
|
121
|
+
const buffer = sketch.export(layer, {
|
|
122
|
+
formats: 'svg',
|
|
123
|
+
output: false, // 关键:返回 Buffer 而非写文件
|
|
124
|
+
scales: '1'
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Buffer 转字符串
|
|
128
|
+
const svgString = buffer.toString('utf-8');
|
|
129
|
+
|
|
130
|
+
// 清理 SVG(移除 Sketch 生成的多余属性)
|
|
131
|
+
return svgString
|
|
132
|
+
.replace(/id="[^"]*"/g, '')
|
|
133
|
+
.replace(/sketch:type="[^"]*"/g, '')
|
|
134
|
+
.replace(/xmlns:sketch="[^"]*"/g, '')
|
|
135
|
+
.replace(/\s+/g, ' ')
|
|
136
|
+
.trim();
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**在 Flutter 中使用导出的 SVG**:
|
|
141
|
+
|
|
142
|
+
```dart
|
|
143
|
+
// 方式1:内联 SVG(推荐小图标)
|
|
144
|
+
SvgPicture.string(
|
|
145
|
+
'''<svg>...</svg>''',
|
|
146
|
+
width: 24,
|
|
147
|
+
height: 24,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// 方式2:保存为文件(推荐大图标)
|
|
151
|
+
// 将导出的 SVG 保存到 assets/icons/ 目录
|
|
152
|
+
SvgPicture.asset('assets/icons/notification.svg')
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 问题 4:列表项间距测量
|
|
156
|
+
|
|
157
|
+
**严重程度**: 🔴 高
|
|
158
|
+
|
|
159
|
+
**现象**:列表中多个同类元素的间距可能不一致,需要逐个测量。
|
|
160
|
+
|
|
161
|
+
**测量脚本**:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
function measureListSpacing(container) {
|
|
165
|
+
const children = container.layers;
|
|
166
|
+
const spacings = [];
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < children.length - 1; i++) {
|
|
169
|
+
const current = children[i];
|
|
170
|
+
const next = children[i + 1];
|
|
171
|
+
|
|
172
|
+
// 垂直间距 = 下一个元素的 y - (当前元素的 y + 高度)
|
|
173
|
+
const spacing = next.frame.y - (current.frame.y + current.frame.height);
|
|
174
|
+
|
|
175
|
+
spacings.push({
|
|
176
|
+
between: `${current.name} → ${next.name}`,
|
|
177
|
+
spacing: Math.round(spacing)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 检查是否一致
|
|
182
|
+
const uniqueSpacings = [...new Set(spacings.map(s => s.spacing))];
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
spacings,
|
|
186
|
+
isConsistent: uniqueSpacings.length === 1,
|
|
187
|
+
recommendedSpacing: uniqueSpacings.length === 1
|
|
188
|
+
? uniqueSpacings[0]
|
|
189
|
+
: Math.round(spacings.reduce((a, b) => a + b.spacing, 0) / spacings.length)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**注意**:如果设计稿中间距不一致,应与设计师确认是故意还是误差。代码中应使用统一间距。
|
|
195
|
+
|
|
196
|
+
### 问题 5:Symbol 内部样式读取
|
|
197
|
+
|
|
198
|
+
**严重程度**: 🟡 中
|
|
199
|
+
|
|
200
|
+
**现象**:Symbol 实例的内部图层样式可能需要通过 `expandedLayers` 访问。
|
|
201
|
+
|
|
202
|
+
**解决方案**:
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
if (layer.type === 'SymbolInstance') {
|
|
206
|
+
layer.expandedLayers.forEach(nestedLayer => {
|
|
207
|
+
// 递归处理嵌套图层
|
|
208
|
+
measureLayer(nestedLayer);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 问题 6:阴影数组顺序
|
|
214
|
+
|
|
215
|
+
**严重程度**: 🟢 低
|
|
216
|
+
|
|
217
|
+
**现象**:多重阴影的顺序可能与视觉效果不一致。
|
|
218
|
+
|
|
219
|
+
**解决方案**:记录所有阴影,并按 blur 值或 enabled 状态排序。
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 📐 标准测量脚本
|
|
224
|
+
|
|
225
|
+
### 完整测量函数
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
const sketch = require('sketch');
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 完整的图层测量函数
|
|
232
|
+
* 正确处理渐变色、阴影、圆角等
|
|
233
|
+
*/
|
|
234
|
+
function measureLayerComplete(layer) {
|
|
235
|
+
const result = {
|
|
236
|
+
name: layer.name,
|
|
237
|
+
type: layer.type,
|
|
238
|
+
frame: {
|
|
239
|
+
x: Math.round(layer.frame.x),
|
|
240
|
+
y: Math.round(layer.frame.y),
|
|
241
|
+
width: Math.round(layer.frame.width),
|
|
242
|
+
height: Math.round(layer.frame.height),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
if (!layer.style) return result;
|
|
247
|
+
|
|
248
|
+
// ========== 填充色 ==========
|
|
249
|
+
const enabledFills = layer.style.fills?.filter(f => f.enabled) || [];
|
|
250
|
+
if (enabledFills.length > 0) {
|
|
251
|
+
result.fills = enabledFills.map(fill => {
|
|
252
|
+
if (fill.fillType === 'Gradient' && fill.gradient) {
|
|
253
|
+
const g = fill.gradient;
|
|
254
|
+
return {
|
|
255
|
+
type: 'gradient',
|
|
256
|
+
gradientType: g.gradientType,
|
|
257
|
+
from: g.from,
|
|
258
|
+
to: g.to,
|
|
259
|
+
stops: g.stops.map(s => ({
|
|
260
|
+
position: s.position,
|
|
261
|
+
color: s.color
|
|
262
|
+
}))
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
type: 'solid',
|
|
267
|
+
color: fill.color
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ========== 边框 ==========
|
|
273
|
+
const enabledBorders = layer.style.borders?.filter(b => b.enabled) || [];
|
|
274
|
+
if (enabledBorders.length > 0) {
|
|
275
|
+
result.borders = enabledBorders.map(b => ({
|
|
276
|
+
color: b.color,
|
|
277
|
+
thickness: b.thickness,
|
|
278
|
+
position: b.position // inside | outside | center
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ========== 阴影 ==========
|
|
283
|
+
const enabledShadows = layer.style.shadows?.filter(s => s.enabled) || [];
|
|
284
|
+
if (enabledShadows.length > 0) {
|
|
285
|
+
result.shadows = enabledShadows.map(s => ({
|
|
286
|
+
color: s.color,
|
|
287
|
+
blur: s.blur,
|
|
288
|
+
x: s.x,
|
|
289
|
+
y: s.y,
|
|
290
|
+
spread: s.spread || 0,
|
|
291
|
+
isInner: s.isInnerShadow || false
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ========== 内阴影 ==========
|
|
296
|
+
const innerShadows = layer.style.innerShadows?.filter(s => s.enabled) || [];
|
|
297
|
+
if (innerShadows.length > 0) {
|
|
298
|
+
result.innerShadows = innerShadows.map(s => ({
|
|
299
|
+
color: s.color,
|
|
300
|
+
blur: s.blur,
|
|
301
|
+
x: s.x,
|
|
302
|
+
y: s.y,
|
|
303
|
+
spread: s.spread || 0
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ========== 圆角 ==========
|
|
308
|
+
if (layer.style.corners) {
|
|
309
|
+
const radii = layer.style.corners.radii;
|
|
310
|
+
// 检查是否四角相同
|
|
311
|
+
if (radii.every(r => r === radii[0])) {
|
|
312
|
+
result.borderRadius = radii[0];
|
|
313
|
+
} else {
|
|
314
|
+
result.borderRadius = radii; // [topLeft, topRight, bottomRight, bottomLeft]
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ========== 文字属性 ==========
|
|
319
|
+
if (layer.type === 'Text') {
|
|
320
|
+
result.text = {
|
|
321
|
+
content: layer.text,
|
|
322
|
+
fontSize: layer.style.fontSize,
|
|
323
|
+
fontFamily: layer.style.fontFamily,
|
|
324
|
+
fontWeight: layer.style.fontWeight,
|
|
325
|
+
textColor: layer.style.textColor,
|
|
326
|
+
alignment: layer.style.alignment,
|
|
327
|
+
lineHeight: layer.style.lineHeight,
|
|
328
|
+
letterSpacing: layer.style.kerning
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ========== 透明度 ==========
|
|
333
|
+
if (layer.style.opacity !== undefined && layer.style.opacity !== 1) {
|
|
334
|
+
result.opacity = layer.style.opacity;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ========== 模糊效果 ==========
|
|
338
|
+
if (layer.style.blur?.enabled) {
|
|
339
|
+
result.blur = {
|
|
340
|
+
type: layer.style.blur.blurType,
|
|
341
|
+
radius: layer.style.blur.radius
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 递归测量整个画板
|
|
350
|
+
*/
|
|
351
|
+
function measureArtboard(artboard) {
|
|
352
|
+
const results = [];
|
|
353
|
+
|
|
354
|
+
function traverse(layer, depth = 0) {
|
|
355
|
+
if (layer.hidden) return;
|
|
356
|
+
|
|
357
|
+
const data = measureLayerComplete(layer);
|
|
358
|
+
data.depth = depth;
|
|
359
|
+
results.push(data);
|
|
360
|
+
|
|
361
|
+
// 递归子图层
|
|
362
|
+
if (layer.layers) {
|
|
363
|
+
layer.layers.forEach(child => traverse(child, depth + 1));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Symbol 展开图层
|
|
367
|
+
if (layer.expandedLayers) {
|
|
368
|
+
layer.expandedLayers.forEach(child => traverse(child, depth + 1));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
traverse(artboard);
|
|
373
|
+
return results;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 使用示例
|
|
377
|
+
const doc = sketch.getSelectedDocument();
|
|
378
|
+
const artboard = doc.selectedLayers.layers[0];
|
|
379
|
+
const measurements = measureArtboard(artboard);
|
|
380
|
+
console.log(JSON.stringify(measurements, null, 2));
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## 🎨 颜色转换
|
|
386
|
+
|
|
387
|
+
### Sketch 颜色到各框架
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
/**
|
|
391
|
+
* 将 Sketch 颜色转换为各框架格式
|
|
392
|
+
* @param {string} sketchColor - Sketch 颜色格式 "#rrggbbaa"
|
|
393
|
+
* @param {string} framework - 目标框架
|
|
394
|
+
*/
|
|
395
|
+
function convertColor(sketchColor, framework) {
|
|
396
|
+
// Sketch 格式: #rrggbbaa
|
|
397
|
+
const r = parseInt(sketchColor.slice(1, 3), 16);
|
|
398
|
+
const g = parseInt(sketchColor.slice(3, 5), 16);
|
|
399
|
+
const b = parseInt(sketchColor.slice(5, 7), 16);
|
|
400
|
+
const a = parseInt(sketchColor.slice(7, 9), 16) / 255;
|
|
401
|
+
|
|
402
|
+
switch (framework) {
|
|
403
|
+
case 'flutter':
|
|
404
|
+
// Flutter: Color(0xAARRGGBB)
|
|
405
|
+
const flutterHex = sketchColor.slice(7, 9) + sketchColor.slice(1, 7);
|
|
406
|
+
return `Color(0x${flutterHex.toUpperCase()})`;
|
|
407
|
+
|
|
408
|
+
case 'css':
|
|
409
|
+
// CSS: rgba(r, g, b, a)
|
|
410
|
+
if (a === 1) {
|
|
411
|
+
return sketchColor.slice(0, 7);
|
|
412
|
+
}
|
|
413
|
+
return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`;
|
|
414
|
+
|
|
415
|
+
case 'react-native':
|
|
416
|
+
// React Native: '#RRGGBBAA' 或 'rgba(...)'
|
|
417
|
+
if (a === 1) {
|
|
418
|
+
return `'${sketchColor.slice(0, 7)}'`;
|
|
419
|
+
}
|
|
420
|
+
return `'rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})'`;
|
|
421
|
+
|
|
422
|
+
default:
|
|
423
|
+
return sketchColor;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 渐变转换
|
|
428
|
+
function convertGradient(gradient, framework) {
|
|
429
|
+
const colors = gradient.stops.map(s =>
|
|
430
|
+
convertColor(s.color, framework)
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
switch (framework) {
|
|
434
|
+
case 'flutter':
|
|
435
|
+
return `LinearGradient(
|
|
436
|
+
begin: Alignment(${gradient.from.x * 2 - 1}, ${gradient.from.y * 2 - 1}),
|
|
437
|
+
end: Alignment(${gradient.to.x * 2 - 1}, ${gradient.to.y * 2 - 1}),
|
|
438
|
+
colors: [${colors.join(', ')}],
|
|
439
|
+
)`;
|
|
440
|
+
|
|
441
|
+
case 'css':
|
|
442
|
+
const angle = Math.atan2(
|
|
443
|
+
gradient.to.y - gradient.from.y,
|
|
444
|
+
gradient.to.x - gradient.from.x
|
|
445
|
+
) * 180 / Math.PI + 90;
|
|
446
|
+
return `linear-gradient(${angle}deg, ${colors.join(', ')})`;
|
|
447
|
+
|
|
448
|
+
default:
|
|
449
|
+
return colors;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## ✅ 使用检查清单
|
|
457
|
+
|
|
458
|
+
### 开始测量前
|
|
459
|
+
|
|
460
|
+
- [ ] 确认选中正确的画板/Frame
|
|
461
|
+
- [ ] 检查是否有隐藏图层需要测量
|
|
462
|
+
- [ ] 确认设计稿是最新版本
|
|
463
|
+
|
|
464
|
+
### 测量过程中
|
|
465
|
+
|
|
466
|
+
- [ ] 对每个颜色检查 `fillType` 是否为 Gradient
|
|
467
|
+
- [ ] 阴影检查是否有多重阴影
|
|
468
|
+
- [ ] 圆角检查四角是否相同
|
|
469
|
+
- [ ] 文字检查行高是否有定义
|
|
470
|
+
|
|
471
|
+
### 测量完成后
|
|
472
|
+
|
|
473
|
+
- [ ] 与设计稿视觉对比
|
|
474
|
+
- [ ] 渐变色验证(最容易出错)
|
|
475
|
+
- [ ] 透明度验证
|
|
476
|
+
- [ ] 响应式适配验证
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 📚 相关资源
|
|
481
|
+
|
|
482
|
+
- [Sketch JavaScript API 文档](https://developer.sketch.com/reference/api/)
|
|
483
|
+
- [设计稿还原规范](../workflows/design-restoration.md)
|
|
484
|
+
- [颜色系统规范](../color-system.md)
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
**维护者**: MTA工作室
|
|
489
|
+
**创建日期**: 2026-01-20
|
|
490
|
+
**最后更新**: 2026-01-21
|
|
491
|
+
|
|
492
|
+
### 更新日志
|
|
493
|
+
|
|
494
|
+
| 版本 | 日期 | 更新内容 |
|
|
495
|
+
|------|------|---------|
|
|
496
|
+
| v1.0 | 2026-01-20 | 初始版本 |
|
|
497
|
+
| v1.1 | 2026-01-21 | 新增:fontWeight不可靠问题及fontName获取方案、图标导出规范、列表间距测量脚本 |
|