Commit 5b4a41ced412fe3623618791ffa3123a3a2cfcdc
1 parent
e090689e
feat(api-select): add immediate option,close #430
Showing
2 changed files
with
151 additions
and
5 deletions
src/components/Form/src/components/ApiSelect copy.vue
0 → 100644
1 | +<template> | ||
2 | + <Select v-bind="attrs" :options="getOptions" v-model:value="state" @focus="handleFetch"> | ||
3 | + <template #[item]="data" v-for="item in Object.keys($slots)"> | ||
4 | + <slot :name="item" v-bind="data"></slot> | ||
5 | + </template> | ||
6 | + <template #suffixIcon v-if="loading"> | ||
7 | + <LoadingOutlined spin /> | ||
8 | + </template> | ||
9 | + <template #notFoundContent v-if="loading"> | ||
10 | + <span> | ||
11 | + <LoadingOutlined spin class="mr-1" /> | ||
12 | + {{ t('component.form.apiSelectNotFound') }} | ||
13 | + </span> | ||
14 | + </template> | ||
15 | + </Select> | ||
16 | +</template> | ||
17 | +<script lang="ts"> | ||
18 | + import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue'; | ||
19 | + import { Select } from 'ant-design-vue'; | ||
20 | + import { isFunction } from '/@/utils/is'; | ||
21 | + import { useRuleFormItem } from '/@/hooks/component/useFormItem'; | ||
22 | + import { useAttrs } from '/@/hooks/core/useAttrs'; | ||
23 | + import { get } from 'lodash-es'; | ||
24 | + | ||
25 | + import { LoadingOutlined } from '@ant-design/icons-vue'; | ||
26 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
27 | + import { propTypes } from '/@/utils/propTypes'; | ||
28 | + | ||
29 | + type OptionsItem = { label: string; value: string; disabled?: boolean }; | ||
30 | + | ||
31 | + export default defineComponent({ | ||
32 | + name: 'ApiSelect', | ||
33 | + components: { | ||
34 | + Select, | ||
35 | + LoadingOutlined, | ||
36 | + }, | ||
37 | + inheritAttrs: false, | ||
38 | + props: { | ||
39 | + value: propTypes.string, | ||
40 | + numberToString: propTypes.bool, | ||
41 | + api: { | ||
42 | + type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>, | ||
43 | + default: null, | ||
44 | + }, | ||
45 | + // api params | ||
46 | + params: { | ||
47 | + type: Object as PropType<Recordable>, | ||
48 | + default: () => {}, | ||
49 | + }, | ||
50 | + // support xxx.xxx.xx | ||
51 | + resultField: propTypes.string.def(''), | ||
52 | + labelField: propTypes.string.def('label'), | ||
53 | + valueField: propTypes.string.def('value'), | ||
54 | + immediate: propTypes.bool.def(true), | ||
55 | + }, | ||
56 | + emits: ['options-change', 'change'], | ||
57 | + setup(props, { emit }) { | ||
58 | + const options = ref<OptionsItem[]>([]); | ||
59 | + const loading = ref(false); | ||
60 | + const isFirstLoad = ref(true); | ||
61 | + const attrs = useAttrs(); | ||
62 | + const { t } = useI18n(); | ||
63 | + | ||
64 | + // Embedded in the form, just use the hook binding to perform form verification | ||
65 | + const [state] = useRuleFormItem(props); | ||
66 | + | ||
67 | + const getOptions = computed(() => { | ||
68 | + const { labelField, valueField, numberToString } = props; | ||
69 | + | ||
70 | + return unref(options).reduce((prev, next: Recordable) => { | ||
71 | + if (next) { | ||
72 | + const value = next[valueField]; | ||
73 | + prev.push({ | ||
74 | + label: next[labelField], | ||
75 | + value: numberToString ? `${value}` : value, | ||
76 | + }); | ||
77 | + } | ||
78 | + return prev; | ||
79 | + }, [] as OptionsItem[]); | ||
80 | + }); | ||
81 | + | ||
82 | + watchEffect(() => { | ||
83 | + if (isFirstLoad.value) { | ||
84 | + props.immediate && fetch(); | ||
85 | + } else { | ||
86 | + fetch(); | ||
87 | + } | ||
88 | + }); | ||
89 | + | ||
90 | + async function fetch() { | ||
91 | + const api = props.api; | ||
92 | + if (!api || !isFunction(api)) return; | ||
93 | + | ||
94 | + try { | ||
95 | + loading.value = true; | ||
96 | + const res = await api(props.params); | ||
97 | + if (Array.isArray(res)) { | ||
98 | + options.value = res; | ||
99 | + emitChange(); | ||
100 | + return; | ||
101 | + } | ||
102 | + if (props.resultField) { | ||
103 | + options.value = get(res, props.resultField) || []; | ||
104 | + } | ||
105 | + emitChange(); | ||
106 | + } catch (error) { | ||
107 | + console.warn(error); | ||
108 | + } finally { | ||
109 | + loading.value = false; | ||
110 | + } | ||
111 | + } | ||
112 | + | ||
113 | + async function handleFetch() { | ||
114 | + if (!props.immediate) { | ||
115 | + await fetch(); | ||
116 | + } | ||
117 | + isFirstLoad.value = false; | ||
118 | + } | ||
119 | + | ||
120 | + function emitChange() { | ||
121 | + emit('options-change', unref(options)); | ||
122 | + } | ||
123 | + | ||
124 | + return { state, attrs, getOptions, loading, t, handleFetch }; | ||
125 | + }, | ||
126 | + }); | ||
127 | +</script> |
src/components/Form/src/components/ApiSelect.vue
1 | <template> | 1 | <template> |
2 | - <Select v-bind="attrs" :options="getOptions" v-model:value="state"> | 2 | + <Select |
3 | + @dropdownVisibleChange="handleFetch" | ||
4 | + v-bind="attrs" | ||
5 | + :options="getOptions" | ||
6 | + v-model:value="state" | ||
7 | + > | ||
3 | <template #[item]="data" v-for="item in Object.keys($slots)"> | 8 | <template #[item]="data" v-for="item in Object.keys($slots)"> |
4 | <slot :name="item" v-bind="data"></slot> | 9 | <slot :name="item" v-bind="data"></slot> |
5 | </template> | 10 | </template> |
@@ -51,11 +56,13 @@ | @@ -51,11 +56,13 @@ | ||
51 | resultField: propTypes.string.def(''), | 56 | resultField: propTypes.string.def(''), |
52 | labelField: propTypes.string.def('label'), | 57 | labelField: propTypes.string.def('label'), |
53 | valueField: propTypes.string.def('value'), | 58 | valueField: propTypes.string.def('value'), |
59 | + immediate: propTypes.bool.def(true), | ||
54 | }, | 60 | }, |
55 | emits: ['options-change', 'change'], | 61 | emits: ['options-change', 'change'], |
56 | setup(props, { emit }) { | 62 | setup(props, { emit }) { |
57 | const options = ref<OptionsItem[]>([]); | 63 | const options = ref<OptionsItem[]>([]); |
58 | const loading = ref(false); | 64 | const loading = ref(false); |
65 | + const isFirstLoad = ref(true); | ||
59 | const attrs = useAttrs(); | 66 | const attrs = useAttrs(); |
60 | const { t } = useI18n(); | 67 | const { t } = useI18n(); |
61 | 68 | ||
@@ -78,7 +85,7 @@ | @@ -78,7 +85,7 @@ | ||
78 | }); | 85 | }); |
79 | 86 | ||
80 | watchEffect(() => { | 87 | watchEffect(() => { |
81 | - fetch(); | 88 | + props.immediate && fetch(); |
82 | }); | 89 | }); |
83 | 90 | ||
84 | async function fetch() { | 91 | async function fetch() { |
@@ -90,20 +97,32 @@ | @@ -90,20 +97,32 @@ | ||
90 | const res = await api(props.params); | 97 | const res = await api(props.params); |
91 | if (Array.isArray(res)) { | 98 | if (Array.isArray(res)) { |
92 | options.value = res; | 99 | options.value = res; |
93 | - emit('options-change', unref(options)); | 100 | + emitChange(); |
94 | return; | 101 | return; |
95 | } | 102 | } |
96 | if (props.resultField) { | 103 | if (props.resultField) { |
97 | options.value = get(res, props.resultField) || []; | 104 | options.value = get(res, props.resultField) || []; |
98 | } | 105 | } |
99 | - emit('options-change', unref(options)); | 106 | + emitChange(); |
100 | } catch (error) { | 107 | } catch (error) { |
101 | console.warn(error); | 108 | console.warn(error); |
102 | } finally { | 109 | } finally { |
103 | loading.value = false; | 110 | loading.value = false; |
104 | } | 111 | } |
105 | } | 112 | } |
106 | - return { state, attrs, getOptions, loading, t }; | 113 | + |
114 | + async function handleFetch() { | ||
115 | + if (!props.immediate && unref(isFirstLoad)) { | ||
116 | + await fetch(); | ||
117 | + isFirstLoad.value = false; | ||
118 | + } | ||
119 | + } | ||
120 | + | ||
121 | + function emitChange() { | ||
122 | + emit('options-change', unref(options)); | ||
123 | + } | ||
124 | + | ||
125 | + return { state, attrs, getOptions, loading, t, handleFetch }; | ||
107 | }, | 126 | }, |
108 | }); | 127 | }); |
109 | </script> | 128 | </script> |