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

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

* fix(upload): 显式传入handleDelete

* update case
This commit is contained in:
Electrolux 2024-05-31 09:54:05 +08:00 committed by GitHub
parent 0bc01d8528
commit d9cdf3f034
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 116 additions and 55 deletions

View File

@ -54,10 +54,11 @@
import { uploadContainerProps } from './props'; import { uploadContainerProps } from './props';
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, isObject, isString } from '@/utils/is';
import UploadModal from './components/UploadModal.vue'; import UploadModal from './components/UploadModal.vue';
import UploadPreviewModal from './components/UploadPreviewModal.vue'; import UploadPreviewModal from './components/UploadPreviewModal.vue';
import { BaseFileItem } from './types/typing';
import { buildUUID } from '@/utils/uuid';
defineOptions({ name: 'BasicUpload' }); defineOptions({ name: 'BasicUpload' });
const props = defineProps(uploadContainerProps); const props = defineProps(uploadContainerProps);
@ -72,7 +73,7 @@
// modal // modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal(); const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const fileList = ref<string[]>([]); const fileList = ref<BaseFileItem[] | any[]>([]);
const showPreview = computed(() => { const showPreview = computed(() => {
const { emptyHidePreview } = props; const { emptyHidePreview } = props;
@ -84,27 +85,64 @@
const value = { ...attrs, ...props }; const value = { ...attrs, ...props };
return omit(value, 'onChange'); 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( watch(
() => props.value, () => props.value,
(value = []) => { (v = []) => {
fileList.value = isArray(value) ? value : []; 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 }, { immediate: true },
); );
// modal // modal
function handleChange(urls: string[]) { function handleChange(urls: string[],valueKey:string) {
fileList.value = [...unref(fileList), ...(urls || [])]; fileList.value = [...unref(fileList), ...(genFileListByUrls(urls) || [])];
emit('update:value', fileList.value); const values = getValue(valueKey);
emit('change', fileList.value); emit('update:value', values);
emit('change', values);
} }
// modal // modal
function handlePreviewChange(urls: string[]) { function handlePreviewChange(fileItems: string[],valueKey:string) {
fileList.value = [...(urls || [])]; fileList.value = [...(fileItems || [])];
emit('update:value', fileList.value); const values = getValue(valueKey);
emit('change', fileList.value); emit('update:value', values);
emit('change', values);
} }
function handleDelete(record: Recordable<any>) { function handleDelete(record: Recordable<any>) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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