feat(api-select): add immediate option,close #430
New file |
| | |
| | | <template> |
| | | <Select v-bind="attrs" :options="getOptions" v-model:value="state" @focus="handleFetch"> |
| | | <template #[item]="data" v-for="item in Object.keys($slots)"> |
| | | <slot :name="item" v-bind="data"></slot> |
| | | </template> |
| | | <template #suffixIcon v-if="loading"> |
| | | <LoadingOutlined spin /> |
| | | </template> |
| | | <template #notFoundContent v-if="loading"> |
| | | <span> |
| | | <LoadingOutlined spin class="mr-1" /> |
| | | {{ t('component.form.apiSelectNotFound') }} |
| | | </span> |
| | | </template> |
| | | </Select> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue'; |
| | | import { Select } from 'ant-design-vue'; |
| | | import { isFunction } from '/@/utils/is'; |
| | | import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
| | | import { useAttrs } from '/@/hooks/core/useAttrs'; |
| | | import { get } from 'lodash-es'; |
| | | |
| | | import { LoadingOutlined } from '@ant-design/icons-vue'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { propTypes } from '/@/utils/propTypes'; |
| | | |
| | | type OptionsItem = { label: string; value: string; disabled?: boolean }; |
| | | |
| | | export default defineComponent({ |
| | | name: 'ApiSelect', |
| | | components: { |
| | | Select, |
| | | LoadingOutlined, |
| | | }, |
| | | inheritAttrs: false, |
| | | props: { |
| | | value: propTypes.string, |
| | | numberToString: propTypes.bool, |
| | | api: { |
| | | type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>, |
| | | default: null, |
| | | }, |
| | | // api params |
| | | params: { |
| | | type: Object as PropType<Recordable>, |
| | | default: () => {}, |
| | | }, |
| | | // support xxx.xxx.xx |
| | | resultField: propTypes.string.def(''), |
| | | labelField: propTypes.string.def('label'), |
| | | valueField: propTypes.string.def('value'), |
| | | immediate: propTypes.bool.def(true), |
| | | }, |
| | | emits: ['options-change', 'change'], |
| | | setup(props, { emit }) { |
| | | const options = ref<OptionsItem[]>([]); |
| | | const loading = ref(false); |
| | | const isFirstLoad = ref(true); |
| | | const attrs = useAttrs(); |
| | | const { t } = useI18n(); |
| | | |
| | | // Embedded in the form, just use the hook binding to perform form verification |
| | | const [state] = useRuleFormItem(props); |
| | | |
| | | const getOptions = computed(() => { |
| | | const { labelField, valueField, numberToString } = props; |
| | | |
| | | return unref(options).reduce((prev, next: Recordable) => { |
| | | if (next) { |
| | | const value = next[valueField]; |
| | | prev.push({ |
| | | label: next[labelField], |
| | | value: numberToString ? `${value}` : value, |
| | | }); |
| | | } |
| | | return prev; |
| | | }, [] as OptionsItem[]); |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | if (isFirstLoad.value) { |
| | | props.immediate && fetch(); |
| | | } else { |
| | | fetch(); |
| | | } |
| | | }); |
| | | |
| | | async function fetch() { |
| | | const api = props.api; |
| | | if (!api || !isFunction(api)) return; |
| | | |
| | | try { |
| | | loading.value = true; |
| | | const res = await api(props.params); |
| | | if (Array.isArray(res)) { |
| | | options.value = res; |
| | | emitChange(); |
| | | return; |
| | | } |
| | | if (props.resultField) { |
| | | options.value = get(res, props.resultField) || []; |
| | | } |
| | | emitChange(); |
| | | } catch (error) { |
| | | console.warn(error); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | async function handleFetch() { |
| | | if (!props.immediate) { |
| | | await fetch(); |
| | | } |
| | | isFirstLoad.value = false; |
| | | } |
| | | |
| | | function emitChange() { |
| | | emit('options-change', unref(options)); |
| | | } |
| | | |
| | | return { state, attrs, getOptions, loading, t, handleFetch }; |
| | | }, |
| | | }); |
| | | </script> |
| | |
| | | <template> |
| | | <Select v-bind="attrs" :options="getOptions" v-model:value="state"> |
| | | <Select |
| | | @dropdownVisibleChange="handleFetch" |
| | | v-bind="attrs" |
| | | :options="getOptions" |
| | | v-model:value="state" |
| | | > |
| | | <template #[item]="data" v-for="item in Object.keys($slots)"> |
| | | <slot :name="item" v-bind="data"></slot> |
| | | </template> |
| | |
| | | resultField: propTypes.string.def(''), |
| | | labelField: propTypes.string.def('label'), |
| | | valueField: propTypes.string.def('value'), |
| | | immediate: propTypes.bool.def(true), |
| | | }, |
| | | emits: ['options-change', 'change'], |
| | | setup(props, { emit }) { |
| | | const options = ref<OptionsItem[]>([]); |
| | | const loading = ref(false); |
| | | const isFirstLoad = ref(true); |
| | | const attrs = useAttrs(); |
| | | const { t } = useI18n(); |
| | | |
| | |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | fetch(); |
| | | props.immediate && fetch(); |
| | | }); |
| | | |
| | | async function fetch() { |
| | |
| | | const res = await api(props.params); |
| | | if (Array.isArray(res)) { |
| | | options.value = res; |
| | | emit('options-change', unref(options)); |
| | | emitChange(); |
| | | return; |
| | | } |
| | | if (props.resultField) { |
| | | options.value = get(res, props.resultField) || []; |
| | | } |
| | | emit('options-change', unref(options)); |
| | | emitChange(); |
| | | } catch (error) { |
| | | console.warn(error); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | return { state, attrs, getOptions, loading, t }; |
| | | |
| | | async function handleFetch() { |
| | | if (!props.immediate && unref(isFirstLoad)) { |
| | | await fetch(); |
| | | isFirstLoad.value = false; |
| | | } |
| | | } |
| | | |
| | | function emitChange() { |
| | | emit('options-change', unref(options)); |
| | | } |
| | | |
| | | return { state, attrs, getOptions, loading, t, handleFetch }; |
| | | }, |
| | | }); |
| | | </script> |