@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.
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
- package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +6 -6
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/types.d.ts +18 -11
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -1
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +2 -2
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -109
- package/dist/cjs/stories/LiveChatWidget.stories.js +7 -166
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
- package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +7 -7
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/types.d.ts +18 -11
- package/dist/esm/components/index.d.ts +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +2 -2
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -109
- package/dist/esm/stories/LiveChatWidget.stories.js +7 -166
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/package.json +1 -1
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -23
- package/src/components/LiveChatWidget/api/chat.ts +8 -7
- package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +1 -5
- package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +17 -8
- package/src/components/LiveChatWidget/components/MessageList.tsx +11 -17
- package/src/components/LiveChatWidget/hooks/useChatState.ts +64 -98
- package/src/components/LiveChatWidget/types.ts +18 -11
- package/src/components/index.ts +2 -1
- package/src/stories/LiveChatWidget.stories.tsx +95 -660
- 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
|
-
//
|
|
166
|
-
|
|
167
|
-
recaptchaAction: 'chat',
|
|
158
|
+
// 自定义位置
|
|
159
|
+
position: { bottom: '24px', right: '30px' },
|
|
168
160
|
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
194
|
+
// 自定义文案
|
|
195
|
+
commonText: {
|
|
196
|
+
learnMore: 'Learn More',
|
|
197
|
+
total: 'Total',
|
|
267
198
|
},
|
|
268
199
|
|
|
269
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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: '
|
|
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: '
|
|
756
|
-
|
|
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
|
-
|
|
776
|
-
|
|
270
|
+
)}
|
|
777
271
|
<div style={{ flex: 1 }}>
|
|
778
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
808
|
-
|
|
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
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
}
|