@anker-in/campaign-ui 0.3.2 → 0.3.4

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 (229) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.d.ts +21 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  3. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  4. package/dist/cjs/components/LiveChatWidget/api/chat.d.ts +23 -2
  5. package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
  6. package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
  7. package/dist/cjs/components/LiveChatWidget/components/ChatHeader.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/components/ChatHeader.js.map +2 -2
  9. package/dist/cjs/components/LiveChatWidget/components/ChatInput.d.ts +5 -0
  10. package/dist/cjs/components/LiveChatWidget/components/ChatInput.js +1 -1
  11. package/dist/cjs/components/LiveChatWidget/components/ChatInput.js.map +3 -3
  12. package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js +2 -2
  13. package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
  14. package/dist/cjs/components/LiveChatWidget/components/ChatWindow.d.ts +5 -0
  15. package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js +1 -1
  16. package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js.map +3 -3
  17. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
  18. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +33 -0
  19. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
  20. package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
  21. package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
  22. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ErrorBlock.js +1 -1
  23. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ErrorBlock.js.map +2 -2
  24. package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
  25. package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js.map +3 -3
  26. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +2 -2
  27. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +3 -3
  28. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +17 -24
  29. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -4
  30. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  31. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +7 -1
  32. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  33. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  34. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
  35. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
  36. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.d.ts +4 -1
  37. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.js +1 -1
  38. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +3 -3
  39. package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
  40. package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
  41. package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
  42. package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +3 -3
  43. package/dist/cjs/components/LiveChatWidget/components/MessageContent.js +1 -1
  44. package/dist/cjs/components/LiveChatWidget/components/MessageContent.js.map +2 -2
  45. package/dist/cjs/components/LiveChatWidget/components/MessageList.js +2 -2
  46. package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +2 -2
  47. package/dist/cjs/components/LiveChatWidget/constants.d.ts +5 -0
  48. package/dist/cjs/components/LiveChatWidget/constants.js +1 -1
  49. package/dist/cjs/components/LiveChatWidget/constants.js.map +3 -3
  50. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.d.ts +9 -0
  51. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
  52. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
  53. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +35 -2
  54. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  55. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  56. package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
  57. package/dist/cjs/components/LiveChatWidget/index.js +1 -1
  58. package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
  59. package/dist/cjs/components/LiveChatWidget/types.d.ts +212 -3
  60. package/dist/cjs/components/LiveChatWidget/types.js +1 -1
  61. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  62. package/dist/cjs/components/LiveChatWidget/utils/fetcher.d.ts +42 -0
  63. package/dist/cjs/components/LiveChatWidget/utils/fetcher.js +2 -0
  64. package/dist/cjs/components/LiveChatWidget/utils/fetcher.js.map +7 -0
  65. package/dist/cjs/components/chat/markdown.js +1 -1
  66. package/dist/cjs/components/chat/markdown.js.map +2 -2
  67. package/dist/cjs/components/credits/creditsBanner/index.js +2 -2
  68. package/dist/cjs/components/credits/creditsBanner/index.js.map +2 -2
  69. package/dist/cjs/components/index.d.ts +2 -0
  70. package/dist/cjs/components/index.js +1 -1
  71. package/dist/cjs/components/index.js.map +3 -3
  72. package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -79
  73. package/dist/cjs/stories/LiveChatWidget.stories.js +8 -47
  74. package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
  75. package/dist/esm/components/LiveChatWidget/LiveChatWidget.d.ts +21 -1
  76. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  77. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  78. package/dist/esm/components/LiveChatWidget/api/chat.d.ts +23 -2
  79. package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
  80. package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
  81. package/dist/esm/components/LiveChatWidget/components/ChatHeader.js +1 -1
  82. package/dist/esm/components/LiveChatWidget/components/ChatHeader.js.map +2 -2
  83. package/dist/esm/components/LiveChatWidget/components/ChatInput.d.ts +5 -0
  84. package/dist/esm/components/LiveChatWidget/components/ChatInput.js +1 -1
  85. package/dist/esm/components/LiveChatWidget/components/ChatInput.js.map +3 -3
  86. package/dist/esm/components/LiveChatWidget/components/ChatMessage.js +2 -2
  87. package/dist/esm/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
  88. package/dist/esm/components/LiveChatWidget/components/ChatWindow.d.ts +5 -0
  89. package/dist/esm/components/LiveChatWidget/components/ChatWindow.js +1 -1
  90. package/dist/esm/components/LiveChatWidget/components/ChatWindow.js.map +3 -3
  91. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
  92. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +33 -0
  93. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
  94. package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
  95. package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
  96. package/dist/esm/components/LiveChatWidget/components/MessageContent/ErrorBlock.js +1 -1
  97. package/dist/esm/components/LiveChatWidget/components/MessageContent/ErrorBlock.js.map +2 -2
  98. package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
  99. package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js.map +3 -3
  100. package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +2 -2
  101. package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +3 -3
  102. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +17 -24
  103. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -4
  104. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  105. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +7 -1
  106. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  107. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  108. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
  109. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
  110. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.d.ts +4 -1
  111. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js +1 -1
  112. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +3 -3
  113. package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
  114. package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
  115. package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
  116. package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +3 -3
  117. package/dist/esm/components/LiveChatWidget/components/MessageContent.js +1 -1
  118. package/dist/esm/components/LiveChatWidget/components/MessageContent.js.map +2 -2
  119. package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
  120. package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +2 -2
  121. package/dist/esm/components/LiveChatWidget/constants.d.ts +5 -0
  122. package/dist/esm/components/LiveChatWidget/constants.js +1 -1
  123. package/dist/esm/components/LiveChatWidget/constants.js.map +3 -3
  124. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.d.ts +9 -0
  125. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
  126. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
  127. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +35 -2
  128. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  129. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  130. package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
  131. package/dist/esm/components/LiveChatWidget/index.js +1 -1
  132. package/dist/esm/components/LiveChatWidget/index.js.map +2 -2
  133. package/dist/esm/components/LiveChatWidget/types.d.ts +212 -3
  134. package/dist/esm/components/LiveChatWidget/utils/fetcher.d.ts +42 -0
  135. package/dist/esm/components/LiveChatWidget/utils/fetcher.js +2 -0
  136. package/dist/esm/components/LiveChatWidget/utils/fetcher.js.map +7 -0
  137. package/dist/esm/components/chat/markdown.js +1 -1
  138. package/dist/esm/components/chat/markdown.js.map +2 -2
  139. package/dist/esm/components/credits/creditsBanner/index.js +2 -2
  140. package/dist/esm/components/credits/creditsBanner/index.js.map +2 -2
  141. package/dist/esm/components/index.d.ts +2 -0
  142. package/dist/esm/components/index.js +1 -1
  143. package/dist/esm/components/index.js.map +3 -3
  144. package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -79
  145. package/dist/esm/stories/LiveChatWidget.stories.js +8 -47
  146. package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
  147. package/dist/index.d.mts +1305 -0
  148. package/dist/index.d.ts +1305 -0
  149. package/dist/index.js +26656 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/index.mjs +26641 -0
  152. package/dist/index.mjs.map +1 -0
  153. package/package.json +8 -1
  154. package/src/components/LiveChatWidget/LiveChatWidget.tsx +887 -0
  155. package/src/components/LiveChatWidget/api/chat.ts +175 -0
  156. package/src/components/LiveChatWidget/components/ChatBubble.tsx +152 -0
  157. package/src/components/LiveChatWidget/components/ChatHeader.tsx +150 -0
  158. package/src/components/LiveChatWidget/components/ChatInput.tsx +253 -0
  159. package/src/components/LiveChatWidget/components/ChatMessage.tsx +190 -0
  160. package/src/components/LiveChatWidget/components/ChatWindow.tsx +363 -0
  161. package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +216 -0
  162. package/src/components/LiveChatWidget/components/MessageContent/CartCard.tsx +202 -0
  163. package/src/components/LiveChatWidget/components/MessageContent/ErrorBlock.tsx +75 -0
  164. package/src/components/LiveChatWidget/components/MessageContent/FAQList.tsx +128 -0
  165. package/src/components/LiveChatWidget/components/MessageContent/PolicyBlock.tsx +152 -0
  166. package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +227 -0
  167. package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +377 -0
  168. package/src/components/LiveChatWidget/components/MessageContent/ProductList.tsx +293 -0
  169. package/src/components/LiveChatWidget/components/MessageContent/PromotionList.tsx +170 -0
  170. package/src/components/LiveChatWidget/components/MessageContent/QuickReplies.tsx +91 -0
  171. package/src/components/LiveChatWidget/components/MessageContent/TextBlock.tsx +110 -0
  172. package/src/components/LiveChatWidget/components/MessageContent/ThinkingBlock.tsx +53 -0
  173. package/src/components/LiveChatWidget/components/MessageContent/index.ts +16 -0
  174. package/src/components/LiveChatWidget/components/MessageContent.tsx +113 -0
  175. package/src/components/LiveChatWidget/components/MessageList.tsx +261 -0
  176. package/src/components/LiveChatWidget/components/ScrollAnchor.tsx +75 -0
  177. package/src/components/LiveChatWidget/constants.ts +36 -0
  178. package/src/components/LiveChatWidget/hooks/useChatAPI.ts +146 -0
  179. package/src/components/LiveChatWidget/hooks/useChatState.ts +1090 -0
  180. package/src/components/LiveChatWidget/hooks/useSession.ts +123 -0
  181. package/src/components/LiveChatWidget/index.tsx +63 -0
  182. package/src/components/LiveChatWidget/types.ts +1011 -0
  183. package/src/components/LiveChatWidget/utils/cartTransformers.ts +72 -0
  184. package/src/components/LiveChatWidget/utils/fetcher.ts +131 -0
  185. package/src/components/LiveChatWidget/utils/messageRenderers.ts +120 -0
  186. package/src/components/LiveChatWidget/utils/productTransformers.ts +149 -0
  187. package/src/components/LiveChatWidget/utils/userId.ts +140 -0
  188. package/src/components/LiveChatWidget/utils/validation.ts +99 -0
  189. package/src/components/chat/markdown.tsx +1 -1
  190. package/src/components/credits/creditsBanner/index.tsx +5 -5
  191. package/src/components/index.ts +23 -0
  192. package/src/stories/LiveChatWidget.stories.tsx +322 -0
  193. package/src/styles/livechat.css +317 -0
  194. package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.d.ts +0 -7
  195. package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.js +0 -2
  196. package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.js.map +0 -7
  197. package/dist/cjs/components/credits/context/utils/atobID.d.ts +0 -1
  198. package/dist/cjs/components/credits/context/utils/atobID.js +0 -2
  199. package/dist/cjs/components/credits/context/utils/atobID.js.map +0 -7
  200. package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.d.ts +0 -5
  201. package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.js +0 -2
  202. package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.js.map +0 -7
  203. package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.d.ts +0 -8
  204. package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.js +0 -2
  205. package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.js.map +0 -7
  206. package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.d.ts +0 -9
  207. package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js +0 -2
  208. package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js.map +0 -7
  209. package/dist/cjs/components/credits/context/utils/variantGetCoupon.d.ts +0 -6
  210. package/dist/cjs/components/credits/context/utils/variantGetCoupon.js +0 -2
  211. package/dist/cjs/components/credits/context/utils/variantGetCoupon.js.map +0 -7
  212. package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.d.ts +0 -7
  213. package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.js +0 -2
  214. package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.js.map +0 -7
  215. package/dist/esm/components/credits/context/utils/atobID.d.ts +0 -1
  216. package/dist/esm/components/credits/context/utils/atobID.js +0 -2
  217. package/dist/esm/components/credits/context/utils/atobID.js.map +0 -7
  218. package/dist/esm/components/credits/context/utils/functionDiscountCalculate.d.ts +0 -5
  219. package/dist/esm/components/credits/context/utils/functionDiscountCalculate.js +0 -2
  220. package/dist/esm/components/credits/context/utils/functionDiscountCalculate.js.map +0 -7
  221. package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.d.ts +0 -8
  222. package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.js +0 -2
  223. package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.js.map +0 -7
  224. package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.d.ts +0 -9
  225. package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js +0 -2
  226. package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js.map +0 -7
  227. package/dist/esm/components/credits/context/utils/variantGetCoupon.d.ts +0 -6
  228. package/dist/esm/components/credits/context/utils/variantGetCoupon.js +0 -2
  229. package/dist/esm/components/credits/context/utils/variantGetCoupon.js.map +0 -7
@@ -0,0 +1,293 @@
1
+ /**
2
+ * 商品列表渲染器
3
+ * 显示多个商品的纵向列表,支持展开/收起
4
+ * 基于 specs/livechat-widget/data-model.md 的商品数据模型
5
+ */
6
+
7
+ import React, { useState } from 'react'
8
+ import type { MessageRenderer, ProductListContent, Product, CommonText } from '../../types'
9
+ import { CURRENCY_SYMBOLS, DEFAULT_COMMON_TEXT } from '../../constants.js'
10
+
11
+ /**
12
+ * 格式化价格
13
+ */
14
+ function formatPrice(price: Product['price']): string {
15
+ const { amount, currency } = price
16
+
17
+ const symbol = CURRENCY_SYMBOLS[currency] || currency
18
+ return `${symbol}${amount.toFixed(2)}`
19
+ }
20
+
21
+ /**
22
+ * 格式化折扣标签文本
23
+ * @param discount 折扣对象
24
+ * @param currency 货币代码
25
+ * @param offText "OFF" 文案
26
+ * @returns 格式化后的折扣文本(如 "$10 OFF" 或 "20% OFF")
27
+ */
28
+ function formatDiscountLabel(
29
+ discount: { discount_type?: string; discount_value?: string | number },
30
+ currency: string,
31
+ offText: string = DEFAULT_COMMON_TEXT.off
32
+ ): string {
33
+ if (!discount.discount_type || discount.discount_value === undefined) {
34
+ return ''
35
+ }
36
+
37
+ // 将 discount_value 转换为数字
38
+ const value =
39
+ typeof discount.discount_value === 'string' ? parseFloat(discount.discount_value) : discount.discount_value
40
+
41
+ if (isNaN(value)) {
42
+ return ''
43
+ }
44
+
45
+ if (discount.discount_type === 'percentage') {
46
+ return `${Math.round(value)}% ${offText}`
47
+ }
48
+
49
+ if (discount.discount_type === 'fixed_amount') {
50
+ const symbol = CURRENCY_SYMBOLS[currency] || currency
51
+ return `${symbol}${Math.round(value)} ${offText}`
52
+ }
53
+
54
+ return ''
55
+ }
56
+
57
+ /**
58
+ * 紧凑型商品卡片(用于纵向列表)
59
+ */
60
+ const CompactProductCard: React.FC<{
61
+ product: Product
62
+ onAddToCart?: (product: Product) => void
63
+ addToCartText?: string
64
+ offText?: string
65
+ }> = ({ product, onAddToCart, addToCartText = DEFAULT_COMMON_TEXT.addToCart, offText = DEFAULT_COMMON_TEXT.off }) => {
66
+ const { title, description, price, imageUrl, stockStatus, averageRating, variants } = product
67
+
68
+ const isOutOfStock = stockStatus === 'out_of_stock'
69
+
70
+ // 获取第一个变体的折扣信息
71
+ const firstVariant = variants?.[0]
72
+ const hasDiscount = firstVariant?.discount?.has_discount
73
+ const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null
74
+ const discount = firstVariant?.discount
75
+
76
+ // 当前显示价格:有折扣时显示折扣价,否则显示原价
77
+ const currentPrice = discountPrice ? { amount: discountPrice, currency: price.currency } : price
78
+
79
+ // 格式化折扣标签
80
+ const discountLabel = discount && hasDiscount ? formatDiscountLabel(discount, price.currency, offText) : ''
81
+
82
+ const handleAddToCart = (e: React.MouseEvent) => {
83
+ e.preventDefault()
84
+ e.stopPropagation()
85
+ if (onAddToCart) {
86
+ onAddToCart(product)
87
+ }
88
+ }
89
+
90
+ return (
91
+ <div className="block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow">
92
+ <div className="block">
93
+ <div className="flex gap-2 p-4">
94
+ {/* 商品图片 */}
95
+ <div className=" flex shrink-0 items-center overflow-hidden rounded-md " style={{ width: '40%' }}>
96
+ <img
97
+ src={imageUrl}
98
+ alt={title}
99
+ className={`h-auto w-full object-cover ${isOutOfStock ? 'opacity-50' : ''}`}
100
+ loading="lazy"
101
+ />
102
+ </div>
103
+
104
+ {/* 商品信息 */}
105
+ <div className="flex flex-1 flex-col justify-center">
106
+ {/* 折扣标签 */}
107
+ {discountLabel && (
108
+ <div
109
+ className="mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white"
110
+ style={{ backgroundColor: '#005D8E', paddingTop: '6px', paddingBottom: '4px' }}
111
+ >
112
+ {discountLabel}
113
+ </div>
114
+ )}
115
+
116
+ {/* 标题 */}
117
+ <h4 className="line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]">
118
+ {title}
119
+ </h4>
120
+
121
+ {/* 描述(可选) */}
122
+ {description && (
123
+ <p className="line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]">
124
+ {description}
125
+ </p>
126
+ )}
127
+
128
+ {/* 价格和评分 */}
129
+ <div className="mt-4 flex items-center gap-2">
130
+ <div className="flex items-center gap-1">
131
+ {/* 当前价格(有折扣时显示折扣价,否则显示原价) */}
132
+ <span className="text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]">
133
+ {formatPrice(currentPrice)}
134
+ </span>
135
+ {/* 原价(划线价)- 仅在有折扣时显示 */}
136
+ {hasDiscount && (
137
+ <span className="text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through">
138
+ {formatPrice(price)}
139
+ </span>
140
+ )}
141
+ </div>
142
+ {/* 评分(可选) */}
143
+ {averageRating !== undefined && (
144
+ <div className="flex items-center gap-0.5 text-xs text-gray-600">
145
+ <span className="text-yellow-500">⭐</span>
146
+ <span>{averageRating.toFixed(1)}</span>
147
+ </div>
148
+ )}
149
+ </div>
150
+
151
+ {/* Add to Cart 按钮 - 在价格下方 */}
152
+ <button
153
+ type="button"
154
+ onClick={handleAddToCart}
155
+ className="mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white"
156
+ style={{ backgroundColor: '#1D1D1F' }}
157
+ >
158
+ {addToCartText}
159
+ </button>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ )
165
+ }
166
+
167
+ /**
168
+ * 商品列表内部组件(支持展开/收起)
169
+ */
170
+ const ProductListComponent: React.FC<{
171
+ products: Product[]
172
+ title?: string
173
+ onAddToCart?: (product: Product) => void
174
+ commonText?: CommonText
175
+ }> = ({ products, title, onAddToCart, commonText }) => {
176
+ const [isExpanded, setIsExpanded] = useState(false)
177
+
178
+ // 合并默认文案和自定义文案
179
+ const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }
180
+
181
+ // 过滤掉 null 或无效的产品
182
+ const validProducts = products.filter(p => p && p.shopifyId)
183
+
184
+ // 默认显示前3个产品
185
+ const INITIAL_DISPLAY_COUNT = 3
186
+ const hasMore = validProducts.length > INITIAL_DISPLAY_COUNT
187
+ const displayedProducts = isExpanded ? validProducts : validProducts.slice(0, INITIAL_DISPLAY_COUNT)
188
+
189
+ return (
190
+ <div className="flex w-full flex-col gap-2">
191
+ {/* 列表标题(可选) */}
192
+ {title && <h3 className="text-sm font-semibold text-gray-900">{title}</h3>}
193
+
194
+ {/* 纵向排列的商品列表 */}
195
+ <div className="flex flex-col gap-1.5">
196
+ {displayedProducts.map(product => {
197
+ if (!product || !product.shopifyId) return null
198
+ return (
199
+ <CompactProductCard
200
+ key={product.shopifyId}
201
+ product={product}
202
+ onAddToCart={onAddToCart}
203
+ addToCartText={mergedText.addToCart}
204
+ offText={mergedText.off}
205
+ />
206
+ )
207
+ })}
208
+ </div>
209
+
210
+ {/* Learn More 按钮 */}
211
+ {hasMore && (
212
+ <button
213
+ type="button"
214
+ onClick={() => setIsExpanded(!isExpanded)}
215
+ className="flex items-center justify-center gap-1.5 px-3 py-2 text-[14px] font-bold leading-[1.2] tracking-tighter text-[#080A0F]"
216
+ >
217
+ <span>{isExpanded ? mergedText.showLess : mergedText.learnMore}</span>
218
+ <svg
219
+ width="14"
220
+ height="14"
221
+ viewBox="0 0 24 24"
222
+ fill="none"
223
+ stroke="currentColor"
224
+ strokeWidth="2"
225
+ strokeLinecap="round"
226
+ strokeLinejoin="round"
227
+ className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`}
228
+ >
229
+ <polyline points="6 9 12 15 18 9" />
230
+ </svg>
231
+ </button>
232
+ )}
233
+ </div>
234
+ )
235
+ }
236
+
237
+ /**
238
+ * 商品列表渲染器
239
+ *
240
+ * 功能:
241
+ * - 纵向展示多个商品
242
+ * - 默认显示前3个产品
243
+ * - 支持展开/收起查看全部
244
+ * - 紧凑型卡片设计
245
+ * - 可选标题
246
+ *
247
+ * 布局:
248
+ * ```
249
+ * 标题(可选)
250
+ * ┌─────────────────┐
251
+ * │ [图] 商品标题 │
252
+ * │ $29.99 │
253
+ * └─────────────────┘
254
+ * ┌─────────────────┐
255
+ * │ [图] 商品标题 │
256
+ * │ $39.99 │
257
+ * └─────────────────┘
258
+ * ┌─────────────────┐
259
+ * │ [图] 商品标题 │
260
+ * │ $49.99 │
261
+ * └─────────────────┘
262
+ * [ Learn More ↓ ]
263
+ * ```
264
+ *
265
+ * @example
266
+ * ```tsx
267
+ * const content: ProductListContent = {
268
+ * type: 'product_list',
269
+ * data: {
270
+ * title: '相关商品推荐',
271
+ * products: [product1, product2, product3, product4, product5]
272
+ * }
273
+ * }
274
+ * <ProductList.render(content, false, false) />
275
+ * ```
276
+ */
277
+ export const ProductList: MessageRenderer = {
278
+ render: content => {
279
+ const productListContent = content as ProductListContent
280
+ const { products, title, onAddToCart, commonText } = productListContent.data
281
+
282
+ // 过滤掉 null 或无效的产品
283
+ const validProducts = products?.filter(p => p && p.shopifyId) || []
284
+
285
+ if (validProducts.length === 0) {
286
+ return null
287
+ }
288
+
289
+ return (
290
+ <ProductListComponent products={validProducts} title={title} onAddToCart={onAddToCart} commonText={commonText} />
291
+ )
292
+ },
293
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * 促销活动列表组件
3
+ * 显示当前进行中的促销活动信息
4
+ * 基于后端数据结构规范:promotion_list
5
+ */
6
+
7
+ import React from 'react'
8
+ import type { MessageRenderer, CommonText } from '../../types'
9
+ import { DEFAULT_COMMON_TEXT } from '../../constants'
10
+
11
+ /**
12
+ * 促销活动数据结构
13
+ */
14
+ export interface PromotionItem {
15
+ id: string // 活动 ID
16
+ title: string // 活动标题
17
+ subtitle?: string // 副标题(如 "Up to 30% off")
18
+ description?: string // 活动描述
19
+ banner_url?: string // Banner 图片 URL
20
+ url?: string // 活动详情页 URL
21
+ time_range: {
22
+ start: string // 开始时间 (ISO 8601)
23
+ end?: string | null // 结束时间,null 表示无结束日期
24
+ is_active: boolean // 是否当前活跃
25
+ }
26
+ priority?: number // 优先级(数字越大越靠前)
27
+ product_count?: number // 参与商品数量
28
+ metadata?: {
29
+ display_order?: number
30
+ target_audience?: string
31
+ highlight_color?: string
32
+ banner_url?:string
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 促销活动列表数据结构
38
+ */
39
+ export interface PromotionListData {
40
+ found: boolean // 是否找到活动
41
+ count: number // 返回的活动数量
42
+ total?: number // 总活动数量(可选)
43
+ results: PromotionItem[] // 活动列表
44
+ commonText?: CommonText // 通用文案配置
45
+ }
46
+
47
+ export interface PromotionListProps {
48
+ /**
49
+ * 促销活动列表数据
50
+ */
51
+ data: PromotionListData
52
+
53
+ /**
54
+ * 是否为用户消息
55
+ */
56
+ isUser?: boolean
57
+
58
+ /**
59
+ * 是否为系统消息
60
+ */
61
+ isSystem?: boolean
62
+ }
63
+
64
+ /**
65
+ * 格式化日期显示
66
+ */
67
+ const formatDate = (dateStr: string): string => {
68
+ const date = new Date(dateStr)
69
+ return date.toLocaleDateString('en-US', {
70
+ year: 'numeric',
71
+ month: 'short',
72
+ day: 'numeric',
73
+ })
74
+ }
75
+
76
+ /**
77
+ * 促销活动列表组件
78
+ *
79
+ * 功能:
80
+ * - 显示当前进行中的促销活动
81
+ * - 支持活动 Banner 图片展示
82
+ * - 显示活动时间范围
83
+ * - 显示参与商品数量
84
+ * - 支持跳转到活动详情页
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * <PromotionList
89
+ * data={{
90
+ * found: true,
91
+ * count: 2,
92
+ * results: [...]
93
+ * }}
94
+ * />
95
+ * ```
96
+ */
97
+ export const PromotionList: React.FC<PromotionListProps> = ({ data, isUser = false, isSystem = false }) => {
98
+ const { found, results, commonText } = data
99
+
100
+ // 合并默认文案和自定义文案
101
+ const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }
102
+
103
+ return (
104
+ <div className="space-y-3">
105
+ {results.map(promotion => {
106
+ const bannerUrl = promotion.banner_url ||promotion?.metadata?.banner_url
107
+
108
+ // 没有图片则不展示
109
+ if (!bannerUrl) {
110
+ return null
111
+ }
112
+
113
+ return (
114
+ <div key={promotion.id} className="relative overflow-hidden rounded-2xl bg-[#F5F6F7]">
115
+ {/* Banner 图片 */}
116
+ <div className="aspect-[16/9] w-full overflow-hidden bg-gray-100">
117
+ <img
118
+ src={bannerUrl}
119
+ alt={promotion.title}
120
+ className="size-full object-cover object-center"
121
+ loading="lazy"
122
+ />
123
+ </div>
124
+
125
+ {/* 活动信息 - 叠加在图片上 */}
126
+ <div
127
+ className="absolute inset-0 flex flex-col justify-end p-4 text-[#080A0F]"
128
+ style={{ color: promotion?.metadata?.highlight_color }}
129
+ >
130
+ <div className="mb-2">
131
+ <h3 className="text-xl font-bold leading-[1.2] tracking-[-0.04em]">{promotion.title}</h3>
132
+ {promotion.subtitle && (
133
+ <p className="text-sm font-bold leading-[1.4] tracking-[-0.02em]">{promotion.subtitle}</p>
134
+ )}
135
+ </div>
136
+
137
+ {/* 查看详情按钮 */}
138
+ {promotion.url && (
139
+ <a
140
+ href={promotion.url + '?ref=LiveChat'}
141
+ target="_blank"
142
+ rel="noopener noreferrer"
143
+ className="inline-flex items-center gap-1 text-sm font-bold tracking-[-0.04em]"
144
+ >
145
+ {mergedText.learnMore}
146
+ <svg className="size-[18px]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
147
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
148
+ </svg>
149
+ </a>
150
+ )}
151
+ </div>
152
+ </div>
153
+ )
154
+ })}
155
+ </div>
156
+ )
157
+ }
158
+
159
+ /**
160
+ * 创建促销活动列表渲染器
161
+ */
162
+ export const PromotionListRenderer: MessageRenderer = {
163
+ render: (content, isUser, isSystem) => {
164
+ if (content.type !== 'promotion_list' || !content.data) {
165
+ return null
166
+ }
167
+
168
+ return <PromotionList data={content.data as PromotionListData} isUser={isUser} isSystem={isSystem} />
169
+ },
170
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * 快捷回复按钮渲染器
3
+ * 显示可点击的快捷回复选项
4
+ * 基于 specs/livechat-widget/data-model.md 的快捷回复数据模型
5
+ */
6
+
7
+ import React from 'react'
8
+ import type { MessageRenderer, QuickRepliesContent, QuickReply } from '../../types'
9
+
10
+ export interface QuickRepliesProps {
11
+ /**
12
+ * 快捷回复点击回调
13
+ */
14
+ onReplyClick?: (reply: QuickReply) => void
15
+ }
16
+
17
+ /**
18
+ * 快捷回复按钮渲染器
19
+ *
20
+ * 功能:
21
+ * - 显示多个快捷回复按钮
22
+ * - 支持图标(可选)
23
+ * - 点击后发送对应的消息
24
+ *
25
+ * 使用场景:
26
+ * - 欢迎消息后的常见问题
27
+ * - 引导用户选择
28
+ * - 快速操作按钮
29
+ *
30
+ * 布局:
31
+ * ```
32
+ * ┌──────────┐ ┌──────────┐
33
+ * │ 🛒 查价格 │ │ 📦 查物流 │
34
+ * └──────────┘ └──────────┘
35
+ * ┌──────────┐ ┌──────────┐
36
+ * │ 📞 联系客服│ │ 💬 其他问题│
37
+ * └──────────┘ └──────────┘
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * const content: QuickRepliesContent = {
43
+ * type: 'quick_replies',
44
+ * data: {
45
+ * replies: [
46
+ * { id: '1', label: '查价格', value: '我想查询商品价格', icon: '🛒' },
47
+ * { id: '2', label: '查物流', value: '我想查询物流信息', icon: '📦' }
48
+ * ]
49
+ * }
50
+ * }
51
+ * <QuickReplies.render(content, false, false) />
52
+ * ```
53
+ */
54
+ export const createQuickRepliesRenderer = (onReplyClick?: (reply: QuickReply) => void): MessageRenderer => ({
55
+ render: (content, isUser, isSystem) => {
56
+ const quickRepliesContent = content as QuickRepliesContent
57
+ const { replies } = quickRepliesContent.data
58
+
59
+ if (!replies || replies.length === 0) {
60
+ return null
61
+ }
62
+
63
+ const handleClick = (reply: QuickReply) => {
64
+ onReplyClick?.(reply)
65
+ }
66
+
67
+ return (
68
+ <div className="flex flex-wrap gap-2">
69
+ {replies.map(reply => (
70
+ <button
71
+ key={reply.id}
72
+ type="button"
73
+ onClick={() => handleClick(reply)}
74
+ className="livechat-quick-reply-button inline-flex font-bold items-center gap-1 rounded-[19px] px-3 py-[6px] text-sm leading-[140%] tracking-[-0.02em] transition-transform"
75
+ >
76
+ {/* 图标(可选) */}
77
+ {reply.icon && <span className="text-base">{reply.icon}</span>}
78
+
79
+ {/* 标签 */}
80
+ <span className='text-left'>{reply.label}</span>
81
+ </button>
82
+ ))}
83
+ </div>
84
+ )
85
+ },
86
+ })
87
+
88
+ /**
89
+ * 默认快捷回复渲染器(无回调)
90
+ */
91
+ export const QuickReplies: MessageRenderer = createQuickRepliesRenderer()
@@ -0,0 +1,110 @@
1
+ /**
2
+ * 文本消息内容渲染器
3
+ * 支持 Markdown 格式
4
+ * 基于 specs/livechat-widget/plan.md 的文本消息设计
5
+ */
6
+
7
+ import React from 'react'
8
+ import ReactMarkdown from 'react-markdown'
9
+ import remarkGfm from 'remark-gfm'
10
+ import type { MessageRenderer, TextContent } from '../../types'
11
+
12
+ /**
13
+ * 文本消息渲染器
14
+ *
15
+ * 功能:
16
+ * - 支持 Markdown 语法(粗体、斜体、链接、列表等)
17
+ * - 安全渲染(React Markdown 自动防护 XSS)
18
+ * - 响应式文本样式
19
+ *
20
+ * Markdown 支持:
21
+ * - 粗体:**text** 或 __text__
22
+ * - 斜体:*text* 或 _text_
23
+ * - 链接:[text](url)
24
+ * - 列表:- item 或 1. item
25
+ * - 代码:`code` 或 ```code block```
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * const content: TextContent = {
30
+ * type: 'text',
31
+ * text: '您好!**这是粗体**,*这是斜体*。'
32
+ * }
33
+ * <TextBlock.render(content, false, false) />
34
+ * ```
35
+ */
36
+ export const TextBlock: MessageRenderer = {
37
+ render: (content, isUser, isSystem) => {
38
+ const textContent = content as TextContent
39
+
40
+ if (!textContent.text) {
41
+ return null
42
+ }
43
+
44
+ return (
45
+ <div className="livechat-markdown text-base md:text-sm">
46
+ <ReactMarkdown
47
+ remarkPlugins={[remarkGfm]}
48
+ components={{
49
+ // 自定义链接样式
50
+ a: ({ node, ...props }) => (
51
+ <a
52
+ {...props}
53
+ className={`underline ${isUser ? 'text-blue-200 hover:text-blue-100' : 'text-blue-600 hover:text-blue-700'}`}
54
+ target="_blank"
55
+ rel="noopener noreferrer"
56
+ />
57
+ ),
58
+ // 自定义代码块样式
59
+ code: ({ node, ...props }: any) =>
60
+ props.inline ? (
61
+ <code
62
+ {...props}
63
+ className={`rounded px-1.5 py-0.5 font-mono text-xs ${isUser ? 'bg-[#004A6E] text-white' : 'bg-gray-200 text-gray-800'}`}
64
+ />
65
+ ) : (
66
+ <code
67
+ {...props}
68
+ className={`block overflow-x-auto rounded px-3 py-2 font-mono text-xs ${isUser ? 'bg-[#004A6E] text-white' : 'bg-gray-200 text-gray-800'}`}
69
+ />
70
+ ),
71
+ // 自定义段落样式
72
+ p: ({ node, ...props }) => <p {...props} className="last:mb-0" />,
73
+ // 自定义列表样式
74
+ ul: ({ node, ...props }) => <ul {...props} className="ml-4 list-disc" />,
75
+ ol: ({ node, ...props }) => <ol {...props} className="mb-2 ml-4 list-decimal" />,
76
+ li: ({ node, ...props }) => <li {...props} className="mb-1" />,
77
+ // 自定义标题样式
78
+ h1: ({ node, ...props }) => <h1 {...props} className="mb-2 font-bold" />,
79
+ h2: ({ node, ...props }) => <h2 {...props} className="mb-2 font-bold" />,
80
+ h3: ({ node, ...props }) => <h3 {...props} className="mb-1 font-bold" />,
81
+ // 自定义强调样式
82
+ strong: ({ node, ...props }) => <strong {...props} className="font-bold" />,
83
+ em: ({ node, ...props }) => <em {...props} className="italic" />,
84
+ // 表格样式
85
+ table: ({ node, ...props }) => (
86
+ <div className="my-2 overflow-x-auto">
87
+ <table {...props} className="min-w-full border-collapse border border-gray-300 text-base md:text-sm" />
88
+ </div>
89
+ ),
90
+ thead: ({ node, ...props }) => (
91
+ <thead {...props} className="bg-gray-100" />
92
+ ),
93
+ tbody: ({ node, ...props }) => <tbody {...props} />,
94
+ tr: ({ node, ...props }) => (
95
+ <tr {...props} className="border-b border-gray-300" />
96
+ ),
97
+ th: ({ node, ...props }) => (
98
+ <th {...props} className="border border-gray-300 px-3 py-2 text-left font-semibold" />
99
+ ),
100
+ td: ({ node, ...props }) => (
101
+ <td {...props} className="border border-gray-300 px-3 py-2" />
102
+ ),
103
+ }}
104
+ >
105
+ {textContent.text}
106
+ </ReactMarkdown>
107
+ </div>
108
+ )
109
+ },
110
+ }