ソースを参照

feat(api-select): add immediate option,close #430

Vben 4 年 前
コミット
5b4a41ced4

+ 127 - 0
src/components/Form/src/components/ApiSelect copy.vue

@@ -0,0 +1,127 @@
+<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>

+ 24 - 5
src/components/Form/src/components/ApiSelect.vue

@@ -1,5 +1,10 @@
 <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>
@@ -51,11 +56,13 @@
       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();
 
@@ -78,7 +85,7 @@
       });
 
       watchEffect(() => {
-        fetch();
+        props.immediate && fetch();
       });
 
       async function fetch() {
@@ -90,20 +97,32 @@
           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>