@baishuyun/ai-design 2.0.18 → 2.0.20
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @baishuyun/ai-design
|
|
2
2
|
|
|
3
|
+
## 2.0.20
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- multiple select
|
|
8
|
+
|
|
9
|
+
## 2.0.19
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix attachment params
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- @baishuyun/types@3.0.19
|
|
16
|
+
- @baishuyun/ai-feat-utils@2.0.19
|
|
17
|
+
|
|
3
18
|
## 2.0.18
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@baishuyun/ai-design",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
4
4
|
"description": "AI 功能 UI 组件库 - 所有 AI 功能的 UI 表现唯一真相来源",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"tw-animate-css": "^1.4.0",
|
|
61
61
|
"vaul": "^1.1.2",
|
|
62
62
|
"zod": "^4.1.13",
|
|
63
|
-
"@baishuyun/ai-feat-utils": "2.0.
|
|
64
|
-
"@baishuyun/types": "3.0.
|
|
63
|
+
"@baishuyun/ai-feat-utils": "2.0.19",
|
|
64
|
+
"@baishuyun/types": "3.0.19"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"react": "^19.0.0",
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"typescript": "^5.5.0",
|
|
84
84
|
"vite": "^5.1.4",
|
|
85
85
|
"vite-plugin-dts": "^4.5.4",
|
|
86
|
-
"@baishuyun/
|
|
87
|
-
"@baishuyun/
|
|
88
|
-
"@baishuyun/
|
|
86
|
+
"@baishuyun/typescript-config": "2.0.9",
|
|
87
|
+
"@baishuyun/types": "3.0.19",
|
|
88
|
+
"@baishuyun/tailwind-config": "3.0.9"
|
|
89
89
|
},
|
|
90
90
|
"keywords": [],
|
|
91
91
|
"author": "",
|
|
@@ -19,6 +19,22 @@ const FolderIcon = () => (
|
|
|
19
19
|
</svg>
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
+
/** Close X icon (12x12) for chip dismiss in multiple mode */
|
|
23
|
+
const CloseIcon = () => (
|
|
24
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className="shrink-0">
|
|
25
|
+
<path d="M3 3L9 9M9 3L3 9" stroke="#999999" strokeWidth="1.2" strokeLinecap="round" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/** More dots icon (12x12) for overflow chip in multiple mode */
|
|
30
|
+
const MoreIcon = () => (
|
|
31
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className="shrink-0">
|
|
32
|
+
<circle cx="2" cy="6" r="1" fill="#999999" />
|
|
33
|
+
<circle cx="6" cy="6" r="1" fill="#999999" />
|
|
34
|
+
<circle cx="10" cy="6" r="1" fill="#999999" />
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
|
|
22
38
|
// ── Types ─────────────────────────────────────────────────────────
|
|
23
39
|
|
|
24
40
|
export interface FormSelectItem {
|
|
@@ -31,21 +47,30 @@ export interface FormSelectItem {
|
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
export interface FormSelectChangeResult {
|
|
50
|
+
/** Set in single-select mode. */
|
|
34
51
|
form?: FormSelectItem;
|
|
52
|
+
/** Set in multiple mode — the full selection after the change. */
|
|
53
|
+
forms?: FormSelectItem[];
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
export interface FormSelectProps {
|
|
38
57
|
items: FormSelectItem[];
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
/** Enable multi-select mode. Default false (single-select). */
|
|
59
|
+
multiple?: boolean;
|
|
60
|
+
/** Controlled value — form id in single mode, form id array in multiple mode. */
|
|
61
|
+
value?: string | string[];
|
|
62
|
+
/** Uncontrolled default value — same shape as `value`. */
|
|
63
|
+
defaultValue?: string | string[];
|
|
41
64
|
onChange?: (result: FormSelectChangeResult) => void;
|
|
42
65
|
placeholder?: string;
|
|
43
66
|
disabled?: boolean;
|
|
44
67
|
className?: string;
|
|
45
|
-
/** Allow selecting an empty value as the first option. */
|
|
68
|
+
/** Allow selecting an empty value as the first option. Only effective in single mode. */
|
|
46
69
|
allowEmpty?: boolean;
|
|
47
70
|
/** Label for the empty option. Default "无". */
|
|
48
71
|
emptyLabel?: string;
|
|
72
|
+
/** Max visible chips in the trigger in multiple mode (default 4). */
|
|
73
|
+
maxVisibleChips?: number;
|
|
49
74
|
}
|
|
50
75
|
|
|
51
76
|
// ── Tree types ────────────────────────────────────────────────────
|
|
@@ -192,12 +217,12 @@ function FormEntryItem({
|
|
|
192
217
|
|
|
193
218
|
function FormTreeItemNode({
|
|
194
219
|
item,
|
|
195
|
-
|
|
220
|
+
selectedIds,
|
|
196
221
|
onSelect,
|
|
197
222
|
}: {
|
|
198
223
|
item: FormTreeItem;
|
|
199
|
-
|
|
200
|
-
onSelect: (
|
|
224
|
+
selectedIds: Set<string>;
|
|
225
|
+
onSelect: (item: FormSelectItem) => void;
|
|
201
226
|
}) {
|
|
202
227
|
const [expanded, setExpanded] = useState(true);
|
|
203
228
|
|
|
@@ -232,7 +257,7 @@ function FormTreeItemNode({
|
|
|
232
257
|
<FormTreeItemNode
|
|
233
258
|
key={child.id}
|
|
234
259
|
item={child}
|
|
235
|
-
|
|
260
|
+
selectedIds={selectedIds}
|
|
236
261
|
onSelect={onSelect}
|
|
237
262
|
/>
|
|
238
263
|
))}
|
|
@@ -244,10 +269,10 @@ function FormTreeItemNode({
|
|
|
244
269
|
<FormEntryItem
|
|
245
270
|
item={item}
|
|
246
271
|
depth={item.depth}
|
|
247
|
-
isSelected={
|
|
272
|
+
isSelected={selectedIds.has(item.id)}
|
|
248
273
|
onClick={() => {
|
|
249
274
|
if (item.form) {
|
|
250
|
-
onSelect(
|
|
275
|
+
onSelect(item.form);
|
|
251
276
|
}
|
|
252
277
|
}}
|
|
253
278
|
/>
|
|
@@ -260,7 +285,7 @@ function FormPopover({
|
|
|
260
285
|
searchQuery,
|
|
261
286
|
onSearchChange,
|
|
262
287
|
tree,
|
|
263
|
-
|
|
288
|
+
selectedIds,
|
|
264
289
|
onSelect,
|
|
265
290
|
hasSearch,
|
|
266
291
|
allowEmpty,
|
|
@@ -270,8 +295,8 @@ function FormPopover({
|
|
|
270
295
|
searchQuery: string;
|
|
271
296
|
onSearchChange: (v: string) => void;
|
|
272
297
|
tree: FormTreeItem[];
|
|
273
|
-
|
|
274
|
-
onSelect: (
|
|
298
|
+
selectedIds: Set<string>;
|
|
299
|
+
onSelect: (item: FormSelectItem) => void;
|
|
275
300
|
hasSearch: boolean;
|
|
276
301
|
allowEmpty: boolean;
|
|
277
302
|
emptyLabel: string;
|
|
@@ -301,7 +326,7 @@ function FormPopover({
|
|
|
301
326
|
<div
|
|
302
327
|
className={cn(
|
|
303
328
|
'flex items-center h-[34px] px-[10px] cursor-pointer rounded-[4px] shrink-0',
|
|
304
|
-
|
|
329
|
+
selectedIds.size === 0 ? 'bg-[#F0F6FF] text-[#0265FF]' : 'text-[#030303] hover:bg-[#EFF0F6]',
|
|
305
330
|
transCls,
|
|
306
331
|
)}
|
|
307
332
|
onClick={onEmptySelect}
|
|
@@ -317,7 +342,7 @@ function FormPopover({
|
|
|
317
342
|
<FormTreeItemNode
|
|
318
343
|
key={node.id}
|
|
319
344
|
item={node}
|
|
320
|
-
|
|
345
|
+
selectedIds={selectedIds}
|
|
321
346
|
onSelect={onSelect}
|
|
322
347
|
/>
|
|
323
348
|
))
|
|
@@ -341,6 +366,7 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
341
366
|
(
|
|
342
367
|
{
|
|
343
368
|
items,
|
|
369
|
+
multiple = false,
|
|
344
370
|
value: controlledValue,
|
|
345
371
|
defaultValue,
|
|
346
372
|
onChange,
|
|
@@ -349,14 +375,15 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
349
375
|
className,
|
|
350
376
|
allowEmpty = false,
|
|
351
377
|
emptyLabel = '无',
|
|
378
|
+
maxVisibleChips = 4,
|
|
352
379
|
},
|
|
353
380
|
ref,
|
|
354
381
|
) => {
|
|
355
382
|
const [isOpen, setIsOpen] = useState(false);
|
|
356
383
|
const [searchQuery, setSearchQuery] = useState('');
|
|
357
|
-
const [internalValue, setInternalValue] = useState<
|
|
358
|
-
|
|
359
|
-
);
|
|
384
|
+
const [internalValue, setInternalValue] = useState<
|
|
385
|
+
string | string[] | undefined
|
|
386
|
+
>(defaultValue);
|
|
360
387
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
361
388
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
362
389
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
|
@@ -364,19 +391,41 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
364
391
|
const { popoverStyle, updatePopoverPosition } =
|
|
365
392
|
useSelectPopoverPosition(triggerRef);
|
|
366
393
|
|
|
367
|
-
const
|
|
394
|
+
const resolvedValue = controlledValue ?? internalValue;
|
|
395
|
+
|
|
396
|
+
const selectedIds = useMemo(() => {
|
|
397
|
+
if (Array.isArray(resolvedValue)) {
|
|
398
|
+
return new Set(resolvedValue);
|
|
399
|
+
}
|
|
400
|
+
return new Set(resolvedValue ? [resolvedValue] : []);
|
|
401
|
+
}, [resolvedValue]);
|
|
402
|
+
|
|
403
|
+
const selectedFormId = Array.isArray(resolvedValue)
|
|
404
|
+
? undefined
|
|
405
|
+
: resolvedValue;
|
|
368
406
|
|
|
369
407
|
// Build the form tree
|
|
370
408
|
const tree = useMemo(() => {
|
|
371
409
|
return buildFormTree(items, searchQuery);
|
|
372
410
|
}, [items, searchQuery]);
|
|
373
411
|
|
|
374
|
-
// Find selected form
|
|
412
|
+
// Find selected form (single mode trigger display)
|
|
375
413
|
const selectedForm = useMemo(() => {
|
|
376
414
|
if (!selectedFormId) return null;
|
|
377
415
|
return items.find((item) => item.id === selectedFormId && item.itemType !== 'group') ?? null;
|
|
378
416
|
}, [items, selectedFormId]);
|
|
379
417
|
|
|
418
|
+
// Selected forms (multiple mode chips)
|
|
419
|
+
const selectedForms = useMemo(() => {
|
|
420
|
+
if (!multiple) return [];
|
|
421
|
+
return items.filter(
|
|
422
|
+
(item) => selectedIds.has(item.id) && item.itemType !== 'group',
|
|
423
|
+
);
|
|
424
|
+
}, [multiple, items, selectedIds]);
|
|
425
|
+
|
|
426
|
+
const visibleChips = selectedForms.slice(0, maxVisibleChips);
|
|
427
|
+
const overflowCount = selectedForms.length - visibleChips.length;
|
|
428
|
+
|
|
380
429
|
const hasSearch = searchQuery.trim().length > 0;
|
|
381
430
|
|
|
382
431
|
const setTriggerRef = useCallback((node: HTMLDivElement | null) => {
|
|
@@ -424,13 +473,41 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
424
473
|
}
|
|
425
474
|
}, [disabled, isOpen, handleClose, updatePopoverPosition]);
|
|
426
475
|
|
|
476
|
+
// Single mode: pick one and close. Multiple mode: toggle and stay open.
|
|
427
477
|
const handleSelect = useCallback(
|
|
428
|
-
(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
478
|
+
(form: FormSelectItem) => {
|
|
479
|
+
if (!multiple) {
|
|
480
|
+
setInternalValue(form.id);
|
|
481
|
+
onChange?.({ form });
|
|
482
|
+
handleClose();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const nextIds = selectedIds.has(form.id)
|
|
486
|
+
? [...selectedIds].filter((id) => id !== form.id)
|
|
487
|
+
: [...selectedIds, form.id];
|
|
488
|
+
setInternalValue(nextIds);
|
|
489
|
+
onChange?.({
|
|
490
|
+
forms: items.filter(
|
|
491
|
+
(item) => nextIds.includes(item.id) && item.itemType !== 'group',
|
|
492
|
+
),
|
|
493
|
+
});
|
|
494
|
+
},
|
|
495
|
+
[multiple, selectedIds, items, onChange, handleClose],
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
const handleRemoveChip = useCallback(
|
|
499
|
+
(formId: string, e: React.MouseEvent) => {
|
|
500
|
+
e.stopPropagation();
|
|
501
|
+
if (disabled) return;
|
|
502
|
+
const nextIds = [...selectedIds].filter((id) => id !== formId);
|
|
503
|
+
setInternalValue(nextIds);
|
|
504
|
+
onChange?.({
|
|
505
|
+
forms: items.filter(
|
|
506
|
+
(item) => nextIds.includes(item.id) && item.itemType !== 'group',
|
|
507
|
+
),
|
|
508
|
+
});
|
|
432
509
|
},
|
|
433
|
-
[
|
|
510
|
+
[disabled, selectedIds, items, onChange],
|
|
434
511
|
);
|
|
435
512
|
|
|
436
513
|
const handleEmptySelect = useCallback(() => {
|
|
@@ -534,6 +611,8 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
534
611
|
|
|
535
612
|
// ── Render ────────────────────────────────────────────────────
|
|
536
613
|
|
|
614
|
+
const hasMultiSelection = multiple && selectedForms.length > 0;
|
|
615
|
+
|
|
537
616
|
return (
|
|
538
617
|
<div ref={ref} className={cn('relative inline-flex min-w-0 w-full', className)}>
|
|
539
618
|
{/* Trigger */}
|
|
@@ -550,23 +629,54 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
550
629
|
)}
|
|
551
630
|
onClick={handleToggle}
|
|
552
631
|
>
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
632
|
+
{hasMultiSelection ? (
|
|
633
|
+
<span className="flex items-center gap-[4px] min-w-0 flex-1 overflow-x-auto scrollbar-hide">
|
|
634
|
+
{visibleChips.map((form) => (
|
|
635
|
+
<span
|
|
636
|
+
key={form.id}
|
|
637
|
+
className="flex items-center gap-[4px] bg-[#EEEEEE] rounded-[2px] h-[26px] px-[6px] shrink-0 max-w-[160px]"
|
|
638
|
+
onClick={(e) => handleRemoveChip(form.id, e)}
|
|
639
|
+
>
|
|
640
|
+
<i
|
|
641
|
+
className={getFormIconClass(form.icon ?? 0)}
|
|
642
|
+
style={{ fontSize: 14, lineHeight: 1 }}
|
|
643
|
+
/>
|
|
644
|
+
<span className="text-[14px] text-[#030303] truncate leading-[normal]">
|
|
645
|
+
{form.name}
|
|
646
|
+
</span>
|
|
647
|
+
<span className="cursor-pointer flex items-center shrink-0 hover:bg-[#EFF0F6]">
|
|
648
|
+
<CloseIcon />
|
|
649
|
+
</span>
|
|
562
650
|
</span>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
651
|
+
))}
|
|
652
|
+
{overflowCount > 0 && (
|
|
653
|
+
<span className="flex items-center gap-[4px] bg-[#EEEEEE] rounded-[2px] h-[26px] px-[6px] shrink-0">
|
|
654
|
+
<span className="text-[14px] text-[#030303] leading-[normal]">
|
|
655
|
+
+{overflowCount}
|
|
656
|
+
</span>
|
|
657
|
+
<MoreIcon />
|
|
658
|
+
</span>
|
|
659
|
+
)}
|
|
660
|
+
</span>
|
|
661
|
+
) : (
|
|
662
|
+
<span className="flex items-center gap-[6px] min-w-0 flex-1">
|
|
663
|
+
{selectedForm ? (
|
|
664
|
+
<>
|
|
665
|
+
<i
|
|
666
|
+
className={getFormIconClass(selectedForm.icon ?? 0)}
|
|
667
|
+
style={{ fontSize: 16, lineHeight: 1 }}
|
|
668
|
+
/>
|
|
669
|
+
<span className="text-[14px] text-[#030303] leading-[normal] truncate">
|
|
670
|
+
{selectedForm.name}
|
|
671
|
+
</span>
|
|
672
|
+
</>
|
|
673
|
+
) : (
|
|
674
|
+
<span className="text-[14px] text-[#999999] leading-[normal] truncate">
|
|
675
|
+
{allowEmpty && !multiple ? emptyLabel : placeholder}
|
|
676
|
+
</span>
|
|
677
|
+
)}
|
|
678
|
+
</span>
|
|
679
|
+
)}
|
|
570
680
|
<span
|
|
571
681
|
className={cn(
|
|
572
682
|
'shrink-0 flex items-center justify-center w-[16px] h-[16px] text-[#666666]',
|
|
@@ -586,10 +696,10 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
586
696
|
searchQuery={searchQuery}
|
|
587
697
|
onSearchChange={setSearchQuery}
|
|
588
698
|
tree={tree}
|
|
589
|
-
|
|
699
|
+
selectedIds={selectedIds}
|
|
590
700
|
onSelect={handleSelect}
|
|
591
701
|
hasSearch={hasSearch}
|
|
592
|
-
allowEmpty={allowEmpty}
|
|
702
|
+
allowEmpty={allowEmpty && !multiple}
|
|
593
703
|
emptyLabel={emptyLabel}
|
|
594
704
|
onEmptySelect={handleEmptySelect}
|
|
595
705
|
/>
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from 'clsx';
|
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
|
3
|
-
import { FieldType, IAddress, ILocation, IField } from '@baishuyun/types';
|
|
3
|
+
import { FieldType, IAddress, ILocation, IField, ILinkedData } from '@baishuyun/types';
|
|
4
4
|
|
|
5
5
|
export function cn(...inputs: ClassValue[]) {
|
|
6
6
|
return twMerge(clsx(inputs));
|
|
@@ -80,6 +80,10 @@ export const toHumanReadable = (value: unknown, type: FieldType): string | unkno
|
|
|
80
80
|
return `经度: ${lnglatXY[0] || ''}, 纬度: ${lnglatXY[1] || ''}`;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
if (type === 'linkdata') {
|
|
84
|
+
return (value as ILinkedData).label;
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
if (Array.isArray(value)) {
|
|
84
88
|
return value.join(', ');
|
|
85
89
|
}
|
|
@@ -140,3 +140,83 @@ export const Interactive: Story = {
|
|
|
140
140
|
placeholder: '请选择表单',
|
|
141
141
|
},
|
|
142
142
|
};
|
|
143
|
+
|
|
144
|
+
export const Multiple: Story = {
|
|
145
|
+
args: {
|
|
146
|
+
items: mockFormItems,
|
|
147
|
+
multiple: true,
|
|
148
|
+
placeholder: '请选择表单(可多选)',
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const MultipleWithSelection: Story = {
|
|
153
|
+
args: {
|
|
154
|
+
items: mockFormItems,
|
|
155
|
+
multiple: true,
|
|
156
|
+
defaultValue: ['form3', 'form4', 'form7'],
|
|
157
|
+
placeholder: '请选择表单(可多选)',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const MultipleWithOverflow: Story = {
|
|
162
|
+
args: {
|
|
163
|
+
items: manyFormItems,
|
|
164
|
+
multiple: true,
|
|
165
|
+
defaultValue: ['form_0', 'form_1', 'form_2', 'form_3', 'form_4', 'form_5'],
|
|
166
|
+
maxVisibleChips: 2,
|
|
167
|
+
placeholder: '请选择表单(可多选)',
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const MultipleDisabled: Story = {
|
|
172
|
+
args: {
|
|
173
|
+
items: mockFormItems,
|
|
174
|
+
multiple: true,
|
|
175
|
+
defaultValue: ['form1', 'form4'],
|
|
176
|
+
disabled: true,
|
|
177
|
+
placeholder: '请选择表单(可多选)',
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const MultipleInteractive: Story = {
|
|
182
|
+
render: (args) => {
|
|
183
|
+
const [value, setValue] = useState<string[]>(['form1']);
|
|
184
|
+
const [selected, setSelected] = useState<FormSelectChangeResult | null>(null);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
188
|
+
<FormSelect
|
|
189
|
+
{...args}
|
|
190
|
+
multiple
|
|
191
|
+
value={value}
|
|
192
|
+
onChange={(result) => {
|
|
193
|
+
setSelected(result);
|
|
194
|
+
setValue(result.forms?.map((f) => f.id) ?? []);
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
197
|
+
<div
|
|
198
|
+
style={{
|
|
199
|
+
padding: 12,
|
|
200
|
+
background: '#F5F7FA',
|
|
201
|
+
borderRadius: 8,
|
|
202
|
+
fontSize: 14,
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
<div>
|
|
206
|
+
<strong>已选数量:</strong> {selected?.forms?.length ?? 1}
|
|
207
|
+
</div>
|
|
208
|
+
<div>
|
|
209
|
+
<strong>已选表单:</strong>{' '}
|
|
210
|
+
{(selected?.forms ?? [])
|
|
211
|
+
.map((f) => `${f.name}(${f.id})`)
|
|
212
|
+
.join('、') || 'form1'}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
args: {
|
|
219
|
+
items: mockFormItems,
|
|
220
|
+
placeholder: '请选择表单(可多选)',
|
|
221
|
+
},
|
|
222
|
+
};
|