@szc-ft/mcp-szcd-client 0.35.0 → 0.38.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/build.js +59 -1
- package/agents/platforms.json +7 -0
- package/agents/szcd-component-expert.qoder.md +2 -0
- package/opencode-extension/opencode.json +9 -0
- package/opencode-extension/skills/local-browser-test/SKILL.md +91 -0
- package/opencode-extension/skills/local-browser-test/config.example.yaml +51 -0
- package/opencode-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
- package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
- package/opencode-extension/skills/local-browser-test/lib/config-loader.js +104 -0
- package/opencode-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
- package/opencode-extension/skills/local-browser-test/lib/global-setup.js +72 -0
- package/opencode-extension/skills/local-browser-test/lib/har-collector.js +124 -0
- package/opencode-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
- package/opencode-extension/skills/local-browser-test/lib/projects.js +46 -0
- package/opencode-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/opencode-extension/skills/local-browser-test/lib/redact.js +94 -0
- package/opencode-extension/skills/local-browser-test/lib/report-retain.js +84 -0
- package/opencode-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
- package/opencode-extension/skills/local-browser-test/lib/storage-state.js +111 -0
- package/opencode-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
- package/opencode-extension/skills/local-browser-test/lib/test-plan.js +2 -0
- package/opencode-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
- package/opencode-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
- package/opencode-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.js +226 -2
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/opencode-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
- package/opencode-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
- package/opencode-extension/skills/szcd-upload-zip/SKILL.md +45 -0
- package/package.json +70 -70
- package/qwen-extension/qwen-extension.json +10 -1
- package/qwen-extension/skills/local-browser-test/SKILL.md +91 -0
- package/qwen-extension/skills/local-browser-test/config.example.yaml +51 -0
- package/qwen-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
- package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
- package/qwen-extension/skills/local-browser-test/lib/config-loader.js +104 -0
- package/qwen-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
- package/qwen-extension/skills/local-browser-test/lib/global-setup.js +72 -0
- package/qwen-extension/skills/local-browser-test/lib/har-collector.js +124 -0
- package/qwen-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
- package/qwen-extension/skills/local-browser-test/lib/projects.js +46 -0
- package/qwen-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/qwen-extension/skills/local-browser-test/lib/redact.js +94 -0
- package/qwen-extension/skills/local-browser-test/lib/report-retain.js +84 -0
- package/qwen-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
- package/qwen-extension/skills/local-browser-test/lib/storage-state.js +111 -0
- package/qwen-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
- package/qwen-extension/skills/local-browser-test/lib/test-plan.js +2 -0
- package/qwen-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
- package/qwen-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
- package/qwen-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
- package/qwen-extension/skills/local-browser-test/local-browser-executor.js +226 -2
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/qwen-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
- package/qwen-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
- package/qwen-extension/skills/szcd-upload-zip/SKILL.md +45 -0
- package/scripts/lib/common.js +7 -0
- package/standard-skill/local-browser-test/SKILL.md +91 -0
- package/standard-skill/local-browser-test/config.example.yaml +51 -0
- package/standard-skill/local-browser-test/lib/api-call-executor.js +251 -0
- package/standard-skill/local-browser-test/lib/browser-engine.js +95 -21
- package/standard-skill/local-browser-test/lib/config-loader.js +104 -0
- package/standard-skill/local-browser-test/lib/console-archiver.js +100 -0
- package/standard-skill/local-browser-test/lib/global-setup.js +72 -0
- package/standard-skill/local-browser-test/lib/har-collector.js +124 -0
- package/standard-skill/local-browser-test/lib/html-reporter.js +248 -0
- package/standard-skill/local-browser-test/lib/projects.js +46 -0
- package/standard-skill/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/standard-skill/local-browser-test/lib/redact.js +94 -0
- package/standard-skill/local-browser-test/lib/report-retain.js +84 -0
- package/standard-skill/local-browser-test/lib/retry-policy.js +160 -0
- package/standard-skill/local-browser-test/lib/storage-state.js +111 -0
- package/standard-skill/local-browser-test/lib/tag-runner.js +103 -0
- package/standard-skill/local-browser-test/lib/test-plan.js +2 -0
- package/standard-skill/local-browser-test/lib/trace-collector.js +94 -0
- package/standard-skill/local-browser-test/lib/trend-aggregator.js +97 -0
- package/standard-skill/local-browser-test/lib/workspace-init.js +178 -0
- package/standard-skill/local-browser-test/local-browser-executor.js +226 -2
- package/standard-skill/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/standard-skill/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/standard-skill/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/standard-skill/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/standard-skill/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/standard-skill/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/standard-skill/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/standard-skill/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/standard-skill/shadcn-design-to-code/SKILL.md +352 -0
- package/standard-skill/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/standard-skill/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/standard-skill/shadcn-design-to-code/references/layouts.md +551 -0
- package/standard-skill/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/standard-skill/shadcn-design-to-code/references/tokens.md +534 -0
- package/standard-skill/szcd-upload-zip/SKILL.md +45 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
- package/qwen-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# shadcn 设计 Token 提取与落地
|
|
2
|
+
|
|
3
|
+
> 从 .sketch 节点提取设计 token(颜色/字号/间距/圆角/阴影),落地到 tailwind.config.ts + globals.css
|
|
4
|
+
|
|
5
|
+
## 核心原则
|
|
6
|
+
|
|
7
|
+
**3 次出现法则**:
|
|
8
|
+
- 出现 ≥ 3 次的颜色/字号/间距/圆角 → 抽到 `tailwind.config.ts` 主题
|
|
9
|
+
- 出现 1-2 次的 → 用 Tailwind arbitrary value 写 className(`bg-[#f2f8fd]`)
|
|
10
|
+
- 一次性色值(如品牌 logo 特殊色)→ 内联 style 或 CSS variable
|
|
11
|
+
|
|
12
|
+
**HSL 通道优先**:
|
|
13
|
+
- shadcn CSS variables 用 HSL 不带单位(如 `--primary: 222.2 47.4% 11.2%`)
|
|
14
|
+
- 任意值用 HEX(如 `--primary: #0f172a`)也能工作,但失去 `bg-primary/50` 这种透明度调节
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. 颜色 token 提取
|
|
19
|
+
|
|
20
|
+
### 1.1 从 Sketch 提取
|
|
21
|
+
|
|
22
|
+
每个 `rectangle` / `group` / `text` 节点都有 `style.fills[i].color.hex` 字段。统计所有 hex 出现频次:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# 伪代码:从 .sketch 解析结果统计
|
|
26
|
+
from collections import Counter
|
|
27
|
+
fills = Counter()
|
|
28
|
+
for node in all_nodes:
|
|
29
|
+
for fill in node.get('style', {}).get('fills', []):
|
|
30
|
+
if fill.get('color', {}).get('hex'):
|
|
31
|
+
fills[fill['color']['hex']] += 1
|
|
32
|
+
|
|
33
|
+
# 输出 top 20
|
|
34
|
+
for hex, count in fills.most_common(20):
|
|
35
|
+
print(f" {hex}: {count}次")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**示例输出**:
|
|
39
|
+
```
|
|
40
|
+
#ffffff: 47次 ← 背景主色
|
|
41
|
+
#0f172a: 32次 ← 文字主色
|
|
42
|
+
#64748b: 28次 ← 文字次色 (text-muted-foreground)
|
|
43
|
+
#f1f5f9: 18次 ← 弱背景 (bg-muted)
|
|
44
|
+
#e2e8f0: 15次 ← 边框 (border)
|
|
45
|
+
#3b82f6: 12次 ← 品牌主色
|
|
46
|
+
#f8fafc: 9次 ← 卡片背景
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 1.2 落地策略
|
|
50
|
+
|
|
51
|
+
**shadcn 默认色板 vs Sketch 提取色**:
|
|
52
|
+
|
|
53
|
+
| shadcn 默认 | 默认 HSL | 对应 Sketch | 落地 |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `--background` | 0 0% 100% | `#ffffff` | 直接用,不改 |
|
|
56
|
+
| `--foreground` | 222.2 47.4% 11.2% | `#0f172a` | 直接用,不改 |
|
|
57
|
+
| `--primary` | 222.2 47.4% 11.2% | `#3b82f6`(品牌蓝) | 改 HSL |
|
|
58
|
+
| `--primary-foreground` | 210 40% 98% | `#ffffff` | 直接用 |
|
|
59
|
+
| `--muted` | 210 40% 96.1% | `#f1f5f9` | 直接用 |
|
|
60
|
+
| `--muted-foreground` | 215.4 16.3% 46.9% | `#64748b` | 直接用 |
|
|
61
|
+
| `--border` | 214.3 31.8% 91.4% | `#e2e8f0` | 直接用 |
|
|
62
|
+
|
|
63
|
+
**如果 Sketch 颜色和 shadcn 默认色板接近**:直接用,不改(保留生态兼容性)。
|
|
64
|
+
|
|
65
|
+
**如果 Sketch 用了品牌自定义色**(如 `#3b82f6` 而不是 `#0f172a`):改 `globals.css` 的 HSL 值。
|
|
66
|
+
|
|
67
|
+
### 1.3 落地代码
|
|
68
|
+
|
|
69
|
+
#### 1.3.1 globals.css(CSS variables)
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
@layer base {
|
|
73
|
+
:root {
|
|
74
|
+
--background: 0 0% 100%; /* #ffffff */
|
|
75
|
+
--foreground: 222.2 47.4% 11.2%; /* #0f172a */
|
|
76
|
+
--card: 0 0% 100%;
|
|
77
|
+
--card-foreground: 222.2 47.4% 11.2%;
|
|
78
|
+
--primary: 217 91% 60%; /* #3b82f6 → HSL 转换 */
|
|
79
|
+
--primary-foreground: 0 0% 100%;
|
|
80
|
+
--secondary: 210 40% 96.1%;
|
|
81
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
82
|
+
--muted: 210 40% 96.1%; /* #f1f5f9 */
|
|
83
|
+
--muted-foreground: 215 16% 47%; /* #64748b */
|
|
84
|
+
--accent: 210 40% 96.1%;
|
|
85
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
86
|
+
--destructive: 0 84% 60%;
|
|
87
|
+
--destructive-foreground: 210 40% 98%;
|
|
88
|
+
--border: 214 32% 91%; /* #e2e8f0 */
|
|
89
|
+
--input: 214 32% 91%;
|
|
90
|
+
--ring: 217 91% 60%; /* 焦点环 = primary */
|
|
91
|
+
--radius: 0.5rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.dark {
|
|
95
|
+
--background: 222 47% 11%;
|
|
96
|
+
--foreground: 210 40% 98%;
|
|
97
|
+
/* ... 暗色版 */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**HEX → HSL 转换工具**:
|
|
103
|
+
```bash
|
|
104
|
+
# Node.js
|
|
105
|
+
node -e "const c=require('color');console.log(c('#3b82f6').hsl().string())"
|
|
106
|
+
# 输出: hsl(217, 91%, 60%)
|
|
107
|
+
|
|
108
|
+
# 提取 HSL 通道值(用于 CSS variable)
|
|
109
|
+
node -e "const c=require('color')('#3b82f6');const hsl=c.hsl().object();console.log(`${hsl.h} ${hsl.s.toFixed(1)}% ${hsl.l.toFixed(1)}%`)"
|
|
110
|
+
# 输出: 217 91% 60%
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### 1.3.2 tailwind.config.ts(业务色扩展)
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import type { Config } from "tailwindcss"
|
|
117
|
+
|
|
118
|
+
const config: Config = {
|
|
119
|
+
darkMode: ["class"],
|
|
120
|
+
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
|
|
121
|
+
theme: {
|
|
122
|
+
extend: {
|
|
123
|
+
colors: {
|
|
124
|
+
// shadcn HSL 通道(globals.css 改这里会同步生效)
|
|
125
|
+
border: "hsl(var(--border))",
|
|
126
|
+
input: "hsl(var(--input))",
|
|
127
|
+
ring: "hsl(var(--ring))",
|
|
128
|
+
background: "hsl(var(--background))",
|
|
129
|
+
foreground: "hsl(var(--foreground))",
|
|
130
|
+
primary: {
|
|
131
|
+
DEFAULT: "hsl(var(--primary))",
|
|
132
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
133
|
+
},
|
|
134
|
+
// ... 其余 shadcn 默认色
|
|
135
|
+
|
|
136
|
+
// 业务扩展色(出现 ≥ 3 次的 token)
|
|
137
|
+
brand: {
|
|
138
|
+
50: "#eff6ff",
|
|
139
|
+
100: "#dbeafe",
|
|
140
|
+
500: "#3b82f6", // ← 品牌主色
|
|
141
|
+
600: "#2563eb",
|
|
142
|
+
900: "#1e3a8a",
|
|
143
|
+
},
|
|
144
|
+
success: "#10b981",
|
|
145
|
+
warning: "#f59e0b",
|
|
146
|
+
danger: "#ef4444",
|
|
147
|
+
// Sketch 中出现 ≥ 3 次的色块
|
|
148
|
+
segment: {
|
|
149
|
+
blue: "#f2f8fd",
|
|
150
|
+
green: "#f5ffed",
|
|
151
|
+
yellow: "#fffbe6",
|
|
152
|
+
gray: "#f5f6f7",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
borderRadius: {
|
|
156
|
+
lg: "var(--radius)",
|
|
157
|
+
md: "calc(var(--radius) - 2px)",
|
|
158
|
+
sm: "calc(var(--radius) - 4px)",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
plugins: [require("tailwindcss-animate")],
|
|
163
|
+
}
|
|
164
|
+
export default config
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### 1.3.3 使用示例
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// 用 shadcn 语义色
|
|
171
|
+
<Button className="bg-primary text-primary-foreground">主操作</Button>
|
|
172
|
+
<Badge className="bg-destructive/10 text-destructive">错误</Badge>
|
|
173
|
+
|
|
174
|
+
// 用业务色
|
|
175
|
+
<div className="bg-brand-500 text-white">品牌主色</div>
|
|
176
|
+
<div className="bg-segment-blue border border-blue-200">蓝色段码块</div>
|
|
177
|
+
<div className="bg-segment-green border border-green-200">绿色段码块</div>
|
|
178
|
+
|
|
179
|
+
// 用 arbitrary value(一次性色值)
|
|
180
|
+
<div className="bg-[#abcdef]">特殊色</div>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 2. 字号/字重 token 提取
|
|
186
|
+
|
|
187
|
+
### 2.1 从 Sketch 提取
|
|
188
|
+
|
|
189
|
+
每个 `text` 节点都有 `text.attributes[0].attributes.{fontSize, fontFamily, color, lineHeight}`。
|
|
190
|
+
|
|
191
|
+
字重从 `fontFamily` 末尾推断:
|
|
192
|
+
- `PingFangSC-Regular` → 400
|
|
193
|
+
- `PingFangSC-Medium` → 500
|
|
194
|
+
- `PingFangSC-Semibold` → 600
|
|
195
|
+
- `PingFangSC-Bold` → 700
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
# 伪代码:统计字号频次
|
|
199
|
+
sizes = Counter()
|
|
200
|
+
for node in text_nodes:
|
|
201
|
+
for attr in node.get('text', {}).get('attributes', []):
|
|
202
|
+
size = attr.get('attributes', {}).get('fontSize')
|
|
203
|
+
if size:
|
|
204
|
+
sizes[size] += 1
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**典型输出**:
|
|
208
|
+
```
|
|
209
|
+
12: 28次 → text-xs (12px)
|
|
210
|
+
14: 47次 → text-sm (14px)
|
|
211
|
+
16: 32次 → text-base (16px)
|
|
212
|
+
18: 15次 → text-lg (18px)
|
|
213
|
+
20: 9次 → text-xl (20px)
|
|
214
|
+
24: 6次 → text-2xl (24px)
|
|
215
|
+
30: 3次 → text-3xl (30px)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 2.2 落地代码
|
|
219
|
+
|
|
220
|
+
shadcn 默认 Tailwind 配置已含 text-xs 到 text-9xl。如果 Sketch 用了非标准字号(13px / 15px / 17px),在 `tailwind.config.ts` 扩展:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
theme: {
|
|
224
|
+
extend: {
|
|
225
|
+
fontSize: {
|
|
226
|
+
// 非标字号
|
|
227
|
+
'2xs': ['11px', '16px'],
|
|
228
|
+
'13': ['13px', '20px'],
|
|
229
|
+
'15': ['15px', '22px'],
|
|
230
|
+
'17': ['17px', '26px'],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**字重对应**:
|
|
237
|
+
|
|
238
|
+
| Sketch fontFamily | font-weight | Tailwind class |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `-Regular` / `-Book` | 400 | `font-normal` |
|
|
241
|
+
| `-Medium` | 500 | `font-medium` |
|
|
242
|
+
| `-Semibold` / `-Demibold` | 600 | `font-semibold` |
|
|
243
|
+
| `-Bold` / `-Heavy` | 700 | `font-bold` |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 3. 间距 token 提取
|
|
248
|
+
|
|
249
|
+
### 3.1 从 Sketch 提取
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
# 伪代码:统计元素 x/y 差值(即间距)
|
|
253
|
+
spacings = Counter()
|
|
254
|
+
for node in all_nodes:
|
|
255
|
+
for sibling in siblings(node):
|
|
256
|
+
gap = abs(sibling.x - (node.x + node.width))
|
|
257
|
+
if 0 < gap < 200:
|
|
258
|
+
spacings[gap] += 1
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**典型输出**(Tailwind 标准 4px 栅格):
|
|
262
|
+
```
|
|
263
|
+
4: 67次 → p-1, m-1, gap-1
|
|
264
|
+
8: 89次 → p-2, m-2, gap-2 (最常用)
|
|
265
|
+
12: 23次 → p-3, m-3
|
|
266
|
+
16: 54次 → p-4, m-4
|
|
267
|
+
20: 18次 → p-5
|
|
268
|
+
24: 41次 → p-6
|
|
269
|
+
32: 15次 → p-8
|
|
270
|
+
40: 7次 → p-10
|
|
271
|
+
48: 4次 → p-12
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 3.2 落地代码
|
|
275
|
+
|
|
276
|
+
shadcn 默认 Tailwind 已含 4px 栅格的 spacing。**如果 Sketch 用了 8px 栅格外值**(如 6px / 10px / 14px),扩展:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
theme: {
|
|
280
|
+
extend: {
|
|
281
|
+
spacing: {
|
|
282
|
+
'1.5': '6px',
|
|
283
|
+
'2.5': '10px',
|
|
284
|
+
'3.5': '14px',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**容器最大宽度**(常被忽略):
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
theme: {
|
|
294
|
+
extend: {
|
|
295
|
+
maxWidth: {
|
|
296
|
+
'8xl': '88rem', // 1408px
|
|
297
|
+
'9xl': '96rem', // 1536px
|
|
298
|
+
'screen-2xl': '1536px',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 4. 圆角 token 提取
|
|
307
|
+
|
|
308
|
+
### 4.1 从 Sketch 提取
|
|
309
|
+
|
|
310
|
+
`shape.cornerRadius` 字段:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
radii = Counter()
|
|
314
|
+
for node in rect_nodes:
|
|
315
|
+
r = node.get('shape', {}).get('cornerRadius')
|
|
316
|
+
if r:
|
|
317
|
+
radii[r] += 1
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**典型输出**:
|
|
321
|
+
```
|
|
322
|
+
0: 89次 → rounded-none
|
|
323
|
+
4: 12次 → rounded-sm
|
|
324
|
+
6: 23次 → rounded-md
|
|
325
|
+
8: 31次 → rounded-lg
|
|
326
|
+
12: 18次 → rounded-xl
|
|
327
|
+
16: 7次 → rounded-2xl
|
|
328
|
+
9999: 4次 → rounded-full
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 4.2 落地代码
|
|
332
|
+
|
|
333
|
+
shadcn 默认 CSS variable `--radius: 0.5rem`(即 8px)对应 `rounded-lg`。
|
|
334
|
+
|
|
335
|
+
**修改全局圆角**(一次性影响所有 shadcn 组件):
|
|
336
|
+
|
|
337
|
+
```css
|
|
338
|
+
/* globals.css */
|
|
339
|
+
:root {
|
|
340
|
+
--radius: 0.75rem; /* 12px → rounded-lg 变 12px */
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**单独覆盖某个组件**:
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
<Button className="rounded-full">完全圆角按钮</Button>
|
|
348
|
+
<Card className="rounded-none">直角卡片</Card>
|
|
349
|
+
<Input className="rounded-md">中等圆角输入框</Input>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 5. 阴影 token 提取
|
|
355
|
+
|
|
356
|
+
### 5.1 从 Sketch 提取
|
|
357
|
+
|
|
358
|
+
每个节点的 `style.shadows[]` 数组:
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
shadows = Counter()
|
|
362
|
+
for node in all_nodes:
|
|
363
|
+
for sh in node.get('style', {}).get('shadows', []):
|
|
364
|
+
key = f"{sh.get('color',{}).get('hex','?')}|{sh.get('offsetX',0)},{sh.get('offsetY',0)},{sh.get('blurRadius',0)}"
|
|
365
|
+
shadows[key] += 1
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**典型输出**:
|
|
369
|
+
```
|
|
370
|
+
#00000010|0,1,2: 18次 → shadow-sm
|
|
371
|
+
#00000014|0,4,6: 9次 → shadow (default)
|
|
372
|
+
#0000001f|0,10,15: 4次 → shadow-md
|
|
373
|
+
#00000026|0,20,25: 2次 → shadow-lg
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 5.2 落地代码
|
|
377
|
+
|
|
378
|
+
shadcn 默认含 `shadow-sm / shadow / shadow-md / shadow-lg / shadow-xl / shadow-2xl`。
|
|
379
|
+
|
|
380
|
+
**Sketch 中的非标阴影**(如 `0,0,20 #0079f740` 蓝色光晕):
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
theme: {
|
|
384
|
+
extend: {
|
|
385
|
+
boxShadow: {
|
|
386
|
+
'glow-blue': '0 0 20px rgba(0, 121, 247, 0.25)',
|
|
387
|
+
'card-hover': '0 8px 30px rgba(0, 0, 0, 0.12)',
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
<Card className="shadow-glow-blue">发光卡片</Card>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 6. 渐变 token 提取
|
|
400
|
+
|
|
401
|
+
### 6.1 从 Sketch 提取
|
|
402
|
+
|
|
403
|
+
`style.fills[0].gradient.stops[]`:
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
gradients = Counter()
|
|
407
|
+
for node in all_nodes:
|
|
408
|
+
fill = node.get('style', {}).get('fills', [{}])[0]
|
|
409
|
+
grad = fill.get('gradient')
|
|
410
|
+
if grad:
|
|
411
|
+
key = '|'.join(f"{s['color']['hex']}@{s.get('position',0):.0%}" for s in grad.get('stops', []))
|
|
412
|
+
gradients[(grad.get('type'), key)] += 1
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**典型输出**:
|
|
416
|
+
```
|
|
417
|
+
('linear', #f3faff@0%|#f8fdff@100%): 5次 → 卡片背景渐变
|
|
418
|
+
('linear', #0ea5e9@0%|#3b82f6@100%): 3次 → 按钮渐变
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 6.2 落地代码
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
{/* 单次使用:arbitrary value */}
|
|
425
|
+
<div className="bg-gradient-to-b from-[#f3faff] to-[#f8fdff]" />
|
|
426
|
+
|
|
427
|
+
{/* 多次使用:抽到 config */}
|
|
428
|
+
<button className="bg-gradient-primary text-white" />
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
// tailwind.config.ts
|
|
433
|
+
theme: {
|
|
434
|
+
extend: {
|
|
435
|
+
backgroundImage: {
|
|
436
|
+
'gradient-primary': 'linear-gradient(135deg, #0ea5e9 0%, #3b82f6 100%)',
|
|
437
|
+
'gradient-card': 'linear-gradient(180deg, #f3faff 0%, #f8fdff 100%)',
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 7. 落地校验清单(写代码后必跑)
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
[ ] 所有出现 ≥ 3 次的颜色都在 tailwind.config.ts colors / globals.css --* 中
|
|
449
|
+
[ ] 所有出现 ≥ 3 次的字号用 text-* 语义类(text-xs/sm/base/lg/xl/2xl)映射
|
|
450
|
+
[ ] 所有出现 ≥ 3 次的圆角用 rounded-* 语义类(rounded-sm/md/lg/xl/full)映射
|
|
451
|
+
[ ] 所有出现 ≥ 3 次的间距用 p-* / m-* / gap-* 映射
|
|
452
|
+
[ ] Sketch 特殊字号(如 13px)已在 fontSize 扩展中
|
|
453
|
+
[ ] Sketch 渐变已抽到 backgroundImage 或 CSS variable
|
|
454
|
+
[ ] Sketch 阴影已抽到 boxShadow 扩展
|
|
455
|
+
[ ] 暗色模式 .dark 段已写(如需要)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**反例(违反 = 重做)**:
|
|
459
|
+
- ❌ 出现 8 次的 `#3b82f6` 还是 `bg-[#3b82f6]` 写 className(应抽到 brand.500)
|
|
460
|
+
- ❌ 出现 5 次的 13px 还是 `text-[13px]`(应抽到 fontSize.13)
|
|
461
|
+
- ❌ globals.css 的 `--primary` 还是默认蓝 `#0f172a`,但代码里到处 `bg-blue-500`
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## 8. 工具函数:自动从 Sketch 提取
|
|
466
|
+
|
|
467
|
+
把以下 Python 脚本保存到 `scripts/extract-design-tokens.py`:
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
#!/usr/bin/env python3
|
|
471
|
+
"""从 sketch-mcp-server 输出的节点 JSON 提取设计 token"""
|
|
472
|
+
import json, sys
|
|
473
|
+
from collections import Counter
|
|
474
|
+
|
|
475
|
+
def extract_tokens(nodes):
|
|
476
|
+
colors = Counter()
|
|
477
|
+
sizes = Counter()
|
|
478
|
+
radii = Counter()
|
|
479
|
+
gradients = Counter()
|
|
480
|
+
|
|
481
|
+
for n in nodes:
|
|
482
|
+
style = n.get('style', {})
|
|
483
|
+
# 颜色
|
|
484
|
+
for f in style.get('fills', []):
|
|
485
|
+
hex = f.get('color', {}).get('hex')
|
|
486
|
+
if hex:
|
|
487
|
+
colors[hex] += 1
|
|
488
|
+
if f.get('gradient'):
|
|
489
|
+
stops = f['gradient'].get('stops', [])
|
|
490
|
+
key = '|'.join(f"{s['color']['hex']}@{s.get('position',0):.0%}" for s in stops)
|
|
491
|
+
gradients[key] += 1
|
|
492
|
+
# 字号
|
|
493
|
+
for attr in n.get('text', {}).get('attributes', []):
|
|
494
|
+
size = attr.get('attributes', {}).get('fontSize')
|
|
495
|
+
if size:
|
|
496
|
+
sizes[size] += 1
|
|
497
|
+
# 圆角
|
|
498
|
+
r = n.get('shape', {}).get('cornerRadius')
|
|
499
|
+
if r is not None:
|
|
500
|
+
radii[r] += 1
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
'colors': colors.most_common(20),
|
|
504
|
+
'sizes': sizes.most_common(10),
|
|
505
|
+
'radii': radii.most_common(10),
|
|
506
|
+
'gradients': gradients.most_common(5),
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if __name__ == '__main__':
|
|
510
|
+
nodes = json.load(sys.stdin)
|
|
511
|
+
tokens = extract_tokens(nodes)
|
|
512
|
+
print(json.dumps(tokens, indent=2, ensure_ascii=False))
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**用法**:
|
|
516
|
+
```bash
|
|
517
|
+
# 把 getMultipleNodeInfo 的输出保存到 nodes.json
|
|
518
|
+
cat nodes.json | python3 extract-design-tokens.py > tokens.json
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
输出示例:
|
|
522
|
+
```json
|
|
523
|
+
{
|
|
524
|
+
"colors": [
|
|
525
|
+
["#ffffff", 47],
|
|
526
|
+
["#0f172a", 32],
|
|
527
|
+
["#3b82f6", 12],
|
|
528
|
+
...
|
|
529
|
+
],
|
|
530
|
+
"sizes": [["14", 47], ["16", 32], ...],
|
|
531
|
+
"radii": [["8", 31], ["6", 23], ...],
|
|
532
|
+
"gradients": []
|
|
533
|
+
}
|
|
534
|
+
```
|
|
@@ -52,6 +52,50 @@ cat ~/.szcd-mcp-config.json
|
|
|
52
52
|
- 若 `MCP_API_KEY` 为空字符串,则上传时不带 `X-API-Key` header
|
|
53
53
|
- 若 `MCP_SERVER_URL` 未配置,默认使用 `http://localhost:3456`
|
|
54
54
|
|
|
55
|
+
### 第 1.5 步:打包目录为 ZIP(跨平台注意事项)
|
|
56
|
+
|
|
57
|
+
> ⚠️ **关键:不要用 Windows PowerShell 的 `Compress-Archive`。**
|
|
58
|
+
> 它生成的 ZIP 用反斜杠 `\` 作路径分隔符,Linux 服务器 `unzip` 会拒绝解压并报错
|
|
59
|
+
> `appears to use backslashes as path sep`,导致子目录结构丢失。
|
|
60
|
+
> (服务端已增加反斜杠归一化兜底,但仍应优先用正确方式打包,避免依赖兜底。)
|
|
61
|
+
|
|
62
|
+
按平台选择打包方式(均产出正斜杠路径的标准 ZIP):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# macOS / Linux / Git-Bash / WSL —— 系统 zip
|
|
66
|
+
zip -r my-package.zip my-package/
|
|
67
|
+
|
|
68
|
+
# 任意平台(含 Windows,只要有 Node)—— 用 tar 产出 zip 不可行,改用下面的 Node 兜底
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Windows 推荐**(按优先级):
|
|
72
|
+
|
|
73
|
+
1. **Git Bash / WSL 里的 `zip`**(最简单,路径分隔符正确):
|
|
74
|
+
```bash
|
|
75
|
+
zip -r my-package.zip my-package/
|
|
76
|
+
```
|
|
77
|
+
2. **若只有 PowerShell**,不要用 `Compress-Archive`;改用 Node.js 内置逻辑打包,强制正斜杠:
|
|
78
|
+
```bash
|
|
79
|
+
node -e "
|
|
80
|
+
const fs=require('fs'),path=require('path'),zlib=require('zlib');
|
|
81
|
+
const root=process.argv[1], out=process.argv[2];
|
|
82
|
+
function crc32(b){let t=[];for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=c&1?0xEDB88320^(c>>>1):c>>>1;t[n]=c>>>0;}let crc=-1;for(let i=0;i<b.length;i++)crc=(crc>>>8)^t[(crc^b[i])&0xFF];return(crc^-1)>>>0;}
|
|
83
|
+
let locals=[],centrals=[],off=0;
|
|
84
|
+
(function walk(dir,pre){for(const e of fs.readdirSync(dir,{withFileTypes:true})){const rel=pre?pre+'/'+e.name:e.name;const abs=path.join(dir,e.name);
|
|
85
|
+
if(e.isDirectory())walk(abs,rel);else{
|
|
86
|
+
const data=fs.readFileSync(abs),name=Buffer.from(rel,'utf8'),comp=zlib.deflateRawSync(data),crc=crc32(data);
|
|
87
|
+
const lh=Buffer.alloc(30);lh.writeUInt32LE(0x04034b50,0);lh.writeUInt16LE(20,4);lh.writeUInt16LE(8,8);lh.writeUInt32LE(crc,14);lh.writeUInt32LE(comp.length,18);lh.writeUInt32LE(data.length,22);lh.writeUInt16LE(name.length,26);
|
|
88
|
+
locals.push(Buffer.concat([lh,name,comp]));
|
|
89
|
+
const ch=Buffer.alloc(46);ch.writeUInt32LE(0x02014b50,0);ch.writeUInt16LE(20,4);ch.writeUInt16LE(20,6);ch.writeUInt16LE(8,10);ch.writeUInt32LE(crc,16);ch.writeUInt32LE(comp.length,20);ch.writeUInt32LE(data.length,24);ch.writeUInt16LE(name.length,28);ch.writeUInt32LE(off,42);
|
|
90
|
+
centrals.push(Buffer.concat([ch,name]));off+=locals[locals.length-1].length;}}})(root,'');
|
|
91
|
+
const lb=Buffer.concat(locals),cb=Buffer.concat(centrals),eo=Buffer.alloc(22);
|
|
92
|
+
eo.writeUInt32LE(0x06054b50,0);eo.writeUInt16LE(centrals.length,8);eo.writeUInt16LE(centrals.length,10);eo.writeUInt32LE(cb.length,12);eo.writeUInt32LE(lb.length,16);
|
|
93
|
+
fs.writeFileSync(out,Buffer.concat([lb,cb,eo]));console.log('zip written:',out);
|
|
94
|
+
" my-package my-package.zip
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
打包后用 `unzip -l my-package.zip` 检查路径是否为正斜杠(若看到 `\` 则不可上传)。
|
|
98
|
+
|
|
55
99
|
### 第 2 步:上传 ZIP 文件
|
|
56
100
|
|
|
57
101
|
```bash
|
|
@@ -107,6 +151,7 @@ curl -s "$MCP_URL/api/download?path=/scity/zengzhijie/mcp/uploads/my-package/dat
|
|
|
107
151
|
- 同名 zip 包名会覆盖服务器上已有的目录
|
|
108
152
|
- 解压后文件持久存储,无自动过期
|
|
109
153
|
- 上传路径始终为 `/upload-zip`(不含 `/api/` 前缀)
|
|
154
|
+
- 服务端解压前会把条目名中的反斜杠 `\` 归一化为正斜杠 `/`(兼容 Windows `Compress-Archive` 产物),但应优先按「第 1.5 步」用正确方式打包
|
|
110
155
|
|
|
111
156
|
## 完整命令示例(一步完成)
|
|
112
157
|
|