@anker-in/campaign-ui 0.2.11-beta.40 → 0.2.11-beta.42

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 (54) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  3. package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
  4. package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
  5. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +6 -6
  6. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
  7. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
  8. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
  9. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  10. package/dist/cjs/components/LiveChatWidget/components/MessageList.js +3 -3
  11. package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
  12. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
  13. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  14. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  15. package/dist/cjs/components/LiveChatWidget/types.d.ts +18 -11
  16. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  17. package/dist/cjs/components/index.d.ts +1 -1
  18. package/dist/cjs/components/index.js +1 -1
  19. package/dist/cjs/components/index.js.map +2 -2
  20. package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -109
  21. package/dist/cjs/stories/LiveChatWidget.stories.js +7 -166
  22. package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
  23. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  24. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  25. package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
  26. package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
  27. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +7 -7
  28. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
  29. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
  30. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
  31. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  32. package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
  33. package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
  34. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
  35. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  36. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  37. package/dist/esm/components/LiveChatWidget/types.d.ts +18 -11
  38. package/dist/esm/components/index.d.ts +1 -1
  39. package/dist/esm/components/index.js +1 -1
  40. package/dist/esm/components/index.js.map +2 -2
  41. package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -109
  42. package/dist/esm/stories/LiveChatWidget.stories.js +7 -166
  43. package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
  44. package/package.json +1 -1
  45. package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -23
  46. package/src/components/LiveChatWidget/api/chat.ts +8 -7
  47. package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +1 -5
  48. package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +17 -8
  49. package/src/components/LiveChatWidget/components/MessageList.tsx +11 -17
  50. package/src/components/LiveChatWidget/hooks/useChatState.ts +64 -98
  51. package/src/components/LiveChatWidget/types.ts +18 -11
  52. package/src/components/index.ts +2 -1
  53. package/src/stories/LiveChatWidget.stories.tsx +95 -660
  54. package/src/styles/livechat.css +29 -0
@@ -142,195 +142,62 @@ export default meta
142
142
  type Story = StoryObj<typeof LiveChatWidget>
143
143
 
144
144
  /**
145
- * 默认配置
146
- *
147
- * 最简单的使用方式,使用默认配置。
145
+ * 默认配置 - 展示所有功能
148
146
  */
149
147
  export const Default: Story = {
150
148
  args: {
149
+ // 基础配置
151
150
  loginUserId: 'test_test',
152
151
  apiBaseUrl: 'http://172.16.38.183:3003',
153
- cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',
154
- accessToken: '47b1aa2c0797043f9baba39388029d70',
155
- title: 'eufy',
156
-
157
- commonText: {
158
- learnMore: 'Learn More',
159
- total: 'Total',
160
- },
161
-
162
152
  site: 'beta.eufy.com',
163
153
  channelCode: 'dtc',
154
+ title: 'eufy AI Assistant',
155
+ cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',
156
+ accessToken: '47b1aa2c0797043f9baba39388029d70',
164
157
 
165
- // reCAPTCHA 配置(提供 sitekey 即自动启用)
166
- // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',
167
- recaptchaAction: 'chat',
158
+ // 自定义位置
159
+ position: { bottom: '24px', right: '30px' },
168
160
 
169
- welcomeMessage: '',
170
- bottomTips: '',
171
- },
172
- }
161
+ // 欢迎消息
162
+ welcomeMessage: `Welcome to eufy AI Assistant!
173
163
 
174
- /**
175
- * 自定义位置
176
- *
177
- * 使用自定义位置对象来控制气泡按钮的位置。
178
- */
179
- export const CustomPosition: Story = {
180
- args: {
181
- position: { bottom: '1.5rem', left: '1.5rem' },
182
- },
183
- }
164
+ I can help you with:
165
+ - Product recommendations
166
+ - Order tracking
167
+ - FAQs and support
184
168
 
185
- /**
186
- * 自定义品牌样式
187
- *
188
- * 自定义 Logo 和欢迎消息。
189
- */
190
- export const CustomBranding: Story = {
191
- args: {
192
- logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=100&h=100&fit=crop',
193
- welcomeMessage: '欢迎来到 Soundcore!我是您的专属 AI 助手 🎧',
194
- },
195
- }
169
+ How can I assist you today?`,
196
170
 
197
- /**
198
- * 带快捷回复
199
- *
200
- * 在欢迎消息后显示快捷回复按钮。
201
- */
202
- export const WithQuickReplies: Story = {
203
- args: {
171
+ // 快捷回复
204
172
  quickReplies: [
205
- {
206
- id: '1',
207
- label: '查询价格',
208
- value: '我想了解产品价格',
209
- icon: '💰',
210
- },
211
- {
212
- id: '2',
213
- label: '查询物流',
214
- value: '我想查询订单物流',
215
- icon: '📦',
216
- },
217
- {
218
- id: '3',
219
- label: '售后服务',
220
- value: '我需要售后帮助',
221
- icon: '🔧',
222
- },
223
- {
224
- id: '4',
225
- label: '产品推荐',
226
- value: '请推荐适合我的产品',
227
- icon: '⭐',
228
- },
173
+ { id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '📦' },
174
+ { id: '2', label: 'Track Order', value: 'I want to track my order', icon: '🚚' },
175
+ { id: '3', label: 'Support', value: 'I need help with my device', icon: '🔧' },
176
+ { id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '⭐' },
229
177
  ],
230
- },
231
- }
232
-
233
- /**
234
- * 自定义欢迎消息
235
- *
236
- * 自定义首次打开聊天窗口时的欢迎消息。
237
- */
238
- export const CustomWelcomeMessage: Story = {
239
- args: {
240
- welcomeMessage: `
241
- 👋 您好!欢迎来到 Soundcore 官方商城。
242
178
 
243
- 我是您的 AI 购物助手,可以帮您:
244
- - 🔍 查找产品
245
- - 💰 比较价格
246
- - 📦 跟踪订单
247
- - 💬 解答疑问
179
+ // 法规协议弹窗
180
+ complianceConfig: {
181
+ title: "Hi! I'm your eufy AI assistant.",
182
+ content: "AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",
183
+ checkboxText: 'By starting to use "Live Chat", you agree to Anker\'s <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.',
184
+ agreeButtonText: "Agree"
185
+ },
248
186
 
249
- 有什么我可以帮助您的吗?
250
- `.trim(),
251
- },
252
- }
187
+ // reCAPTCHA 配置(取消注释以启用)
188
+ // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',
189
+ recaptchaAction: 'livechat',
253
190
 
254
- /**
255
- * 事件回调
256
- *
257
- * 监听组件事件进行自定义处理。
258
- */
259
- export const WithEventCallbacks: Story = {
260
- args: {
261
- title: '',
191
+ // 显示新会话按钮
262
192
  showNewSessionButton: true,
263
193
 
264
- position: {
265
- bottom: '24px',
266
- right: '30px',
194
+ // 自定义文案
195
+ commonText: {
196
+ learnMore: 'Learn More',
197
+ total: 'Total',
267
198
  },
268
199
 
269
- chatBubbleIcon: 'https://cdn.shopify.com/s/files/1/0504/7094/4954/files/Rectangle_400770314.png?v=1768894153',
270
-
271
- // 自定义请求头示例
272
- headers: undefined,
273
-
274
- site: 'beta.eufy.com',
275
- },
276
-
277
- render: args => (
278
- <LiveChatWidget
279
- {...args}
280
- onOpen={() => {
281
- console.log('[Story] Chat opened')
282
- }}
283
- onClose={() => {
284
- console.log('[Story] Chat closed')
285
- }}
286
- onMessageSend={(message: string) => {
287
- console.log('[Story] Message sent:', message)
288
- }}
289
- onError={(error: Error) => {
290
- console.error('[Story] Error occurred:', error)
291
- }}
292
- onTextMessage={() => {
293
- console.log('📝 [Story] AI started replying with text message')
294
- }}
295
- onProductList={() => {
296
- console.log('📦 [Story] AI replied with product list card')
297
- }}
298
- onPromotionList={() => {
299
- console.log('🎉 [Story] AI replied with promotion list card')
300
- }}
301
- onAddToCart={(product: any) => {
302
- console.log('🛒 [Story] Add to Cart clicked!')
303
- console.log('📦 Product Info:', {
304
- id: product.shopifyId,
305
- title: product.title,
306
- price: product.price,
307
- imageUrl: product.imageUrl,
308
- productUrl: product.productUrl,
309
- })
310
- console.log('📋 Full Product Object:', product)
311
-
312
- // 模拟添加到购物车成功
313
- alert(`✅ "${product.title}" 已添加到购物车!\n\n价格: ${product.price.currency} ${product.price.amount}`)
314
- }}
315
- onCart={(cartId: string, checkoutUrl?: string) => {
316
- console.log('🛒 [Story] Cart button clicked!')
317
- console.log('📋 Cart ID:', cartId)
318
- console.log('🔗 Checkout URL:', checkoutUrl)
319
-
320
- // 模拟购物车按钮点击成功
321
- alert(`购物车按钮被点击!\n\nCart ID: ${cartId}\nCheckout URL: ${checkoutUrl || '无'}`)
322
- }}
323
- />
324
- ),
325
- }
326
-
327
- /**
328
- * 自定义视频消息渲染器
329
- *
330
- * 扩展消息类型,添加视频消息支持。
331
- */
332
- export const CustomVideoRenderer: Story = {
333
- args: {
200
+ // 自定义消息渲染器
334
201
  customRenderers: {
335
202
  video: {
336
203
  render: (content: MessageContent) => {
@@ -338,30 +205,17 @@ export const CustomVideoRenderer: Story = {
338
205
  return (
339
206
  <div className="w-full">
340
207
  <video src={videoContent.url} controls className="w-full rounded-lg" poster={videoContent.poster}>
341
- 您的浏览器不支持视频播放
208
+ Your browser does not support video playback
342
209
  </video>
343
210
  {videoContent.title && <p className="mt-2 text-sm text-gray-600">{videoContent.title}</p>}
344
211
  </div>
345
212
  )
346
213
  },
347
214
  } as MessageRenderer,
348
- },
349
- },
350
- }
351
-
352
- /**
353
- * 自定义图片画廊渲染器
354
- *
355
- * 扩展消息类型,添加图片画廊支持。
356
- */
357
- export const CustomImageGalleryRenderer: Story = {
358
- args: {
359
- customRenderers: {
360
215
  image_gallery: {
361
216
  render: (content: MessageContent) => {
362
217
  const galleryContent = content as any
363
218
  const images = galleryContent.images || []
364
-
365
219
  return (
366
220
  <div className="grid grid-cols-2 gap-2">
367
221
  {images.map((image: any, index: number) => (
@@ -378,510 +232,91 @@ export const CustomImageGalleryRenderer: Story = {
378
232
  },
379
233
  } as MessageRenderer,
380
234
  },
381
- },
382
- }
383
-
384
- /**
385
- * 移动端预览
386
- *
387
- * 在移动端尺寸下查看效果(全屏显示)。
388
- */
389
- export const MobileView: Story = {
390
- parameters: {
391
- viewport: {
392
- defaultViewport: 'mobile1',
393
- },
394
- },
395
- args: {},
396
- }
397
235
 
398
- /**
399
- * 平板端预览
400
- *
401
- * 在平板端尺寸下查看效果。
402
- */
403
- export const TabletView: Story = {
404
- parameters: {
405
- viewport: {
406
- defaultViewport: 'tablet',
407
- },
408
- },
409
- args: {},
410
- }
411
-
412
- /**
413
- * 默认位置展示
414
- *
415
- * 展示气泡按钮的默认位置(右下角)。
416
- */
417
- export const DefaultPositionDemo: Story = {
418
- render: () => (
419
- <div className="relative h-screen w-full bg-gray-50">
420
- <div className="absolute inset-0 flex items-center justify-center">
421
- <p className="text-gray-500">页面内容区域</p>
422
- </div>
423
-
424
- {/* 演示默认位置 */}
425
- <div className="absolute bottom-4 right-4 text-xs text-gray-400">默认位置 (右下角)</div>
426
-
427
- <LiveChatWidget apiBaseUrl="https://beta-api-livechat.anker.com" site="www.eufy.com" />
428
- </div>
429
- ),
430
- }
431
-
432
- /**
433
- * 产品对比消息
434
- *
435
- * 展示产品对比类型的消息渲染效果,包括价格、会员价、变体数量、折扣等多个维度的对比。
436
- *
437
- * 产品对比组件会以网格布局展示多个产品的基本信息,并在下方显示各维度的对比数据。
438
- */
439
- export const ProductComparisonMessage: Story = {
440
- render: args => (
441
- <LiveChatWidget
442
- {...args}
443
- onAddToCart={(product: any) => {
444
- console.log('🛒 [ProductComparison Story] Add to Cart clicked!')
445
- console.log('📦 Product Info:', {
446
- id: product.shopifyId,
447
- title: product.title,
448
- price: product.price,
449
- imageUrl: product.imageUrl,
450
- productUrl: product.productUrl,
451
- })
452
- console.log('📋 Full Product Object:', product)
453
-
454
- // 模拟添加到购物车成功
455
- alert(`✅ "${product.title}" 已添加到购物车!\n\n价格: ${product.price.currency} ${product.price.amount}`)
456
- }}
457
- />
458
- ),
459
- args: {
460
- welcomeMessage: '以下是您关注的产品对比信息:',
461
- },
462
- parameters: {
463
- docs: {
464
- description: {
465
- story: `
466
- 产品对比消息支持以下维度:
467
-
468
- - **价格对比**: 显示产品的价格区间和促销标签
469
- - **会员价对比**: 显示会员专享价格(如果可用)
470
- - **变体数量对比**: 显示产品的可选变体数量
471
- - **折扣对比**: 显示产品是否有折扣活动
472
-
473
- 组件会自动处理从后端返回的产品对比数据,包括:
474
- - 产品基本信息(图片、标题、价格等)
475
- - 多维度对比数据(价格、会员价、变体、折扣)
476
- - 响应式布局(移动端可横向滚动)
477
-
478
- **测试 Add to Cart 功能:**
479
- - 点击产品对比卡片中的 "Add to Cart" 按钮
480
- - 查看控制台输出的产品信息
481
- - 会弹出确认提示框显示已添加到购物车
482
- `,
483
- },
484
- },
485
- },
486
- }
487
-
488
- /**
489
- * FAQ 列表消息
490
- *
491
- * 展示常见问题列表,支持折叠/展开。
492
- *
493
- * FAQ 组件会显示搜索结果的常见问题,每个问题可以点击展开查看答案。
494
- */
495
- export const FAQListMessage: Story = {
496
- args: {
497
- welcomeMessage: '以下是与您的问题相关的常见解答:',
498
- },
499
- parameters: {
500
- docs: {
501
- description: {
502
- story: `
503
- FAQ 列表消息支持:
504
-
505
- - **可折叠问题**: 点击问题标题展开/折叠答案
506
- - **Markdown 答案**: 答案支持 Markdown 格式,可显示富文本
507
- - **相关问题**: 展开答案后显示相关问题,点击可触发新搜索
508
- - **分类标记**: 根据问题分类显示(配送、退货、产品、支付、通用)
509
- - **搜索结果统计**: 显示找到的问题数量
510
-
511
- 使用场景:
512
- - 用户询问常见问题时返回 FAQ 列表
513
- - 产品专属问题查询
514
- - 按分类查询 FAQ
515
- `,
516
- },
517
- },
518
- },
519
- }
520
-
521
- /**
522
- * 带 reCAPTCHA 配置
523
- *
524
- * 展示如何配置 Google reCAPTCHA v3 验证,保护 API 请求免受滥用。
525
- *
526
- * reCAPTCHA 会在后台自动验证用户行为,无需用户手动操作。
527
- */
528
- export const WithRecaptcha: Story = {
529
- args: {
530
- // 配置你的 reCAPTCHA site key(提供即自动启用)
531
- recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',
532
- // 可选:自定义 action 名称
533
- recaptchaAction: 'livechat',
534
- },
535
- parameters: {
536
- docs: {
537
- description: {
538
- story: `
539
- ### reCAPTCHA v3 配置
540
-
541
- 启用 reCAPTCHA v3 可以保护你的 API 免受机器人和滥用行为的攻击。
542
-
543
- **配置步骤:**
544
-
545
- 1. 在 [Google reCAPTCHA](https://www.google.com/recaptcha/admin) 创建 v3 密钥
546
- 2. 在页面中加载 reCAPTCHA 脚本:
547
- \`\`\`html
548
- <script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
549
- \`\`\`
550
- 3. 配置 LiveChatWidget 组件(提供 sitekey 即自动启用):
551
- \`\`\`tsx
552
- <LiveChatWidget
553
- recaptchaSitekey="YOUR_SITE_KEY"
554
- recaptchaAction="livechat"
555
- />
556
- \`\`\`
557
-
558
- **工作原理:**
559
-
560
- - 提供 recaptchaSitekey 参数即自动启用 reCAPTCHA 验证
561
- - 每次发送消息或创建会话时,会自动获取 reCAPTCHA token
562
- - token 会通过 \`X-Recaptcha-Token\` header 发送到后端
563
- - 后端需要验证 token 的有效性
564
-
565
- **注意事项:**
566
-
567
- - reCAPTCHA v3 在后台运行,不会打断用户体验
568
- - 建议为不同的操作使用不同的 \`recaptchaAction\` 以便分析
569
- - 确保后端正确验证 reCAPTCHA token
570
- - 不提供 recaptchaSitekey 则不启用验证(仅用于开发环境)
571
- `,
572
- },
573
- },
574
- },
575
- }
576
-
577
- /**
578
- * 法规协议弹窗
579
- *
580
- * 演示如何配置法规协议弹窗,在用户首次点击聊天气泡时显示。
581
- *
582
- * 用户必须同意协议后才能打开聊天窗口。
583
- */
584
- export const WithComplianceDialog: Story = {
585
- args: {
586
- loginUserId: 'test_test',
587
- apiBaseUrl: 'http://172.16.38.183:3003',
588
- site: 'beta.eufy.com',
589
- channelCode: 'dtc',
590
- title: 'eufy AI Assistant',
591
-
592
- // 法规协议弹窗配置
593
- complianceConfig: {
594
- title: "Hi! I'm your eufy AI assistant.",
595
- content: "AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data",
596
- checkboxText: 'By starting to use "Live Chat", you agree to Anker\'s <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.',
597
- agreeButtonText: "Agree"
598
- },
599
- },
600
- parameters: {
601
- docs: {
602
- description: {
603
- story: `
604
- ### 法规协议弹窗功能
605
-
606
- 使用 \`complianceConfig\` 属性可以在用户首次点击聊天气泡时显示法规协议弹窗。
607
-
608
- **功能特性:**
609
-
610
- - ✅ 用户必须勾选复选框才能点击"同意"按钮
611
- - ✅ 支持在勾选框文本中嵌入链接(使用 \`{link}\` 占位符)
612
- - ✅ 支持 HTML 格式的内容文本
613
- - ✅ 同意后自动打开聊天窗口
614
- - ✅ 关闭弹窗不会打开聊天窗口
615
- - ✅ 同意后再次点击气泡直接打开聊天(无需重复同意)
616
- - ✅ 响应式设计,移动端友好
617
- - ✅ **与聊天窗口位置一致**(右下角或底部固定)
618
- - ✅ **无遮罩层设计**,不遮挡页面内容
619
-
620
- **测试步骤:**
621
-
622
- 1. 点击聊天气泡按钮
623
- 2. 查看弹出的法规协议弹窗
624
- 3. 尝试不勾选复选框时点击"Agree"按钮(按钮应该是禁用状态)
625
- 4. 勾选复选框后点击"Agree"
626
- 5. 观察弹窗关闭并自动打开聊天窗口
627
- 6. 关闭聊天窗口后再次点击气泡(应该直接打开聊天,不再显示法规弹窗)
628
-
629
- **配置项说明:**
630
-
631
- \`\`\`typescript
632
- complianceConfig: {
633
- title: string // 弹窗标题
634
- content: string // 内容文本(支持 HTML)
635
- checkboxText: string // 勾选框文本(支持完整 HTML,包括 <a> 标签)
636
- agreeButtonText?: string // 同意按钮文案(默认 "Agree")
637
- }
638
- \`\`\`
639
-
640
- **示例:**
641
-
642
- \`\`\`tsx
643
- checkboxText: 'By starting to use "Live Chat", you agree to <a href="https://example.com/privacy" target="_blank">PRIVACY NOTICE</a>.'
644
- \`\`\`
645
-
646
- **样式定制:**
647
-
648
- 所有样式通过 CSS 类名暴露,可以全局覆盖:
649
-
650
- \`\`\`css
651
- .livechat-compliance-content { /* 弹窗主体 */ }
652
- .livechat-compliance-title { /* 标题 */ }
653
- .livechat-compliance-text { /* 内容文本 */ }
654
- .livechat-compliance-checkbox { /* 复选框 */ }
655
- .livechat-compliance-link { /* 链接 */ }
656
- .livechat-compliance-button { /* 同意按钮 */ }
657
- \`\`\`
658
- `,
659
- },
660
- },
661
- },
662
- }
663
-
664
- /**
665
- * 法规协议弹窗 - 无链接版本
666
- *
667
- * 演示不包含链接的简化版法规协议弹窗。
668
- */
669
- export const WithComplianceDialogNoLink: Story = {
670
- args: {
671
- loginUserId: 'test_test',
672
- apiBaseUrl: 'http://172.16.38.183:3003',
673
- site: 'beta.eufy.com',
674
- channelCode: 'dtc',
675
- title: 'AI Assistant',
676
-
677
- // 简化版法规协议(不包含链接)
678
- complianceConfig: {
679
- title: "Welcome to AI Chat Support",
680
- content: "By using this service, you agree to our terms of service and privacy policy. AI responses may contain errors. Please verify important information.",
681
- checkboxText: "I have read and agree to the terms and conditions",
682
- agreeButtonText: "Continue"
683
- },
684
- },
685
- parameters: {
686
- docs: {
687
- description: {
688
- story: `
689
- ### 简化版法规协议弹窗
690
-
691
- 如果不需要在勾选框中显示链接,可以省略 \`linkText\` 和 \`linkUrl\` 属性。
692
-
693
- 这个版本适用于:
694
- - 简单的用户协议确认
695
- - 免责声明
696
- - 服务条款同意
697
-
698
- **与完整版的区别:**
699
- - 不包含可点击的链接
700
- - 勾选框文本为纯文本
701
- - 配置更简单
702
- `,
703
- },
704
- },
705
- },
706
- }
707
-
708
- /**
709
- * 自定义产品卡片渲染
710
- *
711
- * 演示如何使用 productCardRender 自定义产品卡片的渲染样式。
712
- *
713
- * 当后端返回产品占位符 {{product:xxx}} 时,会使用自定义渲染函数来展示产品卡片。
714
- */
715
- export const CustomProductCardRender: Story = {
716
- args: {
717
- loginUserId: 'test_test',
718
- apiBaseUrl: 'http://172.16.38.183:3003',
719
- cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',
720
- accessToken: '47b1aa2c0797043f9baba39388029d70',
721
- title: 'eufy',
722
- site: 'beta.eufy.com',
723
- channelCode: 'dtc',
724
- welcomeMessage: '你好!我可以为你推荐产品,试试发送 "推荐产品" 来查看自定义产品卡片效果。',
236
+ // 自定义产品卡片渲染
237
+ productCardRender: (product, productHandle) => {
238
+ // product 可能为 undefined,此时可用 productHandle 查询
239
+ if (!product) {
240
+ return (
241
+ <div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>
242
+ <p>Product loading... (handle: {productHandle})</p>
243
+ </div>
244
+ )
245
+ }
725
246
 
726
- // 自定义产品卡片渲染函数
727
- // 注意:product 参数是原始后端数据(未经转换)
728
- productCardRender: product => {
729
- // 原始后端数据使用 snake_case 字段名
730
- const imageUrl = product.featured_image || ''
731
- const title = product.title || ''
732
- const description = product.description || ''
733
- const price = product.price_range?.min || product.variants?.[0]?.price || 0
734
- const currency = product.price_range?.currency || product.variants?.[0]?.currency || 'USD'
735
- const averageRating = product.average_rating
247
+ const imageUrl = product?.featured_image || ''
248
+ const title = product?.title || ''
249
+ const description = product?.description || ''
250
+ const averageRating = product?.average_rating
736
251
 
737
252
  return (
738
253
  <div
739
254
  style={{
740
255
  border: '2px solid #4CAF50',
741
256
  borderRadius: '16px',
742
- padding: '20px',
257
+ padding: '16px',
743
258
  margin: '12px 0',
744
259
  backgroundColor: '#f0f9ff',
745
260
  boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',
746
- transition: 'transform 0.2s',
747
- }}
748
- onMouseEnter={e => {
749
- e.currentTarget.style.transform = 'scale(1.02)'
750
- }}
751
- onMouseLeave={e => {
752
- e.currentTarget.style.transform = 'scale(1)'
753
261
  }}
754
262
  >
755
- <div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
756
- <div
757
- style={{
758
- width: '70px',
759
- height: 'auto',
760
- flexShrink: 0,
761
- borderRadius: '12px',
762
- overflow: 'hidden',
763
- border: '2px solid #e0e0e0',
764
- }}
765
- >
263
+ <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
264
+ {imageUrl && (
766
265
  <img
767
266
  src={imageUrl}
768
267
  alt={title}
769
- style={{
770
- width: '100%',
771
- height: '100%',
772
- objectFit: 'cover',
773
- }}
268
+ style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}
774
269
  />
775
- </div>
776
-
270
+ )}
777
271
  <div style={{ flex: 1 }}>
778
- <h3
779
- style={{
780
- margin: '0 0 8px 0',
781
- fontSize: '18px',
782
- fontWeight: 'bold',
783
- color: '#1a1a1a',
784
- lineHeight: '1.4',
785
- }}
786
- >
787
- {title}
788
- </h3>
789
-
272
+ <h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>
790
273
  {description && (
791
- <p
792
- style={{
793
- margin: '0 0 12px 0',
794
- fontSize: '14px',
795
- color: '#666',
796
- lineHeight: '1.5',
797
- display: '-webkit-box',
798
- WebkitLineClamp: 2,
799
- WebkitBoxOrient: 'vertical',
800
- overflow: 'hidden',
801
- }}
802
- >
803
- {description}
274
+ <p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>
275
+ {description.slice(0, 80)}...
804
276
  </p>
805
277
  )}
806
-
807
- <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
808
- <div
809
- style={{
810
- fontSize: '24px',
811
- fontWeight: 'bold',
812
- color: '#4CAF50',
813
- }}
814
- >
815
- 'test price'
816
- </div>
817
-
818
- {averageRating !== undefined && (
819
- <div
820
- style={{
821
- display: 'flex',
822
- alignItems: 'center',
823
- gap: '4px',
824
- fontSize: '14px',
825
- color: '#666',
826
- }}
827
- >
828
- <span style={{ color: '#FFB800' }}>⭐</span>
829
- <span>{averageRating.toFixed(1)}</span>
830
- </div>
831
- )}
832
-
833
- <button
834
- style={{
835
- marginLeft: 'auto',
836
- padding: '8px 20px',
837
- backgroundColor: '#4CAF50',
838
- color: 'white',
839
- borderRadius: '8px',
840
- textDecoration: 'none',
841
- fontSize: '14px',
842
- fontWeight: 'bold',
843
- transition: 'background-color 0.2s',
844
- }}
845
- onClick={() => {
846
- console.log('View Details', product)
847
- }}
848
- >
849
- View Details
850
- </button>
851
- </div>
278
+ {averageRating && (
279
+ <span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>
280
+ )}
852
281
  </div>
282
+ <button
283
+ style={{
284
+ padding: '8px 16px',
285
+ backgroundColor: '#4CAF50',
286
+ color: 'white',
287
+ borderRadius: '8px',
288
+ border: 'none',
289
+ cursor: 'pointer',
290
+ }}
291
+ onClick={() => console.log('View product:', productHandle, product)}
292
+ >
293
+ View
294
+ </button>
853
295
  </div>
854
296
  </div>
855
297
  )
856
298
  },
857
-
858
- onAddToCart: product => {
859
- console.log('Add to cart:', product)
860
- alert(`Added ${product.title} to cart!`)
861
- },
862
299
  },
863
- parameters: {
864
- docs: {
865
- description: {
866
- story: `
867
- ### 自定义产品卡片渲染
868
300
 
869
- 使用 \`productCardRender\` 属性可以完全自定义产品卡片的展示样式。
870
-
871
- **功能说明:**
872
-
873
- - 当后端返回带有产品占位符 \`{{product:xxx}}\` 的文本流时,会使用自定义渲染函数
874
- - 自定义函数接收完整的产品对象作为参数
875
- - 返回 React 节点,可以是任何合法的 JSX
876
- - 如果不提供 \`productCardRender\`,则使用默认的产品卡片样式
877
-
878
- **测试步骤:**
879
-
880
- 1. 打开聊天窗口
881
- 2. 发送 "推荐产品" 或任何会触发产品推荐的消息
882
- 3. 观察返回的产品卡片使用的是自定义样式(绿色边框、悬停放大效果)
883
- `,
884
- },
885
- },
886
- },
301
+ render: args => (
302
+ <LiveChatWidget
303
+ {...args}
304
+ // 所有事件回调
305
+ onOpen={() => console.log('[LiveChat] Chat opened')}
306
+ onClose={() => console.log('[LiveChat] Chat closed')}
307
+ onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}
308
+ onError={(error: Error) => console.error('[LiveChat] Error:', error)}
309
+ onTextMessage={() => console.log('[LiveChat] AI text message received')}
310
+ onProductList={() => console.log('[LiveChat] Product list received')}
311
+ onPromotionList={() => console.log('[LiveChat] Promotion list received')}
312
+ onAddToCart={(product: any) => {
313
+ console.log('[LiveChat] Add to cart:', product)
314
+ alert(`Added "${product.title}" to cart!`)
315
+ }}
316
+ onCart={(cartId: string, checkoutUrl?: string) => {
317
+ console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })
318
+ alert(`Cart ID: ${cartId}`)
319
+ }}
320
+ />
321
+ ),
887
322
  }