mta-mcp 1.0.0 → 1.0.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.
@@ -136,85 +136,26 @@ get_standard_by_id({ ids: ['flutter', 'flutter-ui-system'] })
136
136
 
137
137
  ---
138
138
 
139
- ## 🎨 设计稿还原强制规范
140
-
141
- > ⚠️ 使用 Sketch/Figma MCP 时必须遵守
142
-
143
- ### 🔴 全局样式优先(最高优先级)
144
-
145
- **在还原任何 UI 前,必须首先检查是否存在全局样式规范!**
146
-
147
- #### 强制检查流程
148
-
149
- 1. **圆角** - 优先使用 `DesignRadius` 中的标准值:
150
- | 设计值 | 使用常量 | 用途 |
151
- |--------|----------|------|
152
- | 20px | `DesignRadius.xl` | 大统计卡片 |
153
- | 16px | `DesignRadius.lg` | 通用卡片、CTA按钮 |
154
- | 14px | `DesignRadius.md` | Tab、选择框 |
155
- | 12px | `DesignRadius.base` | 图标背景、中型按钮 |
156
- | 10px | `DesignRadius.sm` | 小按钮 |
157
- | 8px | `DesignRadius.xs` | 输入框 |
158
-
159
- 2. **按钮高度** - 优先使用 `DesignButtonSizes`:
160
- | 设计值 | 使用常量 | 用途 |
161
- |--------|----------|------|
162
- | 56px | `DesignButtonSizes.heightCTA` | 主CTA按钮 |
163
- | 40px | `DesignButtonSizes.heightMedium` | 标准按钮 |
164
- | 36px | `DesignButtonSizes.heightIcon` | 图标按钮 |
165
- | 32px | `DesignButtonSizes.heightSmall` | 小按钮 |
166
- | 28px | `DesignButtonSizes.heightWarning` | 警告按钮 |
167
-
168
- 3. **阴影** - 优先使用 `DesignShadows`:
169
- | 模式 | 使用常量 | 用途 |
170
- |------|----------|------|
171
- | (4,4,8) | `DesignShadows.raised` | 按钮、小卡片 |
172
- | (6,6,12) | `DesignShadows.card` | 普通卡片 |
173
- | (8,8,20) | `DesignShadows.elevated` | 大卡片 |
174
- | CTA红色 | `DesignShadows.cta` | 主操作按钮 |
175
-
176
- 4. **颜色** - 优先使用 `DesignColors`:
177
- | 颜色 | 使用常量 |
178
- |------|----------|
179
- | #1C2B45 | `DesignColors.textPrimary` |
180
- | #1C2B4599 | `DesignColors.textSecondary.withOpacity(0.6)` |
181
- | #E8EDF5 | `DesignColors.pageBackground` |
182
- | #EEF3FA→#D9E0EB | `DesignColors.cardGradient` |
183
- | #D0121B | `DesignColors.primary` |
184
- | #FF9800 | `DesignColors.warning` |
185
-
186
- #### 禁止事项
187
- - ❌ 硬编码 `BorderRadius.circular(16)` - 应使用 `DesignRadius.lg`
188
- - ❌ 硬编码 `height: 56` - 应使用 `DesignButtonSizes.heightCTA`
189
- - ❌ 硬编码 `Color(0xFF1C2B45)` - 应使用 `DesignColors.textPrimary`
190
- - ❌ 手写阴影参数 - 应使用 `DesignShadows` 预设
191
-
192
- ### 强制要求:完整读取选中元素及其所有子集
193
-
194
- ```javascript
195
- // 正确:一次性获取容器+所有子元素的完整信息
196
- function extractComplete(element) {
197
- // 1. 容器信息
198
- console.log(`容器: ${element.frame.width}x${element.frame.height}`);
199
-
200
- // 2. 所有子元素信息(关键!)
201
- element.layers.forEach(child => {
202
- console.log(`子元素: ${child.name}`);
203
- console.log(` 位置: Y=${child.frame.y}px`);
204
- console.log(` 尺寸: ${child.frame.width}x${child.frame.height}`);
205
- if (child.type === 'Text') {
206
- console.log(` 字号: ${child.style.fontSize}px`);
207
- console.log(` 行高: ${child.frame.height / child.style.fontSize}`);
208
- }
209
- });
210
- }
211
- ```
139
+ ## 🎨 设计稿还原
212
140
 
213
- ### 禁止事项
214
- - ❌ 只读容器属性,忽略内部元素位置
215
- - ❌ 每次只查一个属性,分散多轮获取
216
- - ❌ 假设"差不多"而不验证精确像素值
217
- - 用"试错法"调整参数
141
+ > ⚠️ 详细规范请调用:`get_standard_by_id({ id: 'design-restoration' })`
142
+
143
+ ### 快速提示
144
+
145
+ | 关键点 | 说明 |
146
+ |--------|------|
147
+ | **渐变色** | 检查 `fillType` 后读取 `gradient.stops`,勿直接用 `color` 属性 |
148
+ | **全局样式** | 还原前先 grep 搜索项目现有 Token(颜色、圆角、阴影) |
149
+ | **新拟态阴影** | 双阴影:右下深色 + 左上高光 |
150
+ | **行高** | Flutter `height` 是比例值,非像素(如 24px/16px = 1.5) |
151
+
152
+ ### 相关规范
153
+
154
+ | 规范 | 调用方式 |
155
+ |------|----------|
156
+ | 设计稿还原流程 | `get_standard_by_id({ id: 'design-restoration' })` |
157
+ | Sketch MCP 用法 | `get_standard_by_id({ id: 'sketch-mcp' })` |
158
+ | 跨框架语法映射 | `get_standard_by_id({ id: 'syntax-mapping' })` |
218
159
 
219
160
  ---
220
161
 
@@ -136,39 +136,26 @@ get_standard_by_id({ ids: ['vue3-composition', 'element-plus', 'i18n'] })
136
136
 
137
137
  ---
138
138
 
139
- ## 🎨 设计稿还原强制规范(跨平台通用)
139
+ ## 🎨 设计稿还原
140
140
 
141
- > ⚠️ 还原 Sketch/Figma 设计稿时必须遵守
141
+ > ⚠️ 详细规范请调用:`get_standard_by_id({ id: 'design-restoration' })`
142
142
 
143
- ### 🔴 全局样式优先(最高优先级)
143
+ ### 快速提示
144
144
 
145
- **在还原任何 UI 前,必须首先检查项目是否存在全局样式规范!**
145
+ | 关键点 | 说明 |
146
+ |--------|------|
147
+ | **渐变色** | 检查 `fillType` 后读取 `gradient.stops`,勿直接用 `color` 属性 |
148
+ | **全局样式** | 还原前先搜索项目现有 CSS 变量 / Token |
149
+ | **渐变语法** | `background: linear-gradient(135deg, #E01020, #B8101A)` |
150
+ | **阴影语法** | `box-shadow: 8px 8px 20px rgba(28,43,69,0.15)` |
146
151
 
147
- #### 检查顺序
152
+ ### 相关规范
148
153
 
149
- 1. **检查项目设计 Token** - 如 `design_constants.dart`、`design-tokens.ts`
150
- 2. **优先使用语义化常量** - 而非硬编码数值
151
- 3. **颜色/圆角/阴影/间距** - 必须使用全局定义
152
-
153
- #### 常见样式对照表
154
-
155
- | 类型 | 常见值 | 应使用 |
156
- |------|--------|--------|
157
- | **圆角** | 16px | `$radius.lg` / CSS 变量 `--radius-lg` |
158
- | **圆角** | 12px | `$radius.base` |
159
- | **圆角** | 8px | `$radius.xs` |
160
- | **按钮高度** | 56px | `$button.heightCTA` |
161
- | **按钮高度** | 40px | `$button.heightMedium` |
162
- | **按钮高度** | 32px | `$button.heightSmall` |
163
- | **阴影** | 拟物卡片 | `$shadow.card` |
164
- | **主色** | 红色CTA | `$color.primary` |
165
- | **文字色** | 深色 | `$color.textPrimary` |
166
-
167
- #### 禁止事项
168
- - ❌ 硬编码 `border-radius: 16px` - 应使用 CSS 变量或常量
169
- - ❌ 硬编码 `height: 56px` - 应使用按钮尺寸常量
170
- - ❌ 硬编码 `color: #1C2B45` - 应使用颜色变量
171
- - ❌ 手写 `box-shadow` - 应使用预设阴影 mixin
154
+ | 规范 | 调用方式 |
155
+ |------|----------|
156
+ | 设计稿还原流程 | `get_standard_by_id({ id: 'design-restoration' })` |
157
+ | Sketch MCP 用法 | `get_standard_by_id({ id: 'sketch-mcp' })` |
158
+ | 跨框架语法映射 | `get_standard_by_id({ id: 'syntax-mapping' })` |
172
159
 
173
160
  ---
174
161
 
@@ -85,5 +85,28 @@ miniprogram/
85
85
 
86
86
  ---
87
87
 
88
+ ## 🎨 设计稿还原
89
+
90
+ > ⚠️ 详细规范请调用:`get_standard_by_id({ id: 'design-restoration' })`
91
+
92
+ ### 快速提示
93
+
94
+ | 关键点 | 说明 |
95
+ |--------|------|
96
+ | **渐变色** | 检查 `fillType` 后读取 `gradient.stops`,勿直接用 `color` 属性 |
97
+ | **全局样式** | 还原前先搜索项目现有变量(app.wxss 中的 CSS 变量) |
98
+ | **rpx 换算** | 设计稿 px * 2 = rpx(基于 750px 设计稿) |
99
+ | **阴影语法** | `box-shadow: 8px 8px 20px rgba(28,43,69,0.15)` |
100
+
101
+ ### 相关规范
102
+
103
+ | 规范 | 调用方式 |
104
+ |------|----------|
105
+ | 设计稿还原流程 | `get_standard_by_id({ id: 'design-restoration' })` |
106
+ | Sketch MCP 用法 | `get_standard_by_id({ id: 'sketch-mcp' })` |
107
+ | 跨框架语法映射 | `get_standard_by_id({ id: 'syntax-mapping' })` |
108
+
109
+ ---
110
+
88
111
  **维护团队**: MTA工作室
89
112
  **设计理念**: Agent 只提供获取指引,详细规范由 MCP 工具从 npm 包动态获取
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mta-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MTA - 智能项目分析与编码规范管理 MCP 服务器",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,349 @@
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:Symbol 内部样式读取
56
+
57
+ **严重程度**: 🟡 中
58
+
59
+ **现象**:Symbol 实例的内部图层样式可能需要通过 `expandedLayers` 访问。
60
+
61
+ **解决方案**:
62
+
63
+ ```javascript
64
+ if (layer.type === 'SymbolInstance') {
65
+ layer.expandedLayers.forEach(nestedLayer => {
66
+ // 递归处理嵌套图层
67
+ measureLayer(nestedLayer);
68
+ });
69
+ }
70
+ ```
71
+
72
+ ### 问题 3:阴影数组顺序
73
+
74
+ **严重程度**: 🟢 低
75
+
76
+ **现象**:多重阴影的顺序可能与视觉效果不一致。
77
+
78
+ **解决方案**:记录所有阴影,并按 blur 值或 enabled 状态排序。
79
+
80
+ ---
81
+
82
+ ## 📐 标准测量脚本
83
+
84
+ ### 完整测量函数
85
+
86
+ ```javascript
87
+ const sketch = require('sketch');
88
+
89
+ /**
90
+ * 完整的图层测量函数
91
+ * 正确处理渐变色、阴影、圆角等
92
+ */
93
+ function measureLayerComplete(layer) {
94
+ const result = {
95
+ name: layer.name,
96
+ type: layer.type,
97
+ frame: {
98
+ x: Math.round(layer.frame.x),
99
+ y: Math.round(layer.frame.y),
100
+ width: Math.round(layer.frame.width),
101
+ height: Math.round(layer.frame.height),
102
+ },
103
+ };
104
+
105
+ if (!layer.style) return result;
106
+
107
+ // ========== 填充色 ==========
108
+ const enabledFills = layer.style.fills?.filter(f => f.enabled) || [];
109
+ if (enabledFills.length > 0) {
110
+ result.fills = enabledFills.map(fill => {
111
+ if (fill.fillType === 'Gradient' && fill.gradient) {
112
+ const g = fill.gradient;
113
+ return {
114
+ type: 'gradient',
115
+ gradientType: g.gradientType,
116
+ from: g.from,
117
+ to: g.to,
118
+ stops: g.stops.map(s => ({
119
+ position: s.position,
120
+ color: s.color
121
+ }))
122
+ };
123
+ }
124
+ return {
125
+ type: 'solid',
126
+ color: fill.color
127
+ };
128
+ });
129
+ }
130
+
131
+ // ========== 边框 ==========
132
+ const enabledBorders = layer.style.borders?.filter(b => b.enabled) || [];
133
+ if (enabledBorders.length > 0) {
134
+ result.borders = enabledBorders.map(b => ({
135
+ color: b.color,
136
+ thickness: b.thickness,
137
+ position: b.position // inside | outside | center
138
+ }));
139
+ }
140
+
141
+ // ========== 阴影 ==========
142
+ const enabledShadows = layer.style.shadows?.filter(s => s.enabled) || [];
143
+ if (enabledShadows.length > 0) {
144
+ result.shadows = enabledShadows.map(s => ({
145
+ color: s.color,
146
+ blur: s.blur,
147
+ x: s.x,
148
+ y: s.y,
149
+ spread: s.spread || 0,
150
+ isInner: s.isInnerShadow || false
151
+ }));
152
+ }
153
+
154
+ // ========== 内阴影 ==========
155
+ const innerShadows = layer.style.innerShadows?.filter(s => s.enabled) || [];
156
+ if (innerShadows.length > 0) {
157
+ result.innerShadows = innerShadows.map(s => ({
158
+ color: s.color,
159
+ blur: s.blur,
160
+ x: s.x,
161
+ y: s.y,
162
+ spread: s.spread || 0
163
+ }));
164
+ }
165
+
166
+ // ========== 圆角 ==========
167
+ if (layer.style.corners) {
168
+ const radii = layer.style.corners.radii;
169
+ // 检查是否四角相同
170
+ if (radii.every(r => r === radii[0])) {
171
+ result.borderRadius = radii[0];
172
+ } else {
173
+ result.borderRadius = radii; // [topLeft, topRight, bottomRight, bottomLeft]
174
+ }
175
+ }
176
+
177
+ // ========== 文字属性 ==========
178
+ if (layer.type === 'Text') {
179
+ result.text = {
180
+ content: layer.text,
181
+ fontSize: layer.style.fontSize,
182
+ fontFamily: layer.style.fontFamily,
183
+ fontWeight: layer.style.fontWeight,
184
+ textColor: layer.style.textColor,
185
+ alignment: layer.style.alignment,
186
+ lineHeight: layer.style.lineHeight,
187
+ letterSpacing: layer.style.kerning
188
+ };
189
+ }
190
+
191
+ // ========== 透明度 ==========
192
+ if (layer.style.opacity !== undefined && layer.style.opacity !== 1) {
193
+ result.opacity = layer.style.opacity;
194
+ }
195
+
196
+ // ========== 模糊效果 ==========
197
+ if (layer.style.blur?.enabled) {
198
+ result.blur = {
199
+ type: layer.style.blur.blurType,
200
+ radius: layer.style.blur.radius
201
+ };
202
+ }
203
+
204
+ return result;
205
+ }
206
+
207
+ /**
208
+ * 递归测量整个画板
209
+ */
210
+ function measureArtboard(artboard) {
211
+ const results = [];
212
+
213
+ function traverse(layer, depth = 0) {
214
+ if (layer.hidden) return;
215
+
216
+ const data = measureLayerComplete(layer);
217
+ data.depth = depth;
218
+ results.push(data);
219
+
220
+ // 递归子图层
221
+ if (layer.layers) {
222
+ layer.layers.forEach(child => traverse(child, depth + 1));
223
+ }
224
+
225
+ // Symbol 展开图层
226
+ if (layer.expandedLayers) {
227
+ layer.expandedLayers.forEach(child => traverse(child, depth + 1));
228
+ }
229
+ }
230
+
231
+ traverse(artboard);
232
+ return results;
233
+ }
234
+
235
+ // 使用示例
236
+ const doc = sketch.getSelectedDocument();
237
+ const artboard = doc.selectedLayers.layers[0];
238
+ const measurements = measureArtboard(artboard);
239
+ console.log(JSON.stringify(measurements, null, 2));
240
+ ```
241
+
242
+ ---
243
+
244
+ ## 🎨 颜色转换
245
+
246
+ ### Sketch 颜色到各框架
247
+
248
+ ```javascript
249
+ /**
250
+ * 将 Sketch 颜色转换为各框架格式
251
+ * @param {string} sketchColor - Sketch 颜色格式 "#rrggbbaa"
252
+ * @param {string} framework - 目标框架
253
+ */
254
+ function convertColor(sketchColor, framework) {
255
+ // Sketch 格式: #rrggbbaa
256
+ const r = parseInt(sketchColor.slice(1, 3), 16);
257
+ const g = parseInt(sketchColor.slice(3, 5), 16);
258
+ const b = parseInt(sketchColor.slice(5, 7), 16);
259
+ const a = parseInt(sketchColor.slice(7, 9), 16) / 255;
260
+
261
+ switch (framework) {
262
+ case 'flutter':
263
+ // Flutter: Color(0xAARRGGBB)
264
+ const flutterHex = sketchColor.slice(7, 9) + sketchColor.slice(1, 7);
265
+ return `Color(0x${flutterHex.toUpperCase()})`;
266
+
267
+ case 'css':
268
+ // CSS: rgba(r, g, b, a)
269
+ if (a === 1) {
270
+ return sketchColor.slice(0, 7);
271
+ }
272
+ return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`;
273
+
274
+ case 'react-native':
275
+ // React Native: '#RRGGBBAA' 或 'rgba(...)'
276
+ if (a === 1) {
277
+ return `'${sketchColor.slice(0, 7)}'`;
278
+ }
279
+ return `'rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})'`;
280
+
281
+ default:
282
+ return sketchColor;
283
+ }
284
+ }
285
+
286
+ // 渐变转换
287
+ function convertGradient(gradient, framework) {
288
+ const colors = gradient.stops.map(s =>
289
+ convertColor(s.color, framework)
290
+ );
291
+
292
+ switch (framework) {
293
+ case 'flutter':
294
+ return `LinearGradient(
295
+ begin: Alignment(${gradient.from.x * 2 - 1}, ${gradient.from.y * 2 - 1}),
296
+ end: Alignment(${gradient.to.x * 2 - 1}, ${gradient.to.y * 2 - 1}),
297
+ colors: [${colors.join(', ')}],
298
+ )`;
299
+
300
+ case 'css':
301
+ const angle = Math.atan2(
302
+ gradient.to.y - gradient.from.y,
303
+ gradient.to.x - gradient.from.x
304
+ ) * 180 / Math.PI + 90;
305
+ return `linear-gradient(${angle}deg, ${colors.join(', ')})`;
306
+
307
+ default:
308
+ return colors;
309
+ }
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## ✅ 使用检查清单
316
+
317
+ ### 开始测量前
318
+
319
+ - [ ] 确认选中正确的画板/Frame
320
+ - [ ] 检查是否有隐藏图层需要测量
321
+ - [ ] 确认设计稿是最新版本
322
+
323
+ ### 测量过程中
324
+
325
+ - [ ] 对每个颜色检查 `fillType` 是否为 Gradient
326
+ - [ ] 阴影检查是否有多重阴影
327
+ - [ ] 圆角检查四角是否相同
328
+ - [ ] 文字检查行高是否有定义
329
+
330
+ ### 测量完成后
331
+
332
+ - [ ] 与设计稿视觉对比
333
+ - [ ] 渐变色验证(最容易出错)
334
+ - [ ] 透明度验证
335
+ - [ ] 响应式适配验证
336
+
337
+ ---
338
+
339
+ ## 📚 相关资源
340
+
341
+ - [Sketch JavaScript API 文档](https://developer.sketch.com/reference/api/)
342
+ - [设计稿还原规范](../workflows/design-restoration.md)
343
+ - [颜色系统规范](../color-system.md)
344
+
345
+ ---
346
+
347
+ **维护者**: MTA工作室
348
+ **创建日期**: 2026-01-20
349
+ **最后更新**: 2026-01-20
@@ -0,0 +1,256 @@
1
+ # 跨框架样式语法映射表
2
+
3
+ > 设计稿参数到各框架代码的转换参考
4
+
5
+ ## 📋 概述
6
+
7
+ 本文档提供设计稿参数到不同框架(Flutter、Vue/CSS、React Native、小程序)的语法映射,用于设计稿还原时的快速查阅。
8
+
9
+ ---
10
+
11
+ ## 📐 尺寸与布局
12
+
13
+ ### 宽高
14
+
15
+ | 设计稿 | Flutter | Vue/CSS | React Native | 小程序 |
16
+ |--------|---------|---------|--------------|--------|
17
+ | width: 100px | `width: 100` | `width: 100px` | `width: 100` | `width: 200rpx` |
18
+ | height: 50px | `height: 50` | `height: 50px` | `height: 50` | `height: 100rpx` |
19
+ | 100% 宽度 | `double.infinity` | `width: 100%` | `width: '100%'` | `width: 100%` |
20
+
21
+ ### 内边距 (Padding)
22
+
23
+ | 设计稿 | Flutter | Vue/CSS | React Native | 小程序 |
24
+ |--------|---------|---------|--------------|--------|
25
+ | 全部 16px | `padding: EdgeInsets.all(16)` | `padding: 16px` | `padding: 16` | `padding: 32rpx` |
26
+ | 水平 16px | `padding: EdgeInsets.symmetric(horizontal: 16)` | `padding: 0 16px` | `paddingHorizontal: 16` | `padding: 0 32rpx` |
27
+ | 垂直 12px | `padding: EdgeInsets.symmetric(vertical: 12)` | `padding: 12px 0` | `paddingVertical: 12` | `padding: 24rpx 0` |
28
+ | 上20 右16 下12 左16 | `padding: EdgeInsets.fromLTRB(16, 20, 16, 12)` | `padding: 20px 16px 12px 16px` | `paddingTop: 20, ...` | `padding: 40rpx 32rpx 24rpx` |
29
+
30
+ ### 外边距 (Margin)
31
+
32
+ | 设计稿 | Flutter | Vue/CSS | React Native | 小程序 |
33
+ |--------|---------|---------|--------------|--------|
34
+ | 底部 16px | `margin: EdgeInsets.only(bottom: 16)` | `margin-bottom: 16px` | `marginBottom: 16` | `margin-bottom: 32rpx` |
35
+ | 水平居中 | `Container(alignment: Alignment.center)` | `margin: 0 auto` | `alignSelf: 'center'` | `margin: 0 auto` |
36
+
37
+ ---
38
+
39
+ ## 🎨 颜色
40
+
41
+ ### 纯色
42
+
43
+ | 设计稿 | Flutter | Vue/CSS | React Native |
44
+ |--------|---------|---------|--------------|
45
+ | #1C2B45 | `Color(0xFF1C2B45)` | `#1C2B45` | `'#1C2B45'` |
46
+ | #1C2B45 50%透明 | `Color(0x801C2B45)` | `rgba(28,43,69,0.5)` | `'rgba(28,43,69,0.5)'` |
47
+
48
+ ### 透明度换算
49
+
50
+ | 透明度 | 十六进制 | 用法示例 |
51
+ |--------|---------|---------|
52
+ | 100% | FF | `0xFF1C2B45` |
53
+ | 90% | E6 | `0xE61C2B45` |
54
+ | 80% | CC | `0xCC1C2B45` |
55
+ | 70% | B3 | `0xB31C2B45` |
56
+ | 60% | 99 | `0x991C2B45` |
57
+ | 50% | 80 | `0x801C2B45` |
58
+ | 40% | 66 | `0x661C2B45` |
59
+ | 30% | 4D | `0x4D1C2B45` |
60
+ | 20% | 33 | `0x331C2B45` |
61
+ | 15% | 26 | `0x261C2B45` |
62
+ | 12% | 1F | `0x1F1C2B45` |
63
+ | 10% | 1A | `0x1A1C2B45` |
64
+ | 5% | 0D | `0x0D1C2B45` |
65
+ | 0% | 00 | `0x001C2B45` |
66
+
67
+ ### 渐变色
68
+
69
+ **设计稿**: 从 #E01020 到 #B8101A,左上到右下
70
+
71
+ ```dart
72
+ // Flutter
73
+ LinearGradient(
74
+ begin: Alignment.topLeft,
75
+ end: Alignment.bottomRight,
76
+ colors: [Color(0xFFE01020), Color(0xFFB8101A)],
77
+ )
78
+ ```
79
+
80
+ ```css
81
+ /* CSS */
82
+ background: linear-gradient(135deg, #E01020 0%, #B8101A 100%);
83
+ ```
84
+
85
+ ```javascript
86
+ // React Native (需要 react-native-linear-gradient)
87
+ <LinearGradient
88
+ colors={['#E01020', '#B8101A']}
89
+ start={{x: 0, y: 0}}
90
+ end={{x: 1, y: 1}}
91
+ />
92
+ ```
93
+
94
+ ```css
95
+ /* 小程序 */
96
+ background: linear-gradient(135deg, #E01020, #B8101A);
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 📦 圆角
102
+
103
+ | 设计稿 | Flutter | Vue/CSS | React Native |
104
+ |--------|---------|---------|--------------|
105
+ | 全部 12px | `borderRadius: BorderRadius.circular(12)` | `border-radius: 12px` | `borderRadius: 12` |
106
+ | 只有顶部 12px | `borderRadius: BorderRadius.vertical(top: Radius.circular(12))` | `border-radius: 12px 12px 0 0` | `borderTopLeftRadius: 12, borderTopRightRadius: 12` |
107
+ | 各角不同 [8,12,16,20] | `borderRadius: BorderRadius.only(topLeft: Radius.circular(8), ...)` | `border-radius: 8px 12px 16px 20px` | 逐角设置 |
108
+
109
+ ---
110
+
111
+ ## 🌑 阴影
112
+
113
+ ### 单一阴影
114
+
115
+ **设计稿**: x=8, y=8, blur=20, color=#1C2B45 15%
116
+
117
+ ```dart
118
+ // Flutter
119
+ BoxShadow(
120
+ color: Color(0x261C2B45), // 15% = 0x26
121
+ blurRadius: 20,
122
+ offset: Offset(8, 8),
123
+ spreadRadius: 0,
124
+ )
125
+ ```
126
+
127
+ ```css
128
+ /* CSS */
129
+ box-shadow: 8px 8px 20px 0px rgba(28, 43, 69, 0.15);
130
+ ```
131
+
132
+ ```javascript
133
+ // React Native
134
+ shadowColor: '#1C2B45',
135
+ shadowOffset: { width: 8, height: 8 },
136
+ shadowOpacity: 0.15,
137
+ shadowRadius: 20,
138
+ elevation: 8, // Android
139
+ ```
140
+
141
+ ### 新拟态双阴影
142
+
143
+ **设计稿**: 右下深色 + 左上浅色
144
+
145
+ ```dart
146
+ // Flutter
147
+ boxShadow: [
148
+ // 右下阴影(深色)
149
+ BoxShadow(
150
+ color: Color(0x261C2B45), // 15%
151
+ blurRadius: 20,
152
+ offset: Offset(8, 8),
153
+ ),
154
+ // 左上高光(浅色)
155
+ BoxShadow(
156
+ color: Color(0xE6FFFFFF), // 90%
157
+ blurRadius: 20,
158
+ offset: Offset(-8, -8),
159
+ ),
160
+ ]
161
+ ```
162
+
163
+ ```css
164
+ /* CSS */
165
+ box-shadow:
166
+ 8px 8px 20px rgba(28, 43, 69, 0.15),
167
+ -8px -8px 20px rgba(255, 255, 255, 0.9);
168
+ ```
169
+
170
+ ---
171
+
172
+ ## ✏️ 字体
173
+
174
+ ### 基础属性
175
+
176
+ | 设计稿 | Flutter | Vue/CSS | React Native |
177
+ |--------|---------|---------|--------------|
178
+ | 字号 16px | `fontSize: 16` | `font-size: 16px` | `fontSize: 16` |
179
+ | 字重 SemiBold(600) | `fontWeight: FontWeight.w600` | `font-weight: 600` | `fontWeight: '600'` |
180
+ | 字重 Bold(700) | `fontWeight: FontWeight.bold` | `font-weight: bold` | `fontWeight: 'bold'` |
181
+ | 行高 24px (字号16) | `height: 1.5` (比例!) | `line-height: 24px` | `lineHeight: 24` |
182
+ | 字间距 0.5px | `letterSpacing: 0.5` | `letter-spacing: 0.5px` | `letterSpacing: 0.5` |
183
+
184
+ ### 字重映射
185
+
186
+ | 名称 | 数值 | Flutter | CSS |
187
+ |------|------|---------|-----|
188
+ | Thin | 100 | `FontWeight.w100` | `font-weight: 100` |
189
+ | ExtraLight | 200 | `FontWeight.w200` | `font-weight: 200` |
190
+ | Light | 300 | `FontWeight.w300` | `font-weight: 300` |
191
+ | Regular | 400 | `FontWeight.w400` / `FontWeight.normal` | `font-weight: 400` |
192
+ | Medium | 500 | `FontWeight.w500` | `font-weight: 500` |
193
+ | SemiBold | 600 | `FontWeight.w600` | `font-weight: 600` |
194
+ | Bold | 700 | `FontWeight.w700` / `FontWeight.bold` | `font-weight: 700` |
195
+ | ExtraBold | 800 | `FontWeight.w800` | `font-weight: 800` |
196
+ | Black | 900 | `FontWeight.w900` | `font-weight: 900` |
197
+
198
+ ### ⚠️ Flutter 行高注意
199
+
200
+ Flutter 的 `TextStyle.height` 是**比例值**,不是像素值!
201
+
202
+ ```dart
203
+ // 设计稿:字号 16px,行高 24px
204
+ TextStyle(
205
+ fontSize: 16,
206
+ height: 1.5, // 24 / 16 = 1.5
207
+ )
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 🔲 边框
213
+
214
+ | 设计稿 | Flutter | Vue/CSS | React Native |
215
+ |--------|---------|---------|--------------|
216
+ | 1px #E5E5E5 实线 | `border: Border.all(color: Color(0xFFE5E5E5), width: 1)` | `border: 1px solid #E5E5E5` | `borderWidth: 1, borderColor: '#E5E5E5'` |
217
+ | 只有底边框 | `border: Border(bottom: BorderSide(...))` | `border-bottom: 1px solid #E5E5E5` | `borderBottomWidth: 1, ...` |
218
+
219
+ ---
220
+
221
+ ## 📱 单位换算
222
+
223
+ ### 小程序 rpx
224
+
225
+ 小程序设计稿通常基于 750px 宽度:
226
+
227
+ ```
228
+ rpx = px * 2
229
+
230
+ 设计稿 16px → 小程序 32rpx
231
+ 设计稿 100px → 小程序 200rpx
232
+ ```
233
+
234
+ ### React Native
235
+
236
+ React Native 使用逻辑像素(dp/pt),通常与设计稿 1:1:
237
+
238
+ ```javascript
239
+ // 设计稿 16px
240
+ style={{ fontSize: 16 }}
241
+ ```
242
+
243
+ 如需适配,使用 `PixelRatio` 或响应式方案。
244
+
245
+ ---
246
+
247
+ ## 📚 相关规范
248
+
249
+ - [设计稿还原规范](./workflows/design-restoration.md)
250
+ - [Sketch MCP 最佳实践](./mcp-tools/sketch-mcp.md)
251
+
252
+ ---
253
+
254
+ **维护者**: MTA工作室
255
+ **创建日期**: 2026-01-20
256
+ **适用版本**: 通用
@@ -0,0 +1,363 @@
1
+ # 设计稿还原规范 (Design Restoration Standard)
2
+
3
+ > 通用规范 - 适用于所有视觉化开发场景(跨平台/前端/全栈)
4
+
5
+ ## 📋 概述
6
+
7
+ 本规范定义了从设计工具(Sketch、Figma、Adobe XD 等)还原 UI 到代码的标准流程和注意事项,确保在不同框架(Flutter、Vue、React、小程序等)中都能准确还原设计稿。
8
+
9
+ **适用场景**:
10
+ - 使用 Sketch MCP、Figma MCP 等工具测量设计稿
11
+ - 根据设计图片手动还原 UI
12
+ - 跨平台/多框架项目的样式统一
13
+
14
+ ---
15
+
16
+ ## 🎯 核心原则
17
+
18
+ ### 1. 测量优先级
19
+
20
+ 按以下优先级获取设计参数:
21
+
22
+ 1. **MCP 工具直接读取** - 最准确,但需注意 API 陷阱
23
+ 2. **设计工具检查器** - 设计师侧边栏的属性面板
24
+ 3. **标注插件导出** - 如 Sketch Measure、Zeplin
25
+ 4. **肉眼估算** - 最后手段,需反复对比验证
26
+
27
+ ### 2. 验证闭环
28
+
29
+ ```
30
+ 测量 → 编码 → 截图对比 → 微调 → 确认
31
+ ```
32
+
33
+ 每个元素都必须完成这个闭环。
34
+
35
+ ### 3. 原子化记录
36
+
37
+ 将设计参数按类别记录,便于复用和维护。
38
+
39
+ ---
40
+
41
+ ## 📐 测量要素清单
42
+
43
+ ### 必须测量的参数
44
+
45
+ | 类别 | 参数 | 说明 |
46
+ |------|------|------|
47
+ | **尺寸** | width, height | 精确到像素 |
48
+ | **位置** | x, y, margin, padding | 相对于父容器 |
49
+ | **颜色** | 填充色、边框色、阴影色 | ⚠️ 注意渐变 |
50
+ | **圆角** | borderRadius | 四角可能不同 |
51
+ | **字体** | family, size, weight, height | 行高很重要 |
52
+ | **阴影** | color, blur, offset, spread | 多重阴影 |
53
+ | **边框** | color, width, style | 虚线/实线 |
54
+
55
+ ### ⚠️ 常见陷阱
56
+
57
+ #### 1. 渐变色读取错误
58
+
59
+ **问题**:API 返回渐变的"代表色"而非实际渐变定义
60
+
61
+ ```javascript
62
+ // ❌ 错误方式
63
+ layer.style.fills[0].color // 返回错误的单色
64
+
65
+ // ✅ 正确方式
66
+ if (fill.fillType === 'Gradient') {
67
+ fill.gradient.stops.map(s => s.color) // 获取所有色标
68
+ }
69
+ ```
70
+
71
+ #### 2. 透明度丢失
72
+
73
+ **问题**:颜色值可能不包含透明度信息
74
+
75
+ ```javascript
76
+ // 确保读取完整的 RGBA
77
+ color: "#1C2B4580" // 包含透明度
78
+ // 而不是
79
+ color: "#1C2B45" // 丢失透明度
80
+ ```
81
+
82
+ #### 3. 字体权重映射
83
+
84
+ 设计稿中的字重名称需要映射为数值:
85
+
86
+ | 设计稿名称 | 数值 | Flutter | CSS |
87
+ |-----------|------|---------|-----|
88
+ | Thin | 100 | FontWeight.w100 | font-weight: 100 |
89
+ | Light | 300 | FontWeight.w300 | font-weight: 300 |
90
+ | Regular | 400 | FontWeight.w400 | font-weight: 400 |
91
+ | Medium | 500 | FontWeight.w500 | font-weight: 500 |
92
+ | SemiBold | 600 | FontWeight.w600 | font-weight: 600 |
93
+ | Bold | 700 | FontWeight.w700 | font-weight: 700 |
94
+
95
+ #### 4. 行高计算差异
96
+
97
+ 不同框架对行高的处理不同:
98
+
99
+ ```
100
+ 设计稿行高: 24px, 字号: 16px
101
+
102
+ Flutter: height = 24 / 16 = 1.5 (比例值)
103
+ CSS: line-height: 24px 或 line-height: 1.5
104
+ 小程序: line-height: 24px
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 🎨 颜色规范
110
+
111
+ ### 颜色提取完整流程
112
+
113
+ ```javascript
114
+ function extractColor(fill) {
115
+ if (!fill || !fill.enabled) return null;
116
+
117
+ switch (fill.fillType) {
118
+ case 'Color':
119
+ return { type: 'solid', color: fill.color };
120
+
121
+ case 'Gradient':
122
+ return {
123
+ type: 'gradient',
124
+ gradientType: fill.gradient.gradientType, // Linear/Radial/Angular
125
+ direction: {
126
+ from: fill.gradient.from, // { x: 0, y: 0 }
127
+ to: fill.gradient.to // { x: 1, y: 1 }
128
+ },
129
+ stops: fill.gradient.stops.map(s => ({
130
+ position: s.position, // 0 ~ 1
131
+ color: s.color
132
+ }))
133
+ };
134
+
135
+ case 'Pattern':
136
+ return { type: 'pattern', image: fill.pattern?.image };
137
+
138
+ default:
139
+ return null;
140
+ }
141
+ }
142
+ ```
143
+
144
+ ### 渐变方向映射
145
+
146
+ | 设计稿方向 | Flutter | CSS |
147
+ |-----------|---------|-----|
148
+ | 左上→右下 | Alignment.topLeft → bottomRight | to bottom right |
149
+ | 上→下 | Alignment.topCenter → bottomCenter | to bottom |
150
+ | 左→右 | Alignment.centerLeft → centerRight | to right |
151
+
152
+ ### 颜色透明度表示
153
+
154
+ ```dart
155
+ // Flutter - 使用 0x 前缀
156
+ Color(0x801C2B45) // 50% 透明度
157
+
158
+ // CSS - 使用 rgba 或 8位十六进制
159
+ rgba(28, 43, 69, 0.5)
160
+ #1C2B4580
161
+
162
+ // 小程序 - 同 CSS
163
+ ```
164
+
165
+ ---
166
+
167
+ ## 📦 框架语法映射
168
+
169
+ ### 容器/盒模型
170
+
171
+ | 属性 | Flutter | Vue/CSS | React Native | 小程序 |
172
+ |------|---------|---------|--------------|--------|
173
+ | 宽度 | `width: 100` | `width: 100px` | `width: 100` | `width: 100rpx` |
174
+ | 高度 | `height: 100` | `height: 100px` | `height: 100` | `height: 100rpx` |
175
+ | 内边距 | `padding: EdgeInsets.all(16)` | `padding: 16px` | `padding: 16` | `padding: 32rpx` |
176
+ | 外边距 | `margin: EdgeInsets.only(top: 8)` | `margin-top: 8px` | `marginTop: 8` | `margin-top: 16rpx` |
177
+ | 圆角 | `borderRadius: BorderRadius.circular(12)` | `border-radius: 12px` | `borderRadius: 12` | `border-radius: 24rpx` |
178
+
179
+ ### 颜色
180
+
181
+ | 场景 | Flutter | Vue/CSS | React Native |
182
+ |------|---------|---------|--------------|
183
+ | 纯色 | `Color(0xFF1C2B45)` | `#1C2B45` | `'#1C2B45'` |
184
+ | 透明色 | `Color(0x801C2B45)` | `rgba(28,43,69,0.5)` | `'rgba(28,43,69,0.5)'` |
185
+ | 渐变 | `LinearGradient(colors: [...])` | `linear-gradient(...)` | `LinearGradient` |
186
+
187
+ ### 阴影
188
+
189
+ ```dart
190
+ // Flutter
191
+ BoxShadow(
192
+ color: Color(0x261C2B45),
193
+ blurRadius: 20,
194
+ offset: Offset(8, 8),
195
+ spreadRadius: 0,
196
+ )
197
+ ```
198
+
199
+ ```css
200
+ /* CSS */
201
+ box-shadow: 8px 8px 20px 0px rgba(28, 43, 69, 0.15);
202
+ ```
203
+
204
+ ```javascript
205
+ // React Native
206
+ shadowColor: '#1C2B45',
207
+ shadowOffset: { width: 8, height: 8 },
208
+ shadowOpacity: 0.15,
209
+ shadowRadius: 20,
210
+ elevation: 8, // Android
211
+ ```
212
+
213
+ ### 字体
214
+
215
+ | 属性 | Flutter | CSS | React Native |
216
+ |------|---------|-----|--------------|
217
+ | 字号 | `fontSize: 16` | `font-size: 16px` | `fontSize: 16` |
218
+ | 字重 | `fontWeight: FontWeight.w600` | `font-weight: 600` | `fontWeight: '600'` |
219
+ | 行高 | `height: 1.5` (比例) | `line-height: 24px` | `lineHeight: 24` |
220
+ | 颜色 | `color: Color(0xFF1C2B45)` | `color: #1C2B45` | `color: '#1C2B45'` |
221
+
222
+ ---
223
+
224
+ ## 🔧 MCP 工具使用规范
225
+
226
+ ### Sketch MCP
227
+
228
+ #### 正确的测量脚本模板
229
+
230
+ ```javascript
231
+ const sketch = require('sketch');
232
+ const doc = sketch.getSelectedDocument();
233
+ const artboard = doc.selectedLayers.layers[0];
234
+
235
+ function measureLayer(layer) {
236
+ const result = {
237
+ name: layer.name,
238
+ type: layer.type,
239
+ frame: {
240
+ x: Math.round(layer.frame.x),
241
+ y: Math.round(layer.frame.y),
242
+ width: Math.round(layer.frame.width),
243
+ height: Math.round(layer.frame.height),
244
+ },
245
+ };
246
+
247
+ // 样式
248
+ if (layer.style) {
249
+ // 填充色 - 正确处理渐变
250
+ if (layer.style.fills?.length > 0) {
251
+ result.fills = layer.style.fills
252
+ .filter(f => f.enabled)
253
+ .map(f => extractColor(f));
254
+ }
255
+
256
+ // 边框
257
+ if (layer.style.borders?.length > 0) {
258
+ result.borders = layer.style.borders
259
+ .filter(b => b.enabled)
260
+ .map(b => ({
261
+ color: b.color,
262
+ thickness: b.thickness,
263
+ }));
264
+ }
265
+
266
+ // 阴影
267
+ if (layer.style.shadows?.length > 0) {
268
+ result.shadows = layer.style.shadows
269
+ .filter(s => s.enabled)
270
+ .map(s => ({
271
+ color: s.color,
272
+ blur: s.blur,
273
+ x: s.x,
274
+ y: s.y,
275
+ spread: s.spread,
276
+ }));
277
+ }
278
+
279
+ // 圆角
280
+ if (layer.style.corners) {
281
+ result.cornerRadius = layer.style.corners.radii;
282
+ }
283
+ }
284
+
285
+ // 文字特有属性
286
+ if (layer.type === 'Text') {
287
+ result.text = {
288
+ content: layer.text,
289
+ fontSize: layer.style.fontSize,
290
+ fontFamily: layer.style.fontFamily,
291
+ fontWeight: layer.style.fontWeight,
292
+ textColor: layer.style.textColor,
293
+ alignment: layer.style.alignment,
294
+ lineHeight: layer.style.lineHeight,
295
+ };
296
+ }
297
+
298
+ return result;
299
+ }
300
+ ```
301
+
302
+ ### Figma MCP(预留)
303
+
304
+ ```javascript
305
+ // Figma API 颜色读取
306
+ function getFigmaColor(paint) {
307
+ if (paint.type === 'SOLID') {
308
+ return {
309
+ type: 'solid',
310
+ color: rgbaToHex(paint.color, paint.opacity),
311
+ };
312
+ } else if (paint.type === 'GRADIENT_LINEAR') {
313
+ return {
314
+ type: 'gradient',
315
+ stops: paint.gradientStops.map(s => ({
316
+ position: s.position,
317
+ color: rgbaToHex(s.color),
318
+ })),
319
+ };
320
+ }
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ## ✅ 还原检查清单
327
+
328
+ ### 每个元素必查
329
+
330
+ - [ ] 尺寸是否精确匹配
331
+ - [ ] 位置/间距是否正确
332
+ - [ ] 颜色是否正确(特别注意渐变)
333
+ - [ ] 圆角是否正确
334
+ - [ ] 阴影效果是否还原
335
+ - [ ] 字体样式是否匹配
336
+
337
+ ### 整体验收
338
+
339
+ - [ ] 与设计稿截图叠加对比
340
+ - [ ] 不同屏幕尺寸下的适配
341
+ - [ ] 交互状态(hover、active、disabled)
342
+ - [ ] 暗黑模式(如有)
343
+
344
+ ---
345
+
346
+ ## 📚 相关规范
347
+
348
+ - [Sketch MCP 最佳实践](./mcp-tools/sketch-mcp.md)
349
+ - [Figma MCP 最佳实践](./mcp-tools/figma-mcp.md)(计划中)
350
+ - [颜色系统规范](./color-system.md)(计划中)
351
+
352
+ ---
353
+
354
+ ## 🔄 版本历史
355
+
356
+ | 版本 | 日期 | 更新内容 |
357
+ |------|------|---------|
358
+ | v1.0 | 2026-01-20 | 初始版本,包含渐变色读取问题解决方案 |
359
+
360
+ ---
361
+
362
+ **维护者**: MTA工作室
363
+ **创建日期**: 2026-01-20