@tfdesign/b-end 1.1.4 → 1.1.6
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 +9 -5
- package/skills/tfds/GLOBAL_DESIGN_RULES.md +22 -2
- package/skills/tfds/LAYOUT_RECIPES.md +6 -3
- package/skills/tfds/LAYOUT_RULES.md +99 -39
- package/skills/tfds/PAGE_ARCHETYPES.md +9 -5
- package/skills/tfds/SKILL.md +8 -4
- package/skills/tfds/components.index.json +80 -59
- package/skills/tfds/components.summary.json +24 -24
- package/src/_b_end_runtime/components/Filter.jsx +11 -3
- package/src/_b_end_runtime/components/Filter.tokens.js +3 -0
- package/src/_b_end_runtime/components/FormTitle.jsx +2 -2
- package/src/_b_end_runtime/components/FormTitle.tokens.js +1 -1
- package/src/_b_end_runtime/components/FullScreenPage.jsx +2 -2
- package/src/_b_end_runtime/components/FullScreenPage.tokens.js +6 -2
- package/src/_b_end_runtime/components/TagBar.jsx +2 -142
- package/src/_b_end_runtime/components/TagBar.tokens.js +3 -3
- package/src/_b_end_runtime/components.js +103 -41
- package/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx +10 -6
- package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +1 -1
- package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +1 -1
- package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +1 -1
- package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +1 -1
- package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +1 -1
- package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +186 -37
- package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +3 -3
- package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +5 -31
- package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +3 -2
- package/src/_b_end_runtime/page-patterns/pageListShared.jsx +5 -1
- package/src/_b_end_runtime/patterns.js +13 -6
- package/src/_b_end_runtime/preview-registry.jsx +7 -8
- package/src/index.d.ts +2 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react';
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import Button from '../components/Button';
|
|
3
3
|
import Icon from '../components/Icon';
|
|
4
4
|
import Sheet from '../components/Sheet';
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
FilterBar,
|
|
9
9
|
LIST_COLUMNS,
|
|
10
10
|
PAGE_CONTEXT_LABEL,
|
|
11
|
+
PAGE_FRAME_CONTENT_MARGIN,
|
|
12
|
+
PAGE_FRAME_PANEL_GAP,
|
|
11
13
|
PageHeader,
|
|
12
14
|
WHITE_CARD_STYLE,
|
|
13
15
|
buildListDataSource,
|
|
@@ -23,9 +25,9 @@ import {
|
|
|
23
25
|
*
|
|
24
26
|
* 规则语义:
|
|
25
27
|
* · 该页是“横向大卡工作区默认支持宽度拖拽”的标准示例
|
|
26
|
-
* · 主卡始终 `flex-1 min-w-0
|
|
27
|
-
*
|
|
28
|
-
* · 左卡外层保持 `overflow-visible
|
|
28
|
+
* · 主卡始终 `flex-1 min-w-0`,左侧辅助白卡容器负责拖拽调宽
|
|
29
|
+
* · TagBar 自身只负责标签导航与底部展开/收起按钮,收起/展开联动整个白卡容器
|
|
30
|
+
* · 左卡外层保持 `overflow-visible`,确保容器级拖拽把手不会被裁切
|
|
29
31
|
*
|
|
30
32
|
* 业务接入:
|
|
31
33
|
* · MCP_TAG_TREE 替换为业务真实分类树
|
|
@@ -52,7 +54,17 @@ const MCP_TAGBAR_WIDTH_LIMITS = {
|
|
|
52
54
|
initial: 260,
|
|
53
55
|
min: 200,
|
|
54
56
|
max: 360,
|
|
57
|
+
collapsed: 72,
|
|
55
58
|
};
|
|
59
|
+
const TAGBAR_PANEL_RESIZE_HANDLE = 'absolute top-0 bottom-0 -right-[6px] z-20 w-3 cursor-col-resize touch-none select-none focus-visible:outline-none';
|
|
60
|
+
const TAGBAR_PANEL_RESIZE_BAR = 'absolute right-[5px] top-0 bottom-0 w-[2px] rounded-full bg-transparent transition-colors duration-150';
|
|
61
|
+
|
|
62
|
+
function clampTagBarPanelWidth(value) {
|
|
63
|
+
return Math.min(
|
|
64
|
+
Math.max(value, MCP_TAGBAR_WIDTH_LIMITS.min),
|
|
65
|
+
MCP_TAGBAR_WIDTH_LIMITS.max,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
56
68
|
|
|
57
69
|
/* ── 左侧 TagBar:业务切换 ── */
|
|
58
70
|
const MCP_BUSINESSES = [
|
|
@@ -69,9 +81,30 @@ const MCP_TAG_TREE = [
|
|
|
69
81
|
iconName: 'database-01-stroked',
|
|
70
82
|
iconBgToken: 'blue-50',
|
|
71
83
|
children: [
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
{
|
|
85
|
+
id: 'data-poi',
|
|
86
|
+
label: 'POI 数据',
|
|
87
|
+
children: [
|
|
88
|
+
{ id: 'data-poi-basic', label: '门店基础信息' },
|
|
89
|
+
{ id: 'data-poi-geo', label: '门店地理位置' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'data-user',
|
|
94
|
+
label: '用户数据',
|
|
95
|
+
children: [
|
|
96
|
+
{ id: 'data-user-profile', label: '用户画像标签' },
|
|
97
|
+
{ id: 'data-user-behavior', label: '用户行为聚合' },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'data-order',
|
|
102
|
+
label: '订单数据',
|
|
103
|
+
children: [
|
|
104
|
+
{ id: 'data-order-query', label: '订单查询服务' },
|
|
105
|
+
{ id: 'data-order-fulfillment', label: '履约状态识别' },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
75
108
|
],
|
|
76
109
|
},
|
|
77
110
|
{
|
|
@@ -80,8 +113,22 @@ const MCP_TAG_TREE = [
|
|
|
80
113
|
iconName: 'gift-01-stroked',
|
|
81
114
|
iconBgToken: 'orange-50',
|
|
82
115
|
children: [
|
|
83
|
-
{
|
|
84
|
-
|
|
116
|
+
{
|
|
117
|
+
id: 'marketing-coupon',
|
|
118
|
+
label: '券与活动',
|
|
119
|
+
children: [
|
|
120
|
+
{ id: 'marketing-coupon-center', label: '营销活动配置' },
|
|
121
|
+
{ id: 'marketing-coupon-verify', label: '优惠券核销' },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'marketing-content',
|
|
126
|
+
label: '内容审核',
|
|
127
|
+
children: [
|
|
128
|
+
{ id: 'marketing-content-text', label: '文本内容审核' },
|
|
129
|
+
{ id: 'marketing-content-material', label: '达人素材复审' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
85
132
|
],
|
|
86
133
|
},
|
|
87
134
|
{
|
|
@@ -90,30 +137,44 @@ const MCP_TAG_TREE = [
|
|
|
90
137
|
iconName: 'cpu-chip-01-stroked',
|
|
91
138
|
iconBgToken: 'purple-50',
|
|
92
139
|
children: [
|
|
93
|
-
{
|
|
94
|
-
|
|
140
|
+
{
|
|
141
|
+
id: 'algo-recommend',
|
|
142
|
+
label: '推荐召回',
|
|
143
|
+
children: [
|
|
144
|
+
{ id: 'algo-recommend-engine', label: '推荐引擎调用' },
|
|
145
|
+
{ id: 'algo-recommend-recall', label: '多路召回服务' },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'algo-rank',
|
|
150
|
+
label: '排序与策略',
|
|
151
|
+
children: [
|
|
152
|
+
{ id: 'algo-rank-strategy', label: '排序策略配置' },
|
|
153
|
+
{ id: 'algo-rank-experiment', label: 'AB 实验查询' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
95
156
|
],
|
|
96
157
|
},
|
|
97
158
|
];
|
|
98
159
|
|
|
99
|
-
const MCP_DEFAULT_TAG_ID = 'data';
|
|
100
|
-
const MCP_DEFAULT_EXPANDED_IDS = ['data', 'marketing', 'algo'];
|
|
160
|
+
const MCP_DEFAULT_TAG_ID = 'data-order-query';
|
|
161
|
+
const MCP_DEFAULT_EXPANDED_IDS = ['data', 'data-order', 'marketing', 'marketing-coupon', 'algo', 'algo-recommend'];
|
|
101
162
|
|
|
102
163
|
const MCP_DATA = [
|
|
103
|
-
{ name: 'POI 数据 MCP', tag: '数据接入', desc: '统一调用 POI 基础信息、坐标、营业时间等字段。', tagIds: ['data', 'data-poi'] },
|
|
104
|
-
{ name: '门店地理 MCP', tag: '数据接入', desc: '门店所在城市、商圈、行政区编码的批量查询能力。', tagIds: ['data', 'data-poi'] },
|
|
105
|
-
{ name: '用户画像 MCP', tag: '画像服务', desc: '封装画像中台的标签查询、人群圈选能力。', tagIds: ['data', 'data-user'] },
|
|
106
|
-
{ name: '用户行为 MCP', tag: '行为分析', desc: '近 30 天浏览、点击、下单等行为聚合查询。', tagIds: ['data', 'data-user'] },
|
|
107
|
-
{ name: '订单查询 MCP', tag: '订单服务', desc: '聚合主站、本地推、抖音生服等多端订单数据。', tagIds: ['data', 'data-order'] },
|
|
108
|
-
{ name: '履约状态 MCP', tag: '订单服务', desc: '物流节点、配送进度与异常订单的实时识别。', tagIds: ['data', 'data-order'] },
|
|
109
|
-
{ name: '营销中台 MCP', tag: '营销服务', desc: '券、活动、补贴等核心营销动作的统一入口。', tagIds: ['marketing', 'marketing-coupon'] },
|
|
110
|
-
{ name: '优惠券核销 MCP', tag: '营销服务', desc: '券码核销、回写与异常订单的自动回收处理。', tagIds: ['marketing', 'marketing-coupon'] },
|
|
111
|
-
{ name: '内容审核 MCP', tag: '内容安全', desc: '文本 / 图片 / 视频审核能力,支持送审与复审。', tagIds: ['marketing', 'marketing-content'] },
|
|
112
|
-
{ name: '素材审核 MCP', tag: '内容安全', desc: '达人投稿物料的风险识别与一键复检流程。', tagIds: ['marketing', 'marketing-content'] },
|
|
113
|
-
{ name: '推荐引擎 MCP', tag: '推荐服务', desc: '在线召回、排序与多场景策略调用接口。', tagIds: ['algo', 'algo-recommend'] },
|
|
114
|
-
{ name: '召回服务 MCP', tag: '推荐服务', desc: '兴趣 / 协同 / 热度多路召回的统一封装。', tagIds: ['algo', 'algo-recommend'] },
|
|
115
|
-
{ name: '排序策略 MCP', tag: '策略服务', desc: '提供多目标排序、人群定向与冷启动策略。', tagIds: ['algo', 'algo-rank'] },
|
|
116
|
-
{ name: 'AB 实验 MCP', tag: '策略服务', desc: '实验创建、分流、效果指标的统一查询入口。', tagIds: ['algo', 'algo-rank'] },
|
|
164
|
+
{ name: 'POI 数据 MCP', tag: '数据接入', desc: '统一调用 POI 基础信息、坐标、营业时间等字段。', tagIds: ['data', 'data-poi', 'data-poi-basic'] },
|
|
165
|
+
{ name: '门店地理 MCP', tag: '数据接入', desc: '门店所在城市、商圈、行政区编码的批量查询能力。', tagIds: ['data', 'data-poi', 'data-poi-geo'] },
|
|
166
|
+
{ name: '用户画像 MCP', tag: '画像服务', desc: '封装画像中台的标签查询、人群圈选能力。', tagIds: ['data', 'data-user', 'data-user-profile'] },
|
|
167
|
+
{ name: '用户行为 MCP', tag: '行为分析', desc: '近 30 天浏览、点击、下单等行为聚合查询。', tagIds: ['data', 'data-user', 'data-user-behavior'] },
|
|
168
|
+
{ name: '订单查询 MCP', tag: '订单服务', desc: '聚合主站、本地推、抖音生服等多端订单数据。', tagIds: ['data', 'data-order', 'data-order-query'] },
|
|
169
|
+
{ name: '履约状态 MCP', tag: '订单服务', desc: '物流节点、配送进度与异常订单的实时识别。', tagIds: ['data', 'data-order', 'data-order-fulfillment'] },
|
|
170
|
+
{ name: '营销中台 MCP', tag: '营销服务', desc: '券、活动、补贴等核心营销动作的统一入口。', tagIds: ['marketing', 'marketing-coupon', 'marketing-coupon-center'] },
|
|
171
|
+
{ name: '优惠券核销 MCP', tag: '营销服务', desc: '券码核销、回写与异常订单的自动回收处理。', tagIds: ['marketing', 'marketing-coupon', 'marketing-coupon-verify'] },
|
|
172
|
+
{ name: '内容审核 MCP', tag: '内容安全', desc: '文本 / 图片 / 视频审核能力,支持送审与复审。', tagIds: ['marketing', 'marketing-content', 'marketing-content-text'] },
|
|
173
|
+
{ name: '素材审核 MCP', tag: '内容安全', desc: '达人投稿物料的风险识别与一键复检流程。', tagIds: ['marketing', 'marketing-content', 'marketing-content-material'] },
|
|
174
|
+
{ name: '推荐引擎 MCP', tag: '推荐服务', desc: '在线召回、排序与多场景策略调用接口。', tagIds: ['algo', 'algo-recommend', 'algo-recommend-engine'] },
|
|
175
|
+
{ name: '召回服务 MCP', tag: '推荐服务', desc: '兴趣 / 协同 / 热度多路召回的统一封装。', tagIds: ['algo', 'algo-recommend', 'algo-recommend-recall'] },
|
|
176
|
+
{ name: '排序策略 MCP', tag: '策略服务', desc: '提供多目标排序、人群定向与冷启动策略。', tagIds: ['algo', 'algo-rank', 'algo-rank-strategy'] },
|
|
177
|
+
{ name: 'AB 实验 MCP', tag: '策略服务', desc: '实验创建、分流、效果指标的统一查询入口。', tagIds: ['algo', 'algo-rank', 'algo-rank-experiment'] },
|
|
117
178
|
];
|
|
118
179
|
|
|
119
180
|
/* MCP_TAG_TREE 中所有合法 tag id 的扁平集合,用于过滤判定(业务 id 不参与筛选) */
|
|
@@ -138,16 +199,83 @@ export default function McpManagementPage() {
|
|
|
138
199
|
const [selectedTagId, setSelectedTagId] = useState(MCP_DEFAULT_TAG_ID);
|
|
139
200
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
140
201
|
const [tagBarWidth, setTagBarWidth] = useState(MCP_TAGBAR_WIDTH_LIMITS.initial);
|
|
202
|
+
const [tagBarCollapsed, setTagBarCollapsed] = useState(false);
|
|
203
|
+
const [tagBarPanelResizeActive, setTagBarPanelResizeActive] = useState(false);
|
|
204
|
+
const lastExpandedTagBarWidthRef = useRef(MCP_TAGBAR_WIDTH_LIMITS.initial);
|
|
205
|
+
const tagBarPanelDragRef = useRef(null);
|
|
141
206
|
|
|
142
207
|
const dataSource = useMemo(
|
|
143
208
|
() => buildListDataSource('mcp', filterMcpByTag(MCP_DATA, selectedTagId)),
|
|
144
209
|
[selectedTagId],
|
|
145
210
|
);
|
|
146
211
|
|
|
212
|
+
const handleTagBarPanelWidthChange = useCallback((nextWidth) => {
|
|
213
|
+
const clampedWidth = clampTagBarPanelWidth(nextWidth);
|
|
214
|
+
lastExpandedTagBarWidthRef.current = clampedWidth;
|
|
215
|
+
setTagBarCollapsed(false);
|
|
216
|
+
setTagBarWidth(clampedWidth);
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
const handleTagBarCollapsedChange = useCallback((nextCollapsed) => {
|
|
220
|
+
setTagBarCollapsed(nextCollapsed);
|
|
221
|
+
if (nextCollapsed) {
|
|
222
|
+
lastExpandedTagBarWidthRef.current = clampTagBarPanelWidth(tagBarWidth);
|
|
223
|
+
setTagBarWidth(MCP_TAGBAR_WIDTH_LIMITS.collapsed);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
setTagBarWidth(lastExpandedTagBarWidthRef.current);
|
|
227
|
+
}, [tagBarWidth]);
|
|
228
|
+
|
|
229
|
+
const handleTagBarPanelResizePointerDown = useCallback((event) => {
|
|
230
|
+
event.preventDefault();
|
|
231
|
+
const startWidth = tagBarCollapsed
|
|
232
|
+
? lastExpandedTagBarWidthRef.current
|
|
233
|
+
: tagBarWidth;
|
|
234
|
+
tagBarPanelDragRef.current = {
|
|
235
|
+
startX: event.clientX,
|
|
236
|
+
startWidth,
|
|
237
|
+
};
|
|
238
|
+
setTagBarCollapsed(false);
|
|
239
|
+
setTagBarPanelResizeActive(true);
|
|
240
|
+
|
|
241
|
+
const previousUserSelect = document.body.style.userSelect;
|
|
242
|
+
document.body.style.userSelect = 'none';
|
|
243
|
+
|
|
244
|
+
const handlePointerMove = (moveEvent) => {
|
|
245
|
+
if (!tagBarPanelDragRef.current) return;
|
|
246
|
+
const deltaX = moveEvent.clientX - tagBarPanelDragRef.current.startX;
|
|
247
|
+
handleTagBarPanelWidthChange(tagBarPanelDragRef.current.startWidth + deltaX);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const stopDragging = () => {
|
|
251
|
+
tagBarPanelDragRef.current = null;
|
|
252
|
+
setTagBarPanelResizeActive(false);
|
|
253
|
+
document.body.style.userSelect = previousUserSelect;
|
|
254
|
+
window.removeEventListener('pointermove', handlePointerMove);
|
|
255
|
+
window.removeEventListener('pointerup', stopDragging);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
window.addEventListener('pointermove', handlePointerMove);
|
|
259
|
+
window.addEventListener('pointerup', stopDragging, { once: true });
|
|
260
|
+
}, [handleTagBarPanelWidthChange, tagBarCollapsed, tagBarWidth]);
|
|
261
|
+
|
|
262
|
+
const handleTagBarPanelResizeKeyDown = useCallback((event) => {
|
|
263
|
+
let nextWidth = tagBarCollapsed
|
|
264
|
+
? lastExpandedTagBarWidthRef.current
|
|
265
|
+
: tagBarWidth;
|
|
266
|
+
if (event.key === 'ArrowLeft') nextWidth -= 16;
|
|
267
|
+
if (event.key === 'ArrowRight') nextWidth += 16;
|
|
268
|
+
if (event.key === 'Home') nextWidth = MCP_TAGBAR_WIDTH_LIMITS.min;
|
|
269
|
+
if (event.key === 'End') nextWidth = MCP_TAGBAR_WIDTH_LIMITS.max;
|
|
270
|
+
if (nextWidth === (tagBarCollapsed ? lastExpandedTagBarWidthRef.current : tagBarWidth)) return;
|
|
271
|
+
event.preventDefault();
|
|
272
|
+
handleTagBarPanelWidthChange(nextWidth);
|
|
273
|
+
}, [handleTagBarPanelWidthChange, tagBarCollapsed, tagBarWidth]);
|
|
274
|
+
|
|
147
275
|
return (
|
|
148
276
|
<div
|
|
149
|
-
className="flex flex-1 min-h-0 min-w-0 relative"
|
|
150
|
-
style={{ margin:
|
|
277
|
+
className="tfds-page-mcp-management flex flex-1 min-h-0 min-w-0 relative"
|
|
278
|
+
style={{ margin: PAGE_FRAME_CONTENT_MARGIN, gap: PAGE_FRAME_PANEL_GAP }}
|
|
151
279
|
>
|
|
152
280
|
{/* 新建 MCP Sheet 侧边抽屉 */}
|
|
153
281
|
{sheetOpen && (
|
|
@@ -175,9 +303,9 @@ export default function McpManagementPage() {
|
|
|
175
303
|
</div>
|
|
176
304
|
</div>
|
|
177
305
|
)}
|
|
178
|
-
{/*
|
|
306
|
+
{/* 左侧辅助白卡:容器级拖拽调宽,TagBar 只负责标签导航与底部展开/收起 */}
|
|
179
307
|
<div
|
|
180
|
-
className="flex shrink-0 overflow-visible"
|
|
308
|
+
className="relative flex shrink-0 overflow-visible"
|
|
181
309
|
style={{
|
|
182
310
|
...WHITE_CARD_STYLE,
|
|
183
311
|
width: `${tagBarWidth}px`,
|
|
@@ -191,15 +319,36 @@ export default function McpManagementPage() {
|
|
|
191
319
|
onSelect={(id) => setSelectedTagId(id)}
|
|
192
320
|
defaultExpandedIds={MCP_DEFAULT_EXPANDED_IDS}
|
|
193
321
|
width={tagBarWidth}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
resizable
|
|
198
|
-
collapsible={false}
|
|
322
|
+
collapsed={tagBarCollapsed}
|
|
323
|
+
onCollapsedChange={handleTagBarCollapsedChange}
|
|
324
|
+
collapsible
|
|
199
325
|
/* 作为独立辅助大卡展示:白卡外层负责容器层级,TagBar 内部不再额外画右分割线 */
|
|
200
326
|
className="!border-r-0"
|
|
201
|
-
style={{ background: 'transparent' }}
|
|
327
|
+
style={{ width: '100%', background: 'transparent' }}
|
|
202
328
|
/>
|
|
329
|
+
<div
|
|
330
|
+
className={TAGBAR_PANEL_RESIZE_HANDLE}
|
|
331
|
+
role="separator"
|
|
332
|
+
aria-label="调整标签栏白卡宽度"
|
|
333
|
+
aria-orientation="vertical"
|
|
334
|
+
aria-valuemin={MCP_TAGBAR_WIDTH_LIMITS.min}
|
|
335
|
+
aria-valuemax={MCP_TAGBAR_WIDTH_LIMITS.max}
|
|
336
|
+
aria-valuenow={Math.round(tagBarCollapsed ? lastExpandedTagBarWidthRef.current : tagBarWidth)}
|
|
337
|
+
tabIndex={0}
|
|
338
|
+
onPointerDown={handleTagBarPanelResizePointerDown}
|
|
339
|
+
onKeyDown={handleTagBarPanelResizeKeyDown}
|
|
340
|
+
onPointerEnter={() => setTagBarPanelResizeActive(true)}
|
|
341
|
+
onPointerLeave={() => {
|
|
342
|
+
if (!tagBarPanelDragRef.current) setTagBarPanelResizeActive(false);
|
|
343
|
+
}}
|
|
344
|
+
onFocus={() => setTagBarPanelResizeActive(true)}
|
|
345
|
+
onBlur={() => {
|
|
346
|
+
if (!tagBarPanelDragRef.current) setTagBarPanelResizeActive(false);
|
|
347
|
+
}}
|
|
348
|
+
data-tfds-component="TagBarPanel.ResizeHandle"
|
|
349
|
+
>
|
|
350
|
+
<span className={`${TAGBAR_PANEL_RESIZE_BAR} ${tagBarPanelResizeActive ? 'bg-border-default' : 'bg-transparent'}`} />
|
|
351
|
+
</div>
|
|
203
352
|
</div>
|
|
204
353
|
|
|
205
354
|
{/* 右侧主白卡:保持弹性撑满剩余空间,不参与固定宽计算 */}
|
|
@@ -5,7 +5,7 @@ import Filter from '../components/Filter';
|
|
|
5
5
|
import Icon from '../components/Icon';
|
|
6
6
|
import Input from '../components/Input';
|
|
7
7
|
import Table from '../components/Table';
|
|
8
|
-
import { PAGE_CONTEXT_LABEL, PageHeader, WHITE_CARD_STYLE } from './pageListShared';
|
|
8
|
+
import { PAGE_CONTEXT_LABEL, PAGE_FRAME_CONTENT_MARGIN, PageHeader, WHITE_CARD_STYLE } from './pageListShared';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* StrategyListPage — 策略管理列表页(页面示例0)
|
|
@@ -115,9 +115,9 @@ export default function StrategyListPage() {
|
|
|
115
115
|
|
|
116
116
|
return (
|
|
117
117
|
<div
|
|
118
|
-
className="flex flex-1 min-h-0 min-w-0 flex-col overflow-hidden"
|
|
118
|
+
className="tfds-page-strategy-list flex flex-1 min-h-0 min-w-0 flex-col overflow-hidden"
|
|
119
119
|
style={{
|
|
120
|
-
margin:
|
|
120
|
+
margin: PAGE_FRAME_CONTENT_MARGIN,
|
|
121
121
|
...WHITE_CARD_STYLE,
|
|
122
122
|
}}
|
|
123
123
|
>
|
|
@@ -6,13 +6,13 @@ import Icon from '../components/Icon';
|
|
|
6
6
|
import Tabs from '../components/Tabs';
|
|
7
7
|
import Tag from '../components/Tag';
|
|
8
8
|
import { getTeamMemberByIndex, getTeamMemberByName } from '../teamMembers';
|
|
9
|
-
import { FilterBar, WHITE_CARD_STYLE } from './pageListShared';
|
|
9
|
+
import { FilterBar, PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN, WHITE_CARD_STYLE } from './pageListShared';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* TabTopBarListPage — B 端"胶囊 Tab 标题栏 + 卡片列表 + 详情面板"模板(列表示例 3)
|
|
13
13
|
*
|
|
14
14
|
* 模板结构(自上而下):
|
|
15
|
-
* · TopBar
|
|
15
|
+
* · TopBar:胶囊 Tab 一级导航 + 操作按钮组
|
|
16
16
|
* · 白卡(自上而下):
|
|
17
17
|
* 筛选栏
|
|
18
18
|
* └ 横向两栏:左卡片流(flex-1 可滚动) + 右详情面板(shrink-0 460px,可关闭)
|
|
@@ -26,7 +26,6 @@ import { FilterBar, WHITE_CARD_STYLE } from './pageListShared';
|
|
|
26
26
|
* · 详情打开/收起为宽度 + 透明度 280ms 缓动过渡,无突兀闪现
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
const SCENE_LABEL = '渠道切换';
|
|
30
29
|
const FILTERS = [
|
|
31
30
|
{
|
|
32
31
|
label: '知识类型',
|
|
@@ -251,19 +250,12 @@ export default function TabTopBarListPage({ onCreateQA }) {
|
|
|
251
250
|
}
|
|
252
251
|
|
|
253
252
|
return (
|
|
254
|
-
|
|
253
|
+
<div className="tfds-page-tab-top-bar-list flex flex-1 min-h-0 min-w-0 flex-col overflow-hidden">
|
|
255
254
|
{/* 顶部:胶囊 Tab 标题栏 */}
|
|
256
255
|
<div
|
|
257
256
|
className="flex min-w-0 shrink-0 items-center gap-2 overflow-x-auto"
|
|
258
257
|
style={{ padding: '12px 16px 12px 0' }}
|
|
259
258
|
>
|
|
260
|
-
<ScenePill label={SCENE_LABEL} />
|
|
261
|
-
|
|
262
|
-
<div
|
|
263
|
-
className="h-8 w-px shrink-0"
|
|
264
|
-
style={{ background: 'var(--color-border-default, #E4E7EC)' }}
|
|
265
|
-
/>
|
|
266
|
-
|
|
267
259
|
<Tabs
|
|
268
260
|
variant="pill"
|
|
269
261
|
size="md"
|
|
@@ -299,7 +291,7 @@ export default function TabTopBarListPage({ onCreateQA }) {
|
|
|
299
291
|
<div
|
|
300
292
|
className="flex flex-1 min-h-0 min-w-0 flex-col overflow-hidden"
|
|
301
293
|
style={{
|
|
302
|
-
margin:
|
|
294
|
+
margin: PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN,
|
|
303
295
|
...WHITE_CARD_STYLE,
|
|
304
296
|
}}
|
|
305
297
|
>
|
|
@@ -337,7 +329,7 @@ export default function TabTopBarListPage({ onCreateQA }) {
|
|
|
337
329
|
</div>
|
|
338
330
|
</div>
|
|
339
331
|
</div>
|
|
340
|
-
|
|
332
|
+
</div>
|
|
341
333
|
);
|
|
342
334
|
}
|
|
343
335
|
|
|
@@ -537,21 +529,3 @@ function SectionBadge({ type, children }) {
|
|
|
537
529
|
</span>
|
|
538
530
|
);
|
|
539
531
|
}
|
|
540
|
-
|
|
541
|
-
/* ── 顶部场景胶囊 ── */
|
|
542
|
-
function ScenePill({ label }) {
|
|
543
|
-
return (
|
|
544
|
-
<div
|
|
545
|
-
className="inline-flex items-center shrink-0 bg-white/50 ring-1 ring-white ring-inset rounded-full"
|
|
546
|
-
style={{ padding: '6px 8px' }}
|
|
547
|
-
>
|
|
548
|
-
<button
|
|
549
|
-
type="button"
|
|
550
|
-
className="inline-flex items-center gap-1 cursor-pointer h-8 px-4 rounded-full bg-transparent border-0 text-sm [font-weight:var(--font-semibold)] text-blueGrey-900 leading-5"
|
|
551
|
-
>
|
|
552
|
-
<span>{label}</span>
|
|
553
|
-
<Icon name="chevron-down-stroked" />
|
|
554
|
-
</button>
|
|
555
|
-
</div>
|
|
556
|
-
);
|
|
557
|
-
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
FilterBar,
|
|
7
7
|
LIST_COLUMNS,
|
|
8
8
|
PAGE_CONTEXT_LABEL,
|
|
9
|
+
PAGE_FRAME_CONTENT_MARGIN,
|
|
9
10
|
PageHeader,
|
|
10
11
|
WHITE_CARD_STYLE,
|
|
11
12
|
buildListDataSource,
|
|
@@ -54,9 +55,9 @@ export default function VariableManagementPage({ onNewVariable }) {
|
|
|
54
55
|
|
|
55
56
|
return (
|
|
56
57
|
<div
|
|
57
|
-
className="flex flex-1 min-h-0 min-w-0 flex-col overflow-hidden"
|
|
58
|
+
className="tfds-page-variable-management flex flex-1 min-h-0 min-w-0 flex-col overflow-hidden"
|
|
58
59
|
style={{
|
|
59
|
-
margin:
|
|
60
|
+
margin: PAGE_FRAME_CONTENT_MARGIN,
|
|
60
61
|
...WHITE_CARD_STYLE,
|
|
61
62
|
}}
|
|
62
63
|
>
|
|
@@ -18,6 +18,10 @@ export const WHITE_CARD_STYLE = {
|
|
|
18
18
|
background: 'var(--color-white, #FFFFFF)',
|
|
19
19
|
borderRadius: '12px',
|
|
20
20
|
};
|
|
21
|
+
/* 基础页面框架:右侧内容区与左侧 NavBar 的横向间距统一参考页面示例3,左侧不再额外加 16px 灰底间隔 */
|
|
22
|
+
export const PAGE_FRAME_CONTENT_MARGIN = '16px 16px 16px 0';
|
|
23
|
+
export const PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN = '0 16px 16px 0';
|
|
24
|
+
export const PAGE_FRAME_PANEL_GAP = '8px';
|
|
21
25
|
|
|
22
26
|
/* ── 表格列定义(所有列表型模板共用:名称 / 类型 / 描述 / 创建人 / 状态 / 更新时间 / 操作) ── */
|
|
23
27
|
export const LIST_COLUMNS = [
|
|
@@ -27,7 +31,7 @@ export const LIST_COLUMNS = [
|
|
|
27
31
|
{ key: 'creator', dataIndex: 'creator', title: '创建人', type: 'avatar', width: 152 },
|
|
28
32
|
{ key: 'status', dataIndex: 'status', title: '状态', type: 'status', width: 120 },
|
|
29
33
|
{ key: 'updatedAt', dataIndex: 'updatedAt', title: '更新时间', type: 'datetime', width: 180 },
|
|
30
|
-
{ key: 'actions', dataIndex: 'actions', title: '操作', type: 'actions'
|
|
34
|
+
{ key: 'actions', dataIndex: 'actions', title: '操作', type: 'actions' },
|
|
31
35
|
];
|
|
32
36
|
|
|
33
37
|
const ACTION_BUTTONS = [
|
|
@@ -40,7 +40,11 @@ export const PATTERNS = [
|
|
|
40
40
|
rules: [
|
|
41
41
|
'【模板定位】本模板是客服 / 在线 Agent 工作台的页面级框架,用于“客服正在处理会话、工单、质检、托管”等工作台页面;它不是普通业务组件、不是 NavBar,也不是会话列表本体。',
|
|
42
42
|
'【选型优先级】客服工作台框架在客服业务页面中优先级最高。当用户明确提到“客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent 工作台 / 客服接待台”等客服工作台场景时,优先使用本模板作为整页外框;右侧主白卡默认必须继续使用 `IMConversationPattern + InfoDisplayPanel` 组合,而不是用表格、普通 Card 或 AI 对话页替代 IM 聊天区。',
|
|
43
|
+
'【结构命中优先】当用户提到“IM 工作台 / 客服 IM 工作台 / 三栏 IM / 左侧 IM 列表 / 会话列表 + 沟通聊天区 + 信息区 / 右侧信息 Tabs / 托管助手 + 历史工单 + 工单日志”等结构信号时,默认先判断 `CustomerServiceWorkspaceFramePattern` 是否可兼容;只要页面主结构是左侧会话/工单队列、中间 IM 沟通聊天区、右侧信息展示区或 N 个信息 tab,就应优先使用客服工作台框架,而不是新造三栏框架、手搓 `ConversationList + IMConversationPattern + Card Tabs`,也不是只用单独 `IMConversationPattern`。',
|
|
44
|
+
'【IM 模板边界】只有“单个当前线程详情页 / 私信详情 / 站内信详情”这类不需要左侧多会话队列和右侧信息区的场景,才优先使用 `IMConversationPattern`。一旦出现客服工作台、IM 工作台、左侧会话列表、右侧信息栏、托管助手、历史工单、工单日志或多信息 tab,就升级为 `CustomerServiceWorkspaceFramePattern`。',
|
|
43
45
|
'【Figma 对齐】默认视觉对齐 Figma 节点 8279:67909「在线Agent」:根容器浅灰底 `--color-blueGrey-200`、16px 内边距、顶部 24px 状态条、下方左侧半透明板块 + 右侧主白卡;6 个可见圆角统一为 16px:左侧板块左上 / 左下,右侧主白卡四角。',
|
|
46
|
+
'【外层间距边界】客服工作台框架自身已经包含页面模版所需的四周内部间距(例如根容器 16px 内边距和工作区内部节奏),当页面使用 `CustomerServiceWorkspaceFramePattern` 时,外层不得再额外包 `main p-4`、`section margin`、`Card`、`bg-surface rounded-*` 或任何 padding/margin wrapper。理解为:除框架自身内部间距外,工作台的顶部、底部、右侧边界就是浏览器页面打开时的顶部、底部、右侧边界。',
|
|
47
|
+
'【左侧菜单栏组合】客服工作台可以在最左侧自动组合平台菜单栏组件,例如 `NavBar platform="bytehi"` / ByteHi 菜单栏。组合方式必须是 AppShell 级横向骨架:`<NavBar className="shrink-0" />` 在最左侧,`<CustomerServiceWorkspaceFramePattern />` 作为右侧 `flex-1 min-w-0 min-h-0 overflow-hidden` 工作区;禁止在 NavBar 与客服工作台之间再插入额外间距或白卡外壳。',
|
|
44
48
|
'【结构】根节点必须直接作为页面主工作区或右侧主内容区的一级框架;内部顺序固定为 Header(客服名称 / 在线状态 / 指标工具 / 模式切换) + Workspace(左侧上下文占位 + 右侧主面板)。不要再用大白卡或 `bg-surface rounded-*` 包住整个模板。',
|
|
45
49
|
'【顶部栏】左侧显示客服名称与在线时长;中部数据栏必须默认按页面可用宽度动态居中,不随左侧客服名称或右侧模式切换偏移;指标容器使用 `bg-white/60 + border-white + radius-md + px-4 py-1`;在线状态胶囊与工具入口这类按钮化页面元素必须复用基础 `Button`;右侧基础模式 / 托管模式属于同页模式切换,必须复用基础 `Tabs`,默认使用 `variant="segment"`,不要手写 tab button 或用 NavBar 替代。',
|
|
46
50
|
'【顶部数据】指标区左侧数据仅用于展示当前页面相关的高优关注数据,形式固定为 Icon + 数字值;最多展示 4 类数据,超出时应按业务优先级裁切;每一类数据都必须支持 hover/focus 通过 Tooltip 查看文案解释,tooltip 文案优先来自 `tooltip` / `description`,否则回退到 `label`。',
|
|
@@ -60,10 +64,11 @@ export const PATTERNS = [
|
|
|
60
64
|
'【右侧信息栏整体拖拽】在客服工作台框架模版里,右侧 InfoDisplayPanel 必须支持整体宽度拖拽,拖拽热区位于信息栏左边界,默认热区 8px;拖拽时改变的是右侧信息栏整体宽度,左侧 IM 聊天区自动占满剩余空间,而不是改由 InfoDisplayPanel 本体处理页面级宽度。推荐默认宽度约 380px,最小宽度 320px,并保证左侧 IM 区至少保留 360px 可读宽度;同时保留 InfoDisplayPanel 内部相邻栏之间的原生拖拽能力。',
|
|
61
65
|
'【InfoDisplayPanel 动态 tab 拆分】右侧信息展示区必须保留 InfoDisplayPanel 的动态 tab1 / tab2 / tab3 栏拆分能力:业务只有 1 个分类时降级为单栏标题 + 内容;业务有 2 个分类时最多支持拆成 2 栏;业务有 3 个及以上分类且宽度达到三栏门槛时支持拆成 3 栏。tab 数量、名称、顺序和内容必须来自当前会话 / 工单上下文的业务数据,不允许固定写死 3 个静态按钮,也不允许用多个 Card 或 div 手搓三栏替代。',
|
|
62
66
|
'【右侧信息 Tab 区内容定义】客服工作台右侧 InfoDisplayPanel 是“当前处理对象的辅助信息工作区”,每个 tab 都是业务内容插槽而不是固定占位。业务可在 `panels[].content` 中直接传入任意 React 内容,或通过 `renderPanelContent({ panel, index, activeTabId })` 按当前 tab 动态渲染;允许承载托管助手 ChatMessage 执行流、用户画像、订单/商品详情、历史工单、工单日志、沟通记录、风险提示、售后策略、处理工具、表单、表格片段、图文/视频信息或自定义复合组件。',
|
|
67
|
+
'【托管助手 Tab 默认规则】当右侧信息 tab 的名称或语义命中“托管助手 / 托管助理 / AI 托管 / 托管模式 / 辅助回复 / 推荐回复 / HiAI”时,默认必须使用本模板内置托管助手交互形态:先展示一条与左侧 IM 聊天区最新用户气泡文案完全一致的 `ChatMessage role="user"` 灰色气泡,再展示 `HiAI` 的 `ChatMessage` 执行流,执行完成后展示推荐回复文本,推荐回复必须映射该用户消息之后左侧 IM 客服侧实际回复文案,并提供“复制到输入框”和“发送给用户”两个纯图标 Button 的真实交互。禁止只写一个静态 AssistantPanel、普通 Card 或单条 AI 文本。',
|
|
63
68
|
'【右侧信息 Tab 区自定义约束】自定义内容只能替换每个 tab 的内容区,不能替换 InfoDisplayPanel 的外框、Tabs、拆分/合并按钮、栏间拖拽、最小宽度、响应式列数和滚动容器。内容组件必须使用 `w-full min-w-0`、自动换行和内部滚动适配窄栏;禁止写固定大宽度、绝对定位撑破栏位,禁止为了展示自定义内容而改用多个 Card、自制 Tabs 或普通 div 拼出右侧信息栏。',
|
|
64
69
|
'【拖拽与拆分保留验收】客服工作台生成页必须同时满足:拖动左侧列表边界时 ConversationList 默认列表 / 卡片列表容器实时适配;拖动右侧 InfoDisplayPanel 左边界时信息栏整体宽度变化且 IM 区自动吃剩余空间;拆出 InfoDisplayPanel tab 后,栏间分隔线可继续调节相邻栏宽,且每栏不小于 200px。任一能力缺失都视为没有正确使用客服工作台框架。',
|
|
65
70
|
'【右侧插槽】如业务需要替换右侧主内容,可放 ChatConversationPattern、Table、表单详情或工单处理面板;但客服接待类页面优先保持“IM 对话 + InfoDisplayPanel”的组合,不要用多个普通 Card 临时拼出右侧信息区。',
|
|
66
|
-
'【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。',
|
|
71
|
+
'【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / IM 工作台 / 三栏 IM / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 托管助手 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。',
|
|
67
72
|
'【容器语义】本模板自身已经包含浅灰页面底与右侧主白卡,生成页面时不要外层再套 `Card color="grey"`、`bg-surface rounded-xl` 或大白卡 section,否则会形成“灰底 + 大白卡 + 框架”的错误嵌套。',
|
|
68
73
|
'【组件复用】统计图标、工具图标必须复用 `Icon`;在线状态胶囊与工具按钮必须复用 `Button`;右上角模式切换必须复用 `Tabs`;主面板内若有头像,继续遵守本地成员头像素材规则;不要手写 svg、tab button、button div 或随机外链头像。',
|
|
69
74
|
],
|
|
@@ -117,12 +122,12 @@ export default function CustomerServicePage() {
|
|
|
117
122
|
'【标题区防竖排铁律】白卡/面板标题是板块语义锚点,必须横向可读,⛔ 禁止出现“原始信息只读”这类一字一行的竖状标题。禁止 `writing-mode` / `text-orientation` / `break-all` / `w-[16px]` / `max-w-[24px]` 等压窄标题的写法。标题栏标准结构:左侧标题容器 `min-w-[120px] flex-1` + `<FormTitle />`,右侧 Tabs/按钮容器 `shrink-0 flex flex-wrap justify-end gap-2`;窄面板保持默认无描述,必要时把 Tabs/按钮移到下一行或正文顶部,不能挤压标题。详见 LAYOUT_RULES §3.4.1 / GLOBAL_DESIGN_RULES §8.4。',
|
|
118
123
|
'【标题状态 Tag 归位铁律】如果白卡/面板标题右侧有数量、状态、身份、分类 Tag(如 `0 条消息`、`Beta`、`只读`、`推荐`),它属于主标题语义,必须写进 `<FormTitle titleSuffix={<Tag ... />} />` 并紧跟主标题显示;⛔ 禁止把 Tag 放到 `justify-between` 的右侧操作区,也禁止 `<FormTitle ... /><Tag ... />` 让标签跟在整个标题组件后面。右侧操作区只放 Button、Tabs、筛选和更多菜单。',
|
|
119
124
|
'【页面 header 灰底裸放铁律】**页面级 header(标题 + 描述 + 返回 + 面包屑 + 页面级 Pill Tabs + 主次操作按钮)必须直接坐灰底,绝不包白卡**。白卡的语义是"工作区容器"——里面只装真正在做事的内容(列表 / 表格 / 对话流 / 参数表单 / 运行结果 / 编辑器主体);页面 header 是"页面元数据 / 切换位置 / 触发动作",必须直接坐 `var(--color-blueGrey-200)` 灰底上。⛔ 严禁把"标题 + 描述 + 操作按钮"包成 `<div bg=surface rounded-lg p-6>`——这会让顶部"标题白卡"和下方"内容白卡"形成同等大小的两张白卡,主从混乱、气息感丢失。参考样本:CopilotPagePattern 的 TopBar 是标杆(返回 + 标题 + Pill Tabs + 主次操作全部直接坐灰底)。详见 LAYOUT_RULES § 1.5。',
|
|
120
|
-
'
|
|
125
|
+
'【全宽撑满铁律(左右大空白专项)】**白卡必须撑满浏览器全宽**:⛔ 严禁用 `p-2` / `px-6` / `px-8` 当 main 外框间距(常见误区:把白卡内部 padding p-6 错用到 main);⛔ 严禁在 main 内套 `<div className="max-w-* mx-auto">` 居中容器——这是左右大空白的主因;⛔ 严禁根容器 / main 加 `max-w-screen-xl` / `max-w-7xl` 等收窄值;白卡 div 自身不加 `mx-*` 外边距,靠 `flex-1` 或 `shrink-0 w-[Npx]` 控制宽度。基础页面框架的右侧内容区外距统一使用 `PAGE_FRAME_CONTENT_MARGIN = 16px 16px 16px 0`,即左侧 NavBar 与右侧白卡之间不再额外加 16px 灰底空隙;页面示例3因顶部 Tab 占位,白卡使用 `PAGE_FRAME_CONTENT_WITH_TOPBAR_MARGIN = 0 16px 16px 0`。三层间距金字塔:右侧内容区外距 16/16/16/0、白卡间距 8px(`PAGE_FRAME_PANEL_GAP`)、白卡内 padding 24px(p-6)。详见 LAYOUT_RULES §1.2 / §6.1。',
|
|
121
126
|
'【白卡纯白无描边铁律】**最外层大白卡容器必须是"纯白底 + 12px 圆角 + 无 border + 无 shadow"**:⛔ 严禁在背景为 `var(--color-surface)` 的最外层 div / Card 上加 `border` / `outline` / `ring` / `borderColor` / `border-border-default` 等任何描边;白卡的层级感**唯一靠"灰底 vs 白卡的明度差 + 12px 圆角"**承担,加 border 会让明度差被划线覆盖、视觉权重变硬、气息感丢失。✅ **保留**:白卡**内部**所有 TFDS 组件自带的描边(Input / Select / TagInput / Tag / Tabs line / Table 行线 / 内嵌 grey Card 等——这些是控件功能边界,不是卡片层级边界)。判别口诀:背景色 `--color-surface`(白)→ **禁止** border;背景色 `--color-blueGrey-200`(灰,页面外框)→ **允许** border。详见 GLOBAL_DESIGN_RULES § 5.5。',
|
|
122
127
|
'【Copilot详情页补充】当页面既不是纯入口页,也不是纯消息流页,而是“左侧/中部展示上下文结果,右侧或顶部继续让 AI 协助处理”的混合协作场景,可使用 `CopilotPagePattern`。它适用于“带上下文结果面板的 AI 协同详情页”,但仍不应被误写成纯白卡列表管理页。',
|
|
123
128
|
'【Tabs 硬约束】主工作区内凡多块互斥内容的面板切换**必须**用基础组件 `<Tabs />`;**卡片内**小模块切换、**内容区顶部**布局级切换**默认优先** `variant="segment"`(分段器);所有 4 种 Tabs 变体在白卡/内容区/表单分段/筛选维度/Playground 面板内默认尺寸统一为 **SM**(省略 size 或 `size="sm"`),⛔ 禁止内容区默认 `size="md"` / `size="lg"`;只有平台顶部 header / 页面级顶导 Tabs 可按场景使用 MD/LG。Tabs 默认内置横向滚动,父级必须 `min-w-0 max-w-full`,禁止 `w-fit` / `min-w-max` / 固定大宽度导致 Tabs 撑破卡片、侧栏或页面;禁止 `Button` 排一行或 `Tag` 冒充切换器',
|
|
124
129
|
'【模板独立】每个"列表示例"对应一个独立 .jsx 文件(如 VariableManagementPage / McpManagementPage),业务接入直接复制单文件即可',
|
|
125
|
-
'【示例映射(选型入口)】页面示例0=StrategyListPage(树形可展开列表+版本子行+分页);页面示例1=VariableManagementPage(单白卡:标题/筛选/表格);页面示例2=McpManagementPage(双白卡:左侧可拖拽辅助大卡 + 右侧弹性主白卡列表,是“横向大卡默认支持宽度拖拽”的标准示例);页面示例3=TabTopBarListPage(TopBar
|
|
130
|
+
'【示例映射(选型入口)】页面示例0=StrategyListPage(树形可展开列表+版本子行+分页);页面示例1=VariableManagementPage(单白卡:标题/筛选/表格);页面示例2=McpManagementPage(双白卡:左侧可拖拽辅助大卡 + 右侧弹性主白卡列表,是“横向大卡默认支持宽度拖拽”的标准示例);页面示例3=TabTopBarListPage(TopBar 一级胶囊 Tab + 操作组,左上角不展示“渠道切换”场景胶囊,也不展示其右侧竖向分割线;白卡内“列表+详情面板”联动);页面示例4=NoAccessPage(空状态+主行动:申请权限);页面示例5=ConstructionPage(空状态:功能建设中)',
|
|
126
131
|
'【页面示例3·信息卡片3】TabTopBarListPage 的左侧知识列表条目必须引用业务组件 `Card type="info3"`(信息卡片3),不得在页面内手写重复卡片样式。信息卡片3适用于知识库知识列表、规则列表、策略列表等密集信息场景,结构固定为“标题 + 右上状态 Tag + 描述 + 底部元信息”,圆角固定 12px(rounded-lg)。页面示例3只负责传入 question/answer/status/typeLabel/updatedAt/creator 与 selected/onAction;页面示例3的列表卡片处于白色工作区内,必须显式使用 `color="grey"` 灰色卡片样式;信息卡片3的背景色、描边和 hover 规则必须与其它 Card 分类一致,统一遵守 `color=white/grey` 的背景反衬规则;整卡选中 UI 由 Card 的 `selected` 统一控制,语义是“原本同一条灰色描边变为绿色描边”,禁止新增 ring / outline / 外扩描边,也不允许改写卡片背景色。点击已选卡片收起详情、点击其它卡片切换详情的交互状态仍由页面模板维护。',
|
|
127
132
|
'【关键词→示例】“策略管理/策略列表/版本子行/引用次数/渠道”→示例0;“变量管理/字段变量/流程引用”→示例1;“MCP/工具管理/分类树筛选/左侧标签树”→示例2;“知识库/QA对/文档知识/案例库/右侧详情面板/TopBar胶囊Tab”→示例3;“无权限/申请权限/无访问权限”→示例4;“建设中/敬请期待/开发中”→示例5',
|
|
128
133
|
'【NavBar】通过 navItems 自定义菜单项(id / label / iconName),通过 selectedItemId 高亮当前菜单;菜单 id 与右侧模板一一对应',
|
|
@@ -130,7 +135,7 @@ export default function CustomerServicePage() {
|
|
|
130
135
|
'【标题栏·FormTitle】新写页面时白卡顶栏主标题必须用 `<FormTitle variant="level-1" title="…" />`(默认不显示副标题),标题旁状态/数量 Tag 用 `titleSuffix` 贴主标题;与右侧主按钮同一行用 `flex items-start justify-between gap-3` 组合;左侧标题区 `min-w-[120px] flex-1`,右侧操作区 `shrink-0 flex-wrap`,**禁止**再手写竖条 + `h2`,也禁止把标题挤成竖排。`pageListShared.jsx` 中的 `PageHeader` 仅为既有模板实现参考。',
|
|
131
136
|
'【筛选栏】搜索 Input 必须和 Filter 在同一 flex 行,默认固定 240px(`flex: 0 0 240px` + `--size-input-width: 240px`),禁止 `--size-input-width: 100%` / `flex-1` / `w-full` 撑满整行;后接多个带 options 的标准 `<Filter />` 基础组件,包含"我创建的"也必须使用 Filter;项与项之间 8px、整体 36px 高、wrap 多行容错,禁止再手写 FilterPill / Checkbox 胶囊 / rounded-full 筛选按钮',
|
|
132
137
|
'【表单宽度判断】所有 form 类型组件都要按字段语义决定宽度,而不是统一 `w-full`:搜索/短关键词=中宽;枚举筛选/状态/时间范围=窄到中宽;名称/标题=中到宽;描述/备注/多行文本=宽或全宽;多值标签输入按内容与容器宽度决定',
|
|
133
|
-
'【双白卡变体】当列表需要左侧维度筛选(如按分类树过滤)时,在主白卡左侧再加一张**独立白圆角辅助大卡**包住 `TagBar
|
|
138
|
+
'【双白卡变体】当列表需要左侧维度筛选(如按分类树过滤)时,在主白卡左侧再加一张**独立白圆角辅助大卡**包住 `TagBar`。该类“横向并列的大卡工作区”默认就要支持宽度拖拽:左卡外层白卡维护 `tagBarWidth`、`collapsed`、`onCollapsedChange` 与容器级拖拽把手,外层保持 `overflow-visible` 以露出拖拽把手,右侧主卡保持 `flex-1 min-w-0` 弹性撑满;两白卡间距 8px。TagBar 自身始终透明底,不提供“样式/tone”配置,背景由左侧白卡容器决定;TagBar 默认使用三级标签树,一级分类必须包含彩色图标。参见 McpManagementPage',
|
|
134
139
|
'【多白卡分区(通用)】当页面右侧需要多个业务板块时,必须拆分为多个独立白卡(而不是一整张通栏白底背景)。推荐:灰底内容区外层统一用 `padding: 16px` 或等价 `margin: 16px` 外框节奏;框架层最外层白卡统一 `纯白背景 + 12px 圆角`,不加浅灰描边;白卡之间 `gap: 8px`;白卡内部布局用 `padding: 24px` + `gap: 16px`(参见 VariableManagementPage / McpManagementPage)。内部卡片、列表项、表单控件原有边框/描边保持不变。',
|
|
135
140
|
'【表格】使用 Table fixedColumnsMode="last" 让操作列固定在最右,pagination.total 与当前 dataSource.length 同步',
|
|
136
141
|
'【共享子组件】PageHeader / FilterBar / 列定义 / mock 工具集中在 pageListShared.jsx,避免每个模板重复实现',
|
|
@@ -213,8 +218,10 @@ export default function MyPage({ defaultSelectedItemId = 'example-1' }) {
|
|
|
213
218
|
'【整体背景】页面以浅灰大背景(`--color-blueGrey-200`)为主:Hero、筛选行、卡片网格**直接坐灰底**,不要再额外包一整张“右侧白色大卡片容器”(白卡只用于推荐卡片本身)。',
|
|
214
219
|
'【反例扫描】如果同一右侧首页函数里同时出现 `ChatInput`、模板 `Card` 网格,并且外层存在 `background: var(--color-surface)` / `bg-surface rounded-*` 包住 Hero + 输入框 + Tabs + 网格,说明 AI 把入口页误生成成白卡管理页,必须拆掉这层 wrapper。',
|
|
215
220
|
'【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。',
|
|
216
|
-
'【Hero
|
|
217
|
-
'【Hero
|
|
221
|
+
'【Hero 区】Hero 内容整体居中,但标题组和 ChatInput 必须共用同一个最大宽 680px 的居中容器(`width: 100%; maxWidth: 680px; margin: 0 auto`),不能标题单独全页居中、输入框单独居中造成视觉错位。Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant="default"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。',
|
|
222
|
+
'【Hero 标题统一对齐容器】标题、描述、助手名/问候语必须放在与 ChatInput 同一个 680px 容器内对齐:知识库/模板库/工具入口等带明确业务标题的场景,标题组默认 `text-left w-full`,左边界对齐输入框左边界;纯欢迎语/个人助手问候场景才允许在同一容器内 `text-center`。禁止标题组使用页面全宽居中,而 ChatInput 使用另一套宽度。',
|
|
223
|
+
'【Hero 输入区禁止 Form】Hero 区的标题、副标题、AI 身份提示和主 ChatInput 都属于入口页启动区,不是表单字段。禁止用 `<Form />`、Form 的 `items[].label`、`labelAi` 或字段标题来包裹 / 模拟输入框顶部的“AI”标题;如果需要在输入框上方展示“AI / 助手名 / 问候语”,必须作为 Hero 标题组放在与 ChatInput 相同的 680px 居中容器内,不得使用 Form。',
|
|
224
|
+
'【ChatInput 内部不可重排】入口页主输入必须直接使用 `<ChatInput variant="default" />`,并通过 `catBarText` 和 `placeholder` 配置内容。禁止把 `catBarText`(如“可直接提问:用户催退款时如何解释到账时效?”)抽到 ChatInput 外部另写一行文字;禁止给 ChatInput 内部猫条、placeholder 或输入文案加 `text-center` / `justify-center`,组件内部文案必须保持左对齐。',
|
|
218
225
|
'【Hero 反例扫描】同一 ChatHome 页面中如果出现 `<Form items={[{ label: "AI", type: "input" | "textarea" }]}>`、`labelAi` 或 Form 字段标题紧贴主 ChatInput 上方,说明 AI 把入口页主输入误生成为表单字段,必须删除 Form,改回居中 Hero 标题 + `<ChatInput variant="default" />`。',
|
|
219
226
|
'【筛选与分类】分类切换必须用 `Tabs size="sm"`;筛选行 `padding: 16px 40px`,左 Tabs、右搜索。模板/助手搜索框是辅助筛选入口,不是页面主输入,必须固定宽度并自动对齐到页面右侧:可用外层 `<div className="ml-auto shrink-0" style={{ flex: "0 0 240px", width: "240px", minWidth: "240px", maxWidth: "240px" }}>` 包裹 `Input`,也可直接给 `Input` 传 `style={{ flex: "0 0 240px", "--size-input-width": "240px" }}`;`Input` 的 `style` 作用于根容器。Tabs 外层使用 `flex shrink-0` 保持左侧胶囊 Tabs 可见。筛选行容器禁止使用 `flex-wrap`、`justify-between`、`space-y-*`、`flex-col`;禁止让筛选搜索框 `flex-1`、`w-full`、撑满 Tabs 右侧剩余空间,或换到 Tabs 下方单独占一行。',
|
|
220
227
|
'【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;当前列表直接坐浅灰页面底,所以卡片统一使用 `Card color="white"`。通用铁律是:纯白容器里必须改用 `color="grey"`,灰色/浅灰/其他非纯白容器里必须使用 `color="white"`,且这条规则对所有 Card 分类都生效;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
|
|
@@ -1813,12 +1813,12 @@ export const PREVIEW_REGISTRY = {
|
|
|
1813
1813
|
lines.push('];');
|
|
1814
1814
|
lines.push('');
|
|
1815
1815
|
lines.push('const items = [');
|
|
1816
|
-
lines.push(" { id: 'account', label: '账号', iconName: 'user-01-stroked', iconBgToken: 'brand-50', children: [{ id: 'account-registration', label: '账号注册/登录', children: [{ id: 'account-register-too-many', label: '注册时提示「注册账号过多」' }, { id: 'account-face-fail', label: '注册/登录时用户刷脸/实名认证失败' }] }, { id: 'account-
|
|
1817
|
-
lines.push(" { id: 'base-product', label: '基础产品', iconName: 'layers-two-01-stroked', iconBgToken: 'blue-50' },");
|
|
1818
|
-
lines.push(" { id: 'social', label: '社交', iconName: 'heart-circle-stroked', iconBgToken: 'pink-50' },");
|
|
1816
|
+
lines.push(" { id: 'account', label: '账号', iconName: 'user-01-stroked', iconBgToken: 'brand-50', children: [{ id: 'account-registration', label: '账号注册/登录', children: [{ id: 'account-register-too-many', label: '注册时提示「注册账号过多」' }, { id: 'account-face-fail', label: '注册/登录时用户刷脸/实名认证失败' }] }, { id: 'account-security', label: '账号安全', children: [{ id: 'account-risk', label: '账号风险校验' }, { id: 'account-recovery', label: '账号找回申诉' }] }] },");
|
|
1817
|
+
lines.push(" { id: 'base-product', label: '基础产品', iconName: 'layers-two-01-stroked', iconBgToken: 'blue-50', children: [{ id: 'base-product-feed', label: '首页浏览', children: [{ id: 'feed-refresh', label: '推荐流刷新卡顿' }, { id: 'feed-cache', label: '首页缓存清理' }] }, { id: 'base-product-message', label: '消息中心', children: [{ id: 'message-red-dot', label: '消息红点不同步' }, { id: 'message-delay', label: '互动消息延迟' }] }] },");
|
|
1818
|
+
lines.push(" { id: 'social', label: '社交', iconName: 'heart-circle-stroked', iconBgToken: 'pink-50', children: [{ id: 'social-follow', label: '关注关系', children: [{ id: 'follow-fail', label: '关注失败重试' }, { id: 'fans-sync', label: '粉丝关系同步延迟' }] }, { id: 'social-private-chat', label: '私信沟通', children: [{ id: 'private-chat-send', label: '私信发送失败' }, { id: 'private-chat-risk', label: '私信风控提示' }] }] },");
|
|
1819
1819
|
lines.push('];');
|
|
1820
1820
|
lines.push('');
|
|
1821
|
-
lines.push('{/* 双白卡场景:外层白圆角 + overflow-visible
|
|
1821
|
+
lines.push('{/* 双白卡场景:外层白圆角 + overflow-visible;TagBar 自身透明底,背景由所属容器决定 */}');
|
|
1822
1822
|
lines.push('<div style={{ background: "var(--color-white)", borderRadius: 12, overflow: "visible", width: 240 }}>');
|
|
1823
1823
|
lines.push(' <TagBar');
|
|
1824
1824
|
lines.push(' businesses={businesses}');
|
|
@@ -1829,7 +1829,6 @@ export const PREVIEW_REGISTRY = {
|
|
|
1829
1829
|
if (layout === 'collapsed') lines.push(' defaultCollapsed');
|
|
1830
1830
|
if (searchable) lines.push(' searchable');
|
|
1831
1831
|
lines.push(' collapsible');
|
|
1832
|
-
lines.push(' tone="transparent"');
|
|
1833
1832
|
lines.push(' className="!border-r-0"');
|
|
1834
1833
|
lines.push(' style={{ background: "transparent" }}');
|
|
1835
1834
|
lines.push(' />');
|
|
@@ -5023,7 +5022,7 @@ export const PREVIEW_REGISTRY = {
|
|
|
5023
5022
|
: { background: 'transparent', border: '1px solid #E4E7EC', borderRadius: '12px', overflow: 'hidden' };
|
|
5024
5023
|
|
|
5025
5024
|
const demoContent = (
|
|
5026
|
-
<div style={{ display: 'flex', flex: 1, minHeight: 0, gap: '
|
|
5025
|
+
<div style={{ display: 'flex', flex: 1, minHeight: 0, gap: '16px', padding: '0 16px 16px' }}>
|
|
5027
5026
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, ...cardStyle }}>
|
|
5028
5027
|
<StepCardHeader step="1" title="步骤标题一" hint="辅助信息" />
|
|
5029
5028
|
</div>
|
|
@@ -5067,13 +5066,13 @@ export const PREVIEW_REGISTRY = {
|
|
|
5067
5066
|
lines.push('>');
|
|
5068
5067
|
if (bg === 'grey') {
|
|
5069
5068
|
lines.push(' {/* 灰底模式:页面是浅灰底,内部面板必须切换为白卡样式 */}');
|
|
5070
|
-
lines.push(' <div className="flex gap-4
|
|
5069
|
+
lines.push(' <div className="flex gap-4 px-4 pb-4 pt-0 h-full">');
|
|
5071
5070
|
lines.push(' <div className="flex-1 bg-white rounded-xl p-6">主编辑面板</div>');
|
|
5072
5071
|
lines.push(' <div className="w-60 bg-white rounded-xl p-6">配置侧栏面板</div>');
|
|
5073
5072
|
lines.push(' </div>');
|
|
5074
5073
|
} else {
|
|
5075
5074
|
lines.push(' {/* 白底模式:页面保持一体化,内部面板用描边样式,不再叠加白卡 */}');
|
|
5076
|
-
lines.push(' <div className="flex gap-4
|
|
5075
|
+
lines.push(' <div className="flex gap-4 px-4 pb-4 pt-0 h-full">');
|
|
5077
5076
|
lines.push(' <div className="flex-1 rounded-xl border border-border-default p-6">基础信息面板</div>');
|
|
5078
5077
|
lines.push(' <div className="w-60 rounded-xl border border-border-default p-6">辅助配置面板</div>');
|
|
5079
5078
|
lines.push(' </div>');
|