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.
Files changed (57) hide show
  1. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  2. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  3. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  4. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  5. package/.agent/plugins/poster-plugin/package.json +2 -1
  6. package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
  7. package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
  8. package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
  9. package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
  10. package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
  11. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
  12. package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
  13. package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
  14. package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
  15. package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
  16. package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
  17. package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
  18. package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
  19. package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
  20. package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
  21. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
  22. package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
  23. package/.agent/plugins/poster-plugin/src/composer.js +392 -150
  24. package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
  25. package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
  26. package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
  27. package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
  28. package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
  29. package/.agent/plugins/poster-plugin/src/index.js +430 -7
  30. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
  31. package/.agent/plugins/poster-plugin/test-background.svg +1 -0
  32. package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
  33. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  34. package/.agent/sessions/cli_default.json +1089 -145
  35. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
  36. package/.claude/settings.local.json +6 -1
  37. package/output/beef-love-poster.png +0 -0
  38. package/output/international-news-daily.png +0 -0
  39. package/package.json +2 -1
  40. package/plugins/extension-executor-plugin.js +33 -32
  41. package/plugins/file-system-plugin.js +4 -19
  42. package/plugins/subagent-plugin.js +37 -14
  43. package/plugins/weixin-plugin.js +167 -47
  44. package/poster-test-2.png +0 -0
  45. package/skills/poster-guide/SKILL.md +497 -5
  46. package/src/core/agent-chat.js +141 -8
  47. package/src/core/agent.js +6 -3
  48. package/calc_tokens_weixin.js +0 -81
  49. package/foliko-creative-3.png +0 -0
  50. package/foliko-creative-4.png +0 -0
  51. package/foliko-creative-5.png +0 -0
  52. package/story-cover-book-v2.png +0 -0
  53. package/story-cover-japanese-1.png +0 -0
  54. package/story-cover-japanese-2.png +0 -0
  55. package/story-cover-japanese-3.png +0 -0
  56. package/story-cover-moran.png +0 -0
  57. 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, y,
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