@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
|
@@ -143,10 +143,19 @@ export const MessageList: React.FC<MessageListProps> = ({
|
|
|
143
143
|
// 监听滚动事件,控制按钮显示
|
|
144
144
|
useEffect(() => {
|
|
145
145
|
const container = listRef.current
|
|
146
|
+
console.log('[MessageList] useEffect - container:', container)
|
|
146
147
|
if (!container) return
|
|
147
148
|
|
|
148
149
|
const handleScroll = () => {
|
|
149
|
-
|
|
150
|
+
const nearBottom = isNearBottom()
|
|
151
|
+
console.log('[MessageList] handleScroll:', {
|
|
152
|
+
scrollTop: container.scrollTop,
|
|
153
|
+
scrollHeight: container.scrollHeight,
|
|
154
|
+
clientHeight: container.clientHeight,
|
|
155
|
+
isNearBottom: nearBottom,
|
|
156
|
+
willShowButton: !nearBottom,
|
|
157
|
+
})
|
|
158
|
+
setShowScrollButton(!nearBottom)
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
// 初始检查
|
|
@@ -223,22 +232,7 @@ export const MessageList: React.FC<MessageListProps> = ({
|
|
|
223
232
|
{/* 回到底部按钮 */}
|
|
224
233
|
<button
|
|
225
234
|
onClick={scrollToBottom}
|
|
226
|
-
className={`
|
|
227
|
-
style={{
|
|
228
|
-
position: 'absolute',
|
|
229
|
-
bottom: '24px',
|
|
230
|
-
left: '50%',
|
|
231
|
-
zIndex: 10,
|
|
232
|
-
width: '40px',
|
|
233
|
-
height: '40px',
|
|
234
|
-
borderRadius: '50%',
|
|
235
|
-
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
236
|
-
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
|
237
|
-
transition: 'all 300ms ease-in-out',
|
|
238
|
-
opacity: showScrollButton ? 1 : 0,
|
|
239
|
-
cursor: 'pointer',
|
|
240
|
-
border: 'none',
|
|
241
|
-
}}
|
|
235
|
+
className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}
|
|
242
236
|
aria-label="Scroll to bottom"
|
|
243
237
|
aria-hidden={!showScrollButton}
|
|
244
238
|
>
|
|
@@ -20,8 +20,8 @@ import { transformCartData } from '../utils/cartTransformers'
|
|
|
20
20
|
* 处理缓冲区中的文本,检测完整的 {{product:xxx}} 占位符
|
|
21
21
|
*
|
|
22
22
|
* @param buffer 当前缓冲区内容(包含新接收的文本)
|
|
23
|
-
* @param productMap
|
|
24
|
-
* @param rawProductMap
|
|
23
|
+
* @param productMap handle → Product 的映射表
|
|
24
|
+
* @param rawProductMap handle → raw backend product 的映射表
|
|
25
25
|
* @param onAddToCart 添加到购物车回调
|
|
26
26
|
* @param productCardRender 自定义产品卡片渲染函数
|
|
27
27
|
* @returns { contents: 需要添加的内容数组, remainingBuffer: 剩余缓冲区内容 }
|
|
@@ -31,7 +31,7 @@ function parseStreamingText(
|
|
|
31
31
|
productMap: Map<string, Product>,
|
|
32
32
|
rawProductMap: Map<string, any>,
|
|
33
33
|
onAddToCart?: (product: Product) => void,
|
|
34
|
-
productCardRender?: (product: any) => React.ReactNode
|
|
34
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
35
35
|
): { contents: MessageContent[]; remainingBuffer: string } {
|
|
36
36
|
const contents: MessageContent[] = []
|
|
37
37
|
const regex = /\{\{(?:product:)?([^}]+)\}\}/g
|
|
@@ -55,22 +55,24 @@ function parseStreamingText(
|
|
|
55
55
|
const product = productMap.get(productId)
|
|
56
56
|
const rawProduct = rawProductMap.get(productId)
|
|
57
57
|
|
|
58
|
+
// 无论是否找到产品数据,都渲染 product_card,应用层可通过 productHandle 查询产品
|
|
58
59
|
if (product) {
|
|
59
60
|
console.log('[useChatState] 🎯 实时检测到产品:', productId, '→', product.title)
|
|
60
|
-
contents.push({
|
|
61
|
-
type: 'product_card',
|
|
62
|
-
data: {
|
|
63
|
-
product: product,
|
|
64
|
-
rawProduct: rawProduct,
|
|
65
|
-
onAddToCart: onAddToCart,
|
|
66
|
-
productCardRender: productCardRender
|
|
67
|
-
}
|
|
68
|
-
} as ProductCardContent)
|
|
69
61
|
} else {
|
|
70
|
-
|
|
71
|
-
console.warn('[useChatState] ⚠️ 实时解析未找到产品,已隐藏:', productId)
|
|
62
|
+
console.log('[useChatState] 📦 实时检测到产品占位符,产品数据待应用层查询:', productId)
|
|
72
63
|
}
|
|
73
64
|
|
|
65
|
+
contents.push({
|
|
66
|
+
type: 'product_card',
|
|
67
|
+
data: {
|
|
68
|
+
product: product,
|
|
69
|
+
rawProduct: rawProduct,
|
|
70
|
+
productHandle: productId,
|
|
71
|
+
onAddToCart: onAddToCart,
|
|
72
|
+
productCardRender: productCardRender
|
|
73
|
+
}
|
|
74
|
+
} as ProductCardContent)
|
|
75
|
+
|
|
74
76
|
lastIndex = regex.lastIndex
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -102,18 +104,19 @@ function parseStreamingText(
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
|
-
* 解析文本中的 {{
|
|
107
|
+
* 解析文本中的 {{handle}},返回 [text, product_card, text, ...] 数组(用于历史消息重组)
|
|
106
108
|
*
|
|
107
|
-
* @param text 包含 {{
|
|
108
|
-
* @param productMap
|
|
109
|
+
* @param text 包含 {{handle}} 标记的文本
|
|
110
|
+
* @param productMap handle → Product 的映射表
|
|
111
|
+
* @param rawProductMap handle → raw backend product 的映射表
|
|
109
112
|
* @param onAddToCart 添加到购物车回调
|
|
110
113
|
* @param productCardRender 自定义产品卡片渲染函数
|
|
111
114
|
* @returns MessageContent 数组
|
|
112
115
|
*
|
|
113
116
|
* @example
|
|
114
117
|
* 输入:
|
|
115
|
-
* text: "我推荐以下产品:\n{{
|
|
116
|
-
* productMap: Map { '
|
|
118
|
+
* text: "我推荐以下产品:\n{{product-handle}}\n这款产品性价比很高。"
|
|
119
|
+
* productMap: Map { 'product-handle' => Product {...} }
|
|
117
120
|
* 输出:
|
|
118
121
|
* [
|
|
119
122
|
* { type: 'text', text: '我推荐以下产品:' },
|
|
@@ -126,7 +129,7 @@ function parseTextWithProductIds(
|
|
|
126
129
|
productMap: Map<string, Product>,
|
|
127
130
|
rawProductMap: Map<string, any>,
|
|
128
131
|
onAddToCart?: (product: Product) => void,
|
|
129
|
-
productCardRender?: (product: any) => React.ReactNode
|
|
132
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
130
133
|
): MessageContent[] {
|
|
131
134
|
const result: MessageContent[] = []
|
|
132
135
|
// 修改正则表达式以匹配 {{product:ID}} 格式
|
|
@@ -149,25 +152,26 @@ function parseTextWithProductIds(
|
|
|
149
152
|
} as TextContent)
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
//
|
|
155
|
+
// 添加产品卡片(无论是否找到产品数据,都渲染 product_card)
|
|
153
156
|
const product = productMap.get(productId)
|
|
154
157
|
const rawProduct = rawProductMap.get(productId)
|
|
155
158
|
if (product) {
|
|
156
159
|
console.log(`[useChatState] ✅ 找到产品匹配: ${productId} → ${product.title}`)
|
|
157
|
-
result.push({
|
|
158
|
-
type: 'product_card',
|
|
159
|
-
data: {
|
|
160
|
-
product: product,
|
|
161
|
-
rawProduct: rawProduct,
|
|
162
|
-
onAddToCart: onAddToCart,
|
|
163
|
-
productCardRender: productCardRender,
|
|
164
|
-
},
|
|
165
|
-
} as ProductCardContent)
|
|
166
160
|
} else {
|
|
167
|
-
|
|
168
|
-
console.warn(`[useChatState] ❌ Product not found for ID: "${productId}",已隐藏`)
|
|
161
|
+
console.log(`[useChatState] 📦 产品占位符,产品数据待应用层查询: ${productId}`)
|
|
169
162
|
}
|
|
170
163
|
|
|
164
|
+
result.push({
|
|
165
|
+
type: 'product_card',
|
|
166
|
+
data: {
|
|
167
|
+
product: product,
|
|
168
|
+
rawProduct: rawProduct,
|
|
169
|
+
productHandle: productId,
|
|
170
|
+
onAddToCart: onAddToCart,
|
|
171
|
+
productCardRender: productCardRender,
|
|
172
|
+
},
|
|
173
|
+
} as ProductCardContent)
|
|
174
|
+
|
|
171
175
|
lastIndex = regex.lastIndex
|
|
172
176
|
}
|
|
173
177
|
|
|
@@ -184,16 +188,17 @@ function parseTextWithProductIds(
|
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
/**
|
|
187
|
-
* 重组消息内容:解析文本中的 {{
|
|
191
|
+
* 重组消息内容:解析文本中的 {{handle}},替换为产品卡片
|
|
188
192
|
*
|
|
189
193
|
* 处理逻辑:
|
|
190
194
|
* 1. 遍历消息的所有 content blocks
|
|
191
|
-
* 2. 对于 text 类型,解析其中的 {{
|
|
195
|
+
* 2. 对于 text 类型,解析其中的 {{handle}} 并拆分为多个 content
|
|
192
196
|
* 3. 跳过 product_list 类型(已经被拆分到文本中)
|
|
193
197
|
* 4. 其他类型直接保留
|
|
194
198
|
*
|
|
195
199
|
* @param contents 原始消息内容数组
|
|
196
|
-
* @param productMap
|
|
200
|
+
* @param productMap handle → Product 的映射表
|
|
201
|
+
* @param rawProductMap handle → raw backend product 的映射表
|
|
197
202
|
* @param onAddToCart 添加到购物车回调
|
|
198
203
|
* @param productCardRender 自定义产品卡片渲染函数
|
|
199
204
|
* @returns 重组后的消息内容数组
|
|
@@ -203,7 +208,7 @@ function reorganizeMessageContent(
|
|
|
203
208
|
productMap: Map<string, Product>,
|
|
204
209
|
rawProductMap: Map<string, any>,
|
|
205
210
|
onAddToCart?: (product: Product) => void,
|
|
206
|
-
productCardRender?: (product: any) => React.ReactNode
|
|
211
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
207
212
|
): MessageContent[] {
|
|
208
213
|
const result: MessageContent[] = []
|
|
209
214
|
|
|
@@ -229,7 +234,7 @@ function reorganizeMessageContent(
|
|
|
229
234
|
|
|
230
235
|
/**
|
|
231
236
|
* 处理单条消息的重组(用于历史消息加载)
|
|
232
|
-
*
|
|
237
|
+
* 如果消息包含带有 {{}} 的文本,则进行重组
|
|
233
238
|
*
|
|
234
239
|
* @param message 原始消息
|
|
235
240
|
* @param onAddToCart 添加到购物车回调
|
|
@@ -239,14 +244,8 @@ function reorganizeMessageContent(
|
|
|
239
244
|
function maybeReorganizeHistoricalMessage(
|
|
240
245
|
message: Message,
|
|
241
246
|
onAddToCart?: (product: Product) => void,
|
|
242
|
-
productCardRender?: (product: any) => React.ReactNode
|
|
247
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
243
248
|
): Message {
|
|
244
|
-
// 检查消息是否包含 product_list
|
|
245
|
-
const hasProductList = message.content.some(c => c.type === 'product_list')
|
|
246
|
-
if (!hasProductList) {
|
|
247
|
-
return message // 没有 product_list,不需要重组
|
|
248
|
-
}
|
|
249
|
-
|
|
250
249
|
// 检查消息是否包含带有 {{}} 的文本
|
|
251
250
|
const hasPlaceholder = message.content.some(
|
|
252
251
|
c => c.type === 'text' && /\{\{(?:product:)?[^}]+\}\}/.test((c as TextContent).text)
|
|
@@ -257,9 +256,9 @@ function maybeReorganizeHistoricalMessage(
|
|
|
257
256
|
|
|
258
257
|
console.log('[useChatState] 检测到历史消息需要重组, 消息ID:', message.id)
|
|
259
258
|
|
|
260
|
-
// 构建产品映射
|
|
259
|
+
// 构建产品映射 (handle → Product)
|
|
261
260
|
const productMap = new Map<string, Product>()
|
|
262
|
-
// 从 structured_content 中提取原始后端产品数据
|
|
261
|
+
// 从 structured_content 中提取原始后端产品数据 (handle → rawProduct)
|
|
263
262
|
const rawProductMap = new Map<string, any>()
|
|
264
263
|
|
|
265
264
|
// 优先从 structured_content 获取原始产品数据(如果存在)
|
|
@@ -267,22 +266,9 @@ function maybeReorganizeHistoricalMessage(
|
|
|
267
266
|
message.structured_content.forEach(structuredContent => {
|
|
268
267
|
if (structuredContent.type === 'product_list' && Array.isArray(structuredContent.data)) {
|
|
269
268
|
structuredContent.data.forEach((rawProduct: any) => {
|
|
270
|
-
if (rawProduct && rawProduct.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
rawProductMap.set(shopifyId, rawProduct)
|
|
274
|
-
|
|
275
|
-
// 2. 同时提取数字部分作为key
|
|
276
|
-
const numericId = shopifyId.match(/\d+$/)?.[0]
|
|
277
|
-
if (numericId) {
|
|
278
|
-
rawProductMap.set(numericId, rawProduct)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
console.log('[useChatState] 从 structured_content 提取原始产品数据:', {
|
|
282
|
-
fullId: shopifyId,
|
|
283
|
-
numericId,
|
|
284
|
-
title: rawProduct.title,
|
|
285
|
-
})
|
|
269
|
+
if (rawProduct && rawProduct.handle) {
|
|
270
|
+
rawProductMap.set(rawProduct.handle, rawProduct)
|
|
271
|
+
console.log('[useChatState] 从 structured_content 提取原始产品数据, handle:', rawProduct.handle)
|
|
286
272
|
}
|
|
287
273
|
})
|
|
288
274
|
}
|
|
@@ -294,15 +280,8 @@ function maybeReorganizeHistoricalMessage(
|
|
|
294
280
|
if (content.type === 'product_list') {
|
|
295
281
|
const productListContent = content as ProductListContent
|
|
296
282
|
productListContent.data.products.forEach(product => {
|
|
297
|
-
if (product && product.
|
|
298
|
-
|
|
299
|
-
productMap.set(product.shopifyId, product)
|
|
300
|
-
|
|
301
|
-
// 2. 同时提取数字部分作为key
|
|
302
|
-
const numericId = product.shopifyId.match(/\d+$/)?.[0]
|
|
303
|
-
if (numericId) {
|
|
304
|
-
productMap.set(numericId, product)
|
|
305
|
-
}
|
|
283
|
+
if (product && product.handle) {
|
|
284
|
+
productMap.set(product.handle, product)
|
|
306
285
|
}
|
|
307
286
|
})
|
|
308
287
|
}
|
|
@@ -393,8 +372,10 @@ export interface UseChatStateOptions {
|
|
|
393
372
|
|
|
394
373
|
/**
|
|
395
374
|
* 自定义产品卡片渲染函数
|
|
375
|
+
* @param product 产品数据(如果在 product_list 中找到),否则为 undefined
|
|
376
|
+
* @param productHandle 文本占位符中的产品 ID,可用于应用层查询产品数据
|
|
396
377
|
*/
|
|
397
|
-
productCardRender?: (product:
|
|
378
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
398
379
|
}
|
|
399
380
|
|
|
400
381
|
export interface UseChatStateReturn {
|
|
@@ -554,10 +535,10 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
|
|
|
554
535
|
// 标记当前消息是否已触发 onTextMessage 回调(避免重复触发)
|
|
555
536
|
const textMessageCallbackTriggeredRef = useRef<boolean>(false)
|
|
556
537
|
|
|
557
|
-
// 产品映射缓存 (
|
|
538
|
+
// 产品映射缓存 (handle → Product),用于实时解析占位符
|
|
558
539
|
const productMapRef = useRef<Map<string, Product>>(new Map())
|
|
559
540
|
|
|
560
|
-
// 原始产品数据缓存 (
|
|
541
|
+
// 原始产品数据缓存 (handle → raw backend product),用于 productCardRender
|
|
561
542
|
const rawProductMapRef = useRef<Map<string, any>>(new Map())
|
|
562
543
|
|
|
563
544
|
// 文本缓冲区,用于存储未完成的文本(处理占位符跨越多个 delta 的情况)
|
|
@@ -798,40 +779,25 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
|
|
|
798
779
|
onProductList?.()
|
|
799
780
|
|
|
800
781
|
// 缓存原始后端产品数据(用于 productCardRender)
|
|
782
|
+
// 使用 handle 作为 key 进行匹配
|
|
801
783
|
contentData.forEach((rawProduct: any) => {
|
|
802
|
-
if (rawProduct && rawProduct.
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
rawProductMapRef.current.set(shopifyId, rawProduct)
|
|
806
|
-
|
|
807
|
-
// 2. 同时提取数字部分作为key
|
|
808
|
-
const numericId = shopifyId.match(/\d+$/)?.[0]
|
|
809
|
-
if (numericId) {
|
|
810
|
-
rawProductMapRef.current.set(numericId, rawProduct)
|
|
811
|
-
}
|
|
784
|
+
if (rawProduct && rawProduct.handle) {
|
|
785
|
+
rawProductMapRef.current.set(rawProduct.handle, rawProduct)
|
|
786
|
+
console.log('[useChatState] 缓存原始产品数据, handle:', rawProduct.handle)
|
|
812
787
|
}
|
|
813
788
|
})
|
|
814
789
|
|
|
815
790
|
// 使用统一的产品转换工具函数
|
|
816
791
|
const transformedProducts = transformProducts(contentData, site)
|
|
817
792
|
|
|
818
|
-
// 建立产品映射缓存 (
|
|
819
|
-
// 用于后续在 message_end 时解析文本中的 {{
|
|
793
|
+
// 建立产品映射缓存 (handle → Product)
|
|
794
|
+
// 用于后续在 message_end 时解析文本中的 {{handle}}
|
|
820
795
|
transformedProducts.forEach(product => {
|
|
821
|
-
if (product && product.
|
|
822
|
-
|
|
823
|
-
productMapRef.current.set(product.shopifyId, product)
|
|
824
|
-
|
|
825
|
-
// 2. 同时提取数字部分作为key(如 "123"),支持简写格式
|
|
826
|
-
// 从 "gid://shopify/Product/123" 或 "123" 中提取纯数字
|
|
827
|
-
const numericId = product.shopifyId.match(/\d+$/)?.[0]
|
|
828
|
-
if (numericId) {
|
|
829
|
-
productMapRef.current.set(numericId, product)
|
|
830
|
-
}
|
|
796
|
+
if (product && product.handle) {
|
|
797
|
+
productMapRef.current.set(product.handle, product)
|
|
831
798
|
|
|
832
799
|
console.log('[useChatState] 建立产品映射:', {
|
|
833
|
-
|
|
834
|
-
numericId,
|
|
800
|
+
handle: product.handle,
|
|
835
801
|
title: product.title,
|
|
836
802
|
})
|
|
837
803
|
}
|
|
@@ -86,10 +86,11 @@ export interface TextContent {
|
|
|
86
86
|
export interface ProductCardContent {
|
|
87
87
|
type: 'product_card'
|
|
88
88
|
data: {
|
|
89
|
-
product
|
|
89
|
+
product?: Product
|
|
90
90
|
rawProduct?: any // Raw backend product data (for custom render)
|
|
91
|
+
productHandle: string // Product ID from placeholder {{product:ID}}, for app-level product lookup
|
|
91
92
|
onAddToCart?: (product: Product) => void
|
|
92
|
-
productCardRender?: (product: any) => React.ReactNode
|
|
93
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -820,7 +821,6 @@ export interface LiveChatWidgetProps {
|
|
|
820
821
|
|
|
821
822
|
/**
|
|
822
823
|
* 欢迎消息
|
|
823
|
-
* @default "你好!我是 AI 助手,有什么可以帮助你的吗?"
|
|
824
824
|
*/
|
|
825
825
|
welcomeMessage?: string
|
|
826
826
|
|
|
@@ -952,21 +952,28 @@ export interface LiveChatWidgetProps {
|
|
|
952
952
|
* 自定义产品卡片渲染函数
|
|
953
953
|
* 用于自定义渲染 product_card 类型的产品卡片
|
|
954
954
|
* 当提供此函数时,将替代默认的产品卡片渲染逻辑
|
|
955
|
-
* @param product
|
|
955
|
+
* @param product 原始后端产品数据(未经转换的数据),如果在 product_list 中找不到则为 undefined
|
|
956
|
+
* @param productHandle 文本占位符中的产品 ID(如 {{product:ID}} 中的 ID),可用于应用层查询产品数据
|
|
956
957
|
* @returns React 可渲染的内容
|
|
957
958
|
* @example
|
|
958
959
|
* ```tsx
|
|
959
960
|
* <LiveChatWidget
|
|
960
|
-
* productCardRender={(product) =>
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
* <
|
|
964
|
-
*
|
|
965
|
-
*
|
|
961
|
+
* productCardRender={(product, productHandle) => {
|
|
962
|
+
* // product 可能为 undefined,此时可用 productHandle 查询产品
|
|
963
|
+
* if (!product) {
|
|
964
|
+
* return <ProductCardByHandle handle={productHandle} />
|
|
965
|
+
* }
|
|
966
|
+
* return (
|
|
967
|
+
* <div>
|
|
968
|
+
* <h3>{product.title}</h3>
|
|
969
|
+
* <p>{product.price_range?.min}</p>
|
|
970
|
+
* </div>
|
|
971
|
+
* )
|
|
972
|
+
* }}
|
|
966
973
|
* />
|
|
967
974
|
* ```
|
|
968
975
|
*/
|
|
969
|
-
productCardRender?: (product: any) => React.ReactNode
|
|
976
|
+
productCardRender?: (product: any, productHandle: string) => React.ReactNode
|
|
970
977
|
|
|
971
978
|
/**
|
|
972
979
|
* 输入框底部提示文本
|