@huibo-ui/react-antd 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Cascader.d.ts +11 -3
- package/lib/components/Cascader.js +21 -1
- package/lib/components/DatePicker.js +61 -3
- package/lib/components/Descriptions.d.ts +28 -2
- package/lib/components/Descriptions.js +78 -7
- package/lib/components/Form.js +21 -3
- package/lib/components/Modal.js +4 -1
- package/lib/components/Select.d.ts +7 -0
- package/lib/components/Select.js +39 -4
- package/package.json +1 -1
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export
|
|
2
|
+
export interface CascaderProps {
|
|
3
3
|
options?: any[];
|
|
4
4
|
value?: any;
|
|
5
|
-
onChange?: (value: any) => void;
|
|
5
|
+
onChange?: (value: any, selectOptions?: any) => void;
|
|
6
6
|
multiple?: boolean;
|
|
7
7
|
placeholder?: string;
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
allowClear?: boolean;
|
|
10
10
|
showSearch?: boolean;
|
|
11
|
+
/** antd fieldNames:自定义节点字段名,如 { label:'name', value:'id', children:'child' } */
|
|
12
|
+
fieldNames?: {
|
|
13
|
+
label?: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
children?: string;
|
|
16
|
+
};
|
|
11
17
|
style?: React.CSSProperties;
|
|
12
|
-
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
export declare function Cascader(props: CascaderProps): React.JSX.Element;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { HbCascader } from '@huibo-ui/react';
|
|
3
|
+
/**
|
|
4
|
+
* 按 antd fieldNames 递归把自定义节点字段映射为 hb-cascader 认的 {label,value,children},
|
|
5
|
+
* 保留原始字段(onChange 第二参数对齐 antd 返回原始 option)。
|
|
6
|
+
*/
|
|
7
|
+
function mapCascader(nodes, fn) {
|
|
8
|
+
if (!nodes || nodes.length === 0)
|
|
9
|
+
return [];
|
|
10
|
+
if (!fn)
|
|
11
|
+
return nodes;
|
|
12
|
+
const ln = fn.label || 'label';
|
|
13
|
+
const vn = fn.value || 'value';
|
|
14
|
+
const cn = fn.children || 'children';
|
|
15
|
+
return nodes.map(n => {
|
|
16
|
+
const mapped = { ...n, label: n[ln], value: n[vn] };
|
|
17
|
+
if (Array.isArray(n[cn]))
|
|
18
|
+
mapped.children = mapCascader(n[cn], fn);
|
|
19
|
+
return mapped;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
3
22
|
export function Cascader(props) {
|
|
4
|
-
|
|
23
|
+
const options = mapCascader(props.options, props.fieldNames);
|
|
24
|
+
return (_jsx(HbCascader, { options: options, modelValue: props.value, multiple: props.multiple, placeholder: props.placeholder, disabled: props.disabled, clearable: props.allowClear, filterable: props.showSearch, onHbChange: (e) => props.onChange?.(e.detail), style: props.style }));
|
|
5
25
|
}
|
|
@@ -1,15 +1,73 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { HbDatePicker, HbDateRangePicker } from '@huibo-ui/react';
|
|
3
|
+
/**
|
|
4
|
+
* antd DatePicker 兼容层。
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ dayjs 互操作(已知限制):
|
|
7
|
+
* antd5 DatePicker 以 dayjs 对象作为 value/onChange 第一参数。huibo hb-date-picker
|
|
8
|
+
* 内部只认 string | Date。本层做了输入侧兼容(dayjs/moment-like 带 .toDate/.format
|
|
9
|
+
* 的值会被归一化为 Date 传入 core),输出侧 onChange 返回 core 的原始值(string|Date)
|
|
10
|
+
* + 正确格式化的 dateString。若业务代码对 onChange 的 value 调 .format()(dayjs 用法),
|
|
11
|
+
* 需自行包一层或后续引入 dayjs 做完整往返(dayjs 仅在消费方 antd5 侧可得,本包不硬依赖)。
|
|
12
|
+
*/
|
|
13
|
+
/** 支持 YYYY MM DD HH mm ss 的最小格式化(与 core formatDate 对齐) */
|
|
14
|
+
function fmtDate(date, fmt) {
|
|
15
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
16
|
+
return fmt
|
|
17
|
+
.replace('YYYY', String(date.getFullYear()))
|
|
18
|
+
.replace('MM', p(date.getMonth() + 1))
|
|
19
|
+
.replace('DD', p(date.getDate()))
|
|
20
|
+
.replace('HH', p(date.getHours()))
|
|
21
|
+
.replace('mm', p(date.getMinutes()))
|
|
22
|
+
.replace('ss', p(date.getSeconds()));
|
|
23
|
+
}
|
|
24
|
+
/** 把任意输入值归一化为 core 可接受的 string | Date | undefined */
|
|
25
|
+
function normalizeValue(v) {
|
|
26
|
+
if (v === null || v === undefined || v === '')
|
|
27
|
+
return undefined;
|
|
28
|
+
if (v instanceof Date)
|
|
29
|
+
return v;
|
|
30
|
+
if (typeof v === 'string' || typeof v === 'number')
|
|
31
|
+
return v;
|
|
32
|
+
// dayjs / moment-like
|
|
33
|
+
if (typeof v.toDate === 'function')
|
|
34
|
+
return v.toDate();
|
|
35
|
+
if (typeof v.format === 'function')
|
|
36
|
+
return v.format('YYYY-MM-DD HH:mm:ss');
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
/** core 输出(string|Date|undefined)→ [value, dateString] */
|
|
40
|
+
function toOutgoing(raw, fmt) {
|
|
41
|
+
if (raw === undefined || raw === null || raw === '')
|
|
42
|
+
return [undefined, ''];
|
|
43
|
+
const d = raw instanceof Date ? raw : new Date(raw);
|
|
44
|
+
const ds = isNaN(d.getTime()) ? '' : fmtDate(d, fmt);
|
|
45
|
+
return [raw, ds];
|
|
46
|
+
}
|
|
3
47
|
export function DatePicker(props) {
|
|
4
|
-
|
|
48
|
+
// showTime 且未指定 format 时,默认带时分秒(对齐 antd)
|
|
49
|
+
const defaultFmt = props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
|
|
50
|
+
const fmt = props.format || defaultFmt;
|
|
51
|
+
// core type 仅支持 date/month/year,week/quarter 退化为 date
|
|
52
|
+
const picker = props.picker;
|
|
53
|
+
const coreType = picker === 'month' || picker === 'year' ? picker : 'date';
|
|
54
|
+
return (_jsx(HbDatePicker, { modelValue: normalizeValue(props.value), type: coreType, format: fmt, clearable: props.allowClear, disabled: props.disabled, size: props.size === 'middle' ? 'default' : props.size, placeholder: props.placeholder, presets: props.presets, showTime: props.showTime, status: props.status, disabledDate: props.disabledDate, style: props.style, className: props.className, onHbChange: (e) => {
|
|
55
|
+
const [val, ds] = toOutgoing(e?.detail, fmt);
|
|
56
|
+
props.onChange?.(val, ds);
|
|
57
|
+
} }));
|
|
5
58
|
}
|
|
6
59
|
/**
|
|
7
60
|
* DatePicker.RangePicker —— 范围选择器,走 hb-date-range-picker。
|
|
8
61
|
* value 为 [start, end] 数组;onChange 回调对齐 antd:(dates, [startStr, endStr])。
|
|
9
62
|
*/
|
|
10
63
|
DatePicker.RangePicker = function RangePicker(props) {
|
|
11
|
-
|
|
64
|
+
const defaultFmt = props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
|
|
65
|
+
const fmt = props.format || defaultFmt;
|
|
66
|
+
const norm = (v) => (Array.isArray(v) ? v.map(normalizeValue) : []);
|
|
67
|
+
return (_jsx(HbDateRangePicker, { modelValue: norm(props.value || []), format: fmt, clearable: props.allowClear, disabled: props.disabled, size: props.size === 'middle' ? 'default' : props.size, showTime: props.showTime, presets: props.presets, status: props.status, disabledDate: props.disabledDate, style: props.style, className: props.className, onHbChange: (e) => {
|
|
12
68
|
const detail = e?.detail || [];
|
|
13
|
-
|
|
69
|
+
const [s, se] = toOutgoing(detail[0], fmt);
|
|
70
|
+
const [e2, ee] = toOutgoing(detail[1], fmt);
|
|
71
|
+
props.onChange?.([s, e2], [se, ee]);
|
|
14
72
|
} }));
|
|
15
73
|
};
|
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* antd Descriptions 兼容层(自包含原生 HTML 渲染)。
|
|
4
|
+
*
|
|
5
|
+
* 为什么不用 core 的 hb-descriptions:
|
|
6
|
+
* 1. hb-descriptions 的 item.value 只接受 string,但 antd 业务里描述项的值
|
|
7
|
+
* 极常见是 React 节点(状态 Tag、链接、格式化金额),传 string 会丢内容。
|
|
8
|
+
* 2. hb-descriptions 不渲染 slotted children,antd4 声明式 <Descriptions.Item>
|
|
9
|
+
* 写法完全无效。
|
|
10
|
+
* 3. hb-descriptions 不认 antd5 的 items 数组 prop(曾导致 scrm 详情页全空)。
|
|
11
|
+
* 故本层用纯 React 渲染(light DOM),支持 items + children 两种写法、完整 React 节点。
|
|
12
|
+
* 样式内联,对齐 antd 视觉(bordered 表格 / 默认带冒号分行)。
|
|
13
|
+
*/
|
|
14
|
+
export interface DescriptionsItem {
|
|
15
|
+
label?: React.ReactNode;
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
span?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface DescriptionsProps {
|
|
3
20
|
title?: React.ReactNode;
|
|
4
21
|
bordered?: boolean;
|
|
5
22
|
column?: number;
|
|
23
|
+
colon?: boolean;
|
|
6
24
|
layout?: 'horizontal' | 'vertical';
|
|
25
|
+
size?: 'default' | 'middle' | 'small';
|
|
26
|
+
items?: DescriptionsItem[];
|
|
27
|
+
/** antd contentStyle:作用到每个值单元格 */
|
|
28
|
+
contentStyle?: React.CSSProperties;
|
|
29
|
+
/** antd labelStyle:作用到每个标签单元格 */
|
|
30
|
+
labelStyle?: React.CSSProperties;
|
|
7
31
|
children?: React.ReactNode;
|
|
8
32
|
style?: React.CSSProperties;
|
|
9
|
-
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function Descriptions(props: DescriptionsProps): React.JSX.Element;
|
|
10
36
|
export declare namespace Descriptions {
|
|
11
37
|
var Item: any;
|
|
12
38
|
}
|
|
@@ -1,13 +1,84 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import React from 'react';
|
|
3
|
+
const SPAN = (v) => {
|
|
4
|
+
const n = Number(v);
|
|
5
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;
|
|
6
|
+
};
|
|
7
|
+
/** 从 antd4 声明式 <Descriptions.Item> children 收集为 items 数组 */
|
|
8
|
+
function itemsFromChildren(children) {
|
|
9
|
+
const out = [];
|
|
10
|
+
React.Children.forEach(children, child => {
|
|
11
|
+
if (!React.isValidElement(child))
|
|
12
|
+
return;
|
|
13
|
+
const p = child.props || {};
|
|
14
|
+
// DescriptionItem 占位组件不实际渲染,但其 props.label/children/span 仍可被父级读取
|
|
15
|
+
if (p.label !== undefined || p.children !== undefined) {
|
|
16
|
+
out.push({ label: p.label, value: p.children, span: SPAN(p.span) });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
3
21
|
export function Descriptions(props) {
|
|
4
|
-
|
|
22
|
+
const { title, bordered = false, column = 3, colon = true, items, contentStyle, labelStyle, children, style, className, } = props;
|
|
23
|
+
const resolved = items && items.length > 0
|
|
24
|
+
? items.map(it => ({ label: it.label, value: it.children, span: SPAN(it.span) }))
|
|
25
|
+
: itemsFromChildren(children);
|
|
26
|
+
const cols = column > 0 ? Math.floor(column) : 3;
|
|
27
|
+
// 按列数把 items 切成行(span 累计超过列数则换行),用于 bordered 表格渲染
|
|
28
|
+
const rows = [];
|
|
29
|
+
let cur = [];
|
|
30
|
+
let used = 0;
|
|
31
|
+
for (const it of resolved) {
|
|
32
|
+
if (used > 0 && used + it.span > cols) {
|
|
33
|
+
rows.push(cur);
|
|
34
|
+
cur = [];
|
|
35
|
+
used = 0;
|
|
36
|
+
}
|
|
37
|
+
cur.push(it);
|
|
38
|
+
used += it.span;
|
|
39
|
+
}
|
|
40
|
+
if (cur.length)
|
|
41
|
+
rows.push(cur);
|
|
42
|
+
const BORDER = '1px solid #f0f0f0';
|
|
43
|
+
const LABEL_BG = '#fafafa';
|
|
44
|
+
const LABEL_COLOR = 'rgba(0,0,0,0.88)';
|
|
45
|
+
const VALUE_COLOR = 'rgba(0,0,0,0.88)';
|
|
46
|
+
const PAD = 16;
|
|
47
|
+
const titleNode = title != null ? (_jsx("div", { style: { fontSize: 16, fontWeight: 500, color: LABEL_COLOR, marginBottom: 12 }, children: title })) : null;
|
|
48
|
+
let body;
|
|
49
|
+
if (bordered) {
|
|
50
|
+
body = (_jsx("table", { style: { borderCollapse: 'collapse', width: '100%', tableLayout: 'fixed', fontSize: 14 }, children: _jsx("tbody", { children: rows.map((row, ri) => (_jsx("tr", { children: row.flatMap((it, ci) => [
|
|
51
|
+
_jsxs("th", { style: {
|
|
52
|
+
textAlign: 'left',
|
|
53
|
+
width: `${(100 / (cols * 2)).toFixed(2)}%`,
|
|
54
|
+
padding: PAD,
|
|
55
|
+
background: LABEL_BG,
|
|
56
|
+
border: BORDER,
|
|
57
|
+
color: 'rgba(0,0,0,0.88)',
|
|
58
|
+
fontWeight: 400,
|
|
59
|
+
verticalAlign: 'top',
|
|
60
|
+
...(labelStyle || {}),
|
|
61
|
+
}, children: [it.label, colon ? ':' : ''] }, `l${ci}`),
|
|
62
|
+
_jsx("td", { colSpan: it.span > 1 ? it.span * 2 - 1 : undefined, style: { padding: PAD, border: BORDER, color: VALUE_COLOR, verticalAlign: 'top', wordBreak: 'break-word', ...(contentStyle || {}) }, children: it.value }, `v${ci}`),
|
|
63
|
+
]) }, ri))) }) }));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
body = (_jsx("div", { style: { display: 'grid', gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }, children: resolved.map((it, i) => (_jsxs("div", { style: {
|
|
67
|
+
gridColumn: it.span > 1 ? `span ${it.span}` : undefined,
|
|
68
|
+
display: 'flex',
|
|
69
|
+
flexWrap: 'wrap',
|
|
70
|
+
alignItems: 'baseline',
|
|
71
|
+
padding: `${PAD}px ${PAD}px ${PAD}px 0`,
|
|
72
|
+
borderBottom: BORDER,
|
|
73
|
+
...(contentStyle || {}),
|
|
74
|
+
}, children: [_jsxs("span", { style: { flex: 'none', maxWidth: '40%', color: 'rgba(0,0,0,0.45)', marginRight: 8, ...(labelStyle || {}) }, children: [it.label, colon ? ':' : ''] }), _jsx("span", { style: { flex: 'auto', minWidth: 0, color: VALUE_COLOR }, children: it.value })] }, i))) }));
|
|
75
|
+
}
|
|
76
|
+
return (_jsxs("div", { className: className, style: style, children: [titleNode, body] }));
|
|
5
77
|
}
|
|
6
78
|
/**
|
|
7
|
-
* Descriptions.Item ——
|
|
8
|
-
*
|
|
9
|
-
* 保证内容可见、不崩溃(视觉与原生略有差异)。
|
|
79
|
+
* Descriptions.Item —— antd4 声明式用法占位。
|
|
80
|
+
* 自身不渲染(返回 null),由父 Descriptions 通过 React.Children 收集为 items。
|
|
10
81
|
*/
|
|
11
|
-
Descriptions.Item = function DescriptionItem(
|
|
12
|
-
return
|
|
82
|
+
Descriptions.Item = function DescriptionItem(_props) {
|
|
83
|
+
return null;
|
|
13
84
|
};
|
package/lib/components/Form.js
CHANGED
|
@@ -76,14 +76,32 @@ export const Form = React.forwardRef(function Form(props, ref) {
|
|
|
76
76
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
77
|
}, []);
|
|
78
78
|
const labelPosition = layout === 'vertical' ? 'top' : layout === 'inline' ? 'left' : 'right';
|
|
79
|
-
const
|
|
80
|
-
|
|
79
|
+
const elRef = React.useRef(null);
|
|
80
|
+
const refCallback = (el) => {
|
|
81
|
+
elRef.current = el;
|
|
82
|
+
form.__setRef?.(el);
|
|
83
|
+
};
|
|
84
|
+
// ⚠️ Stencil React 包装器(@huibo-ui/react)会把所有 onXxx prop 当作「事件监听器」:
|
|
85
|
+
// onFinish → addEventListener('finish', …),但 hb-form 把 onFinish 当「函数属性」直接调用、
|
|
86
|
+
// 并不派发 'finish' 事件 → 走 JSX prop 会被挂成永不触发的监听器,hb-form 的 this.onFinish 恒为 undefined。
|
|
87
|
+
// 故 onFinish / onFinishFailed / onValuesChange 必须直接设在 hb-form 元素实例上。
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
const el = elRef.current;
|
|
90
|
+
if (!el)
|
|
91
|
+
return;
|
|
92
|
+
el.onFinish = onFinish;
|
|
93
|
+
el.onFinishFailed = onFinishFailed
|
|
94
|
+
? (errors) => onFinishFailed({ errorFields: errors, values: {} })
|
|
95
|
+
: undefined;
|
|
96
|
+
el.onValuesChange = (info) => {
|
|
81
97
|
const prop = info?.prop;
|
|
82
98
|
if (prop)
|
|
83
99
|
form.__syncValue?.(prop, info.value);
|
|
84
100
|
if (onValuesChange)
|
|
85
101
|
onValuesChange({ [prop]: info?.value }, {});
|
|
86
|
-
}
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
return (_jsx(HbForm, { ref: refCallback, initialValues: initialValues, labelPosition: labelPosition, labelWidth: labelCol?.span ? `${(labelCol.span / 24) * 100}%` : '80px', style: style, className: className, children: children }));
|
|
87
105
|
});
|
|
88
106
|
Form.useForm = function useForm() {
|
|
89
107
|
const [f] = React.useState(createFormInstance);
|
package/lib/components/Modal.js
CHANGED
|
@@ -6,7 +6,10 @@ export function Modal(props) {
|
|
|
6
6
|
// 默认 footer:OK/Cancel 按钮(对齐 antd 默认行为)。footer=null 时无底部。
|
|
7
7
|
const defaultFooter = (_jsxs("div", { style: { textAlign: 'right', marginTop: 16, display: 'flex', justifyContent: 'flex-end', gap: 8 }, children: [_jsx("button", { type: "button", onClick: (e) => onCancel?.(e), style: { padding: '4px 15px', height: 32, borderRadius: 6, border: '1px solid #d9d9d9', background: '#fff', cursor: 'pointer' }, children: cancelText || '取消' }), _jsx("button", { type: "button", disabled: confirmLoading, onClick: (e) => onOk?.(e), style: { padding: '4px 15px', height: 32, borderRadius: 6, border: 'none', background: 'var(--hb-color-primary, #ff6700)', color: '#fff', cursor: 'pointer' }, children: confirmLoading ? '加载中...' : (okText || '确定') })] }));
|
|
8
8
|
const resolvedFooter = footer === undefined ? defaultFooter : footer;
|
|
9
|
-
|
|
9
|
+
// antd width 默认 520(number);hb-dialog width 是 string,number 必须补 px 否则是非法 CSS 被忽略
|
|
10
|
+
const resolvedWidth = width == null ? undefined : typeof width === 'number' ? `${width}px` : String(width);
|
|
11
|
+
const wrapClassName = props.wrapClassName;
|
|
12
|
+
return (_jsxs(HbDialog, { modelValue: open || false, title: typeof title === 'string' ? title : '', width: resolvedWidth, closeOnClickModal: maskClosable, closeOnPressEscape: keyboard, destroyOnClose: destroyOnClose, showClose: true, style: props.style, className: [props.className, wrapClassName].filter(Boolean).join(' ') || undefined, onHbClose: () => { if (onCancel)
|
|
10
13
|
onCancel({}); }, children: [children, resolvedFooter !== null && resolvedFooter !== undefined ? resolvedFooter : null] }));
|
|
11
14
|
}
|
|
12
15
|
function imperative(opts) {
|
|
@@ -23,6 +23,13 @@ export interface SelectProps {
|
|
|
23
23
|
virtual?: boolean;
|
|
24
24
|
onSearch?: (value: string) => void;
|
|
25
25
|
onDropdownVisibleChange?: (open: boolean) => void;
|
|
26
|
+
/** antd fieldNames:自定义 option 字段名映射,如 { label:'nickName', value:'id' } */
|
|
27
|
+
fieldNames?: {
|
|
28
|
+
label?: string;
|
|
29
|
+
value?: string;
|
|
30
|
+
disabled?: string;
|
|
31
|
+
options?: string;
|
|
32
|
+
};
|
|
26
33
|
}
|
|
27
34
|
export declare function Select(props: SelectProps): React.JSX.Element;
|
|
28
35
|
export declare namespace Select {
|
package/lib/components/Select.js
CHANGED
|
@@ -28,11 +28,46 @@ function SelectOptionComponent(_props) {
|
|
|
28
28
|
}
|
|
29
29
|
export function Select(props) {
|
|
30
30
|
const { value, onChange, options, mode, placeholder, disabled, size, allowClear, showSearch, virtual, style, children, } = props;
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
-
|
|
31
|
+
// antd maxTagCount:多选时折叠超出数量的 tag。映射到 hb-select 的 collapseTags + maxCollapseTags
|
|
32
|
+
const maxTagCount = props.maxTagCount;
|
|
33
|
+
// antd fieldNames:自定义 option 字段名(如 {label:'nickName', value:'id'})。
|
|
34
|
+
// hb-select 只认 {value,label,disabled},必须按 fieldNames 重映射,否则下拉项无文案、值 undefined。
|
|
35
|
+
const fieldNames = props.fieldNames;
|
|
36
|
+
function mapOptions(opts) {
|
|
37
|
+
if (!opts || opts.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
if (!fieldNames)
|
|
40
|
+
return opts;
|
|
41
|
+
const ln = fieldNames.label || 'label';
|
|
42
|
+
const vn = fieldNames.value || 'value';
|
|
43
|
+
const dn = fieldNames.disabled || 'disabled';
|
|
44
|
+
const on = fieldNames.options; // OptGroup 子选项字段
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const o of opts) {
|
|
47
|
+
if (on && Array.isArray(o[on])) {
|
|
48
|
+
for (const c of o[on])
|
|
49
|
+
out.push({ ...c, value: c[vn], label: c[ln], disabled: c[dn] });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
out.push({ ...o, value: o[vn], label: o[ln], disabled: o[dn] });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
// 优先用 options prop;否则从 <Select.Option> children 提取;再按 fieldNames 映射
|
|
58
|
+
const resolvedOptions = mapOptions(options && options.length > 0 ? options : optionsFromChildren(children));
|
|
59
|
+
// 从 options 里查出选中项(单选返回单个 option,多选返回数组),对齐 antd onChange 第二参数
|
|
60
|
+
const resolveOption = (val) => {
|
|
61
|
+
if (!resolvedOptions || resolvedOptions.length === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (Array.isArray(val)) {
|
|
64
|
+
return val.map(x => resolvedOptions.find(o => o.value === x)).filter(o => o !== undefined);
|
|
65
|
+
}
|
|
66
|
+
return resolvedOptions.find(o => o.value === val) || undefined;
|
|
67
|
+
};
|
|
68
|
+
return (_jsx(HbSelect, { modelValue: value, options: resolvedOptions, multiple: mode === 'multiple' || mode === 'tags', placeholder: placeholder, disabled: disabled, size: size === 'middle' ? 'default' : size, clearable: allowClear, filterable: showSearch, virtual: virtual, collapseTags: maxTagCount != null || mode === 'tags', maxCollapseTags: maxTagCount, style: style, onHbChange: (e) => {
|
|
34
69
|
if (onChange)
|
|
35
|
-
onChange(e.detail,
|
|
70
|
+
onChange(e.detail, resolveOption(e.detail));
|
|
36
71
|
}, onHbVisibleChange: (e) => {
|
|
37
72
|
if (props.onDropdownVisibleChange)
|
|
38
73
|
props.onDropdownVisibleChange(e.detail);
|