@tfdesign/b-end 1.0.13 → 1.0.15
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/package.json +1 -1
- package/skills/tfds/CHECKLIST.md +5 -0
- package/skills/tfds/COMMON_FAILURES.md +48 -0
- package/skills/tfds/DESIGN_PRINCIPLES.md +5 -0
- package/skills/tfds/GLOBAL_DESIGN_RULES.md +31 -0
- package/skills/tfds/LAYOUT_RULES.md +31 -0
- package/skills/tfds/components.index.json +75 -27
- package/skills/tfds/components.summary.json +13 -13
- package/src/_b_end_runtime/components/Card.jsx +151 -13
- package/src/_b_end_runtime/components/Card.tokens.js +27 -3
- package/src/_b_end_runtime/components/CardPreview.jsx +11 -3
- package/src/_b_end_runtime/components/ChatMessage.jsx +59 -1
- package/src/_b_end_runtime/components/ConversationList.jsx +68 -68
- package/src/_b_end_runtime/components/ConversationList.tokens.js +5 -3
- package/src/_b_end_runtime/components/FullScreenPage.jsx +1 -0
- package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +13 -15
- package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +2 -0
- package/src/_b_end_runtime/components/Modal.jsx +1 -0
- package/src/_b_end_runtime/components/Sheet.jsx +1 -0
- package/src/_b_end_runtime/components/Table.jsx +7 -0
- package/src/_b_end_runtime/components/Tabs.jsx +46 -3
- package/src/_b_end_runtime/components/Tabs.tokens.js +3 -0
- package/src/_b_end_runtime/components/TagBar.jsx +2 -0
- package/src/_b_end_runtime/components/Toast.jsx +1 -0
- package/src/_b_end_runtime/components/Upload.jsx +1 -0
- package/src/_b_end_runtime/components.js +24 -11
- package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +548 -135
- package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +1 -1
- package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +6 -6
- package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +66 -5
- package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +50 -17
- package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +28 -78
- package/src/_b_end_runtime/patterns.js +32 -21
- package/src/_b_end_runtime/preview-registry.jsx +20 -4
- package/src/index.d.ts +4 -2
|
@@ -35,6 +35,13 @@ const DEFAULT_ANIMATED_DESCRIPTION = '基于业务目标、历史数据和运营
|
|
|
35
35
|
const DEFAULT_ANIMATED_ICON = 'magic-wand-01-stroked';
|
|
36
36
|
const DEFAULT_ANIMATED_BADGE = 'AI 推荐';
|
|
37
37
|
const DEFAULT_ANIMATED_ACTION_TEXT = '立即体验';
|
|
38
|
+
const DEFAULT_INFO3_TITLE = '抖音生服订单已签收后还能申请退款吗?';
|
|
39
|
+
const DEFAULT_INFO3_DESCRIPTION = '已签收订单仍可在 7 天无理由保障期内发起退款,需提供商品照片或服务异常说明,平台介入后通常 24h 内出结果。';
|
|
40
|
+
const DEFAULT_INFO3_STATUS = '生效';
|
|
41
|
+
const DEFAULT_INFO3_META_ITEMS = [
|
|
42
|
+
{ iconName: 'tag-01-stroked', value: 'QA' },
|
|
43
|
+
{ iconName: 'clock-stroked', value: '2026-04-21 10:30' },
|
|
44
|
+
];
|
|
38
45
|
const DEFAULT_INFO_STATS = [
|
|
39
46
|
{ iconName: 'users-01-stroked', value: '128.6K', tooltip: '累计启用该能力的创作者数量' },
|
|
40
47
|
{ iconName: 'star-01-stroked', value: '4.9', tooltip: '用户综合评分,满分 5 分' },
|
|
@@ -53,6 +60,12 @@ const CARD_SURFACE = [
|
|
|
53
60
|
'hover:shadow-card',
|
|
54
61
|
].join(' ');
|
|
55
62
|
|
|
63
|
+
const CARD_INTERACTIVE = [
|
|
64
|
+
'cursor-pointer',
|
|
65
|
+
'focus:outline-none',
|
|
66
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blueGrey-300',
|
|
67
|
+
].join(' ');
|
|
68
|
+
|
|
56
69
|
const DATA_CARD = 'min-h-[var(--size-card-min-height)] flex-col justify-between gap-7';
|
|
57
70
|
const PRODUCT_CARD = 'min-h-[96px] flex-row items-center gap-5 [&_.tfds-tag.bg-white]:hidden';
|
|
58
71
|
const INFO_CARD = 'min-h-[148px] flex-row items-start gap-6 p-6 [&_.tfds-tag.bg-white]:hidden';
|
|
@@ -266,6 +279,34 @@ const ANIMATED_TONE_STYLE = {
|
|
|
266
279
|
},
|
|
267
280
|
};
|
|
268
281
|
|
|
282
|
+
/* ── 信息卡片3 ── */
|
|
283
|
+
const INFO3_CARD = [
|
|
284
|
+
'min-h-[148px] flex-col gap-2 rounded-lg p-4',
|
|
285
|
+
'text-left',
|
|
286
|
+
].join(' ');
|
|
287
|
+
const INFO3_HEADER = 'flex items-start justify-between gap-2';
|
|
288
|
+
const INFO3_TITLE = [
|
|
289
|
+
'm-0 min-w-0 flex-1 text-base [font-weight:var(--font-semibold)] leading-6 text-blueGrey-900',
|
|
290
|
+
'line-clamp-2',
|
|
291
|
+
].join(' ');
|
|
292
|
+
const INFO3_DESCRIPTION = 'm-0 line-clamp-2 text-sm font-normal leading-[22px] text-blueGrey-600';
|
|
293
|
+
const INFO3_META = 'flex min-w-0 flex-wrap items-center gap-3 text-xs font-normal leading-4 text-blueGrey-500';
|
|
294
|
+
const INFO3_META_ITEM = 'inline-flex min-w-0 items-center gap-1';
|
|
295
|
+
const INFO3_META_AVATAR = [
|
|
296
|
+
'[&>img]:!scale-100',
|
|
297
|
+
'[&>img]:!translate-y-0',
|
|
298
|
+
'[&>img]:!object-center',
|
|
299
|
+
].join(' ');
|
|
300
|
+
|
|
301
|
+
const INFO3_STATUS_VARIANT = {
|
|
302
|
+
生效: 'green',
|
|
303
|
+
已发布: 'green',
|
|
304
|
+
草稿: 'grey',
|
|
305
|
+
审核中: 'orange',
|
|
306
|
+
已停用: 'red',
|
|
307
|
+
已下架: 'red',
|
|
308
|
+
};
|
|
309
|
+
|
|
269
310
|
/* ── 底部 ── */
|
|
270
311
|
const FOOTER = 'flex w-full items-center justify-between gap-4';
|
|
271
312
|
const ACTION = [
|
|
@@ -279,12 +320,33 @@ const ACTION = [
|
|
|
279
320
|
'group-hover:border-black group-hover:text-black',
|
|
280
321
|
].join(' ');
|
|
281
322
|
|
|
323
|
+
function getInteractiveCardProps(onAction, actionAriaLabel) {
|
|
324
|
+
if (typeof onAction !== 'function') return {};
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
role: 'button',
|
|
328
|
+
tabIndex: 0,
|
|
329
|
+
'aria-label': actionAriaLabel,
|
|
330
|
+
onClick: onAction,
|
|
331
|
+
onKeyDown: (event) => {
|
|
332
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
onAction(event);
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function handleNestedActionClick(event, onAction) {
|
|
340
|
+
event.stopPropagation();
|
|
341
|
+
onAction?.(event);
|
|
342
|
+
}
|
|
343
|
+
|
|
282
344
|
/**
|
|
283
345
|
* Card — 业务信息摘要卡片
|
|
284
|
-
* @prop {'data'|'product'|'info'|'info2'|'animated'} [type='data'] — 卡片类型:data 为数据卡片,product 为商品卡片,info 为信息卡片1,info2 为信息卡片2;animated 为旧版兼容别名
|
|
346
|
+
* @prop {'data'|'product'|'info'|'info2'|'info3'|'animated'} [type='data'] — 卡片类型:data 为数据卡片,product 为商品卡片,info 为信息卡片1,info2 为信息卡片2,info3 为信息卡片3;animated 为旧版兼容别名
|
|
285
347
|
* @prop {string} [title] — 卡片标题,商品卡片中为商品标题
|
|
286
348
|
* @prop {string} [description] — 卡片描述,商品卡片中为数量、价格等辅助信息
|
|
287
|
-
* @prop {'white'|'grey'} [color='white'] — 卡片颜色:white
|
|
349
|
+
* @prop {'white'|'grey'} [color='white'] — 卡片颜色:white 为白底样式,适用于灰/浅灰/非纯白容器;grey 为 Blue Grey 灰底样式,适用于纯白容器
|
|
288
350
|
* @prop {Array<{iconName: string, value: string, tooltip?: string}>|null} [stats=null] — 数据卡片指标数组,最多展示 3 项;tooltip 用于 hover/focus 展示详细说明
|
|
289
351
|
* @prop {string[]|null} [tags=null] — 数据卡片标签数组,建议 1-2 个短标签;商品卡片和信息卡片1不渲染通用 tags
|
|
290
352
|
* @prop {string} [productStatus='已使用'] — 商品状态标签文案
|
|
@@ -302,19 +364,25 @@ const ACTION = [
|
|
|
302
364
|
* @prop {string|null} [animatedIconName='magic-wand-01-stroked'] — 信息卡片2左上角图标
|
|
303
365
|
* @prop {string|null} [animatedBadge='AI 推荐'] — 信息卡片2左下角徽标
|
|
304
366
|
* @prop {string|null} [animatedActionText='立即体验'] — 信息卡片2底部右侧操作文案
|
|
367
|
+
* @prop {boolean} [selected=false] — 信息卡片3整体选中态,使用浅绿色背景与 brand 描边
|
|
368
|
+
* @prop {string} [status='生效'] — 信息卡片3右上角状态标签文案
|
|
369
|
+
* @prop {'brand'|'red'|'orange'|'yellow'|'green'|'cyan'|'blue'|'purple'|'pink'|'teal'|'grey'|'white'|'ai'|null} [statusVariant=null] — 信息卡片3右上角状态标签颜色;不传时按常见状态自动映射
|
|
370
|
+
* @prop {Array<{iconName?: string, value: string, avatarSrc?: string, avatarAlt?: string}>|null} [metaItems=null] — 信息卡片3底部元信息,适合类型、更新时间、创建人等密集信息
|
|
305
371
|
* @prop {string} [infoMetaLabel='段然'] — 信息卡片1底部人名昵称
|
|
306
372
|
* @prop {string} [infoMetaBadge='官方能力'] — 信息卡片1右上角徽标文案
|
|
307
373
|
* @prop {'brand'|'red'|'orange'|'yellow'|'green'|'cyan'|'blue'|'purple'|'pink'|'teal'|'grey'|'white'|'ai'|null} [infoMetaBadgeVariant=null] — 信息卡片1右上角徽标颜色;黑底白标时可覆盖,彩色浅底时固定 grey
|
|
308
374
|
* @prop {string} [infoMetaAvatarSrc] — 信息卡片1底部人名昵称头像
|
|
309
375
|
* @prop {string} [infoMetaAvatarAlt='用户头像'] — 信息卡片1底部人名昵称头像无障碍文案
|
|
310
376
|
* @prop {Array<{iconName: string, value: string, tooltip?: string}>|null} [infoStats=null] — 信息卡片1底部右侧辅助项数组,最多展示 3 项;tooltip 用于 hover/focus 展示详细说明
|
|
311
|
-
* @prop {function|null} [onAction=null] —
|
|
312
|
-
* @prop {string} [actionAriaLabel='查看卡片详情'] —
|
|
377
|
+
* @prop {function|null} [onAction=null] — 详情跳转 / 进入二级页回调;传入后整张卡片默认可点击
|
|
378
|
+
* @prop {string} [actionAriaLabel='查看卡片详情'] — 整卡与右侧操作按钮共用的无障碍文案
|
|
313
379
|
* @prop {string} [className=''] — 附加类名
|
|
314
380
|
* @prop {object} [style] — 内联样式
|
|
315
381
|
*
|
|
316
382
|
* 通用 tags 仅用于数据卡片左下角;商品和信息卡片1不渲染标题左侧通用标签。
|
|
317
|
-
*
|
|
383
|
+
* 所有 Card 分类都遵循“背景反衬”规则:父容器是纯白色时必须使用灰底卡;父容器是灰色、浅灰色或其他非纯白底时必须使用白底卡。
|
|
384
|
+
* 卡片容器默认半透明白底,hover 后补满白底并出现业务卡片专用投影;灰底卡保持灰底与灰描边,并保留相同投影反馈。
|
|
385
|
+
* 所有 Card 分类只要传入 onAction,就默认整张卡片区域可点击进入;右下角箭头仅作为进入提示,不是唯一热区。
|
|
318
386
|
*/
|
|
319
387
|
export default function Card({
|
|
320
388
|
type = 'data',
|
|
@@ -344,25 +412,30 @@ export default function Card({
|
|
|
344
412
|
animatedIconName = DEFAULT_ANIMATED_ICON,
|
|
345
413
|
animatedBadge = DEFAULT_ANIMATED_BADGE,
|
|
346
414
|
animatedActionText = DEFAULT_ANIMATED_ACTION_TEXT,
|
|
415
|
+
selected = false,
|
|
416
|
+
status = DEFAULT_INFO3_STATUS,
|
|
417
|
+
statusVariant = null,
|
|
418
|
+
metaItems = null,
|
|
347
419
|
children,
|
|
348
420
|
onAction,
|
|
349
421
|
actionAriaLabel = '查看卡片详情',
|
|
350
422
|
className = '',
|
|
351
423
|
style,
|
|
352
424
|
}) {
|
|
353
|
-
const resolvedType = type === 'product' || type === 'info'
|
|
425
|
+
const resolvedType = type === 'product' || type === 'info' || type === 'info3'
|
|
354
426
|
? type
|
|
355
427
|
: (type === 'info2' || type === 'animated' ? 'info2' : 'data');
|
|
428
|
+
// Card 颜色依赖父级背景做反衬,不按卡片类型区分。
|
|
356
429
|
const resolvedColor = color === 'grey' ? 'grey' : 'white';
|
|
357
430
|
const resolvedTitle = title || (
|
|
358
431
|
resolvedType === 'product'
|
|
359
432
|
? DEFAULT_PRODUCT_TITLE
|
|
360
|
-
: (resolvedType === 'info' ? DEFAULT_INFO_TITLE : (resolvedType === 'info2' ? DEFAULT_ANIMATED_TITLE : DEFAULT_DATA_TITLE))
|
|
433
|
+
: (resolvedType === 'info' ? DEFAULT_INFO_TITLE : (resolvedType === 'info2' ? DEFAULT_ANIMATED_TITLE : (resolvedType === 'info3' ? DEFAULT_INFO3_TITLE : DEFAULT_DATA_TITLE)))
|
|
361
434
|
);
|
|
362
435
|
const resolvedDescription = description || (
|
|
363
436
|
resolvedType === 'product'
|
|
364
437
|
? DEFAULT_PRODUCT_DESCRIPTION
|
|
365
|
-
: (resolvedType === 'info' ? DEFAULT_INFO_DESCRIPTION : (resolvedType === 'info2' ? DEFAULT_ANIMATED_DESCRIPTION : DEFAULT_DATA_DESCRIPTION))
|
|
438
|
+
: (resolvedType === 'info' ? DEFAULT_INFO_DESCRIPTION : (resolvedType === 'info2' ? DEFAULT_ANIMATED_DESCRIPTION : (resolvedType === 'info3' ? DEFAULT_INFO3_DESCRIPTION : DEFAULT_DATA_DESCRIPTION)))
|
|
366
439
|
);
|
|
367
440
|
const resolvedStats = (Array.isArray(stats) && stats.length > 0 ? stats : DEFAULT_STATS).slice(0, 3);
|
|
368
441
|
const resolvedTags = resolvedType === 'data'
|
|
@@ -392,24 +465,34 @@ export default function Card({
|
|
|
392
465
|
const resolvedAnimatedTone = Object.prototype.hasOwnProperty.call(ANIMATED_TONE_STYLE, animatedTone)
|
|
393
466
|
? animatedTone
|
|
394
467
|
: 'grey';
|
|
468
|
+
const resolvedInfo3MetaItems = Array.isArray(metaItems) && metaItems.length > 0
|
|
469
|
+
? metaItems
|
|
470
|
+
: DEFAULT_INFO3_META_ITEMS;
|
|
471
|
+
const resolvedStatusVariant = INFO_BADGE_VARIANTS.has(statusVariant)
|
|
472
|
+
? statusVariant
|
|
473
|
+
: (INFO3_STATUS_VARIANT[status] || 'grey');
|
|
474
|
+
const isClickable = typeof onAction === 'function';
|
|
395
475
|
const cardStyle = {
|
|
396
476
|
...(CARD_VARIANT_STYLE[resolvedColor] || {}),
|
|
397
477
|
...(resolvedType === 'info' && resolvedInfoIconStyle === 'tone' ? INFO_ICON_TONE_HOVER_STYLE[resolvedInfoIconTone] : {}),
|
|
398
478
|
...(resolvedType === 'info2' ? ANIMATED_TONE_STYLE[resolvedAnimatedTone] : {}),
|
|
399
479
|
...style,
|
|
400
480
|
};
|
|
481
|
+
const interactiveProps = getInteractiveCardProps(onAction, actionAriaLabel);
|
|
401
482
|
|
|
402
483
|
if (resolvedType === 'product') {
|
|
403
484
|
return (
|
|
404
485
|
<article
|
|
405
486
|
className={[
|
|
406
487
|
CARD_SURFACE,
|
|
488
|
+
isClickable ? CARD_INTERACTIVE : null,
|
|
407
489
|
resolvedInfoLayout === 'icon-right' ? PRODUCT_CARD_ICON_RIGHT : PRODUCT_CARD,
|
|
408
490
|
CARD_VARIANT_CLASS[resolvedColor],
|
|
409
491
|
className,
|
|
410
492
|
].filter(Boolean).join(' ')}
|
|
411
493
|
style={cardStyle}
|
|
412
494
|
data-tfds-component="Card"
|
|
495
|
+
{...interactiveProps}
|
|
413
496
|
>
|
|
414
497
|
<div className={PRODUCT_IMAGE_FRAME}>
|
|
415
498
|
<img
|
|
@@ -463,9 +546,10 @@ export default function Card({
|
|
|
463
546
|
|
|
464
547
|
return (
|
|
465
548
|
<article
|
|
466
|
-
className={[CARD_SURFACE, INFO_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
|
|
549
|
+
className={[CARD_SURFACE, isClickable ? CARD_INTERACTIVE : null, INFO_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
|
|
467
550
|
style={cardStyle}
|
|
468
551
|
data-tfds-component="Card"
|
|
552
|
+
{...interactiveProps}
|
|
469
553
|
>
|
|
470
554
|
{resolvedInfoLayout === 'icon-right' ? null : iconNode}
|
|
471
555
|
|
|
@@ -517,9 +601,10 @@ export default function Card({
|
|
|
517
601
|
if (resolvedType === 'info2') {
|
|
518
602
|
return (
|
|
519
603
|
<article
|
|
520
|
-
className={[CARD_SURFACE, ANIMATED_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
|
|
604
|
+
className={[CARD_SURFACE, isClickable ? CARD_INTERACTIVE : null, ANIMATED_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
|
|
521
605
|
style={cardStyle}
|
|
522
606
|
data-tfds-component="Card"
|
|
607
|
+
{...interactiveProps}
|
|
523
608
|
>
|
|
524
609
|
<div className={ANIMATED_CONTENT}>
|
|
525
610
|
<div className={ANIMATED_HEADER}>
|
|
@@ -556,7 +641,7 @@ export default function Card({
|
|
|
556
641
|
type="button"
|
|
557
642
|
className={ACTION}
|
|
558
643
|
aria-label={animatedActionText || actionAriaLabel}
|
|
559
|
-
onClick={onAction}
|
|
644
|
+
onClick={(event) => handleNestedActionClick(event, onAction)}
|
|
560
645
|
disabled={!onAction}
|
|
561
646
|
>
|
|
562
647
|
<Icon name="arrow-narrow-right-stroked" size={16} />
|
|
@@ -568,11 +653,64 @@ export default function Card({
|
|
|
568
653
|
);
|
|
569
654
|
}
|
|
570
655
|
|
|
656
|
+
if (resolvedType === 'info3') {
|
|
657
|
+
return (
|
|
658
|
+
<article
|
|
659
|
+
className={[
|
|
660
|
+
CARD_SURFACE,
|
|
661
|
+
isClickable ? CARD_INTERACTIVE : null,
|
|
662
|
+
INFO3_CARD,
|
|
663
|
+
CARD_VARIANT_CLASS[resolvedColor],
|
|
664
|
+
className,
|
|
665
|
+
].filter(Boolean).join(' ')}
|
|
666
|
+
style={{
|
|
667
|
+
...cardStyle,
|
|
668
|
+
...(selected ? { borderColor: 'var(--color-brand-500, #00B384)' } : {}),
|
|
669
|
+
}}
|
|
670
|
+
data-tfds-component="Card"
|
|
671
|
+
data-tfds-card-type="info3"
|
|
672
|
+
aria-selected={selected || undefined}
|
|
673
|
+
{...interactiveProps}
|
|
674
|
+
>
|
|
675
|
+
<div className={INFO3_HEADER}>
|
|
676
|
+
<h3 className={INFO3_TITLE}>{resolvedTitle}</h3>
|
|
677
|
+
{status ? (
|
|
678
|
+
<Tag variant={resolvedStatusVariant} className="shrink-0">
|
|
679
|
+
{status}
|
|
680
|
+
</Tag>
|
|
681
|
+
) : null}
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
<p className={INFO3_DESCRIPTION}>{resolvedDescription}</p>
|
|
685
|
+
|
|
686
|
+
<div className={INFO3_META}>
|
|
687
|
+
{resolvedInfo3MetaItems.map((item, index) => (
|
|
688
|
+
<span key={`${item.value}-${index}`} className={INFO3_META_ITEM}>
|
|
689
|
+
{item.avatarSrc ? (
|
|
690
|
+
<Avatar
|
|
691
|
+
size="xxs"
|
|
692
|
+
type="image"
|
|
693
|
+
src={item.avatarSrc}
|
|
694
|
+
alt={item.avatarAlt || `${item.value}头像`}
|
|
695
|
+
className={INFO3_META_AVATAR}
|
|
696
|
+
/>
|
|
697
|
+
) : item.iconName ? (
|
|
698
|
+
<Icon name={item.iconName} size="xs" />
|
|
699
|
+
) : null}
|
|
700
|
+
<span className="truncate">{item.value}</span>
|
|
701
|
+
</span>
|
|
702
|
+
))}
|
|
703
|
+
</div>
|
|
704
|
+
</article>
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
571
708
|
return (
|
|
572
709
|
<article
|
|
573
|
-
className={[CARD_SURFACE, DATA_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
|
|
710
|
+
className={[CARD_SURFACE, isClickable ? CARD_INTERACTIVE : null, DATA_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
|
|
574
711
|
style={cardStyle}
|
|
575
712
|
data-tfds-component="Card"
|
|
713
|
+
{...interactiveProps}
|
|
576
714
|
>
|
|
577
715
|
{hasDataIcon ? (
|
|
578
716
|
<div className={DATA_ICON_ROW}>
|
|
@@ -622,7 +760,7 @@ export default function Card({
|
|
|
622
760
|
type="button"
|
|
623
761
|
className={ACTION}
|
|
624
762
|
aria-label={actionAriaLabel}
|
|
625
|
-
onClick={onAction}
|
|
763
|
+
onClick={(event) => handleNestedActionClick(event, onAction)}
|
|
626
764
|
disabled={!onAction}
|
|
627
765
|
>
|
|
628
766
|
<Icon name="arrow-narrow-right-stroked" size={16} />
|
|
@@ -98,7 +98,7 @@ export const CARD_TOKEN_MAP = {
|
|
|
98
98
|
],
|
|
99
99
|
信息卡片2: [
|
|
100
100
|
{ label: '适用场景', cssProp: 'usage', value: 'AI 能力推荐 / 重点工具入口 / 配置向导 / 首页推荐能力;单页建议最多 1-2 张' },
|
|
101
|
-
{ label: '结构', cssProp: 'layout', value: '左上图标 + 标题/描述 + 左下角徽标 +
|
|
101
|
+
{ label: '结构', cssProp: 'layout', value: '左上图标 + 标题/描述 + 左下角徽标 + 右下角圆形箭头提示入口' },
|
|
102
102
|
{ label: '背景与描边', cssProp: 'background/border', value: '完全复用 Card color=white / grey 的背景、描边和 hover 投影规则' },
|
|
103
103
|
{ label: '交互', cssProp: 'motion', value: '无柔光、无流动动画、无特殊 hover 位移;仅保留普通 Card hover 投影' },
|
|
104
104
|
{ label: '图标色系配置', cssProp: 'prop', value: 'animatedTone: brand / blue / purple / green / orange / grey,默认 grey;仅影响左上图标容器;grey 背景使用 fill-default,描边使用 border-default' },
|
|
@@ -112,10 +112,31 @@ export const CARD_TOKEN_MAP = {
|
|
|
112
112
|
{ label: '描述行数', cssProp: 'line-clamp', value: '最多 2 行' },
|
|
113
113
|
{ label: '徽标位置', cssProp: 'placement', value: '左下角,与标题左边界对齐,并与右下角圆形箭头操作入口同一行垂直居中' },
|
|
114
114
|
{ label: '徽标样式', cssProp: 'component-style', value: 'Tag variant="white" / fontWeight="regular" / size=l / radius=md' },
|
|
115
|
-
{ label: '
|
|
116
|
-
{ label: '
|
|
115
|
+
{ label: '整卡点击', cssProp: 'interaction', value: '传入 onAction 时整张卡片默认可点击进入,支持鼠标点击与 Enter / Space 键盘触发' },
|
|
116
|
+
{ label: '操作按钮', cssProp: 'component-style', value: '复用数据卡片 ACTION:28px 圆形箭头按钮 / hover 黑色描边与黑色箭头 / active 缩放;当 onAction 存在时仅作为进入提示热区,点击后与整卡复用同一回调' },
|
|
117
|
+
{ label: '操作文案', cssProp: 'a11y-label', value: 'animatedActionText / actionAriaLabel 同时作为整卡与圆形箭头按钮语义文案,不直接渲染为文字按钮' },
|
|
118
|
+
],
|
|
119
|
+
信息卡片3: [
|
|
120
|
+
{ label: '适用场景', cssProp: 'usage', value: '密集信息列表 / 知识库知识列表 / 规则列表 / 策略列表 / 工单摘要列表;适合在“列表 + 详情面板”联动页面中作为对象条目' },
|
|
121
|
+
{ label: '结构', cssProp: 'layout', value: '标题 + 右上角状态标签 + 两行描述 + 底部元信息行' },
|
|
122
|
+
{ label: '标题', cssProp: 'component-style', value: '主标题最多 2 行,字号 16px,字重 600,行高 24px,颜色 blueGrey-900' },
|
|
123
|
+
{ label: '描述', cssProp: 'component-style', value: '描述最多 2 行,字号 14px,行高 22px,颜色 blueGrey-600' },
|
|
124
|
+
{ label: '状态标签', cssProp: 'component', value: 'Tag / 右上角 / 状态映射 variant:生效/已发布=green,草稿=grey,审核中=orange,已停用/已下架=red;也可通过 statusVariant 覆盖' },
|
|
125
|
+
{ label: '底部元信息', cssProp: 'component', value: 'metaItems: Array<{ iconName?, value, avatarSrc?, avatarAlt? }>;默认用于类型、更新时间、创建人头像与姓名' },
|
|
126
|
+
{ label: '元信息字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
|
|
127
|
+
{ label: '元信息间距', cssProp: 'gap', value: '12px;每个元信息内部 icon/avatar 与文字间距 4px' },
|
|
128
|
+
{ label: '默认背景', cssProp: 'background', value: '完全复用 Card color=white / grey 的背景规则,不单独定义信息卡片3专属底色' },
|
|
129
|
+
{ label: '默认描边', cssProp: 'border-color', value: '完全复用 Card color=white / grey 的描边规则,不单独定义信息卡片3专属描边' },
|
|
130
|
+
{ label: 'Hover 背景', cssProp: 'background', value: '完全复用 Card color=white / grey 的 hover 背景规则' },
|
|
131
|
+
{ label: '圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px,使用 rounded-lg' },
|
|
132
|
+
{ label: '选中提示', cssProp: 'border-color', token: '--color-brand-500', value: '通过 selected 将原本同一条描边从灰色切换为绿色,不新增 ring / outline / 外扩描边' },
|
|
133
|
+
{ label: '选中交互', cssProp: 'interaction', value: '通过 selected 控制整卡描边颜色;Card 只负责样式,选中/取消选中的业务状态由页面维护' },
|
|
134
|
+
{ label: '整卡点击', cssProp: 'interaction', value: '传入 onAction 后整张卡片默认可点击,支持鼠标点击与 Enter / Space 键盘触发;适用于打开或收起右侧详情面板' },
|
|
117
135
|
],
|
|
118
136
|
卡片: [
|
|
137
|
+
{ label: '容器映射规则', cssProp: 'usage', value: '所有卡片分类统一按父容器背景反衬:纯白容器用 grey 灰底卡;灰色 / 浅灰 / 其他非纯白容器用 white 白底卡' },
|
|
138
|
+
{ label: '规则适用范围', cssProp: 'scope', value: '数据卡片 / 商品卡片 / 信息卡片1 / 信息卡片2 / 信息卡片3 全部生效,不因卡片类型变化' },
|
|
139
|
+
{ label: '查看交互规则', cssProp: 'interaction', value: '所有 Card 分类只要传入 onAction(详情跳转能力),默认整张卡片区域都支持点击查看;箭头按钮仅作为进入提示,不是唯一热区' },
|
|
119
140
|
{ label: '白底背景', cssProp: 'background', token: '--color-card-secondary', value: 'rgba(255,255,255,0.65)', semanticRef: 'bg-card-secondary', state: 'default' },
|
|
120
141
|
{ label: '白底 Hover 背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF', semanticRef: 'bg-surface', state: 'hover' },
|
|
121
142
|
{ label: '白底描边', cssProp: 'border-color', token: '--color-white', value: '#FFFFFF' },
|
|
@@ -125,6 +146,7 @@ export const CARD_TOKEN_MAP = {
|
|
|
125
146
|
{ label: '投影', cssProp: 'box-shadow', token: '--shadow-card', value: '0 30px 50px 0 rgba(0, 9, 36, 0.05)', state: 'hover' },
|
|
126
147
|
],
|
|
127
148
|
操作: [
|
|
149
|
+
{ label: '触发热区', cssProp: 'interaction', value: '当 onAction 存在时,整卡为主热区;数据卡片 / 信息卡片2右下角圆形箭头为辅助提示热区' },
|
|
128
150
|
{ label: '图标色', cssProp: 'color', token: '--color-foreground-muted', value: '#667085', semanticRef: 'text-tertiary', state: 'default' },
|
|
129
151
|
{ label: '图标色', cssProp: 'color', token: '--color-black', value: '#000000', state: 'hover' },
|
|
130
152
|
{ label: '描边色', cssProp: 'border-color', token: '--color-transparent', value: 'transparent', state: 'default' },
|
|
@@ -145,6 +167,8 @@ export const CARD_TOKEN_MAP = {
|
|
|
145
167
|
{ label: '信息卡片1标题', cssProp: 'component', value: 'FormTitle / variant=form / showDescription' },
|
|
146
168
|
{ label: '信息卡片1徽标', cssProp: 'component', value: 'Tag / infoMetaBadgeVariant / size=l / radius=md' },
|
|
147
169
|
{ label: '信息卡片1昵称头像', cssProp: 'component', value: 'Avatar / type=image / shape=round / size=xxs' },
|
|
170
|
+
{ label: '信息卡片3状态标签', cssProp: 'component', value: 'Tag / 右上角状态 / variant 由 statusVariant 或状态映射决定' },
|
|
171
|
+
{ label: '信息卡片3元信息头像', cssProp: 'component', value: 'Avatar / type=image / shape=round / size=xxs' },
|
|
148
172
|
{ label: '数据卡片图标', cssProp: 'component', value: 'Icon / users-01-stroked / message-chat-square-stroked / hearts-stroked / arrow-narrow-right-stroked' },
|
|
149
173
|
{ label: '信息卡片1图标', cssProp: 'component', value: 'Icon / magic-wand-01-stroked / users-01-stroked / star-01-stroked / check-circle-stroked' },
|
|
150
174
|
],
|
|
@@ -16,7 +16,8 @@ export default function CardPreview({
|
|
|
16
16
|
const isProduct = type === 'product';
|
|
17
17
|
const isInfo = type === 'info';
|
|
18
18
|
const isInfo2 = type === 'info2' || type === 'animated';
|
|
19
|
-
const
|
|
19
|
+
const isInfo3 = type === 'info3';
|
|
20
|
+
const isData = !isProduct && !isInfo && !isInfo2 && !isInfo3;
|
|
20
21
|
const resolvedInfoIconStyle = isInfo ? (infoIconStyle || 'inverse') : infoIconStyle;
|
|
21
22
|
const resolvedInfoLayout = isInfo
|
|
22
23
|
? (infoLayout || 'icon-right')
|
|
@@ -30,8 +31,8 @@ export default function CardPreview({
|
|
|
30
31
|
<Card
|
|
31
32
|
type={type}
|
|
32
33
|
color={color}
|
|
33
|
-
title={isProduct ? '海底捞门店通用双人套餐' : (isInfo2 ? '智能策略生成' : undefined)}
|
|
34
|
-
description={isProduct ? '数量 1 · ¥128.00 · 月售 2,361' : (isInfo2 ? '基于业务目标、历史数据和运营规则,自动生成可执行策略建议。' : undefined)}
|
|
34
|
+
title={isProduct ? '海底捞门店通用双人套餐' : (isInfo2 ? '智能策略生成' : (isInfo3 ? '抖音生服订单已签收后还能申请退款吗?' : undefined))}
|
|
35
|
+
description={isProduct ? '数量 1 · ¥128.00 · 月售 2,361' : (isInfo2 ? '基于业务目标、历史数据和运营规则,自动生成可执行策略建议。' : (isInfo3 ? '已签收订单仍可在 7 天无理由保障期内发起退款,需提供商品照片或服务异常说明,平台介入后通常 24h 内出结果。' : undefined))}
|
|
35
36
|
tags={isProduct || isInfo ? [] : undefined}
|
|
36
37
|
productStatus={isProduct ? '已使用' : undefined}
|
|
37
38
|
infoIconTone={isInfo ? infoIconTone : undefined}
|
|
@@ -48,6 +49,13 @@ export default function CardPreview({
|
|
|
48
49
|
animatedIconName={isInfo2 ? 'magic-wand-01-stroked' : undefined}
|
|
49
50
|
animatedBadge={isInfo2 ? 'AI 推荐' : undefined}
|
|
50
51
|
animatedActionText={isInfo2 ? '立即体验' : undefined}
|
|
52
|
+
selected={false}
|
|
53
|
+
status={isInfo3 ? '生效' : undefined}
|
|
54
|
+
metaItems={isInfo3 ? [
|
|
55
|
+
{ iconName: 'tag-01-stroked', value: 'QA' },
|
|
56
|
+
{ iconName: 'clock-stroked', value: '2026-04-21 10:30' },
|
|
57
|
+
{ value: '李思儒' },
|
|
58
|
+
] : undefined}
|
|
51
59
|
actionAriaLabel={isProduct ? undefined : '查看卡片详情'}
|
|
52
60
|
onAction={isProduct ? undefined : () => {}}
|
|
53
61
|
/>
|
|
@@ -797,7 +797,9 @@ function normalizeHumanConfirmNode(node, index) {
|
|
|
797
797
|
options,
|
|
798
798
|
defaultSelectedValue,
|
|
799
799
|
formItems,
|
|
800
|
+
formValues: node?.formValues && typeof node.formValues === 'object' ? node.formValues : null,
|
|
800
801
|
onOptionChange: typeof node?.onOptionChange === 'function' ? node.onOptionChange : null,
|
|
802
|
+
onFormChange: typeof node?.onFormChange === 'function' ? node.onFormChange : null,
|
|
801
803
|
onPrimaryAction: typeof node?.onPrimaryAction === 'function' ? node.onPrimaryAction : null,
|
|
802
804
|
onSecondaryAction: typeof node?.onSecondaryAction === 'function' ? node.onSecondaryAction : null,
|
|
803
805
|
onToggleCollapsed: typeof node?.onToggleCollapsed === 'function' ? node.onToggleCollapsed : null,
|
|
@@ -1643,6 +1645,26 @@ function UserMessageContent({ tokens }) {
|
|
|
1643
1645
|
);
|
|
1644
1646
|
}
|
|
1645
1647
|
|
|
1648
|
+
function getInitialFormValues(formItems = []) {
|
|
1649
|
+
return formItems.reduce((values, item) => {
|
|
1650
|
+
const key = item?.id || item?.name;
|
|
1651
|
+
if (!key) return values;
|
|
1652
|
+
if (item.value !== undefined) values[key] = item.value;
|
|
1653
|
+
else if (item.defaultValue !== undefined) values[key] = item.defaultValue;
|
|
1654
|
+
else if (item.defaultChecked !== undefined) values[key] = item.defaultChecked;
|
|
1655
|
+
else values[key] = '';
|
|
1656
|
+
return values;
|
|
1657
|
+
}, {});
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function getFieldChangeValue(raw) {
|
|
1661
|
+
if (raw && typeof raw === 'object' && raw.target) {
|
|
1662
|
+
const { type, checked, value } = raw.target;
|
|
1663
|
+
return type === 'checkbox' ? checked : value;
|
|
1664
|
+
}
|
|
1665
|
+
return raw;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1646
1668
|
/* ── 人工确认节点 ── */
|
|
1647
1669
|
function HumanConfirmNode({ node, tokenStyles }) {
|
|
1648
1670
|
/* 内部自管折叠态(兼容外部受控:node.collapsed + node.onToggleCollapsed 都传时走外部) */
|
|
@@ -1656,18 +1678,23 @@ function HumanConfirmNode({ node, tokenStyles }) {
|
|
|
1656
1678
|
/* 点击主按钮后进入"已确认"禁用态:内容半透明、按钮禁用 */
|
|
1657
1679
|
const [confirmed, setConfirmed] = useState(node.defaultConfirmed === true);
|
|
1658
1680
|
const [selectedOptionValue, setSelectedOptionValue] = useState(node.defaultSelectedValue);
|
|
1681
|
+
const [formValues, setFormValues] = useState(() => node.formValues || getInitialFormValues(node.formItems));
|
|
1659
1682
|
useEffect(() => {
|
|
1660
1683
|
if (node.defaultConfirmed === true) setConfirmed(true);
|
|
1661
1684
|
}, [node.defaultConfirmed]);
|
|
1662
1685
|
useEffect(() => {
|
|
1663
1686
|
setSelectedOptionValue(node.defaultSelectedValue);
|
|
1664
1687
|
}, [node.defaultSelectedValue]);
|
|
1688
|
+
useEffect(() => {
|
|
1689
|
+
setFormValues(node.formValues || getInitialFormValues(node.formItems));
|
|
1690
|
+
}, [node.formItems, node.formValues]);
|
|
1665
1691
|
const handlePrimary = () => {
|
|
1666
1692
|
const selectedOption = node.options.find((option) => String(option.value) === String(selectedOptionValue)) ?? null;
|
|
1667
1693
|
if (typeof node.onPrimaryAction === 'function') {
|
|
1668
1694
|
node.onPrimaryAction({
|
|
1669
1695
|
value: selectedOptionValue,
|
|
1670
1696
|
option: selectedOption,
|
|
1697
|
+
formValues,
|
|
1671
1698
|
});
|
|
1672
1699
|
}
|
|
1673
1700
|
setConfirmed(true);
|
|
@@ -1682,11 +1709,41 @@ function HumanConfirmNode({ node, tokenStyles }) {
|
|
|
1682
1709
|
node.onOptionChange(nextValue, selectedOption);
|
|
1683
1710
|
}
|
|
1684
1711
|
};
|
|
1712
|
+
const handleFormValueChange = (item, rawValue) => {
|
|
1713
|
+
const key = item?.id || item?.name;
|
|
1714
|
+
if (!key) return;
|
|
1715
|
+
const nextValue = getFieldChangeValue(rawValue);
|
|
1716
|
+
setFormValues((prev) => {
|
|
1717
|
+
const nextValues = { ...prev, [key]: nextValue };
|
|
1718
|
+
if (typeof node.onFormChange === 'function') {
|
|
1719
|
+
node.onFormChange(nextValues, {
|
|
1720
|
+
fieldId: key,
|
|
1721
|
+
value: nextValue,
|
|
1722
|
+
item,
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
return nextValues;
|
|
1726
|
+
});
|
|
1727
|
+
};
|
|
1685
1728
|
|
|
1686
1729
|
const showIntroText = node.mode === 'text-card' && node.introText && !isCollapsed;
|
|
1687
1730
|
const showCardBody = !isCollapsed;
|
|
1688
1731
|
const isOptionCard = node.mode === 'option-card';
|
|
1689
1732
|
const isFormCard = node.mode === 'form-card';
|
|
1733
|
+
const resolvedFormItems = isFormCard
|
|
1734
|
+
? node.formItems.map((item) => {
|
|
1735
|
+
const key = item?.id || item?.name;
|
|
1736
|
+
if (!key) return item;
|
|
1737
|
+
return {
|
|
1738
|
+
...item,
|
|
1739
|
+
value: formValues[key],
|
|
1740
|
+
onChange: (nextValue, ...args) => {
|
|
1741
|
+
handleFormValueChange(item, nextValue);
|
|
1742
|
+
if (typeof item.onChange === 'function') item.onChange(nextValue, ...args);
|
|
1743
|
+
},
|
|
1744
|
+
};
|
|
1745
|
+
})
|
|
1746
|
+
: node.formItems;
|
|
1690
1747
|
|
|
1691
1748
|
const headerClassName = isOptionCard ? HUMAN_CONFIRM_OPTION_HEADER : HUMAN_CONFIRM_HEADER;
|
|
1692
1749
|
const iconWrapClassName = isOptionCard ? HUMAN_CONFIRM_OPTION_ICON_WRAP : HUMAN_CONFIRM_ICON_WRAP;
|
|
@@ -1770,11 +1827,12 @@ function HumanConfirmNode({ node, tokenStyles }) {
|
|
|
1770
1827
|
</RadioGroup>
|
|
1771
1828
|
) : isFormCard ? (
|
|
1772
1829
|
<Form
|
|
1773
|
-
items={
|
|
1830
|
+
items={resolvedFormItems}
|
|
1774
1831
|
layout="vertical"
|
|
1775
1832
|
size="md"
|
|
1776
1833
|
disabled={confirmed}
|
|
1777
1834
|
className="w-full min-w-0 gap-3 pb-2"
|
|
1835
|
+
onSubmit={(event) => event.preventDefault()}
|
|
1778
1836
|
data-tfds-component="ChatMessage.FormCardForm"
|
|
1779
1837
|
/>
|
|
1780
1838
|
) : node.description ? (
|