CustomApiSelect.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <template>
  2. <Select
  3. @dropdownVisibleChange="handleFetch"
  4. v-bind="attrs"
  5. :options="getOptions"
  6. v-model:value="state"
  7. :disabled="disabled"
  8. >
  9. <template #[item]="data" v-for="item in Object.keys($slots)">
  10. <slot :name="item" v-bind="data"></slot>
  11. </template>
  12. <template #suffixIcon v-if="loading">
  13. <LoadingOutlined spin />
  14. </template>
  15. <template #notFoundContent v-if="loading">
  16. <span>
  17. <LoadingOutlined spin class="mr-1" />
  18. {{ t('component.form.apiSelectNotFound') }}
  19. </span>
  20. </template>
  21. </Select>
  22. </template>
  23. <script lang="ts">
  24. import {
  25. defineComponent,
  26. PropType,
  27. ref,
  28. watchEffect,
  29. computed,
  30. unref,
  31. watch,
  32. reactive,
  33. toRefs,
  34. onUpdated,
  35. } from 'vue';
  36. import { Select } from 'ant-design-vue';
  37. import { isFunction } from '/@/utils/is';
  38. import { useRuleFormItem } from '/@/hooks/component/useFormItem';
  39. import { useAttrs } from '/@/hooks/core/useAttrs';
  40. import { get, omit } from 'lodash-es';
  41. import { LoadingOutlined } from '@ant-design/icons-vue';
  42. import { useI18n } from '/@/hooks/web/useI18n';
  43. import { propTypes } from '/@/utils/propTypes';
  44. type OptionsItem = { label: string; value: string; disabled?: boolean };
  45. export default defineComponent({
  46. name: 'CustomApiSelect',
  47. components: {
  48. Select,
  49. LoadingOutlined,
  50. },
  51. inheritAttrs: false,
  52. props: {
  53. value: propTypes.oneOfType([
  54. propTypes.object,
  55. propTypes.number,
  56. propTypes.string,
  57. propTypes.array,
  58. ]),
  59. hideid: Number,
  60. numberToString: propTypes.bool,
  61. api: {
  62. type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
  63. default: null,
  64. },
  65. // api params
  66. params: {
  67. type: Object as PropType<Recordable>,
  68. default: () => ({}),
  69. },
  70. // support xxx.xxx.xx
  71. resultField: propTypes.string.def(''),
  72. labelField: propTypes.string.def('label'),
  73. valueField: propTypes.string.def('value'),
  74. immediate: propTypes.bool.def(true),
  75. },
  76. emits: ['options-change', 'change'],
  77. setup(props, { emit }) {
  78. const options = ref<OptionsItem[]>([]);
  79. const loading = ref(false);
  80. const isFirstLoad = ref(true);
  81. const attrs = useAttrs();
  82. const { t } = useI18n();
  83. const reactData = reactive({
  84. disabled: false,
  85. });
  86. // Embedded in the form, just use the hook binding to perform form verification
  87. const [state] = useRuleFormItem(props);
  88. const getOptions = computed(() => {
  89. const { labelField, valueField, numberToString } = props;
  90. return unref(options).reduce((prev, next: Recordable) => {
  91. if (next) {
  92. const value = next[valueField];
  93. prev.push({
  94. label: next[labelField],
  95. value: numberToString ? `${value}` : value,
  96. ...omit(next, [labelField, valueField]),
  97. });
  98. }
  99. return prev;
  100. }, [] as OptionsItem[]);
  101. });
  102. watchEffect(() => {
  103. props.immediate && fetch();
  104. });
  105. watch(
  106. () => props.hideid,
  107. () => {
  108. fetch();
  109. },
  110. { deep: true }
  111. );
  112. onUpdated(() => {
  113. if (typeof props.value === 'string') {
  114. reactData.disabled = true;
  115. } else {
  116. reactData.disabled = false;
  117. }
  118. });
  119. async function fetch() {
  120. const api = props.api;
  121. if (!api || !isFunction(api)) return;
  122. try {
  123. loading.value = true;
  124. const result = await api(props.params);
  125. const res: OptionsItem[] = [];
  126. result.list.map((item) => {
  127. if (item.id !== props.hideid) {
  128. res.push({ label: item.name, value: item.id });
  129. }
  130. });
  131. if (Array.isArray(res)) {
  132. options.value = res;
  133. emitChange();
  134. return;
  135. }
  136. if (props.resultField) {
  137. options.value = get(res, props.resultField) || [];
  138. }
  139. emitChange();
  140. } catch (error) {
  141. console.warn(error);
  142. } finally {
  143. loading.value = false;
  144. }
  145. }
  146. async function handleFetch() {
  147. if (!props.immediate && unref(isFirstLoad)) {
  148. await fetch();
  149. isFirstLoad.value = false;
  150. }
  151. }
  152. function emitChange() {
  153. emit('options-change', unref(options));
  154. }
  155. return { state, attrs, getOptions, loading, t, handleFetch, ...toRefs(reactData) };
  156. },
  157. });
  158. </script>