feat(upload prop:maxSize): from Upload component accept prop maxSize (AI prompt fixed) (#7059)
* feat(upload prop:maxSize): from component accept prop maxSize * feat(upload prop:maxSize): from component accept prop maxSize * feat(upload prop:maxSize): from component accept prop maxSize * feat(upload prop:maxSize): from component accept prop maxSize
This commit is contained in:
parent
7d2bc2e885
commit
81a61558cb
@ -29,7 +29,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { notification } from 'ant-design-vue';
|
import { message, notification } from 'ant-design-vue';
|
||||||
|
|
||||||
const AutoComplete = defineAsyncComponent(
|
const AutoComplete = defineAsyncComponent(
|
||||||
() => import('ant-design-vue/es/auto-complete'),
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
@ -119,9 +119,149 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const withPreviewUpload = () => {
|
const withPreviewUpload = () => {
|
||||||
|
// 创建默认的上传按钮插槽
|
||||||
|
const createDefaultSlotsWithUpload = (
|
||||||
|
listType: string,
|
||||||
|
placeholder: string,
|
||||||
|
) => {
|
||||||
|
switch (listType) {
|
||||||
|
case 'picture-card': {
|
||||||
|
return {
|
||||||
|
default: () => placeholder,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
icon: h(IconifyIcon, {
|
||||||
|
icon: 'ant-design:upload-outlined',
|
||||||
|
class: 'mb-1 size-4',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
() => placeholder,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 构建预览图片组
|
||||||
|
const previewImage = async (
|
||||||
|
file: UploadFile,
|
||||||
|
visible: Ref<boolean>,
|
||||||
|
fileList: Ref<UploadProps['fileList']>,
|
||||||
|
) => {
|
||||||
|
// 检查是否为图片文件的辅助函数
|
||||||
|
const isImageFile = (file: UploadFile): boolean => {
|
||||||
|
const imageExtensions = new Set([
|
||||||
|
'bmp',
|
||||||
|
'gif',
|
||||||
|
'jpeg',
|
||||||
|
'jpg',
|
||||||
|
'png',
|
||||||
|
'webp',
|
||||||
|
]);
|
||||||
|
if (file.url) {
|
||||||
|
const ext = file.url?.split('.').pop()?.toLowerCase();
|
||||||
|
return ext ? imageExtensions.has(ext) : false;
|
||||||
|
}
|
||||||
|
if (!file.type) {
|
||||||
|
const ext = file.name?.split('.').pop()?.toLowerCase();
|
||||||
|
return ext ? imageExtensions.has(ext) : false;
|
||||||
|
}
|
||||||
|
return file.type.startsWith('image/');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果当前文件不是图片,直接打开
|
||||||
|
if (!isImageFile(file)) {
|
||||||
|
if (file.url) {
|
||||||
|
window.open(file.url, '_blank');
|
||||||
|
} else if (file.preview) {
|
||||||
|
window.open(file.preview, '_blank');
|
||||||
|
} else {
|
||||||
|
message.error($t('ui.formRules.previewWarning'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于图片文件,继续使用预览组
|
||||||
|
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
|
||||||
|
Image,
|
||||||
|
PreviewGroup,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getBase64 = (file: File) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.addEventListener('load', () => resolve(reader.result));
|
||||||
|
reader.addEventListener('error', (error) => reject(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 从fileList中过滤出所有图片文件
|
||||||
|
const imageFiles = (unref(fileList) || []).filter((element) =>
|
||||||
|
isImageFile(element),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 为所有没有预览地址的图片生成预览
|
||||||
|
for (const imgFile of imageFiles) {
|
||||||
|
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
|
||||||
|
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const container: HTMLElement | null = document.createElement('div');
|
||||||
|
document.body.append(container);
|
||||||
|
|
||||||
|
// 用于追踪组件是否已卸载
|
||||||
|
let isUnmounted = false;
|
||||||
|
|
||||||
|
const PreviewWrapper = {
|
||||||
|
setup() {
|
||||||
|
return () => {
|
||||||
|
if (isUnmounted) return null;
|
||||||
|
return h(
|
||||||
|
PreviewGroupComponent,
|
||||||
|
{
|
||||||
|
class: 'hidden',
|
||||||
|
preview: {
|
||||||
|
visible: visible.value,
|
||||||
|
// 设置初始显示的图片索引
|
||||||
|
current: imageFiles.findIndex((f) => f.uid === file.uid),
|
||||||
|
onVisibleChange: (value: boolean) => {
|
||||||
|
visible.value = value;
|
||||||
|
if (!value) {
|
||||||
|
// 延迟清理,确保动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isUnmounted && container) {
|
||||||
|
isUnmounted = true;
|
||||||
|
render(null, container);
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
// 渲染所有图片文件
|
||||||
|
imageFiles.map((imgFile) =>
|
||||||
|
h(ImageComponent, {
|
||||||
|
key: imgFile.uid,
|
||||||
|
src: imgFile.url || imgFile.preview,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(h(PreviewWrapper), container);
|
||||||
|
};
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
name: Upload.name,
|
name: Upload.name,
|
||||||
emits: ['change', 'update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
setup: (
|
setup: (
|
||||||
props: any,
|
props: any,
|
||||||
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
|
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
|
||||||
@ -136,9 +276,19 @@ const withPreviewUpload = () => {
|
|||||||
attrs?.fileList || attrs?.['file-list'] || [],
|
attrs?.fileList || attrs?.['file-list'] || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleBeforeUpload = (file: UploadFile) => {
|
||||||
|
if (attrs.maxSize && (file.size || 0) / 1024 / 1024 > attrs.maxSize) {
|
||||||
|
message.error($t('ui.formRules.sizeLimit', [attrs.maxSize]));
|
||||||
|
file.status = 'removed';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return attrs.beforeUpload?.(file) ?? true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = async (event: UploadChangeParam) => {
|
const handleChange = async (event: UploadChangeParam) => {
|
||||||
fileList.value = event.fileList;
|
fileList.value = event.fileList.filter(
|
||||||
emit('change', event);
|
(file) => file.status !== 'removed',
|
||||||
|
);
|
||||||
emit(
|
emit(
|
||||||
'update:modelValue',
|
'update:modelValue',
|
||||||
event.fileList?.length ? fileList.value : undefined,
|
event.fileList?.length ? fileList.value : undefined,
|
||||||
@ -179,6 +329,7 @@ const withPreviewUpload = () => {
|
|||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
fileList: fileList.value,
|
fileList: fileList.value,
|
||||||
|
beforeUpload: handleBeforeUpload,
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
onPreview: handlePreview,
|
onPreview: handlePreview,
|
||||||
},
|
},
|
||||||
@ -188,146 +339,6 @@ const withPreviewUpload = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDefaultSlotsWithUpload = (
|
|
||||||
listType: string,
|
|
||||||
placeholder: string,
|
|
||||||
) => {
|
|
||||||
switch (listType) {
|
|
||||||
case 'picture-card': {
|
|
||||||
return {
|
|
||||||
default: () => placeholder,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return {
|
|
||||||
default: () =>
|
|
||||||
h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
icon: h(IconifyIcon, {
|
|
||||||
icon: 'ant-design:upload-outlined',
|
|
||||||
class: 'mb-1 size-4',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
() => placeholder,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const previewImage = async (
|
|
||||||
file: UploadFile,
|
|
||||||
visible: Ref<boolean>,
|
|
||||||
fileList: Ref<UploadProps['fileList']>,
|
|
||||||
) => {
|
|
||||||
// 检查是否为图片文件的辅助函数
|
|
||||||
const isImageFile = (file: UploadFile): boolean => {
|
|
||||||
const imageExtensions = new Set([
|
|
||||||
'bmp',
|
|
||||||
'gif',
|
|
||||||
'jpeg',
|
|
||||||
'jpg',
|
|
||||||
'png',
|
|
||||||
'webp',
|
|
||||||
]);
|
|
||||||
if (file.url) {
|
|
||||||
const ext = file.url?.split('.').pop()?.toLowerCase();
|
|
||||||
return ext ? imageExtensions.has(ext) : false;
|
|
||||||
}
|
|
||||||
if (!file.type) {
|
|
||||||
const ext = file.name?.split('.').pop()?.toLowerCase();
|
|
||||||
return ext ? imageExtensions.has(ext) : false;
|
|
||||||
}
|
|
||||||
return file.type.startsWith('image/');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果当前文件不是图片,直接打开
|
|
||||||
if (!isImageFile(file)) {
|
|
||||||
if (file.url) {
|
|
||||||
window.open(file.url, '_blank');
|
|
||||||
} else if (file.preview) {
|
|
||||||
window.open(file.preview, '_blank');
|
|
||||||
} else {
|
|
||||||
console.warn('无法打开文件,没有可用的URL或预览地址');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于图片文件,继续使用预览组
|
|
||||||
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
|
|
||||||
Image,
|
|
||||||
PreviewGroup,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getBase64 = (file: File) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.addEventListener('load', () => resolve(reader.result));
|
|
||||||
reader.addEventListener('error', (error) => reject(error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// 从fileList中过滤出所有图片文件
|
|
||||||
const imageFiles = (unref(fileList) || []).filter((element) =>
|
|
||||||
isImageFile(element),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 为所有没有预览地址的图片生成预览
|
|
||||||
for (const imgFile of imageFiles) {
|
|
||||||
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
|
|
||||||
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const container: HTMLElement | null = document.createElement('div');
|
|
||||||
document.body.append(container);
|
|
||||||
|
|
||||||
// 用于追踪组件是否已卸载
|
|
||||||
let isUnmounted = false;
|
|
||||||
|
|
||||||
const PreviewWrapper = {
|
|
||||||
setup() {
|
|
||||||
return () => {
|
|
||||||
if (isUnmounted) return null;
|
|
||||||
return h(
|
|
||||||
PreviewGroupComponent,
|
|
||||||
{
|
|
||||||
class: 'hidden',
|
|
||||||
preview: {
|
|
||||||
visible: visible.value,
|
|
||||||
// 设置初始显示的图片索引
|
|
||||||
current: imageFiles.findIndex((f) => f.uid === file.uid),
|
|
||||||
onVisibleChange: (value: boolean) => {
|
|
||||||
visible.value = value;
|
|
||||||
if (!value) {
|
|
||||||
// 延迟清理,确保动画完成
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!isUnmounted && container) {
|
|
||||||
isUnmounted = true;
|
|
||||||
render(null, container);
|
|
||||||
container.remove();
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
// 渲染所有图片文件
|
|
||||||
imageFiles.map((imgFile) =>
|
|
||||||
h(ImageComponent, {
|
|
||||||
key: imgFile.uid,
|
|
||||||
src: imgFile.url || imgFile.preview,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render(h(PreviewWrapper), container);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiCascader'
|
| 'ApiCascader'
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"length": "{0} must be {1} characters long",
|
"length": "{0} must be {1} characters long",
|
||||||
"alreadyExists": "{0} `{1}` already exists",
|
"alreadyExists": "{0} `{1}` already exists",
|
||||||
"startWith": "{0} must start with `{1}`",
|
"startWith": "{0} must start with `{1}`",
|
||||||
"invalidURL": "Please input a valid URL"
|
"invalidURL": "Please input a valid URL",
|
||||||
|
"sizeLimit": "The file size cannot exceed {0}MB",
|
||||||
|
"previewWarning": "Unable to open the file, there is no available URL or preview address"
|
||||||
},
|
},
|
||||||
"actionTitle": {
|
"actionTitle": {
|
||||||
"edit": "Modify {0}",
|
"edit": "Modify {0}",
|
||||||
@ -24,7 +26,8 @@
|
|||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"input": "Please enter",
|
"input": "Please enter",
|
||||||
"select": "Please select"
|
"select": "Please select",
|
||||||
|
"upload": "Click to upload"
|
||||||
},
|
},
|
||||||
"captcha": {
|
"captcha": {
|
||||||
"title": "Please complete the security verification",
|
"title": "Please complete the security verification",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"length": "{0}长度必须为{1}个字符",
|
"length": "{0}长度必须为{1}个字符",
|
||||||
"alreadyExists": "{0} `{1}` 已存在",
|
"alreadyExists": "{0} `{1}` 已存在",
|
||||||
"startWith": "{0}必须以 {1} 开头",
|
"startWith": "{0}必须以 {1} 开头",
|
||||||
"invalidURL": "请输入有效的链接"
|
"invalidURL": "请输入有效的链接",
|
||||||
|
"sizeLimit": "文件大小不能超过 {0}MB",
|
||||||
|
"previewWarning": "无法打开文件,没有可用的URL或预览地址"
|
||||||
},
|
},
|
||||||
"actionTitle": {
|
"actionTitle": {
|
||||||
"edit": "修改{0}",
|
"edit": "修改{0}",
|
||||||
@ -24,7 +26,8 @@
|
|||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"input": "请输入",
|
"input": "请输入",
|
||||||
"select": "请选择"
|
"select": "请选择",
|
||||||
|
"upload": "点击上传"
|
||||||
},
|
},
|
||||||
"captcha": {
|
"captcha": {
|
||||||
"title": "请完成安全验证",
|
"title": "请完成安全验证",
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { notification } from 'ant-design-vue';
|
import { message, notification } from 'ant-design-vue';
|
||||||
|
|
||||||
const AutoComplete = defineAsyncComponent(
|
const AutoComplete = defineAsyncComponent(
|
||||||
() => import('ant-design-vue/es/auto-complete'),
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
@ -128,9 +128,149 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const withPreviewUpload = () => {
|
const withPreviewUpload = () => {
|
||||||
|
// 创建默认的上传按钮插槽
|
||||||
|
const createDefaultSlotsWithUpload = (
|
||||||
|
listType: string,
|
||||||
|
placeholder: string,
|
||||||
|
) => {
|
||||||
|
switch (listType) {
|
||||||
|
case 'picture-card': {
|
||||||
|
return {
|
||||||
|
default: () => placeholder,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
icon: h(IconifyIcon, {
|
||||||
|
icon: 'ant-design:upload-outlined',
|
||||||
|
class: 'mb-1 size-4',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
() => placeholder,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 构建预览图片组
|
||||||
|
const previewImage = async (
|
||||||
|
file: UploadFile,
|
||||||
|
visible: Ref<boolean>,
|
||||||
|
fileList: Ref<UploadProps['fileList']>,
|
||||||
|
) => {
|
||||||
|
// 检查是否为图片文件的辅助函数
|
||||||
|
const isImageFile = (file: UploadFile): boolean => {
|
||||||
|
const imageExtensions = new Set([
|
||||||
|
'bmp',
|
||||||
|
'gif',
|
||||||
|
'jpeg',
|
||||||
|
'jpg',
|
||||||
|
'png',
|
||||||
|
'webp',
|
||||||
|
]);
|
||||||
|
if (file.url) {
|
||||||
|
const ext = file.url?.split('.').pop()?.toLowerCase();
|
||||||
|
return ext ? imageExtensions.has(ext) : false;
|
||||||
|
}
|
||||||
|
if (!file.type) {
|
||||||
|
const ext = file.name?.split('.').pop()?.toLowerCase();
|
||||||
|
return ext ? imageExtensions.has(ext) : false;
|
||||||
|
}
|
||||||
|
return file.type.startsWith('image/');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果当前文件不是图片,直接打开
|
||||||
|
if (!isImageFile(file)) {
|
||||||
|
if (file.url) {
|
||||||
|
window.open(file.url, '_blank');
|
||||||
|
} else if (file.preview) {
|
||||||
|
window.open(file.preview, '_blank');
|
||||||
|
} else {
|
||||||
|
message.error($t('ui.formRules.previewWarning'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于图片文件,继续使用预览组
|
||||||
|
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
|
||||||
|
Image,
|
||||||
|
PreviewGroup,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getBase64 = (file: File) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.addEventListener('load', () => resolve(reader.result));
|
||||||
|
reader.addEventListener('error', (error) => reject(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 从fileList中过滤出所有图片文件
|
||||||
|
const imageFiles = (unref(fileList) || []).filter((element) =>
|
||||||
|
isImageFile(element),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 为所有没有预览地址的图片生成预览
|
||||||
|
for (const imgFile of imageFiles) {
|
||||||
|
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
|
||||||
|
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const container: HTMLElement | null = document.createElement('div');
|
||||||
|
document.body.append(container);
|
||||||
|
|
||||||
|
// 用于追踪组件是否已卸载
|
||||||
|
let isUnmounted = false;
|
||||||
|
|
||||||
|
const PreviewWrapper = {
|
||||||
|
setup() {
|
||||||
|
return () => {
|
||||||
|
if (isUnmounted) return null;
|
||||||
|
return h(
|
||||||
|
PreviewGroupComponent,
|
||||||
|
{
|
||||||
|
class: 'hidden',
|
||||||
|
preview: {
|
||||||
|
visible: visible.value,
|
||||||
|
// 设置初始显示的图片索引
|
||||||
|
current: imageFiles.findIndex((f) => f.uid === file.uid),
|
||||||
|
onVisibleChange: (value: boolean) => {
|
||||||
|
visible.value = value;
|
||||||
|
if (!value) {
|
||||||
|
// 延迟清理,确保动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isUnmounted && container) {
|
||||||
|
isUnmounted = true;
|
||||||
|
render(null, container);
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
// 渲染所有图片文件
|
||||||
|
imageFiles.map((imgFile) =>
|
||||||
|
h(ImageComponent, {
|
||||||
|
key: imgFile.uid,
|
||||||
|
src: imgFile.url || imgFile.preview,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(h(PreviewWrapper), container);
|
||||||
|
};
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
name: Upload.name,
|
name: Upload.name,
|
||||||
emits: ['change', 'update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
setup: (
|
setup: (
|
||||||
props: any,
|
props: any,
|
||||||
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
|
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
|
||||||
@ -145,9 +285,19 @@ const withPreviewUpload = () => {
|
|||||||
attrs?.fileList || attrs?.['file-list'] || [],
|
attrs?.fileList || attrs?.['file-list'] || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleBeforeUpload = (file: UploadFile) => {
|
||||||
|
if (attrs.maxSize && (file.size || 0) / 1024 / 1024 > attrs.maxSize) {
|
||||||
|
message.error($t('ui.formRules.sizeLimit', [attrs.maxSize]));
|
||||||
|
file.status = 'removed';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return attrs.beforeUpload?.(file) ?? true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = async (event: UploadChangeParam) => {
|
const handleChange = async (event: UploadChangeParam) => {
|
||||||
fileList.value = event.fileList;
|
fileList.value = event.fileList.filter(
|
||||||
emit('change', event);
|
(file) => file.status !== 'removed',
|
||||||
|
);
|
||||||
emit(
|
emit(
|
||||||
'update:modelValue',
|
'update:modelValue',
|
||||||
event.fileList?.length ? fileList.value : undefined,
|
event.fileList?.length ? fileList.value : undefined,
|
||||||
@ -188,6 +338,7 @@ const withPreviewUpload = () => {
|
|||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
fileList: fileList.value,
|
fileList: fileList.value,
|
||||||
|
beforeUpload: handleBeforeUpload,
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
onPreview: handlePreview,
|
onPreview: handlePreview,
|
||||||
},
|
},
|
||||||
@ -197,146 +348,6 @@ const withPreviewUpload = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDefaultSlotsWithUpload = (
|
|
||||||
listType: string,
|
|
||||||
placeholder: string,
|
|
||||||
) => {
|
|
||||||
switch (listType) {
|
|
||||||
case 'picture-card': {
|
|
||||||
return {
|
|
||||||
default: () => placeholder,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return {
|
|
||||||
default: () =>
|
|
||||||
h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
icon: h(IconifyIcon, {
|
|
||||||
icon: 'ant-design:upload-outlined',
|
|
||||||
class: 'mb-1 size-4',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
() => placeholder,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const previewImage = async (
|
|
||||||
file: UploadFile,
|
|
||||||
visible: Ref<boolean>,
|
|
||||||
fileList: Ref<UploadProps['fileList']>,
|
|
||||||
) => {
|
|
||||||
// 检查是否为图片文件的辅助函数
|
|
||||||
const isImageFile = (file: UploadFile): boolean => {
|
|
||||||
const imageExtensions = new Set([
|
|
||||||
'bmp',
|
|
||||||
'gif',
|
|
||||||
'jpeg',
|
|
||||||
'jpg',
|
|
||||||
'png',
|
|
||||||
'webp',
|
|
||||||
]);
|
|
||||||
if (file.url) {
|
|
||||||
const ext = file.url?.split('.').pop()?.toLowerCase();
|
|
||||||
return ext ? imageExtensions.has(ext) : false;
|
|
||||||
}
|
|
||||||
if (!file.type) {
|
|
||||||
const ext = file.name?.split('.').pop()?.toLowerCase();
|
|
||||||
return ext ? imageExtensions.has(ext) : false;
|
|
||||||
}
|
|
||||||
return file.type.startsWith('image/');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果当前文件不是图片,直接打开
|
|
||||||
if (!isImageFile(file)) {
|
|
||||||
if (file.url) {
|
|
||||||
window.open(file.url, '_blank');
|
|
||||||
} else if (file.preview) {
|
|
||||||
window.open(file.preview, '_blank');
|
|
||||||
} else {
|
|
||||||
console.warn('无法打开文件,没有可用的URL或预览地址');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于图片文件,继续使用预览组
|
|
||||||
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
|
|
||||||
Image,
|
|
||||||
PreviewGroup,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getBase64 = (file: File) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.addEventListener('load', () => resolve(reader.result));
|
|
||||||
reader.addEventListener('error', (error) => reject(error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// 从fileList中过滤出所有图片文件
|
|
||||||
const imageFiles = (unref(fileList) || []).filter((element) =>
|
|
||||||
isImageFile(element),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 为所有没有预览地址的图片生成预览
|
|
||||||
for (const imgFile of imageFiles) {
|
|
||||||
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
|
|
||||||
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const container: HTMLElement | null = document.createElement('div');
|
|
||||||
document.body.append(container);
|
|
||||||
|
|
||||||
// 用于追踪组件是否已卸载
|
|
||||||
let isUnmounted = false;
|
|
||||||
|
|
||||||
const PreviewWrapper = {
|
|
||||||
setup() {
|
|
||||||
return () => {
|
|
||||||
if (isUnmounted) return null;
|
|
||||||
return h(
|
|
||||||
PreviewGroupComponent,
|
|
||||||
{
|
|
||||||
class: 'hidden',
|
|
||||||
preview: {
|
|
||||||
visible: visible.value,
|
|
||||||
// 设置初始显示的图片索引
|
|
||||||
current: imageFiles.findIndex((f) => f.uid === file.uid),
|
|
||||||
onVisibleChange: (value: boolean) => {
|
|
||||||
visible.value = value;
|
|
||||||
if (!value) {
|
|
||||||
// 延迟清理,确保动画完成
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!isUnmounted && container) {
|
|
||||||
isUnmounted = true;
|
|
||||||
render(null, container);
|
|
||||||
container.remove();
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
// 渲染所有图片文件
|
|
||||||
imageFiles.map((imgFile) =>
|
|
||||||
h(ImageComponent, {
|
|
||||||
key: imgFile.uid,
|
|
||||||
src: imgFile.url || imgFile.preview,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render(h(PreviewWrapper), container);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiCascader'
|
| 'ApiCascader'
|
||||||
|
|||||||
@ -342,6 +342,8 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
customRequest: upload_file,
|
customRequest: upload_file,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
maxCount: 1,
|
maxCount: 1,
|
||||||
|
// 单位:MB
|
||||||
|
maxSize: 2,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
showUploadList: true,
|
showUploadList: true,
|
||||||
// 上传列表的内建样式,支持四种基本样式 text, picture, picture-card 和 picture-circle
|
// 上传列表的内建样式,支持四种基本样式 text, picture, picture-card 和 picture-circle
|
||||||
@ -354,7 +356,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
default: () => $t('examples.form.upload-image'),
|
default: () => $t('examples.form.upload-image'),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'selectRequired',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user