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
|
@@ -1,11 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 海报组件化生成器
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 支持 JSON 配置驱动,一次调用生成完整海报
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const paper = require('paper')
|
|
8
8
|
|
|
9
|
+
// 导入新组件
|
|
10
|
+
const createButton = require('./components/button')
|
|
11
|
+
const createIcon = require('./components/icon')
|
|
12
|
+
const createQRCode = require('./components/qrcode')
|
|
13
|
+
const createFrame = require('./components/frame')
|
|
14
|
+
const createBubble = require('./components/bubble')
|
|
15
|
+
const createRibbon = require('./components/ribbon')
|
|
16
|
+
const createSeal = require('./components/seal')
|
|
17
|
+
const createHighlightText = require('./components/highlightText')
|
|
18
|
+
const createBarcode = require('./components/barcode')
|
|
19
|
+
const { loadImageAsRaster } = require('./utils/imageLoader')
|
|
20
|
+
|
|
21
|
+
// 组件包装函数
|
|
22
|
+
async function createButtonComponent(project, canvas, args) {
|
|
23
|
+
return await createButton(project, args)
|
|
24
|
+
}
|
|
25
|
+
async function createIconComponent(project, canvas, args) {
|
|
26
|
+
return await createIcon(project, args)
|
|
27
|
+
}
|
|
28
|
+
async function createQRCodeComponent(project, canvas, args) {
|
|
29
|
+
return await createQRCode(project, args)
|
|
30
|
+
}
|
|
31
|
+
async function createFrameComponent(project, canvas, args) {
|
|
32
|
+
return await createFrame(project, args)
|
|
33
|
+
}
|
|
34
|
+
async function createBubbleComponent(project, canvas, args) {
|
|
35
|
+
return await createBubble(project, args)
|
|
36
|
+
}
|
|
37
|
+
async function createRibbonComponent(project, canvas, args) {
|
|
38
|
+
return await createRibbon(project, args)
|
|
39
|
+
}
|
|
40
|
+
async function createSealComponent(project, canvas, args) {
|
|
41
|
+
return await createSeal(project, args)
|
|
42
|
+
}
|
|
43
|
+
async function createHighlightTextComponent(project, canvas, args) {
|
|
44
|
+
return await createHighlightText(project, args)
|
|
45
|
+
}
|
|
46
|
+
async function createBarcodeComponent(project, canvas, args) {
|
|
47
|
+
return await createBarcode(project, args)
|
|
48
|
+
}
|
|
49
|
+
|
|
9
50
|
/**
|
|
10
51
|
* 辅助函数:将元素添加到活跃层
|
|
11
52
|
*/
|
|
@@ -74,6 +115,16 @@ const COMPONENT_TYPES = {
|
|
|
74
115
|
timeline: 'timeline',
|
|
75
116
|
listItem: 'listItem',
|
|
76
117
|
notification: 'notification',
|
|
118
|
+
// 设计组件
|
|
119
|
+
button: 'button',
|
|
120
|
+
icon: 'icon',
|
|
121
|
+
qrcode: 'qrcode',
|
|
122
|
+
frame: 'frame',
|
|
123
|
+
bubble: 'bubble',
|
|
124
|
+
ribbon: 'ribbon',
|
|
125
|
+
seal: 'seal',
|
|
126
|
+
highlightText: 'highlightText',
|
|
127
|
+
barcode: 'barcode',
|
|
77
128
|
}
|
|
78
129
|
|
|
79
130
|
/**
|
|
@@ -108,13 +159,13 @@ async function createFromConfig(project, canvas, config) {
|
|
|
108
159
|
/**
|
|
109
160
|
* 根据配置创建单个组件
|
|
110
161
|
*/
|
|
111
|
-
function createComponent(project, canvas, config) {
|
|
162
|
+
async function createComponent(project, canvas, config) {
|
|
112
163
|
const { type, ...args } = config
|
|
113
164
|
|
|
114
165
|
switch (type) {
|
|
115
166
|
// 基础元素
|
|
116
167
|
case 'background':
|
|
117
|
-
return createBackgroundElement(project, canvas, args)
|
|
168
|
+
return await createBackgroundElement(project, canvas, args)
|
|
118
169
|
case 'rectangle':
|
|
119
170
|
return createRectangleElement(project, args)
|
|
120
171
|
case 'circle':
|
|
@@ -126,67 +177,87 @@ function createComponent(project, canvas, config) {
|
|
|
126
177
|
case 'text':
|
|
127
178
|
return createTextElement(project, args)
|
|
128
179
|
case 'artText':
|
|
129
|
-
return createArtTextElement(project, args)
|
|
180
|
+
return await createArtTextElement(project, args)
|
|
130
181
|
case 'image':
|
|
131
|
-
return createImageElement(project, args)
|
|
182
|
+
return await createImageElement(project, args)
|
|
132
183
|
case 'svg':
|
|
133
|
-
return createSVGElement(project, args)
|
|
184
|
+
return await createSVGElement(project, args)
|
|
134
185
|
case 'imageFrame':
|
|
135
|
-
return createImageFrameComponent(project, canvas, args)
|
|
186
|
+
return await createImageFrameComponent(project, canvas, args)
|
|
136
187
|
case 'columns':
|
|
137
|
-
return createColumnsComponent(project, canvas, args)
|
|
188
|
+
return await createColumnsComponent(project, canvas, args)
|
|
138
189
|
case 'grid':
|
|
139
|
-
return createGridComponent(project, canvas, args)
|
|
190
|
+
return await createGridComponent(project, canvas, args)
|
|
140
191
|
|
|
141
192
|
// 装饰组件
|
|
142
193
|
case 'star':
|
|
143
|
-
return createStarComponent(project, canvas, args)
|
|
194
|
+
return await createStarComponent(project, canvas, args)
|
|
144
195
|
case 'arrow':
|
|
145
|
-
return createArrowComponent(project, canvas, args)
|
|
196
|
+
return await createArrowComponent(project, canvas, args)
|
|
146
197
|
case 'progressCircle':
|
|
147
|
-
return createProgressCircleComponent(project, canvas, args)
|
|
198
|
+
return await createProgressCircleComponent(project, canvas, args)
|
|
148
199
|
case 'chip':
|
|
149
|
-
return createChipComponent(project, canvas, args)
|
|
200
|
+
return await createChipComponent(project, canvas, args)
|
|
150
201
|
case 'chart':
|
|
151
|
-
return createChartComponent(project, canvas, args)
|
|
202
|
+
return await createChartComponent(project, canvas, args)
|
|
152
203
|
case 'watermark':
|
|
153
|
-
return createWatermarkComponent(project, canvas, args)
|
|
204
|
+
return await createWatermarkComponent(project, canvas, args)
|
|
154
205
|
case 'table':
|
|
155
|
-
return createTableComponent(project, canvas, args)
|
|
206
|
+
return await createTableComponent(project, canvas, args)
|
|
156
207
|
|
|
157
208
|
// 高级组件
|
|
158
209
|
case 'card':
|
|
159
|
-
return createCardComponent(project, canvas, args)
|
|
210
|
+
return await createCardComponent(project, canvas, args)
|
|
160
211
|
case 'badge':
|
|
161
|
-
return createBadgeComponent(project, canvas, args)
|
|
212
|
+
return await createBadgeComponent(project, canvas, args)
|
|
162
213
|
case 'cta':
|
|
163
|
-
return createCTAComponent(project, canvas, args)
|
|
214
|
+
return await createCTAComponent(project, canvas, args)
|
|
164
215
|
case 'feature':
|
|
165
|
-
return createFeatureComponent(project, canvas, args)
|
|
216
|
+
return await createFeatureComponent(project, canvas, args)
|
|
166
217
|
case 'featureGrid':
|
|
167
|
-
return createFeatureGridComponent(project, canvas, args)
|
|
218
|
+
return await createFeatureGridComponent(project, canvas, args)
|
|
168
219
|
case 'divider':
|
|
169
|
-
return createDividerComponent(project, canvas, args)
|
|
220
|
+
return await createDividerComponent(project, canvas, args)
|
|
170
221
|
case 'avatar':
|
|
171
|
-
return createAvatarComponent(project, canvas, args)
|
|
222
|
+
return await createAvatarComponent(project, canvas, args)
|
|
172
223
|
case 'progress':
|
|
173
|
-
return createProgressComponent(project, canvas, args)
|
|
224
|
+
return await createProgressComponent(project, canvas, args)
|
|
174
225
|
case 'rating':
|
|
175
|
-
return createRatingComponent(project, canvas, args)
|
|
226
|
+
return await createRatingComponent(project, canvas, args)
|
|
176
227
|
case 'quote':
|
|
177
|
-
return createQuoteComponent(project, canvas, args)
|
|
228
|
+
return await createQuoteComponent(project, canvas, args)
|
|
178
229
|
case 'statCard':
|
|
179
|
-
return createStatCardComponent(project, canvas, args)
|
|
230
|
+
return await createStatCardComponent(project, canvas, args)
|
|
180
231
|
case 'tagCloud':
|
|
181
|
-
return createTagCloudComponent(project, canvas, args)
|
|
232
|
+
return await createTagCloudComponent(project, canvas, args)
|
|
182
233
|
case 'stepper':
|
|
183
|
-
return createStepperComponent(project, canvas, args)
|
|
234
|
+
return await createStepperComponent(project, canvas, args)
|
|
184
235
|
case 'timeline':
|
|
185
|
-
return createTimelineComponent(project, canvas, args)
|
|
236
|
+
return await createTimelineComponent(project, canvas, args)
|
|
186
237
|
case 'listItem':
|
|
187
|
-
return createListItemComponent(project, canvas, args)
|
|
238
|
+
return await createListItemComponent(project, canvas, args)
|
|
188
239
|
case 'notification':
|
|
189
|
-
return createNotificationComponent(project, canvas, args)
|
|
240
|
+
return await createNotificationComponent(project, canvas, args)
|
|
241
|
+
|
|
242
|
+
// 设计组件
|
|
243
|
+
case 'button':
|
|
244
|
+
return await createButtonComponent(project, canvas, args)
|
|
245
|
+
case 'icon':
|
|
246
|
+
return await createIconComponent(project, canvas, args)
|
|
247
|
+
case 'qrcode':
|
|
248
|
+
return await createQRCodeComponent(project, canvas, args)
|
|
249
|
+
case 'frame':
|
|
250
|
+
return await createFrameComponent(project, canvas, args)
|
|
251
|
+
case 'bubble':
|
|
252
|
+
return await createBubbleComponent(project, canvas, args)
|
|
253
|
+
case 'ribbon':
|
|
254
|
+
return await createRibbonComponent(project, canvas, args)
|
|
255
|
+
case 'seal':
|
|
256
|
+
return await createSealComponent(project, canvas, args)
|
|
257
|
+
case 'highlightText':
|
|
258
|
+
return await createHighlightTextComponent(project, canvas, args)
|
|
259
|
+
case 'barcode':
|
|
260
|
+
return createBarcodeComponent(project, canvas, args)
|
|
190
261
|
|
|
191
262
|
default:
|
|
192
263
|
return { success: false, error: `Unknown component type: ${type}` }
|
|
@@ -195,8 +266,71 @@ function createComponent(project, canvas, config) {
|
|
|
195
266
|
|
|
196
267
|
// ============= 基础元素创建函数 =============
|
|
197
268
|
|
|
198
|
-
function createBackgroundElement(project, canvas, { color, gradient }) {
|
|
199
|
-
if (
|
|
269
|
+
function createBackgroundElement(project, canvas, { color, gradient, image }) {
|
|
270
|
+
if (image) {
|
|
271
|
+
const fs = require('fs')
|
|
272
|
+
const path = require('path')
|
|
273
|
+
|
|
274
|
+
// 确保 image 是字符串
|
|
275
|
+
if (typeof image !== 'string') {
|
|
276
|
+
return { success: false, error: 'Background image must be a string' }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 本地文件路径
|
|
280
|
+
let absolutePath = image
|
|
281
|
+
if (!path.isAbsolute(absolutePath)) {
|
|
282
|
+
absolutePath = path.join(process.cwd(), absolutePath)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!fs.existsSync(absolutePath)) {
|
|
286
|
+
throw new Error(`背景图片文件不存在: ${absolutePath}`)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const buffer = fs.readFileSync(absolutePath)
|
|
290
|
+
const ext = path.extname(absolutePath).toLowerCase()
|
|
291
|
+
const mimeTypes = {
|
|
292
|
+
'.png': 'image/png',
|
|
293
|
+
'.jpg': 'image/jpeg',
|
|
294
|
+
'.jpeg': 'image/jpeg',
|
|
295
|
+
'.gif': 'image/gif',
|
|
296
|
+
'.webp': 'image/webp',
|
|
297
|
+
'.bmp': 'image/bmp'
|
|
298
|
+
}
|
|
299
|
+
const mimeType = mimeTypes[ext] || 'image/png'
|
|
300
|
+
const imageUrl = `data:${mimeType};base64,${buffer.toString('base64')}`
|
|
301
|
+
|
|
302
|
+
const raster = new paper.Raster(imageUrl)
|
|
303
|
+
|
|
304
|
+
// 添加到项目活动层
|
|
305
|
+
if (project && project.activeLayer) {
|
|
306
|
+
project.activeLayer.addChild(raster)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
raster.onLoad = () => {
|
|
310
|
+
// 计算缩放比例,使图片覆盖整个画布(cover 模式)
|
|
311
|
+
const canvasRatio = canvas.width / canvas.height
|
|
312
|
+
const imageRatio = raster.width / raster.height
|
|
313
|
+
|
|
314
|
+
let scaledWidth, scaledHeight, offsetX, offsetY
|
|
315
|
+
|
|
316
|
+
if (imageRatio > canvasRatio) {
|
|
317
|
+
// 图片更宽,以高度为基准缩放
|
|
318
|
+
scaledHeight = canvas.height
|
|
319
|
+
scaledWidth = raster.width * (canvas.height / raster.height)
|
|
320
|
+
offsetX = (canvas.width - scaledWidth) / 2
|
|
321
|
+
offsetY = 0
|
|
322
|
+
} else {
|
|
323
|
+
// 图片更高,以宽度为基准缩放
|
|
324
|
+
scaledWidth = canvas.width
|
|
325
|
+
scaledHeight = raster.height * (canvas.width / raster.width)
|
|
326
|
+
offsetX = 0
|
|
327
|
+
offsetY = (canvas.height - scaledHeight) / 2
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
raster.bounds = new paper.Rectangle(offsetX, offsetY, scaledWidth, scaledHeight)
|
|
331
|
+
raster.sendToBack()
|
|
332
|
+
}
|
|
333
|
+
} else if (gradient) {
|
|
200
334
|
const paperColors = gradient.colors.map(c => new paper.Color(c))
|
|
201
335
|
const { type, direction } = gradient
|
|
202
336
|
|
|
@@ -248,13 +382,23 @@ function createRectangleElement(project, { x, y, width, height, fill, stroke, st
|
|
|
248
382
|
}
|
|
249
383
|
if (opacity !== undefined) rect.opacity = opacity
|
|
250
384
|
|
|
385
|
+
if (project && project.activeLayer) {
|
|
386
|
+
project.activeLayer.addChild(rect)
|
|
387
|
+
}
|
|
388
|
+
|
|
251
389
|
return { success: true, id: rect.id, type: 'rectangle' }
|
|
252
390
|
}
|
|
253
391
|
|
|
254
|
-
function createCircleElement(project, { cx, cy, rx, ry, fill, stroke, strokeWidth, opacity }) {
|
|
392
|
+
function createCircleElement(project, { x, y, cx, cy, radius, rx, ry, fill, stroke, strokeWidth, opacity }) {
|
|
393
|
+
// 兼容 x, y, radius 或 cx, cy, rx, ry 格式
|
|
394
|
+
const centerX = cx || x
|
|
395
|
+
const centerY = cy || y
|
|
396
|
+
const radiusX = rx || radius || 30
|
|
397
|
+
const radiusY = ry || radius || 30
|
|
398
|
+
|
|
255
399
|
const circle = new paper.Path.Ellipse({
|
|
256
|
-
center: [
|
|
257
|
-
radius: [
|
|
400
|
+
center: [centerX, centerY],
|
|
401
|
+
radius: [radiusX, radiusY],
|
|
258
402
|
})
|
|
259
403
|
|
|
260
404
|
if (fill) circle.fillColor = new paper.Color(fill)
|
|
@@ -264,6 +408,10 @@ function createCircleElement(project, { cx, cy, rx, ry, fill, stroke, strokeWidt
|
|
|
264
408
|
}
|
|
265
409
|
if (opacity !== undefined) circle.opacity = opacity
|
|
266
410
|
|
|
411
|
+
if (project && project.activeLayer) {
|
|
412
|
+
project.activeLayer.addChild(circle)
|
|
413
|
+
}
|
|
414
|
+
|
|
267
415
|
return { success: true, id: circle.id, type: 'circle' }
|
|
268
416
|
}
|
|
269
417
|
|
|
@@ -275,6 +423,10 @@ function createLineElement(project, { x1, y1, x2, y2, stroke, strokeWidth }) {
|
|
|
275
423
|
strokeWidth: strokeWidth || 2,
|
|
276
424
|
})
|
|
277
425
|
|
|
426
|
+
if (project && project.activeLayer) {
|
|
427
|
+
project.activeLayer.addChild(line)
|
|
428
|
+
}
|
|
429
|
+
|
|
278
430
|
return { success: true, id: line.id, type: 'line' }
|
|
279
431
|
}
|
|
280
432
|
|
|
@@ -292,6 +444,10 @@ function createPolygonElement(project, { cx, cy, radius, sides, fill, stroke, st
|
|
|
292
444
|
}
|
|
293
445
|
if (opacity !== undefined) polygon.opacity = opacity
|
|
294
446
|
|
|
447
|
+
if (project && project.activeLayer) {
|
|
448
|
+
project.activeLayer.addChild(polygon)
|
|
449
|
+
}
|
|
450
|
+
|
|
295
451
|
return { success: true, id: polygon.id, type: 'polygon' }
|
|
296
452
|
}
|
|
297
453
|
|
|
@@ -313,6 +469,10 @@ function createTextElement(project, { text, x, y, fontSize, fontFamily, color, a
|
|
|
313
469
|
textItem.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
314
470
|
}
|
|
315
471
|
|
|
472
|
+
if (project && project.activeLayer) {
|
|
473
|
+
project.activeLayer.addChild(textItem)
|
|
474
|
+
}
|
|
475
|
+
|
|
316
476
|
return { success: true, id: textItem.id, type: 'text' }
|
|
317
477
|
}
|
|
318
478
|
|
|
@@ -348,100 +508,82 @@ function createArtTextElement(project, { text, x, y, fontSize, fontFamily, gradi
|
|
|
348
508
|
textItem.shadowOffset = new paper.Point(shadow.offsetX || 3, shadow.offsetY || 3)
|
|
349
509
|
}
|
|
350
510
|
|
|
511
|
+
if (project && project.activeLayer) {
|
|
512
|
+
project.activeLayer.addChild(textItem)
|
|
513
|
+
}
|
|
514
|
+
|
|
351
515
|
return { success: true, id: textItem.id, type: 'artText' }
|
|
352
516
|
}
|
|
353
517
|
|
|
354
|
-
function createImageElement(project, { src, x, y, width, height, opacity }) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// 将图片转换为 Base64 data URL
|
|
359
|
-
let imageUrl = src
|
|
360
|
-
|
|
361
|
-
// 如果是本地文件,转换为 Base64
|
|
362
|
-
if (!src.startsWith('data:') && !src.startsWith('http')) {
|
|
363
|
-
let absolutePath = src
|
|
364
|
-
if (!path.isAbsolute(absolutePath)) {
|
|
365
|
-
absolutePath = path.join(process.cwd(), absolutePath)
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (!fs.existsSync(absolutePath)) {
|
|
369
|
-
return { success: false, error: `文件不存在: ${absolutePath}` }
|
|
370
|
-
}
|
|
518
|
+
async function createImageElement(project, { src, x = 0, y = 0, width, height, opacity = 1 }) {
|
|
519
|
+
try {
|
|
520
|
+
const { raster } = await loadImageAsRaster(project, src, { x, y, width, height }, opacity)
|
|
371
521
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
'.jpg': 'image/jpeg',
|
|
377
|
-
'.jpeg': 'image/jpeg',
|
|
378
|
-
'.gif': 'image/gif',
|
|
379
|
-
'.webp': 'image/webp',
|
|
380
|
-
'.bmp': 'image/bmp'
|
|
522
|
+
return {
|
|
523
|
+
success: true,
|
|
524
|
+
id: raster.id,
|
|
525
|
+
type: 'image',
|
|
381
526
|
}
|
|
382
|
-
|
|
383
|
-
|
|
527
|
+
} catch (err) {
|
|
528
|
+
return { success: false, error: `Failed to load image: ${err.message}` }
|
|
384
529
|
}
|
|
530
|
+
}
|
|
385
531
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
raster.onLoad = () => {
|
|
389
|
-
if (width && height) {
|
|
390
|
-
raster.bounds = new paper.Rectangle(x, y, width, height)
|
|
391
|
-
} else if (width) {
|
|
392
|
-
const scale = width / raster.width
|
|
393
|
-
raster.bounds = new paper.Rectangle(x, y, width, raster.height * scale)
|
|
394
|
-
} else if (height) {
|
|
395
|
-
const scale = height / raster.height
|
|
396
|
-
raster.bounds = new paper.Rectangle(x, y, raster.width * scale, height)
|
|
397
|
-
} else {
|
|
398
|
-
raster.position = new paper.Point(x, y)
|
|
399
|
-
}
|
|
532
|
+
const fs = require('fs')
|
|
400
533
|
|
|
401
|
-
|
|
402
|
-
|
|
534
|
+
async function createSVGElement(project, { src, x = 0, y = 0, width, height, opacity = 1 }) {
|
|
535
|
+
const fs = require('fs')
|
|
536
|
+
const path = require('path')
|
|
403
537
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
type: 'image',
|
|
408
|
-
width: raster.width,
|
|
409
|
-
height: raster.height,
|
|
538
|
+
// 确保 src 是字符串
|
|
539
|
+
if (typeof src !== 'string') {
|
|
540
|
+
return { success: false, error: 'SVG source must be a string' }
|
|
410
541
|
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const fs = require('fs')
|
|
414
542
|
|
|
415
|
-
function createSVGElement(project, { src, x, y, width, height, opacity }) {
|
|
416
543
|
let svgContent = src
|
|
417
544
|
|
|
418
545
|
// 如果是文件路径,读取文件内容
|
|
419
546
|
if (!src.startsWith('<') && !src.startsWith('<?xml')) {
|
|
420
547
|
try {
|
|
421
|
-
|
|
548
|
+
let filePath = src
|
|
549
|
+
if (!path.isAbsolute(filePath)) {
|
|
550
|
+
filePath = path.join(process.cwd(), filePath)
|
|
551
|
+
}
|
|
552
|
+
svgContent = fs.readFileSync(filePath, 'utf8')
|
|
422
553
|
} catch (e) {
|
|
423
554
|
return { success: false, error: `Failed to read SVG file: ${e.message}` }
|
|
424
555
|
}
|
|
425
556
|
}
|
|
426
557
|
|
|
427
|
-
// 导入 SVG
|
|
428
|
-
|
|
558
|
+
// 导入 SVG 到指定项目
|
|
559
|
+
let svg
|
|
560
|
+
try {
|
|
561
|
+
svg = project.importSVG(svgContent)
|
|
562
|
+
} catch (e) {
|
|
563
|
+
return { success: false, error: `Failed to import SVG: ${e.message}` }
|
|
564
|
+
}
|
|
429
565
|
|
|
430
566
|
if (!svg) {
|
|
431
567
|
return { success: false, error: 'Failed to import SVG' }
|
|
432
568
|
}
|
|
433
569
|
|
|
570
|
+
// 确保 SVG 添加到活动层
|
|
571
|
+
if (project && project.activeLayer && svg.parent !== project.activeLayer) {
|
|
572
|
+
project.activeLayer.addChild(svg)
|
|
573
|
+
}
|
|
574
|
+
|
|
434
575
|
// 设置位置
|
|
435
576
|
svg.position = new paper.Point(x, y)
|
|
436
577
|
|
|
437
578
|
// 设置尺寸
|
|
438
579
|
if (width && height) {
|
|
439
|
-
svg.bounds.width
|
|
440
|
-
svg.bounds.height
|
|
580
|
+
const scaleX = width / svg.bounds.width
|
|
581
|
+
const scaleY = height / svg.bounds.height
|
|
582
|
+
svg.scale(Math.min(scaleX, scaleY), svg.bounds.center)
|
|
441
583
|
} else if (width) {
|
|
442
|
-
svg.scale(width / svg.bounds.width)
|
|
584
|
+
svg.scale(width / svg.bounds.width, svg.bounds.center)
|
|
443
585
|
} else if (height) {
|
|
444
|
-
svg.scale(height / svg.bounds.height)
|
|
586
|
+
svg.scale(height / svg.bounds.height, svg.bounds.center)
|
|
445
587
|
}
|
|
446
588
|
|
|
447
589
|
// 设置透明度
|
|
@@ -543,9 +685,15 @@ function createCTAComponent(project, canvas, {
|
|
|
543
685
|
x, y, text,
|
|
544
686
|
background = '#007bff', color = '#ffffff',
|
|
545
687
|
border, fontSize = 20, padding = 25, radius = 8, shadow,
|
|
688
|
+
width: customWidth,
|
|
546
689
|
}) {
|
|
547
|
-
|
|
548
|
-
const
|
|
690
|
+
// 确保 text 是字符串
|
|
691
|
+
const textStr = String(text || '')
|
|
692
|
+
// 使用更准确的字符宽度估算:中文约1.0,英文约0.5
|
|
693
|
+
const chineseChars = (textStr.match(/[\u4e00-\u9fa5]/g) || []).length
|
|
694
|
+
const otherChars = textStr.length - chineseChars
|
|
695
|
+
const textWidth = chineseChars * fontSize * 1.0 + otherChars * fontSize * 0.5
|
|
696
|
+
const btnWidth = customWidth || (textWidth + padding * 2)
|
|
549
697
|
const btnHeight = fontSize + padding * 2
|
|
550
698
|
const btnX = x - btnWidth / 2
|
|
551
699
|
|
|
@@ -565,12 +713,18 @@ function createCTAComponent(project, canvas, {
|
|
|
565
713
|
|
|
566
714
|
const buttonText = new paper.PointText({
|
|
567
715
|
point: [x, y + btnHeight / 2 + fontSize / 3],
|
|
568
|
-
content:
|
|
716
|
+
content: textStr,
|
|
569
717
|
fontSize: fontSize,
|
|
570
718
|
fillColor: new paper.Color(color),
|
|
571
719
|
justification: 'center',
|
|
572
720
|
})
|
|
573
721
|
|
|
722
|
+
// 添加到项目
|
|
723
|
+
if (project && project.activeLayer) {
|
|
724
|
+
project.activeLayer.addChild(button)
|
|
725
|
+
project.activeLayer.addChild(buttonText)
|
|
726
|
+
}
|
|
727
|
+
|
|
574
728
|
return { success: true, elements: [{ type: 'rectangle', id: button.id }, { type: 'text', id: buttonText.id }], type: 'cta' }
|
|
575
729
|
}
|
|
576
730
|
|
|
@@ -627,6 +781,13 @@ function createFeatureGridComponent(project, canvas, {
|
|
|
627
781
|
}) {
|
|
628
782
|
const elements = []
|
|
629
783
|
|
|
784
|
+
// 确保 items 是数组
|
|
785
|
+
if (!Array.isArray(items)) {
|
|
786
|
+
items = []
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const rows = items.length > 0 ? Math.ceil(items.length / columns) : 0
|
|
790
|
+
|
|
630
791
|
for (let i = 0; i < items.length; i++) {
|
|
631
792
|
const item = items[i]
|
|
632
793
|
const col = i % columns
|
|
@@ -645,39 +806,56 @@ function createFeatureGridComponent(project, canvas, {
|
|
|
645
806
|
bg.opacity = 0.8
|
|
646
807
|
elements.push(bg)
|
|
647
808
|
|
|
809
|
+
// 添加到项目
|
|
810
|
+
if (project && project.activeLayer) {
|
|
811
|
+
project.activeLayer.addChild(bg)
|
|
812
|
+
}
|
|
813
|
+
|
|
648
814
|
const padding = 15
|
|
649
815
|
let offsetY = itemY + padding
|
|
650
816
|
|
|
651
817
|
if (item.icon) {
|
|
652
|
-
|
|
818
|
+
const iconText = new paper.PointText({
|
|
653
819
|
point: [itemX + padding, offsetY + 24],
|
|
654
820
|
content: item.icon,
|
|
655
821
|
fontSize: 28,
|
|
656
822
|
fillColor: new paper.Color(item.iconColor || '#00ff88'),
|
|
657
823
|
justification: 'left',
|
|
658
|
-
})
|
|
824
|
+
})
|
|
825
|
+
elements.push(iconText)
|
|
826
|
+
if (project && project.activeLayer) {
|
|
827
|
+
project.activeLayer.addChild(iconText)
|
|
828
|
+
}
|
|
659
829
|
offsetY += 35
|
|
660
830
|
}
|
|
661
831
|
|
|
662
832
|
if (item.title) {
|
|
663
|
-
|
|
833
|
+
const titleText = new paper.PointText({
|
|
664
834
|
point: [itemX + padding, offsetY + 18],
|
|
665
835
|
content: item.title,
|
|
666
836
|
fontSize: 16,
|
|
667
837
|
fillColor: new paper.Color(item.titleColor || '#ffffff'),
|
|
668
838
|
justification: 'left',
|
|
669
|
-
})
|
|
839
|
+
})
|
|
840
|
+
elements.push(titleText)
|
|
841
|
+
if (project && project.activeLayer) {
|
|
842
|
+
project.activeLayer.addChild(titleText)
|
|
843
|
+
}
|
|
670
844
|
offsetY += 22
|
|
671
845
|
}
|
|
672
846
|
|
|
673
847
|
if (item.description) {
|
|
674
|
-
|
|
848
|
+
const descText = new paper.PointText({
|
|
675
849
|
point: [itemX + padding, offsetY + 14],
|
|
676
850
|
content: item.description,
|
|
677
851
|
fontSize: 12,
|
|
678
852
|
fillColor: new paper.Color(item.descColor || '#888888'),
|
|
679
853
|
justification: 'left',
|
|
680
|
-
})
|
|
854
|
+
})
|
|
855
|
+
elements.push(descText)
|
|
856
|
+
if (project && project.activeLayer) {
|
|
857
|
+
project.activeLayer.addChild(descText)
|
|
858
|
+
}
|
|
681
859
|
}
|
|
682
860
|
}
|
|
683
861
|
|
|
@@ -685,7 +863,9 @@ function createFeatureGridComponent(project, canvas, {
|
|
|
685
863
|
success: true,
|
|
686
864
|
elements,
|
|
687
865
|
type: 'featureGrid',
|
|
688
|
-
|
|
866
|
+
width: columns * itemWidth + (columns - 1) * gap,
|
|
867
|
+
height: rows * itemHeight + Math.max(0, rows - 1) * gap,
|
|
868
|
+
rows,
|
|
689
869
|
cols: columns,
|
|
690
870
|
}
|
|
691
871
|
}
|
|
@@ -917,12 +1097,22 @@ function createStatCardComponent(project, canvas, { x, y, width = 200, height =
|
|
|
917
1097
|
|
|
918
1098
|
function createTagCloudComponent(project, canvas, { x, y, tags = [], fontSize = 14, padding = 12, gap = 10, maxWidth = 400 }) {
|
|
919
1099
|
const elements = []
|
|
1100
|
+
|
|
1101
|
+
// 确保 tags 是数组
|
|
1102
|
+
if (!Array.isArray(tags) || tags.length === 0) {
|
|
1103
|
+
return { success: true, elements: [], type: 'tagCloud', height: 0 }
|
|
1104
|
+
}
|
|
1105
|
+
|
|
920
1106
|
let currentX = x
|
|
921
1107
|
let currentY = y
|
|
922
1108
|
let rowHeight = 0
|
|
923
1109
|
|
|
924
1110
|
for (const tag of tags) {
|
|
925
|
-
|
|
1111
|
+
// 确保 tag.text 是字符串
|
|
1112
|
+
const tagText = String(tag.text || '')
|
|
1113
|
+
if (!tagText) continue
|
|
1114
|
+
|
|
1115
|
+
const textWidth = tagText.length * fontSize * 0.6
|
|
926
1116
|
const tagWidth = textWidth + padding * 2
|
|
927
1117
|
const tagHeight = fontSize + padding * 2
|
|
928
1118
|
|
|
@@ -940,13 +1130,22 @@ function createTagCloudComponent(project, canvas, { x, y, tags = [], fontSize =
|
|
|
940
1130
|
tagBg.fillColor = new paper.Color(tag.bgColor || '#e0e7ff')
|
|
941
1131
|
elements.push({ type: 'rectangle', id: tagBg.id })
|
|
942
1132
|
|
|
943
|
-
|
|
1133
|
+
// 添加到项目
|
|
1134
|
+
if (project && project.activeLayer) {
|
|
1135
|
+
project.activeLayer.addChild(tagBg)
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const tagTextEl = new paper.PointText({
|
|
944
1139
|
point: [currentX + tagWidth / 2, currentY + tagHeight / 2 + fontSize / 3],
|
|
945
|
-
content:
|
|
1140
|
+
content: tagText,
|
|
946
1141
|
fontSize: fontSize,
|
|
947
1142
|
fillColor: new paper.Color(tag.color || '#4338ca'),
|
|
948
1143
|
justification: 'center',
|
|
949
|
-
})
|
|
1144
|
+
})
|
|
1145
|
+
elements.push(tagTextEl)
|
|
1146
|
+
if (project && project.activeLayer) {
|
|
1147
|
+
project.activeLayer.addChild(tagTextEl)
|
|
1148
|
+
}
|
|
950
1149
|
|
|
951
1150
|
currentX += tagWidth + gap
|
|
952
1151
|
rowHeight = Math.max(rowHeight, tagHeight)
|
|
@@ -1018,24 +1217,34 @@ function createStepperComponent(project, canvas, { x, y, width = 600, steps = []
|
|
|
1018
1217
|
|
|
1019
1218
|
function createTimelineComponent(project, canvas, { x, y, width = 500, items = [], lineColor = '#e2e8f0', dotColor = '#6366f1', dotSize = 16, gap = 60 }) {
|
|
1020
1219
|
const elements = []
|
|
1220
|
+
|
|
1221
|
+
// 确保 items 是数组
|
|
1222
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
1223
|
+
return { success: true, elements: [], type: 'timeline', height: 0 }
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1021
1226
|
const centerX = x + 80
|
|
1022
1227
|
const contentX = x + 120
|
|
1023
1228
|
|
|
1024
1229
|
if (items.length > 1) {
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
strokeColor: new paper.Color(lineColor),
|
|
1031
|
-
strokeWidth: 2,
|
|
1032
|
-
}).id
|
|
1230
|
+
const mainLine = new paper.Path.Line({
|
|
1231
|
+
from: [centerX, y + dotSize / 2],
|
|
1232
|
+
to: [centerX, y + (items.length - 1) * gap + dotSize / 2],
|
|
1233
|
+
strokeColor: new paper.Color(lineColor),
|
|
1234
|
+
strokeWidth: 2,
|
|
1033
1235
|
})
|
|
1236
|
+
elements.push({ type: 'line', id: mainLine.id })
|
|
1237
|
+
if (project && project.activeLayer) {
|
|
1238
|
+
project.activeLayer.addChild(mainLine)
|
|
1239
|
+
}
|
|
1034
1240
|
}
|
|
1035
1241
|
|
|
1036
1242
|
for (let i = 0; i < items.length; i++) {
|
|
1243
|
+
const item = items[i]
|
|
1244
|
+
if (!item) continue
|
|
1245
|
+
|
|
1037
1246
|
const itemY = y + i * gap
|
|
1038
|
-
const isActive =
|
|
1247
|
+
const isActive = item.active !== false
|
|
1039
1248
|
|
|
1040
1249
|
const dot = new paper.Path.Circle({
|
|
1041
1250
|
center: [centerX, itemY + dotSize / 2],
|
|
@@ -1043,42 +1252,65 @@ function createTimelineComponent(project, canvas, { x, y, width = 500, items = [
|
|
|
1043
1252
|
})
|
|
1044
1253
|
dot.fillColor = new paper.Color(isActive ? dotColor : lineColor)
|
|
1045
1254
|
elements.push({ type: 'circle', id: dot.id })
|
|
1255
|
+
if (project && project.activeLayer) {
|
|
1256
|
+
project.activeLayer.addChild(dot)
|
|
1257
|
+
}
|
|
1046
1258
|
|
|
1047
|
-
if (
|
|
1048
|
-
|
|
1259
|
+
if (item.date) {
|
|
1260
|
+
const dateText = new paper.PointText({
|
|
1049
1261
|
point: [x + 10, itemY + dotSize / 2 + 5],
|
|
1050
|
-
content:
|
|
1262
|
+
content: item.date,
|
|
1051
1263
|
fontSize: 12,
|
|
1052
1264
|
fillColor: new paper.Color('#94a3b8'),
|
|
1053
1265
|
justification: 'left',
|
|
1054
|
-
})
|
|
1266
|
+
})
|
|
1267
|
+
elements.push(dateText)
|
|
1268
|
+
if (project && project.activeLayer) {
|
|
1269
|
+
project.activeLayer.addChild(dateText)
|
|
1270
|
+
}
|
|
1055
1271
|
}
|
|
1056
1272
|
|
|
1057
|
-
|
|
1273
|
+
const titleText = new paper.PointText({
|
|
1058
1274
|
point: [contentX, itemY + dotSize / 2 + 5],
|
|
1059
|
-
content:
|
|
1275
|
+
content: item.title || `Event ${i + 1}`,
|
|
1060
1276
|
fontSize: 16,
|
|
1061
1277
|
fillColor: new paper.Color(isActive ? '#1e293b' : '#94a3b8'),
|
|
1062
1278
|
justification: 'left',
|
|
1063
|
-
})
|
|
1279
|
+
})
|
|
1280
|
+
elements.push(titleText)
|
|
1281
|
+
if (project && project.activeLayer) {
|
|
1282
|
+
project.activeLayer.addChild(titleText)
|
|
1283
|
+
}
|
|
1064
1284
|
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1285
|
+
if (item.description) {
|
|
1286
|
+
const descText = new paper.PointText({
|
|
1067
1287
|
point: [contentX, itemY + dotSize / 2 + 28],
|
|
1068
|
-
content:
|
|
1288
|
+
content: item.description,
|
|
1069
1289
|
fontSize: 13,
|
|
1070
1290
|
fillColor: new paper.Color('#64748b'),
|
|
1071
1291
|
justification: 'left',
|
|
1072
|
-
})
|
|
1292
|
+
})
|
|
1293
|
+
elements.push(descText)
|
|
1294
|
+
if (project && project.activeLayer) {
|
|
1295
|
+
project.activeLayer.addChild(descText)
|
|
1296
|
+
}
|
|
1073
1297
|
}
|
|
1074
1298
|
}
|
|
1075
1299
|
|
|
1076
1300
|
return { success: true, elements, type: 'timeline', height: items.length * gap }
|
|
1077
1301
|
}
|
|
1078
1302
|
|
|
1079
|
-
function createListItemComponent(project, canvas, { x, y, width = 400, icon = '→', title, description, badge, badgeColor = '#6366f1', iconColor = '#6366f1', background = '#ffffff', borderColor = '#e5e7eb', height = 60, radius = 8 }) {
|
|
1303
|
+
function createListItemComponent(project, canvas, { x = 0, y = 0, width = 400, icon = '→', title, description, badge, badgeColor = '#6366f1', iconColor = '#6366f1', background = '#ffffff', borderColor = '#e5e7eb', height = 60, radius = 8 }) {
|
|
1080
1304
|
const elements = []
|
|
1081
1305
|
|
|
1306
|
+
// 添加到项目活动层
|
|
1307
|
+
const addToProject = (item) => {
|
|
1308
|
+
if (project && project.activeLayer) {
|
|
1309
|
+
project.activeLayer.addChild(item)
|
|
1310
|
+
}
|
|
1311
|
+
elements.push(item)
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1082
1314
|
const bg = new paper.Path.Rectangle({
|
|
1083
1315
|
point: [x, y],
|
|
1084
1316
|
size: [width, height],
|
|
@@ -1087,32 +1319,35 @@ function createListItemComponent(project, canvas, { x, y, width = 400, icon = '
|
|
|
1087
1319
|
bg.fillColor = new paper.Color(background)
|
|
1088
1320
|
bg.strokeColor = new paper.Color(borderColor)
|
|
1089
1321
|
bg.strokeWidth = 1
|
|
1090
|
-
|
|
1322
|
+
addToProject(bg)
|
|
1091
1323
|
|
|
1092
|
-
|
|
1324
|
+
const iconText = new paper.PointText({
|
|
1093
1325
|
point: [x + 15, y + height / 2 + 6],
|
|
1094
1326
|
content: icon,
|
|
1095
1327
|
fontSize: 20,
|
|
1096
1328
|
fillColor: new paper.Color(iconColor),
|
|
1097
1329
|
justification: 'center',
|
|
1098
|
-
})
|
|
1330
|
+
})
|
|
1331
|
+
addToProject(iconText)
|
|
1099
1332
|
|
|
1100
|
-
|
|
1333
|
+
const titleText = new paper.PointText({
|
|
1101
1334
|
point: [x + 50, y + height / 2 - 5],
|
|
1102
1335
|
content: title || 'List Item',
|
|
1103
1336
|
fontSize: 16,
|
|
1104
1337
|
fillColor: new paper.Color('#1e293b'),
|
|
1105
1338
|
justification: 'left',
|
|
1106
|
-
})
|
|
1339
|
+
})
|
|
1340
|
+
addToProject(titleText)
|
|
1107
1341
|
|
|
1108
1342
|
if (description) {
|
|
1109
|
-
|
|
1343
|
+
const descText = new paper.PointText({
|
|
1110
1344
|
point: [x + 50, y + height / 2 + 15],
|
|
1111
1345
|
content: description,
|
|
1112
1346
|
fontSize: 12,
|
|
1113
1347
|
fillColor: new paper.Color('#64748b'),
|
|
1114
1348
|
justification: 'left',
|
|
1115
|
-
})
|
|
1349
|
+
})
|
|
1350
|
+
addToProject(descText)
|
|
1116
1351
|
}
|
|
1117
1352
|
|
|
1118
1353
|
if (badge) {
|
|
@@ -1120,22 +1355,22 @@ function createListItemComponent(project, canvas, { x, y, width = 400, icon = '
|
|
|
1120
1355
|
const badgeX = x + width - badgeWidth - 15
|
|
1121
1356
|
const badgeY = y + (height - 24) / 2
|
|
1122
1357
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
size: [badgeWidth, 24],
|
|
1128
|
-
radius: 12,
|
|
1129
|
-
}).id
|
|
1358
|
+
const badgeRect = new paper.Path.Rectangle({
|
|
1359
|
+
point: [badgeX, badgeY],
|
|
1360
|
+
size: [badgeWidth, 24],
|
|
1361
|
+
radius: 12,
|
|
1130
1362
|
})
|
|
1363
|
+
badgeRect.fillColor = new paper.Color(badgeColor)
|
|
1364
|
+
addToProject(badgeRect)
|
|
1131
1365
|
|
|
1132
|
-
|
|
1366
|
+
const badgeText = new paper.PointText({
|
|
1133
1367
|
point: [badgeX + badgeWidth / 2, badgeY + 16],
|
|
1134
1368
|
content: badge,
|
|
1135
1369
|
fontSize: 12,
|
|
1136
1370
|
fillColor: new paper.Color('#ffffff'),
|
|
1137
1371
|
justification: 'center',
|
|
1138
|
-
})
|
|
1372
|
+
})
|
|
1373
|
+
addToProject(badgeText)
|
|
1139
1374
|
}
|
|
1140
1375
|
|
|
1141
1376
|
return { success: true, elements, type: 'listItem' }
|
|
@@ -1833,7 +2068,14 @@ function createTableComponent(project, canvas, {
|
|
|
1833
2068
|
cellColor = '#333333', fontSize = 12, headerFontSize = 13, striped = true, stripeColor = '#fafafa'
|
|
1834
2069
|
}) {
|
|
1835
2070
|
const elements = []
|
|
1836
|
-
|
|
2071
|
+
// 确保 columns 是数组
|
|
2072
|
+
if (!Array.isArray(columns) || columns.length === 0) {
|
|
2073
|
+
return { success: true, elements, type: 'table' }
|
|
2074
|
+
}
|
|
2075
|
+
// 确保 rows 是数组
|
|
2076
|
+
if (!Array.isArray(rows)) {
|
|
2077
|
+
rows = []
|
|
2078
|
+
}
|
|
1837
2079
|
|
|
1838
2080
|
const totalHeight = rowHeight * (rows.length + 1)
|
|
1839
2081
|
|