@zjlab-fe/data-hub-ui 0.32.5 → 0.32.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.
@@ -1,17 +1,19 @@
1
1
  # @zjlab-fe/data-hub-ui 组件库 Skill 参考文档
2
2
 
3
- > **版本**:0.14.2 | **包名**:`@zjlab-fe/data-hub-ui` | **自动生成,请勿手动编辑**
3
+ > **版本**:0.26.4 | **包名**:`@zjlab-fe/data-hub-ui` | **自动生成,请勿手动编辑**
4
4
  >
5
5
  > 本文档包含所有公开组件的 API 参考与使用说明,供 AI 编码助手作为 skill 参考。
6
6
  > 安装:`npm install @zjlab-fe/data-hub-ui`
7
7
 
8
8
  ## 目录
9
9
 
10
+ - [SDK-modal](#SDK-modal)
10
11
  - [apply-perm-modal(申请权限弹窗)](#apply-perm-modal)
11
12
  - [auth-tag(权限标签)](#auth-tag)
12
13
  - [bench-card(卡片)](#bench-card)
13
14
  - [confirm-again(二次确认)](#confirm-again)
14
15
  - [copy(复制)](#copy)
16
+ - [corpus-card](#corpus-card)
15
17
  - [feature-card(功能点卡片)](#feature-card)
16
18
  - [file-preview(文件预览)](#file-preview)
17
19
  - [file-uploader(文件上传)](#file-uploader)
@@ -20,16 +22,203 @@
20
22
  - [header(头部导航)](#header)
21
23
  - [input-tag(标签/关键字输入)](#input-tag)
22
24
  - [menu(菜单)](#menu)
25
+ - [model-card](#model-card)
26
+ - [notion-editor](#notion-editor)
23
27
  - [operator-chain(卡片式操作链)](#operator-chain)
24
28
  - [permission-editor(权限编辑器)](#permission-editor)
25
29
  - [popover-select(带描述浮层下拉框)](#popover-select)
26
30
  - [radio-card(单选卡片)](#radio-card)
27
31
  - [section-heading(区块标题)](#section-heading)
28
32
  - [status-tag(状态标签)](#status-tag)
33
+ - [tag-group-filter(标签组过滤器)](#tag-group-filter)
34
+ - [tag-view(标签视图)](#tag-view)
29
35
  - [tip-tap(富文本编辑器)](#tip-tap)
30
36
 
31
37
  ---
32
38
 
39
+ ## SDK-modal
40
+
41
+ **导入方式:**
42
+
43
+ ```ts
44
+ import { SDKModal } from '@zjlab-fe/data-hub-ui';
45
+ ```
46
+
47
+ 用于展示 SDK 使用代码的弹窗组件。支持静态传入代码和异步加载两种方式,同时支持组件式和命令式两种调用形式。
48
+
49
+ ### 代码演示
50
+
51
+ **示例代码(index.tsx):**
52
+
53
+ ```tsx
54
+ import { useState } from 'react';
55
+ import { Button, Space } from 'antd';
56
+ import SDKModal from '@/components/SDK-modal';
57
+
58
+ const MOCK_SDK_CODE = `import mktable
59
+
60
+ client = mktable.Client(
61
+ host="https://api.example.com",
62
+ token="your-access-token"
63
+ )
64
+
65
+ dataset = client.dataset.get("ds-xxxxxxxx")
66
+ df = dataset.to_dataframe()`;
67
+
68
+ function mockLoadSDK(): Promise<string> {
69
+ return new Promise((resolve) => {
70
+ setTimeout(() => resolve(MOCK_SDK_CODE), 1200);
71
+ });
72
+ }
73
+
74
+ function mockLoadSDKError(): Promise<string> {
75
+ return new Promise((_, reject) => {
76
+ setTimeout(() => reject(new Error('加载失败')), 1200);
77
+ });
78
+ }
79
+
80
+ export default function Demo() {
81
+ const [open, setOpen] = useState(false);
82
+ const [openAsync, setOpenAsync] = useState(false);
83
+ const [openAsyncError, setOpenAsyncError] = useState(false);
84
+
85
+ const handleShowImperative = () => {
86
+ SDKModal.show({
87
+ title: '命令式 · 静态 SDK 代码',
88
+ SDKCode: MOCK_SDK_CODE,
89
+ documentLink: 'https://example.com/docs/sdk',
90
+ onClose: () => console.log('命令式弹窗已关闭'),
91
+ });
92
+ };
93
+
94
+ const handleShowImperativeAsync = () => {
95
+ SDKModal.show({
96
+ title: '命令式 · 异步加载 SDK 代码',
97
+ loadSDK: mockLoadSDK,
98
+ documentLink: 'https://example.com/docs/sdk',
99
+ locale: 'en',
100
+ onClose: () => console.log('命令式异步弹窗已关闭'),
101
+ });
102
+ };
103
+
104
+ return (
105
+ <div style={{ padding: 24 }}>
106
+ # SDK Modal 使用示例
107
+
108
+ ### 1. 组件式 · 静态 SDK 代码
109
+ <p>直接传入 SDKCode,弹窗打开即展示,无需异步加载。</p>
110
+ <Button onClick={() => setOpen(true)}>打开(静态)</Button>
111
+ <SDKModal
112
+ open={open}
113
+ title="组件式 · Python SDK 下载代码"
114
+ SDKCode={MOCK_SDK_CODE}
115
+ documentLink="https://example.com/docs/sdk"
116
+ onClose={() => setOpen(false)}
117
+ />
118
+
119
+ ### 2. 组件式 · 异步加载 SDK 代码
120
+ <p>通过 loadSDK 异步获取代码,弹窗打开时展示 Loading 状态。</p>
121
+ <Button onClick={() => setOpenAsync(true)}>打开(异步)</Button>
122
+ <SDKModal
123
+ open={openAsync}
124
+ title="组件式 · 异步加载 SDK"
125
+ loadSDK={mockLoadSDK}
126
+ documentLink="https://example.com/docs/sdk"
127
+ locale="zh"
128
+ onClose={() => setOpenAsync(false)}
129
+ />
130
+
131
+ ### 3. 组件式 · 异步加载 SDK 代码(失败)
132
+ <p>通过 loadSDK 异步获取代码,加载失败时展示错误信息。</p>
133
+ <Button onClick={() => setOpenAsyncError(true)}>打开(异步)</Button>
134
+ <SDKModal
135
+ open={openAsyncError}
136
+ title="组件式 · 异步加载 SDK"
137
+ loadSDK={mockLoadSDKError}
138
+ documentLink="https://example.com/docs/sdk"
139
+ locale="zh"
140
+ onClose={() => setOpenAsyncError(false)}
141
+ />
142
+
143
+ ### 3. 命令式 · 静态 SDK 代码
144
+ <p>调用 SDKModal.show(),无需在 JSX 中声明,适合在事件回调中使用。</p>
145
+ <Space>
146
+ <Button onClick={handleShowImperative}>打开(静态)</Button>
147
+ <Button onClick={handleShowImperativeAsync}>打开(异步 + 英文 locale)</Button>
148
+ </Space>
149
+ </div>
150
+ );
151
+ }
152
+ ```
153
+ ### 调用方式
154
+
155
+ #### 组件式
156
+
157
+ ```tsx
158
+ import SDKModal from '@/components/SDK-modal';
159
+
160
+ <SDKModal
161
+ open={open}
162
+ title="Python SDK 下载代码"
163
+ SDKCode={code}
164
+ documentLink="https://example.com/docs/sdk"
165
+ onClose={() => setOpen(false)}
166
+ />
167
+ ```
168
+
169
+ #### 命令式
170
+
171
+ ```tsx
172
+ import SDKModal from '@/components/SDK-modal';
173
+
174
+ SDKModal.show({
175
+ title: 'Python SDK 下载代码',
176
+ loadSDK: () => fetchSDKCode(),
177
+ documentLink: 'https://example.com/docs/sdk',
178
+ onClose: () => console.log('closed'),
179
+ });
180
+ ```
181
+
182
+ ### API
183
+
184
+ #### SDKModalProps
185
+
186
+ | 属性 | 说明 | 类型 | 默认值 |
187
+ |------|------|------|--------|
188
+ | open | 弹窗是否可见 | `boolean` | — |
189
+ | title | 弹窗标题 | `string` | — |
190
+ | onClose | 关闭回调 | `() => void` | — |
191
+ | SDKCode | 静态 SDK 代码字符串,与 `loadSDK` 二选一 | `string` | — |
192
+ | loadSDK | 异步获取 SDK 代码的函数,与 `SDKCode` 二选一 | `() => Promise<string>` | — |
193
+ | modalConfig | 覆盖默认 Modal 配置,与 antd ModalProps 一致 | `ModalProps` | — |
194
+ | documentLink | 文档链接地址,有值时在代码块下方展示链接 | `string` | — |
195
+ | locale | 语言,`'zh'` 中文 / `'en'` 英文 | `'zh' \| 'en'` | `'zh'` |
196
+
197
+ #### SDKModalConfig
198
+
199
+ `SDKModalConfig = Omit<SDKModalProps, 'open'>`,用于命令式调用 `SDKModal.show(config)`。
200
+
201
+ #### 内置语言包
202
+
203
+ | locale 值 | 语言 | confirm | documentPrefix | documentLinkText |
204
+ |-----------|------|---------|----------------|-----------------|
205
+ | `'zh'`(默认)| 中文 | 知道了 | 详情请查看 | 使用文档 |
206
+ | `'en'` | 英文 | Got it | For more details, see | Documentation |
207
+
208
+ ```tsx
209
+ SDKModal.show({
210
+ title: 'Python SDK',
211
+ SDKCode: code,
212
+ locale: 'en',
213
+ onClose: () => {},
214
+ });
215
+ ```
216
+
217
+ ### 务必遵守以下事项,禁止逾越
218
+ 1. `SDKCode` 和 `loadSDK`选择一项传入, 禁止同时传入。同时传入时,仅生效 `SDKCode`。
219
+
220
+ ---
221
+
33
222
  ## apply-perm-modal(申请权限弹窗)
34
223
 
35
224
  **导入方式:**
@@ -299,6 +488,243 @@ copy函数入参如下:
299
488
 
300
489
  ---
301
490
 
491
+ ## corpus-card
492
+
493
+ **导入方式:**
494
+
495
+ ```ts
496
+ import { CorpusCard } from '@zjlab-fe/data-hub-ui';
497
+ ```
498
+
499
+ 基于 React + Ant Design 实现的语料卡片组件,支持卡片点击激活、详情按钮、标签展示等功能,适用于语料库、模板列表等场景。
500
+
501
+ ### 代码演示
502
+
503
+ **示例代码(index.tsx):**
504
+
505
+ ```tsx
506
+ import CorpusCard from '@/components/corpus-card';
507
+ import mockdata from './mockdata';
508
+ import { useState } from 'react';
509
+
510
+ export default function Demo() {
511
+ const [activeCardId, setActiveCardId] = useState<number | null>(null);
512
+
513
+ const onDetailClick = (id: number) => {
514
+ console.log('详情点击', id);
515
+ };
516
+
517
+ const onCardClick = (id: number) => {
518
+ console.log('卡片点击', id);
519
+ setActiveCardId(id);
520
+ };
521
+
522
+ return (
523
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}>
524
+ {mockdata.data.totalList.map((item) => (
525
+ <CorpusCard
526
+ card={item}
527
+ key={item.id}
528
+ isActive={activeCardId === item.id}
529
+ onClick={onDetailClick}
530
+ onCardClick={onCardClick}
531
+ width={480}
532
+ />
533
+ ))}
534
+ </div>
535
+ );
536
+ }
537
+ ```
538
+ ### API
539
+
540
+ #### CorpusCard
541
+
542
+ | 参数 | 说明 | 类型 | 默认值 | 必须 |
543
+ | --------- | ---------- | -------------------------- | ------- | -- |
544
+ | card | 卡片数据 | `ProcessTemplateListItem` | - | 是 |
545
+ | isActive | 卡片是否激活 | `boolean` | `false` | 否 |
546
+ | width | 卡片宽度 | `number` | `375` | 否 |
547
+ | onClick | 详情按钮点击回调 | `(id: number) => void` | - | 否 |
548
+ | onCardClick | 卡片点击回调 | `(id: number) => void` | - | 否 |
549
+
550
+ ### 接口定义
551
+
552
+ #### ProcessTemplateListItem 接口
553
+
554
+ 卡片数据接口(前端只关注这几个就行)
555
+
556
+ | 属性 | 说明 | 类型 | 必须 |
557
+ | ----------------- | ------------ | ------------------- | -- |
558
+ | id | 卡片唯一标识 | `number` | 是 |
559
+ | name | 卡片标题 | `string` | 是 |
560
+ | description | 卡片描述 | `string` | 是 |
561
+ | labels | 标签列表 | `ProcessTemplateLabel[]` | 是 |
562
+ | labelTypeColorMap | 标签类型颜色映射 | `LabelTypeColor[]` | 否 |
563
+
564
+ #### ProcessTemplateLabel 接口
565
+
566
+ 标签数据接口
567
+
568
+ | 属性 | 说明 | 类型 | 必须 |
569
+ | ----- | ---- | ------- | -- |
570
+ | id | 标签ID | `number` | 是 |
571
+ | name | 标签名称 | `string` | 是 |
572
+ | type | 标签类型 | `number` | 是 |
573
+
574
+ #### LabelTypeColor 接口
575
+
576
+ 标签类型颜色配置接口
577
+
578
+ | 属性 | 说明 | 类型 | 必须 |
579
+ | ----------- | ---- | ------- | -- |
580
+ | type | 标签类型 | `number` | 是 |
581
+ | bgColor | 背景颜色 | `string` | 是 |
582
+ | borderColor | 边框颜色 | `string` | 是 |
583
+ | textColor | 文字颜色 | `string` | 是 |
584
+
585
+ ### 使用说明
586
+
587
+ #### 1. 基础用法
588
+
589
+ ```tsx
590
+ import CorpusCard from '@/components/corpus-card';
591
+
592
+ const cardData = {
593
+ id: 1,
594
+ name: '语料模板1',
595
+ description: '这是一个示例语料模板描述',
596
+ labels: [
597
+ { id: 1, name: '数据处理', type: 1 },
598
+ { id: 2, name: '文本分析', type: 2 },
599
+ ],
600
+ };
601
+
602
+ const App = () => {
603
+ const handleDetailClick = (id: number) => {
604
+ console.log('详情点击', id);
605
+ };
606
+
607
+ const handleCardClick = (id: number) => {
608
+ console.log('卡片点击', id);
609
+ };
610
+
611
+ return (
612
+ <CorpusCard
613
+ card={cardData}
614
+ onClick={handleDetailClick}
615
+ onCardClick={handleCardClick}
616
+ />
617
+ );
618
+ };
619
+ ```
620
+
621
+ #### 2. 卡片激活状态
622
+
623
+ ```tsx
624
+ import { useState } from 'react';
625
+ import CorpusCard from '@/components/corpus-card';
626
+
627
+ const App = () => {
628
+ const [activeCardId, setActiveCardId] = useState<number | null>(null);
629
+
630
+ const handleCardClick = (id: number) => {
631
+ setActiveCardId(id);
632
+ };
633
+
634
+ return (
635
+ <div>
636
+ {cards.map((card) => (
637
+ <CorpusCard
638
+ key={card.id}
639
+ card={card}
640
+ isActive={activeCardId === card.id}
641
+ onCardClick={handleCardClick}
642
+ />
643
+ ))}
644
+ </div>
645
+ );
646
+ };
647
+ ```
648
+
649
+ #### 3. 自定义卡片宽度
650
+
651
+ ```tsx
652
+ import CorpusCard from '@/components/corpus-card';
653
+
654
+ const App = () => {
655
+ return (
656
+ <CorpusCard
657
+ card={cardData}
658
+ width={400}
659
+ onClick={handleDetailClick}
660
+ onCardClick={handleCardClick}
661
+ />
662
+ );
663
+ };
664
+ ```
665
+
666
+ #### 4. 网格布局(每行平铺三个卡片)
667
+
668
+ ```tsx
669
+ import CorpusCard from '@/components/corpus-card';
670
+
671
+ const App = () => {
672
+ return (
673
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}>
674
+ {cards.map((card) => (
675
+ <CorpusCard
676
+ key={card.id}
677
+ card={card}
678
+ onClick={handleDetailClick}
679
+ onCardClick={handleCardClick}
680
+ />
681
+ ))}
682
+ </div>
683
+ );
684
+ };
685
+ ```
686
+
687
+ #### 5. 带标签颜色映射
688
+
689
+ ```tsx
690
+ import CorpusCard from '@/components/corpus-card';
691
+
692
+ const cardData = {
693
+ id: 1,
694
+ name: '语料模板1',
695
+ description: '这是一个示例语料模板描述',
696
+ labels: [
697
+ { id: 1, name: '数据处理', type: 1 },
698
+ { id: 2, name: '文本分析', type: 2 },
699
+ ],
700
+ labelTypeColorMap: [
701
+ { type: 1, bgColor: '#e6f7ff', borderColor: '#91d5ff', textColor: '#1890ff' },
702
+ { type: 2, bgColor: '#f6ffed', borderColor: '#b7eb8f', textColor: '#52c41a' },
703
+ ],
704
+ };
705
+
706
+ const App = () => {
707
+ return (
708
+ <CorpusCard
709
+ card={cardData}
710
+ onClick={handleDetailClick}
711
+ onCardClick={handleCardClick}
712
+ />
713
+ );
714
+ };
715
+ ```
716
+
717
+ ### 注意事项
718
+
719
+ 1. 卡片默认宽度为 375px,可通过 `width` 属性自定义
720
+ 2. 激活状态下卡片会有蓝色边框和背景色渐变效果
721
+ 3. 详情按钮点击不会触发卡片点击事件(已阻止事件冒泡)
722
+ 4. 标签颜色可通过 `labelTypeColorMap` 自定义,未配置则使用默认样式
723
+ 5. 卡片标题和描述支持文本溢出省略显示
724
+ 6. 建议使用 CSS Grid 布局实现卡片网格排列,如每行平铺三个卡片
725
+
726
+ ---
727
+
302
728
  ## feature-card(功能点卡片)
303
729
 
304
730
  **导入方式:**
@@ -674,7 +1100,7 @@ export default function Demo() {
674
1100
  'xlsx/csv': 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/method_Caledonian.csv',
675
1101
  pdf: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/1.pdf',
676
1102
  image: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/figure9.png',
677
- markdown: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/b1047.md',
1103
+ markdown: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/formula.md',
678
1104
  txt: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/test.txt',
679
1105
  json: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/test/test.json',
680
1106
  jsonl: 'https://haina-datahub.zero2x.org/ossRoute/frontend/test/ocr_test.jsonl',
@@ -852,6 +1278,9 @@ export default function Demo() {
852
1278
  openInModal={false}
853
1279
  modalProps={{ show, onCancel: () => setShow(false) }}
854
1280
  filePath={fileMap['pdf']}
1281
+ httpHeaders={{
1282
+ Authorization: 'Bearer 123456',
1283
+ }}
855
1284
  />
856
1285
  </div>
857
1286
  ),
@@ -865,6 +1294,9 @@ export default function Demo() {
865
1294
  openInModal={false}
866
1295
  modalProps={{ show, onCancel: () => setShow(false) }}
867
1296
  filePath={fileMap['image']}
1297
+ httpHeaders={{
1298
+ Authorization: 'Bearer 123456',
1299
+ }}
868
1300
  />
869
1301
  </div>
870
1302
  ),
@@ -2273,7 +2705,7 @@ export default function Demo() {
2273
2705
  import { Menu } from '@zjlab-fe/data-hub-ui';
2274
2706
  ```
2275
2707
 
2276
- 符合团队设计规范的menu菜单
2708
+ 基于 React + Ant Design 实现的后台系统左侧可折叠侧边栏菜单组件,支持路由菜单、外链菜单、自动路由匹配选中、折叠/展开切换、折叠态 Tooltip 提示,适配企业级后台布局规范。
2277
2709
 
2278
2710
  ### 代码演示
2279
2711
 
@@ -2283,64 +2715,646 @@ import { Menu } from '@zjlab-fe/data-hub-ui';
2283
2715
  import { useState } from 'react';
2284
2716
  import { DatabaseOutlined, TagsOutlined, FilterOutlined } from '@ant-design/icons';
2285
2717
  import Menu from '../index';
2286
- const menus = [
2287
- {
2288
- label: '数据增强',
2289
- key: 'data-enhance',
2290
- icon: <DatabaseOutlined></DatabaseOutlined>,
2291
- },
2718
+ import { MenuItem } from '../index';
2719
+
2720
+ import { Layout, theme } from 'antd';
2721
+ const { Content } = Layout;
2722
+
2723
+ const menus: MenuItem[] = [
2292
2724
  {
2293
2725
  label: '数据管理',
2294
2726
  key: 'data-management',
2295
- icon: <TagsOutlined />,
2727
+ icon: <DatabaseOutlined />,
2728
+ children: [
2729
+ {
2730
+ label: '数据集',
2731
+ key: 'data-management-dataset',
2732
+ icon: <DatabaseOutlined />,
2733
+ },
2734
+ ],
2735
+ },
2736
+ {
2737
+ label: '数据加工',
2738
+ key: 'data-processing',
2739
+ children: [
2740
+ {
2741
+ label: '语料生产服务',
2742
+ key: 'data-processing-corpus',
2743
+ icon: <TagsOutlined />,
2744
+ },
2745
+ {
2746
+ label: '语料生产服务',
2747
+ key: '_blank',
2748
+ type: 'link',
2749
+ icon: <FilterOutlined />,
2750
+ },
2751
+ {
2752
+ label: '可视化工作流',
2753
+ key: 'data-processing-workflow',
2754
+ icon: <FilterOutlined />,
2755
+ },
2756
+ ],
2757
+ },
2758
+ {
2759
+ label: '特色工具',
2760
+ key: 'feature-tools',
2761
+ children: [
2762
+ {
2763
+ label: '网页语料生产',
2764
+ key: 'feature-tools-web',
2765
+ icon: <TagsOutlined />,
2766
+ },
2767
+ {
2768
+ label: '文档语料生产',
2769
+ key: 'feature-tools-doc',
2770
+ icon: <TagsOutlined />,
2771
+ },
2772
+ ],
2773
+ },
2774
+ {
2775
+ label: '数据标注',
2776
+ key: 'data-labeling',
2777
+ children: [
2778
+ {
2779
+ label: '文档标注',
2780
+ key: 'data-labeling-doc',
2781
+ icon: <TagsOutlined />,
2782
+ },
2783
+ ],
2784
+ },
2785
+ {
2786
+ label: '模型服务',
2787
+ key: 'model-service',
2788
+ children: [
2789
+ {
2790
+ label: '模型训练',
2791
+ key: 'model-service-train',
2792
+ icon: <FilterOutlined />,
2793
+ },
2794
+ {
2795
+ label: '模型部署',
2796
+ key: 'model-service-deploy',
2797
+ icon: <FilterOutlined />,
2798
+ },
2799
+ {
2800
+ label: '模型管理',
2801
+ key: 'model-service-manage',
2802
+ icon: <FilterOutlined />,
2803
+ },
2804
+ ],
2296
2805
  },
2297
- { label: '模型管理', key: 'model-management', icon: <FilterOutlined /> },
2298
2806
  ];
2299
-
2300
2807
  export default function Demo() {
2301
2808
  const [collapsed, setCollapsed] = useState(false);
2809
+ const [selectedKeys, setSelectedKeys] = useState<string[]>(['data-management-dataset']);
2810
+
2811
+ const {
2812
+ token: { colorBgContainer, borderRadiusLG },
2813
+ } = theme.useToken();
2814
+
2815
+ const handleMenuClick = (item: MenuItem) => {
2816
+ console.log('当前点击的菜单', item);
2817
+ const key = item.key;
2818
+ setSelectedKeys([key]);
2819
+ };
2820
+
2821
+ const handleCollapse = (collapsed: boolean) => {
2822
+ setCollapsed(collapsed);
2823
+ console.log('当前菜单是否收起', collapsed);
2824
+ };
2302
2825
 
2303
2826
  return (
2304
- <>
2305
- <Menu title="数据增强" menus={menus} collapsed={collapsed} onCollapse={setCollapsed}></Menu>
2306
- </>
2827
+ <Layout style={{ minHeight: '100vh' }}>
2828
+ <Menu
2829
+ menus={menus}
2830
+ onClick={handleMenuClick}
2831
+ onCollapse={handleCollapse}
2832
+ collapsed={collapsed}
2833
+ selectedKeys={selectedKeys}
2834
+ />
2835
+ <Layout>
2836
+ <Content
2837
+ style={{
2838
+ margin: '24px 16px',
2839
+ padding: 24,
2840
+ minHeight: 280,
2841
+ background: colorBgContainer,
2842
+ borderRadius: borderRadiusLG,
2843
+ }}
2844
+ >
2845
+ <p>当前选中的菜单: {selectedKeys || '无'}</p>
2846
+ <p>菜单状态: {collapsed ? '收起' : '展开'}</p>
2847
+ <p>右侧内容区域,会根据菜单的展开/收起自动调整宽度</p>
2848
+ </Content>
2849
+ </Layout>
2850
+ </Layout>
2307
2851
  );
2308
2852
  }
2309
2853
  ```
2310
2854
  ### API
2311
2855
 
2312
- | 参数 | 说明 | 类型 | 默认值 | 版本 | 必须 |
2313
- | --- | --- | --- | --- | --- | --- |
2314
- | menus | 菜单项目 |
2315
- | title | 菜单标题 |
2316
- | collapsed | 是否折叠 | `boolean` | - | - | |
2317
- | onCollapse | 折叠的回调 | `(e: React.MouseEvent<HTMLButtonElement>) => void;` | - | - | |
2856
+ #### Menu
2318
2857
 
2319
- ---
2858
+ | 参数 | 说明 | 类型 | 默认值 | 必须 |
2859
+ | ----------- | --------------------- | ------------------------------- | ------- | -- |
2860
+ | menus | 菜单配置数据,支持一级分组 + 二级子菜单 | `MenuItem[]` | - | 是 |
2861
+ | collapsed | 侧边栏是否折叠(受控属性) | `boolean` | `false` | 否 |
2862
+ | onCollapse | 折叠状态切换的回调函数 | `(collapsed: boolean) => void` | - | 否 |
2863
+ | onClick | 菜单点击时的回调函数 | `(item: MenuItem) => void` | - | 否 |
2864
+ | onSelect | 菜单选中项变化的回调函数 | `(key: string \| null) => void` | - | 否 |
2320
2865
 
2321
- ## operator-chain(卡片式操作链)
2866
+ ### 接口定义
2322
2867
 
2323
- **导入方式:**
2868
+ #### MenuType 类型
2324
2869
 
2325
- ```ts
2326
- import { OperatorChain } from '@zjlab-fe/data-hub-ui';
2327
- ```
2328
- ```ts
2329
- // 类型导入
2330
- import type { OperatorChainProps, SingleCardItem, OperatorChainRef } from '@zjlab-fe/data-hub-ui';
2331
- ```
2870
+ 菜单类型枚举
2332
2871
 
2333
- 用于展示和管理算子流程的链式组件
2334
- 支持数据导入导出节点
2335
- 支持是否可编辑模式
2336
- 符合团队样式规范
2337
-
2338
- ### 代码演示
2339
-
2340
- **示例代码(index.tsx):**
2872
+ | 取值 | 说明 |
2873
+ | --------- | -------------- |
2874
+ | `default` | 默认路由菜单(点击跳转路由) |
2875
+ | `link` | 外链菜单(点击不跳转路由) |
2876
+
2877
+ #### MenuItem 接口
2878
+
2879
+ 菜单项配置接口
2880
+
2881
+ | 属性 | 说明 | 类型 | 必须 |
2882
+ | ---------- | ------------- | ----------------- | -------------- |
2883
+ | label | 菜单显示文本 | `string` | 是 |
2884
+ | key | 菜单唯一标识/路由跳转路径 | `string` | 是 |
2885
+ | type | 菜单类型 | `MenuType` | 否(默认`default`) |
2886
+ | icon | 菜单默认图标 | `React.ReactNode` | 否 |
2887
+ | activeicon | 菜单激活态图标(预留) | `React.ReactNode` | 否 |
2888
+ | children | 二级子菜单列表 | `MenuItem[]` | 否 |
2889
+
2890
+ ### 使用说明
2891
+
2892
+ #### 1. 基础用法(路由菜单)
2341
2893
 
2342
2894
  ```tsx
2343
- import { Button, Flex, message } from 'antd';
2895
+ import Menu from '@/components/menu';
2896
+ import { HomeOutlined, SettingOutlined } from '@ant-design/icons';
2897
+
2898
+ // 重要:组件仅生效于菜单第二层级,一级菜单仅作为分组,不支持点击、激活等交互
2899
+ const menus = [
2900
+ {
2901
+ label: '数据管理',
2902
+ children: [
2903
+ { label: '用户管理', key: '/data/user', icon: <SettingOutlined /> },
2904
+ { label: '角色管理', key: '/data/role', icon: <SettingOutlined /> },
2905
+ ],
2906
+ },
2907
+ {
2908
+ label: '系统管理',
2909
+ children: [
2910
+ { label: '用户管理', key: '/system/user', icon: <SettingOutlined /> },
2911
+ { label: '角色管理', key: '/system/role', icon: <SettingOutlined /> },
2912
+ ],
2913
+ },
2914
+ ];
2915
+
2916
+ const App = () => {
2917
+ return <Menu menus={menus} />;
2918
+ };
2919
+ ```
2920
+
2921
+ #### 2. 受控折叠用法
2922
+
2923
+ ```tsx
2924
+ import { useState } from 'react';
2925
+ import Menu from '@/components/menu';
2926
+
2927
+ const App = () => {
2928
+ const [collapsed, setCollapsed] = useState(false);
2929
+
2930
+ return (
2931
+ <Menu
2932
+ menus={menus}
2933
+ collapsed={collapsed}
2934
+ onCollapse={(val) => setCollapsed(val)}
2935
+ />
2936
+ );
2937
+ };
2938
+ ```
2939
+
2940
+ #### 3. 外链菜单用法
2941
+
2942
+ ```tsx
2943
+ const menus = [
2944
+ {
2945
+ label: '帮助中心',
2946
+ // 外链菜单必须配置 type="link",否则会触发路由跳转
2947
+ key: 'https://www.xxx.com/help',
2948
+ type: 'link',
2949
+ },
2950
+ ];
2951
+
2952
+ const handleMenuClick = (item) => {
2953
+ if (item.type === 'link') {
2954
+ window.open(item.key, '_blank');
2955
+ }
2956
+ };
2957
+
2958
+ // 外链菜单无激活状态,需通过 onClick 回调自行处理跳转逻辑
2959
+ <Menu menus={menus} onClick={handleMenuClick} />;
2960
+ ```
2961
+
2962
+ #### 4. 完整回调用法
2963
+
2964
+ ```tsx
2965
+ const App = () => {
2966
+ const handleMenuClick = (item) => {
2967
+ console.log('点击菜单:', item);
2968
+ };
2969
+
2970
+ const handleSelect = (key) => {
2971
+ console.log('选中菜单key:', key);
2972
+ };
2973
+
2974
+ return (
2975
+ <Menu
2976
+ menus={menus}
2977
+ onClick={handleMenuClick}
2978
+ onSelect={handleSelect}
2979
+ />
2980
+ );
2981
+ };
2982
+ ```
2983
+
2984
+ ### 注意事项
2985
+
2986
+ 1. 组件仅对菜单第二层级生效,一级菜单仅作为分组展示,配置 key、icon、路由等属性均无效
2987
+ 2. 外链菜单必须显式配置 type='link',配置后点击不会触发内部路由跳转
2988
+ 3. 外链菜单无激活高亮状态,仅作为普通点击项使用
2989
+ 4. 组件需配合 Ant Design Layout 布局使用,默认支持响应式布局,适配不同屏幕尺寸
2990
+
2991
+ ---
2992
+
2993
+ ## model-card
2994
+
2995
+ **导入方式:**
2996
+
2997
+ ```ts
2998
+ import { ModelCard } from '@zjlab-fe/data-hub-ui';
2999
+ ```
3000
+ ```ts
3001
+ // 类型导入
3002
+ import type { ModelCardProps } from '@zjlab-fe/data-hub-ui';
3003
+ ```
3004
+
3005
+ 用于展示模型信息,包括模型名称、提供方、描述、标签、支持训练、支持部署、支持评测、图标、评分、操作按钮等。
3006
+
3007
+ ### 代码演示
3008
+
3009
+ #### 示例
3010
+
3011
+ 展示模型信息
3012
+
3013
+ **示例代码(index.tsx):**
3014
+
3015
+ ```tsx
3016
+ import ModelCard from '../index';
3017
+
3018
+ const cardList = [
3019
+ {
3020
+ id: 1,
3021
+ name: '模型1',
3022
+ provider: '模型提供方1',
3023
+ description: '这是一个模型1的描述',
3024
+ tags: ['标签1', '标签2'],
3025
+ supportTraining: true,
3026
+ supportDeployment: true,
3027
+ supportEvaluation: true,
3028
+ image: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/modelicon/default.png',
3029
+ stars: 4,
3030
+ onTrain: () => {},
3031
+ onDeploy: () => {},
3032
+ onEvaluate: () => {},
3033
+ onExperience: () => {},
3034
+ },
3035
+ {
3036
+ id: 2,
3037
+ name: '模型2',
3038
+ provider: '模型提供方2',
3039
+ description: '这是一个模型2的描述',
3040
+ tags: ['标签3', '标签4'],
3041
+ supportTraining: true,
3042
+ supportDeployment: true,
3043
+ supportEvaluation: true,
3044
+ image: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/modelicon/default.png',
3045
+ stars: 3,
3046
+ onTrain: () => {},
3047
+ onDeploy: () => {},
3048
+ onEvaluate: () => {},
3049
+ onExperience: () => {},
3050
+ },
3051
+ ];
3052
+
3053
+ export default function Demo() {
3054
+ return (
3055
+ <div>
3056
+ {cardList.map((item) => (
3057
+ <ModelCard key={item.id} {...item} />
3058
+ ))}
3059
+ </div>
3060
+ );
3061
+ }
3062
+ ```
3063
+ ### API
3064
+
3065
+ | 参数 | 说明 | 类型 | 默认值 | 必须 |
3066
+ | --- | --- | --- | --- | --- |
3067
+ | `id` | number | 主键ID | 是 |
3068
+ | `name` | string | 模型名称 | 是 |
3069
+ | `provider` | string | 提供方 | 是 |
3070
+ | `description` | string | 模型描述(一句话) | 是 |
3071
+ | `tags` | string[] | 模型标签 | 是 |
3072
+ | `supportTraining` | Boolean | 是否支持模型训练 | 是 |
3073
+ | `supportDeployment` | Boolean | 是否支持模型部署 | 是 |
3074
+ | `supportEvaluation` | Boolean | 是否支持模型评测 | 是 |
3075
+ | `image` | string | 模型图标URL | 否 |
3076
+ | `stars` | number | 模型评分 | 是 |
3077
+ | `onTrain` | () => void | 模型训练操作回调 | 是 |
3078
+ | `onDeploy` | () => void | 模型部署操作回调 | 是 |
3079
+ | `onEvaluate` | () => void | 模型评测操作回调 | 是 |
3080
+ | `onExperience` | () => void | 模型体验操作回调 | 是 |
3081
+
3082
+ ---
3083
+
3084
+ ## notion-editor
3085
+
3086
+ Notion 风格的富文本编辑器,基于 BlockNote 实现。支持标题、段落、图片、文件、表格、待办列表、代码块等多种内容类型,支持 Markdown/HTML 粘贴,适用于文档编辑、知识库内容录入等场景。
3087
+
3088
+ ### 代码演示
3089
+
3090
+ #### 基础用法
3091
+
3092
+ 支持图片/文件上传、初始化内容、获取/重置内容
3093
+
3094
+ **示例代码(index.tsx):**
3095
+
3096
+ ```tsx
3097
+ import React, { useRef } from 'react';
3098
+ import NotionEditor, { NotionEditorHandle, Block, PartialBlock } from '../index';
3099
+ import axios from 'axios';
3100
+ import { Button, Flex } from 'antd';
3101
+ export default function NotionEditorDemo() {
3102
+ const editorRef = useRef<NotionEditorHandle>(null);
3103
+ const onFileUpload: (file: File) => Promise<string> = async (file) => {
3104
+ // 1. 获取预签名URL
3105
+ const { data } = await axios.get('/api/fes/docs/resource/getUploadUrl', {
3106
+ params: { id: 'c7fe927b-0045-4b36-8b3e-e16259876e18', fileName: file.name },
3107
+ headers: {
3108
+ Authorization:
3109
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiYWNjb3VudF90eXBlIjoiMSIsInVzZXJfaWQiOiIxODIxMDA0MTkyMzI4ODQ3MzYwIiwidXNlcl9uYW1lIjoid21zdGVzdDEiLCJhY2NvdW50X3N0YXR1cyI6IjAifSwiZXhwIjoxNzgxMTYxNjk0fQ.E7zDJ_piFjAHeCfyq9lkudHyoP6LFinYdsSCbAlJy2k',
3110
+ },
3111
+ });
3112
+ const { uploadUrl, fileUrl } = data.data;
3113
+
3114
+ // 2. 上传
3115
+ await axios.put(uploadUrl, file, {
3116
+ headers: { 'Content-Type': file.type || 'application/octet-stream' },
3117
+ });
3118
+
3119
+ return fileUrl;
3120
+ };
3121
+
3122
+ const onImageUploadByUrl: (url: string) => Promise<string> = async (url) => {
3123
+ const { data } = await axios.post(
3124
+ '/api/fes/docs/resource/uploadImageByUrl',
3125
+ {
3126
+ imageUrl: url,
3127
+ id: 'c7fe927b-0045-4b36-8b3e-e16259876e18',
3128
+ },
3129
+ {
3130
+ headers: {
3131
+ Authorization:
3132
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiYWNjb3VudF90eXBlIjoiMSIsInVzZXJfaWQiOiIxODIxMDA0MTkyMzI4ODQ3MzYwIiwidXNlcl9uYW1lIjoid21zdGVzdDEiLCJhY2NvdW50X3N0YXR1cyI6IjAifSwiZXhwIjoxNzgxMTYxNjk0fQ.E7zDJ_piFjAHeCfyq9lkudHyoP6LFinYdsSCbAlJy2k',
3133
+ },
3134
+ },
3135
+ );
3136
+ return data?.data?.url || data?.url || (typeof data?.data === 'string' ? data.data : '');
3137
+ };
3138
+
3139
+ const onChange = (content: Block[]) => {
3140
+ console.log('+++ content:', content);
3141
+ };
3142
+
3143
+ const initContent: PartialBlock[] = [
3144
+ {
3145
+ id: '088a4484-f40b-4c17-92b2-b7c7e89d8d06',
3146
+ type: 'heading',
3147
+ props: {
3148
+ backgroundColor: 'default',
3149
+ textColor: 'default',
3150
+ textAlignment: 'left',
3151
+ level: 1,
3152
+ isToggleable: false,
3153
+ },
3154
+ content: [
3155
+ {
3156
+ type: 'text',
3157
+ text: '这是标题',
3158
+ styles: {},
3159
+ },
3160
+ ],
3161
+ children: [],
3162
+ },
3163
+ {
3164
+ id: '90dd94e1-a217-4dd3-9406-d225e3ab3a38',
3165
+ type: 'paragraph',
3166
+ props: {
3167
+ backgroundColor: 'default',
3168
+ textColor: 'default',
3169
+ textAlignment: 'left',
3170
+ },
3171
+ content: [
3172
+ {
3173
+ type: 'text',
3174
+ text: '这是内容',
3175
+ styles: {},
3176
+ },
3177
+ ],
3178
+ children: [],
3179
+ },
3180
+ {
3181
+ id: '0e537fae-38d8-4d47-b645-bcdea812f04a',
3182
+ type: 'image',
3183
+ props: {
3184
+ textAlignment: 'left',
3185
+ backgroundColor: 'default',
3186
+ name: 'Snipaste_2025-12-11_10-50-04.png',
3187
+ url: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/public/attachments/test/0d4169ec-390e-482f-b367-75abdb1ad310.png',
3188
+ caption: '',
3189
+ showPreview: true,
3190
+ },
3191
+ children: [],
3192
+ },
3193
+ {
3194
+ id: 'd029cee5-443a-4422-b3b8-f7fef48f8aa4',
3195
+ type: 'file',
3196
+ props: {
3197
+ name: '立体化学-0.pdf',
3198
+ url: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/public/attachments/test/11a32231-887e-4610-91dc-83bcd78224b0.pdf',
3199
+ caption: '',
3200
+ backgroundColor: 'default',
3201
+ },
3202
+ children: [],
3203
+ },
3204
+ {
3205
+ id: 'e0b7af21-7bc8-4e04-8959-c217673e1b95',
3206
+ type: 'paragraph',
3207
+ props: {
3208
+ backgroundColor: 'default',
3209
+ textColor: 'default',
3210
+ textAlignment: 'left',
3211
+ },
3212
+ content: [],
3213
+ children: [],
3214
+ },
3215
+ ];
3216
+ return (
3217
+ <div style={{ height: '800px', overflow: 'auto' }}>
3218
+ <Flex gap={24} style={{ padding: 24 }}>
3219
+ <Button
3220
+ type="primary"
3221
+ onClick={() => {
3222
+ console.log(editorRef.current?.getContent());
3223
+ }}
3224
+ >
3225
+ 获取内容(查看控制台)
3226
+ </Button>
3227
+ <Button
3228
+ type="primary"
3229
+ onClick={() => {
3230
+ console.log(
3231
+ editorRef.current?.setContent([
3232
+ {
3233
+ id: '088a4484-f40b-4c17-92b2-b7c7e89d8d06',
3234
+ type: 'heading',
3235
+ props: {
3236
+ backgroundColor: 'default',
3237
+ textColor: 'default',
3238
+ textAlignment: 'left',
3239
+ level: 1,
3240
+ isToggleable: false,
3241
+ },
3242
+ content: [
3243
+ {
3244
+ type: 'text',
3245
+ text: '这是重置后的标题',
3246
+ styles: {},
3247
+ },
3248
+ ],
3249
+ children: [],
3250
+ },
3251
+ {
3252
+ id: '90dd94e1-a217-4dd3-9406-d225e3ab3a38',
3253
+ type: 'paragraph',
3254
+ props: {
3255
+ backgroundColor: 'default',
3256
+ textColor: 'default',
3257
+ textAlignment: 'left',
3258
+ },
3259
+ content: [
3260
+ {
3261
+ type: 'text',
3262
+ text: '这是重置后的内容',
3263
+ styles: {},
3264
+ },
3265
+ ],
3266
+ children: [],
3267
+ },
3268
+ {
3269
+ id: 'd029cee5-443a-4422-b3b8-f7fef48f8aa4',
3270
+ type: 'paragraph',
3271
+ props: {
3272
+ backgroundColor: 'default',
3273
+ textColor: 'default',
3274
+ textAlignment: 'left',
3275
+ },
3276
+ content: [],
3277
+ children: [],
3278
+ },
3279
+ ]),
3280
+ );
3281
+ }}
3282
+ >
3283
+ 重置内容
3284
+ </Button>
3285
+ </Flex>
3286
+
3287
+ <NotionEditor
3288
+ ref={editorRef}
3289
+ docId="test"
3290
+ onImageUpload={onFileUpload}
3291
+ onImageUploadByUrl={onImageUploadByUrl}
3292
+ onFileUpload={onFileUpload}
3293
+ initContent={initContent}
3294
+ editable
3295
+ onChange={onChange}
3296
+ />
3297
+ </div>
3298
+ );
3299
+ }
3300
+ ```
3301
+ ### API
3302
+
3303
+ #### NotionEditorHandle
3304
+
3305
+ | 方法 | 说明 | 类型 |
3306
+ | --- | --- | --- |
3307
+ | getContent | 获取编辑器当前所有 block 内容 | `() => Block[]` |
3308
+ | setContent | 设置编辑器内容(替换全部) | `(content: Block[]) => void` |
3309
+ | focus | 聚焦编辑器 | `() => void` |
3310
+ | isEmpty | 编辑器是否为空 | `() => boolean` |
3311
+
3312
+ #### NotionEditorProps
3313
+
3314
+ | 参数 | 说明 | 类型 | 默认值 | 版本 | 必须 |
3315
+ | --- | --- | --- | --- | --- | --- |
3316
+ | initContent | 初始内容 | `PartialBlock[]` | - | | |
3317
+ | editable | 是否可编辑 | `boolean` | `false` | | |
3318
+ | className | 自定义类名 | `string` | `''` | | |
3319
+ | docId | 粘贴 copy JSON 时用于替换附件 URL 中文档 ID 的值 | `string` | - | | |
3320
+ | onChange | 编辑器内容变化时的回调 | `(blocks: Block[]) => void` | - | | |
3321
+ | onImageUpload | 图片上传处理函数,返回图片访问 URL | `(file: File) => Promise<string>` | - | | |
3322
+ | onImageUploadByUrl | 通过 URL 上传图片的处理函数(用于解析 Markdown 中的图片链接) | `(url: string) => Promise<string>` | - | | |
3323
+ | onFileUpload | 文件上传处理函数,返回文件访问 URL | `(file: File) => Promise<string>` | - | | |
3324
+
3325
+ #### 注意
3326
+
3327
+ - `onImageUpload` 和 `onFileUpload` 未传入时,上传的文件会使用 `URL.createObjectURL` 生成本地临时 URL
3328
+ - 编辑器内置中文语言包,粘贴时自动识别 Markdown 或 HTML 格式
3329
+ - 自定义文件块渲染,点击文件块可在新窗口打开文件
3330
+ - 支持表格单元格拆分(`splitCells: true`)
3331
+
3332
+ ---
3333
+
3334
+ ## operator-chain(卡片式操作链)
3335
+
3336
+ **导入方式:**
3337
+
3338
+ ```ts
3339
+ import { OperatorChain } from '@zjlab-fe/data-hub-ui';
3340
+ ```
3341
+ ```ts
3342
+ // 类型导入
3343
+ import type { OperatorChainProps, SingleCardItem, OperatorChainRef } from '@zjlab-fe/data-hub-ui';
3344
+ ```
3345
+
3346
+ 用于展示和管理算子流程的链式组件
3347
+ 支持数据导入导出节点
3348
+ 支持是否可编辑模式
3349
+ 符合团队样式规范
3350
+ 不支持样式定制
3351
+
3352
+ ### 代码演示
3353
+
3354
+ **示例代码(index.tsx):**
3355
+
3356
+ ```tsx
3357
+ import { Button, Flex, message } from 'antd';
2344
3358
  import OperatorChain, { OperatorChainRef, SingleCardItem } from '../index';
2345
3359
  import { useRef } from 'react';
2346
3360
 
@@ -2362,37 +3376,146 @@ const cards = [
2362
3376
  description: 'Description 3',
2363
3377
  },
2364
3378
  ];
2365
-
2366
3379
  const memu = [
3380
+ {
3381
+ id: '1',
3382
+ title: 'Operator 1',
3383
+ description: 'Description 1',
3384
+ type: '数据清洗算子',
3385
+ typeId: 1,
3386
+ },
3387
+ {
3388
+ id: '2',
3389
+ title: 'Operator 2',
3390
+ description: 'Description 2',
3391
+ type: '特征提取算子',
3392
+ typeId: 2,
3393
+ },
3394
+ {
3395
+ id: '3',
3396
+ title: 'Operator 3',
3397
+ description: 'Description 3',
3398
+ type: '数据聚合算子',
3399
+ typeId: 3,
3400
+ },
2367
3401
  {
2368
3402
  id: '4',
2369
3403
  title: 'Operator 4',
2370
3404
  description: 'Description 4',
3405
+ type: '数据过滤算子',
3406
+ typeId: 4,
2371
3407
  },
2372
3408
  {
2373
3409
  id: '5',
2374
3410
  title: 'Operator 5',
2375
3411
  description: 'Description 5',
3412
+ type: '格式转换算子',
3413
+ typeId: 5,
2376
3414
  },
2377
3415
  {
2378
3416
  id: '6',
2379
3417
  title: 'Operator 6',
2380
3418
  description: 'Description 6',
3419
+ type: '数据清洗算子',
3420
+ typeId: 1,
2381
3421
  },
2382
3422
  {
2383
3423
  id: '7',
2384
3424
  title: 'Operator 7',
2385
3425
  description: 'Description 7',
3426
+ type: '特征提取算子',
3427
+ typeId: 2,
2386
3428
  },
2387
3429
  {
2388
3430
  id: '8',
2389
3431
  title: 'Operator 8',
2390
3432
  description: 'Description 8',
3433
+ type: '数据聚合算子',
3434
+ typeId: 3,
2391
3435
  },
2392
3436
  {
2393
3437
  id: '9',
2394
3438
  title: 'Operator 9',
2395
3439
  description: 'Description 9',
3440
+ type: '数据过滤算子',
3441
+ typeId: 4,
3442
+ },
3443
+ {
3444
+ id: '10',
3445
+ title: 'Operator 10',
3446
+ description: 'Description 10',
3447
+ type: '格式转换算子',
3448
+ typeId: 5,
3449
+ },
3450
+ {
3451
+ id: '11',
3452
+ title: 'Operator 11',
3453
+ description: 'Description 11',
3454
+ type: '数据清洗算子',
3455
+ typeId: 1,
3456
+ },
3457
+ {
3458
+ id: '12',
3459
+ title: 'Operator 12',
3460
+ description: 'Description 12',
3461
+ type: '特征提取算子',
3462
+ typeId: 2,
3463
+ },
3464
+ {
3465
+ id: '13',
3466
+ title: 'Operator 13',
3467
+ description: 'Description 13',
3468
+ type: '数据聚合算子',
3469
+ typeId: 3,
3470
+ },
3471
+ {
3472
+ id: '14',
3473
+ title: 'Operator 14',
3474
+ description: 'Description 14',
3475
+ type: '数据过滤算子',
3476
+ typeId: 4,
3477
+ },
3478
+ {
3479
+ id: '15',
3480
+ title: 'Operator 15',
3481
+ description: 'Description 15',
3482
+ type: '格式转换算子',
3483
+ typeId: 5,
3484
+ },
3485
+ {
3486
+ id: '16',
3487
+ title: 'Operator 16',
3488
+ description: 'Description 16',
3489
+ type: '数据清洗算子',
3490
+ typeId: 1,
3491
+ },
3492
+ {
3493
+ id: '17',
3494
+ title: 'Operator 17',
3495
+ description: 'Description 17',
3496
+ type: '特征提取算子',
3497
+ typeId: 2,
3498
+ },
3499
+ {
3500
+ id: '18',
3501
+ title: 'Operator 18',
3502
+ description: 'Description 18',
3503
+ type: '数据聚合算子',
3504
+ typeId: 3,
3505
+ },
3506
+ {
3507
+ id: '19',
3508
+ title: 'Operator 19',
3509
+ description: 'Description 19',
3510
+ type: '数据过滤算子',
3511
+ typeId: 4,
3512
+ },
3513
+ {
3514
+ id: '20',
3515
+ title: 'Operator 20',
3516
+ description: 'Description 20',
3517
+ type: '格式转换算子',
3518
+ typeId: 5,
2396
3519
  },
2397
3520
  ];
2398
3521
 
@@ -2427,10 +3550,6 @@ export default function Demo() {
2427
3550
  onActive={handleActive}
2428
3551
  isEdit={true}
2429
3552
  ref={chainRef} // 绑定ref
2430
- dropdownStyle={{
2431
- width: '250px',
2432
- maxHeight: '250px',
2433
- }}
2434
3553
  />
2435
3554
 
2436
3555
  <Flex align="center" gap={5}>
@@ -2459,7 +3578,7 @@ export default function Demo() {
2459
3578
 
2460
3579
  <div>
2461
3580
  ### 禁止编辑模式
2462
- <OperatorChain defaultValues={cards} menu={memu} isEdit={false} width="240px" />
3581
+ <OperatorChain defaultValues={cards} menu={memu} isEdit={false} />
2463
3582
  </div>
2464
3583
  </div>
2465
3584
  </div>
@@ -2479,8 +3598,7 @@ export default function Demo() {
2479
3598
  | onClick | 点击卡片时的回调 | `(id: string) => void` | - | 否 |
2480
3599
  | onChange | 卡片数据变化时的回调 | `(values: SingleCardItem[]) => void` | - | 否 |
2481
3600
  | onActive | 激活卡片变化时的回调 | `(id: string \| null) => void` | - | 否 |
2482
- | width | 组件宽度 | `string` | `'240px'` | 否 |
2483
- | dropdownStyle | 下拉菜单样式 | `{ width?: string \| number; maxHeight?: string \| number; overflowY?: React.CSSProperties['overflowY']; }` | `{ width: '200px', maxHeight: '200px', overflowY: 'auto' }` | 否 |
3601
+
2484
3602
 
2485
3603
  ### 接口定义
2486
3604
 
@@ -2491,6 +3609,8 @@ export default function Demo() {
2491
3609
  | id | 卡片唯一标识 | `string` | 是 |
2492
3610
  | title | 卡片标题 | `string` | 否 |
2493
3611
  | description | 卡片描述 | `string` | 否 |
3612
+ | type | 算子类型 | `string` | 否 |
3613
+ | typeId | 算子类型id | `number` | 否 |
2494
3614
 
2495
3615
  #### OperatorChainRef 接口
2496
3616
 
@@ -2507,8 +3627,9 @@ export default function Demo() {
2507
3627
  import { OperatorChain } from '@/components/operator-chain';
2508
3628
 
2509
3629
  const defaultValues = [
2510
- { id: '1', title: '数据清洗', description: '清洗原始数据' },
2511
- { id: '2', title: '数据转换', description: '转换数据格式' },
3630
+ { id: '1', title: '数据清洗', description: '清洗原始数据' ,type: '数据处理', typeId: 1},
3631
+ { id: '2', title: '数据转换', description: '转换数据格式' ,type: '数据转换', typeId: 2},
3632
+ { id: '3', title: '数据聚合', description: '聚合统计数据' ,type: '数据聚合', typeId: 3},
2512
3633
  ];
2513
3634
 
2514
3635
  <OperatorChain
@@ -2562,9 +3683,11 @@ const handleGetData = () => {
2562
3683
  import { OperatorChain } from '@/components/operator-chain';
2563
3684
 
2564
3685
  const menu = [
2565
- { id: 'filter', title: '数据过滤', description: '过滤符合条件的数据' },
2566
- { id: 'transform', title: '数据转换', description: '转换数据格式' },
2567
- { id: 'aggregate', title: '数据聚合', description: '聚合统计数据' },
3686
+ { id: 'filter1', title: '数据过滤1', description: '过滤符合条件的数据', type: '数据处理', typeId: 1 },
3687
+ { id: 'filter2', title: '数据过滤2', description: '过滤符合条件的数据', type: '数据处理', typeId: 1 },
3688
+ { id: 'transform1', title: '数据转换1', description: '转换数据格式', type: '数据转换', typeId: 2 },
3689
+ { id: 'transform2', title: '数据转换2', description: '转换数据格式', type: '数据转换', typeId: 2 },
3690
+ { id: 'aggregate1', title: '数据聚合1', description: '聚合统计数据', type: '数据聚合', typeId: 3 },
2568
3691
  ];
2569
3692
 
2570
3693
  <OperatorChain
@@ -2579,12 +3702,34 @@ const menu = [
2579
3702
  />
2580
3703
  ```
2581
3704
 
3705
+ #### 5. 使用带有类型信息的菜单数据
3706
+
3707
+ ```tsx
3708
+ import { OperatorChain } from '@/components/operator-chain';
3709
+
3710
+ const menu = [
3711
+ { id: 'filter1', title: '数据过滤1', description: '过滤符合条件的数据', type: '数据处理', typeId: 1 },
3712
+ { id: 'filter2', title: '数据过滤2', description: '过滤符合条件的数据', type: '数据处理', typeId: 1 },
3713
+ { id: 'transform1', title: '数据转换1', description: '转换数据格式', type: '数据转换', typeId: 2 },
3714
+ { id: 'transform2', title: '数据转换2', description: '转换数据格式', type: '数据转换', typeId: 2 },
3715
+ { id: 'aggregate1', title: '数据聚合1', description: '聚合统计数据', type: '数据聚合', typeId: 3 },
3716
+ ];
3717
+
3718
+ <OperatorChain
3719
+ isEdit={true}
3720
+ menu={menu}
3721
+ defaultValues={[{ id: '1', title: '初始算子', description: '默认算子' }]}
3722
+ />
3723
+ ```
3724
+
2582
3725
  ### 注意事项
2583
3726
 
2584
3727
  1. `dataIO` 模式下,组件会自动在首尾添加 `IMPORT` 和 `EXPORT` 节点
2585
3728
  2. `default` 模式下,不会自动添加导入导出节点
2586
3729
  3. 编辑模式下需要传入 `menu` 属性才能使用添加功能
2587
- 4. 非编辑模式下,添加按钮和删除按钮不会显示
3730
+ 4. 非编辑模式下,添加按钮和删除按钮不会显示
3731
+ 5. 当 `menu` 数据中包含 `type` 和 `typeId` 字段时,下拉菜单会显示类型筛选功能
3732
+ 6. 下拉菜单支持按算子类型和标题进行搜索过滤
2588
3733
 
2589
3734
  ---
2590
3735
 
@@ -3630,24 +4775,663 @@ export default function Demo() {
3630
4775
  import { StatusTag } from '@zjlab-fe/data-hub-ui';
3631
4776
  ```
3632
4777
 
3633
- > 该组件暂无 README 文档,以下为 demo 示例代码,可参考用法。
4778
+ 状态标签组件,用于展示运行、成功、失败等状态信息。支持四种视觉样式,覆盖表格、表单、卡片、标题栏等常见使用场景。
3634
4779
 
3635
- ### 示例代码(index.tsx)
4780
+ ### 何时使用
4781
+
4782
+ - 需要在列表、表格中展示任务或数据状态
4783
+ - 需要在表单、详情页中以标签形式展示状态
4784
+ - 需要在卡片、面板中展示轻量状态标识
4785
+ - 需要在标题栏中展示带圆点的状态标识
4786
+
4787
+ ### 样式变体
4788
+
4789
+ `variant` 按标签自身的视觉属性命名,而非使用场景:
4790
+
4791
+ | variant | 说明 | 典型场景 |
4792
+ | --- | --- | --- |
4793
+ | `filled` | 浅色填充底,无边框 | 卡片、面板 |
4794
+ | `bordered` | 带边框的标签 | 表单页、详情页 |
4795
+ | `dot` | 圆点 + 文字,无背景 | 表格列 |
4796
+ | `dotFilled` | 圆点 + 浅色填充底 + 描边,中性文字色 | 标题栏 |
4797
+
4798
+ ### 状态类型
4799
+
4800
+ | type | 说明 |
4801
+ | --- | --- |
4802
+ | `running` | 进行中 |
4803
+ | `success` | 成功 / 完成 |
4804
+ | `failed` | 失败 |
4805
+ | `stopped` | 已终止 / 已停止 |
4806
+ | `waiting` | 等待中 / 排队中,视觉与 `stopped` 一致 |
4807
+ | `warning` | 警告 / 冲突 |
4808
+
4809
+ ### 代码演示
4810
+
4811
+ **示例代码(index.tsx):**
3636
4812
 
3637
4813
  ```tsx
3638
4814
  import StatusTag from '@/components/status-tag';
4815
+
4816
+ const statusList = [
4817
+ { type: 'running' as const, label: '过程中' },
4818
+ { type: 'success' as const, label: '完成' },
4819
+ { type: 'failed' as const, label: '失败' },
4820
+ { type: 'stopped' as const, label: '已终止' },
4821
+ { type: 'waiting' as const, label: '等待中' },
4822
+ { type: 'warning' as const, label: '冲突' },
4823
+ ];
4824
+
4825
+ const variantList = [
4826
+ { variant: 'dot' as const, title: '点状(表格中使用)' },
4827
+ { variant: 'bordered' as const, title: '带边框(表单页使用)' },
4828
+ { variant: 'filled' as const, title: '填充底(卡片中使用)' },
4829
+ { variant: 'dotFilled' as const, title: '圆点填充底(标题栏使用)' },
4830
+ ];
4831
+
3639
4832
  export default function Demo() {
3640
4833
  return (
3641
- <>
3642
- <StatusTag type="running">进行状态</StatusTag>
3643
- <StatusTag type="failed">失败状态</StatusTag>
3644
- <StatusTag type="success">成功状态</StatusTag>
3645
- <StatusTag type="stopped">停止状态</StatusTag>
3646
- <StatusTag type="pending">等待状态</StatusTag>
3647
- </>
4834
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 24, padding: 24 }}>
4835
+ {variantList.map(({ variant, title }) => (
4836
+ <section key={variant}>
4837
+ #### {title}
4838
+ <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
4839
+ {statusList.map(({ type, label }) => (
4840
+ <StatusTag key={`${variant}-${type}`} type={type} variant={variant}>
4841
+ {label}
4842
+ </StatusTag>
4843
+ ))}
4844
+ </div>
4845
+ </section>
4846
+ ))}
4847
+ </div>
3648
4848
  );
3649
4849
  }
3650
4850
  ```
4851
+ ### API
4852
+
4853
+ #### StatusTag
4854
+
4855
+ | 参数 | 说明 | 类型 | 默认值 | 必须 |
4856
+ | --- | --- | --- | --- | --- |
4857
+ | type | 状态类型 | `'running' \| 'success' \| 'failed' \| 'stopped' \| 'waiting' \| 'warning'` | `'running'` | 否 |
4858
+ | variant | 视觉样式 | `'filled' \| 'bordered' \| 'dot' \| 'dotFilled'` | `'filled'` | 否 |
4859
+ | className | 自定义类名 | `string` | - | 否 |
4860
+ | children | 标签文案 | `React.ReactNode` | - | 否 |
4861
+
4862
+ ### 使用示例
4863
+
4864
+ ```tsx
4865
+ import { StatusTag } from '@zjlab-fe/data-hub-ui';
4866
+
4867
+ // 填充底(默认)
4868
+ <StatusTag type="running">运行中</StatusTag>
4869
+ <StatusTag type="success">运行成功</StatusTag>
4870
+ <StatusTag type="failed">运行失败</StatusTag>
4871
+ <StatusTag type="stopped">已终止</StatusTag>
4872
+ <StatusTag type="waiting">等待中</StatusTag>
4873
+ <StatusTag type="warning">冲突</StatusTag>
4874
+
4875
+ // 点状,适合表格列
4876
+ <StatusTag type="success" variant="dot">完成</StatusTag>
4877
+ <StatusTag type="failed" variant="dot">失败</StatusTag>
4878
+
4879
+ // 带边框,适合表单页
4880
+ <StatusTag type="running" variant="bordered">过程中</StatusTag>
4881
+ <StatusTag type="warning" variant="bordered">冲突</StatusTag>
4882
+
4883
+ // 圆点填充底,适合标题栏
4884
+ <StatusTag type="running" variant="dotFilled">处理中</StatusTag>
4885
+ <StatusTag type="waiting" variant="dotFilled">排队中</StatusTag>
4886
+ ```
4887
+
4888
+ ### 注意事项
4889
+
4890
+ 1. **文案自定义**:状态文案通过 `children` 传入,组件不内置文案映射,便于适配不同业务语义(如「运行中」/「过程中」)。
4891
+ 2. **样式预设**:颜色、背景、边框等样式由 `type` + `variant` 组合决定,无需手动传色值。
4892
+ 3. **默认样式**:未传 `variant` 时默认为 `filled`,与卡片场景的设计稿一致。
4893
+ 4. **waiting 与 stopped**:两种状态语义不同,但视觉样式保持一致。
4894
+ 5. **dotFilled 文字色**:标题栏样式下文字固定为中性色,状态差异体现在圆点和底色上。
4895
+
4896
+ ---
4897
+
4898
+ ## tag-group-filter(标签组过滤器)
4899
+
4900
+ **导入方式:**
4901
+
4902
+ ```ts
4903
+ import { TagGroupFilter } from '@zjlab-fe/data-hub-ui';
4904
+ ```
4905
+
4906
+ 标签分组筛选组件,支持多组标签的分类筛选和数据体量范围筛选。
4907
+
4908
+ #### 特性
4909
+
4910
+ - **分组展示**:支持多个标签组的折叠/展开
4911
+ - **多选筛选**:每组标签支持多选,独立管理选中状态
4912
+ - **数据体量筛选**:内置数据大小范围筛选器(KB/MB/GB/TB)
4913
+ - **智能布局**:集成 TagView 组件,自动处理标签溢出
4914
+ - **清空功能**:支持快速清空单组或全部选中项
4915
+ - **响应式折叠**:记住各组折叠状态,自动展开所有组
4916
+
4917
+ ### 代码演示
4918
+
4919
+ **示例代码(index.tsx):**
4920
+
4921
+ ```tsx
4922
+ import React, { useState } from 'react';
4923
+ import TagGroupFilter from '@/components/tag-group-filter';
4924
+ import { Card } from '@/demo/shared';
4925
+
4926
+ export default function Demo() {
4927
+ // 基础标签分组数据
4928
+ const [tagSelected1, setTagSelected1] = useState<Record<string, string[]>>({
4929
+ category: [],
4930
+ type: [],
4931
+ });
4932
+
4933
+ const basicTagGroupDatas = [
4934
+ {
4935
+ key: 'category',
4936
+ title: '分类',
4937
+ children: [
4938
+ {
4939
+ children: [
4940
+ { value: 'ai', name: '人工智能' },
4941
+ { value: 'ml', name: '机器学习' },
4942
+ { value: 'dl', name: '深度学习' },
4943
+ { value: 'nlp', name: '自然语言处理' },
4944
+ { value: 'cv', name: '计算机视觉' },
4945
+ { value: 'dm', name: '数据挖掘' },
4946
+ ],
4947
+ },
4948
+ ],
4949
+ },
4950
+ {
4951
+ key: 'type',
4952
+ title: '类型',
4953
+ children: [
4954
+ {
4955
+ children: [
4956
+ { value: 'image', name: '图像' },
4957
+ { value: 'text', name: '文本' },
4958
+ { value: 'audio', name: '音频' },
4959
+ { value: 'video', name: '视频' },
4960
+ { value: 'structured', name: '结构化数据' },
4961
+ ],
4962
+ },
4963
+ ],
4964
+ },
4965
+ ];
4966
+
4967
+ // 带子标题的标签分组数据
4968
+ const [tagSelected2, setTagSelected2] = useState<Record<string, string[]>>({
4969
+ domain: [],
4970
+ industry: [],
4971
+ });
4972
+
4973
+ const subTitleTagGroupDatas = [
4974
+ {
4975
+ key: 'domain',
4976
+ title: '技术领域',
4977
+ children: [
4978
+ {
4979
+ subTitle: '热门领域',
4980
+ children: [
4981
+ { value: 'llm', name: '大语言模型' },
4982
+ { value: 'aigc', name: 'AIGC' },
4983
+ { value: 'multimodal', name: '多模态' },
4984
+ ],
4985
+ },
4986
+ {
4987
+ subTitle: '传统领域',
4988
+ children: [
4989
+ { value: 'search', name: '搜索引擎' },
4990
+ { value: 'recommendation', name: '推荐系统' },
4991
+ { value: 'ads', name: '广告投放' },
4992
+ ],
4993
+ },
4994
+ ],
4995
+ },
4996
+ {
4997
+ key: 'industry',
4998
+ title: '行业应用',
4999
+ children: [
5000
+ {
5001
+ children: [
5002
+ { value: 'finance', name: '金融' },
5003
+ { value: 'healthcare', name: '医疗' },
5004
+ { value: 'education', name: '教育' },
5005
+ { value: 'retail', name: '零售' },
5006
+ { value: 'manufacturing', name: '制造' },
5007
+ ],
5008
+ },
5009
+ ],
5010
+ },
5011
+ ];
5012
+
5013
+ // 带数据体量筛选
5014
+ const [tagSelected3, setTagSelected3] = useState<Record<string, string[]>>({
5015
+ source: [],
5016
+ });
5017
+
5018
+ const [dataSizeValue, setDataSizeValue] = useState<string[]>(['', '']);
5019
+
5020
+ const dataSizeTagGroupDatas = [
5021
+ {
5022
+ key: 'source',
5023
+ title: '数据来源',
5024
+ children: [
5025
+ {
5026
+ children: [
5027
+ { value: 'public', name: '公开数据集' },
5028
+ { value: 'private', name: '私有数据' },
5029
+ { value: 'synthetic', name: '合成数据' },
5030
+ { value: 'user-generated', name: '用户生成内容' },
5031
+ ],
5032
+ },
5033
+ ],
5034
+ },
5035
+ ];
5036
+
5037
+ return (
5038
+ <div style={{ padding: 24, display: 'grid', gap: 24, maxWidth: 1200, margin: '0 auto' }}>
5039
+ {/* 基础用法 */}
5040
+ <Card title="基础用法 - Basic Usage">
5041
+ <p style={{ marginBottom: 16, color: '#666' }}>展示多个标签分组,支持折叠/展开和多选</p>
5042
+ <div style={{ marginBottom: 12, padding: 12, background: '#f5f5f5', borderRadius: 4 }}>
5043
+ <strong>当前选中:</strong>
5044
+ <pre style={{ margin: '8px 0 0', fontSize: 12 }}>{JSON.stringify(tagSelected1, null, 2)}</pre>
5045
+ </div>
5046
+ <div style={{ width: 350, border: '1px solid #e8e8e8', borderRadius: 4, padding: 12 }}>
5047
+ <TagGroupFilter
5048
+ tagGroupDatas={basicTagGroupDatas}
5049
+ tagSelected={tagSelected1}
5050
+ onTagChange={setTagSelected1}
5051
+ />
5052
+ </div>
5053
+ </Card>
5054
+
5055
+ {/* 带子标题 */}
5056
+ <Card title="带子标题 - With Subtitles">
5057
+ <p style={{ marginBottom: 16, color: '#666' }}>每个分组可以包含多个子分组,用子标题区分</p>
5058
+ <div style={{ marginBottom: 12, padding: 12, background: '#f5f5f5', borderRadius: 4 }}>
5059
+ <strong>当前选中:</strong>
5060
+ <pre style={{ margin: '8px 0 0', fontSize: 12 }}>{JSON.stringify(tagSelected2, null, 2)}</pre>
5061
+ </div>
5062
+ <div style={{ width: 350, border: '1px solid #e8e8e8', borderRadius: 4, padding: 12 }}>
5063
+ <TagGroupFilter
5064
+ tagGroupDatas={subTitleTagGroupDatas}
5065
+ tagSelected={tagSelected2}
5066
+ onTagChange={setTagSelected2}
5067
+ />
5068
+ </div>
5069
+ </Card>
5070
+
5071
+ {/* 带数据体量筛选 */}
5072
+ <Card title="带数据体量筛选 - With Data Size Filter">
5073
+ <p style={{ marginBottom: 16, color: '#666' }}>启用数据体量范围筛选器,支持 KB/MB/GB/TB 单位</p>
5074
+ <div style={{ marginBottom: 12, padding: 12, background: '#f5f5f5', borderRadius: 4 }}>
5075
+ <strong>标签选中:</strong>
5076
+ <pre style={{ margin: '8px 0 0', fontSize: 12 }}>{JSON.stringify(tagSelected3, null, 2)}</pre>
5077
+ <strong style={{ marginTop: 8, display: 'block' }}>数据体量范围:</strong>
5078
+ <span style={{ fontSize: 12 }}>
5079
+ {dataSizeValue[0] || '最小值'} - {dataSizeValue[1] || '最大值'}
5080
+ </span>
5081
+ </div>
5082
+ <div style={{ width: 350, border: '1px solid #e8e8e8', borderRadius: 4, padding: 12 }}>
5083
+ <TagGroupFilter
5084
+ tagGroupDatas={dataSizeTagGroupDatas}
5085
+ tagSelected={tagSelected3}
5086
+ onTagChange={setTagSelected3}
5087
+ needDataSizeFilter={true}
5088
+ dataSizeValue={dataSizeValue}
5089
+ onDataSizeChange={setDataSizeValue}
5090
+ />
5091
+ </div>
5092
+ </Card>
5093
+
5094
+ {/* 综合示例 */}
5095
+ <Card title="综合示例 - Comprehensive Example">
5096
+ <p style={{ marginBottom: 16, color: '#666' }}>结合多种功能的完整示例</p>
5097
+ <div style={{ width: 350, border: '1px solid #e8e8e8', borderRadius: 4, padding: 12 }}>
5098
+ <TagGroupFilter
5099
+ tagGroupDatas={[
5100
+ ...basicTagGroupDatas,
5101
+ {
5102
+ key: 'format',
5103
+ title: '格式',
5104
+ children: [
5105
+ {
5106
+ children: [
5107
+ { value: 'json', name: 'JSON' },
5108
+ { value: 'csv', name: 'CSV' },
5109
+ { value: 'parquet', name: 'Parquet' },
5110
+ { value: 'tfrecord', name: 'TFRecord' },
5111
+ ],
5112
+ },
5113
+ ],
5114
+ },
5115
+ ]}
5116
+ tagSelected={tagSelected1}
5117
+ onTagChange={setTagSelected1}
5118
+ needDataSizeFilter={true}
5119
+ dataSizeValue={dataSizeValue}
5120
+ onDataSizeChange={setDataSizeValue}
5121
+ />
5122
+ </div>
5123
+ </Card>
5124
+ </div>
5125
+ );
5126
+ }
5127
+ ```
5128
+ ### API
5129
+
5130
+ #### TagGroupFilter
5131
+
5132
+ | 参数 | 说明 | 类型 | 默认值 | 必须 |
5133
+ | --- | --- | --- | --- | --- |
5134
+ | tagGroupDatas | 标签分组数据 | `ITagGroupDataItem[]` | - | 是 |
5135
+ | tagSelected | 已选中的标签(按组 key 索引) | `Record<string, string[]>` | - | 是 |
5136
+ | needDataSizeFilter | 是否显示数据体量筛选器 | `boolean` | `false` | 否 |
5137
+ | isEn | 是否为英文环境 | `boolean` | `false` | 否 |
5138
+ | dataSizeValue | 数据体量筛选值 `[min, max]` | `string[]` | - | 否 |
5139
+ | tagClassName | 自定义标签类名 | `{ default?: string; checked?: string }` | - | 否 |
5140
+ | onTagChange | 标签选中变化回调 | `(tagSelected: Record<string, string[]>) => void` | - | 是 |
5141
+ | onDataSizeChange | 数据体量筛选变化回调 | `(value: string[]) => void` | - | 否 |
5142
+
5143
+ #### 数据结构
5144
+
5145
+ ##### ITagGroupDataItem
5146
+
5147
+ | 参数 | 说明 | 类型 |
5148
+ | --- | --- | --- |
5149
+ | key | 分组的唯一标识 | `string` |
5150
+ | title | 分组标题 | `string` |
5151
+ | children | 标签列表项 | `ITagListItem[]` |
5152
+
5153
+ ##### ITagListItem
5154
+
5155
+ | 参数 | 说明 | 类型 |
5156
+ | --- | --- | --- |
5157
+ | subTitle | 子标题(可选) | `string` |
5158
+ | children | 标签选项列表 | `{ value: string; name: string }[]` |
5159
+
5160
+ #### 使用示例
5161
+
5162
+ ```tsx
5163
+ import TagGroupFilter from '@zjlab-fe/data-hub-ui/components/tag-group-filter';
5164
+ import { useState } from 'react';
5165
+
5166
+ // 基础用法
5167
+ const [tagSelected, setTagSelected] = useState<Record<string, string[]>>({
5168
+ category: [],
5169
+ type: [],
5170
+ });
5171
+
5172
+ const tagGroupDatas = [
5173
+ {
5174
+ key: 'category',
5175
+ title: '分类',
5176
+ children: [
5177
+ {
5178
+ children: [
5179
+ { value: 'ai', name: '人工智能' },
5180
+ { value: 'ml', name: '机器学习' },
5181
+ { value: 'dl', name: '深度学习' },
5182
+ ],
5183
+ },
5184
+ ],
5185
+ },
5186
+ {
5187
+ key: 'type',
5188
+ title: '类型',
5189
+ children: [
5190
+ {
5191
+ children: [
5192
+ { value: 'image', name: '图像' },
5193
+ { value: 'text', name: '文本' },
5194
+ { value: 'audio', name: '音频' },
5195
+ ],
5196
+ },
5197
+ ],
5198
+ },
5199
+ ];
5200
+
5201
+ <TagGroupFilter
5202
+ tagGroupDatas={tagGroupDatas}
5203
+ tagSelected={tagSelected}
5204
+ onTagChange={setTagSelected}
5205
+ />;
5206
+
5207
+ // 带数据体量筛选
5208
+ const [dataSizeValue, setDataSizeValue] = useState<string[]>(['', '']);
5209
+
5210
+ <TagGroupFilter
5211
+ tagGroupDatas={tagGroupDatas}
5212
+ tagSelected={tagSelected}
5213
+ onTagChange={setTagSelected}
5214
+ needDataSizeFilter={true}
5215
+ dataSizeValue={dataSizeValue}
5216
+ onDataSizeChange={setDataSizeValue}
5217
+ />;
5218
+
5219
+ // 带子标题的分组
5220
+ const tagGroupDatasWithSubTitle = [
5221
+ {
5222
+ key: 'domain',
5223
+ title: '领域',
5224
+ children: [
5225
+ {
5226
+ subTitle: '热门领域',
5227
+ children: [
5228
+ { value: 'cv', name: '计算机视觉' },
5229
+ { value: 'nlp', name: '自然语言处理' },
5230
+ ],
5231
+ },
5232
+ {
5233
+ subTitle: '其他领域',
5234
+ children: [
5235
+ { value: 'speech', name: '语音识别' },
5236
+ { value: 'robot', name: '机器人' },
5237
+ ],
5238
+ },
5239
+ ],
5240
+ },
5241
+ ];
5242
+ ```
5243
+
5244
+ #### 注意事项
5245
+
5246
+ 1. **数据体量筛选位置**:当 `needDataSizeFilter` 为 true 时,数据体量筛选器会自动插入到第三个位置(索引 2)
5247
+ 2. **选中状态管理**:`tagSelected` 是一个对象,key 为分组的 key,value 为该组选中的标签值数组
5248
+ 3. **折叠状态**:组件会自动展开所有分组,用户可手动折叠/展开
5249
+ 4. **清空功能**:点击分组标题旁的清空图标可清空该组所有选中项
5250
+ 6. **数据体量格式**:`dataSizeValue` 数组格式为 `["{value}{unit}", "{value}{unit}"]`,如 `["100kb", "5gb"]`
5251
+ 7. **单位转换**:数据体量筛选器内部会将所有值转换为 MB 进行比较,确保范围有效性
5252
+
5253
+ ---
5254
+
5255
+ ## tag-view(标签视图)
5256
+
5257
+ **导入方式:**
5258
+
5259
+ ```ts
5260
+ import { TagView } from '@zjlab-fe/data-hub-ui';
5261
+ ```
5262
+
5263
+ 智能标签展示组件,支持多选、溢出处理、高亮标记等功能。
5264
+
5265
+ #### 特性
5266
+
5267
+ - **智能溢出处理**:自动计算容器宽度,支持多行显示
5268
+ - **两种溢出模式**:Tooltip 提示模式或展开/收起模式
5269
+ - **支持选择**:可选中标签,支持自定义选中/未选中样式
5270
+ - **高亮标记**:支持 `<mark>` 标签高亮显示部分内容
5271
+ - **响应式**:使用 ResizeObserver 监听容器尺寸变化
5272
+
5273
+ ### 代码演示
5274
+
5275
+ **示例代码(index.tsx):**
5276
+
5277
+ ```tsx
5278
+ import React, { useState } from 'react';
5279
+ import TagView from '@/components/tag-view';
5280
+ import { Card, RadioGroup } from '@/demo/shared';
5281
+
5282
+ export default function Demo() {
5283
+ const [selected, setSelected] = useState<string[]>([]);
5284
+ const [maxLine, setMaxLine] = useState<number>(1);
5285
+
5286
+ // 基础标签数据
5287
+ const basicTags = [
5288
+ { value: 'tag1', name: '人工智能' },
5289
+ { value: 'tag2', name: '机器学习' },
5290
+ { value: 'tag3', name: '深度学习' },
5291
+ { value: 'tag4', name: '自然语言处理' },
5292
+ { value: 'tag5', name: '计算机视觉' },
5293
+ { value: 'tag6', name: '数据挖掘' },
5294
+ { value: 'tag7', name: '大数据分析' },
5295
+ { value: 'tag8', name: '云计算' },
5296
+ { value: 'tag9', name: '边缘计算' },
5297
+ { value: 'tag10', name: '物联网' },
5298
+ ];
5299
+
5300
+ // 带高亮标记的标签数据
5301
+ const highlightTags = [
5302
+ { value: 'tag1', name: '<mark>AI</mark>助手' },
5303
+ { value: 'tag2', name: '<mark>智能</mark>推荐' },
5304
+ { value: 'tag3', name: '<mark>自动</mark>化测试' },
5305
+ { value: 'tag4', name: '<mark>实时</mark>分析' },
5306
+ { value: 'tag5', name: '<mark>分布式</mark>计算' },
5307
+ ];
5308
+
5309
+ return (
5310
+ <div style={{ padding: 24, display: 'grid', gap: 24, maxWidth: 1200, margin: '0 auto' }}>
5311
+ {/* 基础用法 */}
5312
+ <Card title="基础用法 - Basic Usage">
5313
+ <p style={{ marginBottom: 16, color: '#666' }}>展示一组标签,支持自动换行和溢出处理</p>
5314
+ <TagView list={basicTags} maxLine={2} />
5315
+ </Card>
5316
+
5317
+ {/* 带选择功能 */}
5318
+ <Card title="带选择功能 - With Selection">
5319
+ <p style={{ marginBottom: 16, color: '#666' }}>点击标签进行选中/取消选中,已选中的标签会高亮显示</p>
5320
+ <div style={{ marginBottom: 12 }}>
5321
+ <strong>已选中:</strong>
5322
+ {selected.length > 0 ? selected.join(', ') : '无'}
5323
+ </div>
5324
+ <TagView list={basicTags} selected={selected} onChange={setSelected} maxLine={2} />
5325
+ </Card>
5326
+
5327
+ {/* 高亮标记 */}
5328
+ <Card title="高亮标记 - Highlight Marks">
5329
+ <p style={{ marginBottom: 16, color: '#666' }}>支持使用 &lt;mark&gt; 标签高亮显示部分内容</p>
5330
+ <TagView list={highlightTags} maxLine={2} />
5331
+ </Card>
5332
+
5333
+ {/* 溢出模式 */}
5334
+ <Card title="溢出模式 - Overflow Mode">
5335
+ <div style={{ display: 'grid', gap: 16, marginBottom: 20 }}>
5336
+ <RadioGroup<string>
5337
+ label="最大行数 Max Line"
5338
+ value={String(maxLine)}
5339
+ options={[
5340
+ { value: '1', label: '1 行' },
5341
+ { value: '2', label: '2 行' },
5342
+ { value: '3', label: '3 行' },
5343
+ ]}
5344
+ onChange={(val) => setMaxLine(Number(val))}
5345
+ />
5346
+ </div>
5347
+ <div style={{ border: '1px solid #e8e8e8', padding: 16, borderRadius: 4, width: '300px' }}>
5348
+ <TagView
5349
+ list={basicTags}
5350
+ maxLine={maxLine}
5351
+ needTooltip={true}
5352
+ selected={selected}
5353
+ onChange={setSelected}
5354
+ />
5355
+ </div>
5356
+ </Card>
5357
+ </div>
5358
+ );
5359
+ }
5360
+ ```
5361
+ ### API
5362
+
5363
+ #### TagView
5364
+
5365
+ | 参数 | 说明 | 类型 | 默认值 | 必须 |
5366
+ | --- | --- | --- | --- | --- |
5367
+ | list | 标签列表数据 | `{ value: string; name: string }[]` | `[]` | 是 |
5368
+ | maxLine | 最多显示行数 | `number` | `1` | 否 |
5369
+ | needTooltip | 省略部分是否使用 Tooltip,false 则使用展开/收起模式 | `boolean` | `false` | 否 |
5370
+ | selected | 已选中的标签值数组 | `string[]` | - | 否 |
5371
+ | style | 自定义样式 | `React.CSSProperties` | - | 否 |
5372
+ | onChange | 选中状态变化回调 | `(selected: string[]) => void` | - | 否 |
5373
+ | tagClassName | 自定义标签类名 | `{ default?: string; checked?: string }` | - | 否 |
5374
+
5375
+ #### 数据结构
5376
+
5377
+ ##### list 项
5378
+
5379
+ | 参数 | 说明 | 类型 |
5380
+ | --- | --- | --- |
5381
+ | value | 标签的唯一标识值 | `string` |
5382
+ | name | 标签显示的名称,支持 `<mark>` 高亮 | `string` |
5383
+
5384
+ #### 使用示例
5385
+
5386
+ ```tsx
5387
+ import TagView from '@zjlab-fe/data-hub-ui/components/tag-view';
5388
+
5389
+ // 基础用法
5390
+ <TagView
5391
+ list={[
5392
+ { value: 'tag1', name: '标签1' },
5393
+ { value: 'tag2', name: '标签2' },
5394
+ { value: 'tag3', name: '标签3' },
5395
+ ]}
5396
+ />
5397
+
5398
+ // 带选择功能
5399
+ const [selected, setSelected] = useState<string[]>([]);
5400
+
5401
+ <TagView
5402
+ list={tags}
5403
+ selected={selected}
5404
+ onChange={setSelected}
5405
+ maxLine={2}
5406
+ tagClassName={{
5407
+ default: 'custom-default-class',
5408
+ checked: 'custom-checked-class',
5409
+ }}
5410
+ />
5411
+
5412
+ // 使用 Tooltip 模式处理溢出
5413
+ <TagView
5414
+ list={tags}
5415
+ maxLine={1}
5416
+ needTooltip={true}
5417
+ />
5418
+
5419
+ // 带高亮标记
5420
+ <TagView
5421
+ list={[
5422
+ { value: 'tag1', name: '<mark>关键</mark>标签1' },
5423
+ { value: 'tag2', name: '普通标签2' },
5424
+ ]}
5425
+ />
5426
+ ```
5427
+
5428
+ #### 注意事项
5429
+
5430
+ 1. **空数据处理**:当 `list` 为空数组时,组件不会渲染任何内容
5431
+ 2. **高亮标记**:`name` 字段支持 `<mark>` 和 `</mark>` 标签包裹需要高亮的文本
5432
+ 3. **响应式更新**:组件会自动监听容器尺寸变化并重新计算布局
5433
+ 4. **选择模式**:只有传入 `selected` 属性时,标签才可点击选择
5434
+ 5. **溢出计算**:基于实际 DOM 元素宽度进行精确计算,确保布局准确
3651
5435
 
3652
5436
  ---
3653
5437