feat: improve ApiSelect
component (#5075)
* feat: improve `ApiSelect` component * chore: `ApiSelect` props name changed
This commit is contained in:
parent
305549e7f2
commit
d085736bac
@ -49,6 +49,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'AutoComplete'
|
| 'AutoComplete'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
@ -88,7 +89,23 @@ async function initComponentAdapter() {
|
|||||||
component: Select,
|
component: Select,
|
||||||
loadingSlot: 'suffixIcon',
|
loadingSlot: 'suffixIcon',
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
visibleEvent: 'onDropdownVisibleChange',
|
||||||
modelField: 'value',
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiSelect,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: TreeSelect,
|
||||||
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'treeData',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
|
@ -48,6 +48,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
@ -77,7 +78,23 @@ async function initComponentAdapter() {
|
|||||||
...attrs,
|
...attrs,
|
||||||
component: ElSelectV2,
|
component: ElSelectV2,
|
||||||
loadingSlot: 'loading',
|
loadingSlot: 'loading',
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiSelect,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: ElTreeSelect,
|
||||||
|
props: { label: 'label', children: 'children' },
|
||||||
|
nodeKey: 'value',
|
||||||
|
loadingSlot: 'loading',
|
||||||
|
optionsPropName: 'data',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import { Page } from '@vben/common-ui';
|
|||||||
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
|
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
@ -21,6 +22,44 @@ const [Form, formApi] = useVbenForm({
|
|||||||
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
|
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
},
|
},
|
||||||
schema: [
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'ApiSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口转options格式
|
||||||
|
afterFetch: (data: { name: string; path: string }[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'api',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
placeholder: '请选择',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
fieldName: 'string',
|
fieldName: 'string',
|
||||||
|
@ -45,6 +45,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
@ -74,7 +75,24 @@ async function initComponentAdapter() {
|
|||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
component: NSelect,
|
component: NSelect,
|
||||||
modelField: 'value',
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiSelect,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: NTreeSelect,
|
||||||
|
nodeKey: 'value',
|
||||||
|
loadingSlot: 'arrow',
|
||||||
|
keyField: 'value',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'options',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import { Page } from '@vben/common-ui';
|
|||||||
import { NButton, NCard, useMessage } from 'naive-ui';
|
import { NButton, NCard, useMessage } from 'naive-ui';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
@ -20,6 +21,44 @@ const [Form, formApi] = useVbenForm({
|
|||||||
message.success(`表单数据:${JSON.stringify(values)}`);
|
message.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
},
|
},
|
||||||
schema: [
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'ApiSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口转options格式
|
||||||
|
afterFetch: (data: { name: string; path: string }[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'api',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
placeholder: '请选择',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
fieldName: 'string',
|
fieldName: 'string',
|
||||||
|
@ -10,30 +10,47 @@ import { objectOmit } from '@vueuse/core';
|
|||||||
|
|
||||||
type OptionsItem = {
|
type OptionsItem = {
|
||||||
[name: string]: any;
|
[name: string]: any;
|
||||||
|
children?: OptionsItem[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// 组件
|
/** 组件 */
|
||||||
component: VNode;
|
component: VNode;
|
||||||
|
/** 是否将value从数字转为string */
|
||||||
numberToString?: boolean;
|
numberToString?: boolean;
|
||||||
|
/** 获取options数据的函数 */
|
||||||
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
|
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
|
||||||
|
/** 传递给api的参数 */
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
|
/** 从api返回的结果中提取options数组的字段名 */
|
||||||
resultField?: string;
|
resultField?: string;
|
||||||
|
/** label字段名 */
|
||||||
labelField?: string;
|
labelField?: string;
|
||||||
|
/** children字段名,需要层级数据的组件可用 */
|
||||||
|
childrenField?: string;
|
||||||
|
/** value字段名 */
|
||||||
valueField?: string;
|
valueField?: string;
|
||||||
|
/** 组件接收options数据的属性名 */
|
||||||
|
optionsPropName?: string;
|
||||||
|
/** 是否立即调用api */
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
|
/** 每次`visibleEvent`事件发生时都重新请求数据 */
|
||||||
alwaysLoad?: boolean;
|
alwaysLoad?: boolean;
|
||||||
|
/** 在api请求之前的回调函数 */
|
||||||
beforeFetch?: AnyPromiseFunction<any, any>;
|
beforeFetch?: AnyPromiseFunction<any, any>;
|
||||||
|
/** 在api请求之后的回调函数 */
|
||||||
afterFetch?: AnyPromiseFunction<any, any>;
|
afterFetch?: AnyPromiseFunction<any, any>;
|
||||||
|
/** 直接传入选项数据,也作为api返回空数据时的后备数据 */
|
||||||
options?: OptionsItem[];
|
options?: OptionsItem[];
|
||||||
// 尾部插槽
|
/** 组件的插槽名称,用来显示一个"加载中"的图标 */
|
||||||
loadingSlot?: string;
|
loadingSlot?: string;
|
||||||
// 可见时触发的事件名
|
/** 触发api请求的事件名 */
|
||||||
visibleEvent?: string;
|
visibleEvent?: string;
|
||||||
modelField?: string;
|
/** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
|
||||||
|
modelPropName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
|
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
|
||||||
@ -41,6 +58,8 @@ defineOptions({ name: 'ApiSelect', inheritAttrs: false });
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
labelField: 'label',
|
labelField: 'label',
|
||||||
valueField: 'value',
|
valueField: 'value',
|
||||||
|
childrenField: '',
|
||||||
|
optionsPropName: 'options',
|
||||||
resultField: '',
|
resultField: '',
|
||||||
visibleEvent: '',
|
visibleEvent: '',
|
||||||
numberToString: false,
|
numberToString: false,
|
||||||
@ -50,7 +69,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
loadingSlot: '',
|
loadingSlot: '',
|
||||||
beforeFetch: undefined,
|
beforeFetch: undefined,
|
||||||
afterFetch: undefined,
|
afterFetch: undefined,
|
||||||
modelField: 'modelValue',
|
modelPropName: 'modelValue',
|
||||||
api: undefined,
|
api: undefined,
|
||||||
options: () => [],
|
options: () => [],
|
||||||
});
|
});
|
||||||
@ -69,29 +88,34 @@ const loading = ref(false);
|
|||||||
const isFirstLoaded = ref(false);
|
const isFirstLoaded = ref(false);
|
||||||
|
|
||||||
const getOptions = computed(() => {
|
const getOptions = computed(() => {
|
||||||
const { labelField, valueField, numberToString } = props;
|
const { labelField, valueField, childrenField, numberToString } = props;
|
||||||
|
|
||||||
const data: OptionsItem[] = [];
|
|
||||||
const refOptionsData = unref(refOptions);
|
const refOptionsData = unref(refOptions);
|
||||||
|
|
||||||
for (const next of refOptionsData) {
|
function transformData(data: OptionsItem[]): OptionsItem[] {
|
||||||
if (next) {
|
return data.map((item) => {
|
||||||
const value = get(next, valueField);
|
const value = get(item, valueField);
|
||||||
data.push({
|
return {
|
||||||
...objectOmit(next, [labelField, valueField]),
|
...objectOmit(item, [labelField, valueField, childrenField]),
|
||||||
label: get(next, labelField),
|
label: get(item, labelField),
|
||||||
value: numberToString ? `${value}` : value,
|
value: numberToString ? `${value}` : value,
|
||||||
});
|
...(childrenField && item[childrenField]
|
||||||
}
|
? { children: transformData(item[childrenField]) }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data: OptionsItem[] = transformData(refOptionsData);
|
||||||
|
|
||||||
return data.length > 0 ? data : props.options;
|
return data.length > 0 ? data : props.options;
|
||||||
});
|
});
|
||||||
|
|
||||||
const bindProps = computed(() => {
|
const bindProps = computed(() => {
|
||||||
return {
|
return {
|
||||||
[props.modelField]: unref(modelValue),
|
[props.modelPropName]: unref(modelValue),
|
||||||
[`onUpdate:${props.modelField}`]: (val: string) => {
|
[props.optionsPropName]: unref(getOptions),
|
||||||
|
[`onUpdate:${props.modelPropName}`]: (val: string) => {
|
||||||
modelValue.value = val;
|
modelValue.value = val;
|
||||||
},
|
},
|
||||||
...objectOmit(attrs, ['onUpdate:value']),
|
...objectOmit(attrs, ['onUpdate:value']),
|
||||||
@ -168,7 +192,6 @@ function emitChange() {
|
|||||||
<component
|
<component
|
||||||
:is="component"
|
:is="component"
|
||||||
v-bind="bindProps"
|
v-bind="bindProps"
|
||||||
:options="getOptions"
|
|
||||||
:placeholder="$attrs.placeholder"
|
:placeholder="$attrs.placeholder"
|
||||||
>
|
>
|
||||||
<template v-for="item in Object.keys($slots)" #[item]="data">
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
@ -49,6 +49,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'AutoComplete'
|
| 'AutoComplete'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
@ -88,7 +89,23 @@ async function initComponentAdapter() {
|
|||||||
...attrs,
|
...attrs,
|
||||||
component: Select,
|
component: Select,
|
||||||
loadingSlot: 'suffixIcon',
|
loadingSlot: 'suffixIcon',
|
||||||
modelField: 'value',
|
modelPropName: 'value',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiSelect,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: TreeSelect,
|
||||||
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'treeData',
|
||||||
visibleEvent: 'onVisibleChange',
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
|
@ -62,6 +62,23 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
// 界面显示的label
|
// 界面显示的label
|
||||||
label: 'ApiSelect',
|
label: 'ApiSelect',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
placeholder: '请选择',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'InputPassword',
|
component: 'InputPassword',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
Loading…
Reference in New Issue
Block a user