| | |
| | | </template> |
| | | </Select> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue'; |
| | | <script lang="ts" setup> |
| | | import { PropType, ref, computed, unref, watch } from 'vue'; |
| | | import { Select } from 'ant-design-vue'; |
| | | import { isFunction } from '/@/utils/is'; |
| | | import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
| | | import { useAttrs } from '@vben/hooks'; |
| | | import { get, omit } from 'lodash-es'; |
| | | import type { SelectValue } from 'ant-design-vue/es/select'; |
| | | import { isFunction } from '@/utils/is'; |
| | | import { useRuleFormItem } from '@/hooks/component/useFormItem'; |
| | | import { get, omit, isEqual } from 'lodash-es'; |
| | | import { LoadingOutlined } from '@ant-design/icons-vue'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { propTypes } from '/@/utils/propTypes'; |
| | | import { useI18n } from '@/hooks/web/useI18n'; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | |
| | | type OptionsItem = { label: string; value: string; disabled?: boolean }; |
| | | type OptionsItem = { label?: string; value?: string; disabled?: boolean; [name: string]: any }; |
| | | |
| | | export default defineComponent({ |
| | | name: 'ApiSelect', |
| | | components: { |
| | | Select, |
| | | LoadingOutlined, |
| | | defineOptions({ name: 'ApiSelect', inheritAttrs: false }); |
| | | |
| | | const props = defineProps({ |
| | | value: { type: [Array, Object, String, Number] as PropType<SelectValue> }, |
| | | numberToString: propTypes.bool, |
| | | api: { |
| | | type: Function as PropType<(arg?: any) => Promise<OptionsItem[] | Recordable<any>>>, |
| | | default: null, |
| | | }, |
| | | inheritAttrs: false, |
| | | props: { |
| | | value: [Array, Object, String, Number], |
| | | numberToString: propTypes.bool, |
| | | api: { |
| | | type: Function as PropType<(arg?: any) => Promise<OptionsItem[]>>, |
| | | default: null, |
| | | }, |
| | | // api params |
| | | params: propTypes.any.def({}), |
| | | // support xxx.xxx.xx |
| | | resultField: propTypes.string.def(''), |
| | | labelField: propTypes.string.def('label'), |
| | | valueField: propTypes.string.def('value'), |
| | | immediate: propTypes.bool.def(true), |
| | | alwaysLoad: propTypes.bool.def(false), |
| | | // api params |
| | | params: propTypes.any.def({}), |
| | | // support xxx.xxx.xx |
| | | resultField: propTypes.string.def(''), |
| | | labelField: propTypes.string.def('label'), |
| | | valueField: propTypes.string.def('value'), |
| | | immediate: propTypes.bool.def(true), |
| | | alwaysLoad: propTypes.bool.def(false), |
| | | options: { |
| | | type: Array<OptionsItem>, |
| | | default: [], |
| | | }, |
| | | emits: ['options-change', 'change', 'update:value'], |
| | | setup(props, { emit }) { |
| | | const options = ref<OptionsItem[]>([]); |
| | | const loading = ref(false); |
| | | const isFirstLoad = ref(true); |
| | | const emitData = ref<any[]>([]); |
| | | const attrs = useAttrs(); |
| | | const { t } = useI18n(); |
| | | |
| | | // Embedded in the form, just use the hook binding to perform form verification |
| | | const [state] = useRuleFormItem(props, 'value', 'change', emitData); |
| | | |
| | | const getOptions = computed(() => { |
| | | const { labelField, valueField, numberToString } = props; |
| | | |
| | | return unref(options).reduce((prev, next: any) => { |
| | | if (next) { |
| | | const value = get(next, valueField); |
| | | prev.push({ |
| | | ...omit(next, [labelField, valueField]), |
| | | label: get(next, labelField), |
| | | value: numberToString ? `${value}` : value, |
| | | }); |
| | | } |
| | | return prev; |
| | | }, [] as OptionsItem[]); |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | props.immediate && !props.alwaysLoad && fetch(); |
| | | }); |
| | | |
| | | watch( |
| | | () => state.value, |
| | | (v) => { |
| | | emit('update:value', v); |
| | | }, |
| | | ); |
| | | |
| | | watch( |
| | | () => props.params, |
| | | () => { |
| | | !unref(isFirstLoad) && fetch(); |
| | | }, |
| | | { deep: true }, |
| | | ); |
| | | |
| | | async function fetch() { |
| | | const api = props.api; |
| | | if (!api || !isFunction(api)) return; |
| | | options.value = []; |
| | | 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(visible) { |
| | | if (visible) { |
| | | if (props.alwaysLoad) { |
| | | await fetch(); |
| | | } else if (!props.immediate && unref(isFirstLoad)) { |
| | | await fetch(); |
| | | isFirstLoad.value = false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | function emitChange() { |
| | | emit('options-change', unref(getOptions)); |
| | | } |
| | | |
| | | function handleChange(_, ...args) { |
| | | emitData.value = args; |
| | | } |
| | | |
| | | return { state, attrs, getOptions, loading, t, handleFetch, handleChange }; |
| | | beforeFetch: { |
| | | type: Function as PropType<Fn>, |
| | | default: null, |
| | | }, |
| | | afterFetch: { |
| | | type: Function as PropType<Fn>, |
| | | default: null, |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(['options-change', 'change', 'update:value']); |
| | | |
| | | const optionsRef = ref<OptionsItem[]>([]); |
| | | |
| | | const loading = ref(false); |
| | | // 首次是否加载过了 |
| | | const isFirstLoaded = ref(false); |
| | | const emitData = ref<OptionsItem[]>([]); |
| | | const { t } = useI18n(); |
| | | |
| | | // Embedded in the form, just use the hook binding to perform form verification |
| | | const [state] = useRuleFormItem(props, 'value', 'change', emitData); |
| | | |
| | | const getOptions = computed(() => { |
| | | const { labelField, valueField, numberToString } = props; |
| | | |
| | | let data = unref(optionsRef).reduce((prev, next: any) => { |
| | | if (next) { |
| | | const value = get(next, valueField); |
| | | prev.push({ |
| | | ...omit(next, [labelField, valueField]), |
| | | label: get(next, labelField), |
| | | value: numberToString ? `${value}` : value, |
| | | }); |
| | | } |
| | | return prev; |
| | | }, [] as OptionsItem[]); |
| | | return data.length > 0 ? data : props.options; |
| | | }); |
| | | |
| | | watch( |
| | | () => state.value, |
| | | (v) => { |
| | | emit('update:value', v); |
| | | }, |
| | | ); |
| | | |
| | | watch( |
| | | () => props.params, |
| | | (value, oldValue) => { |
| | | if (isEqual(value, oldValue)) return; |
| | | fetch(); |
| | | }, |
| | | { deep: true, immediate: props.immediate }, |
| | | ); |
| | | |
| | | async function fetch() { |
| | | let { api, beforeFetch, afterFetch, params, resultField } = props; |
| | | if (!api || !isFunction(api) || loading.value) return; |
| | | optionsRef.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)) { |
| | | optionsRef.value = res; |
| | | emitChange(); |
| | | return; |
| | | } |
| | | if (resultField) { |
| | | optionsRef.value = get(res, resultField) || []; |
| | | } |
| | | emitChange(); |
| | | } catch (error) { |
| | | console.warn(error); |
| | | // reset status |
| | | isFirstLoaded.value = false; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | async function handleFetch(visible: boolean) { |
| | | if (visible) { |
| | | if (props.alwaysLoad) { |
| | | await fetch(); |
| | | } else if (!props.immediate && !unref(isFirstLoaded)) { |
| | | await fetch(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | function emitChange() { |
| | | emit('options-change', unref(getOptions)); |
| | | } |
| | | |
| | | function handleChange(_, ...args) { |
| | | emitData.value = args; |
| | | } |
| | | </script> |