@doubao-dev/template 0.0.38-canary-22dd4cbf-20260720020201
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/README.md +282 -0
- package/assets/ask-human-card.png +0 -0
- package/assets/checkout-card.png +0 -0
- package/assets/content-card.png +0 -0
- package/assets/price-action-card.png +0 -0
- package/assets/ticket-order-card.png +0 -0
- package/assets/transit-card.png +0 -0
- package/dist/ask-human-card/index.d.ts +44 -0
- package/dist/ask-human-card/index.js +79 -0
- package/dist/ask-human-card/styles.css +66 -0
- package/dist/checkout-card/index.d.ts +55 -0
- package/dist/checkout-card/index.js +40 -0
- package/dist/checkout-card/styles.css +34 -0
- package/dist/components/chevron-right-icon/index.d.ts +6 -0
- package/dist/components/chevron-right-icon/index.js +13 -0
- package/dist/components/extra-action/index.d.ts +50 -0
- package/dist/components/extra-action/index.js +31 -0
- package/dist/components/extra-action/styles.css +10 -0
- package/dist/components/footer/index.d.ts +27 -0
- package/dist/components/footer/index.js +15 -0
- package/dist/components/footer/styles.css +27 -0
- package/dist/components/header/index.d.ts +13 -0
- package/dist/components/header/index.js +27 -0
- package/dist/components/header/styles.css +62 -0
- package/dist/components/info-section/index.d.ts +47 -0
- package/dist/components/info-section/index.js +93 -0
- package/dist/components/info-section/styles.css +151 -0
- package/dist/components/product-summary/index.d.ts +26 -0
- package/dist/components/product-summary/index.js +55 -0
- package/dist/components/product-summary/styles.css +121 -0
- package/dist/components/route-section/index.d.ts +23 -0
- package/dist/components/route-section/index.js +39 -0
- package/dist/components/route-section/styles.css +54 -0
- package/dist/components/transport-summary/index.d.ts +31 -0
- package/dist/components/transport-summary/index.js +109 -0
- package/dist/components/transport-summary/styles.css +214 -0
- package/dist/content-card/index.d.ts +45 -0
- package/dist/content-card/index.js +72 -0
- package/dist/content-card/styles.css +160 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +6 -0
- package/dist/price-action-card/index.d.ts +52 -0
- package/dist/price-action-card/index.js +64 -0
- package/dist/price-action-card/styles.css +161 -0
- package/dist/ticket-order-card/index.d.ts +70 -0
- package/dist/ticket-order-card/index.js +27 -0
- package/dist/ticket-order-card/styles.css +18 -0
- package/dist/transit-card/index.d.ts +52 -0
- package/dist/transit-card/index.js +29 -0
- package/dist/transit-card/styles.css +53 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +0 -0
- package/dist/utils/app-info.d.ts +6 -0
- package/dist/utils/app-info.js +20 -0
- package/dist/utils/icons.d.ts +2 -0
- package/dist/utils/icons.js +28 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# @doubao-dev/template
|
|
2
|
+
|
|
3
|
+
Doubao Apps Widget 模板库,用于快速开发卡片类 widget。模板把标题栏、内容区、信息区和操作区组合成稳定的卡片结构,开发者在 `defineWidget` 里选择模板并传入业务数据即可。
|
|
4
|
+
|
|
5
|
+
推荐一个 widget 直接返回一张模板卡片,不额外包裹无意义的 `view` / `scroll-view`:
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { defineWidget } from '@doubao-dev/framework';
|
|
9
|
+
import { ContentCard, type ContentCardItem } from '@doubao-dev/template';
|
|
10
|
+
|
|
11
|
+
const items: ContentCardItem[] = [
|
|
12
|
+
{
|
|
13
|
+
key: 'a',
|
|
14
|
+
title: '主标题',
|
|
15
|
+
subtitle: '4199 元 | 商品描述',
|
|
16
|
+
thumbnail: <image src="https://example.com/product.png" mode="aspectFill" />
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default defineWidget({
|
|
21
|
+
render() {
|
|
22
|
+
return (
|
|
23
|
+
<ContentCard
|
|
24
|
+
items={items}
|
|
25
|
+
header={{
|
|
26
|
+
actionText: '更多',
|
|
27
|
+
onActionClick: () => console.log('more')
|
|
28
|
+
}}
|
|
29
|
+
footer={{
|
|
30
|
+
secondaryActionButton: {
|
|
31
|
+
text: '查看更多',
|
|
32
|
+
onClick: () => console.log('view more')
|
|
33
|
+
},
|
|
34
|
+
primaryActionButton: {
|
|
35
|
+
text: '立即下单',
|
|
36
|
+
onClick: () => console.log('submit')
|
|
37
|
+
}
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 通用约定
|
|
46
|
+
|
|
47
|
+
- `header` 配置标题栏右侧操作,类型为 `TemplateCardHeaderProps`,包含 `actionText` / `showAction` / `onActionClick`。
|
|
48
|
+
- `footer` 配置底部操作区,类型为 `TemplateCardFooterProps`,包含 `primaryActionButton` / `secondaryActionButton`。只传一个按钮时占据整行。
|
|
49
|
+
- `header`、`footer` 和内容区相互独立。footer 不会影响 `items` 展示数量,内容区也不负责渲染 header/footer。
|
|
50
|
+
- `children` 只接管内容区;模板仍保留卡片外壳、header 和 footer。
|
|
51
|
+
- `className` / `style` / `onClick` 作用在卡片根节点。
|
|
52
|
+
- 样式使用组件根 class 内声明的 `--doubao-*` CSS 变量,不把模板变量挂到 `:root`。
|
|
53
|
+
|
|
54
|
+
## ContentCard
|
|
55
|
+
|
|
56
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.0.38-canary-22dd4cbf-20260720020201/assets/content-card.png" alt="ContentCard 示例" width="400" />
|
|
57
|
+
|
|
58
|
+
通用列表卡,由标题栏、列表内容和底部操作区组成,适合商品、服务、内容入口等可重复条目。
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { ContentCard } from '@doubao-dev/template';
|
|
62
|
+
|
|
63
|
+
<ContentCard
|
|
64
|
+
items={[
|
|
65
|
+
{
|
|
66
|
+
title: '蓝牙降噪耳机 Pro',
|
|
67
|
+
subtitle: '799 元 | 主动降噪 | 现货',
|
|
68
|
+
thumbnail: <image src="https://example.com/product.png" mode="aspectFill" />,
|
|
69
|
+
action: {
|
|
70
|
+
type: 'button',
|
|
71
|
+
text: '下单',
|
|
72
|
+
onClick: () => console.log('order')
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
title: '便携冲牙器',
|
|
77
|
+
subtitle: '189 元 | 三档水压 | 今日达'
|
|
78
|
+
}
|
|
79
|
+
]}
|
|
80
|
+
footer={{
|
|
81
|
+
primaryActionButton: {
|
|
82
|
+
text: '立即下单'
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
说明:
|
|
89
|
+
|
|
90
|
+
- `title` / `subtitle` 支持字符串、数字或自定义节点。字符串和数字使用模板默认单行样式。
|
|
91
|
+
- `thumbnail` 是列表项左侧缩略视觉内容,可传图片、图标或自定义节点。
|
|
92
|
+
- `action` 是列表项右侧操作区,支持 `{ type: 'button' }`、`{ type: 'switch' }`、`{ type: 'playback' }` 或自定义节点。
|
|
93
|
+
- `children` 会替换列表内容区,不包含 header/footer。
|
|
94
|
+
|
|
95
|
+
## PriceActionCard
|
|
96
|
+
|
|
97
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.0.38-canary-22dd4cbf-20260720020201/assets/price-action-card.png" alt="PriceActionCard 示例" width="400" />
|
|
98
|
+
|
|
99
|
+
价格操作卡,由多条"价格 + 信息 + 右侧按钮"组成,适合报价、权益、服务方案等结果列表。
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { PriceActionCard } from '@doubao-dev/template';
|
|
103
|
+
|
|
104
|
+
<PriceActionCard
|
|
105
|
+
items={[
|
|
106
|
+
{
|
|
107
|
+
price: '¥128',
|
|
108
|
+
badgeText: '推荐',
|
|
109
|
+
infoRows: [{ text: '专车接送' }, { text: '免费等待 30 分钟' }],
|
|
110
|
+
actionText: '预订',
|
|
111
|
+
onActionClick: () => console.log('select')
|
|
112
|
+
}
|
|
113
|
+
]}
|
|
114
|
+
footer={{
|
|
115
|
+
primaryActionButton: {
|
|
116
|
+
text: '查看全部报价'
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
说明:
|
|
123
|
+
|
|
124
|
+
- `items` 用于配置价格列表,每条最多展示前 2 行 `infoRows`。
|
|
125
|
+
- `actionText` 为空时不展示列表项右侧按钮。
|
|
126
|
+
- `footer` 使用通用底部操作区,不再使用旧的 `footerActionText`。
|
|
127
|
+
|
|
128
|
+
## CheckoutCard
|
|
129
|
+
|
|
130
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.0.38-canary-22dd4cbf-20260720020201/assets/checkout-card.png" alt="CheckoutCard 示例" width="400" />
|
|
131
|
+
|
|
132
|
+
通用提单卡,由商品摘要、提单信息、费用汇总和底部操作区组成,适合下单确认、支付确认等场景。
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { CheckoutCard } from '@doubao-dev/template';
|
|
136
|
+
|
|
137
|
+
<CheckoutCard
|
|
138
|
+
productItems={[
|
|
139
|
+
{
|
|
140
|
+
title: '双人下午茶套餐',
|
|
141
|
+
spec: '含 2 杯饮品 + 2 款甜点',
|
|
142
|
+
price: '¥68',
|
|
143
|
+
quantity: '1'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
title: '榛果拿铁',
|
|
147
|
+
spec: '少糖 | 热饮',
|
|
148
|
+
price: '¥28',
|
|
149
|
+
quantity: '2',
|
|
150
|
+
showChevron: false
|
|
151
|
+
}
|
|
152
|
+
]}
|
|
153
|
+
infoItems={[
|
|
154
|
+
{ label: '提单门店', value: '朝阳大悦城店' },
|
|
155
|
+
{ label: '预计时间', value: '今天 15:30', valueExtra: '请到店自取' }
|
|
156
|
+
]}
|
|
157
|
+
feeItems={[
|
|
158
|
+
{ label: '包装费', value: '¥2' },
|
|
159
|
+
{ label: '服务费', value: '¥0' }
|
|
160
|
+
]}
|
|
161
|
+
discountText="共优惠 ¥12"
|
|
162
|
+
totalPrice="¥126"
|
|
163
|
+
footer={{
|
|
164
|
+
secondaryActionButton: { text: '修改' },
|
|
165
|
+
primaryActionButton: { text: '提交订单' }
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
说明:
|
|
171
|
+
|
|
172
|
+
- `productItems` 支持单个或多个商品。单项字段复用商品摘要能力,包括 `title`、`spec`、`imageSrc`、`image`、`pricePrefix`、`price`、`quantity`、`showChevron`、`onClick`。
|
|
173
|
+
- `infoItems` 配置提单信息列表。
|
|
174
|
+
- `feeItems`、`discountText`、`totalLabel`、`totalPrice` 配置费用汇总。
|
|
175
|
+
- 底部按钮使用 `footer.primaryActionButton` / `footer.secondaryActionButton`。
|
|
176
|
+
|
|
177
|
+
## TicketOrderCard
|
|
178
|
+
|
|
179
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.0.38-canary-22dd4cbf-20260720020201/assets/ticket-checkout-card.png" alt="TicketOrderCard 示例" width="400" />
|
|
180
|
+
|
|
181
|
+
票务提单卡,由航程信息、订单信息、费用汇总和底部操作区组成,适合机票、火车票等出行确认场景。
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { TicketOrderCard } from '@doubao-dev/template';
|
|
185
|
+
|
|
186
|
+
<TicketOrderCard
|
|
187
|
+
routeItems={[
|
|
188
|
+
{
|
|
189
|
+
title: '北京 - 上海',
|
|
190
|
+
description: '7月1日 周三 08:00-10:10 MU5101 经济舱'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: '上海 - 北京',
|
|
194
|
+
description: '7月3日 周五 19:20-21:35 MU5162 经济舱'
|
|
195
|
+
}
|
|
196
|
+
]}
|
|
197
|
+
infoItems={[
|
|
198
|
+
{ label: '乘机人', value: '张三' },
|
|
199
|
+
{ label: '联系电话', value: '138****8888' }
|
|
200
|
+
]}
|
|
201
|
+
feeItems={[
|
|
202
|
+
{ label: '机票', value: '¥1064' },
|
|
203
|
+
{ label: '机建燃油', value: '¥440' }
|
|
204
|
+
]}
|
|
205
|
+
totalLabel="合计"
|
|
206
|
+
totalPrice="¥1504"
|
|
207
|
+
footer={{
|
|
208
|
+
secondaryActionButton: { text: '修改' },
|
|
209
|
+
primaryActionButton: { text: '提交' }
|
|
210
|
+
}}
|
|
211
|
+
/>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
说明:
|
|
215
|
+
|
|
216
|
+
- `routeItems` 配置航程列表,单项可通过 `showChevron={false}` 隐藏标题右侧箭头。
|
|
217
|
+
- `totalContent` 可以接管合计区域,优先级高于 `totalLabel` / `totalPrice`。
|
|
218
|
+
- 底部按钮使用通用 `footer`。
|
|
219
|
+
|
|
220
|
+
## TransitCard
|
|
221
|
+
|
|
222
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.0.38-canary-22dd4cbf-20260720020201/assets/transit-card.png" alt="TransitCard 示例" width="400" />
|
|
223
|
+
|
|
224
|
+
交通票务列表卡,由多条出发、中转、到达和价格信息组成,适合展示机票、火车票、大巴等推荐列表。
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { TransitCard } from '@doubao-dev/template';
|
|
228
|
+
|
|
229
|
+
<TransitCard
|
|
230
|
+
items={[
|
|
231
|
+
{
|
|
232
|
+
title: '北京南 - 上海虹桥',
|
|
233
|
+
departureMain: '08:00',
|
|
234
|
+
departureSub: '北京南',
|
|
235
|
+
transferTop: '直达',
|
|
236
|
+
arrivalMain: '12:32',
|
|
237
|
+
arrivalDayOffset: '+1',
|
|
238
|
+
arrivalSub: '上海虹桥',
|
|
239
|
+
price: '¥553'
|
|
240
|
+
}
|
|
241
|
+
]}
|
|
242
|
+
footer={{
|
|
243
|
+
primaryActionButton: {
|
|
244
|
+
text: '查看更多车次'
|
|
245
|
+
}
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
说明:
|
|
251
|
+
|
|
252
|
+
- `items` 会全量展示,模板不做"最多 4 条"的截断。
|
|
253
|
+
- `title`、`transfer`、`price` 接收 `ReactNode`,适合需要自定义整块区域的场景。
|
|
254
|
+
- `departureMain`、`departureSub`、`transferTop`、`transferBottom`、`arrivalMain`、`arrivalDayOffset`、`arrivalSub` 接收字符串或数字。
|
|
255
|
+
|
|
256
|
+
## AskHumanCard
|
|
257
|
+
|
|
258
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.0.38-canary-22dd4cbf-20260720020201/assets/ask-human-card.png" alt="AskHumanCard 示例" width="400" />
|
|
259
|
+
|
|
260
|
+
人工确认选项卡,由一组选项组成,适合让用户在多个候选项中确认选择。
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { AskHumanCard } from '@doubao-dev/template';
|
|
264
|
+
|
|
265
|
+
<AskHumanCard
|
|
266
|
+
items={[
|
|
267
|
+
{ text: '朝阳大悦城店 | 0.8km' },
|
|
268
|
+
{ text: '三里屯太古里店 | 2.1km' },
|
|
269
|
+
{ text: '国贸商城店 | 3.4km' },
|
|
270
|
+
{
|
|
271
|
+
text: '都不是',
|
|
272
|
+
showArrow: 'right',
|
|
273
|
+
onClick: () => console.log('skip')
|
|
274
|
+
}
|
|
275
|
+
]}
|
|
276
|
+
/>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
说明:
|
|
280
|
+
|
|
281
|
+
- `items` 配置选项列表,每项可用 `text` 或 `children`。
|
|
282
|
+
- `items[].showArrow` 支持 `'left'`、`'right'`、`'none'`,默认不展示;跳过、固定跳转等入口也通过 `items` 表达。
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CSSProperties, TouchEvent } from '@lynx-js/types';
|
|
2
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
3
|
+
import type { TemplateKey, TemplateText } from '../types.js';
|
|
4
|
+
import './styles.scss';
|
|
5
|
+
/** 卡片行为语义。 */
|
|
6
|
+
export type AskHumanCardVariant = 'suggestion' | 'jump';
|
|
7
|
+
/** 选项箭头展示位置。 */
|
|
8
|
+
export type AskHumanCardArrowPosition = 'left' | 'right' | 'none';
|
|
9
|
+
export interface AskHumanCardProps {
|
|
10
|
+
/** 选项列表。 */
|
|
11
|
+
items?: AskHumanCardItem[];
|
|
12
|
+
/**
|
|
13
|
+
* 选项的行为语义,决定排列方式。
|
|
14
|
+
* - `'suggestion'`(默认):点击向模型发消息;纵向、居左。
|
|
15
|
+
* - `'jump'`:固定跳转;拉通横排(1 项占满、2 项平分,≥3 项回退纵向)。
|
|
16
|
+
*/
|
|
17
|
+
variant?: AskHumanCardVariant;
|
|
18
|
+
/** 卡片根节点自定义类名。 */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** 卡片根节点自定义样式。 */
|
|
21
|
+
style?: CSSProperties;
|
|
22
|
+
/** 点击卡片根节点时触发。 */
|
|
23
|
+
onClick?: (event: TouchEvent) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface AskHumanCardItem {
|
|
26
|
+
/** 选项唯一标识;不传时使用数组下标。 */
|
|
27
|
+
key?: TemplateKey;
|
|
28
|
+
/** 选项文案。 */
|
|
29
|
+
text?: TemplateText;
|
|
30
|
+
/** 选项自定义内容;优先级高于 text。 */
|
|
31
|
+
children?: ReactNode;
|
|
32
|
+
/** 是否显示加载状态;为 true 时选项内展示 loading 动画并禁用点击。 */
|
|
33
|
+
loading?: boolean;
|
|
34
|
+
/** 箭头展示位置;默认 `'none'` 不展示。 */
|
|
35
|
+
showArrow?: AskHumanCardArrowPosition;
|
|
36
|
+
/** 选项根节点自定义类名。 */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** 选项根节点自定义样式。 */
|
|
39
|
+
style?: CSSProperties;
|
|
40
|
+
/** 点击选项时触发。 */
|
|
41
|
+
onClick?: (event?: TouchEvent) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare function AskHumanCard(props: AskHumanCardProps): ReactElement;
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Button, useTheme, useThemeClassName } from "@byted-doubao-apps/components";
|
|
2
|
+
import { createEvent, reportEvent } from "@byted-doubao-apps/framework/api";
|
|
3
|
+
import { clsx } from "clsx";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { getAppInfo } from "../utils/app-info.js";
|
|
6
|
+
import "./styles.css";
|
|
7
|
+
const onShow = createEvent('onShow');
|
|
8
|
+
function reportAskHumanCardEvent(eventName) {
|
|
9
|
+
const appInfo = getAppInfo();
|
|
10
|
+
reportEvent({
|
|
11
|
+
eventName,
|
|
12
|
+
data: {
|
|
13
|
+
current_page: 'chat',
|
|
14
|
+
app_name: appInfo.appName
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function getAskHumanCardArrowIconContent(color) {
|
|
19
|
+
return `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
20
|
+
<path d="M10.5652 3.96967C10.8581 3.67678 11.3328 3.67678 11.6257 3.96967L15.594 7.93794C16.1778 8.52186 16.183 9.4729 15.5955 10.0605L11.6257 14.0302C11.3329 14.323 10.8581 14.323 10.5652 14.0302C10.2723 13.7374 10.2724 13.2626 10.5652 12.9697L13.7849 9.74996H2.47046C2.05631 9.74996 1.72056 9.41408 1.72046 8.99996C1.72046 8.58574 2.05625 8.24996 2.47046 8.24996H13.7849L10.5652 5.03022C10.2723 4.73736 10.2724 4.26257 10.5652 3.96967Z" fill="${color}"/>
|
|
21
|
+
</svg>`;
|
|
22
|
+
}
|
|
23
|
+
function isAskHumanCardTextEmpty(content) {
|
|
24
|
+
return null == content || '' === content;
|
|
25
|
+
}
|
|
26
|
+
function isAskHumanCardNodeEmpty(content) {
|
|
27
|
+
return null == content || 'boolean' == typeof content || '' === content;
|
|
28
|
+
}
|
|
29
|
+
function renderAskHumanCardItem({ item, index, optionBg, arrowColor }) {
|
|
30
|
+
const isLoading = true === item.loading;
|
|
31
|
+
const hasCustomContent = !isAskHumanCardNodeEmpty(item.children);
|
|
32
|
+
const arrowPosition = 'left' === item.showArrow || 'right' === item.showArrow ? item.showArrow : void 0;
|
|
33
|
+
const arrowNode = arrowPosition ? <svg className="doubao-ask-human-card__option-icon" content={getAskHumanCardArrowIconContent(arrowColor)}/> : null;
|
|
34
|
+
if (!isLoading && !hasCustomContent && isAskHumanCardTextEmpty(item.text)) return null;
|
|
35
|
+
return <Button key={item.key ?? index} className={clsx('doubao-ask-human-card__option', {
|
|
36
|
+
'doubao-ask-human-card__option--with-arrow': null != arrowPosition
|
|
37
|
+
}, item.className)} style={{
|
|
38
|
+
backgroundColor: optionBg,
|
|
39
|
+
...item.style
|
|
40
|
+
}} type="default" loading={isLoading} onClick={(event)=>{
|
|
41
|
+
reportAskHumanCardEvent('doubao_apps_taxi_follow_up_click');
|
|
42
|
+
item.onClick?.(event);
|
|
43
|
+
}}>
|
|
44
|
+
{'left' === arrowPosition && arrowNode}
|
|
45
|
+
{hasCustomContent ? item.children : <text className="doubao-ask-human-card__option-text" text-maxline="1">
|
|
46
|
+
{item.text}
|
|
47
|
+
</text>}
|
|
48
|
+
{'right' === arrowPosition && arrowNode}
|
|
49
|
+
</Button>;
|
|
50
|
+
}
|
|
51
|
+
function AskHumanCard(props) {
|
|
52
|
+
const { items = [], variant = 'suggestion', className, style, onClick } = props;
|
|
53
|
+
const theme = useTheme();
|
|
54
|
+
const arrowColor = 'dark' === theme ? '#ffffff' : '#000000';
|
|
55
|
+
const optionBg = 'dark' === theme ? '#000000' : '#ffffff';
|
|
56
|
+
const optionNodes = items.map((item, index)=>renderAskHumanCardItem({
|
|
57
|
+
item,
|
|
58
|
+
index,
|
|
59
|
+
optionBg,
|
|
60
|
+
arrowColor
|
|
61
|
+
})).filter((node)=>null != node);
|
|
62
|
+
const isRow = 'jump' === variant && optionNodes.length > 0 && optionNodes.length <= 2;
|
|
63
|
+
useEffect(()=>{
|
|
64
|
+
const unregisterShow = onShow(()=>{
|
|
65
|
+
reportAskHumanCardEvent('doubao_apps_taxi_follow_up_show');
|
|
66
|
+
});
|
|
67
|
+
return ()=>{
|
|
68
|
+
unregisterShow?.();
|
|
69
|
+
};
|
|
70
|
+
}, []);
|
|
71
|
+
return <view className={useThemeClassName(clsx('doubao-ask-human-card', className))} style={style} bindtap={onClick}>
|
|
72
|
+
{optionNodes.length > 0 && <view className={clsx('doubao-ask-human-card__options', {
|
|
73
|
+
'doubao-ask-human-card__options--row': isRow
|
|
74
|
+
})}>
|
|
75
|
+
{optionNodes}
|
|
76
|
+
</view>}
|
|
77
|
+
</view>;
|
|
78
|
+
}
|
|
79
|
+
export { AskHumanCard };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.doubao-ask-human-card {
|
|
2
|
+
--doubao-ask-human-card-gap: 8px;
|
|
3
|
+
--doubao-ask-human-card-option-height: 44px;
|
|
4
|
+
--doubao-ask-human-card-option-radius: 8px;
|
|
5
|
+
align-items: flex-start;
|
|
6
|
+
gap: var(--doubao-ask-human-card-gap);
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
background-color: var(--bg-base-2-overlay);
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
width: 100%;
|
|
11
|
+
display: flex;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.doubao-ask-human-card__options {
|
|
15
|
+
align-items: flex-start;
|
|
16
|
+
gap: var(--doubao-ask-human-card-gap);
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
width: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.doubao-ask-human-card__options--row {
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
align-items: stretch;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.doubao-ask-human-card__options--row > .doubao-button {
|
|
28
|
+
flex: 1 1 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.doubao-ask-human-card .doubao-button {
|
|
32
|
+
min-width: 0;
|
|
33
|
+
max-width: 100%;
|
|
34
|
+
height: var(--doubao-ask-human-card-option-height);
|
|
35
|
+
--doubao-button-default-height: var(--doubao-ask-human-card-option-height);
|
|
36
|
+
--doubao-button-border-radius: var(--doubao-ask-human-card-option-radius);
|
|
37
|
+
--doubao-button-default-border-radius: var(--doubao-ask-human-card-option-radius);
|
|
38
|
+
--doubao-button-default-padding-x: 12px;
|
|
39
|
+
--doubao-button-default-padding-y: 11px;
|
|
40
|
+
--doubao-button-default-bg: var(--bg-base-1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.doubao-ask-human-card__option--with-arrow {
|
|
44
|
+
gap: 4px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.doubao-ask-human-card__option-text {
|
|
48
|
+
min-width: 0;
|
|
49
|
+
color: var(--neutral-100);
|
|
50
|
+
text-align: center;
|
|
51
|
+
text-overflow: ellipsis;
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
font-family: PingFang SC, sans-serif;
|
|
54
|
+
font-size: 16px;
|
|
55
|
+
font-style: normal;
|
|
56
|
+
font-weight: 500;
|
|
57
|
+
line-height: 22px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.doubao-ask-human-card__option-icon {
|
|
62
|
+
flex-shrink: 0;
|
|
63
|
+
width: 18px;
|
|
64
|
+
height: 18px;
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CSSProperties, TouchEvent } from '@lynx-js/types';
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
3
|
+
import { type TemplateCardFooterProps } from '../components/footer/index.js';
|
|
4
|
+
import { type TemplateCardHeaderProps } from '../components/header/index.js';
|
|
5
|
+
import { type TemplateProductSummaryProps } from '../components/product-summary/index.js';
|
|
6
|
+
import type { TemplateKey, TemplateText } from '../types.js';
|
|
7
|
+
import './styles.scss';
|
|
8
|
+
export interface CheckoutCardProps {
|
|
9
|
+
/** 标题栏配置。 */
|
|
10
|
+
header?: TemplateCardHeaderProps;
|
|
11
|
+
/** 商品列表。 */
|
|
12
|
+
productItems?: CheckoutCardProductItem[];
|
|
13
|
+
/** 提单信息列表。 */
|
|
14
|
+
infoItems?: CheckoutCardInfoItem[];
|
|
15
|
+
/** 费用明细列表。 */
|
|
16
|
+
feeItems?: CheckoutCardFeeItem[];
|
|
17
|
+
/** 优惠信息文案;传空字符串时不展示。 */
|
|
18
|
+
discountText?: string;
|
|
19
|
+
/** 合计价格前的标签文案。 */
|
|
20
|
+
totalLabel?: string;
|
|
21
|
+
/** 合计价格。 */
|
|
22
|
+
totalPrice?: TemplateText;
|
|
23
|
+
/** 底部操作区配置。 */
|
|
24
|
+
footer?: TemplateCardFooterProps;
|
|
25
|
+
/** 卡片根节点自定义类名。 */
|
|
26
|
+
className?: string;
|
|
27
|
+
/** 卡片根节点自定义样式。 */
|
|
28
|
+
style?: CSSProperties;
|
|
29
|
+
/** 点击卡片根节点时触发。 */
|
|
30
|
+
onClick?: (event: TouchEvent) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface CheckoutCardProductItem extends TemplateProductSummaryProps {
|
|
33
|
+
/** 商品唯一标识;不传时使用数组下标。 */
|
|
34
|
+
key?: TemplateKey;
|
|
35
|
+
}
|
|
36
|
+
export interface CheckoutCardInfoItem {
|
|
37
|
+
/** 信息项唯一标识;不传时使用数组下标。 */
|
|
38
|
+
key?: TemplateKey;
|
|
39
|
+
/** 信息项标签。 */
|
|
40
|
+
label: string;
|
|
41
|
+
/** 信息项主内容。 */
|
|
42
|
+
value: ReactNode;
|
|
43
|
+
/** 信息项补充内容。 */
|
|
44
|
+
valueExtra?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
export interface CheckoutCardFeeItem {
|
|
47
|
+
/** 费用项唯一标识;不传时使用数组下标。 */
|
|
48
|
+
key?: TemplateKey;
|
|
49
|
+
/** 费用项标签。 */
|
|
50
|
+
label: string;
|
|
51
|
+
/** 费用项金额或数值。 */
|
|
52
|
+
value: TemplateText;
|
|
53
|
+
}
|
|
54
|
+
export declare function CheckoutCard(props: CheckoutCardProps): ReactElement;
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useThemeClassName } from "@byted-doubao-apps/components";
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { TemplateCardFooter } from "../components/footer/index.js";
|
|
4
|
+
import { TemplateCardHeader } from "../components/header/index.js";
|
|
5
|
+
import { TemplateInfoSection } from "../components/info-section/index.js";
|
|
6
|
+
import { TemplateProductSummary } from "../components/product-summary/index.js";
|
|
7
|
+
import "./styles.css";
|
|
8
|
+
function isCheckoutCardTextEmpty(content) {
|
|
9
|
+
return null == content || '' === content;
|
|
10
|
+
}
|
|
11
|
+
function isCheckoutCardNodeEmpty(content) {
|
|
12
|
+
return null == content || 'boolean' == typeof content || '' === content;
|
|
13
|
+
}
|
|
14
|
+
function hasCheckoutCardProductItemContent(item) {
|
|
15
|
+
return null != item.title && item.title.length > 0 || null != item.spec && item.spec.length > 0 || null != item.imageSrc && item.imageSrc.length > 0 || !isCheckoutCardNodeEmpty(item.image) || null != item.pricePrefix && item.pricePrefix.length > 0 || !isCheckoutCardTextEmpty(item.price) || !isCheckoutCardTextEmpty(item.quantity);
|
|
16
|
+
}
|
|
17
|
+
function renderCheckoutCardProductItem({ item, index }) {
|
|
18
|
+
if (!hasCheckoutCardProductItemContent(item)) return null;
|
|
19
|
+
const { key, onClick, ...productSummaryProps } = item;
|
|
20
|
+
return <view className="doubao-checkout-card__product" key={key ?? index}>
|
|
21
|
+
<TemplateProductSummary {...productSummaryProps} onClick={onClick}/>
|
|
22
|
+
</view>;
|
|
23
|
+
}
|
|
24
|
+
function CheckoutCard(props) {
|
|
25
|
+
const { header, productItems = [], infoItems = [], feeItems = [], discountText, totalLabel, totalPrice, footer, className, style, onClick } = props;
|
|
26
|
+
const productNodes = productItems.map((item, index)=>renderCheckoutCardProductItem({
|
|
27
|
+
item,
|
|
28
|
+
index
|
|
29
|
+
})).filter((node)=>null != node);
|
|
30
|
+
return <view className={useThemeClassName(clsx('doubao-checkout-card', className))} style={style} bindtap={onClick} clip-radius="true">
|
|
31
|
+
<TemplateCardHeader actionText={header?.actionText} showAction={header?.showAction} onActionClick={header?.onActionClick}/>
|
|
32
|
+
|
|
33
|
+
{productNodes.length > 0 && <view className="doubao-checkout-card__products">{productNodes}</view>}
|
|
34
|
+
|
|
35
|
+
<TemplateInfoSection infoItems={infoItems} feeItems={feeItems} discountText={discountText} totalLabel={totalLabel} totalPrice={totalPrice} valueLayout="column"/>
|
|
36
|
+
|
|
37
|
+
<TemplateCardFooter secondaryActionButton={footer?.secondaryActionButton} primaryActionButton={footer?.primaryActionButton}/>
|
|
38
|
+
</view>;
|
|
39
|
+
}
|
|
40
|
+
export { CheckoutCard };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.doubao-checkout-card {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
background-color: var(--bg-base-1);
|
|
4
|
+
border: 1px solid var(--bg-base-2-overlay);
|
|
5
|
+
border-radius: 8px;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: stretch;
|
|
8
|
+
width: 100%;
|
|
9
|
+
display: flex;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.doubao-checkout-card__products {
|
|
14
|
+
background-color: var(--bg-base-1);
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: stretch;
|
|
17
|
+
width: 100%;
|
|
18
|
+
display: flex;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.doubao-checkout-card__product {
|
|
22
|
+
position: relative;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.doubao-checkout-card__product + .doubao-checkout-card__product:before {
|
|
26
|
+
background-color: var(--neutral-transparent-1);
|
|
27
|
+
content: "";
|
|
28
|
+
height: 1px;
|
|
29
|
+
position: absolute;
|
|
30
|
+
top: 0;
|
|
31
|
+
left: 16px;
|
|
32
|
+
right: 16px;
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useTheme } from "@byted-doubao-apps/components";
|
|
2
|
+
function getTemplateChevronRightIconContent(color) {
|
|
3
|
+
return `<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
4
|
+
<path d="M4.51458 11.3743C4.99729 11.6423 5.60576 11.4682 5.87396 10.9856L7.99601 7.16575C8.23233 6.74036 8.35049 6.52766 8.39681 6.30241C8.43781 6.10305 8.43781 5.89744 8.39681 5.69808C8.35049 5.47283 8.23233 5.26013 7.99601 4.83474L5.87396 1.01489C5.60577 0.532166 4.99734 0.358141 4.51458 0.626222C4.03188 0.894415 3.85783 1.50285 4.12591 1.9856L6.22599 5.76721C6.27322 5.85226 6.29684 5.89479 6.3061 5.93983C6.31429 5.97969 6.31429 6.0208 6.3061 6.06066C6.29684 6.1057 6.27322 6.14823 6.22598 6.23328L4.12591 10.0149C3.85791 10.4976 4.03189 11.1061 4.51458 11.3743Z" fill="${color}"/>
|
|
5
|
+
</svg>`;
|
|
6
|
+
}
|
|
7
|
+
function TemplateChevronRightIcon(props) {
|
|
8
|
+
const { className } = props;
|
|
9
|
+
const theme = useTheme();
|
|
10
|
+
const color = 'dark' === theme ? '#666666' : '#cccccc';
|
|
11
|
+
return <svg className={className} content={getTemplateChevronRightIconContent(color)}/>;
|
|
12
|
+
}
|
|
13
|
+
export { TemplateChevronRightIcon };
|