Commit 89414f173e6c34411c7fbe984932aef1f3670380

Authored by zuihou
1 parent 34781d42

feat(Form): 新增 ApiRadioGroup 组件

mock/demo/select-demo.ts
1 1 import { MockMethod } from 'vite-plugin-mock';
2 2 import { resultSuccess } from '../_util';
3 3  
4   -const demoList = (keyword) => {
  4 +const demoList = (keyword, count = 20) => {
5 5 const result = {
6 6 list: [] as any[],
7 7 };
8   - for (let index = 0; index < 20; index++) {
  8 + for (let index = 0; index < count; index++) {
9 9 result.list.push({
10 10 name: `${keyword ?? ''}选项${index}`,
11 11 id: `${index}`,
... ... @@ -20,9 +20,9 @@ export default [
20 20 timeout: 1000,
21 21 method: 'get',
22 22 response: ({ query }) => {
23   - const { keyword } = query;
  23 + const { keyword, count } = query;
24 24 console.log(keyword);
25   - return resultSuccess(demoList(keyword));
  25 + return resultSuccess(demoList(keyword, count));
26 26 },
27 27 },
28 28 ] as MockMethod[];
... ...
src/components/Form/index.ts
... ... @@ -9,5 +9,6 @@ export { useForm } from &#39;./src/hooks/useForm&#39;;
9 9 export { default as ApiSelect } from './src/components/ApiSelect.vue';
10 10 export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
11 11 export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
  12 +export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
12 13  
13 14 export { BasicForm };
... ...
src/components/Form/src/componentMap.ts
... ... @@ -21,6 +21,7 @@ import {
21 21 Divider,
22 22 } from 'ant-design-vue';
23 23  
  24 +import ApiRadioGroup from './components/ApiRadioGroup.vue';
24 25 import RadioButtonGroup from './components/RadioButtonGroup.vue';
25 26 import ApiSelect from './components/ApiSelect.vue';
26 27 import ApiTreeSelect from './components/ApiTreeSelect.vue';
... ... @@ -43,6 +44,7 @@ componentMap.set(&#39;Select&#39;, Select);
43 44 componentMap.set('ApiSelect', ApiSelect);
44 45 componentMap.set('TreeSelect', TreeSelect);
45 46 componentMap.set('ApiTreeSelect', ApiTreeSelect);
  47 +componentMap.set('ApiRadioGroup', ApiRadioGroup);
46 48 componentMap.set('Switch', Switch);
47 49 componentMap.set('RadioButtonGroup', RadioButtonGroup);
48 50 componentMap.set('RadioGroup', Radio.Group);
... ...
src/components/Form/src/components/ApiRadioGroup.vue 0 → 100644
  1 +<!--
  2 + * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
  3 +-->
  4 +<template>
  5 + <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
  6 + <template v-for="item in getOptions" :key="`${item.value}`">
  7 + <RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
  8 + {{ item.label }}
  9 + </RadioButton>
  10 + <Radio v-else :value="item.value" :disabled="item.disabled">
  11 + {{ item.label }}
  12 + </Radio>
  13 + </template>
  14 + </RadioGroup>
  15 +</template>
  16 +<script lang="ts">
  17 + import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue';
  18 + import { Radio } from 'ant-design-vue';
  19 + import { isFunction } from '/@/utils/is';
  20 + import { useRuleFormItem } from '/@/hooks/component/useFormItem';
  21 + import { useAttrs } from '/@/hooks/core/useAttrs';
  22 + import { propTypes } from '/@/utils/propTypes';
  23 + import { get, omit } from 'lodash-es';
  24 + import { useI18n } from '/@/hooks/web/useI18n';
  25 + type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
  26 +
  27 + export default defineComponent({
  28 + name: 'ApiRadioGroup',
  29 + components: {
  30 + RadioGroup: Radio.Group,
  31 + RadioButton: Radio.Button,
  32 + Radio,
  33 + },
  34 + props: {
  35 + api: {
  36 + type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>,
  37 + default: null,
  38 + },
  39 + params: {
  40 + type: [Object, String] as PropType<Recordable | string>,
  41 + default: () => ({}),
  42 + },
  43 + value: {
  44 + type: [String, Number, Boolean] as PropType<string | number | boolean>,
  45 + },
  46 + isBtn: {
  47 + type: [Boolean] as PropType<boolean>,
  48 + default: false,
  49 + },
  50 + numberToString: propTypes.bool,
  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 emitData = ref<any[]>([]);
  62 + const attrs = useAttrs();
  63 + const { t } = useI18n();
  64 + // Embedded in the form, just use the hook binding to perform form verification
  65 + const [state] = useRuleFormItem(props);
  66 +
  67 + // Processing options value
  68 + const getOptions = computed(() => {
  69 + const { labelField, valueField, numberToString } = props;
  70 +
  71 + return unref(options).reduce((prev, next: Recordable) => {
  72 + if (next) {
  73 + const value = next[valueField];
  74 + prev.push({
  75 + label: next[labelField],
  76 + value: numberToString ? `${value}` : value,
  77 + ...omit(next, [labelField, valueField]),
  78 + });
  79 + }
  80 + return prev;
  81 + }, [] as OptionsItem[]);
  82 + });
  83 +
  84 + watchEffect(() => {
  85 + props.immediate && fetch();
  86 + });
  87 +
  88 + watch(
  89 + () => props.params,
  90 + () => {
  91 + !unref(isFirstLoad) && fetch();
  92 + },
  93 + { deep: true },
  94 + );
  95 +
  96 + async function fetch() {
  97 + const api = props.api;
  98 + if (!api || !isFunction(api)) return;
  99 + options.value = [];
  100 + try {
  101 + loading.value = true;
  102 + const res = await api(props.params);
  103 + if (Array.isArray(res)) {
  104 + options.value = res;
  105 + emitChange();
  106 + return;
  107 + }
  108 + if (props.resultField) {
  109 + options.value = get(res, props.resultField) || [];
  110 + }
  111 + emitChange();
  112 + } catch (error) {
  113 + console.warn(error);
  114 + } finally {
  115 + loading.value = false;
  116 + }
  117 + }
  118 +
  119 + function emitChange() {
  120 + emit('options-change', unref(getOptions));
  121 + }
  122 +
  123 + function handleChange(_, ...args) {
  124 + emitData.value = args;
  125 + }
  126 +
  127 + return { state, getOptions, attrs, loading, t, handleChange, props };
  128 + },
  129 + });
  130 +</script>
... ...
src/components/Form/src/types/index.ts
... ... @@ -92,6 +92,7 @@ export type ComponentType =
92 92 | 'ApiSelect'
93 93 | 'TreeSelect'
94 94 | 'ApiTreeSelect'
  95 + | 'ApiRadioGroup'
95 96 | 'RadioButtonGroup'
96 97 | 'RadioGroup'
97 98 | 'Checkbox'
... ...
src/views/demo/form/index.vue
... ... @@ -411,6 +411,50 @@
411 411 },
412 412 },
413 413 {
  414 + field: 'field34',
  415 + component: 'ApiRadioGroup',
  416 + label: '远程Radio',
  417 + helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
  418 + required: true,
  419 + componentProps: {
  420 + api: optionsListApi,
  421 + params: {
  422 + count: 2,
  423 + },
  424 + resultField: 'list',
  425 + // use name as label
  426 + labelField: 'name',
  427 + // use id as value
  428 + valueField: 'id',
  429 + },
  430 + defaultValue: '1',
  431 + colProps: {
  432 + span: 8,
  433 + },
  434 + },
  435 + {
  436 + field: 'field35',
  437 + component: 'ApiRadioGroup',
  438 + label: '远程Radio',
  439 + helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
  440 + required: true,
  441 + componentProps: {
  442 + api: optionsListApi,
  443 + params: {
  444 + count: 2,
  445 + },
  446 + resultField: 'list',
  447 + // use name as label
  448 + labelField: 'name',
  449 + // use id as value
  450 + valueField: 'id',
  451 + isBtn: true,
  452 + },
  453 + colProps: {
  454 + span: 8,
  455 + },
  456 + },
  457 + {
414 458 field: 'divider-linked',
415 459 component: 'Divider',
416 460 label: '字段联动',
... ...