Commit 89414f173e6c34411c7fbe984932aef1f3670380
1 parent
34781d42
feat(Form): 新增 ApiRadioGroup 组件
Showing
6 changed files
with
182 additions
and
4 deletions
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 './src/hooks/useForm'; |
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('Select', 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
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: '字段联动', | ... | ... |