index.vue 27 KB

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