@tfdesign/b-end 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/AI_READ_FIRST.md +131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +353 -0
  4. package/package.json +67 -0
  5. package/scripts/check-tfds-contract.mjs +334 -0
  6. package/scripts/check-tfds-integration.mjs +263 -0
  7. package/scripts/postinstall-cursor-skill.mjs +382 -0
  8. package/scripts/setup.mjs +520 -0
  9. package/skills/tfds/CHECKLIST.md +205 -0
  10. package/skills/tfds/COMMON_FAILURES.md +238 -0
  11. package/skills/tfds/DESIGN_PRINCIPLES.md +477 -0
  12. package/skills/tfds/GLOBAL_DESIGN_RULES.md +636 -0
  13. package/skills/tfds/LAYOUT_RECIPES.md +140 -0
  14. package/skills/tfds/LAYOUT_RULES.md +1355 -0
  15. package/skills/tfds/PAGE_ARCHETYPES.md +201 -0
  16. package/skills/tfds/SKILL.md +188 -0
  17. package/skills/tfds/components.index.json +7305 -0
  18. package/skills/tfds/components.summary.json +1809 -0
  19. package/src/_b_end_runtime/components/AiSuggestionShared.jsx +166 -0
  20. package/src/_b_end_runtime/components/Avatar.jsx +325 -0
  21. package/src/_b_end_runtime/components/Avatar.tokens.js +76 -0
  22. package/src/_b_end_runtime/components/AvatarGridPreview.jsx +56 -0
  23. package/src/_b_end_runtime/components/AvatarGroup.jsx +80 -0
  24. package/src/_b_end_runtime/components/AvatarGroup.tokens.js +28 -0
  25. package/src/_b_end_runtime/components/Button.jsx +144 -0
  26. package/src/_b_end_runtime/components/Button.tokens.js +90 -0
  27. package/src/_b_end_runtime/components/Card.jsx +460 -0
  28. package/src/_b_end_runtime/components/Card.tokens.js +124 -0
  29. package/src/_b_end_runtime/components/CardPreview.jsx +51 -0
  30. package/src/_b_end_runtime/components/ChatBubble.jsx +384 -0
  31. package/src/_b_end_runtime/components/ChatBubble.tokens.js +60 -0
  32. package/src/_b_end_runtime/components/ChatBubblePreview.jsx +129 -0
  33. package/src/_b_end_runtime/components/ChatInput.jsx +1399 -0
  34. package/src/_b_end_runtime/components/ChatInput.tokens.js +75 -0
  35. package/src/_b_end_runtime/components/ChatMessage.jsx +2215 -0
  36. package/src/_b_end_runtime/components/ChatMessage.tokens.js +257 -0
  37. package/src/_b_end_runtime/components/ChatMessagePreview.jsx +388 -0
  38. package/src/_b_end_runtime/components/Checkbox.jsx +317 -0
  39. package/src/_b_end_runtime/components/Checkbox.tokens.js +59 -0
  40. package/src/_b_end_runtime/components/ConversationList.jsx +1264 -0
  41. package/src/_b_end_runtime/components/ConversationList.tokens.js +135 -0
  42. package/src/_b_end_runtime/components/ConversationListPreview.jsx +108 -0
  43. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +324 -0
  44. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +69 -0
  45. package/src/_b_end_runtime/components/DatePicker.jsx +739 -0
  46. package/src/_b_end_runtime/components/DatePicker.tokens.js +99 -0
  47. package/src/_b_end_runtime/components/Empty.jsx +141 -0
  48. package/src/_b_end_runtime/components/Empty.tokens.js +40 -0
  49. package/src/_b_end_runtime/components/Form.jsx +609 -0
  50. package/src/_b_end_runtime/components/Form.tokens.js +77 -0
  51. package/src/_b_end_runtime/components/FormFieldStack.jsx +123 -0
  52. package/src/_b_end_runtime/components/FormFieldStack.tokens.js +12 -0
  53. package/src/_b_end_runtime/components/FormTitle.jsx +119 -0
  54. package/src/_b_end_runtime/components/FormTitle.tokens.js +87 -0
  55. package/src/_b_end_runtime/components/FullScreenPage.jsx +97 -0
  56. package/src/_b_end_runtime/components/FullScreenPage.tokens.js +19 -0
  57. package/src/_b_end_runtime/components/Icon.jsx +172 -0
  58. package/src/_b_end_runtime/components/Icon.tokens.js +26 -0
  59. package/src/_b_end_runtime/components/IconGridPreview.jsx +277 -0
  60. package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +620 -0
  61. package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +71 -0
  62. package/src/_b_end_runtime/components/InfoDisplayPanelPreview.jsx +133 -0
  63. package/src/_b_end_runtime/components/Input.jsx +258 -0
  64. package/src/_b_end_runtime/components/Input.tokens.js +68 -0
  65. package/src/_b_end_runtime/components/InputNumber.jsx +242 -0
  66. package/src/_b_end_runtime/components/InputNumber.tokens.js +55 -0
  67. package/src/_b_end_runtime/components/Modal.jsx +155 -0
  68. package/src/_b_end_runtime/components/Modal.tokens.js +73 -0
  69. package/src/_b_end_runtime/components/NavBar.jsx +842 -0
  70. package/src/_b_end_runtime/components/NavBar.tokens.js +97 -0
  71. package/src/_b_end_runtime/components/NavBarPreview.jsx +11 -0
  72. package/src/_b_end_runtime/components/Radio.jsx +227 -0
  73. package/src/_b_end_runtime/components/Radio.tokens.js +59 -0
  74. package/src/_b_end_runtime/components/Select.jsx +766 -0
  75. package/src/_b_end_runtime/components/Select.tokens.js +99 -0
  76. package/src/_b_end_runtime/components/Sheet.jsx +132 -0
  77. package/src/_b_end_runtime/components/Sheet.tokens.js +61 -0
  78. package/src/_b_end_runtime/components/Slider.jsx +346 -0
  79. package/src/_b_end_runtime/components/Slider.tokens.js +47 -0
  80. package/src/_b_end_runtime/components/Switch.jsx +124 -0
  81. package/src/_b_end_runtime/components/Switch.tokens.js +38 -0
  82. package/src/_b_end_runtime/components/Table.jsx +1338 -0
  83. package/src/_b_end_runtime/components/Table.tokens.js +147 -0
  84. package/src/_b_end_runtime/components/TablePreview.jsx +599 -0
  85. package/src/_b_end_runtime/components/Tabs.jsx +149 -0
  86. package/src/_b_end_runtime/components/Tabs.tokens.js +102 -0
  87. package/src/_b_end_runtime/components/Tag.jsx +199 -0
  88. package/src/_b_end_runtime/components/Tag.tokens.js +171 -0
  89. package/src/_b_end_runtime/components/TagBar.jsx +1134 -0
  90. package/src/_b_end_runtime/components/TagBar.tokens.js +75 -0
  91. package/src/_b_end_runtime/components/TagGridPreview.jsx +23 -0
  92. package/src/_b_end_runtime/components/TagInput.jsx +382 -0
  93. package/src/_b_end_runtime/components/TagInput.tokens.js +52 -0
  94. package/src/_b_end_runtime/components/TextArea.jsx +363 -0
  95. package/src/_b_end_runtime/components/TextArea.tokens.js +65 -0
  96. package/src/_b_end_runtime/components/TimePicker.jsx +444 -0
  97. package/src/_b_end_runtime/components/TimePicker.tokens.js +77 -0
  98. package/src/_b_end_runtime/components/Toast.jsx +120 -0
  99. package/src/_b_end_runtime/components/Toast.tokens.js +146 -0
  100. package/src/_b_end_runtime/components/Tooltip.jsx +282 -0
  101. package/src/_b_end_runtime/components/Tooltip.tokens.js +48 -0
  102. package/src/_b_end_runtime/components/TooltipPreview.jsx +50 -0
  103. package/src/_b_end_runtime/components/Upload.jsx +455 -0
  104. package/src/_b_end_runtime/components/Upload.tokens.js +47 -0
  105. package/src/_b_end_runtime/components/avatar-assets/avatar-default.png +0 -0
  106. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-1.png +0 -0
  107. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-2.png +0 -0
  108. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-3.png +0 -0
  109. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-4.png +0 -0
  110. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-5.png +0 -0
  111. package/src/_b_end_runtime/components/empty-assets/administrator-1.svg +40 -0
  112. package/src/_b_end_runtime/components/empty-assets/administrator-2.svg +33 -0
  113. package/src/_b_end_runtime/components/empty-assets/construction.svg +33 -0
  114. package/src/_b_end_runtime/components/empty-assets/failure.svg +49 -0
  115. package/src/_b_end_runtime/components/empty-assets/idle.svg +34 -0
  116. package/src/_b_end_runtime/components/empty-assets/no-access.svg +36 -0
  117. package/src/_b_end_runtime/components/empty-assets/no-content.svg +77 -0
  118. package/src/_b_end_runtime/components/empty-assets/no-result.svg +61 -0
  119. package/src/_b_end_runtime/components/empty-assets/not-found.svg +46 -0
  120. package/src/_b_end_runtime/components/empty-assets/success.svg +38 -0
  121. package/src/_b_end_runtime/components/file-type-assets/batch-report.png +0 -0
  122. package/src/_b_end_runtime/components/file-type-assets/catcat.svg +21 -0
  123. package/src/_b_end_runtime/components/file-type-assets/code.png +0 -0
  124. package/src/_b_end_runtime/components/file-type-assets/conversation.png +0 -0
  125. package/src/_b_end_runtime/components/file-type-assets/document.png +0 -0
  126. package/src/_b_end_runtime/components/file-type-assets/feishu-card.png +0 -0
  127. package/src/_b_end_runtime/components/file-type-assets/feishu-sheet.png +0 -0
  128. package/src/_b_end_runtime/components/file-type-assets/feishu.png +0 -0
  129. package/src/_b_end_runtime/components/file-type-assets/image.png +0 -0
  130. package/src/_b_end_runtime/components/file-type-assets/index.js +105 -0
  131. package/src/_b_end_runtime/components/file-type-assets/knowledge.png +0 -0
  132. package/src/_b_end_runtime/components/file-type-assets/pdf.png +0 -0
  133. package/src/_b_end_runtime/components/file-type-assets/pe.png +0 -0
  134. package/src/_b_end_runtime/components/file-type-assets/strategy.png +0 -0
  135. package/src/_b_end_runtime/components/file-type-assets/table.png +0 -0
  136. package/src/_b_end_runtime/components/file-type-assets/webpage.png +0 -0
  137. package/src/_b_end_runtime/components/file-type-assets/xmind.png +0 -0
  138. package/src/_b_end_runtime/components/icons/icon-data.js +12496 -0
  139. package/src/_b_end_runtime/components/nav-bar-assets/bytehi-logo-mark.svg +21 -0
  140. package/src/_b_end_runtime/components/table-assets/avatar.png +0 -0
  141. package/src/_b_end_runtime/components/table-assets/button.png +0 -0
  142. package/src/_b_end_runtime/components/table-assets/icon-chevron-down.png +0 -0
  143. package/src/_b_end_runtime/components/table-cell-assets/avatar.png +0 -0
  144. package/src/_b_end_runtime/components/table-cell-assets/button.png +0 -0
  145. package/src/_b_end_runtime/components/table-cell-assets/checkbox.png +0 -0
  146. package/src/_b_end_runtime/components/table-cell-assets/icon-chevron-right.png +0 -0
  147. package/src/_b_end_runtime/components/table-cell-assets/icon.png +0 -0
  148. package/src/_b_end_runtime/components/table-cell-assets/semi-icons-handle.png +0 -0
  149. package/src/_b_end_runtime/components/table-cell-assets/semi-icons-tree-triangle-right.png +0 -0
  150. package/src/_b_end_runtime/components/table-cell-assets/switch.png +0 -0
  151. package/src/_b_end_runtime/components/tagShared.js +3 -0
  152. package/src/_b_end_runtime/components/team-avatar-assets/chengcheng-murphy.png +0 -0
  153. package/src/_b_end_runtime/components/team-avatar-assets/duan-ran.png +0 -0
  154. package/src/_b_end_runtime/components/team-avatar-assets/guo-zhezhi.png +0 -0
  155. package/src/_b_end_runtime/components/team-avatar-assets/li-siru.png +0 -0
  156. package/src/_b_end_runtime/components/team-avatar-assets/liu-delin.png +0 -0
  157. package/src/_b_end_runtime/components.js +3499 -0
  158. package/src/_b_end_runtime/index.js +9 -0
  159. package/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx +395 -0
  160. package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +989 -0
  161. package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +281 -0
  162. package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +380 -0
  163. package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +392 -0
  164. package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +590 -0
  165. package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +237 -0
  166. package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +189 -0
  167. package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +594 -0
  168. package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +87 -0
  169. package/src/_b_end_runtime/page-patterns/pageListShared.jsx +177 -0
  170. package/src/_b_end_runtime/patterns.js +428 -0
  171. package/src/_b_end_runtime/preview-registry.jsx +4719 -0
  172. package/src/_b_end_runtime/teamMembers.js +56 -0
  173. package/src/_b_end_runtime/tokens.js +500 -0
  174. package/src/index.d.ts +1073 -0
  175. package/src/index.js +52 -0
  176. package/theme.css +350 -0
@@ -0,0 +1,392 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import CustomerServiceWorkspaceFrame from '../components/CustomerServiceWorkspaceFrame';
3
+ import ConversationList, { CONVERSATION_LIST_SAMPLE_SECTIONS } from '../components/ConversationList';
4
+ import InfoDisplayPanel from '../components/InfoDisplayPanel';
5
+ import { getTeamMemberByName } from '../teamMembers';
6
+ import IMConversationPattern from './IMConversationPattern';
7
+
8
+ const PANEL_OVERLAP = 32;
9
+ const DEFAULT_SIDE_WIDTH = 432;
10
+ const AVATAR_ONLY_WIDTH = 88;
11
+ const CARD_MIN_WIDTH = 333;
12
+ const MAIN_PANEL_MIN_WIDTH = 720;
13
+ const MAIN_CONTENT_GAP = 16;
14
+ const IM_PANEL_MIN_WIDTH = 360;
15
+ const INFO_PANEL_MIN_WIDTH = 320;
16
+ const INFO_PANEL_DEFAULT_WIDTH = 380;
17
+ const MAIN_PANEL_CONTENT = [
18
+ 'relative flex size-full min-h-0 min-w-0 items-stretch gap-4 p-4',
19
+ ].join(' ');
20
+ const IM_PANEL = 'flex min-w-0 flex-1 overflow-hidden rounded-xl bg-surface';
21
+ const INFO_PANEL_FRAME = 'relative flex h-full min-w-0 shrink-0 items-stretch overflow-visible';
22
+ const INFO_PANEL_RESIZE_HANDLE = [
23
+ 'group/workspace-info-resize absolute inset-y-0 left-0 z-20 flex w-2 -translate-x-1/2 cursor-col-resize items-stretch justify-center border-0 bg-transparent p-0',
24
+ 'touch-none select-none',
25
+ 'focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-blueGrey-400',
26
+ ].join(' ');
27
+ const INFO_PANEL_RESIZE_HANDLE_LINE = [
28
+ 'my-4 w-px rounded-full bg-transparent transition-colors duration-150',
29
+ 'group-hover/workspace-info-resize:bg-brand-400 group-focus-visible/workspace-info-resize:bg-brand-500 group-active/workspace-info-resize:bg-brand-500',
30
+ ].join(' ');
31
+ const WORKSPACE_CONVERSATION_SECTIONS = CONVERSATION_LIST_SAMPLE_SECTIONS;
32
+ const DEFAULT_CONVERSATION_ID = 'pending-1';
33
+ const AGENT_AVATAR = getTeamMemberByName('段然');
34
+ const SCRIPT_DATE = '2026-04-16';
35
+
36
+ function ts(hhmm, ss = '00') {
37
+ return `${SCRIPT_DATE} ${hhmm}:${ss}`;
38
+ }
39
+
40
+ const WORKSPACE_THREAD_CONTENT = {
41
+ 'pending-1': {
42
+ tagLabel: '待干预',
43
+ channel: '抖音生活服务',
44
+ duration: '00:18:57',
45
+ messages: [
46
+ { id: 'pending-1-s1', kind: 'system', text: '已接入AI智能客服', time: ts('14:01') },
47
+ { id: 'pending-1-u1', kind: 'user', text: '我买了海底捞西单店的 2 人餐,到店以后店员说券码暂时核销不了,能帮我看下吗', time: ts('14:02', '12') },
48
+ { id: 'pending-1-b1', kind: 'bot', text: '您好,我是抖音生活服务助手,正在为您查询团购券状态…', time: ts('14:02', '25') },
49
+ { id: 'pending-1-b2', kind: 'bot', text: '请问可以提供下券码或订单号吗?支持直接复制粘贴。', time: ts('14:02', '38') },
50
+ { id: 'pending-1-u2', kind: 'user', text: 'LS20260423-HDL998877,券码尾号 8877。', time: ts('14:03', '04') },
51
+ { id: 'pending-1-b3', kind: 'bot', text: '已查到该订单为“海底捞火锅 2 人餐”,当前券码状态显示待核销,适用门店包含西单大悦城店。', time: ts('14:03', '28') },
52
+ { id: 'pending-1-u3', kind: 'user', text: '朋友都到了,等太久体验很差,如果不能用是不是只能退款?', time: ts('14:04', '03') },
53
+ { id: 'pending-1-b4', kind: 'bot', text: '我会优先帮您确认门店核销链路;如确认为门店同步延迟,可由商家手动核验订单。', time: ts('14:04', '18') },
54
+ { id: 'pending-1-u4', kind: 'user', text: '那你先帮我联系门店吧,不行就转人工处理。', time: ts('14:05', '26') },
55
+ { id: 'pending-1-s2', kind: 'system', text: '已接入人工客服', time: ts('14:06', '32') },
56
+ { id: 'pending-1-a1', kind: 'agent', text: '您好,我是人工客服小语,已看到您的海底捞团购订单。门店反馈当前收银端券码同步延迟,正在手动核验。', time: ts('14:06', '48') },
57
+ { id: 'pending-1-a2', kind: 'agent', text: '我已备注您正在到店等位,并同步门店优先处理;若核销仍失败,会为您申请等位补偿或原路退款。', time: ts('14:07', '02') },
58
+ { id: 'pending-1-u5', kind: 'user', text: '好的,麻烦快一点,我这边还在等叫号。', time: ts('14:07', '18') },
59
+ { id: 'pending-1-a3', kind: 'agent', text: '没问题,我会持续跟进西单店反馈。若 5 分钟内未解决,我会直接帮您升级商家值班人员处理。', time: ts('14:07', '36') },
60
+ ],
61
+ },
62
+ 'pending-2': {
63
+ tagLabel: '待干预',
64
+ channel: '抖音商城',
65
+ duration: '00:12:54',
66
+ messages: [
67
+ { id: 'pending-2-s1', kind: 'system', text: '已接入AI智能客服', time: ts('12:45') },
68
+ { id: 'pending-2-u1', kind: 'user', text: '我这单一直显示待发货,已经超过承诺时间了,什么时候能发?', time: ts('12:46', '10') },
69
+ { id: 'pending-2-b1', kind: 'bot', text: '您好,我正在为您查询订单发货节点和商家履约承诺。', time: ts('12:46', '24') },
70
+ { id: 'pending-2-b2', kind: 'bot', text: '订单 2918575148391024 当前仍在仓库拣货,系统显示已触发催发提醒。', time: ts('12:46', '58') },
71
+ { id: 'pending-2-u2', kind: 'user', text: '页面说今天前发货,现在还没动静,我明天就要用。', time: ts('12:48', '06') },
72
+ { id: 'pending-2-b3', kind: 'bot', text: '理解您的着急,我先为您转人工核对仓库和商家侧的实际处理进度。', time: ts('12:48', '32') },
73
+ { id: 'pending-2-s2', kind: 'system', text: '已接入人工客服', time: ts('12:49', '04') },
74
+ { id: 'pending-2-a1', kind: 'agent', text: '您好,我已看到您的发货进度咨询。当前订单已进入出库队列,但物流单号还未回传。', time: ts('12:49', '20') },
75
+ { id: 'pending-2-a2', kind: 'agent', text: '我会帮您向商家发起加急催发,并要求在 2 小时内反馈是否能按时寄出。', time: ts('12:50', '01') },
76
+ { id: 'pending-2-u3', kind: 'user', text: '如果今天发不了可以直接退款吗?', time: ts('12:51', '16') },
77
+ { id: 'pending-2-a3', kind: 'agent', text: '可以。如果商家确认今日无法发出,我会协助您走未按约发货退款流程,并同步保留催发记录。', time: ts('12:51', '45') },
78
+ ],
79
+ },
80
+ 'pending-3': {
81
+ tagLabel: '待干预',
82
+ channel: '抖音生活服务',
83
+ duration: '00:09:41',
84
+ messages: [
85
+ { id: 'pending-3-s1', kind: 'system', text: '已接入AI智能客服', time: ts('12:32') },
86
+ { id: 'pending-3-u1', kind: 'user', text: '我领的优惠券结算时用不了,页面一直提示核销失败。', time: ts('12:33', '11') },
87
+ { id: 'pending-3-b1', kind: 'bot', text: '您好,我正在核对优惠券状态、适用范围和当前订单金额。', time: ts('12:33', '28') },
88
+ { id: 'pending-3-b2', kind: 'bot', text: '券码记录显示未使用,但当前订单命中的活动规则存在同步异常。', time: ts('12:34', '02') },
89
+ { id: 'pending-3-u2', kind: 'user', text: '我已经重新进了好几次,还是不行,活动马上结束了。', time: ts('12:35', '14') },
90
+ { id: 'pending-3-b3', kind: 'bot', text: '我先帮您保留当前活动权益,并转人工确认是否可补发同等金额优惠。', time: ts('12:35', '33') },
91
+ { id: 'pending-3-s2', kind: 'system', text: '已接入人工客服', time: ts('12:36', '05') },
92
+ { id: 'pending-3-a1', kind: 'agent', text: '您好,我已看到优惠券核销失败记录。当前问题来自活动规则同步延迟,不是您操作导致。', time: ts('12:36', '18') },
93
+ { id: 'pending-3-a2', kind: 'agent', text: '我会先为您登记权益补偿,若原券无法恢复,会补发一张同面额可用券到您的账号。', time: ts('12:37', '01') },
94
+ { id: 'pending-3-u3', kind: 'user', text: '那我今天还能下单吗?', time: ts('12:38', '12') },
95
+ { id: 'pending-3-a3', kind: 'agent', text: '可以,我会在登记中备注“活动期内核销异常”,补偿券会按当前活动权益处理。', time: ts('12:38', '40') },
96
+ ],
97
+ },
98
+ 'managed-1': {
99
+ tagLabel: '托管中',
100
+ channel: '抖音商城',
101
+ duration: '24:30:00',
102
+ messages: [
103
+ { id: 'managed-1-s1', kind: 'system', text: '已进入托管跟进', time: ts('11:06') },
104
+ { id: 'managed-1-u1', kind: 'user', text: '我的包裹物流一直停在转运中心,没有新的更新。', time: ts('11:07', '18') },
105
+ { id: 'managed-1-b1', kind: 'bot', text: '您好,我正在核对订单 2918575148472390 的承运商轨迹。', time: ts('11:07', '35') },
106
+ { id: 'managed-1-b2', kind: 'bot', text: '系统识别到该包裹已超过常规中转时效,建议转人工托管跟进。', time: ts('11:08', '04') },
107
+ { id: 'managed-1-s2', kind: 'system', text: '已接入人工客服', time: ts('11:08', '28') },
108
+ { id: 'managed-1-a1', kind: 'agent', text: '您好,我已看到物流状态异常记录。当前包裹卡在华东转运中心,我会帮您向承运商发起异常件核查。', time: ts('11:09', '12') },
109
+ { id: 'managed-1-u2', kind: 'user', text: '我还需要做什么吗?会不会丢件?', time: ts('11:10', '19') },
110
+ { id: 'managed-1-a2', kind: 'agent', text: '您暂时不需要额外操作。我会持续托管这个会话,承运商反馈前不会让工单自动关闭。', time: ts('11:10', '42') },
111
+ { id: 'managed-1-a3', kind: 'agent', text: '若 24 小时内仍无轨迹更新,我会为您同步发起补寄或退款方案确认。', time: ts('11:11', '18') },
112
+ ],
113
+ },
114
+ 'managed-2': {
115
+ tagLabel: '托管中',
116
+ channel: '抖音商城',
117
+ duration: '00:10:18',
118
+ messages: [
119
+ { id: 'managed-2-s1', kind: 'system', text: '已进入退款托管', time: ts('10:08') },
120
+ { id: 'managed-2-u1', kind: 'user', text: '商家说可以退,但我不确定退款金额是不是全额。', time: ts('10:09', '06') },
121
+ { id: 'managed-2-b1', kind: 'bot', text: '我正在核对订单 2918575148421185 的支付金额、优惠抵扣和售后方案。', time: ts('10:09', '24') },
122
+ { id: 'managed-2-b2', kind: 'bot', text: '当前售后记录显示商家拟同意退款,但需要用户确认退回路径。', time: ts('10:09', '58') },
123
+ { id: 'managed-2-s2', kind: 'system', text: '已接入人工客服', time: ts('10:10', '16') },
124
+ { id: 'managed-2-a1', kind: 'agent', text: '您好,我已看到退款方案。该订单实付金额 128 元,商家同意按实付全额原路退回。', time: ts('10:10', '41') },
125
+ { id: 'managed-2-u2', kind: 'user', text: '优惠券会退回来吗?', time: ts('10:11', '23') },
126
+ { id: 'managed-2-a2', kind: 'agent', text: '本单使用的优惠券属于限时活动券,退款后不会恢复;我已帮您申请同等权益补偿券。', time: ts('10:11', '52') },
127
+ { id: 'managed-2-u3', kind: 'user', text: '那就按这个方案处理吧。', time: ts('10:12', '20') },
128
+ { id: 'managed-2-a3', kind: 'agent', text: '好的,我已记录您确认退款方案,后续退款进度会继续在本会话同步。', time: ts('10:12', '43') },
129
+ ],
130
+ },
131
+ 'managed-3': {
132
+ tagLabel: '托管中',
133
+ channel: '抖音商城',
134
+ duration: '00:09:46',
135
+ messages: [
136
+ { id: 'managed-3-s1', kind: 'system', text: '已进入售后凭证核验', time: ts('09:37') },
137
+ { id: 'managed-3-u1', kind: 'user', text: '商家让我补凭证,但我不知道还缺什么材料。', time: ts('09:38', '08') },
138
+ { id: 'managed-3-b1', kind: 'bot', text: '我正在查看售后凭证要求和您已上传的图片。', time: ts('09:38', '30') },
139
+ { id: 'managed-3-b2', kind: 'bot', text: '当前缺少商品外包装破损位置的清晰照片,建议转人工说明补传范围。', time: ts('09:39', '02') },
140
+ { id: 'managed-3-s2', kind: 'system', text: '已接入人工客服', time: ts('09:39', '20') },
141
+ { id: 'managed-3-a1', kind: 'agent', text: '您好,我已查看您提交的售后凭证。目前商家需要补充外包装破损处和商品批次码照片。', time: ts('09:39', '48') },
142
+ { id: 'managed-3-u2', kind: 'user', text: '我可以现在拍,是不是拍了就能继续处理?', time: ts('09:40', '30') },
143
+ { id: 'managed-3-a2', kind: 'agent', text: '是的。您补传后我会继续托管跟进,并提醒商家在 24 小时内完成复核。', time: ts('09:40', '58') },
144
+ { id: 'managed-3-a3', kind: 'agent', text: '如果商家仍以凭证不足拒绝,我会帮您整理平台介入所需材料。', time: ts('09:41', '20') },
145
+ ],
146
+ },
147
+ 'other-1': {
148
+ tagLabel: '已处理',
149
+ channel: '抖音商城',
150
+ duration: '00:06:12',
151
+ messages: [
152
+ { id: 'other-1-s1', kind: 'system', text: '订单已完成,发起回访', time: ts('16:02') },
153
+ { id: 'other-1-a1', kind: 'agent', text: '您好,看到您之前咨询的订单 2918575148499712 已处理完成,想和您确认一下结果是否符合预期。', time: ts('16:02', '18') },
154
+ { id: 'other-1-u1', kind: 'user', text: '已经收到退款了,处理结果可以。', time: ts('16:03', '01') },
155
+ { id: 'other-1-a2', kind: 'agent', text: '感谢确认。后续如商家再次联系您补充材料,可以直接在本会话回复,我会继续协助。', time: ts('16:03', '24') },
156
+ { id: 'other-1-u2', kind: 'user', text: '好的,谢谢。', time: ts('16:04', '06') },
157
+ { id: 'other-1-a3', kind: 'agent', text: '不客气,祝您生活愉快。', time: ts('16:04', '22') },
158
+ ],
159
+ },
160
+ 'other-2': {
161
+ tagLabel: '已处理',
162
+ channel: '抖音生活服务',
163
+ duration: '00:05:40',
164
+ messages: [
165
+ { id: 'other-2-s1', kind: 'system', text: '门店服务评价回访', time: ts('15:12') },
166
+ { id: 'other-2-u1', kind: 'user', text: '我给门店打了低分,主要是排队时间太长。', time: ts('15:13', '08') },
167
+ { id: 'other-2-b1', kind: 'bot', text: '您好,我已收到您的门店服务评价反馈,正在核对门店履约记录。', time: ts('15:13', '22') },
168
+ { id: 'other-2-s2', kind: 'system', text: '已接入人工客服', time: ts('15:14', '02') },
169
+ { id: 'other-2-a1', kind: 'agent', text: '您好,我已看到您对门店排队时间的反馈。平台会将该问题同步给门店运营侧核查。', time: ts('15:14', '25') },
170
+ { id: 'other-2-u2', kind: 'user', text: '我不是要退款,就是希望后面别这样。', time: ts('15:15', '01') },
171
+ { id: 'other-2-a2', kind: 'agent', text: '理解,已按服务体验问题记录,不会发起退款流程。后续门店整改反馈会在评价治理记录中留存。', time: ts('15:15', '30') },
172
+ ],
173
+ },
174
+ };
175
+
176
+ function clamp(value, min, max) {
177
+ return Math.min(max, Math.max(min, value));
178
+ }
179
+
180
+ function getConversationItems(sections) {
181
+ return (sections || []).flatMap((section) => section.items || []);
182
+ }
183
+
184
+ function findConversationItem(itemId) {
185
+ return getConversationItems(WORKSPACE_CONVERSATION_SECTIONS).find((item) => item.id === itemId);
186
+ }
187
+
188
+ function getPrimaryTagLabel(item, fallback = '') {
189
+ const tags = Array.isArray(item?.tags) ? item.tags : [];
190
+ const primaryTag = [...tags].reverse().find((tag) => ['red', 'green', 'blue', 'orange'].includes(tag?.variant));
191
+ return primaryTag?.label || tags[0]?.label || fallback;
192
+ }
193
+
194
+ function buildThreadContext(item) {
195
+ const resolvedItem = item || findConversationItem(DEFAULT_CONVERSATION_ID);
196
+ const content = WORKSPACE_THREAD_CONTENT[resolvedItem?.id] || {};
197
+
198
+ return {
199
+ id: resolvedItem?.id || DEFAULT_CONVERSATION_ID,
200
+ title: resolvedItem?.title || '当前会话',
201
+ tagLabel: content.tagLabel || getPrimaryTagLabel(resolvedItem),
202
+ userId: resolvedItem?.orderId || '未知会话',
203
+ channel: content.channel || '抖音',
204
+ duration: content.duration || '00:00:00',
205
+ userName: resolvedItem?.userName || '用户',
206
+ userAvatarSrc: resolvedItem?.avatarSrc,
207
+ agentName: AGENT_AVATAR?.name || '客服',
208
+ agentAvatarSrc: AGENT_AVATAR?.avatarSrc,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * CustomerServiceWorkspaceFramePattern — 客服工作台框架
214
+ *
215
+ * 页面级模板:顶部在线 Agent 状态栏 + 指标工具区 + 基础/托管模式切换
216
+ * + 半透明工作区 + 右侧主白卡。模板只定义框架,业务内容通过插槽填充。
217
+ * 在客服工作台语义里,左侧会话列表是当前处理上下文选择器;
218
+ * 右侧主白卡(IM 对话区 + InfoDisplayPanel 信息区)应整体跟随左侧当前选中会话联动变化。
219
+ */
220
+
221
+ export default function CustomerServiceWorkspaceFramePattern() {
222
+ const mainPanelRef = useRef(null);
223
+ const [sideWidth, setSideWidth] = useState(DEFAULT_SIDE_WIDTH);
224
+ const [leftContentMinWidth, setLeftContentMinWidth] = useState(AVATAR_ONLY_WIDTH);
225
+ const [mainPanelWidth, setMainPanelWidth] = useState(0);
226
+ const [infoPanelWidth, setInfoPanelWidth] = useState(INFO_PANEL_DEFAULT_WIDTH);
227
+ const [activeConversationId, setActiveConversationId] = useState(DEFAULT_CONVERSATION_ID);
228
+ const activeConversationItem = useMemo(
229
+ () => findConversationItem(activeConversationId) || findConversationItem(DEFAULT_CONVERSATION_ID),
230
+ [activeConversationId],
231
+ );
232
+ const activeThread = useMemo(
233
+ () => buildThreadContext(activeConversationItem),
234
+ [activeConversationItem],
235
+ );
236
+ const activeMessages = WORKSPACE_THREAD_CONTENT[activeThread.id]?.messages || [];
237
+
238
+ const handleConversationLayoutWidthRequest = useCallback((contentWidth) => {
239
+ setSideWidth(contentWidth + PANEL_OVERLAP);
240
+ }, []);
241
+
242
+ const handleConversationVariantChange = useCallback((nextVariant) => {
243
+ const nextMinWidth = nextVariant === 'card' ? CARD_MIN_WIDTH : AVATAR_ONLY_WIDTH;
244
+ setLeftContentMinWidth(nextMinWidth);
245
+ setSideWidth((currentWidth) => Math.max(currentWidth, nextMinWidth + PANEL_OVERLAP));
246
+ }, []);
247
+
248
+ useEffect(() => {
249
+ const node = mainPanelRef.current;
250
+ if (!node) return undefined;
251
+
252
+ function updateWidth() {
253
+ setMainPanelWidth(node.getBoundingClientRect().width);
254
+ }
255
+
256
+ updateWidth();
257
+ const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateWidth) : null;
258
+ observer?.observe(node);
259
+ window.addEventListener('resize', updateWidth);
260
+
261
+ return () => {
262
+ observer?.disconnect();
263
+ window.removeEventListener('resize', updateWidth);
264
+ };
265
+ }, []);
266
+
267
+ const maxInfoPanelWidth = Number.isFinite(mainPanelWidth) && mainPanelWidth > 0
268
+ ? Math.max(INFO_PANEL_MIN_WIDTH, mainPanelWidth - MAIN_CONTENT_GAP - IM_PANEL_MIN_WIDTH)
269
+ : INFO_PANEL_DEFAULT_WIDTH;
270
+ const currentInfoPanelWidth = clamp(infoPanelWidth, INFO_PANEL_MIN_WIDTH, maxInfoPanelWidth);
271
+
272
+ useEffect(() => {
273
+ setInfoPanelWidth((currentWidth) => clamp(currentWidth, INFO_PANEL_MIN_WIDTH, maxInfoPanelWidth));
274
+ }, [maxInfoPanelWidth]);
275
+
276
+ const updateInfoPanelWidth = useCallback((nextWidth) => {
277
+ setInfoPanelWidth(clamp(nextWidth, INFO_PANEL_MIN_WIDTH, maxInfoPanelWidth));
278
+ }, [maxInfoPanelWidth]);
279
+
280
+ const handleInfoPanelResizePointerDown = useCallback((event) => {
281
+ event.preventDefault();
282
+ const startX = event.clientX;
283
+ const startWidth = currentInfoPanelWidth;
284
+ const previousCursor = document.body.style.cursor;
285
+ const previousUserSelect = document.body.style.userSelect;
286
+ document.body.style.cursor = 'col-resize';
287
+ document.body.style.userSelect = 'none';
288
+
289
+ function handlePointerMove(moveEvent) {
290
+ const delta = moveEvent.clientX - startX;
291
+ updateInfoPanelWidth(startWidth - delta);
292
+ }
293
+
294
+ function handlePointerUp() {
295
+ document.body.style.cursor = previousCursor;
296
+ document.body.style.userSelect = previousUserSelect;
297
+ window.removeEventListener('pointermove', handlePointerMove);
298
+ window.removeEventListener('pointerup', handlePointerUp);
299
+ }
300
+
301
+ window.addEventListener('pointermove', handlePointerMove);
302
+ window.addEventListener('pointerup', handlePointerUp);
303
+ }, [currentInfoPanelWidth, updateInfoPanelWidth]);
304
+
305
+ const handleInfoPanelResizeKeyDown = useCallback((event) => {
306
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && event.key !== 'Home' && event.key !== 'End') return;
307
+ event.preventDefault();
308
+
309
+ if (event.key === 'Home') {
310
+ updateInfoPanelWidth(maxInfoPanelWidth);
311
+ return;
312
+ }
313
+
314
+ if (event.key === 'End') {
315
+ updateInfoPanelWidth(INFO_PANEL_MIN_WIDTH);
316
+ return;
317
+ }
318
+
319
+ const delta = event.shiftKey ? 32 : 16;
320
+ updateInfoPanelWidth(currentInfoPanelWidth + (event.key === 'ArrowLeft' ? delta : -delta));
321
+ }, [currentInfoPanelWidth, maxInfoPanelWidth, updateInfoPanelWidth]);
322
+
323
+ const leftPanel = (
324
+ <ConversationList
325
+ title="会话列表"
326
+ sections={WORKSPACE_CONVERSATION_SECTIONS}
327
+ defaultActiveTab="all"
328
+ activeItemId={activeThread.id}
329
+ onItemClick={(item) => setActiveConversationId(item.id)}
330
+ resizable={false}
331
+ collapsible
332
+ autoCollapseOnNarrow
333
+ onLayoutWidthRequest={handleConversationLayoutWidthRequest}
334
+ onVariantChange={handleConversationVariantChange}
335
+ className="h-full"
336
+ style={{ width: '100%', height: '100%' }}
337
+ />
338
+ );
339
+
340
+ const mainPanel = (
341
+ <div ref={mainPanelRef} className={MAIN_PANEL_CONTENT}>
342
+ <div className={IM_PANEL}>
343
+ <IMConversationPattern
344
+ key={activeThread.id}
345
+ className="h-full"
346
+ style={{ height: '100%' }}
347
+ thread={activeThread}
348
+ messages={activeMessages}
349
+ />
350
+ </div>
351
+ <div className={INFO_PANEL_FRAME} style={{ width: `${currentInfoPanelWidth}px` }}>
352
+ <button
353
+ type="button"
354
+ className={INFO_PANEL_RESIZE_HANDLE}
355
+ aria-label="调整右侧信息面板宽度"
356
+ aria-orientation="vertical"
357
+ aria-valuemin={INFO_PANEL_MIN_WIDTH}
358
+ aria-valuemax={Math.round(maxInfoPanelWidth)}
359
+ aria-valuenow={Math.round(currentInfoPanelWidth)}
360
+ onPointerDown={handleInfoPanelResizePointerDown}
361
+ onKeyDown={handleInfoPanelResizeKeyDown}
362
+ >
363
+ <span className={INFO_PANEL_RESIZE_HANDLE_LINE} aria-hidden="true" />
364
+ </button>
365
+ <InfoDisplayPanel
366
+ className="!h-full w-full"
367
+ style={{ height: '100%' }}
368
+ />
369
+ </div>
370
+ </div>
371
+ );
372
+
373
+ return (
374
+ <div
375
+ className="flex h-full min-h-0 min-w-0 w-full items-stretch overflow-hidden"
376
+ style={{
377
+ background: 'var(--color-blueGrey-200, #F2F4F7)',
378
+ borderRadius: 0,
379
+ }}
380
+ >
381
+ <CustomerServiceWorkspaceFrame
382
+ sideWidth={sideWidth}
383
+ minSideWidth={100}
384
+ leftContentMinWidth={leftContentMinWidth}
385
+ mainMinWidth={MAIN_PANEL_MIN_WIDTH}
386
+ onSideWidthChange={setSideWidth}
387
+ leftPanel={leftPanel}
388
+ mainPanel={mainPanel}
389
+ />
390
+ </div>
391
+ );
392
+ }