Commit 97fe8e2058147a331442340b209eee596f6033db
Committed by
GitHub
1 parent
5c491a42
feat(ApiCascader): add asynchronous cascader component (#1321)
Showing
8 changed files
with
622 additions
and
1 deletions
mock/demo/api-cascader.ts
0 → 100644
1 | +import { MockMethod } from 'vite-plugin-mock'; | ||
2 | +import { resultSuccess } from '../_util'; | ||
3 | + | ||
4 | +const areaList: any[] = [ | ||
5 | + { | ||
6 | + id: '530825900854620160', | ||
7 | + code: '430000', | ||
8 | + parentCode: '100000', | ||
9 | + levelType: 1, | ||
10 | + name: '湖南省', | ||
11 | + province: '湖南省', | ||
12 | + city: null, | ||
13 | + district: null, | ||
14 | + town: null, | ||
15 | + village: null, | ||
16 | + parentPath: '430000', | ||
17 | + createTime: '2020-11-30 15:47:31', | ||
18 | + updateTime: '2020-11-30 16:33:42', | ||
19 | + customized: false, | ||
20 | + usable: true, | ||
21 | + }, | ||
22 | + { | ||
23 | + id: '530825900883980288', | ||
24 | + code: '430100', | ||
25 | + parentCode: '430000', | ||
26 | + levelType: 2, | ||
27 | + name: '长沙市', | ||
28 | + province: '湖南省', | ||
29 | + city: '长沙市', | ||
30 | + district: null, | ||
31 | + town: null, | ||
32 | + village: null, | ||
33 | + parentPath: '430000,430100', | ||
34 | + createTime: '2020-11-30 15:47:31', | ||
35 | + updateTime: '2020-11-30 16:33:42', | ||
36 | + customized: false, | ||
37 | + usable: true, | ||
38 | + }, | ||
39 | + { | ||
40 | + id: '530825900951089152', | ||
41 | + code: '430102', | ||
42 | + parentCode: '430100', | ||
43 | + levelType: 3, | ||
44 | + name: '芙蓉区', | ||
45 | + province: '湖南省', | ||
46 | + city: '长沙市', | ||
47 | + district: '芙蓉区', | ||
48 | + town: null, | ||
49 | + village: null, | ||
50 | + parentPath: '430000,430100,430102', | ||
51 | + createTime: '2020-11-30 15:47:31', | ||
52 | + updateTime: '2020-11-30 16:33:42', | ||
53 | + customized: false, | ||
54 | + usable: true, | ||
55 | + }, | ||
56 | + { | ||
57 | + id: '530825901014003712', | ||
58 | + code: '430104', | ||
59 | + parentCode: '430100', | ||
60 | + levelType: 3, | ||
61 | + name: '岳麓区', | ||
62 | + province: '湖南省', | ||
63 | + city: '长沙市', | ||
64 | + district: '岳麓区', | ||
65 | + town: null, | ||
66 | + village: null, | ||
67 | + parentPath: '430000,430100,430104', | ||
68 | + createTime: '2020-11-30 15:47:31', | ||
69 | + updateTime: '2020-11-30 16:33:42', | ||
70 | + customized: false, | ||
71 | + usable: true, | ||
72 | + }, | ||
73 | + { | ||
74 | + id: '530825900988837888', | ||
75 | + code: '430103', | ||
76 | + parentCode: '430100', | ||
77 | + levelType: 3, | ||
78 | + name: '天心区', | ||
79 | + province: '湖南省', | ||
80 | + city: '长沙市', | ||
81 | + district: '天心区', | ||
82 | + town: null, | ||
83 | + village: null, | ||
84 | + parentPath: '430000,430100,430103', | ||
85 | + createTime: '2020-11-30 15:47:31', | ||
86 | + updateTime: '2020-11-30 16:33:42', | ||
87 | + customized: false, | ||
88 | + usable: true, | ||
89 | + }, | ||
90 | + { | ||
91 | + id: '530826672489115648', | ||
92 | + code: '430103002', | ||
93 | + parentCode: '430103', | ||
94 | + levelType: 4, | ||
95 | + name: '坡子街街道', | ||
96 | + province: '湖南省', | ||
97 | + city: '长沙市', | ||
98 | + district: '天心区', | ||
99 | + town: '坡子街街道', | ||
100 | + village: null, | ||
101 | + parentPath: '430000,430100,430103,430103002', | ||
102 | + createTime: '2020-11-30 15:47:31', | ||
103 | + updateTime: '2020-12-14 15:26:43', | ||
104 | + customized: false, | ||
105 | + usable: true, | ||
106 | + }, | ||
107 | + { | ||
108 | + id: '530840241171607552', | ||
109 | + code: '430103002001', | ||
110 | + parentCode: '430103002', | ||
111 | + levelType: 5, | ||
112 | + name: '八角亭社区', | ||
113 | + province: '湖南省', | ||
114 | + city: '长沙市', | ||
115 | + district: '天心区', | ||
116 | + town: '坡子街街道', | ||
117 | + village: '八角亭社区', | ||
118 | + parentPath: '430000,430100,430103,430103002,430103002001', | ||
119 | + createTime: '2020-11-30 15:47:31', | ||
120 | + updateTime: '2021-01-20 14:07:23', | ||
121 | + customized: false, | ||
122 | + usable: true, | ||
123 | + }, | ||
124 | + { | ||
125 | + id: '530840241200967680', | ||
126 | + code: '430103002002', | ||
127 | + parentCode: '430103002', | ||
128 | + levelType: 5, | ||
129 | + name: '西牌楼社区', | ||
130 | + province: '湖南省', | ||
131 | + city: '长沙市', | ||
132 | + district: '天心区', | ||
133 | + town: '坡子街街道', | ||
134 | + village: '西牌楼社区', | ||
135 | + parentPath: '430000,430100,430103,430103002,430103002002', | ||
136 | + createTime: '2020-11-30 15:47:31', | ||
137 | + updateTime: '2020-11-30 17:30:41', | ||
138 | + customized: false, | ||
139 | + usable: true, | ||
140 | + }, | ||
141 | + { | ||
142 | + id: '530840241230327808', | ||
143 | + code: '430103002003', | ||
144 | + parentCode: '430103002', | ||
145 | + levelType: 5, | ||
146 | + name: '太平街社区', | ||
147 | + province: '湖南省', | ||
148 | + city: '长沙市', | ||
149 | + district: '天心区', | ||
150 | + town: '坡子街街道', | ||
151 | + village: '太平街社区', | ||
152 | + parentPath: '430000,430100,430103,430103002,430103002003', | ||
153 | + createTime: '2020-11-30 15:47:31', | ||
154 | + updateTime: '2020-11-30 17:30:41', | ||
155 | + customized: false, | ||
156 | + usable: true, | ||
157 | + }, | ||
158 | + { | ||
159 | + id: '530840241259687936', | ||
160 | + code: '430103002005', | ||
161 | + parentCode: '430103002', | ||
162 | + levelType: 5, | ||
163 | + name: '坡子街社区', | ||
164 | + province: '湖南省', | ||
165 | + city: '长沙市', | ||
166 | + district: '天心区', | ||
167 | + town: '坡子街街道', | ||
168 | + village: '坡子街社区', | ||
169 | + parentPath: '430000,430100,430103,430103002,430103002005', | ||
170 | + createTime: '2020-11-30 15:47:31', | ||
171 | + updateTime: '2020-11-30 17:30:41', | ||
172 | + customized: false, | ||
173 | + usable: true, | ||
174 | + }, | ||
175 | + { | ||
176 | + id: '530840241284853760', | ||
177 | + code: '430103002006', | ||
178 | + parentCode: '430103002', | ||
179 | + levelType: 5, | ||
180 | + name: '青山祠社区', | ||
181 | + province: '湖南省', | ||
182 | + city: '长沙市', | ||
183 | + district: '天心区', | ||
184 | + town: '坡子街街道', | ||
185 | + village: '青山祠社区', | ||
186 | + parentPath: '430000,430100,430103,430103002,430103002006', | ||
187 | + createTime: '2020-11-30 15:47:31', | ||
188 | + updateTime: '2020-11-30 17:30:41', | ||
189 | + customized: false, | ||
190 | + usable: true, | ||
191 | + }, | ||
192 | + { | ||
193 | + id: '530840241310019584', | ||
194 | + code: '430103002007', | ||
195 | + parentCode: '430103002', | ||
196 | + levelType: 5, | ||
197 | + name: '沙河社区', | ||
198 | + province: '湖南省', | ||
199 | + city: '长沙市', | ||
200 | + district: '天心区', | ||
201 | + town: '坡子街街道', | ||
202 | + village: '沙河社区', | ||
203 | + parentPath: '430000,430100,430103,430103002,430103002007', | ||
204 | + createTime: '2020-11-30 15:47:31', | ||
205 | + updateTime: '2020-11-30 17:30:41', | ||
206 | + customized: false, | ||
207 | + usable: true, | ||
208 | + }, | ||
209 | + { | ||
210 | + id: '530840241381322752', | ||
211 | + code: '430103002008', | ||
212 | + parentCode: '430103002', | ||
213 | + levelType: 5, | ||
214 | + name: '碧湘社区', | ||
215 | + province: '湖南省', | ||
216 | + city: '长沙市', | ||
217 | + district: '天心区', | ||
218 | + town: '坡子街街道', | ||
219 | + village: '碧湘社区', | ||
220 | + parentPath: '430000,430100,430103,430103002,430103002008', | ||
221 | + createTime: '2020-11-30 15:47:31', | ||
222 | + updateTime: '2020-11-30 17:30:41', | ||
223 | + customized: false, | ||
224 | + usable: true, | ||
225 | + }, | ||
226 | + { | ||
227 | + id: '530840241410682880', | ||
228 | + code: '430103002009', | ||
229 | + parentCode: '430103002', | ||
230 | + levelType: 5, | ||
231 | + name: '创远社区', | ||
232 | + province: '湖南省', | ||
233 | + city: '长沙市', | ||
234 | + district: '天心区', | ||
235 | + town: '坡子街街道', | ||
236 | + village: '创远社区', | ||
237 | + parentPath: '430000,430100,430103,430103002,430103002009', | ||
238 | + createTime: '2020-11-30 15:47:31', | ||
239 | + updateTime: '2020-11-30 17:30:41', | ||
240 | + customized: false, | ||
241 | + usable: true, | ||
242 | + }, | ||
243 | + { | ||
244 | + id: '530840241431654400', | ||
245 | + code: '430103002010', | ||
246 | + parentCode: '430103002', | ||
247 | + levelType: 5, | ||
248 | + name: '楚湘社区', | ||
249 | + province: '湖南省', | ||
250 | + city: '长沙市', | ||
251 | + district: '天心区', | ||
252 | + town: '坡子街街道', | ||
253 | + village: '楚湘社区', | ||
254 | + parentPath: '430000,430100,430103,430103002,430103002010', | ||
255 | + createTime: '2020-11-30 15:47:31', | ||
256 | + updateTime: '2020-11-30 17:30:41', | ||
257 | + customized: false, | ||
258 | + usable: true, | ||
259 | + }, | ||
260 | + { | ||
261 | + id: '530840241465208832', | ||
262 | + code: '430103002011', | ||
263 | + parentCode: '430103002', | ||
264 | + levelType: 5, | ||
265 | + name: '西湖社区', | ||
266 | + province: '湖南省', | ||
267 | + city: '长沙市', | ||
268 | + district: '天心区', | ||
269 | + town: '坡子街街道', | ||
270 | + village: '西湖社区', | ||
271 | + parentPath: '430000,430100,430103,430103002,430103002011', | ||
272 | + createTime: '2020-11-30 15:47:31', | ||
273 | + updateTime: '2020-11-30 17:30:41', | ||
274 | + customized: false, | ||
275 | + usable: true, | ||
276 | + }, | ||
277 | + { | ||
278 | + id: '530840241502957568', | ||
279 | + code: '430103002012', | ||
280 | + parentCode: '430103002', | ||
281 | + levelType: 5, | ||
282 | + name: '登仁桥社区', | ||
283 | + province: '湖南省', | ||
284 | + city: '长沙市', | ||
285 | + district: '天心区', | ||
286 | + town: '坡子街街道', | ||
287 | + village: '登仁桥社区', | ||
288 | + parentPath: '430000,430100,430103,430103002,430103002012', | ||
289 | + createTime: '2020-11-30 15:47:31', | ||
290 | + updateTime: '2020-11-30 17:30:41', | ||
291 | + customized: false, | ||
292 | + usable: true, | ||
293 | + }, | ||
294 | + { | ||
295 | + id: '530840241553289216', | ||
296 | + code: '430103002013', | ||
297 | + parentCode: '430103002', | ||
298 | + levelType: 5, | ||
299 | + name: '文庙坪社区', | ||
300 | + province: '湖南省', | ||
301 | + city: '长沙市', | ||
302 | + district: '天心区', | ||
303 | + town: '坡子街街道', | ||
304 | + village: '文庙坪社区', | ||
305 | + parentPath: '430000,430100,430103,430103002,430103002013', | ||
306 | + createTime: '2020-11-30 15:47:31', | ||
307 | + updateTime: '2020-11-30 17:30:41', | ||
308 | + customized: false, | ||
309 | + usable: true, | ||
310 | + }, | ||
311 | +]; | ||
312 | +export default [ | ||
313 | + { | ||
314 | + url: '/basic-api/cascader/getAreaRecord', | ||
315 | + timeout: 1000, | ||
316 | + method: 'post', | ||
317 | + response: ({ body }) => { | ||
318 | + const { parentCode } = body || {}; | ||
319 | + if (!parentCode) { | ||
320 | + return resultSuccess(areaList.filter((it) => it.code === '430000')); | ||
321 | + } | ||
322 | + return resultSuccess(areaList.filter((it) => it.parentCode === parentCode)); | ||
323 | + }, | ||
324 | + }, | ||
325 | +] as MockMethod[]; |
src/api/demo/cascader.ts
0 → 100644
1 | +import { defHttp } from '/@/utils/http/axios'; | ||
2 | +import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'; | ||
3 | + | ||
4 | +enum Api { | ||
5 | + AREA_RECORD = '/cascader/getAreaRecord', | ||
6 | +} | ||
7 | + | ||
8 | +export const areaRecord = (data: AreaParams) => | ||
9 | + defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data }); |
src/api/demo/model/areaModel.ts
0 → 100644
src/components/Form/index.ts
@@ -10,5 +10,6 @@ export { default as ApiSelect } from './src/components/ApiSelect.vue'; | @@ -10,5 +10,6 @@ export { default as ApiSelect } from './src/components/ApiSelect.vue'; | ||
10 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; | 10 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; |
11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; | 11 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; |
12 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | 12 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; |
13 | +export { default as ApiCascader } from './src/components/ApiCascader.vue'; | ||
13 | 14 | ||
14 | export { BasicForm }; | 15 | export { BasicForm }; |
src/components/Form/src/componentMap.ts
@@ -25,6 +25,7 @@ import ApiRadioGroup from './components/ApiRadioGroup.vue'; | @@ -25,6 +25,7 @@ import ApiRadioGroup from './components/ApiRadioGroup.vue'; | ||
25 | import RadioButtonGroup from './components/RadioButtonGroup.vue'; | 25 | import RadioButtonGroup from './components/RadioButtonGroup.vue'; |
26 | import ApiSelect from './components/ApiSelect.vue'; | 26 | import ApiSelect from './components/ApiSelect.vue'; |
27 | import ApiTreeSelect from './components/ApiTreeSelect.vue'; | 27 | import ApiTreeSelect from './components/ApiTreeSelect.vue'; |
28 | +import ApiCascader from './components/ApiCascader.vue'; | ||
28 | import { BasicUpload } from '/@/components/Upload'; | 29 | import { BasicUpload } from '/@/components/Upload'; |
29 | import { StrengthMeter } from '/@/components/StrengthMeter'; | 30 | import { StrengthMeter } from '/@/components/StrengthMeter'; |
30 | import { IconPicker } from '/@/components/Icon'; | 31 | import { IconPicker } from '/@/components/Icon'; |
@@ -50,6 +51,7 @@ componentMap.set('RadioButtonGroup', RadioButtonGroup); | @@ -50,6 +51,7 @@ componentMap.set('RadioButtonGroup', RadioButtonGroup); | ||
50 | componentMap.set('RadioGroup', Radio.Group); | 51 | componentMap.set('RadioGroup', Radio.Group); |
51 | componentMap.set('Checkbox', Checkbox); | 52 | componentMap.set('Checkbox', Checkbox); |
52 | componentMap.set('CheckboxGroup', Checkbox.Group); | 53 | componentMap.set('CheckboxGroup', Checkbox.Group); |
54 | +componentMap.set('ApiCascader', ApiCascader); | ||
53 | componentMap.set('Cascader', Cascader); | 55 | componentMap.set('Cascader', Cascader); |
54 | componentMap.set('Slider', Slider); | 56 | componentMap.set('Slider', Slider); |
55 | componentMap.set('Rate', Rate); | 57 | componentMap.set('Rate', Rate); |
src/components/Form/src/components/ApiCascader.vue
0 → 100644
1 | +<template> | ||
2 | + <a-cascader | ||
3 | + v-model:value="state" | ||
4 | + :options="options" | ||
5 | + :load-data="loadData" | ||
6 | + change-on-select | ||
7 | + @change="handleChange" | ||
8 | + :displayRender="handleRenderDisplay" | ||
9 | + > | ||
10 | + <template #suffixIcon v-if="loading"> | ||
11 | + <LoadingOutlined spin /> | ||
12 | + </template> | ||
13 | + <template #notFoundContent v-if="loading"> | ||
14 | + <span> | ||
15 | + <LoadingOutlined spin class="mr-1" /> | ||
16 | + {{ t('component.form.apiSelectNotFound') }} | ||
17 | + </span> | ||
18 | + </template> | ||
19 | + </a-cascader> | ||
20 | +</template> | ||
21 | +<script lang="ts"> | ||
22 | + import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue'; | ||
23 | + import { Cascader } from 'ant-design-vue'; | ||
24 | + import { propTypes } from '/@/utils/propTypes'; | ||
25 | + import { isFunction } from '/@/utils/is'; | ||
26 | + import { get, omit } from 'lodash-es'; | ||
27 | + import { useRuleFormItem } from '/@/hooks/component/useFormItem'; | ||
28 | + import { LoadingOutlined } from '@ant-design/icons-vue'; | ||
29 | + | ||
30 | + interface Option { | ||
31 | + value: string; | ||
32 | + label: string; | ||
33 | + loading?: boolean; | ||
34 | + isLeaf?: boolean; | ||
35 | + children?: Option[]; | ||
36 | + } | ||
37 | + export default defineComponent({ | ||
38 | + name: 'ApiCascader', | ||
39 | + components: { | ||
40 | + LoadingOutlined, | ||
41 | + [Cascader.name]: Cascader, | ||
42 | + }, | ||
43 | + props: { | ||
44 | + value: { | ||
45 | + type: Array, | ||
46 | + }, | ||
47 | + api: { | ||
48 | + type: Function as PropType<(arg?: Recordable) => Promise<Option[]>>, | ||
49 | + default: null, | ||
50 | + }, | ||
51 | + numberToString: propTypes.bool, | ||
52 | + resultField: propTypes.string.def(''), | ||
53 | + labelField: propTypes.string.def('label'), | ||
54 | + valueField: propTypes.string.def('value'), | ||
55 | + childrenField: propTypes.string.def('children'), | ||
56 | + asyncFetchParamKey: propTypes.string.def('parentCode'), | ||
57 | + immediate: propTypes.bool.def(true), | ||
58 | + // init fetch params | ||
59 | + initFetchParams: { | ||
60 | + type: Object as PropType<Recordable>, | ||
61 | + default: () => ({}), | ||
62 | + }, | ||
63 | + // 是否有下级,默认是 | ||
64 | + isLeaf: { | ||
65 | + type: Function as PropType<(arg: Recordable) => boolean>, | ||
66 | + default: null, | ||
67 | + }, | ||
68 | + displayRenderArray: { | ||
69 | + type: Array, | ||
70 | + }, | ||
71 | + }, | ||
72 | + emits: ['change', 'defaultChange'], | ||
73 | + setup(props, { emit }) { | ||
74 | + const apiData = ref<any[]>([]); | ||
75 | + const options = ref<Option[]>([]); | ||
76 | + const loading = ref<boolean>(false); | ||
77 | + const emitData = ref<any[]>([]); | ||
78 | + const isFirstLoad = ref(true); | ||
79 | + | ||
80 | + // Embedded in the form, just use the hook binding to perform form verification | ||
81 | + const [state] = useRuleFormItem(props, 'value', 'change', emitData); | ||
82 | + | ||
83 | + watch( | ||
84 | + apiData, | ||
85 | + (data) => { | ||
86 | + const opts = generatorOptions(data); | ||
87 | + options.value = opts; | ||
88 | + }, | ||
89 | + { deep: true }, | ||
90 | + ); | ||
91 | + | ||
92 | + function generatorOptions(options: any[]): Option[] { | ||
93 | + const { labelField, valueField, numberToString, childrenField, isLeaf } = props; | ||
94 | + return options.reduce((prev, next: Recordable) => { | ||
95 | + if (next) { | ||
96 | + const value = next[valueField]; | ||
97 | + const item = { | ||
98 | + ...omit(next, [labelField, valueField]), | ||
99 | + label: next[labelField], | ||
100 | + value: numberToString ? `${value}` : value, | ||
101 | + isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false, | ||
102 | + }; | ||
103 | + const children = Reflect.get(next, childrenField); | ||
104 | + if (children) { | ||
105 | + Reflect.set(item, childrenField, generatorOptions(children)); | ||
106 | + } | ||
107 | + prev.push(item); | ||
108 | + } | ||
109 | + return prev; | ||
110 | + }, [] as Option[]); | ||
111 | + } | ||
112 | + | ||
113 | + async function initialFetch() { | ||
114 | + const api = props.api; | ||
115 | + if (!api || !isFunction(api)) return; | ||
116 | + apiData.value = []; | ||
117 | + loading.value = true; | ||
118 | + try { | ||
119 | + const res = await api(props.initFetchParams); | ||
120 | + if (Array.isArray(res)) { | ||
121 | + apiData.value = res; | ||
122 | + return; | ||
123 | + } | ||
124 | + if (props.resultField) { | ||
125 | + apiData.value = get(res, props.resultField) || []; | ||
126 | + } | ||
127 | + } catch (error) { | ||
128 | + console.warn(error); | ||
129 | + } finally { | ||
130 | + loading.value = false; | ||
131 | + } | ||
132 | + } | ||
133 | + | ||
134 | + async function loadData(selectedOptions: Option[]) { | ||
135 | + const targetOption = selectedOptions[selectedOptions.length - 1]; | ||
136 | + targetOption.loading = true; | ||
137 | + | ||
138 | + const api = props.api; | ||
139 | + if (!api || !isFunction(api)) return; | ||
140 | + try { | ||
141 | + const res = await api({ | ||
142 | + [props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'), | ||
143 | + }); | ||
144 | + if (Array.isArray(res)) { | ||
145 | + const children = generatorOptions(res); | ||
146 | + targetOption.children = children; | ||
147 | + return; | ||
148 | + } | ||
149 | + if (props.resultField) { | ||
150 | + const children = generatorOptions(get(res, props.resultField) || []); | ||
151 | + targetOption.children = children; | ||
152 | + } | ||
153 | + } catch (e) { | ||
154 | + console.error(e); | ||
155 | + } finally { | ||
156 | + targetOption.loading = false; | ||
157 | + } | ||
158 | + } | ||
159 | + | ||
160 | + watchEffect(() => { | ||
161 | + props.immediate && initialFetch(); | ||
162 | + }); | ||
163 | + | ||
164 | + watch( | ||
165 | + () => props.initFetchParams, | ||
166 | + () => { | ||
167 | + !unref(isFirstLoad) && initialFetch(); | ||
168 | + }, | ||
169 | + { deep: true }, | ||
170 | + ); | ||
171 | + | ||
172 | + function handleChange(keys, args) { | ||
173 | + emitData.value = keys; | ||
174 | + emit('defaultChange', keys, args); | ||
175 | + } | ||
176 | + | ||
177 | + function handleRenderDisplay({ labels, selectedOptions }) { | ||
178 | + if (unref(emitData).length === selectedOptions.length) { | ||
179 | + return labels.join(' / '); | ||
180 | + } | ||
181 | + if (props.displayRenderArray) { | ||
182 | + return props.displayRenderArray.join(' / '); | ||
183 | + } | ||
184 | + return ''; | ||
185 | + } | ||
186 | + | ||
187 | + return { | ||
188 | + state, | ||
189 | + options, | ||
190 | + loading, | ||
191 | + handleChange, | ||
192 | + loadData, | ||
193 | + handleRenderDisplay, | ||
194 | + }; | ||
195 | + }, | ||
196 | + }); | ||
197 | +</script> |
src/components/Form/src/types/index.ts
@@ -98,6 +98,7 @@ export type ComponentType = | @@ -98,6 +98,7 @@ export type ComponentType = | ||
98 | | 'Checkbox' | 98 | | 'Checkbox' |
99 | | 'CheckboxGroup' | 99 | | 'CheckboxGroup' |
100 | | 'AutoComplete' | 100 | | 'AutoComplete' |
101 | + | 'ApiCascader' | ||
101 | | 'Cascader' | 102 | | 'Cascader' |
102 | | 'DatePicker' | 103 | | 'DatePicker' |
103 | | 'MonthPicker' | 104 | | 'MonthPicker' |
src/views/demo/form/UseForm.vue
@@ -52,6 +52,7 @@ | @@ -52,6 +52,7 @@ | ||
52 | > | 52 | > |
53 | 修改查询按钮 | 53 | 修改查询按钮 |
54 | </a-button> | 54 | </a-button> |
55 | + <a-button @click="handleLoad" class="mr-2"> 联动回显 </a-button> | ||
55 | </div> | 56 | </div> |
56 | <CollapseContainer title="useForm示例"> | 57 | <CollapseContainer title="useForm示例"> |
57 | <BasicForm @register="register" @submit="handleSubmit" /> | 58 | <BasicForm @register="register" @submit="handleSubmit" /> |
@@ -64,6 +65,7 @@ | @@ -64,6 +65,7 @@ | ||
64 | import { CollapseContainer } from '/@/components/Container/index'; | 65 | import { CollapseContainer } from '/@/components/Container/index'; |
65 | import { useMessage } from '/@/hooks/web/useMessage'; | 66 | import { useMessage } from '/@/hooks/web/useMessage'; |
66 | import { PageWrapper } from '/@/components/Page'; | 67 | import { PageWrapper } from '/@/components/Page'; |
68 | + import { areaRecord } from '/@/api/demo/cascader'; | ||
67 | 69 | ||
68 | const schemas: FormSchema[] = [ | 70 | const schemas: FormSchema[] = [ |
69 | { | 71 | { |
@@ -166,6 +168,48 @@ | @@ -166,6 +168,48 @@ | ||
166 | ], | 168 | ], |
167 | }, | 169 | }, |
168 | }, | 170 | }, |
171 | + { | ||
172 | + field: 'field8', | ||
173 | + component: 'ApiCascader', | ||
174 | + label: '联动', | ||
175 | + colProps: { | ||
176 | + span: 8, | ||
177 | + }, | ||
178 | + componentProps: { | ||
179 | + api: areaRecord, | ||
180 | + apiParamKey: 'parentCode', | ||
181 | + dataField: 'data', | ||
182 | + labelField: 'name', | ||
183 | + valueField: 'code', | ||
184 | + initFetchParams: { | ||
185 | + parentCode: '', | ||
186 | + }, | ||
187 | + isLeaf: (record) => { | ||
188 | + return !(record.levelType < 3); | ||
189 | + }, | ||
190 | + }, | ||
191 | + }, | ||
192 | + { | ||
193 | + field: 'field9', | ||
194 | + component: 'ApiCascader', | ||
195 | + label: '联动回显', | ||
196 | + colProps: { | ||
197 | + span: 8, | ||
198 | + }, | ||
199 | + componentProps: { | ||
200 | + api: areaRecord, | ||
201 | + apiParamKey: 'parentCode', | ||
202 | + dataField: 'data', | ||
203 | + labelField: 'name', | ||
204 | + valueField: 'code', | ||
205 | + initFetchParams: { | ||
206 | + parentCode: '', | ||
207 | + }, | ||
208 | + isLeaf: (record) => { | ||
209 | + return !(record.levelType < 3); | ||
210 | + }, | ||
211 | + }, | ||
212 | + }, | ||
169 | ]; | 213 | ]; |
170 | 214 | ||
171 | export default defineComponent({ | 215 | export default defineComponent({ |
@@ -173,7 +217,7 @@ | @@ -173,7 +217,7 @@ | ||
173 | setup() { | 217 | setup() { |
174 | const { createMessage } = useMessage(); | 218 | const { createMessage } = useMessage(); |
175 | 219 | ||
176 | - const [register, { setProps }] = useForm({ | 220 | + const [register, { setProps, setFieldsValue, updateSchema }] = useForm({ |
177 | labelWidth: 120, | 221 | labelWidth: 120, |
178 | schemas, | 222 | schemas, |
179 | actionColOptions: { | 223 | actionColOptions: { |
@@ -181,6 +225,35 @@ | @@ -181,6 +225,35 @@ | ||
181 | }, | 225 | }, |
182 | fieldMapToTime: [['fieldTime', ['startTime', 'endTime'], 'YYYY-MM']], | 226 | fieldMapToTime: [['fieldTime', ['startTime', 'endTime'], 'YYYY-MM']], |
183 | }); | 227 | }); |
228 | + | ||
229 | + async function handleLoad() { | ||
230 | + const promiseFn = function () { | ||
231 | + return new Promise((resolve) => { | ||
232 | + setTimeout(() => { | ||
233 | + resolve({ | ||
234 | + field9: ['430000', '430100', '430102'], | ||
235 | + province: '湖南省', | ||
236 | + city: '长沙市', | ||
237 | + district: '岳麓区', | ||
238 | + }); | ||
239 | + }, 1000); | ||
240 | + }); | ||
241 | + }; | ||
242 | + | ||
243 | + const item = await promiseFn(); | ||
244 | + | ||
245 | + const { field9, province, city, district } = item as any; | ||
246 | + await updateSchema({ | ||
247 | + field: 'field9', | ||
248 | + componentProps: { | ||
249 | + displayRenderArray: [province, city, district], | ||
250 | + }, | ||
251 | + }); | ||
252 | + await setFieldsValue({ | ||
253 | + field9, | ||
254 | + }); | ||
255 | + } | ||
256 | + | ||
184 | return { | 257 | return { |
185 | register, | 258 | register, |
186 | schemas, | 259 | schemas, |
@@ -188,6 +261,7 @@ | @@ -188,6 +261,7 @@ | ||
188 | createMessage.success('click search,values:' + JSON.stringify(values)); | 261 | createMessage.success('click search,values:' + JSON.stringify(values)); |
189 | }, | 262 | }, |
190 | setProps, | 263 | setProps, |
264 | + handleLoad, | ||
191 | }; | 265 | }; |
192 | }, | 266 | }, |
193 | }); | 267 | }); |