mta-mcp 1.0.2 → 1.0.3
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/package.json +1 -1
- 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/package.json
CHANGED
package/templates/README.md
CHANGED
|
@@ -70,6 +70,11 @@ templates/
|
|
|
70
70
|
│ ├── api-layer/
|
|
71
71
|
│ └── themes/
|
|
72
72
|
├── react/ # React 项目模板(预留)
|
|
73
|
+
├── design-measurement/ # 设计稿测量模板 ✅ 新增
|
|
74
|
+
│ ├── README.md # 使用指南
|
|
75
|
+
│ ├── component-measurement.js # 组件测量
|
|
76
|
+
│ ├── style-extraction.js # 样式提取
|
|
77
|
+
│ └── gap-measurement.js # 间距测量
|
|
73
78
|
└── common/ # 跨框架通用模板
|
|
74
79
|
└── types/ # TypeScript 类型定义 ✅
|
|
75
80
|
```
|
|
@@ -108,6 +113,15 @@ templates/
|
|
|
108
113
|
|------|--------|------|------|
|
|
109
114
|
| `common/types` | types, 类型 | TypeScript 通用类型 | ✅ 可用 |
|
|
110
115
|
|
|
116
|
+
### 设计稿测量模板 🆕
|
|
117
|
+
|
|
118
|
+
| 模板 | 关键词 | 说明 | 状态 |
|
|
119
|
+
|------|--------|------|------|
|
|
120
|
+
| `design-measurement` | 测量, measure | 设计稿测量脚本合集 | ✅ 可用 |
|
|
121
|
+
| `component-measurement.js` | 组件, 边距 | 组件尺寸+相对边距 | ✅ 可用 |
|
|
122
|
+
| `style-extraction.js` | 样式, 渐变, 阴影 | 完整样式提取 | ✅ 可用 |
|
|
123
|
+
| `gap-measurement.js` | 间距, gap | 同级元素间距 | ✅ 可用 |
|
|
124
|
+
|
|
111
125
|
---
|
|
112
126
|
|
|
113
127
|
## 🔧 模板规范
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 🎨 设计稿测量模板
|
|
2
|
+
|
|
3
|
+
> 适用于 Sketch MCP、Figma 等设计工具的测量脚本模板
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🤖 Agent 使用指令
|
|
8
|
+
|
|
9
|
+
**触发词**:
|
|
10
|
+
- "测量选中的组件"
|
|
11
|
+
- "获取设计稿边距"
|
|
12
|
+
- "分析图层样式"
|
|
13
|
+
- "获取渐变色参数"
|
|
14
|
+
|
|
15
|
+
**自动匹配模板**:
|
|
16
|
+
|
|
17
|
+
| 场景 | 对应模板 |
|
|
18
|
+
|------|----------|
|
|
19
|
+
| 分析整体结构 | `structure-overview.js` |
|
|
20
|
+
| 测量单个组件 | `component-measurement.js` |
|
|
21
|
+
| 计算间距关系 | `gap-measurement.js` |
|
|
22
|
+
| 提取样式参数 | `style-extraction.js` |
|
|
23
|
+
| 分析嵌套Symbol | `nested-symbol-analysis.js` |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 📁 模板文件
|
|
28
|
+
|
|
29
|
+
### 1. structure-overview.js
|
|
30
|
+
用途:分析选中元素的整体结构和层级
|
|
31
|
+
|
|
32
|
+
### 2. component-measurement.js
|
|
33
|
+
用途:测量单个组件的尺寸、边距、内边距(自动计算相对值)
|
|
34
|
+
|
|
35
|
+
### 3. gap-measurement.js
|
|
36
|
+
用途:计算同级元素之间的间距
|
|
37
|
+
|
|
38
|
+
### 4. style-extraction.js
|
|
39
|
+
用途:提取颜色、渐变、阴影、圆角等样式参数
|
|
40
|
+
|
|
41
|
+
### 5. nested-symbol-analysis.js
|
|
42
|
+
用途:递归分析 Symbol 内部嵌套结构
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🔧 使用方式
|
|
47
|
+
|
|
48
|
+
### 方式一:直接调用 MCP 工具
|
|
49
|
+
```
|
|
50
|
+
调用 get_standard_by_id({ id: 'sketch-mcp' }) 获取完整脚本
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 方式二:复制模板脚本
|
|
54
|
+
```javascript
|
|
55
|
+
// 从模板文件中复制脚本到 mcp_sketch_run_code
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## ⚠️ 核心原则
|
|
61
|
+
|
|
62
|
+
1. **测量 = 记录 + 计算**
|
|
63
|
+
- 记录绝对坐标
|
|
64
|
+
- 立即计算相对边距
|
|
65
|
+
- 转换为目标框架可用的值
|
|
66
|
+
|
|
67
|
+
2. **坐标系转换**
|
|
68
|
+
- iOS SafeArea 偏移:44px(刘海屏)/ 20px(非刘海)
|
|
69
|
+
- Android 状态栏:24-48dp
|
|
70
|
+
- Web:通常不需要
|
|
71
|
+
|
|
72
|
+
3. **渐变角度转换**
|
|
73
|
+
- Sketch: from{x,y} → to{x,y}(0-1 范围)
|
|
74
|
+
- CSS: `linear-gradient(angle, ...)`
|
|
75
|
+
- Flutter: `Alignment(x, y)`(-1 到 1 范围)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
**维护者**: MTA工作室
|
|
80
|
+
**版本**: v1.0
|
|
81
|
+
**创建日期**: 2025-01-21
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件测量模板 - 自动计算相对边距
|
|
3
|
+
* 用途:测量单个组件的完整布局参数
|
|
4
|
+
*
|
|
5
|
+
* 使用方式:在 Sketch 中选中目标组件,运行此脚本
|
|
6
|
+
*/
|
|
7
|
+
const sketch = require('sketch')
|
|
8
|
+
|
|
9
|
+
const sel = sketch.getSelectedDocument().selectedLayers.layers
|
|
10
|
+
if (!sel.length) {
|
|
11
|
+
console.log('请先选中一个组件')
|
|
12
|
+
} else {
|
|
13
|
+
const layer = sel[0]
|
|
14
|
+
const parent = layer.parent
|
|
15
|
+
|
|
16
|
+
// 基础信息
|
|
17
|
+
console.log('=== 组件测量结果 ===')
|
|
18
|
+
console.log(`名称: ${layer.name}`)
|
|
19
|
+
console.log(`类型: ${layer.type}`)
|
|
20
|
+
|
|
21
|
+
// 尺寸信息
|
|
22
|
+
console.log(`\n--- 尺寸 ---`)
|
|
23
|
+
console.log(`宽度: ${layer.frame.width}`)
|
|
24
|
+
console.log(`高度: ${layer.frame.height}`)
|
|
25
|
+
|
|
26
|
+
// 绝对坐标(供参考)
|
|
27
|
+
console.log(`\n--- 绝对坐标(供参考)---`)
|
|
28
|
+
console.log(`x: ${layer.frame.x}`)
|
|
29
|
+
console.log(`y: ${layer.frame.y}`)
|
|
30
|
+
|
|
31
|
+
// ⭐ 关键:计算相对边距(可直接使用)
|
|
32
|
+
console.log(`\n--- 相对边距(可直接使用)---`)
|
|
33
|
+
if (parent && parent.frame) {
|
|
34
|
+
const paddingLeft = layer.frame.x - parent.frame.x
|
|
35
|
+
const paddingTop = layer.frame.y - parent.frame.y
|
|
36
|
+
const paddingRight = (parent.frame.x + parent.frame.width) - (layer.frame.x + layer.frame.width)
|
|
37
|
+
const paddingBottom = (parent.frame.y + parent.frame.height) - (layer.frame.y + layer.frame.height)
|
|
38
|
+
|
|
39
|
+
console.log(`距父容器左边: ${paddingLeft}`)
|
|
40
|
+
console.log(`距父容器顶部: ${paddingTop}`)
|
|
41
|
+
console.log(`距父容器右边: ${paddingRight}`)
|
|
42
|
+
console.log(`距父容器底部: ${paddingBottom}`)
|
|
43
|
+
console.log(`父容器: ${parent.name} (${parent.frame.width}x${parent.frame.height})`)
|
|
44
|
+
} else {
|
|
45
|
+
console.log('无父容器或为顶层元素')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 框架转换提示
|
|
49
|
+
console.log(`\n--- 框架转换 ---`)
|
|
50
|
+
console.log(`Flutter: padding: EdgeInsets.fromLTRB(${layer.frame.x}, ${layer.frame.y}, 0, 0)`)
|
|
51
|
+
console.log(`CSS: padding: ${layer.frame.y}px ${layer.frame.x}px;`)
|
|
52
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 间距测量模板 - 计算同级元素间距
|
|
3
|
+
* 用途:测量多个元素之间的垂直/水平间距
|
|
4
|
+
*
|
|
5
|
+
* 使用方式:在 Sketch 中选中多个同级元素,运行此脚本
|
|
6
|
+
*/
|
|
7
|
+
const sketch = require('sketch')
|
|
8
|
+
|
|
9
|
+
const sel = sketch.getSelectedDocument().selectedLayers.layers
|
|
10
|
+
if (sel.length < 2) {
|
|
11
|
+
console.log('请选中至少2个元素来计算间距')
|
|
12
|
+
} else {
|
|
13
|
+
// 按 Y 坐标排序(从上到下)
|
|
14
|
+
const sortedByY = [...sel].sort((a, b) => a.frame.y - b.frame.y)
|
|
15
|
+
|
|
16
|
+
console.log('=== 垂直间距测量 ===')
|
|
17
|
+
console.log(`选中元素数量: ${sel.length}`)
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < sortedByY.length - 1; i++) {
|
|
20
|
+
const current = sortedByY[i]
|
|
21
|
+
const next = sortedByY[i + 1]
|
|
22
|
+
|
|
23
|
+
// 计算垂直间距 = 下一个元素顶部 - 当前元素底部
|
|
24
|
+
const gap = next.frame.y - (current.frame.y + current.frame.height)
|
|
25
|
+
|
|
26
|
+
console.log(`\n${current.name} ↓ ${next.name}`)
|
|
27
|
+
console.log(` 垂直间距: ${gap}px`)
|
|
28
|
+
console.log(` CSS: margin-bottom: ${gap}px; 或 gap: ${gap}px;`)
|
|
29
|
+
console.log(` Flutter: SizedBox(height: ${gap})`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 按 X 坐标排序(从左到右)
|
|
33
|
+
const sortedByX = [...sel].sort((a, b) => a.frame.x - b.frame.x)
|
|
34
|
+
|
|
35
|
+
console.log('\n=== 水平间距测量 ===')
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < sortedByX.length - 1; i++) {
|
|
38
|
+
const current = sortedByX[i]
|
|
39
|
+
const next = sortedByX[i + 1]
|
|
40
|
+
|
|
41
|
+
// 计算水平间距 = 下一个元素左边 - 当前元素右边
|
|
42
|
+
const gap = next.frame.x - (current.frame.x + current.frame.width)
|
|
43
|
+
|
|
44
|
+
console.log(`\n${current.name} → ${next.name}`)
|
|
45
|
+
console.log(` 水平间距: ${gap}px`)
|
|
46
|
+
console.log(` CSS: margin-right: ${gap}px; 或 gap: ${gap}px;`)
|
|
47
|
+
console.log(` Flutter: SizedBox(width: ${gap})`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 汇总信息
|
|
51
|
+
console.log('\n=== 汇总 ===')
|
|
52
|
+
const verticalGaps = []
|
|
53
|
+
const horizontalGaps = []
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < sortedByY.length - 1; i++) {
|
|
56
|
+
const gap = sortedByY[i + 1].frame.y - (sortedByY[i].frame.y + sortedByY[i].frame.height)
|
|
57
|
+
verticalGaps.push(gap)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < sortedByX.length - 1; i++) {
|
|
61
|
+
const gap = sortedByX[i + 1].frame.x - (sortedByX[i].frame.x + sortedByX[i].frame.width)
|
|
62
|
+
horizontalGaps.push(gap)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const uniqueVGaps = [...new Set(verticalGaps)]
|
|
66
|
+
const uniqueHGaps = [...new Set(horizontalGaps)]
|
|
67
|
+
|
|
68
|
+
if (uniqueVGaps.length === 1) {
|
|
69
|
+
console.log(`垂直间距统一: ${uniqueVGaps[0]}px → 可使用 flex gap 或 Column mainAxisSpacing`)
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`垂直间距不统一: ${verticalGaps.join(', ')} → 需要单独设置`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (uniqueHGaps.length === 1) {
|
|
75
|
+
console.log(`水平间距统一: ${uniqueHGaps[0]}px → 可使用 flex gap 或 Row mainAxisSpacing`)
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`水平间距不统一: ${horizontalGaps.join(', ')} → 需要单独设置`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 样式提取模板 - 完整样式参数
|
|
3
|
+
* 用途:提取颜色、渐变、阴影、圆角等样式
|
|
4
|
+
*
|
|
5
|
+
* 使用方式:在 Sketch 中选中目标元素,运行此脚本
|
|
6
|
+
*/
|
|
7
|
+
const sketch = require('sketch')
|
|
8
|
+
|
|
9
|
+
const sel = sketch.getSelectedDocument().selectedLayers.layers
|
|
10
|
+
if (!sel.length) {
|
|
11
|
+
console.log('请先选中一个元素')
|
|
12
|
+
} else {
|
|
13
|
+
const layer = sel[0]
|
|
14
|
+
const style = layer.style
|
|
15
|
+
|
|
16
|
+
console.log('=== 样式提取结果 ===')
|
|
17
|
+
console.log(`元素: ${layer.name}`)
|
|
18
|
+
|
|
19
|
+
// 圆角
|
|
20
|
+
if (layer.style?.corners) {
|
|
21
|
+
console.log(`\n--- 圆角 ---`)
|
|
22
|
+
console.log(`圆角: ${JSON.stringify(layer.style.corners.radii)}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 填充(颜色/渐变)
|
|
26
|
+
if (style?.fills?.length) {
|
|
27
|
+
console.log(`\n--- 填充 ---`)
|
|
28
|
+
style.fills.forEach((fill, i) => {
|
|
29
|
+
if (fill.fillType === 'Color') {
|
|
30
|
+
console.log(`填充${i}: 纯色 ${fill.color}`)
|
|
31
|
+
// 转换提示
|
|
32
|
+
const hex = fill.color
|
|
33
|
+
console.log(` CSS: background: ${hex};`)
|
|
34
|
+
console.log(` Flutter: Color(0xFF${hex.replace('#', '').toUpperCase()})`)
|
|
35
|
+
} else if (fill.fillType === 'Gradient') {
|
|
36
|
+
console.log(`填充${i}: 渐变`)
|
|
37
|
+
console.log(` 类型: ${fill.gradient.gradientType}`)
|
|
38
|
+
console.log(` 起点: from(${fill.gradient.from.x}, ${fill.gradient.from.y})`)
|
|
39
|
+
console.log(` 终点: to(${fill.gradient.to.x}, ${fill.gradient.to.y})`)
|
|
40
|
+
console.log(` 色标:`)
|
|
41
|
+
fill.gradient.stops.forEach((stop, j) => {
|
|
42
|
+
console.log(` ${j}: ${stop.color} at ${(stop.position * 100).toFixed(0)}%`)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// 计算渐变角度
|
|
46
|
+
const dx = fill.gradient.to.x - fill.gradient.from.x
|
|
47
|
+
const dy = fill.gradient.to.y - fill.gradient.from.y
|
|
48
|
+
const angle = Math.round(Math.atan2(dy, dx) * 180 / Math.PI + 90)
|
|
49
|
+
|
|
50
|
+
// CSS 转换
|
|
51
|
+
const colors = fill.gradient.stops.map(s => `${s.color} ${(s.position * 100).toFixed(0)}%`).join(', ')
|
|
52
|
+
console.log(` CSS: linear-gradient(${angle}deg, ${colors})`)
|
|
53
|
+
|
|
54
|
+
// Flutter 转换
|
|
55
|
+
const fromX = (fill.gradient.from.x * 2 - 1).toFixed(2)
|
|
56
|
+
const fromY = (fill.gradient.from.y * 2 - 1).toFixed(2)
|
|
57
|
+
const toX = (fill.gradient.to.x * 2 - 1).toFixed(2)
|
|
58
|
+
const toY = (fill.gradient.to.y * 2 - 1).toFixed(2)
|
|
59
|
+
console.log(` Flutter: begin: Alignment(${fromX}, ${fromY}), end: Alignment(${toX}, ${toY})`)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 边框
|
|
65
|
+
if (style?.borders?.length) {
|
|
66
|
+
console.log(`\n--- 边框 ---`)
|
|
67
|
+
style.borders.forEach((border, i) => {
|
|
68
|
+
console.log(`边框${i}: ${border.color} 宽度=${border.thickness}`)
|
|
69
|
+
console.log(` CSS: border: ${border.thickness}px solid ${border.color};`)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 阴影
|
|
74
|
+
if (style?.shadows?.length) {
|
|
75
|
+
console.log(`\n--- 阴影 ---`)
|
|
76
|
+
style.shadows.forEach((shadow, i) => {
|
|
77
|
+
console.log(`阴影${i}:`)
|
|
78
|
+
console.log(` 颜色: ${shadow.color}`)
|
|
79
|
+
console.log(` 偏移: x=${shadow.x}, y=${shadow.y}`)
|
|
80
|
+
console.log(` 模糊: ${shadow.blur}`)
|
|
81
|
+
console.log(` 扩展: ${shadow.spread}`)
|
|
82
|
+
console.log(` CSS: box-shadow: ${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.spread}px ${shadow.color};`)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 内阴影
|
|
87
|
+
if (style?.innerShadows?.length) {
|
|
88
|
+
console.log(`\n--- 内阴影 ---`)
|
|
89
|
+
style.innerShadows.forEach((shadow, i) => {
|
|
90
|
+
console.log(`内阴影${i}:`)
|
|
91
|
+
console.log(` 颜色: ${shadow.color}`)
|
|
92
|
+
console.log(` 偏移: x=${shadow.x}, y=${shadow.y}`)
|
|
93
|
+
console.log(` 模糊: ${shadow.blur}`)
|
|
94
|
+
console.log(` CSS: box-shadow: inset ${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.color};`)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 文字样式(如果是文本)
|
|
99
|
+
if (layer.type === 'Text') {
|
|
100
|
+
console.log(`\n--- 文字样式 ---`)
|
|
101
|
+
const textStyle = layer.style
|
|
102
|
+
console.log(`内容: ${layer.text}`)
|
|
103
|
+
console.log(`字体: ${textStyle.fontFamily || 'default'}`)
|
|
104
|
+
console.log(`字号: ${textStyle.fontSize}`)
|
|
105
|
+
console.log(`行高: ${textStyle.lineHeight || 'auto'}`)
|
|
106
|
+
console.log(`颜色: ${textStyle.textColor}`)
|
|
107
|
+
console.log(`对齐: ${textStyle.alignment}`)
|
|
108
|
+
}
|
|
109
|
+
}
|