mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
183 lines
5.1 KiB
Vue
183 lines
5.1 KiB
Vue
<script setup lang="ts">
|
||
import { ref, useAttrs, watch } from 'vue';
|
||
import type { UploadFileInfo, UploadProps } from 'naive-ui';
|
||
import { fetchBatchDeleteOss } from '@/service/api/system/oss';
|
||
import { getToken } from '@/store/modules/auth/shared';
|
||
import { getServiceBaseURL } from '@/utils/service';
|
||
|
||
defineOptions({
|
||
name: 'FileUpload'
|
||
});
|
||
|
||
interface Props {
|
||
action?: string;
|
||
data?: Record<string, any>;
|
||
defaultUpload?: boolean;
|
||
showTip?: boolean;
|
||
max?: number;
|
||
accept?: string;
|
||
fileSize?: number;
|
||
uploadType?: 'file' | 'image';
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
action: `/resource/oss/upload`,
|
||
data: undefined,
|
||
defaultUpload: true,
|
||
showTip: true,
|
||
max: 5,
|
||
accept: '.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf',
|
||
fileSize: 5,
|
||
uploadType: 'file'
|
||
});
|
||
|
||
const attrs: UploadProps = useAttrs();
|
||
|
||
let fileNum = 0;
|
||
const fileList = ref<UploadFileInfo[]>([]);
|
||
|
||
const needRelaodData = ref(false);
|
||
|
||
defineExpose({
|
||
needRelaodData
|
||
});
|
||
watch(
|
||
() => fileList.value,
|
||
newValue => {
|
||
needRelaodData.value = newValue.length > 0;
|
||
}
|
||
);
|
||
|
||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||
|
||
const headers: Record<string, string> = {
|
||
Authorization: `Bearer ${getToken()}`,
|
||
clientid: import.meta.env.VITE_APP_CLIENT_ID!
|
||
};
|
||
|
||
function beforeUpload(options: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
|
||
fileNum += 1;
|
||
const { file } = options;
|
||
|
||
// 校检文件类型
|
||
if (props.accept) {
|
||
const fileName = file.name.split('.');
|
||
const fileExt = `.${fileName[fileName.length - 1]}`;
|
||
const isTypeOk = props.accept.split(',')?.includes(fileExt);
|
||
if (!isTypeOk) {
|
||
window.$message?.error(`文件格式不正确, 请上传 ${props.accept} 格式文件!`);
|
||
return false;
|
||
}
|
||
}
|
||
// 校检文件名是否包含特殊字符
|
||
if (file.name.includes(',')) {
|
||
window.$message?.error('文件名不正确,不能包含英文逗号!');
|
||
return false;
|
||
}
|
||
// 校检文件大小
|
||
if (props.fileSize && file.file?.size) {
|
||
const isLt = file.file?.size / 1024 / 1024 < props.fileSize;
|
||
if (!isLt) {
|
||
window.$message?.error(`上传文件大小不能超过 ${props.fileSize} MB!`);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function isErrorState(xhr: XMLHttpRequest) {
|
||
const responseText = xhr?.responseText;
|
||
const response = JSON.parse(responseText);
|
||
return response.code !== 200;
|
||
}
|
||
|
||
function handleFinish(options: { file: UploadFileInfo; event?: ProgressEvent }) {
|
||
fileNum -= 1;
|
||
const { file, event } = options;
|
||
// @ts-expect-error Ignore type errors
|
||
const responseText = event?.target?.responseText;
|
||
const response = JSON.parse(responseText);
|
||
const oss: Api.System.Oss = response.data;
|
||
fileList.value.find(item => item.id === file.id)!.id = String(oss.ossId);
|
||
file.id = String(oss.ossId);
|
||
file.url = oss.url;
|
||
file.name = oss.fileName;
|
||
if (fileNum === 0) {
|
||
window.$message?.success('上传成功');
|
||
}
|
||
return file;
|
||
}
|
||
|
||
function handleError(options: { file: UploadFileInfo; event?: ProgressEvent }) {
|
||
const { event } = options;
|
||
// @ts-expect-error Ignore type errors
|
||
const responseText = event?.target?.responseText;
|
||
const msg = JSON.parse(responseText).msg;
|
||
window.$message?.error(msg || '上传失败');
|
||
}
|
||
|
||
async function handleRemove(file: UploadFileInfo) {
|
||
if (file.status !== 'finished') {
|
||
return;
|
||
}
|
||
const { error } = await fetchBatchDeleteOss([file.id]);
|
||
if (error) return;
|
||
window.$message?.success('删除成功');
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<NUpload
|
||
v-bind="attrs"
|
||
v-model:file-list="fileList"
|
||
:action="`${baseURL}${action}`"
|
||
:data="data"
|
||
:headers="headers"
|
||
:max="max"
|
||
:accept="accept"
|
||
:multiple="max > 1"
|
||
directory-dnd
|
||
:default-upload="defaultUpload"
|
||
:list-type="uploadType === 'image' ? 'image-card' : 'text'"
|
||
:is-error-state="isErrorState"
|
||
@finish="handleFinish"
|
||
@error="handleError"
|
||
@before-upload="beforeUpload"
|
||
@remove="({ file }) => handleRemove(file)"
|
||
>
|
||
<NUploadDragger v-if="uploadType === 'file'">
|
||
<div class="mb-12px flex-center">
|
||
<SvgIcon icon="material-symbols:unarchive-outline" class="text-58px color-#d8d8db dark:color-#a1a1a2" />
|
||
</div>
|
||
<NText class="text-16px">点击或者拖动文件到该区域来上传</NText>
|
||
<NP v-if="showTip" depth="3" class="mt-8px text-center">
|
||
请上传
|
||
<template v-if="fileSize">
|
||
大小不超过
|
||
<b class="text-red-500">{{ fileSize }}MB</b>
|
||
</template>
|
||
<template v-if="accept">
|
||
,且格式为
|
||
<b class="text-red-500">{{ accept.replaceAll(',', '/') }}</b>
|
||
</template>
|
||
的文件
|
||
</NP>
|
||
</NUploadDragger>
|
||
</NUpload>
|
||
<NP v-if="showTip && uploadType === 'image'" depth="3" class="mt-12px">
|
||
请上传
|
||
<template v-if="fileSize">
|
||
大小不超过
|
||
<b class="text-red-500">{{ fileSize }}MB</b>
|
||
</template>
|
||
<template v-if="accept">
|
||
,且格式为
|
||
<b class="text-red-500">{{ accept.replaceAll(',', '/') }}</b>
|
||
</template>
|
||
的文件
|
||
</NP>
|
||
</template>
|
||
|
||
<style scoped></style>
|