upLoadBreakPoint.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. <template>
  2. <div class="mydiv">
  3. <a-spin :spinning="spinning" tip="读取中...">
  4. <a-upload-dragger
  5. ref="fileUpload"
  6. :file-list="fileList"
  7. :before-upload="beforeUpload"
  8. @change="handleChange"
  9. :show-upload-list="false"
  10. :customRequest="customRequest"
  11. :multiple="false"
  12. :drag="true"
  13. :progress="progress"
  14. >
  15. <div >
  16. <div><p class="ant-upload-text" style="display: inline-block;">点击上传或将文件拖拽至此区域上传</p></div>
  17. <div> <p class="ant-upload-hint" style="display: inline-block;">
  18. 按住Ctrl可同时多选,支持上传
  19. </p></div>
  20. <div> <p class="ant-upload-hint" style="width: 50%;
  21. display: inline-block;">
  22. {{props.upLoadfileLists.join('/')}}
  23. </p></div>
  24. <div><p class="ant-upload-hint" style="display: inline-block;">
  25. 等单个文件不能超过2G
  26. </p></div>
  27. </div>
  28. </a-upload-dragger>
  29. </a-spin>
  30. <!-- <div style="margin-bottom: 20px">
  31. <a-button v-if="uploadFileList.length > 0" type="primary" @click="uploadFilesList">上传</a-button>
  32. </div> -->
  33. <div v-for="(item, index) in uploadFileList" :key="index">
  34. <div style="padding: 10px">
  35. <div style="display: flex; width: 100%; align-items: center; justify-content: space-between">
  36. <div>
  37. <span>{{ item.name.length > 20 ? item.name.slice(0, 20) + '...' + item.fileSuffix : item.name }}</span>
  38. </div>
  39. <div>
  40. <span v-if="item.time != ''" style="display: block; color: blue">{{ item.time }}</span>
  41. </div>
  42. <div>
  43. <div>
  44. <!-- <span-->
  45. <!-- v-if="item.percents == 0"-->
  46. <!-- style="color: red; cursor: pointer; margin-left: 10px"-->
  47. <!-- >读取中</span-->
  48. <!-- >-->
  49. <span
  50. v-if="item.percents == 0 || item.percents == 100"
  51. style="color: red; cursor: pointer; margin-left: 10px"
  52. @click="handlerRemoveItem(index)"
  53. >删除</span
  54. >
  55. <!-- <span-->
  56. <!-- v-if="-->
  57. <!-- item.percents >= 0 &&-->
  58. <!-- item.percents < 100 &&-->
  59. <!-- (pauseFlags[item.md5] == false || pauseFlags[item.md5] == undefined)-->
  60. <!-- "-->
  61. <!-- style="color: blue; cursor: pointer; margin-left: 10px"-->
  62. <!-- @click="pauseUpload(index)"-->
  63. <!-- >暂停</span-->
  64. <!-- >-->
  65. <!-- <span-->
  66. <!-- v-if="item.percents >= 0 && item.percents < 100 && pauseFlags[item.md5] == true"-->
  67. <!-- style="color: green; cursor: pointer; margin-left: 10px"-->
  68. <!-- @click="resumeUpload(index)"-->
  69. <!-- >恢复</span-->
  70. <!-- >-->
  71. </div>
  72. </div>
  73. </div>
  74. <!-- <a-progress :percent="item.percents" />-->
  75. </div>
  76. </div>
  77. <!-- <div> -->
  78. <!-- <el-progress :text-inside="true" :stroke-width="20" :percentage="successfulChunkPercents" status="success" /> -->
  79. <!-- <a-progress :percent="successfulChunkPercents" /> -->
  80. <!-- </div> -->
  81. <!-- 已上传列表-->
  82. <!-- <el-table :data="uploadList" border style="width: 100%">
  83. <el-table-column fixed prop="id.date" label="日期" width="150"> </el-table-column>
  84. <el-table-column prop="url" label="下载地址"> </el-table-column>
  85. <el-table-column label="操作">
  86. <template #default="scope">
  87. <el-button link type="primary" size="small" @click.prevent="deleteFile(scope.row.url)"> 删除 </el-button>
  88. <el-button link type="primary" size="small" @click.prevent="downloadFile(scope.row.url)"> 下载 </el-button>
  89. </template>
  90. </el-table-column>
  91. </el-table> -->
  92. </div>
  93. </template>
  94. <script setup>
  95. import { ref, onMounted } from 'vue'
  96. import axios from 'axios'
  97. import sysConfig from '@/config/index'
  98. import { miniyunStore } from '@/store/miniyun'
  99. const myMiniyunStore = miniyunStore()
  100. const fileUpload = ref(null)
  101. import { message } from 'ant-design-vue'
  102. import SparkMD5 from 'spark-md5'
  103. import tool from '@/utils/tool'
  104. const uploadFileList = ref([])
  105. const spinning = ref(false)
  106. const chunkSize = ref(5 * 1024 * 1024)
  107. const props = defineProps({
  108. uploadCount: {
  109. type: Number,
  110. default: () => 10
  111. },
  112. upLoadfileLists: {
  113. type: Array,
  114. // 视频"wmv","avi","flv","mpeg","mpg","rmvb","mov","mkv","mp4"以及文档"doc","docx","ppt","pptx","xls","xlsx","pdf"应该都允许
  115. default: () => ['jpg','png','pdf','mp4','wmv','avi','flv','mpeg','mpg','rmvb','mov','mkv','docx','doc','xlsx','xls','pptx','ppt','txt','cad','zip','rar','dwg','dxf','dwt']
  116. },
  117. })
  118. const handlerRemoveItem = (index) => {
  119. const item = uploadFileList.value[index]
  120. // if (item && item.md5) {
  121. // delete pauseFlags.value[item.md5] // 清理暂停标志
  122. // }
  123. uploadFileList.value.splice(index, 1)
  124. // emit('onSuccess', uploadFileList.value)
  125. }
  126. const pauseUpload = (index) => {
  127. const item = uploadFileList.value[index]
  128. if (item && item.md5) {
  129. pauseFlags.value[item.md5] = true
  130. uploadingTasks.value[item.md5] = false
  131. }
  132. }
  133. const resumeUpload = (index) => {
  134. const item = uploadFileList.value[index]
  135. if (!item || !item.md5) return
  136. pauseFlags.value[item.md5] = false
  137. // 如果当前上传任务小于 2,则开始上传
  138. const activeTasks = Object.keys(uploadingTasks.value).filter((key) => uploadingTasks.value[key])
  139. if (activeTasks.length < 2) {
  140. uploadingTasks.value[item.md5] = true
  141. // uploadSingleFile(item)
  142. } else {
  143. pauseFlags.value[item.md5] = true
  144. }
  145. }
  146. const calculateFileMD5 = (file) => {
  147. return new Promise((resolve, reject) => {
  148. const reader = new FileReader()
  149. const spark = new SparkMD5.ArrayBuffer()
  150. reader.onload = (e) => {
  151. spark.append(e.target.result)
  152. const md5 = spark.end()
  153. resolve(md5)
  154. }
  155. reader.onerror = () => reject(new Error('文件读取失败'))
  156. reader.readAsArrayBuffer(file)
  157. })
  158. }
  159. const updateProgress = (chunkSize) => {
  160. uploadedSize.value += chunkSize
  161. const percent = Math.round((uploadedSize.value / totalSize.value) * 100)
  162. console.log(`上传进度: ${percent}%`)
  163. }
  164. const calculateSpeed = (startTime, uploadedSize) => {
  165. const currentTime = new Date().getTime()
  166. const timeElapsed = (currentTime - startTime) / 1000 // 单位:秒
  167. if (timeElapsed > 0) {
  168. const speed = uploadedSize / timeElapsed // 单位:字节/秒
  169. return speed
  170. }
  171. return 0
  172. }
  173. const estimateRemainingTime = (startTime, uploadedSize, totalSize) => {
  174. console.log('疑问', ' 总的 ', totalSize, ' 变化的 ', uploadedSize)
  175. const remainingSize = totalSize - uploadedSize // 剩余文件大小
  176. const speed = calculateSpeed(startTime, uploadedSize) // 平均上传速度(字节/秒)
  177. if (speed > 0) {
  178. const remainingTimeSeconds = remainingSize / speed // 剩余时间(秒)
  179. return remainingTimeSeconds
  180. }
  181. return Infinity // 如果上传速度为 0,则无法估算
  182. }
  183. const formatTime = (seconds) => {
  184. const minutes = Math.floor(seconds / 60)
  185. const secs = Math.floor(seconds % 60)
  186. if (minutes == 0 && secs == 0) {
  187. return ''
  188. }
  189. return `${minutes} 分 ${secs} 秒`
  190. }
  191. // 异步方法:选择文件时触发 ============
  192. const handleFileChange = async (file) => {
  193. // const reader = new FileReader()
  194. // const spark = new SparkMD5.ArrayBuffer()
  195. // console.log("读取文件",file.raw)
  196. // reader.readAsArrayBuffer(file.raw) // 异步读取文件内容
  197. // const fileContent = await new Promise((resolve, reject) => {
  198. // reader.onload = (event) => {
  199. // console.log('开始读取文件了...', event, reader.result)
  200. // spark.append(event.target.result)
  201. // resolve(spark.end())
  202. // }
  203. // reader.onerror = (error) => {
  204. // console.log('读取文件时发生错误...', error)
  205. // reject(error)
  206. // }
  207. // reader.readAsArrayBuffer(file)
  208. // })
  209. // console.log('111文件的md5哈希值是 fileContent:', fileContent)
  210. // 计算MD5哈希值
  211. let fileMd5 = await calculateFileMD5(file.raw)
  212. file.raw.md5 = fileMd5
  213. console.log('====开始获取File对象了...', file)
  214. let fileSuffix = '.' + file.raw.name.split('.').pop() // 得到.文件类型
  215. let currentFile = file.raw
  216. console.log('currentFile:', currentFile)
  217. // for (let i = 0; i < chunkCount.value; i++) {
  218. // // 文件开始遍历切片存入数组
  219. // const start = i * chunkSize.value
  220. // console.log('循环中的currentFile:', currentFile.value)
  221. // const end = Math.min(
  222. // start + chunkSize.value - 1,
  223. // currentFile.value.size - 1
  224. // )
  225. // chunkList.value[i] = currentFile.value.slice(start, end)
  226. // }
  227. let chunkList = splitFileByChunkSize(currentFile, chunkSize.value)
  228. console.log('file对象:', file)
  229. return {
  230. name: file.raw.name,
  231. size: currentFile.size,
  232. md5: fileMd5, // md5哈希值
  233. chunks: chunkList, // 分块列表
  234. fileSuffix: fileSuffix, // 后缀
  235. percents: 0,
  236. time: ''
  237. }
  238. // console.log('md5:', fileMd5.value)
  239. // console.log('chunkList:', chunkList.value)
  240. // console.log('fileSuffix:', fileSuffix.value)
  241. // console.log('currentFile:', currentFile.value)
  242. // uploadFileList.value.push({
  243. // name: file.raw.name,
  244. // size: currentFile.value.size,
  245. // md5: fileMd5.value, // md5哈希值
  246. // chunks: chunkList.value, // 分块列表
  247. // fileSuffix: fileSuffix.value, // 后缀
  248. // percents: 0,
  249. // time: ''
  250. // }) // 使用对象数组进行处理 ============
  251. // chunkList.value = []
  252. // console.log('uploadFileList是:', uploadFileList.value)
  253. // console.log('结束handleFileChange了')
  254. }
  255. const splitFileByChunkSize = (file, chunkSizeValue) => {
  256. const fileSize = file.size
  257. const chunkCount = Math.ceil(fileSize / chunkSizeValue) // 计算总分块数
  258. // console.log(
  259. // '统计:',
  260. // '块数',
  261. // chunkCount,
  262. // '文件大小',
  263. // fileSize,
  264. // ' 单块 ',
  265. // chunkSizeValue
  266. // )
  267. let chunkList = []
  268. for (let i = 0; i < chunkCount; i++) {
  269. const start = i * chunkSizeValue
  270. const min = start + chunkSizeValue
  271. console.log('奇怪:', ' start ', start, ' chunkSizeValue ', chunkSizeValue, ' min ', min)
  272. const end = Math.min(min, fileSize)
  273. // console.log(
  274. // '统计:',
  275. // ' start ',
  276. // start,
  277. // ' end ',
  278. // end,
  279. // ' 单块 ',
  280. // chunkSizeValue,
  281. // ' 比较谁大 ',
  282. // start + chunkSizeValue,
  283. // ' ssss ',
  284. // fileSize
  285. // )
  286. chunkList[i] = file.slice(start, end) // 注意:slice 是 [start, end) 前闭后开区间
  287. console.log(
  288. '准备开始:',
  289. ' 循环次数 ',
  290. i,
  291. ' 块数 ',
  292. chunkCount,
  293. '开始的大小',
  294. start,
  295. '结束的大小',
  296. end,
  297. ' file ',
  298. file.slice(start, end)
  299. )
  300. }
  301. console.log('分片完成:', chunkList)
  302. return chunkList
  303. }
  304. const checkMd5List = async (uploadFile) => {
  305. let md5List = []
  306. let element = {
  307. md5: uploadFile.md5,
  308. size: uploadFile.size,
  309. chunkSize: uploadFile.chunks.length,
  310. fileName: uploadFile.name,
  311. fileSuffix: uploadFile.fileSuffix
  312. }
  313. md5List.push(element)
  314. await axios
  315. .post(sysConfig.API_URL+'/api/webapp/minio/checkMd5List', md5List, { headers: { Token: tool.data.get('TOKEN') } })
  316. .then((res) => {
  317. console.log('文件上传返回结果:', res.data)
  318. // return
  319. var list = res.data
  320. if (list.length !== 0) {
  321. let upList = []
  322. for (let item of list) {
  323. console.log('item回来的', JSON.stringify(item))
  324. if (uploadFile.md5 === item.md5) {
  325. uploadFile.userFileId = item.userFileId
  326. //重要的步骤
  327. if (item.userFileId) {
  328. uploadFile.percents = 100
  329. pauseFlags.value[item.md5] = false
  330. // emit('onSuccess', uploadFile)
  331. }
  332. // upList.push(item)
  333. }
  334. }
  335. console.log('upList是:', upList)
  336. // uploadFileList.value.push(uploadFile)
  337. // emit('onSuccess', uploadFileList.value)
  338. } else {
  339. // clearFileList()
  340. }
  341. // 文件均存在minio中了,无需上传
  342. // if (uploadFileList.value.length === 0) {
  343. // successfulChunkPercents.value = 100
  344. // alert('文件上传成功')
  345. // }
  346. })
  347. .catch((error) => {
  348. console.log('检查返回错误', error)
  349. })
  350. }
  351. // 点击上传按钮触发多文件上传 ===============
  352. const uploadFilesList = async () => {
  353. if (upLoadTag.value == true) {
  354. message.loading('正在上传')
  355. return
  356. }
  357. upLoadTag.value = true
  358. emit('onUpLoading', upLoadTag.value)
  359. if (currentFile.value == null) {
  360. // alert('请选择文件后再上传!')
  361. message.error('请选择文件后再上传!')
  362. successfulChunkPercents.value = 0 // 重置百分比
  363. fileList.value = [] // 文件列表
  364. return
  365. }
  366. // 检查所有文件中是否存在未上传的,未上传则需要上传对应的文件 =========
  367. let md5List = []
  368. console.log('准备上传', uploadFileList.value)
  369. for (let i = 0; i < uploadFileList.value.length; i++) {
  370. let element = {
  371. // md5: uploadFileList.value[i].md5,
  372. md5: uploadFileList.value[i].md5,
  373. chunkSize: uploadFileList.value[i].chunks.length,
  374. fileName: uploadFileList.value[i].name,
  375. fileSuffix: uploadFileList.value[i].fileSuffix
  376. }
  377. md5List.push(element)
  378. }
  379. console.log('上传的md5_suffix_List是:', md5List)
  380. await axios
  381. .post(sysConfig.API_URL+'/api/webapp/minio/checkMd5List', md5List, { headers: { Token: tool.data.get('TOKEN') } })
  382. .then((res) => {
  383. console.log('文件上传返回结果:', res.data)
  384. // return
  385. var list = res.data
  386. if (list.length !== 0) {
  387. let upList = []
  388. for (let item1 of uploadFileList.value) {
  389. for (let item2 of list) {
  390. console.log('item回来的', JSON.stringify(item2))
  391. if (item1.md5 === item2.md5) {
  392. //重要的步骤
  393. item1.userFileId = item2.userFileId
  394. if (item1.userFileId) {
  395. item1.percents = 100
  396. }
  397. upList.push(item1)
  398. }
  399. }
  400. }
  401. console.log('upList是:', upList)
  402. uploadFileList.value = upList
  403. } else {
  404. clearFileList()
  405. }
  406. console.log('最后必须上传的文件:', uploadFileList.value)
  407. // 文件均存在minio中了,无需上传
  408. // if (uploadFileList.value.length === 0) {
  409. // successfulChunkPercents.value = 100
  410. // alert('文件上传成功')
  411. // }
  412. })
  413. .catch((error) => {
  414. console.log('检查返回错误', error)
  415. })
  416. // return
  417. console.log('开始上传', uploadFileList.value)
  418. // 检查上传的多个文件是否均存在,如果部分存在,进行剔除,剩余部分仍旧进行上传。
  419. // 分块的Promises化
  420. const chunkPromises = []
  421. // 上传分块的数组,校验是否完成上传
  422. const chunksUploadedList = []
  423. // 直接计算一共多少个分块
  424. uploadFileList.value.forEach((item) => {
  425. allChunks.value += item.chunks.length
  426. })
  427. console.log('所有文件加起来一共有多少个分块?', allChunks.value, uploadFileList.value)
  428. for (const item of uploadFileList.value) {
  429. const md5 = item.md5
  430. uploadChunks.value = 0
  431. if (item.userFileId == undefined || item.userFileId == null) {
  432. startTime.value = new Date().getTime() // 开始时间戳(毫秒
  433. totalSize.value = item.size // 文件总大小
  434. uploadedSize.value = 0
  435. for (let i = 0; i < item.chunks.length; i++) {
  436. console.log('上传第', i + 1, '个分片')
  437. let chunk = item.chunks[i]
  438. console.log('去上传...', i + 1, chunk)
  439. // 检查是否暂停
  440. console.log('看看...', ' 列表 ', pauseFlags.value, ' md5 ', md5)
  441. while (pauseFlags.value[md5] && true == pauseFlags.value[md5]) {
  442. await new Promise((resolve) => setTimeout(resolve, 100)) // 等待 1 秒后再次检查
  443. }
  444. chunkPromises.push(
  445. await uploadFilesChunk(
  446. {
  447. md5,
  448. chunk,
  449. chunkIndex: i + 1,
  450. fileSuffix: item.fileSuffix,
  451. chunkSize: item.chunks.length,
  452. fileName: item.name
  453. },
  454. () => {
  455. chunksUploaded.value++
  456. uploadChunks.value++
  457. // successfulChunkPercents.value = 100 * (uploadChunks.value / allChunks.value).toFixed(2)
  458. uploadedSize.value += chunk.size // 更新已上传大小
  459. const remainingTime = estimateRemainingTime()
  460. console.log(`预计剩余时间: ${formatTime(remainingTime)}`)
  461. item.percents = (100 * (uploadChunks.value / item.chunks.length)).toFixed(2)
  462. item.time = formatTime(remainingTime)
  463. console.log(
  464. '执行了自增...',
  465. '分片长度',
  466. item.chunks.length,
  467. '执行到多少了',
  468. uploadChunks.value,
  469. '百分比',
  470. 100 * (uploadChunks.value / item.chunks.length).toFixed(2)
  471. )
  472. console.log('this.uploadChunks:', uploadChunks.value)
  473. console.log('this.allChunks:', allChunks.value)
  474. console.log('进度:', (uploadChunks.value / allChunks.value).toFixed(2))
  475. }
  476. )
  477. )
  478. }
  479. }
  480. console.log('this.chunkUploaded是:', chunksUploaded.value)
  481. chunksUploadedList.push(chunksUploaded.value) // 存储不同文件的上传分块数量
  482. chunksUploaded.value = 0
  483. }
  484. await Promise.all(chunkPromises)
  485. console.log('上传完成!')
  486. console.log('chunksUploadList是:', chunksUploadedList)
  487. let mergeResults = []
  488. for (let i = 0; i < chunksUploadedList.length; i++) {
  489. console.log('this.uploadFileList' + i + '是:' + uploadFileList.value[i].chunks.length)
  490. console.log('chunksUploadedList' + i + '是:' + chunksUploadedList[i])
  491. if (uploadFileList.value[i].chunks.length === chunksUploadedList[i]) {
  492. const mergeResult = await axios.post(
  493. // `/api/webapp/disk/minio/merge?md5=${uploadFileList.value[i].md5}&fileSuffix=${uploadFileList.value[i].fileSuffix}&chunkTotal=${chunksUploadedList[i]}`
  494. sysConfig.API_URL+`/api/webapp/minio/merge?md5=${uploadFileList.value[i].md5}&fileSuffix=${uploadFileList.value[i].fileSuffix}&chunkTotal=${chunksUploadedList[i]}&fileName=${uploadFileList.value[i].name}&fileSize=${uploadFileList.value[i].size}`,
  495. null,
  496. { headers: { Token: tool.data.get('TOKEN') } }
  497. )
  498. // if (mergeResult.data.startsWith('[miss_chunk]')) {
  499. // alert('文件缺失,请重新上传')
  500. // return
  501. // }
  502. console.log('合并结果1:', mergeResult)
  503. uploadFileList.value[i].userFileId = mergeResult.data.userFileId
  504. // mergeResults.push(mergeResult)
  505. }
  506. }
  507. console.log('合并结果2:', uploadFileList.value)
  508. upLoadTag.value = false
  509. // 上传完成,清理任务状态
  510. delete uploadingTasks.value[md5]
  511. emit('onUpLoading', upLoadTag.value)
  512. let finalRes = true
  513. // emit('onSuccess', uploadFileList.value)
  514. // for (const result of mergeResults) {
  515. // if (result.data === '失败') {
  516. // finalRes = false
  517. // alert('上传失败!请重新上传')
  518. // return
  519. // } else {
  520. // alert('上传成功!')
  521. // successfulChunkPercents.value = 100
  522. // clearFileList()
  523. // // getList()
  524. // return
  525. // }
  526. // }
  527. }
  528. // 多文件上传分片 ============
  529. const uploadFilesChunk = async (data, onSuccess) => {
  530. console.log('进入了uploadFileChunk方法...')
  531. let retryTime = 5 //重试次数
  532. const formData = new FormData()
  533. formData.append('md5', data.md5)
  534. // formData.append('md5', md5)
  535. formData.append('chunkIndex', data.chunkIndex)
  536. formData.append('chunk', data.chunk)
  537. formData.append('chunkSize', data.chunkSize)
  538. formData.append('fileSuffix', data.fileSuffix)
  539. formData.append('fileName', data.fileName)
  540. return axios
  541. .post(sysConfig.API_URL+'/api/webapp/minio/upload', formData, {
  542. headers: { 'Content-Type': 'multipart/form-data', Token: tool.data.get('TOKEN') }
  543. })
  544. .then((res) => onSuccess())
  545. .catch((error) => {
  546. console.log('上传分片失败了...', error)
  547. if (retryTime > 0) {
  548. retryTime--
  549. return uploadChunk(data, onSuccess)
  550. }
  551. })
  552. }
  553. // 上传分片 旧
  554. const uploadChunk = (data, onSuccess) => {
  555. let retryTime = 5 //重试次数
  556. const formData = new FormData()
  557. // formData.append('identifier', fileMd5.value)
  558. formData.append('md5', data.md5)
  559. // formData.append('md5', md5)
  560. formData.append('chunkIndex', data.chunkIndex)
  561. formData.append('chunk', data.chunk)
  562. formData.append('chunkSize', data.chunkSize)
  563. formData.append('fileSuffix', data.fileSuffix)
  564. formData.append('fileName', data.fileName)
  565. return axios
  566. .post(sysConfig.API_URL+'/api/webapp/minio/upload', formData, {
  567. headers: { 'Content-Type': 'multipart/form-data', Token: tool.data.get('TOKEN') }
  568. })
  569. .then((res) => onSuccess())
  570. .catch((error) => {
  571. if (retryTime > 0) {
  572. retryTime--
  573. return uploadChunk(data, onSuccess)
  574. }
  575. })
  576. }
  577. // 删除文件
  578. const deleteFile = (url) => {
  579. axios
  580. .get(sysConfig.API_URL+`/api/webapp/disk/delete?url=` + url)
  581. .then((res) => {
  582. console.log('删除文件:', res.data)
  583. alert(res.data ? '删除成功!' : '删除失败!')
  584. // getList()
  585. })
  586. .catch((error) => {
  587. console.log('删除失败:', error)
  588. })
  589. console.log('url是:', url)
  590. }
  591. const downloadFile = (url) => {
  592. window.location.href = url
  593. }
  594. /**
  595. * 判断文件名的后缀是否存在于指定数组中
  596. * @param {string} fileName - 文件名(如 "sss.mp4")
  597. * @param {Array<string>} suffixArray - 后缀数组(如 ['jpg','png','pdf','mp4',...])
  598. * @returns {boolean} - 是否存在
  599. */
  600. const isFileSuffixInArray = (fileName, suffixArray) => {
  601. // 获取文件后缀(包括点号)
  602. const fileSuffix = fileName.split('.').pop();
  603. // 检查后缀是否在数组中(包含点号匹配)
  604. return suffixArray.includes(fileSuffix);
  605. }
  606. const beforeUpload = async (file) => {
  607. console.log('选择了文件', file)
  608. spinning.value = true
  609. // upLoadfileLists
  610. if(!isFileSuffixInArray( file.name,props.upLoadfileLists)){
  611. const fileSuffix = file.name.split('.').pop();
  612. message.error('不允许上传后缀' +fileSuffix)
  613. spinning.value = false
  614. return false
  615. }
  616. if(uploadFileList.value.length >= props.uploadCount){
  617. message.error('超过上传条目' + props.uploadCount + "条")
  618. spinning.value = false
  619. return false
  620. }
  621. let upFile = await handleFileChange({ raw: file })
  622. console.log('可以上传的文件内容是', upFile)
  623. // 检查本地 uploadFileList 是否已存在该 md5 文件
  624. const exists = uploadFileList.value.some((item) => item.md5 === upFile.md5)
  625. if (exists) {
  626. message.warning('该文件已存在,不再重复添加')
  627. spinning.value = false
  628. return false
  629. }
  630. spinning.value = false
  631. // await checkMd5List(upFile)
  632. uploadFileList.value.push(upFile)
  633. // emit('onSuccess', uploadFileList.value)
  634. // const activeTasks = Object.keys(uploadingTasks.value).filter((key) => uploadingTasks.value[key])
  635. //
  636. // if (activeTasks.length < 2) {
  637. // pauseFlags.value[upFile.md5] = false
  638. // uploadingTasks.value[upFile.md5] = true
  639. // } else {
  640. // pauseFlags.value[upFile.md5] = true
  641. // message.warning('上传队列已满,将进入暂停状态')
  642. // }
  643. // await uploadSingleFile(upFile)
  644. // spinning.value = false
  645. return false // 阻止默认上传
  646. }
  647. const handleChange = (info) => {
  648. const { file } = info
  649. if (file.status === 'removed') {
  650. fileList.value = []
  651. clearFileList()
  652. }
  653. }
  654. const uploadSingleFile = async (fileObj) => {
  655. const file = fileObj
  656. const md5 = file.md5
  657. const index = uploadFileList.value.findIndex((item) => item.md5 === md5)
  658. if (index === -1) return
  659. const item = uploadFileList.value[index]
  660. if (!item || item.userFileId) return
  661. // // 如果是暂停状态则不执行上传
  662. // while (pauseFlags.value[md5]) {
  663. // await new Promise((resolve) => setTimeout(resolve, 500))
  664. // }
  665. // 添加到正在上传任务中
  666. uploadingTasks.value[md5] = true
  667. file.startTime = new Date().getTime()
  668. file.uploadedSize = 0
  669. const chunkPromises = []
  670. for (let i = 0; i < item.chunks.length; i++) {
  671. let chunk = item.chunks[i]
  672. while (pauseFlags.value[md5]) {
  673. await new Promise((resolve) => setTimeout(resolve, 500))
  674. }
  675. chunkPromises.push(
  676. await uploadFilesChunk(
  677. {
  678. md5,
  679. chunk,
  680. chunkIndex: i + 1,
  681. fileSuffix: item.fileSuffix,
  682. chunkSize: item.chunks.length,
  683. fileName: item.name
  684. },
  685. () => {
  686. // chunksUploaded.value++
  687. // uploadChunks.value++
  688. // successfulChunkPercents.value = 100 * (uploadChunks.value / allChunks.value).toFixed(2)
  689. item.uploadedSize += chunk.size // 更新已上传大小
  690. const remainingTime = estimateRemainingTime(item.startTime, item.uploadedSize, item.size)
  691. console.log(`预计剩余时间: ${formatTime(remainingTime)}`)
  692. // item.percents = (100 * (uploadChunks.value / item.chunks.length)).toFixed(2)
  693. item.time = formatTime(remainingTime)
  694. const percent = ((i + 1) / item.chunks.length) * 100
  695. item.percents = percent.toFixed(2)
  696. console.log(`我得名字: `, item.name, ' i ', i, ' item.chunks.length ', item.chunks.length)
  697. // item.time = formatTime(estimateRemainingTime())
  698. }
  699. )
  700. )
  701. }
  702. await Promise.all(chunkPromises)
  703. // 合并分片
  704. const mergeResult = await axios.post(
  705. sysConfig.API_URL+`/api/webapp/minio/merge?md5=${md5}&fileSuffix=${item.fileSuffix}&chunkTotal=${item.chunks.length}&fileName=${item.name}&fileSize=${item.size}`,
  706. null,
  707. { headers: { Token: tool.data.get('TOKEN') } }
  708. )
  709. uploadFileList.value[index].userFileId = mergeResult.data.userFileId
  710. uploadFileList.value[index].percents = 100
  711. uploadingTasks.value[item.md5] = false
  712. // item.time = '上传完成'
  713. // 尝试恢复一个被暂停的任务
  714. autoResumePausedUpload()
  715. // upLoadTag.value = false
  716. emit('onSuccess', uploadFileList.value)
  717. }
  718. // watch(
  719. // () => uploadFileList.value,
  720. // (newValue) => {
  721. // emit('onSuccess', newValue)
  722. // },
  723. // { immediate: true }
  724. // )
  725. const autoResumePausedUpload = async () => {
  726. const activeTasks = Object.keys(uploadingTasks.value).filter((key) => uploadingTasks.value[key])
  727. if (activeTasks.length >= 2) return
  728. // 查找第一个被暂停的文件
  729. for (let i = 0; i < uploadFileList.value.length; i++) {
  730. const item = uploadFileList.value[i]
  731. if (item.percents > 0 && item.percents < 100 && pauseFlags.value[item.md5] === true) {
  732. console.log(`自动恢复 ${item.name}`)
  733. pauseFlags.value[item.md5] = false
  734. uploadingTasks.value[item.md5] = true
  735. await uploadSingleFile(item)
  736. break
  737. }
  738. }
  739. }
  740. const customRequest = () => {}
  741. const getList = () => {
  742. axios
  743. .get('http://127.0.0.1:9000/api/webapp/disk/minio/list')
  744. .then((res) => {
  745. this.uploadList = res.data
  746. console.log('获取列表结果成功:', res.data)
  747. })
  748. .catch((error) => {
  749. console.error('获取列表失败:', error)
  750. })
  751. }
  752. const clearFileList = () => {
  753. // successfulChunkPercents.value = 0
  754. uploadFileList.value = []
  755. // fileList.value = []
  756. }
  757. const open = () => {
  758. clearFileList()
  759. }
  760. onMounted(() => {
  761. nextTick(() => {
  762. // if ( fileUpload.value && fileUpload.value.$el.querySelector('input')) {
  763. // fileUpload.value.$el.querySelector('input').webkitdirectory = true;
  764. // }
  765. });
  766. });
  767. onUpdated(() => {
  768. console.log('组件已更新')
  769. })
  770. const getUploadFileList = () => {
  771. return uploadFileList.value
  772. }
  773. defineExpose({open, getUploadFileList})
  774. </script>
  775. <style scoped lang="less">
  776. /*大力水手*/
  777. .mydiv {
  778. :deep(.ant-upload-btn) {
  779. display: block !important;
  780. }
  781. }
  782. </style>