foliko 1.1.1 → 1.1.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/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
- package/.agent/plugins/poster-plugin/package.json +2 -1
- package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
- package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
- package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
- package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
- package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
- package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
- package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
- package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
- package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
- package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
- package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
- package/.agent/plugins/poster-plugin/src/composer.js +392 -150
- package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
- package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
- package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
- package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
- package/.agent/plugins/poster-plugin/src/index.js +430 -7
- package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
- package/.agent/plugins/poster-plugin/test-background.svg +1 -0
- package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
- package/.agent/plugins/poster-plugin/test-image.png +0 -0
- package/.agent/sessions/cli_default.json +1089 -145
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
- package/.claude/settings.local.json +6 -1
- package/output/beef-love-poster.png +0 -0
- package/output/international-news-daily.png +0 -0
- package/package.json +2 -1
- package/plugins/extension-executor-plugin.js +33 -32
- package/plugins/file-system-plugin.js +4 -19
- package/plugins/subagent-plugin.js +37 -14
- package/plugins/weixin-plugin.js +167 -47
- package/poster-test-2.png +0 -0
- package/skills/poster-guide/SKILL.md +497 -5
- package/src/core/agent-chat.js +141 -8
- package/src/core/agent.js +6 -3
- package/calc_tokens_weixin.js +0 -81
- package/foliko-creative-3.png +0 -0
- package/foliko-creative-4.png +0 -0
- package/foliko-creative-5.png +0 -0
- package/story-cover-book-v2.png +0 -0
- package/story-cover-japanese-1.png +0 -0
- package/story-cover-japanese-2.png +0 -0
- package/story-cover-japanese-3.png +0 -0
- package/story-cover-moran.png +0 -0
- package/undefined.png +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 装饰边框组件 - 多种风格
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 创建边框
|
|
9
|
+
*/
|
|
10
|
+
function createFrame(project, args) {
|
|
11
|
+
const {
|
|
12
|
+
x = 0,
|
|
13
|
+
y = 0,
|
|
14
|
+
width = 400,
|
|
15
|
+
height = 300,
|
|
16
|
+
style = 'simple', // simple, double, dashed, dotted, corner, vintage, modern, floral
|
|
17
|
+
color = '#000000',
|
|
18
|
+
borderWidth = 2,
|
|
19
|
+
radius = 0,
|
|
20
|
+
padding = 0,
|
|
21
|
+
opacity = 1,
|
|
22
|
+
} = args
|
|
23
|
+
|
|
24
|
+
const items = []
|
|
25
|
+
const effectiveX = x + padding
|
|
26
|
+
const effectiveY = y + padding
|
|
27
|
+
const effectiveWidth = width - padding * 2
|
|
28
|
+
const effectiveHeight = height - padding * 2
|
|
29
|
+
|
|
30
|
+
const strokeOpts = {
|
|
31
|
+
strokeColor: new paper.Color(color),
|
|
32
|
+
strokeWidth: borderWidth,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (style) {
|
|
36
|
+
case 'simple':
|
|
37
|
+
// 简单矩形
|
|
38
|
+
items.push(new paper.Path.Rectangle({
|
|
39
|
+
point: [effectiveX, effectiveY],
|
|
40
|
+
size: [effectiveWidth, effectiveHeight],
|
|
41
|
+
radius: radius,
|
|
42
|
+
...strokeOpts,
|
|
43
|
+
}))
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
case 'double':
|
|
47
|
+
// 双线边框
|
|
48
|
+
items.push(new paper.Path.Rectangle({
|
|
49
|
+
point: [effectiveX, effectiveY],
|
|
50
|
+
size: [effectiveWidth, effectiveHeight],
|
|
51
|
+
radius: radius,
|
|
52
|
+
...strokeOpts,
|
|
53
|
+
}))
|
|
54
|
+
items.push(new paper.Path.Rectangle({
|
|
55
|
+
point: [effectiveX + 8, effectiveY + 8],
|
|
56
|
+
size: [effectiveWidth - 16, effectiveHeight - 16],
|
|
57
|
+
radius: radius,
|
|
58
|
+
...strokeOpts,
|
|
59
|
+
}))
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
case 'dashed':
|
|
63
|
+
// 虚线边框
|
|
64
|
+
const dashed = new paper.Path.Rectangle({
|
|
65
|
+
point: [effectiveX, effectiveY],
|
|
66
|
+
size: [effectiveWidth, effectiveHeight],
|
|
67
|
+
radius: radius,
|
|
68
|
+
...strokeOpts,
|
|
69
|
+
})
|
|
70
|
+
dashed.dashArray = [10, 5]
|
|
71
|
+
items.push(dashed)
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
case 'dotted':
|
|
75
|
+
// 点线边框
|
|
76
|
+
const dotted = new paper.Path.Rectangle({
|
|
77
|
+
point: [effectiveX, effectiveY],
|
|
78
|
+
size: [effectiveWidth, effectiveHeight],
|
|
79
|
+
radius: radius,
|
|
80
|
+
strokeColor: new paper.Color(color),
|
|
81
|
+
strokeWidth: borderWidth,
|
|
82
|
+
dashArray: [2, 4],
|
|
83
|
+
})
|
|
84
|
+
items.push(dotted)
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
case 'corner':
|
|
88
|
+
// 四角装饰
|
|
89
|
+
const cornerSize = Math.min(30, effectiveWidth / 4, effectiveHeight / 4)
|
|
90
|
+
// 左上角
|
|
91
|
+
items.push(new paper.Path.Line(
|
|
92
|
+
[effectiveX, effectiveY + cornerSize],
|
|
93
|
+
[effectiveX, effectiveY],
|
|
94
|
+
{ ...strokeOpts }
|
|
95
|
+
))
|
|
96
|
+
items.push(new paper.Path.Line(
|
|
97
|
+
[effectiveX, effectiveY],
|
|
98
|
+
[effectiveX + cornerSize, effectiveY],
|
|
99
|
+
{ ...strokeOpts }
|
|
100
|
+
))
|
|
101
|
+
// 右上角
|
|
102
|
+
items.push(new paper.Path.Line(
|
|
103
|
+
[effectiveX + effectiveWidth - cornerSize, effectiveY],
|
|
104
|
+
[effectiveX + effectiveWidth, effectiveY],
|
|
105
|
+
{ ...strokeOpts }
|
|
106
|
+
))
|
|
107
|
+
items.push(new paper.Path.Line(
|
|
108
|
+
[effectiveX + effectiveWidth, effectiveY],
|
|
109
|
+
[effectiveX + effectiveWidth, effectiveY + cornerSize],
|
|
110
|
+
{ ...strokeOpts }
|
|
111
|
+
))
|
|
112
|
+
// 左下角
|
|
113
|
+
items.push(new paper.Path.Line(
|
|
114
|
+
[effectiveX, effectiveY + effectiveHeight - cornerSize],
|
|
115
|
+
[effectiveX, effectiveY + effectiveHeight],
|
|
116
|
+
{ ...strokeOpts }
|
|
117
|
+
))
|
|
118
|
+
items.push(new paper.Path.Line(
|
|
119
|
+
[effectiveX, effectiveY + effectiveHeight],
|
|
120
|
+
[effectiveX + cornerSize, effectiveY + effectiveHeight],
|
|
121
|
+
{ ...strokeOpts }
|
|
122
|
+
))
|
|
123
|
+
// 右下角
|
|
124
|
+
items.push(new paper.Path.Line(
|
|
125
|
+
[effectiveX + effectiveWidth - cornerSize, effectiveY + effectiveHeight],
|
|
126
|
+
[effectiveX + effectiveWidth, effectiveY + effectiveHeight],
|
|
127
|
+
{ ...strokeOpts }
|
|
128
|
+
))
|
|
129
|
+
items.push(new paper.Path.Line(
|
|
130
|
+
[effectiveX + effectiveWidth, effectiveY + effectiveHeight - cornerSize],
|
|
131
|
+
[effectiveX + effectiveWidth, effectiveY + effectiveHeight],
|
|
132
|
+
{ ...strokeOpts }
|
|
133
|
+
))
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
case 'vintage':
|
|
137
|
+
// 复古边框 - 双线+角装饰
|
|
138
|
+
const vintageOuter = new paper.Path.Rectangle({
|
|
139
|
+
point: [effectiveX, effectiveY],
|
|
140
|
+
size: [effectiveWidth, effectiveHeight],
|
|
141
|
+
...strokeOpts,
|
|
142
|
+
})
|
|
143
|
+
items.push(vintageOuter)
|
|
144
|
+
const vintageInner = new paper.Path.Rectangle({
|
|
145
|
+
point: [effectiveX + 6, effectiveY + 6],
|
|
146
|
+
size: [effectiveWidth - 12, effectiveHeight - 12],
|
|
147
|
+
...strokeOpts,
|
|
148
|
+
})
|
|
149
|
+
items.push(vintageInner)
|
|
150
|
+
// 四角小方块
|
|
151
|
+
const cornerBlockSize = 8
|
|
152
|
+
const corners = [
|
|
153
|
+
[effectiveX, effectiveY],
|
|
154
|
+
[effectiveX + effectiveWidth - cornerBlockSize, effectiveY],
|
|
155
|
+
[effectiveX, effectiveY + effectiveHeight - cornerBlockSize],
|
|
156
|
+
[effectiveX + effectiveWidth - cornerBlockSize, effectiveY + effectiveHeight - cornerBlockSize],
|
|
157
|
+
]
|
|
158
|
+
corners.forEach(([cx, cy]) => {
|
|
159
|
+
items.push(new paper.Path.Rectangle({
|
|
160
|
+
point: [cx, cy],
|
|
161
|
+
size: [cornerBlockSize, cornerBlockSize],
|
|
162
|
+
fillColor: new paper.Color(color),
|
|
163
|
+
}))
|
|
164
|
+
})
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
case 'modern':
|
|
168
|
+
// 现代边框 - 粗细交替
|
|
169
|
+
const modernPath = new paper.Path.Rectangle({
|
|
170
|
+
point: [effectiveX, effectiveY],
|
|
171
|
+
size: [effectiveWidth, effectiveHeight],
|
|
172
|
+
radius: radius,
|
|
173
|
+
...strokeOpts,
|
|
174
|
+
})
|
|
175
|
+
modernPath.dashArray = [20, 5, 5, 5]
|
|
176
|
+
items.push(modernPath)
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
case 'floral':
|
|
180
|
+
// 花纹边框 - 角装饰+边线
|
|
181
|
+
const floral = new paper.Path.Rectangle({
|
|
182
|
+
point: [effectiveX, effectiveY],
|
|
183
|
+
size: [effectiveWidth, effectiveHeight],
|
|
184
|
+
...strokeOpts,
|
|
185
|
+
})
|
|
186
|
+
items.push(floral)
|
|
187
|
+
// 四个角的花纹
|
|
188
|
+
const fCornerSize = Math.min(25, effectiveWidth / 6, effectiveHeight / 6)
|
|
189
|
+
const fCorners = [
|
|
190
|
+
{ x: effectiveX, y: effectiveY, rotation: 0 },
|
|
191
|
+
{ x: effectiveX + effectiveWidth - fCornerSize, y: effectiveY, rotation: 90 },
|
|
192
|
+
{ x: effectiveX, y: effectiveY + effectiveHeight - fCornerSize, rotation: 270 },
|
|
193
|
+
{ x: effectiveX + effectiveWidth - fCornerSize, y: effectiveY + effectiveHeight - fCornerSize, rotation: 180 },
|
|
194
|
+
]
|
|
195
|
+
fCorners.forEach(({ x: fx, y: fy, rotation }) => {
|
|
196
|
+
const ornament = new paper.Path()
|
|
197
|
+
ornament.add(new paper.Point(fx + fCornerSize / 2, fy + fCornerSize))
|
|
198
|
+
ornament.add(new paper.Point(fx + fCornerSize, fy + fCornerSize / 2))
|
|
199
|
+
ornament.add(new paper.Point(fx + fCornerSize, fy + fCornerSize))
|
|
200
|
+
ornament.strokeColor = new paper.Color(color)
|
|
201
|
+
ornament.strokeWidth = borderWidth
|
|
202
|
+
ornament.rotate(rotation, new paper.Point(fx + fCornerSize, fy + fCornerSize))
|
|
203
|
+
items.push(ornament)
|
|
204
|
+
})
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
default:
|
|
208
|
+
items.push(new paper.Path.Rectangle({
|
|
209
|
+
point: [effectiveX, effectiveY],
|
|
210
|
+
size: [effectiveWidth, effectiveHeight],
|
|
211
|
+
radius: radius,
|
|
212
|
+
...strokeOpts,
|
|
213
|
+
}))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 应用透明度
|
|
217
|
+
if (opacity !== 1) {
|
|
218
|
+
items.forEach(item => {
|
|
219
|
+
item.opacity = opacity
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
success: true,
|
|
225
|
+
type: 'frame',
|
|
226
|
+
items: items.map(i => i.id),
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = createFrame
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 高亮文字组件 - 荧光笔效果
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 创建高亮文字
|
|
9
|
+
*/
|
|
10
|
+
function createHighlightText(project, args) {
|
|
11
|
+
const {
|
|
12
|
+
x = 0,
|
|
13
|
+
y = 0,
|
|
14
|
+
text = '',
|
|
15
|
+
fontSize = 48,
|
|
16
|
+
fontFamily,
|
|
17
|
+
color = '#000000',
|
|
18
|
+
highlightColor = '#ffff00',
|
|
19
|
+
highlightStyle = 'marker', // marker, underline, background, stroke
|
|
20
|
+
highlightWidth = 20,
|
|
21
|
+
strokeWidth = 2,
|
|
22
|
+
shadow,
|
|
23
|
+
opacity = 1,
|
|
24
|
+
} = args
|
|
25
|
+
|
|
26
|
+
const items = []
|
|
27
|
+
|
|
28
|
+
// 创建文字以获取尺寸
|
|
29
|
+
const textItem = new paper.PointText({
|
|
30
|
+
point: [x, y + fontSize],
|
|
31
|
+
content: text,
|
|
32
|
+
fontSize,
|
|
33
|
+
fontFamily: fontFamily || 'sans-serif',
|
|
34
|
+
fillColor: new paper.Color(color),
|
|
35
|
+
justification: 'left',
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const textBounds = textItem.bounds
|
|
39
|
+
|
|
40
|
+
// 根据样式添加高亮
|
|
41
|
+
switch (highlightStyle) {
|
|
42
|
+
case 'marker':
|
|
43
|
+
// 荧光笔效果 - 倾斜的矩形
|
|
44
|
+
const markerHeight = fontSize * 0.8
|
|
45
|
+
const marker = new paper.Path.Rectangle({
|
|
46
|
+
point: [textBounds.x - 5, textBounds.y + fontSize * 0.3],
|
|
47
|
+
size: [textBounds.width + 10, markerHeight],
|
|
48
|
+
})
|
|
49
|
+
marker.fillColor = new paper.Color(highlightColor)
|
|
50
|
+
marker.opacity = 0.5
|
|
51
|
+
marker.rotate(-5, marker.bounds.center)
|
|
52
|
+
if (shadow) {
|
|
53
|
+
marker.shadowColor = new paper.Color(shadow.color || '#000000')
|
|
54
|
+
marker.shadowBlur = shadow.blur || 5
|
|
55
|
+
marker.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
56
|
+
}
|
|
57
|
+
if (opacity !== 1) marker.opacity *= opacity
|
|
58
|
+
items.push(marker)
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
case 'underline':
|
|
62
|
+
// 下划线
|
|
63
|
+
const underlineY = textBounds.y + fontSize + 5
|
|
64
|
+
const underline = new paper.Path.Line(
|
|
65
|
+
[textBounds.x, underlineY],
|
|
66
|
+
[textBounds.x + textBounds.width, underlineY]
|
|
67
|
+
)
|
|
68
|
+
underline.strokeColor = new paper.Color(highlightColor)
|
|
69
|
+
underline.strokeWidth = highlightWidth
|
|
70
|
+
underline.strokeCap = 'round'
|
|
71
|
+
if (shadow) {
|
|
72
|
+
underline.shadowColor = new paper.Color(shadow.color || '#000000')
|
|
73
|
+
underline.shadowBlur = shadow.blur || 5
|
|
74
|
+
underline.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
75
|
+
}
|
|
76
|
+
if (opacity !== 1) underline.opacity = opacity
|
|
77
|
+
items.push(underline)
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
case 'background':
|
|
81
|
+
// 背景色块
|
|
82
|
+
const padding = 8
|
|
83
|
+
const bg = new paper.Path.Rectangle({
|
|
84
|
+
point: [textBounds.x - padding, textBounds.y - 5],
|
|
85
|
+
size: [textBounds.width + padding * 2, fontSize + 10],
|
|
86
|
+
radius: 4,
|
|
87
|
+
})
|
|
88
|
+
bg.fillColor = new paper.Color(highlightColor)
|
|
89
|
+
bg.opacity = 0.6
|
|
90
|
+
if (shadow) {
|
|
91
|
+
bg.shadowColor = new paper.Color(shadow.color || '#000000')
|
|
92
|
+
bg.shadowBlur = shadow.blur || 5
|
|
93
|
+
bg.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
94
|
+
}
|
|
95
|
+
if (opacity !== 1) bg.opacity *= opacity
|
|
96
|
+
items.push(bg)
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
case 'stroke':
|
|
100
|
+
// 描边效果
|
|
101
|
+
textItem.strokeColor = new paper.Color(highlightColor)
|
|
102
|
+
textItem.strokeWidth = strokeWidth
|
|
103
|
+
if (shadow) {
|
|
104
|
+
textItem.shadowColor = new paper.Color(shadow.color || '#000000')
|
|
105
|
+
textItem.shadowBlur = shadow.blur || 5
|
|
106
|
+
textItem.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
107
|
+
}
|
|
108
|
+
if (opacity !== 1) textItem.opacity = opacity
|
|
109
|
+
items.push(textItem)
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
case 'neon':
|
|
113
|
+
// 霓虹效果
|
|
114
|
+
textItem.strokeColor = new paper.Color(highlightColor)
|
|
115
|
+
textItem.strokeWidth = strokeWidth * 2
|
|
116
|
+
const neonClone = textItem.clone()
|
|
117
|
+
neonClone.strokeWidth = strokeWidth * 4
|
|
118
|
+
neonClone.strokeColor = new paper.Color(highlightColor)
|
|
119
|
+
neonClone.fillColor = null
|
|
120
|
+
neonClone.opacity = 0.3
|
|
121
|
+
if (opacity !== 1) {
|
|
122
|
+
textItem.opacity = opacity
|
|
123
|
+
neonClone.opacity *= opacity
|
|
124
|
+
}
|
|
125
|
+
items.push(neonClone)
|
|
126
|
+
items.push(textItem)
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 如果不是 stroke 样式,需要重新添加文字
|
|
131
|
+
if (highlightStyle !== 'stroke' && highlightStyle !== 'neon') {
|
|
132
|
+
if (opacity !== 1) textItem.opacity = opacity
|
|
133
|
+
items.push(textItem)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
type: 'highlightText',
|
|
139
|
+
items: items.map(i => i.id),
|
|
140
|
+
bounds: textBounds,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = createHighlightText
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图标组件 - 支持 emoji、图片图标
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const { loadImageAsRaster } = require('../utils/imageLoader')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建图标
|
|
10
|
+
*/
|
|
11
|
+
async function createIcon(project, args) {
|
|
12
|
+
const {
|
|
13
|
+
x = 0,
|
|
14
|
+
y = 0,
|
|
15
|
+
size = 64,
|
|
16
|
+
icon, // emoji 或 图片URL
|
|
17
|
+
color,
|
|
18
|
+
backgroundColor,
|
|
19
|
+
borderColor,
|
|
20
|
+
borderWidth = 0,
|
|
21
|
+
radius = 0,
|
|
22
|
+
shadow,
|
|
23
|
+
opacity = 1,
|
|
24
|
+
} = args
|
|
25
|
+
|
|
26
|
+
const items = []
|
|
27
|
+
|
|
28
|
+
// 背景
|
|
29
|
+
if (backgroundColor || borderColor) {
|
|
30
|
+
const bg = new paper.Path.Rectangle({
|
|
31
|
+
point: [x, y],
|
|
32
|
+
size: [size, size],
|
|
33
|
+
radius: radius,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (backgroundColor) {
|
|
37
|
+
bg.fillColor = new paper.Color(backgroundColor)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (borderColor) {
|
|
41
|
+
bg.strokeColor = new paper.Color(borderColor)
|
|
42
|
+
bg.strokeWidth = borderWidth
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (shadow) {
|
|
46
|
+
bg.shadowColor = new paper.Color(shadow.color || '#000000')
|
|
47
|
+
bg.shadowBlur = shadow.blur || 5
|
|
48
|
+
bg.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (opacity !== 1) {
|
|
52
|
+
bg.opacity = opacity
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
items.push(bg)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 图标内容
|
|
59
|
+
if (icon) {
|
|
60
|
+
// 判断是否为图片路径(本地文件、http、data URL)
|
|
61
|
+
const isImagePath = icon.startsWith('http') || icon.startsWith('data:') ||
|
|
62
|
+
(icon.match(/\.(png|jpg|jpeg|gif|svg|webp|bmp)$/i) && !icon.match(/[\u4e00-\u9fa5]/))
|
|
63
|
+
|
|
64
|
+
if (isImagePath) {
|
|
65
|
+
// 图片图标 - 使用 imageLoader
|
|
66
|
+
const padding = backgroundColor ? 8 : 0
|
|
67
|
+
const iconSize = size - padding * 2
|
|
68
|
+
const { raster } = await loadImageAsRaster(project, icon, { x: x + padding, y: y + padding, width: iconSize, height: iconSize }, opacity)
|
|
69
|
+
items.push(raster)
|
|
70
|
+
} else {
|
|
71
|
+
// Emoji 或文字图标
|
|
72
|
+
const fontSize = Math.min(size * 0.6, 64)
|
|
73
|
+
const textItem = new paper.PointText({
|
|
74
|
+
point: [x + size / 2, y + size / 2 + fontSize / 3],
|
|
75
|
+
content: icon,
|
|
76
|
+
fontSize,
|
|
77
|
+
justification: 'center',
|
|
78
|
+
})
|
|
79
|
+
if (color) {
|
|
80
|
+
textItem.fillColor = new paper.Color(color)
|
|
81
|
+
}
|
|
82
|
+
if (opacity !== 1) textItem.opacity = opacity
|
|
83
|
+
items.push(textItem)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
type: 'icon',
|
|
90
|
+
items: items.map(i => i.id),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = createIcon
|
|
@@ -28,6 +28,15 @@ const createChip = require('./chip')
|
|
|
28
28
|
const createChart = require('./chart')
|
|
29
29
|
const createWatermark = require('./watermark')
|
|
30
30
|
const createTable = require('./table')
|
|
31
|
+
const createButton = require('./button')
|
|
32
|
+
const createIcon = require('./icon')
|
|
33
|
+
const createQRCode = require('./qrcode')
|
|
34
|
+
const createFrame = require('./frame')
|
|
35
|
+
const createBubble = require('./bubble')
|
|
36
|
+
const createRibbon = require('./ribbon')
|
|
37
|
+
const createSeal = require('./seal')
|
|
38
|
+
const createHighlightText = require('./highlightText')
|
|
39
|
+
const createBarcode = require('./barcode')
|
|
31
40
|
|
|
32
41
|
module.exports = {
|
|
33
42
|
// 原有组件
|
|
@@ -59,4 +68,14 @@ module.exports = {
|
|
|
59
68
|
createChart,
|
|
60
69
|
createWatermark,
|
|
61
70
|
createTable,
|
|
71
|
+
// 新增设计组件
|
|
72
|
+
createButton,
|
|
73
|
+
createIcon,
|
|
74
|
+
createQRCode,
|
|
75
|
+
createFrame,
|
|
76
|
+
createBubble,
|
|
77
|
+
createRibbon,
|
|
78
|
+
createSeal,
|
|
79
|
+
createHighlightText,
|
|
80
|
+
createBarcode,
|
|
62
81
|
}
|
|
@@ -26,7 +26,8 @@ const paper = require('paper')
|
|
|
26
26
|
*/
|
|
27
27
|
function createListItem(project, canvas, args) {
|
|
28
28
|
const {
|
|
29
|
-
x
|
|
29
|
+
x = 0,
|
|
30
|
+
y = 0,
|
|
30
31
|
width = 400,
|
|
31
32
|
icon = '→',
|
|
32
33
|
title,
|
|
@@ -109,7 +110,7 @@ function createListItem(project, canvas, args) {
|
|
|
109
110
|
elements.push({ type: 'text', id: badgeText.id })
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
return { success: true, elements }
|
|
113
|
+
return { success: true, elements, type: 'listItem' }
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
/**
|
|
@@ -117,7 +118,7 @@ function createListItem(project, canvas, args) {
|
|
|
117
118
|
*/
|
|
118
119
|
function createList(project, canvas, args) {
|
|
119
120
|
const {
|
|
120
|
-
x, y,
|
|
121
|
+
x = 0, y = 0,
|
|
121
122
|
items = [],
|
|
122
123
|
gap = 10,
|
|
123
124
|
width = 400,
|
|
@@ -139,8 +140,8 @@ function createList(project, canvas, args) {
|
|
|
139
140
|
success: true,
|
|
140
141
|
elements,
|
|
141
142
|
height: currentY - y,
|
|
143
|
+
type: 'list',
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
module.exports = createListItem
|
|
146
|
-
module.exports.createList = createList
|
|
147
|
+
module.exports = { createListItem, createList }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 二维码组件 - 本地生成
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const QRCode = require('qrcode')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建二维码
|
|
10
|
+
*/
|
|
11
|
+
async function createQRCode(project, args) {
|
|
12
|
+
const {
|
|
13
|
+
x = 0,
|
|
14
|
+
y = 0,
|
|
15
|
+
size = 200,
|
|
16
|
+
content = '',
|
|
17
|
+
color = '#000000',
|
|
18
|
+
backgroundColor = '#ffffff',
|
|
19
|
+
logo,
|
|
20
|
+
logoSize,
|
|
21
|
+
opacity = 1,
|
|
22
|
+
} = args
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// 生成 QR 码图片
|
|
26
|
+
const qrDataUrl = await QRCode.toDataURL(content, {
|
|
27
|
+
width: size,
|
|
28
|
+
margin: 2,
|
|
29
|
+
color: {
|
|
30
|
+
dark: color,
|
|
31
|
+
light: backgroundColor,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// 使用 imageLoader 加载
|
|
36
|
+
const { loadImageAsRaster } = require('../utils/imageLoader')
|
|
37
|
+
const { raster } = await loadImageAsRaster(project, qrDataUrl, { x, y, width: size, height: size }, opacity)
|
|
38
|
+
const items = [raster]
|
|
39
|
+
|
|
40
|
+
// 添加logo
|
|
41
|
+
if (logo) {
|
|
42
|
+
const ls = logoSize || size * 0.2
|
|
43
|
+
const { raster: logoRaster } = await loadImageAsRaster(project, logo, {
|
|
44
|
+
x: x + (size - ls) / 2,
|
|
45
|
+
y: y + (size - ls) / 2,
|
|
46
|
+
width: ls,
|
|
47
|
+
height: ls
|
|
48
|
+
}, opacity)
|
|
49
|
+
|
|
50
|
+
// 添加白色背景
|
|
51
|
+
const logoBg = new paper.Path.Rectangle({
|
|
52
|
+
point: [x + (size - ls) / 2 - 5, y + (size - ls) / 2 - 5],
|
|
53
|
+
size: [ls + 10, ls + 10],
|
|
54
|
+
fillColor: new paper.Color(backgroundColor),
|
|
55
|
+
radius: 4,
|
|
56
|
+
})
|
|
57
|
+
logoBg.sendToBack()
|
|
58
|
+
items.push(logoRaster)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
type: 'qrcode',
|
|
64
|
+
items: items.map(i => i.id),
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: `Failed to generate QR code: ${err.message}`,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = createQRCode
|