324 lines
9.0 KiB
Vue
324 lines
9.0 KiB
Vue
<template>
|
||
<div class="upload-container">
|
||
<div class="fileUpload">
|
||
<div class="item-upload">
|
||
<span v-if="required" class="required">*</span>
|
||
<n-button type="success" onclick="document.getElementById('fpfileName').click()">
|
||
文件选择
|
||
</n-button>
|
||
<!-- <el-button type="primary" οnclick="document.getElementById('fpfileName').click()">文件选择</el-button> -->
|
||
<input
|
||
type="file"
|
||
id="fpfileName"
|
||
class="file2"
|
||
style="display: none"
|
||
@change="selectFile"
|
||
/>
|
||
</div>
|
||
<div v-if="showFileName" class="item-info">
|
||
{{ fileName }}
|
||
</div>
|
||
<div v-else class="item-info">支持{{ getAcceptType() }}文件格式</div>
|
||
<div v-if="progress > 0" class="item-cancel">
|
||
<n-button type="success" style="margin-top: 5px" @click="cancelUploadInfo">
|
||
X
|
||
</n-button>
|
||
</div>
|
||
</div>
|
||
<div class="item-process" v-if="progress > 0">
|
||
<n-progress
|
||
type="line"
|
||
:percentage="progress"
|
||
indicator-placement="inside"
|
||
processing
|
||
/>
|
||
<!-- <el-progress :text-inside="true" :stroke-width="20" :percentage="progress"/> -->
|
||
</div>
|
||
<div v-if="requiredValid" class="item-valid">请上传文件</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
// import { message } from 'element-plus';
|
||
// import { request } from "http";
|
||
import { defineComponent, onMounted, reactive, toRefs } from "vue";
|
||
export default defineComponent({
|
||
name: "file-upload",
|
||
props: {
|
||
//文件接受类型
|
||
accept: {
|
||
type: Array,
|
||
default: ["doc", "docx", "pdf", "safetensors", "ckpt"],
|
||
},
|
||
//是否必填
|
||
required: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
//文件限制大小
|
||
fileSize: {
|
||
type: Number,
|
||
default: 2000,
|
||
},
|
||
},
|
||
setup(props) {
|
||
const data = reactive({
|
||
file: null,
|
||
fileName: "",
|
||
fileSize: 0,
|
||
progress: 0,
|
||
objectKey: "",
|
||
chunkSize: 5 * 1024 * 1024,
|
||
totalChunks: 0,
|
||
currentChunk: 1,
|
||
uploadId: "",
|
||
partArr: [],
|
||
showFileName: false,
|
||
bucketName: "",
|
||
objectKeyName: "",
|
||
required: props.required,
|
||
requiredValid: false,
|
||
});
|
||
const message = useMessage();
|
||
|
||
// 选择文件
|
||
const selectFile = async (event) => {
|
||
data.file = event.target.files[0];
|
||
event.target.value = "";
|
||
if (data?.file?.size) {
|
||
const fileSize = Math.round((data?.file?.size / 1024 / 1024) * 100) / 100;
|
||
if (fileSize > props.fileSize) {
|
||
message.warning("文件大小超过上限" + props.fileSize + "MB");
|
||
return;
|
||
}
|
||
data.fileSize = fileSize;
|
||
console.log(data.fileSize);
|
||
const fileName = data.file.name;
|
||
const index = fileName.lastIndexOf(".");
|
||
const fileType = fileName.substring(index + 1, fileName.length);
|
||
if (!props.accept.includes(fileType)) {
|
||
message.warning("文件类型不支持");
|
||
return;
|
||
}
|
||
data.progress = 0;
|
||
data.fileName = data.file.name;
|
||
data.showFileName = true;
|
||
data.totalChunks = Math.ceil(data.file.size / data.chunkSize);
|
||
data.objectKey = guid() + "." + fileType;
|
||
const res = await request.get("/file/getUploadId/" + data.objectKey);
|
||
data.uploadId = res.data;
|
||
data.progress = 1;
|
||
uploadFile();
|
||
// getUploadId(data.objectKey).then((res)=>{
|
||
// data.uploadId = res.data
|
||
// data.progress = 1;
|
||
// uploadFile();
|
||
// })
|
||
}
|
||
};
|
||
|
||
// 文件上传
|
||
const uploadFile = async () => {
|
||
const index = data.currentChunk - 1;
|
||
const start = index * data.chunkSize;
|
||
const end = Math.min((index + 1) * data.chunkSize, data.file.size);
|
||
const formData = new FormData();
|
||
formData.append("file", data.file.slice(start, end));
|
||
formData.append("chunk", data.currentChunk);
|
||
formData.append("objectKey", data.objectKey);
|
||
formData.append("uploadId", data.uploadId);
|
||
// 调用后端接口上传切片数据
|
||
try {
|
||
const res = await request.post("/file/chunk", formData, {
|
||
headers: {
|
||
"Content-Type": "multipart/form-data",
|
||
},
|
||
});
|
||
data.partArr.push(res.data);
|
||
console.log(data.progress);
|
||
data.currentChunk++;
|
||
data.progress = Math.floor((data.currentChunk / data.totalChunks) * 100);
|
||
if (data.currentChunk <= data.totalChunks) {
|
||
uploadFile();
|
||
} else {
|
||
// 所有切片上传完成
|
||
data.progress = 99;
|
||
console.log(data.progress);
|
||
complete();
|
||
}
|
||
} catch (err) {
|
||
initParam();
|
||
console.error("切片上传失败:", error);
|
||
message.warning("文件上传异常或上传已取消");
|
||
}
|
||
// uploadFileInfo(formData).then((res) => {
|
||
// data.partArr.push(res.data)
|
||
// console.log(data.progress)
|
||
// data.currentChunk++;
|
||
// data.progress = Math.floor((data.currentChunk / data.totalChunks) * 100);
|
||
// if (data.currentChunk <= data.totalChunks) {
|
||
// uploadFile();
|
||
// } else {
|
||
// // 所有切片上传完成
|
||
// data.progress = 99;
|
||
// console.log(data.progress)
|
||
// complete();
|
||
// }
|
||
// }).catch((error) => {
|
||
// initParam()
|
||
// console.error('切片上传失败:', error);
|
||
// message.warning("文件上传异常或上传已取消")
|
||
// });
|
||
};
|
||
|
||
// 上传完成并合并分段上传
|
||
const complete = async () => {
|
||
try {
|
||
const res = await request.post(
|
||
`/file/completeUpload?objectKey=${data.objectKey}&uploadId=${data.uploadId}`,
|
||
data.partArr
|
||
);
|
||
console.log("文件上传完成");
|
||
console.log(res);
|
||
data.bucketName = res.data.bucketName;
|
||
data.objectKeyName = res.data.objectKey;
|
||
data.progress = 100;
|
||
initParam();
|
||
} catch (err) {
|
||
console.error("文件上传失败:", error);
|
||
message.warning('"文件上传失败"');
|
||
}
|
||
// completeUpload(data.partArr,data.objectKey,data.uploadId).then((res) => {
|
||
// console.log('文件上传完成');
|
||
// console.log(res);
|
||
// data.bucketName = res.data.bucketName
|
||
// data.objectKeyName = res.data.objectKey
|
||
// data.progress = 100;
|
||
// initParam()
|
||
// }).catch((error) => {
|
||
// console.error('文件上传失败:', error);
|
||
// message.error("文件上传失败")
|
||
// });
|
||
};
|
||
|
||
// 取消上传
|
||
const cancelUploadInfo = () => {
|
||
if (data.progress !== 100) {
|
||
cancelUpload(data.objectKey, data.uploadId).then(() => {
|
||
data.progress = 0;
|
||
data.showFileName = false;
|
||
initParam();
|
||
});
|
||
} else {
|
||
data.progress = 0;
|
||
data.showFileName = false;
|
||
initParam();
|
||
}
|
||
};
|
||
|
||
const getAcceptType = () => {
|
||
let str = "";
|
||
if (props.accept) {
|
||
for (let item of props.accept) {
|
||
str += item + ",";
|
||
}
|
||
}
|
||
return str.substr(0, str.length - 1);
|
||
};
|
||
|
||
// 构建uuid
|
||
const guid = () => {
|
||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||
var r = (Math.random() * 16) | 0,
|
||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||
return v.toString(16);
|
||
});
|
||
};
|
||
|
||
// 初始化参数
|
||
const initParam = () => {
|
||
data.file = null;
|
||
data.objectKey = "";
|
||
data.totalChunks = 0;
|
||
data.currentChunk = 1;
|
||
data.uploadId = "";
|
||
data.partArr = [];
|
||
data.requiredValid = false;
|
||
};
|
||
|
||
// 必传校验
|
||
const validRequired = () => {
|
||
if (data.bucketName && data.objectKeyName) {
|
||
data.requiredValid = false;
|
||
return true;
|
||
} else {
|
||
data.requiredValid = true;
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// 返回上传结果 桶名称、文件名、文件大小
|
||
const getUploadInfo = () => {
|
||
return {
|
||
bucketName: data.bucketName,
|
||
fileName: data.objectKeyName,
|
||
fileSize: data.fileSize,
|
||
};
|
||
};
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
data.progress = 0;
|
||
});
|
||
|
||
return {
|
||
...toRefs(data),
|
||
selectFile,
|
||
uploadFile,
|
||
getAcceptType,
|
||
validRequired,
|
||
getUploadInfo,
|
||
cancelUploadInfo,
|
||
};
|
||
},
|
||
});
|
||
</script>
|
||
<style scoped>
|
||
.upload-container {
|
||
.fileUpload {
|
||
display: flex;
|
||
flex-direction: row;
|
||
padding: 0px 8px;
|
||
.item-upload {
|
||
width: 100px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
.required {
|
||
color: red;
|
||
padding: 0px 5px;
|
||
}
|
||
}
|
||
.item-info {
|
||
flex: 1;
|
||
padding-top: 12px;
|
||
font-size: 13px;
|
||
}
|
||
.item-cancel {
|
||
width: 30px;
|
||
padding-right: 20px;
|
||
}
|
||
}
|
||
.item-process {
|
||
margin-top: 5px;
|
||
padding: 0px 23px;
|
||
box-sizing: border-box;
|
||
}
|
||
.item-valid {
|
||
color: red;
|
||
padding: 0px 10px;
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
</style>
|