@tfdesign/b-end 1.0.11 → 1.0.13

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 (26) hide show
  1. package/README.md +23 -25
  2. package/package.json +1 -1
  3. package/skills/tfds/components.index.json +271 -67
  4. package/skills/tfds/components.summary.json +101 -62
  5. package/src/_b_end_runtime/components/ChatMessage.jsx +210 -61
  6. package/src/_b_end_runtime/components/ChatMessage.tokens.js +30 -0
  7. package/src/_b_end_runtime/components/ChatMessagePreview.jsx +14 -0
  8. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +30 -6
  9. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +5 -0
  10. package/src/_b_end_runtime/components/Filter.jsx +390 -0
  11. package/src/_b_end_runtime/components/Filter.tokens.js +98 -0
  12. package/src/_b_end_runtime/components/Input.jsx +3 -1
  13. package/src/_b_end_runtime/components/Modal.jsx +10 -3
  14. package/src/_b_end_runtime/components/Radio.jsx +174 -4
  15. package/src/_b_end_runtime/components/Radio.tokens.js +22 -0
  16. package/src/_b_end_runtime/components.js +124 -13
  17. package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +35 -26
  18. package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +14 -1
  19. package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +19 -12
  20. package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +14 -1
  21. package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +15 -2
  22. package/src/_b_end_runtime/page-patterns/pageListShared.jsx +54 -36
  23. package/src/_b_end_runtime/patterns.js +33 -21
  24. package/src/_b_end_runtime/preview-registry.jsx +180 -8
  25. package/src/index.d.ts +30 -1
  26. package/src/index.js +2 -1
@@ -11,7 +11,7 @@ import Icon from '../components/Icon';
11
11
  *
12
12
  * 右侧内容区从上到下:
13
13
  * 1. Hero 区:大标题 + 副标题 + 居中 ChatInput + 快捷建议 chip
14
- * 2. 筛选行(左 Search + 右胶囊 Tab),左右 40px 边距
14
+ * 2. 筛选行(左胶囊 Tab + 右固定宽 Search),左右 40px 边距
15
15
  * 3. 自适应卡片网格(auto-fill minmax(260px,1fr)),左右 40px 边距
16
16
  */
17
17
 
@@ -23,6 +23,13 @@ const FILTER_TABS = [
23
23
  { label: '数据完善', id: '数据完善' },
24
24
  ];
25
25
 
26
+ const FILTER_SEARCH_WRAP_STYLE = {
27
+ flex: '0 0 240px',
28
+ width: '240px',
29
+ minWidth: '240px',
30
+ maxWidth: '240px',
31
+ };
32
+
26
33
  /* ── 示例卡片数据(20 张,category 对应 Tab id)── */
27
34
  const MOCK_CARDS = [
28
35
  {
@@ -197,16 +204,16 @@ export default function ChatHomePagePattern() {
197
204
  />
198
205
  </div>
199
206
 
200
- {/* 右侧内容区 */}
201
- <div className="flex flex-1 min-w-0 flex-col min-h-0 overflow-hidden">
207
+ {/* 右侧内容区:整体滚动,Hero / 筛选 / 卡片一起滑动 */}
208
+ <div className="flex flex-1 min-w-0 flex-col min-h-0 overflow-y-auto overflow-x-hidden">
202
209
 
203
210
  {/* ① Hero 区 */}
204
211
  <div
205
212
  className="flex flex-col items-center shrink-0"
206
- style={{ padding: '120px 40px 80px' }}
213
+ style={{ padding: '84px 40px 80px' }}
207
214
  >
208
215
  <h1
209
- className="m-0 text-center text-3xl [font-weight:var(--font-semibold)] leading-9"
216
+ className="m-0 text-center text-4xl [font-weight:var(--font-semibold)] leading-10"
210
217
  style={{
211
218
  color: 'var(--foreground, #0F1C35)',
212
219
  marginBottom: '6px',
@@ -219,7 +226,7 @@ export default function ChatHomePagePattern() {
219
226
  className="m-0 text-sm text-center"
220
227
  style={{
221
228
  color: 'var(--foreground-muted, rgba(15,28,53,0.45))',
222
- marginBottom: '24px',
229
+ marginBottom: '48px',
223
230
  }}
224
231
  >
225
232
  直接描述你的需求,或从下方模板快速开始
@@ -230,32 +237,34 @@ export default function ChatHomePagePattern() {
230
237
  </div>
231
238
  </div>
232
239
 
233
- {/* ② 筛选行:左侧胶囊 Tab + 右侧搜索,左右 40px */}
240
+ {/* ② 筛选行:左侧胶囊 Tab + 右侧固定宽搜索,左右 40px */}
234
241
  <div
235
- className="flex min-w-0 shrink-0 flex-wrap items-center justify-between gap-4"
242
+ className="flex min-w-0 shrink-0 items-center gap-4"
236
243
  style={{ padding: '16px 40px' }}
237
244
  >
238
- <Tabs
239
- variant="pill"
240
- size="sm"
241
- items={FILTER_TABS}
242
- defaultIndex={0}
243
- onChange={(index) => setActiveTabIndex(index)}
244
- />
245
- <Input
246
- placeholder="搜索标题、描述"
247
- prefix={<Icon name="search-md-stroked" size="sm" />}
248
- allowClear
249
- value={searchValue}
250
- onChange={(e) => setSearchValue(e.target.value)}
251
- className="max-w-full"
252
- style={{ flex: '0 1 240px', width: '240px', minWidth: '200px', '--size-input-width': '100%' }}
253
- />
245
+ <div className="flex shrink-0">
246
+ <Tabs
247
+ variant="pill"
248
+ size="sm"
249
+ items={FILTER_TABS}
250
+ defaultIndex={0}
251
+ onChange={(index) => setActiveTabIndex(index)}
252
+ />
253
+ </div>
254
+ <div className="ml-auto shrink-0" style={FILTER_SEARCH_WRAP_STYLE}>
255
+ <Input
256
+ placeholder="搜索标题、描述"
257
+ prefix={<Icon name="search-md-stroked" size="sm" />}
258
+ allowClear
259
+ value={searchValue}
260
+ onChange={(e) => setSearchValue(e.target.value)}
261
+ />
262
+ </div>
254
263
  </div>
255
264
 
256
- {/* ③ 卡片网格(自适应列,可滚动,左右 40px) */}
265
+ {/* ③ 卡片网格(随页面整体滚动,左右 40px) */}
257
266
  <div
258
- className="flex-1 min-h-0 overflow-y-auto"
267
+ className="shrink-0"
259
268
  style={{ padding: '0 40px 32px' }}
260
269
  >
261
270
  <div
@@ -34,7 +34,20 @@ import {
34
34
  */
35
35
 
36
36
  const PAGE_TITLE = 'MCP 工具管理';
37
- const MCP_FILTERS = ['MCP 类型', '创建人', '状态'];
37
+ const MCP_FILTERS = [
38
+ {
39
+ label: 'MCP 类型',
40
+ options: ['数据接入', '画像服务', '订单服务', '营销服务', '推荐服务', '策略服务'],
41
+ },
42
+ {
43
+ label: '创建人',
44
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
45
+ },
46
+ {
47
+ label: '状态',
48
+ options: ['已启用', '调试中', '已停用', '审核中'],
49
+ },
50
+ ];
38
51
  const MCP_TAGBAR_WIDTH_LIMITS = {
39
52
  initial: 260,
40
53
  min: 200,
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import Modal from '../components/Modal';
3
3
  import Button from '../components/Button';
4
+ import Filter from '../components/Filter';
4
5
  import Icon from '../components/Icon';
5
6
  import Input from '../components/Input';
6
7
  import Table from '../components/Table';
@@ -93,7 +94,20 @@ const MOCK_STRATEGIES = [
93
94
  },
94
95
  ];
95
96
 
96
- const STRATEGY_FILTERS = ['管理员', '发布状态', '策略类型'];
97
+ const STRATEGY_FILTERS = [
98
+ {
99
+ label: '管理员',
100
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
101
+ },
102
+ {
103
+ label: '发布状态',
104
+ options: ['线上生效', '实验中', '暂未生效', '草稿中', '已发布'],
105
+ },
106
+ {
107
+ label: '策略类型',
108
+ options: ['账号服务', '权益申诉', '账号处罚', '举报处理', '售后协同'],
109
+ },
110
+ ];
97
111
 
98
112
  export default function StrategyListPage() {
99
113
  const [expandedId, setExpandedId] = useState('experience-1');
@@ -170,19 +184,12 @@ function StrategyFilterBar({ filters = [] }) {
170
184
  <Input
171
185
  placeholder="搜索"
172
186
  prefix={<Icon name="search-md-stroked" />}
173
- className="max-w-full"
174
- style={{ flex: '0 1 240px', width: '240px', minWidth: '200px', '--size-input-width': '100%' }}
187
+ className="shrink-0"
188
+ style={{ flex: '0 0 240px', '--size-input-width': '240px' }}
175
189
  />
176
190
 
177
- {filters.map((label) => (
178
- <button
179
- key={label}
180
- type="button"
181
- className="inline-flex h-8 shrink-0 cursor-pointer items-center gap-2 rounded-full border border-border-default bg-white px-3 text-sm [font-weight:var(--font-semibold)] text-blueGrey-800 transition-colors duration-150 hover:bg-fill"
182
- >
183
- <span>{label}</span>
184
- <Icon name="chevron-down-stroked" className="text-blueGrey-600" />
185
- </button>
191
+ {filters.map((item) => (
192
+ <Filter key={item.label} {...item} />
186
193
  ))}
187
194
  </div>
188
195
  );
@@ -26,7 +26,20 @@ import { FilterBar, WHITE_CARD_STYLE } from './pageListShared';
26
26
  */
27
27
 
28
28
  const SCENE_LABEL = '渠道切换';
29
- const FILTERS = ['知识类型', '创建人', '状态'];
29
+ const FILTERS = [
30
+ {
31
+ label: '知识类型',
32
+ options: ['QA', '文档', '案例'],
33
+ },
34
+ {
35
+ label: '创建人',
36
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
37
+ },
38
+ {
39
+ label: '状态',
40
+ options: ['生效', '已发布', '草稿', '审核中', '已停用'],
41
+ },
42
+ ];
30
43
  const DETAIL_WIDTH = 'min(460px, 40vw)';
31
44
 
32
45
  /* 状态 → Tag 颜色变体(Tag.variant) */
@@ -16,7 +16,7 @@ import {
16
16
  *
17
17
  * 模板结构(自上而下):
18
18
  * · 标题栏:左侧竖条 + 标题 + 业务上下文胶囊 + 右侧"新建变量"按钮
19
- * · 筛选栏:搜索框 + 下拉筛选胶囊 + Checkbox"我创建的"
19
+ * · 筛选栏:搜索框 + 标准 Filter 下拉筛选项
20
20
  * · 表格:列定义共享自 pageListShared
21
21
  *
22
22
  * 业务接入:
@@ -25,7 +25,20 @@ import {
25
25
  */
26
26
 
27
27
  const PAGE_TITLE = '变量管理';
28
- const VARIABLE_FILTERS = ['变量类型', '创建人', '流程引用'];
28
+ const VARIABLE_FILTERS = [
29
+ {
30
+ label: '变量类型',
31
+ options: ['String', 'Number', 'Boolean', 'Enum'],
32
+ },
33
+ {
34
+ label: '创建人',
35
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
36
+ },
37
+ {
38
+ label: '流程引用',
39
+ options: ['已引用', '未引用'],
40
+ },
41
+ ];
29
42
 
30
43
  const VARIABLE_DATA = [
31
44
  { name: 'user_id', tag: 'String', desc: '当前对话用户的统一标识,登录态自动注入。' },
@@ -6,7 +6,7 @@
6
6
  * 业务接入时只需替换数据源(list)和文案配置(标题/筛选项/按钮)。
7
7
  */
8
8
 
9
- import Checkbox from '../components/Checkbox';
9
+ import Filter from '../components/Filter';
10
10
  import FormTitle from '../components/FormTitle';
11
11
  import Icon from '../components/Icon';
12
12
  import Input from '../components/Input';
@@ -45,6 +45,15 @@ const STATUS_OPTIONS = [
45
45
  { label: '审核中', tone: 'warning' },
46
46
  ];
47
47
 
48
+ const DEFAULT_FILTER_OPTIONS = {
49
+ 创建人: TEAM_MEMBERS.slice(0, 4).map((member) => ({ label: member.name, value: member.name })),
50
+ 状态: STATUS_OPTIONS.map((item) => ({ label: item.label, value: item.label })),
51
+ '我创建的': [
52
+ { label: '是', value: 'yes' },
53
+ { label: '否', value: 'no' },
54
+ ],
55
+ };
56
+
48
57
  const UPDATED_AT_POOL = [
49
58
  '2026-04-21 10:30:00',
50
59
  '2026-04-20 18:45:00',
@@ -68,6 +77,36 @@ function makeStatus(index) {
68
77
  return STATUS_OPTIONS[index % STATUS_OPTIONS.length];
69
78
  }
70
79
 
80
+ function fallbackFilterOptions(label) {
81
+ return ['选项一', '选项二', '选项三'].map((suffix) => ({
82
+ label: `${label}${suffix}`,
83
+ value: `${label}-${suffix}`,
84
+ }));
85
+ }
86
+
87
+ function getFilterOptions(label, options) {
88
+ if (Array.isArray(options) && options.length > 0) return options;
89
+ return DEFAULT_FILTER_OPTIONS[label] || fallbackFilterOptions(label);
90
+ }
91
+
92
+ function normalizeFilterItem(item) {
93
+ if (typeof item === 'string') {
94
+ return {
95
+ label: item,
96
+ options: getFilterOptions(item),
97
+ };
98
+ }
99
+
100
+ if (!item || typeof item !== 'object') return null;
101
+ const label = item.label;
102
+ if (!label) return null;
103
+
104
+ return {
105
+ ...item,
106
+ options: getFilterOptions(label, item.options),
107
+ };
108
+ }
109
+
71
110
  /**
72
111
  * 把"业务原始数据"批量套上 mock 的创建人 / 状态 / 更新时间 / 操作按钮,
73
112
  * 形成 Table 可直接消费的 dataSource。
@@ -127,51 +166,30 @@ export function PageHeader({ title, contextLabel, actions }) {
127
166
 
128
167
  /**
129
168
  * 筛选栏(白卡内、标题栏之下)
130
- * 结构:左侧 300px 搜索框 + 一组 FilterPill(白底圆角胶囊 + 文字 + 下拉箭头)
131
- * + 1 Checkbox 胶囊"我创建的"。
169
+ * 结构:左侧固定 240px 搜索框 + 一组标准 Filter 基础组件
170
+ * + 1 个标准 Filter"我创建的"。
132
171
  *
133
- * @param {string[]} filters — 下拉筛选项的标签文案数组
134
- * @param {string} [checkboxLabel='我创建的'] — 末尾 Checkbox 胶囊文案;传 null 则隐藏
172
+ * @param {Array<string|{label:string,options?:Array}>} filters — 下拉筛选项配置
173
+ * @param {string} [checkboxLabel='我创建的'] — 末尾"我创建的"筛选项文案;传 null 则隐藏
135
174
  */
136
175
  export function FilterBar({ filters = [], checkboxLabel = '我创建的' }) {
176
+ const filterItems = filters.map(normalizeFilterItem).filter(Boolean);
177
+ const ownerFilter = checkboxLabel
178
+ ? normalizeFilterItem({ label: checkboxLabel, options: DEFAULT_FILTER_OPTIONS[checkboxLabel] })
179
+ : null;
180
+
137
181
  return (
138
182
  <div className="flex items-center shrink-0 flex-wrap" style={{ gap: '8px' }}>
139
183
  <Input
140
184
  placeholder="搜索"
141
185
  prefix={<Icon name="search-md-stroked" />}
142
- className="max-w-full"
143
- style={{ flex: '0 1 240px', width: '240px', minWidth: '200px', '--size-input-width': '100%' }}
186
+ className="shrink-0"
187
+ style={{ flex: '0 0 240px', '--size-input-width': '240px' }}
144
188
  />
145
- {filters.map((label) => (
146
- <FilterPill key={label} label={label} />
189
+ {filterItems.map((item) => (
190
+ <Filter key={item.label} {...item} />
147
191
  ))}
148
- {checkboxLabel && <FilterCheckPill label={checkboxLabel} />}
149
- </div>
150
- );
151
- }
152
-
153
- /* 单个筛选触发器:白底 + 描边 + 圆角胶囊 + 文字 + chevron */
154
- function FilterPill({ label }) {
155
- return (
156
- <button
157
- type="button"
158
- className="inline-flex items-center shrink-0 cursor-pointer bg-white rounded-full border border-border-default text-sm font-semibold text-blueGrey-800 hover:bg-fill transition-colors duration-150"
159
- style={{ height: '32px', padding: '0 12px', gap: '8px' }}
160
- >
161
- <span>{label}</span>
162
- <Icon name="chevron-down-stroked" className="text-blueGrey-600" />
163
- </button>
164
- );
165
- }
166
-
167
- /* Checkbox 类型的筛选项:白底圆角胶囊里嵌一个 Checkbox */
168
- function FilterCheckPill({ label }) {
169
- return (
170
- <div
171
- className="inline-flex items-center shrink-0 bg-white rounded-full border border-border-default"
172
- style={{ height: '32px', padding: '0 12px' }}
173
- >
174
- <Checkbox size="md">{label}</Checkbox>
192
+ {ownerFilter && <Filter key={ownerFilter.label} {...ownerFilter} />}
175
193
  </div>
176
194
  );
177
195
  }
@@ -43,6 +43,8 @@ export const PATTERNS = [
43
43
  '【Figma 对齐】默认视觉对齐 Figma 节点 8279:67909「在线Agent」:根容器浅灰底 `--color-blueGrey-200`、16px 内边距、顶部 24px 状态条、下方左侧半透明板块 + 右侧主白卡;6 个可见圆角统一为 16px:左侧板块左上 / 左下,右侧主白卡四角。',
44
44
  '【结构】根节点必须直接作为页面主工作区或右侧主内容区的一级框架;内部顺序固定为 Header(客服名称 / 在线状态 / 指标工具 / 模式切换) + Workspace(左侧上下文占位 + 右侧主面板)。不要再用大白卡或 `bg-surface rounded-*` 包住整个模板。',
45
45
  '【顶部栏】左侧显示客服名称与在线时长;中部数据栏必须默认按页面可用宽度动态居中,不随左侧客服名称或右侧模式切换偏移;指标容器使用 `bg-white/60 + border-white + radius-md + px-4 py-1`;在线状态胶囊与工具入口这类按钮化页面元素必须复用基础 `Button`;右侧基础模式 / 托管模式属于同页模式切换,必须复用基础 `Tabs`,默认使用 `variant="segment"`,不要手写 tab button 或用 NavBar 替代。',
46
+ '【顶部数据】指标区左侧数据仅用于展示当前页面相关的高优关注数据,形式固定为 Icon + 数字值;最多展示 4 类数据,超出时应按业务优先级裁切;每一类数据都必须支持 hover/focus 通过 Tooltip 查看文案解释,tooltip 文案优先来自 `tooltip` / `description`,否则回退到 `label`。',
47
+ '【顶部工具】指标区右侧按钮仅用于平台框架级入口,例如全局设置、平台公告、平台数据统计等;最多展示 3 个按钮,超出时应按平台级优先级裁切;按钮必须复用 `Button iconOnly` 并提供 `tooltip` / `aria-label`,不要放置页面内局部操作或业务表单操作。',
46
48
  '【工作区层级】Workspace 外层只负责横向布局、覆盖层级与拖拽,不承担可见圆角;左侧板块自身为 `bg-white/50 + border-white + rounded-xl`,右侧主面板自身为白色 `surface + border-white + rounded-xl`,两者高度一致、宽度不同,并通过主面板 `margin-left: -32px` 形成“右侧白板覆盖在左侧灰板上”的叠压视觉,而不是中间留缝的并排关系。',
47
49
  '【可见圆角】最终可见的 6 个角统一为 16px:左侧板块左上 / 左下两个角露出;右侧主白卡四角露出;左侧板块右上 / 右下两个角必须被右侧主白卡完整覆盖。主面板覆盖量必须大于圆角半径(推荐 32px = 2 × 16px),防止主面板左上 / 左下圆角切角处露出左侧板块的右边界或描边,避免出现“残缺一块”的视觉。',
48
50
  '【左右拖拽】左侧区域与右侧主白卡之间必须提供纵向拖拽热区,默认热区 8px;拖拽热区覆盖在主面板左边缘上,不占用布局宽度、不制造中间空隙;拖动时调整左侧区域宽度并让右侧主面板 `flex-1 min-w-0` 自动占满剩余空间;左侧默认宽度 432px(400px 可视会话列表 + 32px 主面板覆盖区);拖拽最小宽度优先由左侧业务组件最小可用宽度决定,例如 ConversationList 默认列表纯头像锁定 88px 时,左侧板块最小宽度为 `max(100px 框架兜底, 88px + 32px 覆盖区) = 120px`;ConversationList 卡片列表最小内容宽度为 333px,左侧板块最小宽度为 365px;右侧纯白主容器最小宽度 380px,左侧最大可拖宽度需按 `工作区宽度 + 32px 覆盖量 - 380px` 动态计算;拖拽条需支持键盘左右方向键微调。',
@@ -95,6 +97,7 @@ export default function CustomerServicePage() {
95
97
  '【布局 Recipe】必须按 `LAYOUT_RECIPES.md` 选择 `base-management`:灰底 AppShell + `main p-4 gap-2` + 一张或多张白色 WorkSurface;页面 header / 顶部 Tabs / 主次操作直接坐灰底。',
96
98
  '【五模板定位】本模板只解决“管理一批结构化对象”的 B 端工作区:变量、MCP、策略、知识库、数据资产、审核任务、配置列表等。用户第一步必须是筛选 / 搜索 / 浏览对象 / 批量操作 / 查看详情;如果第一步是“输入一句需求开始 AI 任务”,改用 `ChatHomePagePattern`;如果第一步是“继续追问 AI 任务”,改用 `ChatConversationPattern`;如果右侧主角是对象/产物编辑且 AI 只是侧助,改用 `CopilotPagePattern`;如果是真实客服/私信线程,改用 `IMConversationPattern`。',
97
99
  '【组件选型门禁】白卡标题必须用 `FormTitle`;筛选字段按语义用 `Input` / `Select` / `DatePicker` / `TagInput` / `Checkbox`;结构化列表必须用 `Table`;状态/类型/数量徽标必须用 `Tag`;所有操作必须用 `Button`;左侧分类维度必须用 `TagBar`;弹层按复杂度用 `Modal` 或 `Sheet`;空状态用 `Empty`。禁止手写标题、状态徽标、表格、筛选胶囊或按钮。',
100
+ '【筛选栏同一行硬约束】列表页白卡顶部筛选栏是查询工具条,不是业务正文表单;搜索框、下拉框 / 日期 / 标签等筛选控件、重置与查询按钮必须处在同一行流内,默认使用 `flex items-center flex-wrap gap-2`。搜索框建议宽 240px,下拉筛选建议 160-200px,按钮 `shrink-0`;仅当容器宽度不足时允许在同一个 flex 行流中自然换行,禁止每个控件独占一行、禁止用 `flex-col` / `space-y-*` / `Form layout="vertical"` 把搜索框、下拉框和按钮上下堆叠。',
98
101
  '【页面范式选型总则】TFDS 页面框架不只有一种白卡管理页范式。生成页面前必须先判断“用户当前要完成什么任务”,再在 `BasePageFramePattern`、`ChatHomePagePattern`、`ChatConversationPattern`(必要时 `CopilotPagePattern`)之间选最合适的页面模板,禁止把所有页面默认都生成成白卡单栏/双栏/多栏管理页。',
99
102
  '【判断标准】用“用户进入页面后的第一步动作”决定布局:第一步是“发起一个 AI 任务 / 输入一句需求 / 从模板开始”→ 优先 `ChatHomePagePattern`(AI 入口页);第一步是“围绕一个已开始的任务持续追问、查看消息流、继续与 AI 协作”→ 优先 `ChatConversationPattern`(AI 对话页);第一步是“筛选、搜索、浏览对象列表、查看详情、批量操作结构化信息”→ 优先 `BasePageFramePattern`(白卡管理页)。',
100
103
  '【AI入口页适用场景】当 `ChatInput` 本身就是页面的核心主功能,且页面目标是“帮助用户开始一件事”而不是“先管理一批对象”时,必须优先使用 `ChatHomePagePattern`。典型场景:AI 工作台首页、智能分析入口、模板广场、报告生成入口、用户尚未进入具体上下文的助手首页。此时核心输入区与推荐内容应直接坐落在浅灰大背景上,不要强行套成典型白卡后台页。',
@@ -118,7 +121,7 @@ export default function CustomerServicePage() {
118
121
  '【NavBar】通过 navItems 自定义菜单项(id / label / iconName),通过 selectedItemId 高亮当前菜单;菜单 id 与右侧模板一一对应',
119
122
  '【列表页结构】标题栏 → 筛选栏 → 表格,垂直 16px 间距,与白卡四边各 24px 边距',
120
123
  '【标题栏·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` 仅为既有模板实现参考。',
121
- '【筛选栏】搜索 Input 与筛选项同栏时不要撑满整行,默认使用约 240px 的中等宽度(空间不足再换行);后接多个 FilterPill(白底圆角胶囊 + 文字 + chevron)+ 末尾 Checkbox 胶囊"我创建的";项与项之间 8px、整体 36px 高、wrap 多行容错',
124
+ '【筛选栏】搜索 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 筛选按钮',
122
125
  '【表单宽度判断】所有 form 类型组件都要按字段语义决定宽度,而不是统一 `w-full`:搜索/短关键词=中宽;枚举筛选/状态/时间范围=窄到中宽;名称/标题=中到宽;描述/备注/多行文本=宽或全宽;多值标签输入按内容与容器宽度决定',
123
126
  '【双白卡变体】当列表需要左侧维度筛选(如按分类树过滤)时,在主白卡左侧再加一张**独立白圆角辅助大卡**包住 `TagBar`。该类“横向并列的大卡工作区”默认就要支持宽度拖拽:左卡用 `width + onWidthChange + minWidth + maxWidth + resizable`,外层保持 `overflow-visible` 以露出拖拽把手,右侧主卡保持 `flex-1 min-w-0` 弹性撑满;两白卡间距 8px。**勿**用 `tone="panel"` 代替白卡(panel 为浅灰侧条,不是白卡片)。参见 McpManagementPage',
124
127
  '【多白卡分区(通用)】当页面右侧需要多个业务板块时,必须拆分为多个独立白卡(而不是一整张通栏白底背景)。推荐:灰底内容区外层统一用 `padding: 16px` 或等价 `margin: 16px` 外框节奏;框架层最外层白卡统一 `纯白背景 + 12px 圆角`,不加浅灰描边;白卡之间 `gap: 8px`;白卡内部布局用 `padding: 24px` + `gap: 16px`(参见 VariableManagementPage / McpManagementPage)。内部卡片、列表项、表单控件原有边框/描边保持不变。',
@@ -202,10 +205,10 @@ export default function MyPage({ defaultSelectedItemId = 'example-1' }) {
202
205
  '【组件选型门禁】主输入必须用 `ChatInput`,不可用原生 textarea 或 Input 冒充 AI 输入;模板/助手/案例推荐必须用 `Card`;分类切换必须用 `Tabs`;搜索框用 `Input` + `Icon` 前缀;无结果用 `Empty`。禁止用 Button/Tag 排一行冒充 Tabs,禁止手写卡片。',
203
206
  '【整体背景】页面以浅灰大背景(`--color-blueGrey-200`)为主:Hero、筛选行、卡片网格**直接坐灰底**,不要再额外包一整张“右侧白色大卡片容器”(白卡只用于推荐卡片本身)。',
204
207
  '【反例扫描】如果同一右侧首页函数里同时出现 `ChatInput`、模板 `Card` 网格,并且外层存在 `background: var(--color-surface)` / `bg-surface rounded-*` 包住 Hero + 输入框 + Tabs + 网格,说明 AI 把入口页误生成成白卡管理页,必须拆掉这层 wrapper。',
205
- '【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-hidden`,由内部区域控制滚动。',
206
- '【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;默认使用 `ChatInput variant="default"` 作为入口主输入;标准节奏为 `padding: 120px 40px 80px`。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。',
207
- '【筛选与分类】分类切换必须用 `Tabs size="sm"`;筛选行 `padding: 16px 40px`,左 Tabs、右搜索,搜索框约 240px,空间不足允许换行。',
208
- '【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;卡片使用 `Card color="white"`;列表区域 `flex-1 min-h-0 overflow-y-auto` 独立滚动,不挤压上方区域。',
208
+ '【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。',
209
+ '【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant="default"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。',
210
+ '【筛选与分类】分类切换必须用 `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 下方单独占一行。',
211
+ '【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;卡片使用 `Card color="white"`;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
209
212
  '【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义',
210
213
  ],
211
214
  code: `import NavBar from './components/NavBar';
@@ -222,6 +225,13 @@ const CATEGORY_TABS = [
222
225
  { label: '数据分析', icon: <Icon name="bar-chart-01-stroked" /> },
223
226
  ];
224
227
 
228
+ const TEMPLATE_SEARCH_WRAP_STYLE = {
229
+ flex: '0 0 240px',
230
+ width: '240px',
231
+ minWidth: '240px',
232
+ maxWidth: '240px',
233
+ };
234
+
225
235
  export default function ChatHomePage() {
226
236
  return (
227
237
  <div
@@ -236,44 +246,46 @@ export default function ChatHomePage() {
236
246
  <NavBar platform="ola" selectedItemId="knowledge" />
237
247
  </div>
238
248
 
239
- <div className="flex flex-1 min-w-0 min-h-0 flex-col overflow-hidden">
249
+ <div className="flex flex-1 min-w-0 min-h-0 flex-col overflow-y-auto overflow-x-hidden">
240
250
  {/* Hero 区:欢迎语 + ChatInput */}
241
251
  <div
242
252
  className="flex flex-col items-center shrink-0"
243
253
  style={{
244
- padding: '120px 40px 80px',
254
+ padding: '84px 40px 80px',
245
255
  }}
246
256
  >
247
257
  <div className="flex flex-col items-center gap-2 w-full text-center">
248
- <h1 className="m-0 text-3xl [font-weight:var(--font-semibold)] leading-9" style={{ color: 'var(--foreground)' }}>
258
+ <h1 className="m-0 text-4xl [font-weight:var(--font-semibold)] leading-10" style={{ color: 'var(--foreground)' }}>
249
259
  今天想做什么?
250
260
  </h1>
251
261
  <p className="m-0 text-sm" style={{ color: 'var(--foreground-muted)' }}>
252
262
  从下方搜索助手,或直接输入你的需求,AI 帮你快速搞定
253
263
  </p>
254
264
  </div>
255
- <div className="w-full" style={{ maxWidth: '680px' }}>
265
+ <div className="w-full" style={{ maxWidth: '680px', marginTop: '48px' }}>
256
266
  <ChatInput variant="default" placeholder="描述你想完成的任务…" />
257
267
  </div>
258
268
  </div>
259
269
 
260
- {/* 筛选行:Tabs + 搜索,直接坐灰底 */}
270
+ {/* 筛选行:Tabs + 固定宽搜索,直接坐灰底 */}
261
271
  <div
262
- className="flex min-w-0 shrink-0 flex-wrap items-center justify-between gap-4"
272
+ className="flex min-w-0 shrink-0 items-center gap-4"
263
273
  style={{ padding: '16px 40px' }}
264
274
  >
265
- <Tabs variant="pill" size="sm" items={CATEGORY_TABS} defaultIndex={0} />
266
- <Input
267
- placeholder="搜索标题、描述"
268
- prefix={<Icon name="search-md-stroked" size="sm" />}
269
- allowClear
270
- className="max-w-full"
271
- style={{ flex: '0 1 240px', width: '240px', minWidth: '200px', '--size-input-width': '100%' }}
272
- />
275
+ <div className="flex shrink-0">
276
+ <Tabs variant="pill" size="sm" items={CATEGORY_TABS} defaultIndex={0} />
277
+ </div>
278
+ <div className="ml-auto shrink-0" style={TEMPLATE_SEARCH_WRAP_STYLE}>
279
+ <Input
280
+ placeholder="搜索标题、描述"
281
+ prefix={<Icon name="search-md-stroked" size="sm" />}
282
+ allowClear
283
+ />
284
+ </div>
273
285
  </div>
274
286
 
275
- {/* 卡片列表(可滚动):卡片自身是白卡,外层不包白卡 */}
276
- <div className="flex-1 min-h-0 overflow-y-auto" style={{ padding: '0 40px 40px' }}>
287
+ {/* 卡片列表:随右侧页面整体滚动,卡片自身是白卡,外层不包白卡 */}
288
+ <div className="shrink-0" style={{ padding: '0 40px 40px' }}>
277
289
  <div className="grid gap-4" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))' }}>
278
290
  {/* 👉 替换为业务卡片数据 */}
279
291
  <Card color="white" title="智能客服助手" description="基于大模型的全渠道客服对话助手" tags={['客服', '对话']} />