Browse Source

chore(upload): use uid && fix handleDelete (#3872)

* chore(upload): 重构组件,添加key作为标识

* fix(upload): 显式传入handleDelete

* update case
Electrolux 1 year ago
parent
commit
d9cdf3f034

+ 52 - 14
src/components/Upload/src/BasicUpload.vue

@@ -54,10 +54,11 @@
   import { uploadContainerProps } from './props';
   import { omit } from 'lodash-es';
   import { useI18n } from '@/hooks/web/useI18n';
-  import { isArray } from '@/utils/is';
+  import { isArray, isObject, isString } from '@/utils/is';
   import UploadModal from './components/UploadModal.vue';
   import UploadPreviewModal from './components/UploadPreviewModal.vue';
-
+  import { BaseFileItem } from './types/typing';
+  import { buildUUID } from '@/utils/uuid';
   defineOptions({ name: 'BasicUpload' });
 
   const props = defineProps(uploadContainerProps);
@@ -72,7 +73,7 @@
   //   预览modal
   const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
 
-  const fileList = ref<string[]>([]);
+  const fileList = ref<BaseFileItem[] | any[]>([]);
 
   const showPreview = computed(() => {
     const { emptyHidePreview } = props;
@@ -84,27 +85,64 @@
     const value = { ...attrs, ...props };
     return omit(value, 'onChange');
   });
-
+  function getValue(valueKey="url") {
+    const list = (fileList.value || []).map((item: any) => {
+      return item[valueKey];
+    });
+    return list;
+  }
+  function genFileListByUrls(urls: string[]) {
+    const list = urls.map((e) => {
+      return {
+        uid: buildUUID(),
+        url: e,
+      };
+    });
+    return list;
+  }
   watch(
     () => props.value,
-    (value = []) => {
-      fileList.value = isArray(value) ? value : [];
+    (v = []) => {
+      let values: string[] = [];
+      if (v) {
+        if (isArray(v)) {
+          values = v;
+        } else if (typeof v == 'string') {
+          values.push(v);
+        }
+        fileList.value = values.map((item,i) => {
+          if (item && isString(item)) {
+            return {
+              uid: buildUUID(),
+              url: item,
+            };
+          } else if (item && isObject(item)) {
+            return item;
+          } else {
+            return;
+          }
+        }) as any;
+      }
+      emit('update:value', values);
+      emit('change', values);
     },
     { immediate: true },
   );
 
   // 上传modal保存操作
-  function handleChange(urls: string[]) {
-    fileList.value = [...unref(fileList), ...(urls || [])];
-    emit('update:value', fileList.value);
-    emit('change', fileList.value);
+  function handleChange(urls: string[],valueKey:string) {
+    fileList.value = [...unref(fileList), ...(genFileListByUrls(urls) || [])];
+    const values = getValue(valueKey);
+    emit('update:value', values);
+    emit('change', values);
   }
 
   // 预览modal保存操作
-  function handlePreviewChange(urls: string[]) {
-    fileList.value = [...(urls || [])];
-    emit('update:value', fileList.value);
-    emit('change', fileList.value);
+  function handlePreviewChange(fileItems: string[],valueKey:string) {
+    fileList.value = [...(fileItems || [])];
+    const values = getValue(valueKey);
+    emit('update:value', values);
+    emit('change', values);
   }
 
   function handleDelete(record: Recordable<any>) {

+ 1 - 1
src/components/Upload/src/components/UploadModal.vue

@@ -71,7 +71,7 @@
   const props = defineProps({
     ...basicProps,
     previewFileList: {
-      type: Array as PropType<string[]>,
+      type: Array as PropType<string[] | any[]>,
       default: () => [],
     },
   });

+ 20 - 21
src/components/Upload/src/components/UploadPreviewModal.vue

@@ -14,15 +14,15 @@
   import { watch, ref } from 'vue';
   import FileList from './FileList.vue';
   import { BasicModal, useModalInner } from '@/components/Modal';
-  import { previewProps } from '../props';
-  import { FileBasicColumn, PreviewFileItem } from '../types/typing';
+  import { handleFnKey, previewProps } from '../props';
+  import { BaseFileItem, FileBasicColumn, PreviewFileItem } from '../types/typing';
   import { downloadByUrl } from '@/utils/file/download';
   import { createPreviewColumns, createPreviewActionColumn } from './data';
   import { useI18n } from '@/hooks/web/useI18n';
   import { isArray, isFunction } from '@/utils/is';
   import { BasicColumn } from '@/components/Table';
   import { useMessage } from '@/hooks/web/useMessage';
-
+  import { buildUUID } from '@/utils/uuid';
   const { createMessage } = useMessage();
 
   const props = defineProps(previewProps);
@@ -35,7 +35,7 @@
   const [register] = useModalInner();
   const { t } = useI18n();
 
-  const fileListRef = ref<PreviewFileItem[] | Array<any>>([]);
+  const fileListRef = ref<BaseFileItem[] | Array<any>>([]);
   watch(
     () => props.previewColumns,
     () => {
@@ -64,14 +64,15 @@
       fileListRef.value = value
         .filter((item) => !!item)
         .map((item) => {
-          if (typeof item != 'string') {
-            console.error('return value should be string');
+          if (typeof item != 'object') {
+            console.error('return value should be object');
             return;
           }
           return {
-            url: item,
-            type: item.split('.').pop() || '',
-            name: item.split('/').pop() || '',
+            uid: item?.uid,
+            url: item?.url,
+            type: item?.url?.split('.').pop() || '',
+            name: item?.url?.split('/').pop() || '',
           };
         });
     },
@@ -79,28 +80,26 @@
   );
 
   // 删除
-  function handleRemove(record: PreviewFileItem | Record<string, any>, urlKey = 'url') {
-    const index = fileListRef.value.findIndex((item) => item[urlKey] === record[urlKey]);
+  function handleRemove(obj: Record<handleFnKey, any>) {
+    let { record = {}, valueKey = 'url', uidKey = 'uid' } = obj;
+    const index = fileListRef.value.findIndex((item) => item[uidKey] === record[uidKey]);
     if (index !== -1) {
       const removed = fileListRef.value.splice(index, 1);
-      emit('delete', removed[0][urlKey]);
-      emit(
-        'list-change',
-        fileListRef.value.map((item) => item[urlKey]),
-      );
+      emit('delete', removed[0][uidKey]);
+      emit('list-change', fileListRef.value, valueKey);
     }
   }
   // 添加
-  function handleAdd(record: PreviewFileItem | Record<string, any>, urlKey = 'url') {
+  function handleAdd(obj: Record<handleFnKey, any>) {
+    let { record = {}, valueKey = 'url', uidKey = 'uid' } = obj;
     const { maxNumber } = props;
     if (fileListRef.value.length + fileListRef.value.length > maxNumber) {
       return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
     }
+    record[uidKey] = record[uidKey] ?? buildUUID();
+    record[valueKey] = record[valueKey];
     fileListRef.value = [...fileListRef.value, record];
-    emit(
-      'list-change',
-      fileListRef.value.map((item) => item[urlKey]),
-    );
+    emit('list-change', fileListRef.value, valueKey);
   }
   // 下载
   function handleDownload(record: PreviewFileItem) {

+ 12 - 3
src/components/Upload/src/components/data.tsx

@@ -5,6 +5,7 @@ import { Progress, Tag } from 'ant-design-vue';
 import TableAction from '@/components/Table/src/components/TableAction.vue';
 import ThumbUrl from './ThumbUrl.vue';
 import { useI18n } from '@/hooks/web/useI18n';
+import { previewColumnsFnType } from '../props';
 
 const { t } = useI18n();
 
@@ -81,7 +82,11 @@ export function createActionColumn(handleRemove: Function): FileBasicColumn {
         {
           label: t('component.upload.del'),
           color: 'error',
-          onClick: handleRemove.bind(null, record),
+          onClick: handleRemove.bind(null, {
+            record,
+            uidKey: 'uid',
+            valueKey: 'url',
+          }),
         },
       ];
       return <TableAction actions={actions} outside={true} />;
@@ -112,7 +117,7 @@ export function createPreviewActionColumn({
   handleRemove,
   handleDownload,
 }: {
-  handleRemove: Fn;
+  handleRemove: previewColumnsFnType['handleRemove'];
   handleDownload: Fn;
 }): BasicColumn {
   return {
@@ -125,7 +130,11 @@ export function createPreviewActionColumn({
         {
           label: t('component.upload.del'),
           color: 'error',
-          onClick: handleRemove.bind(null, record),
+          onClick: handleRemove.bind(null, {
+            record,
+            uidKey: 'uid',
+            valueKey: 'url',
+          }),
         },
         {
           label: t('component.upload.download'),

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

@@ -1,5 +1,5 @@
 import type { PropType } from 'vue';
-import { FileBasicColumn } from './types/typing';
+import { BaseFileItem, FileBasicColumn } from './types/typing';
 
 import type { Options } from 'sortablejs';
 
@@ -14,9 +14,10 @@ type SortableOptions = Merge<
     // ...可扩展
   }
 >;
-type previewColumnsFnType = {
-  handleRemove: (record: Record<string, any>, key: string) => any;
-  handleAdd: (record: Record<string, any>, key: string) => any;
+export type handleFnKey = "record" | "valueKey" | "uidKey"
+export type previewColumnsFnType = {
+  handleRemove: (record: Record<handleFnKey, any>) => any;
+  handleAdd: (record: Record<handleFnKey, any>) => any;
 };
 export const previewType = {
   previewColumns: {
@@ -26,7 +27,7 @@ export const previewType = {
     required: false,
   },
   beforePreviewData: {
-    type: Function as PropType<(arg: string[]) => Recordable<any>>,
+    type: Function as PropType<(arg: BaseFileItem[] | any) => Recordable<any>>,
     default: null,
     required: false,
   },
@@ -112,7 +113,7 @@ export const uploadContainerProps = {
 
 export const previewProps = {
   value: {
-    type: Array as PropType<string[]>,
+    type: Array as PropType<BaseFileItem[] | any[]>,
     default: () => [],
   },
   maxNumber: {

+ 5 - 0
src/components/Upload/src/types/typing.ts

@@ -20,6 +20,11 @@ export interface FileItem {
   uuid: string;
 }
 
+export interface BaseFileItem {
+  uid: string | number;
+  url: string;
+  name?: string;
+}
 export interface PreviewFileItem {
   url: string;
   name: string;

+ 19 - 10
src/views/demo/comp/upload/Upload4.vue

@@ -106,10 +106,14 @@
                       type: 'primary',
                       style: 'margin:4px',
                       onclick: () => {
-                        handleAdd(
-                          { url6: 'https://vebn.oss-cn-beijing.aliyuncs.com/vben/logo.png' },
-                          'url6',
-                        );
+                        handleAdd({
+                          record: {
+                            id6: new Date().getTime(),
+                            url6: 'https://vebn.oss-cn-beijing.aliyuncs.com/vben/logo.png',
+                          },
+                          uidKey: 'id6',
+                          valueKey: 'url6',
+                        });
                       },
                     },
                     () => '点我新增',
@@ -119,7 +123,11 @@
                     {
                       danger: true,
                       onclick: () => {
-                        handleRemove({ url6: record.url6 }, 'url6');
+                        handleRemove({
+                          record: { url6: record.url6 },
+                          uidKey: 'url6',
+                          valueKey: 'url6',
+                        });
                       },
                     },
                     () => '点我删除',
@@ -133,14 +141,15 @@
           let data = arg
             .filter((item) => !!item)
             .map((item) => {
-              if (typeof item !== 'string') {
-                console.error('return value should be string');
+              if (typeof item !== 'object') {
+                console.error('return value should be object');
                 return;
               }
               return {
-                url6: item,
-                type6: item.split('.').pop() || '',
-                name6: item.split('/').pop() || '',
+                uid: item?.uid,
+                url6: item?.url,
+                type6: item?.url?.split('.').pop() || '',
+                name6: item?.url?.split('/').pop() || '',
               };
             });
           return data;