Browse Source

优化Upload组件目录结构,以及图片上传组件 (#3241)

* fix(vxe-table): theme dark is not work

* perf(ImageUpload): 优化Upload组件目录结构,以及图片上传组件
zhang 1 year ago
parent
commit
dccc8f625d

+ 0 - 1
src/components/Form/index.ts

@@ -13,6 +13,5 @@ export { default as ApiTree } from './src/components/ApiTree.vue';
 export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
 export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
 export { default as ApiCascader } from './src/components/ApiCascader.vue';
 export { default as ApiCascader } from './src/components/ApiCascader.vue';
 export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
 export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
-export { default as ImageUpload } from './src/components/ImageUpload.vue';
 
 
 export { BasicForm };
 export { BasicForm };

+ 1 - 2
src/components/Form/src/componentMap.ts

@@ -27,8 +27,7 @@ import ApiTree from './components/ApiTree.vue';
 import ApiTreeSelect from './components/ApiTreeSelect.vue';
 import ApiTreeSelect from './components/ApiTreeSelect.vue';
 import ApiCascader from './components/ApiCascader.vue';
 import ApiCascader from './components/ApiCascader.vue';
 import ApiTransfer from './components/ApiTransfer.vue';
 import ApiTransfer from './components/ApiTransfer.vue';
-import ImageUpload from './components/ImageUpload.vue';
-import { BasicUpload } from '/@/components/Upload';
+import { BasicUpload, ImageUpload } from '/@/components/Upload';
 import { StrengthMeter } from '/@/components/StrengthMeter';
 import { StrengthMeter } from '/@/components/StrengthMeter';
 import { IconPicker } from '/@/components/Icon';
 import { IconPicker } from '/@/components/Icon';
 import { CountdownInput } from '/@/components/CountDown';
 import { CountdownInput } from '/@/components/CountDown';

+ 0 - 253
src/components/Form/src/components/ImageUpload.vue

@@ -1,253 +0,0 @@
-<template>
-  <div class="clearfix">
-    <a-upload
-      v-model:file-list="fileList"
-      :list-type="listType"
-      :multiple="multiple"
-      :max-count="maxCount"
-      :customRequest="handleCustomRequest"
-      :before-upload="handleBeforeUpload"
-      v-bind="$attrs"
-      @preview="handlePreview"
-      v-model:value="state"
-    >
-      <div v-if="fileList.length < maxCount">
-        <plus-outlined />
-        <div style="margin-top: 8px">
-          {{ t('component.upload.upload') }}
-        </div>
-      </div>
-    </a-upload>
-    <a-modal :open="previewOpen" :footer="null" @cancel="handleCancel">
-      <img alt="example" style="width: 100%" :src="previewImage" />
-    </a-modal>
-  </div>
-</template>
-
-<script lang="ts">
-  import { defineComponent, PropType, reactive, ref, watch } from 'vue';
-  import { message, Modal, Upload, UploadProps } from 'ant-design-vue';
-  import { UploadFile } from 'ant-design-vue/lib/upload/interface';
-  import { useI18n } from '@/hooks/web/useI18n';
-  import { join } from 'lodash-es';
-  import { buildShortUUID } from '@/utils/uuid';
-  import { isArray, isNotEmpty, isUrl } from '@/utils/is';
-  import { useRuleFormItem } from '@/hooks/component/useFormItem';
-  import { useAttrs } from '@vben/hooks';
-  import { PlusOutlined } from '@ant-design/icons-vue';
-
-  type ImageUploadType = 'text' | 'picture' | 'picture-card';
-
-  export default defineComponent({
-    name: 'ImageUpload',
-    components: {
-      PlusOutlined,
-      AUpload: Upload,
-      AModal: Modal,
-    },
-    inheritAttrs: false,
-    props: {
-      value: [Array, String],
-      api: {
-        type: Function as PropType<(file: UploadFile) => Promise<string>>,
-        default: null,
-      },
-      listType: {
-        type: String as PropType<ImageUploadType>,
-        default: () => 'picture-card',
-      },
-      // 文件类型
-      fileType: {
-        type: Array,
-        default: () => ['image/png', 'image/jpeg'],
-      },
-      multiple: {
-        type: Boolean,
-        default: () => false,
-      },
-      // 最大数量的文件
-      maxCount: {
-        type: Number,
-        default: () => 1,
-      },
-      // 最小数量的文件
-      minCount: {
-        type: Number,
-        default: () => 0,
-      },
-      // 文件最大多少MB
-      maxSize: {
-        type: Number,
-        default: () => 2,
-      },
-    },
-    emits: ['change', 'update:value'],
-    setup(props, { emit }) {
-      const attrs = useAttrs();
-      const { t } = useI18n();
-      const previewOpen = ref(false);
-      const previewImage = ref('');
-      const emitData = ref<any[] | any | undefined>();
-      const fileList = ref<UploadFile[]>([]);
-
-      // Embedded in the form, just use the hook binding to perform form verification
-      const [state] = useRuleFormItem(props, 'value', 'change', emitData);
-
-      const fileState = reactive<{
-        newList: any[];
-        newStr: string;
-        oldStr: string;
-      }>({
-        newList: [],
-        newStr: '',
-        oldStr: '',
-      });
-
-      watch(
-        () => fileList.value,
-        (v) => {
-          fileState.newList = v
-            .filter((item: any) => {
-              return item?.url && item.status === 'done' && isUrl(item?.url);
-            })
-            .map((item: any) => item?.url);
-          fileState.newStr = join(fileState.newList);
-          // 不相等代表数据变更
-          if (fileState.newStr !== fileState.oldStr) {
-            fileState.oldStr = fileState.newStr;
-            emitData.value = props.multiple ? fileState.newList : fileState.newStr;
-            state.value = props.multiple ? fileState.newList : fileState.newStr;
-          }
-        },
-        {
-          deep: true,
-        },
-      );
-
-      watch(
-        () => state.value,
-        (v) => {
-          changeFileValue(v);
-          emit('update:value', v);
-        },
-      );
-
-      function changeFileValue(value: any) {
-        const stateStr = props.multiple ? join((value as string[]) || []) : value || '';
-        if (stateStr !== fileState.oldStr) {
-          fileState.oldStr = stateStr;
-          let list: string[] = [];
-          if (props.multiple) {
-            if (isNotEmpty(value)) {
-              if (isArray(value)) {
-                list = value as string[];
-              } else {
-                list.push(value as string);
-              }
-            }
-          } else {
-            if (isNotEmpty(value)) {
-              list.push(value as string);
-            }
-          }
-          fileList.value = list.map((item) => {
-            const uuid = buildShortUUID();
-            return {
-              uid: uuid,
-              name: uuid,
-              status: 'done',
-              url: item,
-            };
-          });
-        }
-      }
-
-      /** 关闭查看 */
-      const handleCancel = () => {
-        previewOpen.value = false;
-      };
-
-      /** 查看图片 */
-      // @ts-ignore
-      const handlePreview = async (file: UploadProps['fileList'][number]) => {
-        if (!file.url && !file.preview) {
-          file.preview = (await getBase64(file.originFileObj)) as string;
-        }
-        previewImage.value = file.url || file.preview;
-        previewOpen.value = true;
-      };
-
-      /** 上传前校验 */
-      const handleBeforeUpload: UploadProps['beforeUpload'] = (file) => {
-        if (fileList.value.length > props.maxCount) {
-          fileList.value.splice(props.maxCount, fileList.value.length - props.maxCount);
-          message.error(t('component.upload.maxNumber', [props.maxCount]));
-          return Upload.LIST_IGNORE;
-        }
-        const isPNG = props.fileType.includes(file.type);
-        if (!isPNG) {
-          message.error(t('component.upload.acceptUpload', [props.fileType.toString()]));
-        }
-        const isLt2M = file.size / 1024 / 1024 < props.maxSize;
-        if (!isLt2M) {
-          message.error(t('component.upload.maxSizeMultiple', [props.maxSize]));
-        }
-        if (!(isPNG && isLt2M)) {
-          fileList.value.pop();
-        }
-        return (isPNG && isLt2M) || Upload.LIST_IGNORE;
-      };
-
-      /** 自定义上传 */
-      const handleCustomRequest = async (option: any) => {
-        const { file } = option;
-        await props
-          .api(option)
-          .then((url) => {
-            file.url = url;
-            file.status = 'done';
-            fileList.value.pop();
-            fileList.value.push(file);
-          })
-          .catch(() => {
-            fileList.value.pop();
-          });
-      };
-
-      function getBase64(file: File) {
-        return new Promise((resolve, reject) => {
-          const reader = new FileReader();
-          reader.readAsDataURL(file);
-          reader.onload = () => resolve(reader.result);
-          reader.onerror = (error) => reject(error);
-        });
-      }
-
-      return {
-        previewOpen,
-        fileList,
-        state,
-        attrs,
-        t,
-        handlePreview,
-        handleBeforeUpload,
-        handleCustomRequest,
-        handleCancel,
-        previewImage,
-      };
-    },
-  });
-</script>
-
-<style scoped>
-  /* you can make up upload button and sample style by using stylesheets */
-  .ant-upload-select-picture-card i {
-    color: #999;
-    font-size: 32px;
-  }
-
-  .ant-upload-select-picture-card .ant-upload-text {
-    margin-top: 8px;
-    color: #666;
-  }
-</style>

+ 1 - 1
src/components/Modal/src/index.less

@@ -82,7 +82,7 @@
     display: inline-block;
     display: inline-block;
     width: 96px;
     width: 96px;
     height: 56px;
     height: 56px;
-    line-height: 56px;
+    line-height: 56px !important;
   }
   }
 
 
   &-confirm-body {
   &-confirm-body {

+ 2 - 0
src/components/Upload/index.ts

@@ -1,4 +1,6 @@
 import { withInstall } from '/@/utils';
 import { withInstall } from '/@/utils';
 import basicUpload from './src/BasicUpload.vue';
 import basicUpload from './src/BasicUpload.vue';
+import uploadImage from './src/components/ImageUpload.vue';
 
 
+export const ImageUpload = withInstall(uploadImage);
 export const BasicUpload = withInstall(basicUpload);
 export const BasicUpload = withInstall(basicUpload);

+ 2 - 2
src/components/Upload/src/BasicUpload.vue

@@ -47,8 +47,8 @@
   import { omit } from 'lodash-es';
   import { omit } from 'lodash-es';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { isArray } from '/@/utils/is';
   import { isArray } from '/@/utils/is';
-  import UploadModal from './UploadModal.vue';
-  import UploadPreviewModal from './UploadPreviewModal.vue';
+  import UploadModal from './components/UploadModal.vue';
+  import UploadPreviewModal from './components/UploadPreviewModal.vue';
 
 
   export default defineComponent({
   export default defineComponent({
     name: 'BasicUpload',
     name: 'BasicUpload',

+ 1 - 1
src/components/Upload/src/FileList.vue → src/components/Upload/src/components/FileList.vue

@@ -1,5 +1,5 @@
 <script lang="tsx">
 <script lang="tsx">
-  import { fileListProps } from './props';
+  import { fileListProps } from '../props';
   import { isFunction, isDef } from '/@/utils/is';
   import { isFunction, isDef } from '/@/utils/is';
   import { useSortable } from '/@/hooks/web/useSortable';
   import { useSortable } from '/@/hooks/web/useSortable';
   import { useModalContext } from '/@/components/Modal/src/hooks/useModalContext';
   import { useModalContext } from '/@/components/Modal/src/hooks/useModalContext';

+ 161 - 0
src/components/Upload/src/components/ImageUpload.vue

@@ -0,0 +1,161 @@
+<template>
+  <div>
+    <Upload
+      v-bind="$attrs"
+      v-model:file-list="fileList"
+      :list-type="listType"
+      :accept="getStringAccept"
+      :before-upload="beforeUpload"
+      :custom-request="customRequest"
+      @preview="handlePreview"
+      @remove="handleRemove"
+    >
+      <div v-if="fileList && fileList.length < maxNumber">
+        <plus-outlined />
+        <div style="margin-top: 8px">{{ t('component.upload.upload') }}</div>
+      </div>
+    </Upload>
+    <Modal :open="previewOpen" :title="previewTitle" :footer="null" @cancel="handleCancel">
+      <img alt="" style="width: 100%" :src="previewImage" />
+    </Modal>
+  </div>
+</template>
+
+<script lang="ts" setup name="ImageUpload">
+  import { ref, toRefs, watch } from 'vue';
+  import { PlusOutlined } from '@ant-design/icons-vue';
+  import { Upload, Modal } from 'ant-design-vue';
+  import type { UploadProps } from 'ant-design-vue';
+  import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
+  import { useMessage } from '@/hooks/web/useMessage';
+  import { isArray, isFunction } from '@/utils/is';
+  import { warn } from '@/utils/log';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useUploadType } from '../hooks/useUpload';
+  import { uploadContainerProps } from '../props';
+  import { isImgTypeByName } from '../helper';
+
+  const emit = defineEmits(['change', 'update:value', 'delete']);
+  const props = defineProps({
+    ...uploadContainerProps,
+  });
+  const { t } = useI18n();
+  const { createMessage } = useMessage();
+  const { accept, helpText, maxNumber, maxSize } = toRefs(props);
+  const { getStringAccept } = useUploadType({
+    acceptRef: accept,
+    helpTextRef: helpText,
+    maxNumberRef: maxNumber,
+    maxSizeRef: maxSize,
+  });
+  const previewOpen = ref<boolean>(false);
+  const previewImage = ref<string>('');
+  const previewTitle = ref<string>('');
+
+  const fileList = ref<UploadProps['fileList']>([]);
+  const isLtMsg = ref<boolean>(true);
+  const isActMsg = ref<boolean>(true);
+
+  watch(
+    () => props.value,
+    (v) => {
+      if (isArray(v)) {
+        fileList.value = v.map((url, i) => ({
+          uid: String(-i),
+          name: url ? url.substring(url.lastIndexOf('/') + 1) : 'image.png',
+          status: 'done',
+          url,
+        }));
+      }
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+
+  function getBase64(file: File) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.readAsDataURL(file);
+      reader.onload = () => resolve(reader.result);
+      reader.onerror = (error) => reject(error);
+    });
+  }
+
+  const handlePreview = async (file: UploadProps['fileList'][number]) => {
+    if (!file.url && !file.preview) {
+      file.preview = (await getBase64(file.originFileObj)) as string;
+    }
+    previewImage.value = file.url || file.preview;
+    previewOpen.value = true;
+    previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
+  };
+
+  const handleRemove = async (file: UploadProps['fileList'][number]) => {
+    if (fileList.value) {
+      const index = fileList.value.findIndex((item: any) => item.uuid === file.uuid);
+      index !== -1 && fileList.value.splice(index, 1);
+      emit('change', fileList.value);
+      emit('delete', file);
+    }
+  };
+
+  const handleCancel = () => {
+    previewOpen.value = false;
+    previewTitle.value = '';
+  };
+
+  const beforeUpload = (file: File) => {
+    const { maxSize, accept } = props;
+    const { name } = file;
+    isActMsg.value = isImgTypeByName(name);
+    if (!isActMsg.value) {
+      createMessage.error(t('component.upload.acceptUpload', [accept]));
+      isActMsg.value = false;
+      // 防止弹出多个错误提示
+      setTimeout(() => (isActMsg.value = true), 1000);
+    }
+    isLtMsg.value = file.size / 1024 / 1024 > maxSize;
+    if (isLtMsg.value) {
+      createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
+      isLtMsg.value = false;
+      // 防止弹出多个错误提示
+      setTimeout(() => (isLtMsg.value = true), 1000);
+    }
+    return (isActMsg.value && !isLtMsg.value) || Upload.LIST_IGNORE;
+  };
+
+  async function customRequest(info: UploadRequestOption<any>) {
+    const { api } = props;
+    if (!api || !isFunction(api)) {
+      return warn('upload api must exist and be a function');
+    }
+    try {
+      const res = await props.api?.({
+        data: {
+          ...(props.uploadParams || {}),
+        },
+        file: info.file,
+        name: props.name,
+        filename: props.filename,
+      });
+      info.onSuccess!(res.data);
+      emit('change', fileList.value);
+    } catch (e: any) {
+      info.onError!(e);
+    }
+  }
+</script>
+
+<style lang="less">
+  .ant-upload-select-picture-card i {
+    color: #999;
+    font-size: 32px;
+  }
+
+  .ant-upload-select-picture-card .ant-upload-text {
+    margin-top: 8px;
+    color: #666;
+  }
+</style>

+ 0 - 0
src/components/Upload/src/ThumbUrl.vue → src/components/Upload/src/components/ThumbUrl.vue


+ 8 - 8
src/components/Upload/src/UploadModal.vue → src/components/Upload/src/components/UploadModal.vue

@@ -53,14 +53,14 @@
   import { Upload, Alert } from 'ant-design-vue';
   import { Upload, Alert } from 'ant-design-vue';
   import { BasicModal, useModalInner } from '/@/components/Modal';
   import { BasicModal, useModalInner } from '/@/components/Modal';
   // hooks
   // hooks
-  import { useUploadType } from './useUpload';
+  import { useUploadType } from '../hooks/useUpload';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useMessage } from '/@/hooks/web/useMessage';
   //   types
   //   types
-  import { FileItem, UploadResultStatus } from './typing';
-  import { basicProps } from './props';
+  import { FileItem, UploadResultStatus } from '../types/typing';
+  import { basicProps } from '../props';
   import { createTableColumns, createActionColumn } from './data';
   import { createTableColumns, createActionColumn } from './data';
   // utils
   // utils
-  import { checkImgType, getBase64WithFile } from './helper';
+  import { checkImgType, getBase64WithFile } from '../helper';
   import { buildUUID } from '/@/utils/uuid';
   import { buildUUID } from '/@/utils/uuid';
   import { isFunction } from '/@/utils/is';
   import { isFunction } from '/@/utils/is';
   import { warn } from '/@/utils/log';
   import { warn } from '/@/utils/log';
@@ -193,7 +193,7 @@
           );
           );
           const { data } = ret;
           const { data } = ret;
           item.status = UploadResultStatus.SUCCESS;
           item.status = UploadResultStatus.SUCCESS;
-          item.responseData = data;
+          item.response = data;
           return {
           return {
             success: true,
             success: true,
             error: null,
             error: null,
@@ -247,9 +247,9 @@
         const fileList: string[] = [];
         const fileList: string[] = [];
 
 
         for (const item of fileListRef.value) {
         for (const item of fileListRef.value) {
-          const { status, responseData } = item;
-          if (status === UploadResultStatus.SUCCESS && responseData) {
-            fileList.push(responseData.url);
+          const { status, response } = item;
+          if (status === UploadResultStatus.SUCCESS && response) {
+            fileList.push(response.url);
           }
           }
         }
         }
         // 存在一个上传成功的即可保存
         // 存在一个上传成功的即可保存

+ 2 - 2
src/components/Upload/src/UploadPreviewModal.vue → src/components/Upload/src/components/UploadPreviewModal.vue

@@ -14,8 +14,8 @@
   import { defineComponent, watch, ref } from 'vue';
   import { defineComponent, watch, ref } from 'vue';
   import FileList from './FileList.vue';
   import FileList from './FileList.vue';
   import { BasicModal, useModalInner } from '/@/components/Modal';
   import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { previewProps } from './props';
-  import { PreviewFileItem } from './typing';
+  import { previewProps } from '../props';
+  import { PreviewFileItem } from '../types/typing';
   import { downloadByUrl } from '/@/utils/file/download';
   import { downloadByUrl } from '/@/utils/file/download';
   import { createPreviewColumns, createPreviewActionColumn } from './data';
   import { createPreviewColumns, createPreviewActionColumn } from './data';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useI18n } from '/@/hooks/web/useI18n';

+ 2 - 2
src/components/Upload/src/data.tsx → src/components/Upload/src/components/data.tsx

@@ -1,6 +1,6 @@
 import type { BasicColumn, ActionItem } from '/@/components/Table';
 import type { BasicColumn, ActionItem } from '/@/components/Table';
-import { FileBasicColumn, FileItem, PreviewFileItem, UploadResultStatus } from './typing';
-import { isImgTypeByName } from './helper';
+import { FileBasicColumn, FileItem, PreviewFileItem, UploadResultStatus } from '../types/typing';
+import { isImgTypeByName } from '../helper';
 import { Progress, Tag } from 'ant-design-vue';
 import { Progress, Tag } from 'ant-design-vue';
 import TableAction from '/@/components/Table/src/components/TableAction.vue';
 import TableAction from '/@/components/Table/src/components/TableAction.vue';
 import ThumbUrl from './ThumbUrl.vue';
 import ThumbUrl from './ThumbUrl.vue';

+ 0 - 0
src/components/Upload/src/useUpload.ts → src/components/Upload/src/hooks/useUpload.ts


+ 7 - 1
src/components/Upload/src/props.ts

@@ -1,5 +1,5 @@
 import type { PropType } from 'vue';
 import type { PropType } from 'vue';
-import { FileBasicColumn } from './typing';
+import { FileBasicColumn } from './types/typing';
 
 
 import type { Options } from 'sortablejs';
 import type { Options } from 'sortablejs';
 
 
@@ -13,7 +13,13 @@ type SortableOptions = Merge<
   }
   }
 >;
 >;
 
 
+type ListType = 'text' | 'picture' | 'picture-card';
+
 export const basicProps = {
 export const basicProps = {
+  listType: {
+    type: String as PropType<ListType>,
+    default: 'picture-card',
+  },
   helpText: {
   helpText: {
     type: String as PropType<string>,
     type: String as PropType<string>,
     default: '',
     default: '',

+ 2 - 2
src/components/Upload/src/typing.ts → src/components/Upload/src/types/typing.ts

@@ -1,4 +1,4 @@
-import { BasicColumn } from '../../Table';
+import { BasicColumn } from '/@/components/Table';
 import { UploadApiResult } from '/@/api/sys/model/uploadModel';
 import { UploadApiResult } from '/@/api/sys/model/uploadModel';
 
 
 export enum UploadResultStatus {
 export enum UploadResultStatus {
@@ -15,7 +15,7 @@ export interface FileItem {
   percent: number;
   percent: number;
   file: File;
   file: File;
   status?: UploadResultStatus;
   status?: UploadResultStatus;
-  responseData?: UploadApiResult;
+  response?: UploadApiResult;
   uuid: string;
   uuid: string;
 }
 }
 
 

+ 24 - 6
src/views/demo/form/index.vue

@@ -70,6 +70,7 @@
   import { cloneDeep } from 'lodash-es';
   import { cloneDeep } from 'lodash-es';
   import { areaRecord } from '/@/api/demo/cascader';
   import { areaRecord } from '/@/api/demo/cascader';
   import { uploadApi } from '/@/api/sys/upload';
   import { uploadApi } from '/@/api/sys/upload';
+  // import { isArray } from '/@/utils/is';
 
 
   const valueSelectA = ref<string[]>([]);
   const valueSelectA = ref<string[]>([]);
   const valueSelectB = ref<string[]>([]);
   const valueSelectB = ref<string[]>([]);
@@ -743,13 +744,30 @@
     {
     {
       field: 'field23',
       field: 'field23',
       component: 'ImageUpload',
       component: 'ImageUpload',
-      label: '字段23',
-      colProps: {
-        span: 8,
-      },
+      label: '上传图片',
+      required: true,
+      defaultValue: [
+        'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+      ],
       componentProps: {
       componentProps: {
-        api: () => Promise.resolve('https://via.placeholder.com/600/92c952'),
-      },
+        api: uploadApi,
+        accept: ['png', 'jpeg', 'jpg'],
+        maxSize: 2,
+        maxNumber: 1,
+      },
+      // rules: [
+      //   {
+      //     required: true,
+      //     trigger: 'change',
+      //     validator(_, value) {
+      //       if (isArray(value) && value.length > 0) {
+      //         return Promise.resolve();
+      //       } else {
+      //         return Promise.reject('请选择上传图片');
+      //       }
+      //     },
+      //   },
+      // ],
     },
     },
   ];
   ];