Browse Source

feat(Form): 新增 ApiRadioGroup 组件

zuihou 3 năm trước cách đây
mục cha
commit
89414f173e

+ 4 - 4
mock/demo/select-demo.ts

@@ -1,11 +1,11 @@
 import { MockMethod } from 'vite-plugin-mock';
 import { resultSuccess } from '../_util';
 
-const demoList = (keyword) => {
+const demoList = (keyword, count = 20) => {
   const result = {
     list: [] as any[],
   };
-  for (let index = 0; index < 20; index++) {
+  for (let index = 0; index < count; index++) {
     result.list.push({
       name: `${keyword ?? ''}选项${index}`,
       id: `${index}`,
@@ -20,9 +20,9 @@ export default [
     timeout: 1000,
     method: 'get',
     response: ({ query }) => {
-      const { keyword } = query;
+      const { keyword, count } = query;
       console.log(keyword);
-      return resultSuccess(demoList(keyword));
+      return resultSuccess(demoList(keyword, count));
     },
   },
 ] as MockMethod[];

+ 1 - 0
src/components/Form/index.ts

@@ -9,5 +9,6 @@ export { useForm } from './src/hooks/useForm';
 export { default as ApiSelect } from './src/components/ApiSelect.vue';
 export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
 export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
+export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
 
 export { BasicForm };

+ 2 - 0
src/components/Form/src/componentMap.ts

@@ -21,6 +21,7 @@ import {
   Divider,
 } from 'ant-design-vue';
 
+import ApiRadioGroup from './components/ApiRadioGroup.vue';
 import RadioButtonGroup from './components/RadioButtonGroup.vue';
 import ApiSelect from './components/ApiSelect.vue';
 import ApiTreeSelect from './components/ApiTreeSelect.vue';
@@ -43,6 +44,7 @@ componentMap.set('Select', Select);
 componentMap.set('ApiSelect', ApiSelect);
 componentMap.set('TreeSelect', TreeSelect);
 componentMap.set('ApiTreeSelect', ApiTreeSelect);
+componentMap.set('ApiRadioGroup', ApiRadioGroup);
 componentMap.set('Switch', Switch);
 componentMap.set('RadioButtonGroup', RadioButtonGroup);
 componentMap.set('RadioGroup', Radio.Group);

+ 130 - 0
src/components/Form/src/components/ApiRadioGroup.vue

@@ -0,0 +1,130 @@
+<!--
+ * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
+-->
+<template>
+  <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
+    <template v-for="item in getOptions" :key="`${item.value}`">
+      <RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
+        {{ item.label }}
+      </RadioButton>
+      <Radio v-else :value="item.value" :disabled="item.disabled">
+        {{ item.label }}
+      </Radio>
+    </template>
+  </RadioGroup>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue';
+  import { Radio } from 'ant-design-vue';
+  import { isFunction } from '/@/utils/is';
+  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
+  import { useAttrs } from '/@/hooks/core/useAttrs';
+  import { propTypes } from '/@/utils/propTypes';
+  import { get, omit } from 'lodash-es';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
+
+  export default defineComponent({
+    name: 'ApiRadioGroup',
+    components: {
+      RadioGroup: Radio.Group,
+      RadioButton: Radio.Button,
+      Radio,
+    },
+    props: {
+      api: {
+        type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>,
+        default: null,
+      },
+      params: {
+        type: [Object, String] as PropType<Recordable | string>,
+        default: () => ({}),
+      },
+      value: {
+        type: [String, Number, Boolean] as PropType<string | number | boolean>,
+      },
+      isBtn: {
+        type: [Boolean] as PropType<boolean>,
+        default: false,
+      },
+      numberToString: propTypes.bool,
+      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 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);
+
+      // Processing options value
+      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,
+              ...omit(next, [labelField, valueField]),
+            });
+          }
+          return prev;
+        }, [] as OptionsItem[]);
+      });
+
+      watchEffect(() => {
+        props.immediate && fetch();
+      });
+
+      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;
+        }
+      }
+
+      function emitChange() {
+        emit('options-change', unref(getOptions));
+      }
+
+      function handleChange(_, ...args) {
+        emitData.value = args;
+      }
+
+      return { state, getOptions, attrs, loading, t, handleChange, props };
+    },
+  });
+</script>

+ 1 - 0
src/components/Form/src/types/index.ts

@@ -92,6 +92,7 @@ export type ComponentType =
   | 'ApiSelect'
   | 'TreeSelect'
   | 'ApiTreeSelect'
+  | 'ApiRadioGroup'
   | 'RadioButtonGroup'
   | 'RadioGroup'
   | 'Checkbox'

+ 44 - 0
src/views/demo/form/index.vue

@@ -410,6 +410,50 @@
         span: 8,
       },
     },
+    {
+      field: 'field34',
+      component: 'ApiRadioGroup',
+      label: '远程Radio',
+      helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
+      required: true,
+      componentProps: {
+        api: optionsListApi,
+        params: {
+          count: 2,
+        },
+        resultField: 'list',
+        // use name as label
+        labelField: 'name',
+        // use id as value
+        valueField: 'id',
+      },
+      defaultValue: '1',
+      colProps: {
+        span: 8,
+      },
+    },
+    {
+      field: 'field35',
+      component: 'ApiRadioGroup',
+      label: '远程Radio',
+      helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
+      required: true,
+      componentProps: {
+        api: optionsListApi,
+        params: {
+          count: 2,
+        },
+        resultField: 'list',
+        // use name as label
+        labelField: 'name',
+        // use id as value
+        valueField: 'id',
+        isBtn: true,
+      },
+      colProps: {
+        span: 8,
+      },
+    },
     {
       field: 'divider-linked',
       component: 'Divider',