@tfdesign/b-end 1.0.12 → 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.
- package/README.md +23 -25
- package/package.json +1 -1
- package/skills/tfds/components.index.json +212 -51
- package/skills/tfds/components.summary.json +88 -49
- package/src/_b_end_runtime/components/ChatMessage.jsx +1 -1
- package/src/_b_end_runtime/components/Filter.jsx +390 -0
- package/src/_b_end_runtime/components/Filter.tokens.js +98 -0
- package/src/_b_end_runtime/components/Input.jsx +3 -1
- package/src/_b_end_runtime/components/Modal.jsx +10 -3
- package/src/_b_end_runtime/components.js +92 -1
- package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +14 -1
- package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +19 -12
- package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +14 -1
- package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +15 -2
- package/src/_b_end_runtime/page-patterns/pageListShared.jsx +54 -36
- package/src/_b_end_runtime/patterns.js +2 -2
- package/src/_b_end_runtime/preview-registry.jsx +95 -0
- package/src/index.d.ts +27 -0
- package/src/index.js +2 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// AvatarGroup:同一语境下「多人」的缩略表达(固定 5 人交叠),不做可配人数列表头。
|
|
10
10
|
// Icon:装饰性或功能性矢量符号(按钮内、列表前缀、空状态),不承担「人像」语义。
|
|
11
11
|
// Button:触发提交/打开弹窗/行内操作等离散动作;不负责「切换同页多块内容」。
|
|
12
|
+
// Filter:筛选栏中的单个筛选胶囊/筛选触发器;传入 options 时内置多选下拉面板。
|
|
12
13
|
// Tabs:同一容器内多块平级内容的视图切换(不离开当前页);不负责路由级站点导航。
|
|
13
14
|
// Input:用户自造字符串(名称、搜索词、备注);选项来自固定枚举时不要用 Input 冒充选择器。
|
|
14
15
|
// Select:从较多或会增长的枚举里选一个/多选(tag 模式);选项很少且需一眼比较时用 Radio。
|
|
@@ -570,6 +571,30 @@ const TAG_PREVIEW = {
|
|
|
570
571
|
},
|
|
571
572
|
};
|
|
572
573
|
|
|
574
|
+
const FILTER_PREVIEW = {
|
|
575
|
+
state: {
|
|
576
|
+
outline: { bgColor: '#FFFFFF', borderColor: '#E4E7EC', textColor: '#182230' },
|
|
577
|
+
fill: { bgColor: 'rgba(83, 96, 143, 0.07)', borderColor: '#E4E7EC', textColor: '#182230' },
|
|
578
|
+
selected: { bgColor: '#EAFAF6', borderColor: '#87DEC9', textColor: '#129683' },
|
|
579
|
+
disabled: { bgColor: '#F9FAFB', borderColor: '#E6E7EA', textColor: '#98A2B3' },
|
|
580
|
+
selectedDisabled: { bgColor: '#EAFAF6', borderColor: '#A8E8D7', textColor: '#87DEC9' },
|
|
581
|
+
},
|
|
582
|
+
base: {
|
|
583
|
+
height: '36px',
|
|
584
|
+
paddingLeft: '12px',
|
|
585
|
+
paddingRight: '12px',
|
|
586
|
+
paddingY: 'auto',
|
|
587
|
+
gap: '8px',
|
|
588
|
+
borderRadius: '9999px',
|
|
589
|
+
fontSize: '14px',
|
|
590
|
+
lineHeight: '20px',
|
|
591
|
+
labelWeight: '600',
|
|
592
|
+
valueWeight: '400',
|
|
593
|
+
iconSize: '12px',
|
|
594
|
+
clearIconSize: '16px',
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
|
|
573
598
|
const TAGINPUT_PREVIEW = {
|
|
574
599
|
status: {
|
|
575
600
|
default: { bgColor: '#FFFFFF', borderColor: 'rgba(45,66,107,0.12)', hoverBorderColor: '#D0D5DD', focusBorderColor: '#56D3BC' },
|
|
@@ -1610,7 +1635,7 @@ export const COMPONENTS = [
|
|
|
1610
1635
|
'【消息原子】一个 ChatMessage = AI 对话页中的一条消息原子,role="ai" 渲染 AI 消息(适配容器宽度),role="user" 渲染用户气泡(右对齐 + 8% 容器宽度左缩进 ≈ 500px 容器下 40px 缩进)',
|
|
1611
1636
|
'【AI 友好命名】props 一律用最短同义词:header / thinking / plan / confirms / followUps / actions / userContent;读 props 即知 DOM 结构(<ChatMessage header thinking plan ... />)',
|
|
1612
1637
|
'【7 类子组件】内部按 AI 头像 / 文本回复 / 执行流 / 卡片回复 / 深度思考 / 操作栏 / 用户气泡 7 类子组件按需组合,全部通过 props 启用,互相正交不冲突',
|
|
1613
|
-
'【theme 必引】入口 CSS 必须 @import "@
|
|
1638
|
+
'【theme 必引】入口 CSS 必须 @import "@tfdesign/b-end/theme.css"。ChatMessage 子区块使用 --color-border-default、--color-neutral、--color-neutral-500/300/700、--tfds-ai-execution-* 等;未引入 theme 时 var() 无效,描边易退化为浏览器默认深黑(追问按钮等)。',
|
|
1614
1639
|
'【禁止手搓描边】勿包一层 div 并加 border-black / ring-black;追问 followUps 为白底 + border-border-default + hover:border-border-strong。',
|
|
1615
1640
|
|
|
1616
1641
|
/* —— 1. AI 头像 —— */
|
|
@@ -3220,6 +3245,72 @@ export const COMPONENTS = [
|
|
|
3220
3245
|
'span rounded bg-', '手搓 Tag', '自制状态标签', 'px-2 py-1 bg-red-100',
|
|
3221
3246
|
],
|
|
3222
3247
|
},
|
|
3248
|
+
{
|
|
3249
|
+
id: 'filter',
|
|
3250
|
+
name: 'Filter',
|
|
3251
|
+
element: 'div',
|
|
3252
|
+
category: 'basic',
|
|
3253
|
+
description:
|
|
3254
|
+
'筛选胶囊项:用于筛选栏中的单个筛选触发器或已选条件展示,36px 高、全圆角、标签 semibold + 值 regular。支持点击展开下拉面板、多选、白底常态、填充态、品牌选中态、禁用态和可清除态。',
|
|
3255
|
+
componentFile: './components/Filter.jsx',
|
|
3256
|
+
tokensFile: './components/Filter.tokens.js',
|
|
3257
|
+
props: [
|
|
3258
|
+
{ name: 'label', type: 'string', default: '筛选项' },
|
|
3259
|
+
{ name: 'value', type: 'string|number|null', default: null },
|
|
3260
|
+
{ name: 'options', type: 'array', default: [] },
|
|
3261
|
+
{ name: 'selectedValues', type: 'array', default: undefined },
|
|
3262
|
+
{ name: 'defaultValue', type: 'array', default: [] },
|
|
3263
|
+
{ name: 'onChange', type: 'function', default: null },
|
|
3264
|
+
{ name: 'selected', type: 'boolean', default: false },
|
|
3265
|
+
{ name: 'filled', type: 'boolean', default: false },
|
|
3266
|
+
{ name: 'disabled', type: 'boolean', default: false },
|
|
3267
|
+
{ name: 'closable', type: 'boolean', default: false },
|
|
3268
|
+
{ name: 'onClear', type: 'function', default: null },
|
|
3269
|
+
{ name: 'className', type: 'string', default: '' },
|
|
3270
|
+
],
|
|
3271
|
+
labels: {
|
|
3272
|
+
selected: { true: '选中', false: '未选中' },
|
|
3273
|
+
filled: { true: '填充态', false: '白底态' },
|
|
3274
|
+
closable: { true: '清除图标', false: '下拉箭头' },
|
|
3275
|
+
disabled: { true: '禁用', false: '可交互' },
|
|
3276
|
+
},
|
|
3277
|
+
_preview: FILTER_PREVIEW,
|
|
3278
|
+
rules: [
|
|
3279
|
+
FONT_WEIGHT_RUNTIME_RULE,
|
|
3280
|
+
'【定位】Filter 是筛选栏中的单个胶囊触发器/已选筛选项,不是完整筛选条;完整筛选区域由多个 Filter 横向组合。',
|
|
3281
|
+
'【选型·vs Tag】Tag 是展示型短标签,不承载打开筛选面板;Filter 可点击、可打开筛选面板、可展示筛选值和清除入口。',
|
|
3282
|
+
'【选型·vs Button】Filter 只用于筛选条件选择/收起/清除;普通动作(提交、取消、导出、新建)仍使用 Button。',
|
|
3283
|
+
'【尺寸】固定高度 36px(--size-control-md),左右内距 12px(--spacing-3,对齐 Select md),内容 gap 8px(--spacing-2);不要随意压缩为 24px 或 32px,以保证和 B 端 Input/Select 默认高度对齐。',
|
|
3284
|
+
'【文字】label 使用 semibold 600,value 使用 normal 400;文案建议 label 2-6 字、value 1-8 字,过长值应在业务层截断或改用 Tooltip。',
|
|
3285
|
+
'【下拉多选】传入 options 后点击胶囊展开下拉面板;支持 selectedValues 受控、defaultValue 非受控、onChange(nextValues) 回调;点击外部或 Escape 关闭。',
|
|
3286
|
+
'【值展示】未显式传 value 时,单选中展示选项 label,多选中展示“已选 N 项”;显式 value 优先用于自定义展示。',
|
|
3287
|
+
'【状态】selected=true 使用品牌浅底 + 品牌描边 + 品牌文字;filled=true 使用中性填充底;disabled=true 使用禁用文字与禁用底,且不可点击。',
|
|
3288
|
+
'【图标】无选中值时显示 12px 下拉箭头;closable=true 或已选中且未禁用时显示 16px 清除按钮(视觉与 Select 清除入口一致),点击清空多选值、关闭下拉并触发 onClear,不触发展开。',
|
|
3289
|
+
'【语义结构】外层使用 role="combobox/button" 的 div 触发器,避免清除 button 嵌套在 button 内;不要改回 button 包 button 的非法 DOM。',
|
|
3290
|
+
'【组合】有值但仍可展开修改时传 options;已选条件需要一键移除时使用 closable 或依赖默认已选清除入口。',
|
|
3291
|
+
'【禁止手搓】不要用 div/span + rounded-full 自行拼筛选 chip;筛选入口统一使用 Filter 保证 token、字重、焦点态与禁用态一致。',
|
|
3292
|
+
],
|
|
3293
|
+
examples: [
|
|
3294
|
+
{ label: '基础筛选项', code: '<Filter label="筛选项" />' },
|
|
3295
|
+
{ label: '多选下拉', code: '<Filter label="筛选项" options={[{ label: "选项一", value: "1" }, { label: "选项二", value: "2" }]} />' },
|
|
3296
|
+
{ label: '默认已选', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} />' },
|
|
3297
|
+
{ label: '受控多选', code: '<Filter label="筛选项" options={options} selectedValues={values} onChange={setValues} />' },
|
|
3298
|
+
{ label: '选中态', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected />' },
|
|
3299
|
+
{ label: '填充态', code: '<Filter label="筛选项" filled />' },
|
|
3300
|
+
{ label: '可清除', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} filled closable onClear={() => {}} />' },
|
|
3301
|
+
{ label: '选中可清除', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected closable onClear={() => {}} />' },
|
|
3302
|
+
{ label: '禁用态', code: '<Filter label="筛选项" disabled />' },
|
|
3303
|
+
{ label: '禁用选中态', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected disabled />' },
|
|
3304
|
+
{ label: '❌ Bad(手搓筛选 chip)', code: '/* 禁止!筛选入口不要手写 span/div */\n<span className="rounded-full border px-4 py-2">筛选项</span>' },
|
|
3305
|
+
{ label: '✅ Good(用 Filter)', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected />' },
|
|
3306
|
+
],
|
|
3307
|
+
keywords: [
|
|
3308
|
+
'Filter', 'filter', '筛选', '筛选项', '筛选组件', '筛选胶囊', '筛选 chip',
|
|
3309
|
+
'筛选触发器', '已选筛选', '过滤条件', 'filter item', 'filter chip', 'pill filter',
|
|
3310
|
+
'closable', 'clear', '清除筛选', '下拉筛选', '多选筛选', 'selected filter',
|
|
3311
|
+
'span rounded-full border', '手搓筛选', '自制筛选 chip',
|
|
3312
|
+
],
|
|
3313
|
+
},
|
|
3223
3314
|
{
|
|
3224
3315
|
id: 'toast',
|
|
3225
3316
|
name: 'Toast',
|
|
@@ -34,7 +34,20 @@ import {
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
const PAGE_TITLE = 'MCP 工具管理';
|
|
37
|
-
const MCP_FILTERS = [
|
|
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="
|
|
174
|
-
style={{ flex: '0
|
|
187
|
+
className="shrink-0"
|
|
188
|
+
style={{ flex: '0 0 240px', '--size-input-width': '240px' }}
|
|
175
189
|
/>
|
|
176
190
|
|
|
177
|
-
{filters.map((
|
|
178
|
-
<
|
|
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
|
-
* · 筛选栏:搜索框 +
|
|
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
|
|
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
|
-
*
|
|
131
|
-
* + 1
|
|
169
|
+
* 结构:左侧固定 240px 搜索框 + 一组标准 Filter 基础组件
|
|
170
|
+
* + 1 个标准 Filter"我创建的"。
|
|
132
171
|
*
|
|
133
|
-
* @param {string
|
|
134
|
-
* @param {string} [checkboxLabel='我创建的'] — 末尾
|
|
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="
|
|
143
|
-
style={{ flex: '0
|
|
186
|
+
className="shrink-0"
|
|
187
|
+
style={{ flex: '0 0 240px', '--size-input-width': '240px' }}
|
|
144
188
|
/>
|
|
145
|
-
{
|
|
146
|
-
<
|
|
189
|
+
{filterItems.map((item) => (
|
|
190
|
+
<Filter key={item.label} {...item} />
|
|
147
191
|
))}
|
|
148
|
-
{
|
|
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
|
}
|
|
@@ -121,7 +121,7 @@ export default function CustomerServicePage() {
|
|
|
121
121
|
'【NavBar】通过 navItems 自定义菜单项(id / label / iconName),通过 selectedItemId 高亮当前菜单;菜单 id 与右侧模板一一对应',
|
|
122
122
|
'【列表页结构】标题栏 → 筛选栏 → 表格,垂直 16px 间距,与白卡四边各 24px 边距',
|
|
123
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` 仅为既有模板实现参考。',
|
|
124
|
-
'【筛选栏】搜索 Input
|
|
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 筛选按钮',
|
|
125
125
|
'【表单宽度判断】所有 form 类型组件都要按字段语义决定宽度,而不是统一 `w-full`:搜索/短关键词=中宽;枚举筛选/状态/时间范围=窄到中宽;名称/标题=中到宽;描述/备注/多行文本=宽或全宽;多值标签输入按内容与容器宽度决定',
|
|
126
126
|
'【双白卡变体】当列表需要左侧维度筛选(如按分类树过滤)时,在主白卡左侧再加一张**独立白圆角辅助大卡**包住 `TagBar`。该类“横向并列的大卡工作区”默认就要支持宽度拖拽:左卡用 `width + onWidthChange + minWidth + maxWidth + resizable`,外层保持 `overflow-visible` 以露出拖拽把手,右侧主卡保持 `flex-1 min-w-0` 弹性撑满;两白卡间距 8px。**勿**用 `tone="panel"` 代替白卡(panel 为浅灰侧条,不是白卡片)。参见 McpManagementPage',
|
|
127
127
|
'【多白卡分区(通用)】当页面右侧需要多个业务板块时,必须拆分为多个独立白卡(而不是一整张通栏白底背景)。推荐:灰底内容区外层统一用 `padding: 16px` 或等价 `margin: 16px` 外框节奏;框架层最外层白卡统一 `纯白背景 + 12px 圆角`,不加浅灰描边;白卡之间 `gap: 8px`;白卡内部布局用 `padding: 24px` + `gap: 16px`(参见 VariableManagementPage / McpManagementPage)。内部卡片、列表项、表单控件原有边框/描边保持不变。',
|
|
@@ -207,7 +207,7 @@ export default function MyPage({ defaultSelectedItemId = 'example-1' }) {
|
|
|
207
207
|
'【反例扫描】如果同一右侧首页函数里同时出现 `ChatInput`、模板 `Card` 网格,并且外层存在 `background: var(--color-surface)` / `bg-surface rounded-*` 包住 Hero + 输入框 + Tabs + 网格,说明 AI 把入口页误生成成白卡管理页,必须拆掉这层 wrapper。',
|
|
208
208
|
'【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。',
|
|
209
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
|
|
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
211
|
'【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;卡片使用 `Card color="white"`;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
|
|
212
212
|
'【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义',
|
|
213
213
|
],
|
|
@@ -37,6 +37,7 @@ import DatePicker from './components/DatePicker';
|
|
|
37
37
|
import TimePicker from './components/TimePicker';
|
|
38
38
|
import Toast from './components/Toast';
|
|
39
39
|
import Tag from './components/Tag';
|
|
40
|
+
import Filter from './components/Filter';
|
|
40
41
|
import TagInput from './components/TagInput';
|
|
41
42
|
import TagGridPreview from './components/TagGridPreview';
|
|
42
43
|
import TablePreview, {
|
|
@@ -77,6 +78,7 @@ import { DATEPICKER_TOKEN_MAP } from './components/DatePicker.tokens';
|
|
|
77
78
|
import { TIMEPICKER_TOKEN_MAP } from './components/TimePicker.tokens';
|
|
78
79
|
import { TOAST_TOKEN_MAP } from './components/Toast.tokens';
|
|
79
80
|
import { TAG_TOKEN_MAP } from './components/Tag.tokens';
|
|
81
|
+
import { FILTER_TOKEN_MAP } from './components/Filter.tokens';
|
|
80
82
|
import { TAGINPUT_TOKEN_MAP } from './components/TagInput.tokens';
|
|
81
83
|
import { TABLE_TOKEN_MAP } from './components/Table.tokens';
|
|
82
84
|
import { TOOLTIP_TOKEN_MAP } from './components/Tooltip.tokens';
|
|
@@ -112,6 +114,7 @@ import datePickerJsxRaw from './components/DatePicker.jsx?raw';
|
|
|
112
114
|
import timePickerJsxRaw from './components/TimePicker.jsx?raw';
|
|
113
115
|
import toastJsxRaw from './components/Toast.jsx?raw';
|
|
114
116
|
import tagJsxRaw from './components/Tag.jsx?raw';
|
|
117
|
+
import filterJsxRaw from './components/Filter.jsx?raw';
|
|
115
118
|
import tagInputJsxRaw from './components/TagInput.jsx?raw';
|
|
116
119
|
import tableJsxRaw from './components/Table.jsx?raw';
|
|
117
120
|
import tooltipJsxRaw from './components/Tooltip.jsx?raw';
|
|
@@ -136,6 +139,37 @@ function SwitchPreview({ variant = 'brand', defaultChecked, disabled }) {
|
|
|
136
139
|
);
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
const FILTER_SAMPLE_OPTIONS = [
|
|
143
|
+
{ label: '选项一', value: 'option-1' },
|
|
144
|
+
{ label: '选项二', value: 'option-2' },
|
|
145
|
+
{ label: '选项三', value: 'option-3' },
|
|
146
|
+
{ label: '禁用项', value: 'option-4', disabled: true },
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
function FilterPreview({
|
|
150
|
+
initial = 'empty',
|
|
151
|
+
disabled = false,
|
|
152
|
+
}) {
|
|
153
|
+
const [singleValues, setSingleValues] = useState(
|
|
154
|
+
initial === 'selected' ? ['option-1'] : [],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
setSingleValues(initial === 'selected' ? ['option-1'] : []);
|
|
159
|
+
}, [initial]);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<Filter
|
|
163
|
+
label="筛选项"
|
|
164
|
+
options={FILTER_SAMPLE_OPTIONS}
|
|
165
|
+
selectedValues={singleValues}
|
|
166
|
+
onChange={setSingleValues}
|
|
167
|
+
disabled={disabled}
|
|
168
|
+
onClear={() => {}}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
139
173
|
const FORM_CONTROL_PREVIEW_WIDTH_STYLE = {
|
|
140
174
|
width: 'min(300px, 100%)',
|
|
141
175
|
maxWidth: '100%',
|
|
@@ -4158,6 +4192,67 @@ export const PREVIEW_REGISTRY = {
|
|
|
4158
4192
|
},
|
|
4159
4193
|
},
|
|
4160
4194
|
|
|
4195
|
+
filter: {
|
|
4196
|
+
component: FilterPreview,
|
|
4197
|
+
tokenMap: FILTER_TOKEN_MAP,
|
|
4198
|
+
jsxSource: filterJsxRaw,
|
|
4199
|
+
|
|
4200
|
+
getPreviewAreaStyle: () => ({
|
|
4201
|
+
alignItems: 'center',
|
|
4202
|
+
justifyContent: 'center',
|
|
4203
|
+
overflow: 'auto',
|
|
4204
|
+
padding: '32px',
|
|
4205
|
+
}),
|
|
4206
|
+
|
|
4207
|
+
controls: [
|
|
4208
|
+
{
|
|
4209
|
+
id: 'initial',
|
|
4210
|
+
label: '初始值',
|
|
4211
|
+
type: 'seg',
|
|
4212
|
+
options: [
|
|
4213
|
+
{ id: 'empty', label: '空' },
|
|
4214
|
+
{ id: 'selected', label: '已选' },
|
|
4215
|
+
],
|
|
4216
|
+
default: 'empty',
|
|
4217
|
+
},
|
|
4218
|
+
{
|
|
4219
|
+
id: 'state',
|
|
4220
|
+
label: '交互',
|
|
4221
|
+
type: 'seg',
|
|
4222
|
+
options: [
|
|
4223
|
+
{ id: 'default', label: '可点' },
|
|
4224
|
+
{ id: 'disabled', label: '禁用' },
|
|
4225
|
+
],
|
|
4226
|
+
default: 'default',
|
|
4227
|
+
},
|
|
4228
|
+
],
|
|
4229
|
+
|
|
4230
|
+
mapProps: (cv) => ({
|
|
4231
|
+
initial: cv.initial || 'empty',
|
|
4232
|
+
disabled: cv.state === 'disabled',
|
|
4233
|
+
}),
|
|
4234
|
+
|
|
4235
|
+
generateUsage: (_enums, cv) => {
|
|
4236
|
+
const lines = [`import Filter from './components/Filter';`];
|
|
4237
|
+
const props = ['label="筛选项"'];
|
|
4238
|
+
props.push('options={options}');
|
|
4239
|
+
if ((cv.initial || 'empty') === 'selected') props.push('defaultValue={["option-1"]}');
|
|
4240
|
+
if (cv.state === 'disabled') props.push('disabled');
|
|
4241
|
+
|
|
4242
|
+
lines.push('');
|
|
4243
|
+
lines.push('const options = [');
|
|
4244
|
+
lines.push(' { label: "选项一", value: "option-1" },');
|
|
4245
|
+
lines.push(' { label: "选项二", value: "option-2" },');
|
|
4246
|
+
lines.push(' { label: "选项三", value: "option-3" },');
|
|
4247
|
+
lines.push('];');
|
|
4248
|
+
lines.push('');
|
|
4249
|
+
lines.push('<Filter');
|
|
4250
|
+
props.forEach((p) => lines.push(` ${p}`));
|
|
4251
|
+
lines.push('/>');
|
|
4252
|
+
return lines.join('\n');
|
|
4253
|
+
},
|
|
4254
|
+
},
|
|
4255
|
+
|
|
4161
4256
|
'tag-input': {
|
|
4162
4257
|
component: TagInputPreview,
|
|
4163
4258
|
tokenMap: TAGINPUT_TOKEN_MAP,
|
package/src/index.d.ts
CHANGED
|
@@ -951,6 +951,33 @@ export interface TagProps extends TfdsCommonProps {
|
|
|
951
951
|
}
|
|
952
952
|
export const Tag: React.FC<TagProps>;
|
|
953
953
|
|
|
954
|
+
/** Filter — 筛选胶囊项:用于筛选栏中的单个筛选触发器或已选条件展示,36px 高、全圆角、标签 semibold + 值 regular。支持点击展开下拉面板、多选、白底常态、填充态、品牌选中态、禁用态和可清除态。 */
|
|
955
|
+
export interface FilterProps extends TfdsCommonProps {
|
|
956
|
+
/** string, default: "筛选项" */
|
|
957
|
+
label?: string;
|
|
958
|
+
/** string|number|null, default: null */
|
|
959
|
+
value?: unknown;
|
|
960
|
+
/** array, default: [] */
|
|
961
|
+
options?: unknown[];
|
|
962
|
+
/** array, default: undefined */
|
|
963
|
+
selectedValues?: unknown[];
|
|
964
|
+
/** array, default: [] */
|
|
965
|
+
defaultValue?: unknown[];
|
|
966
|
+
/** function, default: null */
|
|
967
|
+
onChange?: (...args: any[]) => any;
|
|
968
|
+
/** boolean, default: false */
|
|
969
|
+
selected?: boolean;
|
|
970
|
+
/** boolean, default: false */
|
|
971
|
+
filled?: boolean;
|
|
972
|
+
/** boolean, default: false */
|
|
973
|
+
disabled?: boolean;
|
|
974
|
+
/** boolean, default: false */
|
|
975
|
+
closable?: boolean;
|
|
976
|
+
/** function, default: null */
|
|
977
|
+
onClear?: (...args: any[]) => any;
|
|
978
|
+
}
|
|
979
|
+
export const Filter: React.FC<FilterProps>;
|
|
980
|
+
|
|
954
981
|
/** Toast — 轻量反馈条:信息/成功/警示/错误四态,左侧状态图标(相对行首额外 4px 左边距)+ 主文案 + 可选文字操作(绿色文字 Button)+ 可关闭;宽度随内容收缩(w-fit),最大不超过 min(100%,560px);圆角 12px、可选语义描边(bordered)、内距 12px、主文案 14px 半粗,行内纵向居中对齐。规范仅覆盖单条条目视觉;全局队列、停留时长与 Portal 由业务集成(对齐 HiUI Toast)。 */
|
|
955
982
|
export interface ToastProps extends TfdsCommonProps {
|
|
956
983
|
/** enum<info | success | warning | error>, default: "info" */
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @tfdesign/b-end 公开 API(由 scripts/generate-tfds-b-end-bundle.mjs 生成,勿手改)
|
|
3
3
|
* 实现位于包内 ./src/_b_end_runtime/(与主仓 src/data/systems/b-end 同步,可独立 npm 安装)
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -35,6 +35,7 @@ export { default as Modal } from './_b_end_runtime/components/Modal.jsx';
|
|
|
35
35
|
export { default as Sheet } from './_b_end_runtime/components/Sheet.jsx';
|
|
36
36
|
export { default as TagInput } from './_b_end_runtime/components/TagInput.jsx';
|
|
37
37
|
export { default as Tag } from './_b_end_runtime/components/Tag.jsx';
|
|
38
|
+
export { default as Filter } from './_b_end_runtime/components/Filter.jsx';
|
|
38
39
|
export { default as Toast } from './_b_end_runtime/components/Toast.jsx';
|
|
39
40
|
export { default as Tooltip } from './_b_end_runtime/components/Tooltip.jsx';
|
|
40
41
|
export { default as Empty } from './_b_end_runtime/components/Empty.jsx';
|