index.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <template>
  2. <div class="chunk-upload">
  3. <el-button type="primary" @click="handleClick" :disabled="uploading">
  4. {{ uploading ? '上传中...' : '选择并上传文件' }}
  5. </el-button>
  6. <div v-for="(progress, index) in uploadProgressList" :key="index" class="progress-item">
  7. <p>File {{ index + 1 }}: {{ progress.fileName }}</p>
  8. <el-progress :percentage="progress.percentage"></el-progress>
  9. </div>
  10. <input type="file" ref="fileInput" @change="handleFileChange" style="display: none;" multiple />
  11. </div>
  12. </template>
  13. <script lang="ts">
  14. import { defineComponent, ref } from "vue";
  15. import { ElMessage } from "element-plus";
  16. import axios from "axios";
  17. import { v1 as uuidv1 } from "uuid";
  18. axios.defaults.baseURL = "http://10.181.7.236:9988";
  19. export default defineComponent({
  20. name: "ChunkUpload",
  21. props: {
  22. maxFileSize: {
  23. type: Number,
  24. default: 10 * 1024 * 1024, // 默认10MB
  25. },
  26. maxFiles: {
  27. type: Number,
  28. default: 3, // 默认最大上传3个文件
  29. },
  30. },
  31. setup(props) {
  32. const fileInput = ref<HTMLInputElement | null>(null);
  33. const uploading = ref(false);
  34. const uploadProgressList = ref<{ fileName: string; percentage: number }[]>([]);
  35. const CHUNK_SIZE = 1 * 1024 * 1024; // 每个分片的大小为 1MB
  36. const handleClick = () => {
  37. fileInput.value?.click();
  38. };
  39. const handleFileChange = async (event: Event) => {
  40. const files = (event.target as HTMLInputElement).files;
  41. if (!files || files.length === 0) {
  42. return;
  43. }
  44. // 检查文件数量
  45. if (files.length > props.maxFiles) {
  46. ElMessage.error(`最多只能上传 ${props.maxFiles} 个文件`);
  47. return;
  48. }
  49. uploading.value = true;
  50. uploadProgressList.value = Array.from(files).map(file => ({
  51. fileName: file.name,
  52. percentage: 0,
  53. }));
  54. try {
  55. for (let i = 0; i < files.length; i++) {
  56. const file = files[i];
  57. if (file.size > props.maxFileSize) {
  58. ElMessage.error(`文件 ${file.name} 大小不能超过 ${props.maxFileSize / (1024 * 1024)} MB`);
  59. continue; // 跳过这个文件继续上传下一个
  60. }
  61. const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  62. const fileIdentifier = await generateFileIdentifier();
  63. for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
  64. const start = chunkIndex * CHUNK_SIZE;
  65. const chunk = file.slice(start, start + CHUNK_SIZE);
  66. await uploadChunk(chunk, chunkIndex, totalChunks, fileIdentifier);
  67. uploadProgressList.value[i].percentage = Math.round(((chunkIndex + 1) / totalChunks) * 100);
  68. }
  69. // 文件分片上传完成后,调用合并接口
  70. const uuidFilename = await mergeChunks(fileIdentifier, file.name);
  71. console.log("上传成功的文件 UUID 名称:", uuidFilename);
  72. }
  73. ElMessage.success("文件上传完成!");
  74. } catch (error) {
  75. ElMessage.error("文件上传失败!");
  76. console.error("Upload error:", error);
  77. } finally {
  78. uploading.value = false;
  79. }
  80. };
  81. const uploadChunk = async (
  82. chunk: Blob,
  83. chunkNumber: number,
  84. totalChunks: number,
  85. fileIdentifier: string
  86. ) => {
  87. const formData = new FormData();
  88. formData.append("file", chunk);
  89. try {
  90. await axios.post(`/api/file/upload/uploadfile`, formData, {
  91. params: {
  92. chunknumber: chunkNumber,
  93. identifier: fileIdentifier,
  94. },
  95. headers: {
  96. "Content-Type": "multipart/form-data",
  97. },
  98. });
  99. } catch (error) {
  100. throw new Error(`上传分片 ${chunkNumber} 失败`);
  101. }
  102. };
  103. const mergeChunks = async (identifier: string, filename: string) => {
  104. try {
  105. const response = await axios.post("/api/file/upload/mergefile", null, {
  106. params: {
  107. identifier: identifier,
  108. filename: filename,
  109. chunkstar: 0, // 假设所有分片的开始序号为0
  110. },
  111. });
  112. if (response.status !== 200) {
  113. throw new Error("文件合并失败");
  114. }
  115. return response.data.uuidFilename;
  116. } catch (error) {
  117. throw new Error("合并请求失败");
  118. }
  119. };
  120. const generateFileIdentifier = async (): Promise<string> => {
  121. return uuidv1(); // 生成一个固定的 UUID1
  122. };
  123. return {
  124. fileInput,
  125. uploading,
  126. uploadProgressList,
  127. handleClick,
  128. handleFileChange,
  129. };
  130. },
  131. });
  132. </script>
  133. <style scoped>
  134. .chunk-upload {
  135. padding: 20px;
  136. }
  137. .progress-item {
  138. margin-top: 10px;
  139. }
  140. </style>