@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.
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.d.ts +21 -1
- 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.d.ts +23 -2
- 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/ChatHeader.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/ChatHeader.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/ChatInput.d.ts +5 -0
- package/dist/cjs/components/LiveChatWidget/components/ChatInput.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/ChatInput.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js +2 -2
- package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ChatWindow.d.ts +5 -0
- package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +33 -0
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ErrorBlock.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ErrorBlock.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +17 -24
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -4
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +7 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.d.ts +4 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/constants.d.ts +5 -0
- package/dist/cjs/components/LiveChatWidget/constants.js +1 -1
- package/dist/cjs/components/LiveChatWidget/constants.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.d.ts +9 -0
- package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +35 -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/index.d.ts +1 -1
- package/dist/cjs/components/LiveChatWidget/index.js +1 -1
- package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/types.d.ts +212 -3
- package/dist/cjs/components/LiveChatWidget/types.js +1 -1
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/components/LiveChatWidget/utils/fetcher.d.ts +42 -0
- package/dist/cjs/components/LiveChatWidget/utils/fetcher.js +2 -0
- package/dist/cjs/components/LiveChatWidget/utils/fetcher.js.map +7 -0
- package/dist/cjs/components/chat/markdown.js +1 -1
- package/dist/cjs/components/chat/markdown.js.map +2 -2
- package/dist/cjs/components/credits/creditsBanner/index.js +2 -2
- package/dist/cjs/components/credits/creditsBanner/index.js.map +2 -2
- package/dist/cjs/components/index.d.ts +2 -0
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +3 -3
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -79
- package/dist/cjs/stories/LiveChatWidget.stories.js +8 -47
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.d.ts +21 -1
- 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.d.ts +23 -2
- 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/ChatHeader.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/ChatHeader.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/ChatInput.d.ts +5 -0
- package/dist/esm/components/LiveChatWidget/components/ChatInput.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/ChatInput.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ChatMessage.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ChatWindow.d.ts +5 -0
- package/dist/esm/components/LiveChatWidget/components/ChatWindow.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/ChatWindow.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +33 -0
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
- package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ErrorBlock.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ErrorBlock.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +17 -24
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -4
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +7 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.d.ts +4 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/constants.d.ts +5 -0
- package/dist/esm/components/LiveChatWidget/constants.js +1 -1
- package/dist/esm/components/LiveChatWidget/constants.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.d.ts +9 -0
- package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +35 -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/index.d.ts +1 -1
- package/dist/esm/components/LiveChatWidget/index.js +1 -1
- package/dist/esm/components/LiveChatWidget/index.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/types.d.ts +212 -3
- package/dist/esm/components/LiveChatWidget/utils/fetcher.d.ts +42 -0
- package/dist/esm/components/LiveChatWidget/utils/fetcher.js +2 -0
- package/dist/esm/components/LiveChatWidget/utils/fetcher.js.map +7 -0
- package/dist/esm/components/chat/markdown.js +1 -1
- package/dist/esm/components/chat/markdown.js.map +2 -2
- package/dist/esm/components/credits/creditsBanner/index.js +2 -2
- package/dist/esm/components/credits/creditsBanner/index.js.map +2 -2
- package/dist/esm/components/index.d.ts +2 -0
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +3 -3
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -79
- package/dist/esm/stories/LiveChatWidget.stories.js +8 -47
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/index.d.mts +1305 -0
- package/dist/index.d.ts +1305 -0
- package/dist/index.js +26656 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +26641 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +8 -1
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +887 -0
- package/src/components/LiveChatWidget/api/chat.ts +175 -0
- package/src/components/LiveChatWidget/components/ChatBubble.tsx +152 -0
- package/src/components/LiveChatWidget/components/ChatHeader.tsx +150 -0
- package/src/components/LiveChatWidget/components/ChatInput.tsx +253 -0
- package/src/components/LiveChatWidget/components/ChatMessage.tsx +190 -0
- package/src/components/LiveChatWidget/components/ChatWindow.tsx +363 -0
- package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +216 -0
- package/src/components/LiveChatWidget/components/MessageContent/CartCard.tsx +202 -0
- package/src/components/LiveChatWidget/components/MessageContent/ErrorBlock.tsx +75 -0
- package/src/components/LiveChatWidget/components/MessageContent/FAQList.tsx +128 -0
- package/src/components/LiveChatWidget/components/MessageContent/PolicyBlock.tsx +152 -0
- package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +227 -0
- package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +377 -0
- package/src/components/LiveChatWidget/components/MessageContent/ProductList.tsx +293 -0
- package/src/components/LiveChatWidget/components/MessageContent/PromotionList.tsx +170 -0
- package/src/components/LiveChatWidget/components/MessageContent/QuickReplies.tsx +91 -0
- package/src/components/LiveChatWidget/components/MessageContent/TextBlock.tsx +110 -0
- package/src/components/LiveChatWidget/components/MessageContent/ThinkingBlock.tsx +53 -0
- package/src/components/LiveChatWidget/components/MessageContent/index.ts +16 -0
- package/src/components/LiveChatWidget/components/MessageContent.tsx +113 -0
- package/src/components/LiveChatWidget/components/MessageList.tsx +261 -0
- package/src/components/LiveChatWidget/components/ScrollAnchor.tsx +75 -0
- package/src/components/LiveChatWidget/constants.ts +36 -0
- package/src/components/LiveChatWidget/hooks/useChatAPI.ts +146 -0
- package/src/components/LiveChatWidget/hooks/useChatState.ts +1090 -0
- package/src/components/LiveChatWidget/hooks/useSession.ts +123 -0
- package/src/components/LiveChatWidget/index.tsx +63 -0
- package/src/components/LiveChatWidget/types.ts +1011 -0
- package/src/components/LiveChatWidget/utils/cartTransformers.ts +72 -0
- package/src/components/LiveChatWidget/utils/fetcher.ts +131 -0
- package/src/components/LiveChatWidget/utils/messageRenderers.ts +120 -0
- package/src/components/LiveChatWidget/utils/productTransformers.ts +149 -0
- package/src/components/LiveChatWidget/utils/userId.ts +140 -0
- package/src/components/LiveChatWidget/utils/validation.ts +99 -0
- package/src/components/chat/markdown.tsx +1 -1
- package/src/components/credits/creditsBanner/index.tsx +5 -5
- package/src/components/index.ts +23 -0
- package/src/stories/LiveChatWidget.stories.tsx +322 -0
- package/src/styles/livechat.css +317 -0
- package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.d.ts +0 -7
- package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.js +0 -2
- package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.js.map +0 -7
- package/dist/cjs/components/credits/context/utils/atobID.d.ts +0 -1
- package/dist/cjs/components/credits/context/utils/atobID.js +0 -2
- package/dist/cjs/components/credits/context/utils/atobID.js.map +0 -7
- package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.d.ts +0 -5
- package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.js +0 -2
- package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.js.map +0 -7
- package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.d.ts +0 -8
- package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.js +0 -2
- package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.js.map +0 -7
- package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.d.ts +0 -9
- package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js +0 -2
- package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js.map +0 -7
- package/dist/cjs/components/credits/context/utils/variantGetCoupon.d.ts +0 -6
- package/dist/cjs/components/credits/context/utils/variantGetCoupon.js +0 -2
- package/dist/cjs/components/credits/context/utils/variantGetCoupon.js.map +0 -7
- package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.d.ts +0 -7
- package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.js +0 -2
- package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.js.map +0 -7
- package/dist/esm/components/credits/context/utils/atobID.d.ts +0 -1
- package/dist/esm/components/credits/context/utils/atobID.js +0 -2
- package/dist/esm/components/credits/context/utils/atobID.js.map +0 -7
- package/dist/esm/components/credits/context/utils/functionDiscountCalculate.d.ts +0 -5
- package/dist/esm/components/credits/context/utils/functionDiscountCalculate.js +0 -2
- package/dist/esm/components/credits/context/utils/functionDiscountCalculate.js.map +0 -7
- package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.d.ts +0 -8
- package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.js +0 -2
- package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.js.map +0 -7
- package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.d.ts +0 -9
- package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js +0 -2
- package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js.map +0 -7
- package/dist/esm/components/credits/context/utils/variantGetCoupon.d.ts +0 -6
- package/dist/esm/components/credits/context/utils/variantGetCoupon.js +0 -2
- package/dist/esm/components/credits/context/utils/variantGetCoupon.js.map +0 -7
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 购物车卡片渲染器
|
|
3
|
+
* 显示购物车内容、价格汇总和结账按钮
|
|
4
|
+
* 基于后端返回的购物车数据结构
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import type { MessageRenderer, CartContent, CartLine, CartAmount } from '../../types'
|
|
9
|
+
import { CURRENCY_SYMBOLS, DEFAULT_COMMON_TEXT } from '../../constants.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 格式化金额
|
|
13
|
+
* @param amount 金额对象
|
|
14
|
+
* @returns 格式化后的金额字符串(如 "$99.99")
|
|
15
|
+
*/
|
|
16
|
+
function formatAmount(amount: CartAmount): string {
|
|
17
|
+
const { amount: value, currencyCode } = amount
|
|
18
|
+
|
|
19
|
+
const symbol = CURRENCY_SYMBOLS[currencyCode] || currencyCode
|
|
20
|
+
const numValue = parseFloat(value)
|
|
21
|
+
|
|
22
|
+
return `${symbol}${numValue.toFixed(2)}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 购物车商品行组件
|
|
27
|
+
*/
|
|
28
|
+
const CartLineItem: React.FC<{
|
|
29
|
+
line: CartLine
|
|
30
|
+
}> = ({ line }) => {
|
|
31
|
+
const { quantity, merchandise, cost } = line
|
|
32
|
+
const { product, title: variantTitle, image } = merchandise
|
|
33
|
+
|
|
34
|
+
// 商品图片 URL
|
|
35
|
+
const imageUrl = image?.url || ''
|
|
36
|
+
|
|
37
|
+
// 判断是否有折扣(总价 < 原价)
|
|
38
|
+
const hasDiscount =
|
|
39
|
+
parseFloat(cost.totalAmount.amount) < parseFloat(cost.subtotalAmount.amount) &&
|
|
40
|
+
cost.totalAmount.currencyCode === cost.subtotalAmount.currencyCode
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="flex gap-4">
|
|
44
|
+
{/* 商品图片 */}
|
|
45
|
+
<div className="shrink-0 overflow-hidden rounded-md" style={{ width: '72px', height: '72px' }}>
|
|
46
|
+
<img src={imageUrl} alt={product.title} className="size-full object-cover" loading="lazy" />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* 商品信息 */}
|
|
50
|
+
<div className="flex flex-1 flex-col">
|
|
51
|
+
<h4 className="line-clamp-2 text-sm tablet:text-[16px] font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]">
|
|
52
|
+
{product.title}
|
|
53
|
+
</h4>
|
|
54
|
+
<div className="flex items-end justify-between gap-2">
|
|
55
|
+
{/* 左侧:标题、变体、数量 */}
|
|
56
|
+
<div className="flex-1">
|
|
57
|
+
{variantTitle && (
|
|
58
|
+
<p className="mt-0.5 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#4A4C56]">{variantTitle}</p>
|
|
59
|
+
)}
|
|
60
|
+
<p className="mt-1 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]">×{quantity}</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* 右侧:价格 */}
|
|
64
|
+
<div className="flex gap-1 text-right">
|
|
65
|
+
<div className="tablet:text-[16px] text-sm font-bold leading-[1.4] tracking-[-0.02em] text-gray-900">
|
|
66
|
+
{formatAmount(cost.totalAmount)}
|
|
67
|
+
</div>
|
|
68
|
+
{hasDiscount && (
|
|
69
|
+
<div className="tablet:text-[16px] text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#4A4C56] line-through">
|
|
70
|
+
{formatAmount(cost.subtotalAmount)}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 价格汇总组件(简化版)
|
|
82
|
+
*/
|
|
83
|
+
const CartSummary: React.FC<{
|
|
84
|
+
total: CartAmount
|
|
85
|
+
totalText: string
|
|
86
|
+
}> = ({ total, totalText }) => {
|
|
87
|
+
return (
|
|
88
|
+
<div className="border-t border-gray-200 p-4">
|
|
89
|
+
<div className="flex items-center justify-between">
|
|
90
|
+
<span className="text-base font-bold leading-[1.4] tracking-[-0.02em] text-gray-900">{totalText}</span>
|
|
91
|
+
<span className="text-base tablet:text-[18px] font-bold leading-[1.4] tracking-[-0.02em] text-gray-900">
|
|
92
|
+
{formatAmount(total)}
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 购物车卡片渲染器
|
|
101
|
+
*
|
|
102
|
+
* 功能:
|
|
103
|
+
* - 显示购物车商品列表
|
|
104
|
+
* - 显示价格汇总(小计、折扣、总计)
|
|
105
|
+
* - 显示折扣码
|
|
106
|
+
* - 提供 Checkout 按钮
|
|
107
|
+
* - 空购物车状态
|
|
108
|
+
*
|
|
109
|
+
* 布局:
|
|
110
|
+
* ```
|
|
111
|
+
* ┌─────────────────────────────────┐
|
|
112
|
+
* │ 购物车 (3 件商品) │
|
|
113
|
+
* ├─────────────────────────────────┤
|
|
114
|
+
* │ [图] 商品1 │
|
|
115
|
+
* │ 变体: Black │
|
|
116
|
+
* │ 数量: 2 $199.98 │
|
|
117
|
+
* ├─────────────────────────────────┤
|
|
118
|
+
* │ [图] 商品2 │
|
|
119
|
+
* │ 变体: White │
|
|
120
|
+
* │ 数量: 1 $99.99 │
|
|
121
|
+
* ├─────────────────────────────────┤
|
|
122
|
+
* │ 小计 $299.97 │
|
|
123
|
+
* │ 折扣 [SPRING20] -$30.00 │
|
|
124
|
+
* │ 总计 $269.97 │
|
|
125
|
+
* ├─────────────────────────────────┤
|
|
126
|
+
* │ [Checkout 按钮] │
|
|
127
|
+
* └─────────────────────────────────┘
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```tsx
|
|
132
|
+
* const content: CartContent = {
|
|
133
|
+
* type: 'cart',
|
|
134
|
+
* data: {
|
|
135
|
+
* isEmpty: false,
|
|
136
|
+
* cartId: "gid://...",
|
|
137
|
+
* totalQuantity: 3,
|
|
138
|
+
* lines: [...],
|
|
139
|
+
* cost: {...},
|
|
140
|
+
* checkoutUrl: "https://..."
|
|
141
|
+
* }
|
|
142
|
+
* }
|
|
143
|
+
* <CartCard.render(content, false, false) />
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export const CartCard: MessageRenderer = {
|
|
147
|
+
render: content => {
|
|
148
|
+
const cartContent = content as CartContent
|
|
149
|
+
const { data } = cartContent
|
|
150
|
+
|
|
151
|
+
if (!data) {
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { isEmpty, lines, cost, checkoutUrl, onCart, cartId, commonText } = data
|
|
156
|
+
|
|
157
|
+
// 合并默认文案和自定义文案
|
|
158
|
+
const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }
|
|
159
|
+
|
|
160
|
+
// 处理购物车按钮点击
|
|
161
|
+
const handleCart = () => {
|
|
162
|
+
if (onCart) {
|
|
163
|
+
onCart(cartId, checkoutUrl)
|
|
164
|
+
} else if (checkoutUrl) {
|
|
165
|
+
window.open(checkoutUrl, '_blank', 'noopener,noreferrer')
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 空购物车状态 - 不展示组件
|
|
170
|
+
if (isEmpty || !lines || lines.length === 0) {
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div className="w-full max-w-md overflow-hidden rounded-2xl shadow-sm" style={{ backgroundColor: '#F5F6F7' }}>
|
|
176
|
+
{/* 商品列表 */}
|
|
177
|
+
<div className="flex flex-col gap-6 overflow-y-auto p-4">
|
|
178
|
+
{lines.map(line => (
|
|
179
|
+
<CartLineItem key={line.id} line={line} />
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* 价格汇总 */}
|
|
184
|
+
<CartSummary total={cost.totalAmount} totalText={mergedText.total} />
|
|
185
|
+
|
|
186
|
+
{/* Checkout 按钮 */}
|
|
187
|
+
{(checkoutUrl || onCart) && (
|
|
188
|
+
<div className="px-4 pb-4">
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
onClick={handleCart}
|
|
192
|
+
className="w-full rounded-full py-[10px] text-center text-sm font-bold leading-[1.4] tracking-[-0.02em] text-white"
|
|
193
|
+
style={{ backgroundColor: '#1D1D1F' }}
|
|
194
|
+
>
|
|
195
|
+
{mergedText.viewMore}
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
)
|
|
201
|
+
},
|
|
202
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误消息渲染器
|
|
3
|
+
* 显示错误提示信息
|
|
4
|
+
* 基于 specs/livechat-widget/plan.md 的错误处理设计
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import type { MessageRenderer, ErrorContent } from '../../types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 错误消息渲染器
|
|
12
|
+
*
|
|
13
|
+
* 功能:
|
|
14
|
+
* - 显示错误消息
|
|
15
|
+
* - 红色背景警告样式
|
|
16
|
+
* - 可选错误代码
|
|
17
|
+
*
|
|
18
|
+
* 错误类型:
|
|
19
|
+
* - 网络错误
|
|
20
|
+
* - API 错误
|
|
21
|
+
* - 会话过期
|
|
22
|
+
* - 其他系统错误
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const content: ErrorContent = {
|
|
27
|
+
* type: 'error',
|
|
28
|
+
* data: {
|
|
29
|
+
* message: '网络连接失败,请重试',
|
|
30
|
+
* code: 'NETWORK_ERROR'
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* <ErrorBlock.render(content, false, false) />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const ErrorBlock: MessageRenderer = {
|
|
37
|
+
render: (content, isUser, isSystem) => {
|
|
38
|
+
const errorContent = content as ErrorContent
|
|
39
|
+
const { message, code } = errorContent.data
|
|
40
|
+
|
|
41
|
+
if (!message) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="flex flex-col gap-1 rounded-lg border border-red-200 bg-red-50 px-3 py-2">
|
|
47
|
+
{/* 错误图标 + 消息 */}
|
|
48
|
+
<div className="flex items-start gap-2">
|
|
49
|
+
{/* 错误图标 */}
|
|
50
|
+
<svg
|
|
51
|
+
className="mt-0.5 size-5 shrink-0 text-red-600"
|
|
52
|
+
viewBox="0 0 24 24"
|
|
53
|
+
fill="none"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
strokeWidth="2"
|
|
56
|
+
strokeLinecap="round"
|
|
57
|
+
strokeLinejoin="round"
|
|
58
|
+
>
|
|
59
|
+
<circle cx="12" cy="12" r="10" />
|
|
60
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
61
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
62
|
+
</svg>
|
|
63
|
+
|
|
64
|
+
{/* 错误消息 */}
|
|
65
|
+
<div className="flex-1">
|
|
66
|
+
<p className="text-sm font-medium text-red-800">{message}</p>
|
|
67
|
+
|
|
68
|
+
{/* 错误代码(可选) */}
|
|
69
|
+
{code && <p className="mt-1 font-mono text-xs text-red-600">Error code: {code}</p>}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAQ 列表组件
|
|
3
|
+
* 显示常见问题列表,支持折叠/展开
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState } from 'react'
|
|
7
|
+
import ReactMarkdown from 'react-markdown'
|
|
8
|
+
import remarkGfm from 'remark-gfm'
|
|
9
|
+
import type { FAQListContent, FAQItem, MessageRenderer, MessageContent } from '../../types'
|
|
10
|
+
|
|
11
|
+
export interface FAQListProps {
|
|
12
|
+
content: FAQListContent
|
|
13
|
+
onQuestionClick?: (question: string) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FAQ 列表渲染器
|
|
18
|
+
* 用于注册到 MessageRendererRegistry
|
|
19
|
+
*/
|
|
20
|
+
export const FAQListRenderer: MessageRenderer = {
|
|
21
|
+
render: (content: MessageContent) => {
|
|
22
|
+
if (content.type !== 'faq_list') {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return <FAQList content={content as FAQListContent} />
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const FAQList: React.FC<FAQListProps> = ({ content, onQuestionClick }) => {
|
|
30
|
+
const { found, count, total, results } = content.data
|
|
31
|
+
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
|
|
32
|
+
|
|
33
|
+
// 如果没有找到结果
|
|
34
|
+
if (!found || results.length === 0) {
|
|
35
|
+
return (
|
|
36
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
37
|
+
<p className="text-sm text-gray-500">未找到相关问题</p>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 切换展开/折叠
|
|
43
|
+
const toggleExpand = (id: string) => {
|
|
44
|
+
setExpandedIds(prev => {
|
|
45
|
+
const newSet = new Set(prev)
|
|
46
|
+
if (newSet.has(id)) {
|
|
47
|
+
newSet.delete(id)
|
|
48
|
+
} else {
|
|
49
|
+
newSet.add(id)
|
|
50
|
+
}
|
|
51
|
+
return newSet
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 处理相关问题点击
|
|
56
|
+
const handleRelatedQuestionClick = (question: string) => {
|
|
57
|
+
onQuestionClick?.(question)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="space-y-2">
|
|
62
|
+
{/* FAQ 列表 */}
|
|
63
|
+
<div className="space-y-2">
|
|
64
|
+
{results.map(item => {
|
|
65
|
+
const isExpanded = expandedIds.has(item.id)
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div key={item.id} className="overflow-hidden rounded-2xl bg-[#F5F6F7] transition-all">
|
|
69
|
+
{/* 问题标题 - 可点击展开/折叠 */}
|
|
70
|
+
<button
|
|
71
|
+
onClick={() => toggleExpand(item.id)}
|
|
72
|
+
className="flex w-full items-center justify-between gap-3 p-4 text-left transition-colors "
|
|
73
|
+
>
|
|
74
|
+
<div className="flex-1">
|
|
75
|
+
<div className="flex items-center gap-2">
|
|
76
|
+
<span className="text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]">
|
|
77
|
+
{item.question}
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* 展开/折叠图标 */}
|
|
83
|
+
<svg
|
|
84
|
+
className={`size-5 shrink-0 text-[#080A0F] transition-transform ${isExpanded ? 'rotate-180' : ''}`}
|
|
85
|
+
fill="none"
|
|
86
|
+
viewBox="0 0 24 24"
|
|
87
|
+
stroke="currentColor"
|
|
88
|
+
>
|
|
89
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
90
|
+
</svg>
|
|
91
|
+
</button>
|
|
92
|
+
|
|
93
|
+
{/* 答案内容 - 展开时显示 */}
|
|
94
|
+
{isExpanded && (
|
|
95
|
+
<div className=" bg-[#F5F6F7] px-4 py-3 pt-0">
|
|
96
|
+
<div
|
|
97
|
+
className="prose prose-sm max-w-none border-t border-gray-100 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#4A4C56]"
|
|
98
|
+
style={{ paddingTop: '12px' }}
|
|
99
|
+
>
|
|
100
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{item.answer}</ReactMarkdown>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* 相关问题 */}
|
|
104
|
+
{item.relatedQuestions && item.relatedQuestions.length > 0 && (
|
|
105
|
+
<div className="mt-4 border-t border-gray-200 pt-3">
|
|
106
|
+
<p className="mb-2 text-xs font-medium text-gray-500">You may also want to know:</p>
|
|
107
|
+
<div className="space-y-1">
|
|
108
|
+
{item.relatedQuestions.map((q, idx) => (
|
|
109
|
+
<button
|
|
110
|
+
key={idx}
|
|
111
|
+
onClick={() => handleRelatedQuestionClick(q)}
|
|
112
|
+
className="block w-full text-left text-xs text-blue-600 hover:text-blue-700 hover:underline"
|
|
113
|
+
>
|
|
114
|
+
{q}
|
|
115
|
+
</button>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
})}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 政策说明渲染器
|
|
3
|
+
* 显示退货、保修等政策信息
|
|
4
|
+
* 基于 specs/livechat-widget/data-model.md 的政策数据模型
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState } from 'react'
|
|
8
|
+
import ReactMarkdown from 'react-markdown'
|
|
9
|
+
import remarkGfm from 'remark-gfm'
|
|
10
|
+
import type { MessageRenderer, PolicyContent } from '../../types'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 政策说明渲染器
|
|
14
|
+
*
|
|
15
|
+
* 功能:
|
|
16
|
+
* - 显示政策标题和内容
|
|
17
|
+
* - 支持 Markdown 格式
|
|
18
|
+
* - 可折叠/展开(内容较长时)
|
|
19
|
+
*
|
|
20
|
+
* 政策类型:
|
|
21
|
+
* - 退货政策
|
|
22
|
+
* - 保修政策
|
|
23
|
+
* - 运费政策
|
|
24
|
+
* - 隐私政策
|
|
25
|
+
*
|
|
26
|
+
* 布局:
|
|
27
|
+
* ```
|
|
28
|
+
* ┌─────────────────────────┐
|
|
29
|
+
* │ 📋 政策标题 │
|
|
30
|
+
* ├─────────────────────────┤
|
|
31
|
+
* │ 政策内容... │
|
|
32
|
+
* │ (支持 Markdown) │
|
|
33
|
+
* │ │
|
|
34
|
+
* │ [展开/收起] │
|
|
35
|
+
* └─────────────────────────┘
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const content: PolicyContent = {
|
|
41
|
+
* type: 'policy',
|
|
42
|
+
* data: {
|
|
43
|
+
* title: '退货政策',
|
|
44
|
+
* content: '我们提供 30 天无理由退货...'
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* <PolicyBlock.render(content, false, false) />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export const PolicyBlock: MessageRenderer = {
|
|
51
|
+
render: (content, isUser, isSystem) => {
|
|
52
|
+
const policyContent = content as PolicyContent
|
|
53
|
+
const { title, content: policyText } = policyContent.data
|
|
54
|
+
|
|
55
|
+
if (!title || !policyText) {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 判断内容是否较长(超过 200 字符)
|
|
60
|
+
const isLongContent = policyText.length > 200
|
|
61
|
+
|
|
62
|
+
return <PolicyCard title={title} content={policyText} isLong={isLongContent} />
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 政策卡片组件(支持折叠)
|
|
68
|
+
*/
|
|
69
|
+
const PolicyCard: React.FC<{
|
|
70
|
+
title: string
|
|
71
|
+
content: string
|
|
72
|
+
isLong: boolean
|
|
73
|
+
}> = ({ title, content, isLong }) => {
|
|
74
|
+
const [isExpanded, setIsExpanded] = useState(!isLong)
|
|
75
|
+
|
|
76
|
+
const toggleExpand = () => {
|
|
77
|
+
setIsExpanded(prev => !prev)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="overflow-hidden rounded-lg border border-blue-200 bg-blue-50">
|
|
82
|
+
{/* 标题 */}
|
|
83
|
+
<div className="flex items-center gap-2 border-b border-blue-200 bg-blue-100 px-3 py-2">
|
|
84
|
+
<svg
|
|
85
|
+
width="18"
|
|
86
|
+
height="18"
|
|
87
|
+
viewBox="0 0 24 24"
|
|
88
|
+
fill="none"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
strokeWidth="2"
|
|
91
|
+
strokeLinecap="round"
|
|
92
|
+
strokeLinejoin="round"
|
|
93
|
+
className="shrink-0 text-blue-700"
|
|
94
|
+
>
|
|
95
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
96
|
+
<polyline points="14 2 14 8 20 8" />
|
|
97
|
+
<line x1="16" y1="13" x2="8" y2="13" />
|
|
98
|
+
<line x1="16" y1="17" x2="8" y2="17" />
|
|
99
|
+
<polyline points="10 9 9 9 8 9" />
|
|
100
|
+
</svg>
|
|
101
|
+
<h3 className="text-sm font-semibold text-blue-900">{title}</h3>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* 内容 */}
|
|
105
|
+
<div className="px-3 py-2">
|
|
106
|
+
<div
|
|
107
|
+
className={`
|
|
108
|
+
text-sm leading-relaxed text-blue-900
|
|
109
|
+
${!isExpanded && isLong ? 'line-clamp-3' : ''}
|
|
110
|
+
`}
|
|
111
|
+
>
|
|
112
|
+
<ReactMarkdown
|
|
113
|
+
remarkPlugins={[remarkGfm]}
|
|
114
|
+
components={{
|
|
115
|
+
p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
|
|
116
|
+
ul: ({ node, ...props }) => <ul {...props} className="mb-2 ml-4 list-disc" />,
|
|
117
|
+
ol: ({ node, ...props }) => <ol {...props} className="mb-2 ml-4 list-decimal" />,
|
|
118
|
+
li: ({ node, ...props }) => <li {...props} className="mb-1" />,
|
|
119
|
+
strong: ({ node, ...props }) => <strong {...props} className="font-bold" />,
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{content}
|
|
123
|
+
</ReactMarkdown>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* 展开/收起按钮(仅长内容显示) */}
|
|
127
|
+
{isLong && (
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={toggleExpand}
|
|
131
|
+
className="mt-2 flex items-center gap-1 text-xs font-medium text-blue-700 hover:text-blue-800"
|
|
132
|
+
>
|
|
133
|
+
<span>{isExpanded ? '收起' : '展开'}</span>
|
|
134
|
+
<svg
|
|
135
|
+
width="14"
|
|
136
|
+
height="14"
|
|
137
|
+
viewBox="0 0 24 24"
|
|
138
|
+
fill="none"
|
|
139
|
+
stroke="currentColor"
|
|
140
|
+
strokeWidth="2"
|
|
141
|
+
strokeLinecap="round"
|
|
142
|
+
strokeLinejoin="round"
|
|
143
|
+
className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`}
|
|
144
|
+
>
|
|
145
|
+
<polyline points="6 9 12 15 18 9" />
|
|
146
|
+
</svg>
|
|
147
|
+
</button>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
)
|
|
152
|
+
}
|