Skip to content

Commit

Permalink
feat: add api-select component (#5024)
Browse files Browse the repository at this point in the history
  • Loading branch information
anncwb authored Dec 4, 2024
1 parent db38ef5 commit 9896a67
Show file tree
Hide file tree
Showing 12 changed files with 737 additions and 677 deletions.
18 changes: 16 additions & 2 deletions apps/web-antd/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState, IconPicker } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -48,6 +48,7 @@ const withDefaultPlaceholder = <T extends Component>(

// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
Expand Down Expand Up @@ -78,7 +79,20 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),

ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelField: 'value',
},
slots,
);
},
AutoComplete,
Checkbox,
CheckboxGroup,
Expand Down
18 changes: 16 additions & 2 deletions apps/web-ele/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState, IconPicker } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand All @@ -22,6 +22,7 @@ import {
ElNotification,
ElRadioGroup,
ElSelect,
ElSelectV2,
ElSpace,
ElSwitch,
ElTimePicker,
Expand All @@ -41,6 +42,7 @@ const withDefaultPlaceholder = <T extends Component>(

// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
Expand All @@ -62,7 +64,19 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),

ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: ElSelectV2,
loadingSlot: 'loading',
visibleEvent: 'onDropdownVisibleChange',
},
slots,
);
},
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
// 自定义默认按钮
Expand Down
15 changes: 14 additions & 1 deletion apps/web-naive/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState, IconPicker } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -42,6 +42,7 @@ const withDefaultPlaceholder = <T extends Component>(

// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
Expand All @@ -64,6 +65,18 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),

ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: NSelect,
modelField: 'value',
},
slots,
);
},
Checkbox: NCheckbox,
CheckboxGroup: NCheckboxGroup,
DatePicker: NDatePicker,
Expand Down
4 changes: 4 additions & 0 deletions packages/@core/base/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,16 @@
"dayjs": "catalog:",
"defu": "catalog:",
"lodash.clonedeep": "catalog:",
"lodash.get": "catalog:",
"lodash.isequal": "catalog:",
"nprogress": "catalog:",
"tailwind-merge": "catalog:",
"theme-colors": "catalog:"
},
"devDependencies": {
"@types/lodash.clonedeep": "catalog:",
"@types/lodash.get": "catalog:",
"@types/lodash.isequal": "catalog:",
"@types/nprogress": "catalog:"
}
}
2 changes: 2 additions & 0 deletions packages/@core/base/shared/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export * from './update-css-variables';
export * from './util';
export * from './window';
export { default as cloneDeep } from 'lodash.clonedeep';
export { default as get } from 'lodash.get';
export { default as isEqual } from 'lodash.isequal';
182 changes: 182 additions & 0 deletions packages/effects/common-ui/src/components/api-select/api-select.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<script lang="ts" setup>
import type { AnyPromiseFunction } from '@vben/types';
import { computed, ref, unref, useAttrs, type VNode, watch } from 'vue';
import { LoaderCircle } from '@vben/icons';
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
import { objectOmit } from '@vueuse/core';
type OptionsItem = {
[name: string]: any;
disabled?: boolean;
label?: string;
value?: string;
};
interface Props {
// 组件
component: VNode;
numberToString?: boolean;
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
params?: Record<string, any>;
resultField?: string;
labelField?: string;
valueField?: string;
immediate?: boolean;
alwaysLoad?: boolean;
beforeFetch?: AnyPromiseFunction<any, any>;
afterFetch?: AnyPromiseFunction<any, any>;
options?: OptionsItem[];
// 尾部插槽
loadingSlot?: string;
// 可见时触发的事件名
visibleEvent?: string;
modelField?: string;
}
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
const props = withDefaults(defineProps<Props>(), {
labelField: 'label',
valueField: 'value',
resultField: '',
visibleEvent: '',
numberToString: false,
params: () => ({}),
immediate: true,
alwaysLoad: false,
loadingSlot: '',
beforeFetch: undefined,
afterFetch: undefined,
modelField: 'modelValue',
api: undefined,
options: () => [],
});
const emit = defineEmits<{
optionsChange: [OptionsItem[]];
}>();
const modelValue = defineModel({ default: '' });
const attrs = useAttrs();
const refOptions = ref<OptionsItem[]>([]);
const loading = ref(false);
// 首次是否加载过了
const isFirstLoaded = ref(false);
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
const data: OptionsItem[] = [];
const refOptionsData = unref(refOptions);
for (const next of refOptionsData) {
if (next) {
const value = get(next, valueField);
data.push({
...objectOmit(next, [labelField, valueField]),
label: get(next, labelField),
value: numberToString ? `${value}` : value,
});
}
}
return data.length > 0 ? data : props.options;
});
const bindProps = computed(() => {
return {
[props.modelField]: unref(modelValue),
[`onUpdate:${props.modelField}`]: (val: string) => {
modelValue.value = val;
},
...objectOmit(attrs, ['onUpdate:value']),
...(props.visibleEvent
? {
[props.visibleEvent]: handleFetchForVisible,
}
: {}),
};
});
async function fetchApi() {
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api) || loading.value) {
return;
}
refOptions.value = [];
try {
loading.value = true;
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
isFirstLoaded.value = true;
if (Array.isArray(res)) {
refOptions.value = res;
emitChange();
return;
}
if (resultField) {
refOptions.value = get(res, resultField) || [];
}
emitChange();
} catch (error) {
console.warn(error);
// reset status
isFirstLoaded.value = false;
} finally {
loading.value = false;
}
}
async function handleFetchForVisible(visible: boolean) {
if (visible) {
if (props.alwaysLoad) {
await fetchApi();
} else if (!props.immediate && !unref(isFirstLoaded)) {
await fetchApi();
}
}
}
watch(
() => props.params,
(value, oldValue) => {
if (isEqual(value, oldValue)) {
return;
}
fetchApi();
},
{ deep: true, immediate: props.immediate },
);
function emitChange() {
emit('optionsChange', unref(getOptions));
}
</script>
<template>
<div v-bind="{ ...$attrs }">
<component
:is="component"
v-bind="bindProps"
:options="getOptions"
:placeholder="$attrs.placeholder"
>
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template v-if="loadingSlot && loading" #[loadingSlot]>
<LoaderCircle class="animate-spin" />
</template>
</component>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ApiSelect } from './api-select.vue';
1 change: 1 addition & 0 deletions packages/effects/common-ui/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './api-select';
export * from './captcha';
export * from './ellipsis-text';
export * from './icon-picker';
Expand Down
17 changes: 16 additions & 1 deletion playground/src/adapter/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';

import { globalShareState, IconPicker } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';

import {
Expand Down Expand Up @@ -48,6 +48,7 @@ const withDefaultPlaceholder = <T extends Component>(

// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
Expand Down Expand Up @@ -79,6 +80,20 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),

ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
modelField: 'value',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
AutoComplete,
Checkbox,
CheckboxGroup,
Expand Down
Loading

0 comments on commit 9896a67

Please sign in to comment.