Commit 40071529d276bfdbff7610adf01aa4af36ff3f77

Authored by Cyrus Zhou
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' },
      ],
    },
    ....
```
src/components/Form/index.ts
@@ -12,5 +12,6 @@ export { default as ApiTreeSelect } from &#39;./src/components/ApiTreeSelect.vue&#39;; @@ -12,5 +12,6 @@ export { default as ApiTreeSelect } from &#39;./src/components/ApiTreeSelect.vue&#39;;
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 &#39;./components/ApiSelect.vue&#39;; @@ -27,6 +27,7 @@ import ApiSelect from &#39;./components/ApiSelect.vue&#39;;
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(&#39;ApiCascader&#39;, ApiCascader); @@ -57,6 +58,7 @@ componentMap.set(&#39;ApiCascader&#39;, 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
@@ -113,4 +113,5 @@ export type ComponentType = @@ -113,4 +113,5 @@ export type ComponentType =
113 | 'Render' 113 | 'Render'
114 | 'Slider' 114 | 'Slider'
115 | 'Rate' 115 | 'Rate'
116 - | 'Divider'; 116 + | 'Divider'
  117 + | 'ApiTransfer';
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>