@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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/skills/tfds/CHECKLIST.md +5 -0
  3. package/skills/tfds/COMMON_FAILURES.md +48 -0
  4. package/skills/tfds/DESIGN_PRINCIPLES.md +5 -0
  5. package/skills/tfds/GLOBAL_DESIGN_RULES.md +31 -0
  6. package/skills/tfds/LAYOUT_RULES.md +31 -0
  7. package/skills/tfds/components.index.json +75 -27
  8. package/skills/tfds/components.summary.json +13 -13
  9. package/src/_b_end_runtime/components/Card.jsx +151 -13
  10. package/src/_b_end_runtime/components/Card.tokens.js +27 -3
  11. package/src/_b_end_runtime/components/CardPreview.jsx +11 -3
  12. package/src/_b_end_runtime/components/ChatMessage.jsx +59 -1
  13. package/src/_b_end_runtime/components/ConversationList.jsx +68 -68
  14. package/src/_b_end_runtime/components/ConversationList.tokens.js +5 -3
  15. package/src/_b_end_runtime/components/FullScreenPage.jsx +1 -0
  16. package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +13 -15
  17. package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +2 -0
  18. package/src/_b_end_runtime/components/Modal.jsx +1 -0
  19. package/src/_b_end_runtime/components/Sheet.jsx +1 -0
  20. package/src/_b_end_runtime/components/Table.jsx +7 -0
  21. package/src/_b_end_runtime/components/Tabs.jsx +46 -3
  22. package/src/_b_end_runtime/components/Tabs.tokens.js +3 -0
  23. package/src/_b_end_runtime/components/TagBar.jsx +2 -0
  24. package/src/_b_end_runtime/components/Toast.jsx +1 -0
  25. package/src/_b_end_runtime/components/Upload.jsx +1 -0
  26. package/src/_b_end_runtime/components.js +24 -11
  27. package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +548 -135
  28. package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +1 -1
  29. package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +6 -6
  30. package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +66 -5
  31. package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +50 -17
  32. package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +28 -78
  33. package/src/_b_end_runtime/patterns.js +32 -21
  34. package/src/_b_end_runtime/preview-registry.jsx +20 -4
  35. 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 为白底默认样式,grey 为 Blue Grey 灰底样式
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
- * 卡片容器默认半透明白底,hover 后补满白底并出现业务卡片专用投影。
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: '操作按钮', cssProp: 'component-style', value: '复用数据卡片 ACTION:28px 圆形箭头按钮 / hover 黑色描边与黑色箭头 / active 缩放;无 onAction 时禁用' },
116
- { label: '操作文案', cssProp: 'a11y-label', value: 'animatedActionText 作为圆形箭头按钮语义文案,不直接渲染为文字按钮' },
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 isData = !isProduct && !isInfo && !isInfo2;
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={node.formItems}
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 ? (