Commit 40071529d276bfdbff7610adf01aa4af36ff3f77
Committed by
GitHub
1 parent
6f5711b0
添加 Form ApiTransfer , 修复标签页切换灰屏不显示内容问题 (#2052)
* Table BasicColumn 添加 editDynamicDisabled Co-authored-by: Cyrus Zhou <6802207@qq.com> 使用方式同 Form FormSchema dynamicDisabled ``` export const Columns: BasicColumn[] = [ { title: 'Title', dataIndex: 'Title', editRow: true, editComponent: 'Select', editDynamicDisabled: ({ record }) => record.isDisabled, }, * editComponentProps onChange 功能恢复 Co-authored-by: Cyrus Zhou <6802207@qq.com> 说明: ...omit(compProps, 'onChange') 这会忽略 onChange ,导致 editComponentProps onChange 被取消 如下功能将不支持: ``` editComponentProps: ({ record }) => { return { options: effectTypeData, onChange: () => { }, }; }, ``` * tableData == null 报错 * ApiSelect 第一次选择触发required错误提示问题 * 恢复 虽然可以解决第一次选择提示报错问题,但是会导致 onChange: (e: any, options: any) => 无法获得 options 的值 * 修复标签页切换灰屏不显示内容问题 Co-authored-by: Cyrus Zhou <6802207@qq.com> 问题描述页面没有用 div 包括 会提示 Component inside <Transition> renders non-element root node that cannot be animated , 导致页灰屏必须刷新页面才可以显示内容 * 添加 Form ApiTransfer ## 使用方式 api 方式: ``` ...... component: 'ApiTransfer', componentProps: { api: sysUserSelector, labelField: 'name', valueField: 'id', }, ..... ``` 数据方式: ``` .... componentProps: { dataSource: [ { title: 'Test01', key: '0', disabled: false, description: 'description 01' }, { title: 'Test02', key: '1', disabled: false, description: 'description 02' }, { title: 'Test03', key: '2', disabled: false, description: 'description 03' }, { title: 'Test04', key: '3', disabled: false, description: 'description 04' }, { title: 'Test05', key: '4', disabled: false, description: 'description 05' }, ], }, .... ```
Showing
5 changed files
with
146 additions
and
3 deletions
src/components/Form/index.ts
@@ -12,5 +12,6 @@ export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; | @@ -12,5 +12,6 @@ export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; | ||
12 | export { default as ApiTree } from './src/components/ApiTree.vue'; | 12 | export { default as ApiTree } from './src/components/ApiTree.vue'; |
13 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | 13 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; |
14 | export { default as ApiCascader } from './src/components/ApiCascader.vue'; | 14 | export { default as ApiCascader } from './src/components/ApiCascader.vue'; |
15 | +export { default as ApiTransfer } from './src/components/ApiTransfer.vue'; | ||
15 | 16 | ||
16 | export { BasicForm }; | 17 | export { BasicForm }; |
src/components/Form/src/componentMap.ts
@@ -27,6 +27,7 @@ import ApiSelect from './components/ApiSelect.vue'; | @@ -27,6 +27,7 @@ import ApiSelect from './components/ApiSelect.vue'; | ||
27 | import ApiTree from './components/ApiTree.vue'; | 27 | import ApiTree from './components/ApiTree.vue'; |
28 | import ApiTreeSelect from './components/ApiTreeSelect.vue'; | 28 | import ApiTreeSelect from './components/ApiTreeSelect.vue'; |
29 | import ApiCascader from './components/ApiCascader.vue'; | 29 | import ApiCascader from './components/ApiCascader.vue'; |
30 | +import ApiTransfer from './components/ApiTransfer.vue'; | ||
30 | import { BasicUpload } from '/@/components/Upload'; | 31 | import { BasicUpload } from '/@/components/Upload'; |
31 | import { StrengthMeter } from '/@/components/StrengthMeter'; | 32 | import { StrengthMeter } from '/@/components/StrengthMeter'; |
32 | import { IconPicker } from '/@/components/Icon'; | 33 | import { IconPicker } from '/@/components/Icon'; |
@@ -57,6 +58,7 @@ componentMap.set('ApiCascader', ApiCascader); | @@ -57,6 +58,7 @@ componentMap.set('ApiCascader', ApiCascader); | ||
57 | componentMap.set('Cascader', Cascader); | 58 | componentMap.set('Cascader', Cascader); |
58 | componentMap.set('Slider', Slider); | 59 | componentMap.set('Slider', Slider); |
59 | componentMap.set('Rate', Rate); | 60 | componentMap.set('Rate', Rate); |
61 | +componentMap.set('ApiTransfer', ApiTransfer); | ||
60 | 62 | ||
61 | componentMap.set('DatePicker', DatePicker); | 63 | componentMap.set('DatePicker', DatePicker); |
62 | componentMap.set('MonthPicker', DatePicker.MonthPicker); | 64 | componentMap.set('MonthPicker', DatePicker.MonthPicker); |
src/components/Form/src/components/ApiTransfer.vue
0 → 100644
1 | +<template> | ||
2 | + <Transfer | ||
3 | + :data-source="getdataSource" | ||
4 | + show-search | ||
5 | + :filter-option="filterOption" | ||
6 | + :render="(item) => item.title" | ||
7 | + :showSelectAll="showSelectAll" | ||
8 | + :selectedKeys="selectedKeys" | ||
9 | + :targetKeys="getTargetKeys" | ||
10 | + :showSearch="showSearch" | ||
11 | + @change="handleChange" | ||
12 | + /> | ||
13 | +</template> | ||
14 | + | ||
15 | +<script lang="ts"> | ||
16 | + import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue'; | ||
17 | + import { Transfer } from 'ant-design-vue'; | ||
18 | + import { isFunction } from '/@/utils/is'; | ||
19 | + import { get, omit } from 'lodash-es'; | ||
20 | + import { propTypes } from '/@/utils/propTypes'; | ||
21 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
22 | + import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'; | ||
23 | + export default defineComponent({ | ||
24 | + name: 'ApiTransfer', | ||
25 | + components: { Transfer }, | ||
26 | + props: { | ||
27 | + value: { type: Array<string> }, | ||
28 | + api: { | ||
29 | + type: Function as PropType<(arg?: Recordable) => Promise<TransferItem[]>>, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + params: { type: Object }, | ||
33 | + dataSource: { type: Array<TransferItem> }, | ||
34 | + immediate: propTypes.bool.def(true), | ||
35 | + alwaysLoad: propTypes.bool.def(false), | ||
36 | + afterFetch: { type: Function as PropType<Fn> }, | ||
37 | + resultField: propTypes.string.def(''), | ||
38 | + labelField: propTypes.string.def('title'), | ||
39 | + valueField: propTypes.string.def('key'), | ||
40 | + showSearch: { type: Boolean, default: false }, | ||
41 | + disabled: { type: Boolean, default: false }, | ||
42 | + filterOption: { | ||
43 | + type: Function as PropType<(inputValue: string, item: TransferItem) => boolean>, | ||
44 | + }, | ||
45 | + selectedKeys: { type: Array<string> }, | ||
46 | + showSelectAll: { type: Boolean, default: false }, | ||
47 | + targetKeys: { type: Array<string> }, | ||
48 | + }, | ||
49 | + emits: ['options-change', 'change'], | ||
50 | + setup(props, { attrs, emit }) { | ||
51 | + const _dataSource = ref<TransferItem[]>([]); | ||
52 | + const _targetKeys = ref<string[]>([]); | ||
53 | + const { t } = useI18n(); | ||
54 | + | ||
55 | + const getAttrs = computed(() => { | ||
56 | + return { | ||
57 | + ...(!props.api ? { dataSource: unref(_dataSource) } : {}), | ||
58 | + ...attrs, | ||
59 | + }; | ||
60 | + }); | ||
61 | + const getdataSource = computed(() => { | ||
62 | + const { labelField, valueField } = props; | ||
63 | + | ||
64 | + return unref(_dataSource).reduce((prev, next: Recordable) => { | ||
65 | + if (next) { | ||
66 | + prev.push({ | ||
67 | + ...omit(next, [labelField, valueField]), | ||
68 | + title: next[labelField], | ||
69 | + key: next[valueField], | ||
70 | + }); | ||
71 | + } | ||
72 | + return prev; | ||
73 | + }, [] as TransferItem[]); | ||
74 | + }); | ||
75 | + const getTargetKeys = computed<string[]>(() => { | ||
76 | + if (unref(_targetKeys).length > 0) { | ||
77 | + return unref(_targetKeys); | ||
78 | + } | ||
79 | + if (Array.isArray(props.value)) { | ||
80 | + return props.value; | ||
81 | + } | ||
82 | + return []; | ||
83 | + }); | ||
84 | + | ||
85 | + function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) { | ||
86 | + _targetKeys.value = keys; | ||
87 | + console.log(direction); | ||
88 | + console.log(moveKeys); | ||
89 | + emit('change', keys); | ||
90 | + } | ||
91 | + | ||
92 | + watchEffect(() => { | ||
93 | + props.immediate && !props.alwaysLoad && fetch(); | ||
94 | + }); | ||
95 | + | ||
96 | + watch( | ||
97 | + () => props.params, | ||
98 | + () => { | ||
99 | + fetch(); | ||
100 | + }, | ||
101 | + { deep: true }, | ||
102 | + ); | ||
103 | + | ||
104 | + async function fetch() { | ||
105 | + const api = props.api; | ||
106 | + if (!api || !isFunction(api)) { | ||
107 | + if (Array.isArray(props.dataSource)) { | ||
108 | + _dataSource.value = props.dataSource; | ||
109 | + } | ||
110 | + return; | ||
111 | + } | ||
112 | + _dataSource.value = []; | ||
113 | + try { | ||
114 | + const res = await api(props.params); | ||
115 | + if (Array.isArray(res)) { | ||
116 | + _dataSource.value = res; | ||
117 | + emitChange(); | ||
118 | + return; | ||
119 | + } | ||
120 | + if (props.resultField) { | ||
121 | + _dataSource.value = get(res, props.resultField) || []; | ||
122 | + } | ||
123 | + emitChange(); | ||
124 | + } catch (error) { | ||
125 | + console.warn(error); | ||
126 | + } finally { | ||
127 | + } | ||
128 | + } | ||
129 | + function emitChange() { | ||
130 | + emit('options-change', unref(getdataSource)); | ||
131 | + } | ||
132 | + return { getTargetKeys, getdataSource, t, getAttrs, handleChange }; | ||
133 | + }, | ||
134 | + }); | ||
135 | +</script> |
src/components/Form/src/types/index.ts
src/layouts/page/index.vue
@@ -15,9 +15,13 @@ | @@ -15,9 +15,13 @@ | ||
15 | appear | 15 | appear |
16 | > | 16 | > |
17 | <keep-alive v-if="openCache" :include="getCaches"> | 17 | <keep-alive v-if="openCache" :include="getCaches"> |
18 | - <component :is="Component" :key="route.fullPath" /> | 18 | + <div :key="route.name"> |
19 | + <component :is="Component" :key="route.fullPath" /> | ||
20 | + </div> | ||
19 | </keep-alive> | 21 | </keep-alive> |
20 | - <component v-else :is="Component" :key="route.fullPath" /> | 22 | + <div v-else :key="route.name"> |
23 | + <component :is="Component" :key="route.fullPath" /> | ||
24 | + </div> | ||
21 | </transition> | 25 | </transition> |
22 | </template> | 26 | </template> |
23 | </RouterView> | 27 | </RouterView> |