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.
- package/agents/flutter.agent.md +19 -78
- package/agents/vue3.agent.md +15 -28
- package/agents/wechat-miniprogram.agent.md +23 -0
- package/package.json +1 -1
- package/standards/mcp-tools/sketch-mcp.md +349 -0
- package/standards/syntax-mapping.md +256 -0
- package/standards/workflows/design-restoration.md +363 -0
package/agents/flutter.agent.md
CHANGED
|
@@ -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
|
|
package/agents/vue3.agent.md
CHANGED
|
@@ -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
|
-
> ⚠️
|
|
141
|
+
> ⚠️ 详细规范请调用:`get_standard_by_id({ id: 'design-restoration' })`
|
|
142
142
|
|
|
143
|
-
###
|
|
143
|
+
### 快速提示
|
|
144
144
|
|
|
145
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
@@ -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
|