前端逻辑更新
This commit is contained in:
parent
a1e5554823
commit
9b1a2f51e8
41
minio-admin/src/main/java/com/mmg/types/JsonResponse.java
Normal file
41
minio-admin/src/main/java/com/mmg/types/JsonResponse.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package com.mmg.types;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class JsonResponse {
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
private String msg;
|
||||||
|
private Object data;
|
||||||
|
|
||||||
|
public JsonResponse(Integer code, String msg, Object data) {
|
||||||
|
this.code = code;
|
||||||
|
this.msg = msg;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonResponse success(String msg) {
|
||||||
|
return new JsonResponse(0, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonResponse success() {
|
||||||
|
return new JsonResponse(0, "", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonResponse data(Object data) {
|
||||||
|
return new JsonResponse(0, "", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonResponse error(String msg, Integer code) {
|
||||||
|
return new JsonResponse(code, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonResponse error(String msg) {
|
||||||
|
return new JsonResponse(-1, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonResponse error(String msg, Object data) {
|
||||||
|
return new JsonResponse(-1, msg, data);
|
||||||
|
}
|
||||||
|
}
|
@ -7,4 +7,10 @@ import {RouterLink, RouterView} from 'vue-router'
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
17
minio-fornt/src/api/resource.js
Normal file
17
minio-fornt/src/api/resource.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// src/api/resource.js
|
||||||
|
import axios from '../utils/request';
|
||||||
|
|
||||||
|
export const resource = {
|
||||||
|
resourceList: (page, size, title, type, categoryIds) =>
|
||||||
|
axios.get('/resource/list', {
|
||||||
|
params: { page, size, title, type, categoryIds },
|
||||||
|
}),
|
||||||
|
|
||||||
|
destroyResource: (id) => axios.delete(`/resource/${id}`),
|
||||||
|
|
||||||
|
destroyResourceMulti: (ids) => axios.post('/resource/batch-delete', { ids }),
|
||||||
|
|
||||||
|
videoDetail: (id) => axios.get(`/resource/${id}`),
|
||||||
|
|
||||||
|
videoUpdate: (id, data) => axios.put(`/resource/${id}`, data),
|
||||||
|
};
|
6
minio-fornt/src/api/resourceCategory.js
Normal file
6
minio-fornt/src/api/resourceCategory.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// src/api/resourceCategory.js
|
||||||
|
import axios from '../utils/request';
|
||||||
|
|
||||||
|
export const resourceCategory = {
|
||||||
|
resourceCategoryList: () => axios.get('/resource-category/list'),
|
||||||
|
};
|
@ -1,49 +1,66 @@
|
|||||||
import request from '@/utils/request'
|
// src/api/upload.js
|
||||||
|
import axios from '../utils/request';
|
||||||
|
|
||||||
//上传信息
|
/**
|
||||||
export function uploadScreenshot(data){
|
* 获取上传中的文件信息
|
||||||
return request({
|
* @param {string} fileMD5 - 文件的 MD5 值
|
||||||
url:'upload/multipart/uploadScreenshot',
|
* @returns {Promise}
|
||||||
method:'post',
|
*/
|
||||||
data
|
export const getUploadingFile = (fileMD5) => {
|
||||||
})
|
return axios.get(`/upload/getUploadingFile/${fileMD5}`);
|
||||||
}
|
|
||||||
|
|
||||||
//上传信息
|
|
||||||
export function uploadFileInfo(data){
|
|
||||||
return request({
|
|
||||||
url:'upload/multipart/uploadFileInfo',
|
|
||||||
method:'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传校验
|
|
||||||
export function checkUpload(MD5) {
|
|
||||||
return request({
|
|
||||||
url: `upload/multipart/check?md5=${MD5}`,
|
|
||||||
method: 'get',
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
// 初始化上传
|
* 校验文件是否已经上传
|
||||||
export function initUpload(data) {
|
* @param {string} md5 - 文件的 MD5 值
|
||||||
return request({
|
* @returns {Promise}
|
||||||
url: `upload/multipart/init`,
|
*/
|
||||||
method: 'post',
|
export const checkFileUploadedByMd5 = (md5) => {
|
||||||
data
|
return axios.get('/upload/multipart/check', {
|
||||||
})
|
params: { md5 },
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
// 初始化上传
|
* 初始化分片上传
|
||||||
export function mergeUpload(data) {
|
* @param {Object} fileUploadInfo - 文件上传信息
|
||||||
return request({
|
* @returns {Promise}
|
||||||
url: `upload/multipart/merge`,
|
*/
|
||||||
method: 'post',
|
export const initMultiPartUpload = (fileUploadInfo) => {
|
||||||
data
|
return axios.post('/upload/multipart/init', fileUploadInfo);
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成分片上传
|
||||||
|
* @param {Object} fileUploadInfo - 文件上传信息
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export const completeMultiPartUpload = (fileUploadInfo) => {
|
||||||
|
return axios.post('/upload/multipart/merge', fileUploadInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传截图
|
||||||
|
* @param {FormData} formData - 包含截图文件的 FormData
|
||||||
|
* @param {string} bucketName - Bucket 名称
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export const uploadScreenshot = (formData, bucketName) => {
|
||||||
|
return axios.post('/upload/multipart/uploadScreenshot', formData, {
|
||||||
|
params: { bucketName },
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Bucket
|
||||||
|
* @param {string} bucketName - Bucket 名称
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export const createBucket = (bucketName) => {
|
||||||
|
return axios.post('/upload/createBucket', null, {
|
||||||
|
params: { bucketName },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
260
minio-fornt/src/components/Resource/FileUploader.vue
Normal file
260
minio-fornt/src/components/Resource/FileUploader.vue
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
<template>
|
||||||
|
<div class="file-uploader">
|
||||||
|
<el-upload
|
||||||
|
ref="upload"
|
||||||
|
:file-list="fileList"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:auto-upload="false"
|
||||||
|
multiple
|
||||||
|
drag
|
||||||
|
accept=".doc,.docx,.ppt,.pptx,.pdf,.txt,.rar,.zip,.jpg,.jpeg,.png"
|
||||||
|
@change="handleChange"
|
||||||
|
>
|
||||||
|
<i class="el-icon-upload"></i>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div slot="tip" class="el-upload__tip">支持上传的格式: DOC, DOCX, PPT, PPTX, PDF, TXT, RAR, ZIP</div>
|
||||||
|
</el-upload>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!fileList.length"
|
||||||
|
@click="uploadFiles"
|
||||||
|
class="upload-button"
|
||||||
|
>
|
||||||
|
上传
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-progress v-if="uploadProgress > 0" :percentage="uploadProgress" class="upload-progress" />
|
||||||
|
|
||||||
|
<div v-if="uploadResult" class="upload-result">
|
||||||
|
<h3>上传结果</h3>
|
||||||
|
<p>
|
||||||
|
文件 URL:
|
||||||
|
<a :href="uploadResult.url" target="_blank">{{ uploadResult.url }}</a>
|
||||||
|
</p>
|
||||||
|
<p>文件名: {{ uploadResult.resource.originalName }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorMessage" class="error-message">
|
||||||
|
<el-alert :title="errorMessage" type="error" show-icon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref } from 'vue';
|
||||||
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import {
|
||||||
|
checkFileUploadedByMd5,
|
||||||
|
getUploadingFile,
|
||||||
|
initMultiPartUpload,
|
||||||
|
completeMultiPartUpload,
|
||||||
|
} from '../../api/upload.js';
|
||||||
|
import SparkMD5 from 'spark-md5';
|
||||||
|
|
||||||
|
interface FileUploadInfo {
|
||||||
|
adminId?: string;
|
||||||
|
categoryIds?: string[];
|
||||||
|
resourceType: string;
|
||||||
|
originalName: string;
|
||||||
|
extension: string;
|
||||||
|
size: number;
|
||||||
|
savePath: string;
|
||||||
|
uploadId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Resource {
|
||||||
|
id: string;
|
||||||
|
originalName: string;
|
||||||
|
extension: string;
|
||||||
|
size: number;
|
||||||
|
md5: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadResult {
|
||||||
|
url: string;
|
||||||
|
resource: Resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FileUploader',
|
||||||
|
emits: ['upload-success'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const fileList = ref<any[]>([]);
|
||||||
|
const uploadProgress = ref<number>(0);
|
||||||
|
const uploadResult = ref<UploadResult | null>(null);
|
||||||
|
const errorMessage = ref<string>('');
|
||||||
|
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (info: any) => {
|
||||||
|
fileList.value = info.fileList;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateMD5 = (file: File): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const chunkSize = 2097152; // 2MB
|
||||||
|
const chunks = Math.ceil(file.size / chunkSize);
|
||||||
|
let currentChunk = 0;
|
||||||
|
const spark = new SparkMD5.ArrayBuffer();
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
|
||||||
|
fileReader.onload = (e) => {
|
||||||
|
if (e.target && e.target.result) {
|
||||||
|
spark.append(e.target.result as ArrayBuffer);
|
||||||
|
currentChunk++;
|
||||||
|
if (currentChunk < chunks) {
|
||||||
|
loadNext();
|
||||||
|
} else {
|
||||||
|
const md5 = spark.end();
|
||||||
|
resolve(md5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileReader.onerror = () => {
|
||||||
|
reject('文件读取错误');
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadNext = () => {
|
||||||
|
const start = currentChunk * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
fileReader.readAsArrayBuffer(file.slice(start, end));
|
||||||
|
};
|
||||||
|
|
||||||
|
loadNext();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFiles = async () => {
|
||||||
|
if (fileList.value.length === 0) return;
|
||||||
|
|
||||||
|
uploadProgress.value = 0;
|
||||||
|
uploadResult.value = null;
|
||||||
|
errorMessage.value = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = fileList.value[0].raw as File;
|
||||||
|
const fileMD5 = await calculateMD5(file);
|
||||||
|
console.log('文件 MD5:', fileMD5);
|
||||||
|
|
||||||
|
// 检查文件是否已上传
|
||||||
|
const checkResponse = await checkFileUploadedByMd5(fileMD5);
|
||||||
|
if (checkResponse.data.exists) {
|
||||||
|
// 文件已存在,获取文件信息
|
||||||
|
const getFileResponse = await getUploadingFile(fileMD5);
|
||||||
|
uploadResult.value = getFileResponse.data.file;
|
||||||
|
ElMessage.success('文件已存在,已获取文件信息');
|
||||||
|
emit('upload-success');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化分片上传
|
||||||
|
const initInfo: FileUploadInfo = {
|
||||||
|
resourceType: 'file',
|
||||||
|
originalName: file.name,
|
||||||
|
extension: getFileExtension(file.name),
|
||||||
|
size: file.size,
|
||||||
|
savePath: `uploads/${file.name}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initResponse = await initMultiPartUpload(initInfo);
|
||||||
|
const { uploadId, preSignedUrls, partCount } = initResponse.data.data;
|
||||||
|
|
||||||
|
// 分片上传
|
||||||
|
const partSize = 5 * 1024 * 1024; // 5MB
|
||||||
|
let uploadedBytes = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < partCount; i++) {
|
||||||
|
const start = i * partSize;
|
||||||
|
const end = Math.min(start + partSize, file.size);
|
||||||
|
const blob = file.slice(start, end);
|
||||||
|
const preSignedUrl = preSignedUrls[i];
|
||||||
|
|
||||||
|
// 正确声明配置类型为 AxiosRequestConfig<Blob>
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': file.type, // 设置文件类型
|
||||||
|
},
|
||||||
|
onUploadProgress: (progressEvent: ProgressEvent) => {
|
||||||
|
if (progressEvent.total) {
|
||||||
|
const percentCompleted = Math.round(
|
||||||
|
(progressEvent.loaded * 100) / progressEvent.total
|
||||||
|
);
|
||||||
|
uploadProgress.value = Math.min(
|
||||||
|
percentCompleted + (i * 100) / partCount,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用 PUT 请求,上传文件
|
||||||
|
await axios.put(preSignedUrl, blob, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成分片上传
|
||||||
|
const completeInfo: FileUploadInfo = {
|
||||||
|
resourceType: 'file',
|
||||||
|
originalName: file.name,
|
||||||
|
extension: getFileExtension(file.name),
|
||||||
|
size: file.size,
|
||||||
|
savePath: `uploads/${file.name}`,
|
||||||
|
uploadId: uploadId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const completeResponse = await completeMultiPartUpload(completeInfo);
|
||||||
|
uploadResult.value = completeResponse.data.data;
|
||||||
|
ElMessage.success('文件上传成功');
|
||||||
|
emit('upload-success');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
errorMessage.value = error.response?.data?.message || '上传失败';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileExtension = (filename: string): string => {
|
||||||
|
const index = filename.lastIndexOf('.');
|
||||||
|
if (index === -1) return '';
|
||||||
|
return filename.substring(index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileList,
|
||||||
|
uploadProgress,
|
||||||
|
uploadResult,
|
||||||
|
errorMessage,
|
||||||
|
beforeUpload,
|
||||||
|
handleChange,
|
||||||
|
uploadFiles,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.file-uploader {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.upload-button {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.upload-progress {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.upload-result {
|
||||||
|
margin-top: 16px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
285
minio-fornt/src/components/Resource/UploadCoursewareButton.vue
Normal file
285
minio-fornt/src/components/Resource/UploadCoursewareButton.vue
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
<!-- src/components/UploadCoursewareButton.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="showModal = true">
|
||||||
|
上传课件
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
title="上传课件"
|
||||||
|
:visible.sync="showModal"
|
||||||
|
width="50%"
|
||||||
|
@close="handleCancel"
|
||||||
|
>
|
||||||
|
<el-upload
|
||||||
|
class="upload-demo"
|
||||||
|
drag
|
||||||
|
action=""
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:file-list="[]"
|
||||||
|
multiple
|
||||||
|
:auto-upload="false"
|
||||||
|
:on-change="handleChange"
|
||||||
|
>
|
||||||
|
<i class="el-icon-upload"></i>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div slot="tip" class="el-upload__tip">支持多文件上传,支持word、ppt、pdf、zip、rar、txt格式文件</div>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
:data="fileList"
|
||||||
|
style="width: 100%; margin-top: 20px;"
|
||||||
|
:show-header="false"
|
||||||
|
row-key="id"
|
||||||
|
>
|
||||||
|
<el-table-column prop="name">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ scope.row.file.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="size">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ (scope.row.file.size / 1024 / 1024).toFixed(2) }} MB</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="progress">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-progress v-if="scope.row.upload.status === 'uploading'" :percentage="scope.row.upload.progress" />
|
||||||
|
<span v-else-if="scope.row.upload.status === 'waiting'">等待上传</span>
|
||||||
|
<span v-else-if="scope.row.upload.status === 'success'" style="color: green;">上传成功</span>
|
||||||
|
<span v-else-if="scope.row.upload.status === 'error'" style="color: red;">{{ scope.row.upload.remark }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleUpload">上传</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UploadCoursewareButton',
|
||||||
|
props: {
|
||||||
|
categoryIds: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const showModal = ref(false);
|
||||||
|
const fileList = ref([]);
|
||||||
|
|
||||||
|
// 生成唯一ID
|
||||||
|
const generateUUID = () => {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
const r = (Math.random() * 16) | 0,
|
||||||
|
v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文件类型验证
|
||||||
|
const allowedTypes = ['doc', 'docx', 'ppt', 'pptx', 'pdf', 'zip', 'rar', 'txt'];
|
||||||
|
|
||||||
|
const beforeUpload = (file) => {
|
||||||
|
const extension = getFileExtension(file.name);
|
||||||
|
if (!allowedTypes.includes(extension)) {
|
||||||
|
ElMessage.error(`${file.name} 是不支持的文件类型`);
|
||||||
|
return false; // 阻止上传
|
||||||
|
}
|
||||||
|
// 添加到 fileList
|
||||||
|
const newFileItem = {
|
||||||
|
id: generateUUID(),
|
||||||
|
file: file,
|
||||||
|
upload: {
|
||||||
|
progress: 0,
|
||||||
|
status: 'waiting', // 'waiting', 'uploading', 'success', 'error'
|
||||||
|
remark: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
fileList.value.push(newFileItem);
|
||||||
|
console.log('文件添加到待上传列表:', newFileItem);
|
||||||
|
return false; // 阻止自动上传
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (info) => {
|
||||||
|
// 由于使用自定义上传,这里无需处理
|
||||||
|
console.log('文件变化:', info);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpload = async () => {
|
||||||
|
console.log('开始上传文件列表:', fileList.value);
|
||||||
|
for (const fileItem of fileList.value) {
|
||||||
|
if (fileItem.upload.status === 'waiting') {
|
||||||
|
await uploadFile(fileItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit('update');
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = async (fileItem) => {
|
||||||
|
fileItem.upload.status = 'uploading';
|
||||||
|
console.log(`开始上传文件: ${fileItem.file.name}`);
|
||||||
|
try {
|
||||||
|
// 获取上传ID
|
||||||
|
const uploadIdResponse = await minioUploadId(getFileExtension(fileItem.file.name));
|
||||||
|
const { resource_type, upload_id, filename } = uploadIdResponse.data;
|
||||||
|
console.log('获取上传ID成功:', uploadIdResponse.data);
|
||||||
|
|
||||||
|
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||||
|
const totalChunks = Math.ceil(fileItem.file.size / chunkSize);
|
||||||
|
let currentChunk = 0;
|
||||||
|
|
||||||
|
while (currentChunk < totalChunks) {
|
||||||
|
const start = currentChunk * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, fileItem.file.size);
|
||||||
|
const blob = fileItem.file.slice(start, end);
|
||||||
|
|
||||||
|
// 获取预签名URL
|
||||||
|
const preSignUrlResponse = await minioPreSignUrl(upload_id, filename, currentChunk + 1);
|
||||||
|
const { url } = preSignUrlResponse.data;
|
||||||
|
console.log(`获取预签名URL成功 (分片 ${currentChunk + 1}):`, url);
|
||||||
|
|
||||||
|
// 上传分片
|
||||||
|
const uploadResponse = await fetch(url, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': fileItem.file.type,
|
||||||
|
},
|
||||||
|
body: blob,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!uploadResponse.ok) {
|
||||||
|
throw new Error(`分片 ${currentChunk + 1} 上传失败`);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChunk++;
|
||||||
|
fileItem.upload.progress = Math.floor((currentChunk / totalChunks) * 100);
|
||||||
|
console.log(`分片 ${currentChunk} 上传成功,进度: ${fileItem.upload.progress}%`);
|
||||||
|
// 触发响应式更新
|
||||||
|
fileList.value = [...fileList.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并分片
|
||||||
|
const mergeData = {
|
||||||
|
extension: getFileExtension(fileItem.file.name),
|
||||||
|
original_filename: fileItem.file.name,
|
||||||
|
category_ids: props.categoryIds.join(','),
|
||||||
|
size: fileItem.file.size,
|
||||||
|
upload_id: upload_id,
|
||||||
|
filename: filename,
|
||||||
|
poster: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeResponse = await minioMergeFile(mergeData);
|
||||||
|
const { url: mergedUrl } = mergeResponse.data;
|
||||||
|
console.log('合并分片成功:', mergeResponse.data);
|
||||||
|
|
||||||
|
fileItem.upload.progress = 100;
|
||||||
|
fileItem.upload.status = 'success';
|
||||||
|
ElMessage.success(`${fileItem.file.name} 上传成功`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`上传文件失败 (${fileItem.file.name}):`, error);
|
||||||
|
fileItem.upload.status = 'error';
|
||||||
|
fileItem.upload.remark = error.message || '上传失败';
|
||||||
|
ElMessage.error(`${fileItem.file.name} 上传失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
// 取消上传中或等待上传的文件
|
||||||
|
fileList.value.forEach((item) => {
|
||||||
|
if (item.upload.status !== 'success') {
|
||||||
|
item.upload.status = 'error';
|
||||||
|
item.upload.remark = '上传被取消';
|
||||||
|
console.log(`上传被取消: ${item.file.name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileList.value = [];
|
||||||
|
showModal.value = false;
|
||||||
|
emit('update');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileExtension = (filename) => {
|
||||||
|
const index = filename.lastIndexOf('.');
|
||||||
|
if (index === -1) return '';
|
||||||
|
return filename.substring(index + 1).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟API调用
|
||||||
|
const minioUploadId = async (extension) => {
|
||||||
|
// 替换为实际的API调用
|
||||||
|
console.log('请求获取上传ID');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
data: {
|
||||||
|
resource_type: 'courseware',
|
||||||
|
upload_id: 'unique-upload-id',
|
||||||
|
filename: 'uploaded-file-name.' + extension,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const minioPreSignUrl = async (upload_id, filename, chunkNumber) => {
|
||||||
|
// 替换为实际的API调用
|
||||||
|
console.log(`请求获取预签名URL (分片 ${chunkNumber})`);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
data: {
|
||||||
|
url: `https://example.com/upload/${upload_id}/${filename}/chunk-${chunkNumber}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const minioMergeFile = async (mergeData) => {
|
||||||
|
// 替换为实际的API调用
|
||||||
|
console.log('请求合并分片');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
data: {
|
||||||
|
url: 'https://example.com/upload/complete',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
showModal,
|
||||||
|
fileList,
|
||||||
|
beforeUpload,
|
||||||
|
handleChange,
|
||||||
|
handleUpload,
|
||||||
|
handleCancel,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.upload-demo i {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
.upload-demo .el-upload__text {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.upload-demo .el-upload__tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,13 +1,19 @@
|
|||||||
import {createApp} from 'vue'
|
// src/main.js
|
||||||
import App from './App.vue'
|
import { createApp } from 'vue';
|
||||||
import router from './router'
|
import App from './App.vue';
|
||||||
import ElementPlus from 'element-plus'
|
import router from './router';
|
||||||
import 'element-plus/dist/index.css'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
// 引入 Element Plus 及其样式
|
||||||
|
import ElementPlus from 'element-plus';
|
||||||
|
import 'element-plus/dist/index.css';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router)
|
// 使用 Element Plus
|
||||||
app.use(ElementPlus)
|
app.use(ElementPlus);
|
||||||
|
|
||||||
app.mount('#app')
|
// 使用路由
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
// 挂载应用
|
||||||
|
app.mount('#app');
|
||||||
|
11
minio-fornt/src/utils/dateFormat.js
Normal file
11
minio-fornt/src/utils/dateFormat.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// src/utils/dateFormat.js
|
||||||
|
export const dateFormat = (dateStr) => {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = (`0${date.getMonth() + 1}`).slice(-2);
|
||||||
|
const day = (`0${date.getDate()}`).slice(-2);
|
||||||
|
const hours = (`0${date.getHours()}`).slice(-2);
|
||||||
|
const minutes = (`0${date.getMinutes()}`).slice(-2);
|
||||||
|
const seconds = (`0${date.getSeconds()}`).slice(-2);
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
|
};
|
@ -1,42 +1,34 @@
|
|||||||
import axios from 'axios'
|
// src/utils/request.js
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const request = axios.create({
|
// 创建 axios 实例
|
||||||
baseURL: `http://localhost:9090`,
|
const instance = axios.create({
|
||||||
timeout: 30000
|
baseURL: 'http://localhost:9090', // 替换为您的后端地址
|
||||||
})
|
timeout: 10000,
|
||||||
|
|
||||||
// request 拦截器
|
|
||||||
// 可以自请求发送前对请求做一些处理
|
|
||||||
// 比如统一加token,对请求参数统一加密
|
|
||||||
request.interceptors.request.use(config => {
|
|
||||||
config.headers['Content-Type'] = 'application/json;charset=utf-8';
|
|
||||||
return config
|
|
||||||
}, error => {
|
|
||||||
return Promise.reject(error)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// response 拦截器
|
// 请求拦截器
|
||||||
// 可以在接口响应后统一处理结果
|
instance.interceptors.request.use(
|
||||||
request.interceptors.response.use(
|
(config) => {
|
||||||
response => {
|
// 可以在这里添加请求头,例如 token
|
||||||
let res = response.data;
|
// config.headers['Authorization'] = 'Bearer token';
|
||||||
// 如果是返回的文件
|
return config;
|
||||||
if (response.headers === 'blob') {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
// 兼容服务端返回的字符串数据
|
|
||||||
if (typeof res === 'string') {
|
|
||||||
res = res ? JSON.parse(res) : res
|
|
||||||
console.log(res)
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
},
|
||||||
error => {
|
(error) => {
|
||||||
console.log('err' + error) // for debug
|
return Promise.reject(error);
|
||||||
return Promise.reject(error)
|
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
ElMessage.error(error.response?.data?.message || '请求失败');
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default request
|
export default instance;
|
||||||
|
|
||||||
|
@ -1,338 +1,59 @@
|
|||||||
|
<!-- src/views/FileView.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="file-view">
|
||||||
<div style="display:none;">
|
<h2>文件上传</h2>
|
||||||
<video width="500" height="240" controls id="upvideo">
|
<FileUploader @upload-success="handleUploadSuccess" />
|
||||||
</video>
|
<el-button type="primary" class="mt-16" @click="createBucket">
|
||||||
</div>
|
创建 Bucket
|
||||||
<h2>上传示例</h2>
|
</el-button>
|
||||||
<div class="upload-demo">
|
|
||||||
<el-upload ref="upload" action="https://jsonplaceholder.typicode.com/posts/"
|
|
||||||
:on-remove="handleRemove" :on-change="handleFileChange" :file-list="data.uploadFileList"
|
|
||||||
:show-file-list="false"
|
|
||||||
:auto-upload="false" multiple>
|
|
||||||
<el-button slot="trigger" type="primary" plain>选择文件</el-button>
|
|
||||||
</el-upload>
|
|
||||||
<el-button style="margin: 5px;" type="success" @click="handler">上传</el-button>
|
|
||||||
<el-button type="danger" @click="clearFileHandler">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<table style="margin-top: 20px">
|
|
||||||
<th>
|
|
||||||
文件名
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
文件大小
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
上传进度
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
状态
|
|
||||||
</th>
|
|
||||||
</table>
|
|
||||||
<!-- 文件列表 -->
|
|
||||||
<div class="file-list-wrapper">
|
|
||||||
<el-collapse>
|
|
||||||
<el-collapse-item v-for="item in data.uploadFileList">
|
|
||||||
<template #title>
|
|
||||||
<div class="upload-file-item">
|
|
||||||
<div class="file-info-item file-name" :title="item.name">{{ item.name }}</div>
|
|
||||||
<div class="file-info-item file-size">{{ item.size }}</div>
|
|
||||||
<div class="file-info-item file-progress">
|
|
||||||
<span class="file-progress-label"></span>
|
|
||||||
<el-progress :percentage="item.uploadProgress" class="file-progress-value"/>
|
|
||||||
</div>
|
|
||||||
<div class="file-info-item file-size"><span></span>
|
|
||||||
<el-tag v-if="item.status === '等待上传'" size="small" type="info">等待上传</el-tag>
|
|
||||||
<el-tag v-else-if="item.status === '校验MD5'" size="small" type="warning">校验MD5</el-tag>
|
|
||||||
<el-tag v-else-if="item.status === '正在上传'" size="small">正在上传</el-tag>
|
|
||||||
<el-tag v-else-if="item.status === '上传成功'" size="small" type="success">上传完成</el-tag>
|
|
||||||
<el-tag v-else size="small">正在上传</el-tag>
|
|
||||||
<!-- <el-tag v-else size="medium" type="danger">上传错误</el-tag>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="file-chunk-list-wrapper">
|
|
||||||
<!-- 分片列表 -->
|
|
||||||
<el-table :data="item.chunkList" max-height="400" style="width: 100%">
|
|
||||||
<el-table-column prop="chunkNumber" label="分片序号" width="180">
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="progress" label="上传进度">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-progress v-if="!row.status || row.progressStatus === 'normal'"
|
|
||||||
:percentage="row.progress"/>
|
|
||||||
<el-progress v-else :percentage="row.progress" :status="row.progressStatus"
|
|
||||||
:text-inside="true" :stroke-width="16"/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="status" label="状态" width="180">
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script lang="ts">
|
||||||
import {ref, reactive} from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import {checkUpload, initUpload, mergeUpload, uploadFileInfo} from '@/api/upload';
|
import FileUploader from '../components/Resource/FileUploader.vue';
|
||||||
import {fileSuffixTypeUtil} from '@/utils/FileUtil';
|
import { createBucket } from '../api/upload.js';
|
||||||
import axios from 'axios';
|
import { ElInput, ElButton, ElMessage } from 'element-plus';
|
||||||
import SparkMD5 from 'spark-md5';
|
|
||||||
import {ElMessageBox} from "element-plus";
|
|
||||||
|
|
||||||
const FILE_UPLOAD_ID_KEY = 'file_upload_id';
|
export default defineComponent({
|
||||||
const chunkSize = 10 * 1024 * 1024; // 10MB
|
name: 'FileView',
|
||||||
let currentFileIndex = 0;
|
components: {
|
||||||
|
FileUploader,
|
||||||
const FileStatus = {
|
},
|
||||||
wait: '等待上传',
|
setup() {
|
||||||
getMd5: '校验MD5',
|
const handleUploadSuccess = () => {
|
||||||
chip: '正在创建序列',
|
ElMessage.success('文件上传成功!');
|
||||||
uploading: '正在上传',
|
// 可以在这里执行其他操作,如刷新列表等
|
||||||
success: '上传成功',
|
|
||||||
error: '上传错误'
|
|
||||||
};
|
|
||||||
|
|
||||||
const simultaneousUploads = ref(3);
|
|
||||||
const data = reactive({
|
|
||||||
uploadFileList: []
|
|
||||||
});
|
|
||||||
|
|
||||||
const handler = () => {
|
|
||||||
if (data.uploadFileList.length === 0) {
|
|
||||||
ElMessageBox.alert('请先选择文件')
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (currentFileIndex >= data.uploadFileList.length) {
|
|
||||||
ElMessageBox.alert('文件上传完成')
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const currentFile = data.uploadFileList[currentFileIndex];
|
|
||||||
currentFile.status = FileStatus.getMd5;
|
|
||||||
currentFile.chunkUploadedList = [];
|
|
||||||
|
|
||||||
getFileMd5(currentFile.raw, async (md5, totalChunks) => {
|
|
||||||
const checkResult = await checkFileUploadedByMd5(md5);
|
|
||||||
if (checkResult.code === 1) {
|
|
||||||
currentFile.status = FileStatus.success;
|
|
||||||
currentFile.uploadProgress = 100;
|
|
||||||
currentFileIndex++;
|
|
||||||
handler();
|
|
||||||
return;
|
|
||||||
} else if (checkResult.code === 2) {
|
|
||||||
currentFile.status = FileStatus.uploading;
|
|
||||||
currentFile.chunkUploadedList = checkResult.data.chunkUploadedList;
|
|
||||||
} else {
|
|
||||||
console.log('未上传');
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFile.status = FileStatus.chip;
|
|
||||||
let fileChunks = createFileChunk(currentFile.raw, chunkSize);
|
|
||||||
let type = fileSuffixTypeUtil(currentFile.name);
|
|
||||||
let param = {
|
|
||||||
fileName: currentFile.name,
|
|
||||||
fileSize: currentFile.size,
|
|
||||||
chunkSize: chunkSize,
|
|
||||||
chunkNum: totalChunks,
|
|
||||||
fileMd5: md5,
|
|
||||||
contentType: 'application/octet-stream',
|
|
||||||
fileType: type,
|
|
||||||
chunkUploadedList: currentFile.chunkUploadedList
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let uploadIdInfoResult = await getFileUploadUrls(param);
|
const createBucket = async () => {
|
||||||
let uploadIdInfo = uploadIdInfoResult.data;
|
const bucketName = prompt('请输入要创建的 Bucket 名称');
|
||||||
let uploadUrls = uploadIdInfo.urlList;
|
if (!bucketName) {
|
||||||
|
ElMessage.warning('Bucket 名称不能为空');
|
||||||
currentFile.chunkList = [];
|
return;
|
||||||
if (uploadUrls && fileChunks.length !== uploadUrls.length) {
|
|
||||||
await ElMessageBox.alert('文件上传完成')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileChunks.map((chunkItem, index) => {
|
|
||||||
if (currentFile.chunkUploadedList.indexOf(index + 1) !== -1) {
|
|
||||||
currentFile.chunkList.push({
|
|
||||||
chunkNumber: index + 1,
|
|
||||||
chunk: chunkItem,
|
|
||||||
uploadUrl: uploadUrls[index],
|
|
||||||
progress: 100,
|
|
||||||
progressStatus: 'success',
|
|
||||||
status: '上传成功'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
currentFile.chunkList.push({
|
|
||||||
chunkNumber: index + 1,
|
|
||||||
chunk: chunkItem,
|
|
||||||
uploadUrl: uploadUrls[index],
|
|
||||||
progress: 0,
|
|
||||||
status: '—'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let tempFileChunks = [];
|
try {
|
||||||
currentFile.chunkList.forEach((item) => {
|
await createBucket();
|
||||||
tempFileChunks.push(item);
|
ElMessage.success('Bucket 创建成功');
|
||||||
});
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.response?.data?.message || 'Bucket 创建失败');
|
||||||
currentFile.status = FileStatus.uploading;
|
|
||||||
tempFileChunks = processUploadChunkList(tempFileChunks);
|
|
||||||
await uploadChunkBase(tempFileChunks);
|
|
||||||
|
|
||||||
if (uploadIdInfo.uploadId === "SingleFileUpload") {
|
|
||||||
currentFile.status = FileStatus.success;
|
|
||||||
currentFileIndex++;
|
|
||||||
handler();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const mergeResult = await mergeFile({
|
|
||||||
uploadId: uploadIdInfo.uploadId,
|
|
||||||
fileName: currentFile.name,
|
|
||||||
fileMd5: md5,
|
|
||||||
fileType: type,
|
|
||||||
chunkNum: uploadIdInfo.urlList.length,
|
|
||||||
chunkSize: chunkSize,
|
|
||||||
fileSize: currentFile.size
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!mergeResult.data) {
|
|
||||||
currentFile.status = FileStatus.error;
|
|
||||||
this.$message.error(mergeResult.error);
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(FILE_UPLOAD_ID_KEY);
|
|
||||||
currentFile.status = FileStatus.success;
|
|
||||||
currentFileIndex++;
|
|
||||||
handler();
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearFileHandler = () => {
|
return {
|
||||||
data.uploadFileList.splice(0, data.uploadFileList.length);
|
handleUploadSuccess,
|
||||||
currentFileIndex = 0;
|
createBucket,
|
||||||
};
|
};
|
||||||
|
},
|
||||||
const handleFileChange = (file, fileList) => {
|
});
|
||||||
initFileProperties(file);
|
|
||||||
data.uploadFileList.splice(0, data.uploadFileList.length, ...fileList);
|
|
||||||
console.log("data.uploadFileList", data.uploadFileList)
|
|
||||||
};
|
|
||||||
|
|
||||||
const initFileProperties = (file) => {
|
|
||||||
file.chunkList = [];
|
|
||||||
file.status = FileStatus.wait;
|
|
||||||
file.progressStatus = 'warning';
|
|
||||||
file.uploadProgress = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemove = (file, fileList) => {
|
|
||||||
data.uploadFileList.splice(0, data.uploadFileList.length, ...fileList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFileMd5 = (file, callback) => {
|
|
||||||
let fileReader = new FileReader();
|
|
||||||
fileReader.readAsArrayBuffer(file);
|
|
||||||
fileReader.onload = function (e) {
|
|
||||||
let spark = new SparkMD5.ArrayBuffer();
|
|
||||||
spark.append(e.target.result);
|
|
||||||
callback(spark.end(), Math.ceil(file.size / chunkSize));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createFileChunk = (file, size) => {
|
|
||||||
let fileChunks = [];
|
|
||||||
let cur = 0;
|
|
||||||
while (cur < file.size) {
|
|
||||||
fileChunks.push({file: file.slice(cur, cur + size)});
|
|
||||||
cur += size;
|
|
||||||
}
|
|
||||||
return fileChunks;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkFileUploadedByMd5 = async (md5) => {
|
|
||||||
const response = await checkUpload(md5);
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFileUploadUrls = async (param) => {
|
|
||||||
const response = await initUpload(param);
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
const processUploadChunkList = (chunkList) => {
|
|
||||||
const temp = [];
|
|
||||||
chunkList.forEach((chunk) => {
|
|
||||||
temp.push(chunk);
|
|
||||||
});
|
|
||||||
return temp;
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadChunkBase = async (chunkList) => {
|
|
||||||
const uploadPromiseList = [];
|
|
||||||
const limit = simultaneousUploads.value;
|
|
||||||
for (let i = 0; i < chunkList.length; i++) {
|
|
||||||
if (chunkList[i].progress !== 100) {
|
|
||||||
chunkList[i].status = FileStatus.uploading;
|
|
||||||
let params = chunkList[i];
|
|
||||||
uploadPromiseList.push(
|
|
||||||
uploadFileChunk(params)
|
|
||||||
.then(() => {
|
|
||||||
chunkList[i].progress = 100;
|
|
||||||
chunkList[i].progressStatus = 'success';
|
|
||||||
chunkList[i].status = '上传成功';
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
chunkList[i].progress = 100;
|
|
||||||
chunkList[i].progressStatus = 'exception';
|
|
||||||
chunkList[i].status = '上传失败';
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (uploadPromiseList.length === limit || i === chunkList.length - 1) {
|
|
||||||
await Promise.all(uploadPromiseList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadFileChunk = async (chunk) => {
|
|
||||||
let formData = new FormData();
|
|
||||||
formData.append('file', chunk.chunk.file);
|
|
||||||
await axios.put(chunk.uploadUrl, formData, {
|
|
||||||
headers: {'Content-Type': 'application/octet-stream'}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.file-view {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
.mt-16 {
|
||||||
.file-list-wrapper {
|
margin-top: 16px;
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-file-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-info-item {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-progress {
|
|
||||||
width: 200px;
|
|
||||||
margin: 0 20px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user