|
@@ -1,339 +0,0 @@
|
|
|
-<template>
|
|
|
- <div id="global-uploader" :class="{ 'global-uploader-single': !global }">
|
|
|
- <!-- 上传 -->
|
|
|
- <HcUploader
|
|
|
- ref="uploaderRef"
|
|
|
- class="uploader-app"
|
|
|
- :options="optionsValue"
|
|
|
- :file-status-text="fileStatusText"
|
|
|
- :auto-start="false"
|
|
|
- @file-added="onFileAdded"
|
|
|
- @file-success="onFileSuccess"
|
|
|
- @file-progress="onFileProgress"
|
|
|
- @file-error="onFileError"
|
|
|
- >
|
|
|
- <HcUploaderUnsupport/>
|
|
|
- <HcUploaderBtn id="global-uploader-btn" ref="uploadBtnRef">选择文件</HcUploaderBtn>
|
|
|
- <HcUploaderList v-show="panelShow">
|
|
|
- <template #default="{ fileList }">
|
|
|
- <div class="file-panel" :class="{ collapse: collapse }">
|
|
|
- <div class="file-title">
|
|
|
- <div class="title">文件列表 {{fileList.length > 0 ? `(${fileList.length})` : ''}}</div>
|
|
|
- <div class="operate">
|
|
|
- <el-button :title="collapse ? '展开' : '折叠'" link @click="collapse = !collapse">
|
|
|
- <i :class="collapse ? 'ri-fullscreen-line' : 'ri-subtract-line'"/>
|
|
|
- </el-button>
|
|
|
- <el-button title="关闭" link @click="close">
|
|
|
- <i class="ri-close-line"/>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <ul class="file-list">
|
|
|
- <li v-for="file in fileList" :key="file.id" class="file-item">
|
|
|
- <HcUploaderFile ref="files" :class="['file_' + file.id, customStatus]" :file="file" :list="true"/>
|
|
|
- </li>
|
|
|
- <div v-if="!fileList.length" class="no-file">
|
|
|
- <i class="ri-file-text-line" style="font-size: 24px"/>
|
|
|
- 暂无待上传文件
|
|
|
- </div>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </HcUploaderList>
|
|
|
- </HcUploader>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import {computed, nextTick, onMounted, ref} from 'vue'
|
|
|
-import {getTokenHeader} from '~src/api/request/header';
|
|
|
-import HcUploader from './components/uploader.vue'
|
|
|
-import HcUploaderBtn from './components/btn.vue'
|
|
|
-import HcUploaderUnsupport from './components/unsupport.vue'
|
|
|
-import HcUploaderList from './components/list.vue'
|
|
|
-import HcUploaderFile from './components/file.vue'
|
|
|
-import {getArrValue, getObjValue, getFileSuffix} from "js-fast-way";
|
|
|
-import {ElNotification} from "element-plus";
|
|
|
-import {generateMD5} from './common/md5'
|
|
|
-import Bus from '~src/plugins/bus.js'
|
|
|
-
|
|
|
-const acceptType = 'image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword'
|
|
|
-
|
|
|
-export default {
|
|
|
- name: 'GlobalUploader',
|
|
|
- props: {
|
|
|
- global: {
|
|
|
- type: Boolean,
|
|
|
- default: true
|
|
|
- }
|
|
|
- },
|
|
|
- components: {
|
|
|
- HcUploader,
|
|
|
- HcUploaderBtn,
|
|
|
- HcUploaderUnsupport,
|
|
|
- HcUploaderList,
|
|
|
- HcUploaderFile
|
|
|
- },
|
|
|
- emits: ['fileAdded', 'fileSuccess', 'fileError', 'filesChange', 'fileProgress'],
|
|
|
- setup(props, {emit}) {
|
|
|
- const optionsValue = {
|
|
|
- target: '/api/blade-resource/largeFile/endpoint/upload-file',
|
|
|
- chunkSize: '2048000',
|
|
|
- fileParameterName: 'file',
|
|
|
- maxChunkRetries: 3,
|
|
|
- headers: getTokenHeader(),
|
|
|
- // 是否开启服务器分片校验
|
|
|
- testChunks: true,
|
|
|
- testMethod: 'POST',
|
|
|
- // 服务器分片校验函数,秒传及断点续传基础
|
|
|
- checkChunkUploadedByResponse: (chunk, message) => {
|
|
|
- let skip = false
|
|
|
- try {
|
|
|
- let objMessage = getObjValue(JSON.parse(message))
|
|
|
- if (objMessage['skipUpload']) {
|
|
|
- skip = true
|
|
|
- } else {
|
|
|
- skip = (getArrValue(objMessage['uploaded'])).indexOf(chunk.offset + 1) >= 0
|
|
|
- }
|
|
|
- } catch (e) {}
|
|
|
- return skip
|
|
|
- },
|
|
|
- query: (file, chunk) => {
|
|
|
- return {...file.params}
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const initOptions = ({target, fileName, maxChunk, accept}) => {
|
|
|
- // 自定义上传url
|
|
|
- if (target) {
|
|
|
- uploader.value.opts.target = target
|
|
|
- }
|
|
|
- // 自定义文件上传参数名
|
|
|
- if (fileName) {
|
|
|
- uploader.value.opts.fileParameterName = fileName
|
|
|
- }
|
|
|
- // 并发上传数量
|
|
|
- if (maxChunk) {
|
|
|
- uploader.value.opts.maxChunkRetries = maxChunk
|
|
|
- }
|
|
|
- // 自定义文件上传类型
|
|
|
- if (accept) {
|
|
|
- nextTick(() => {
|
|
|
- let input = document.querySelector('#global-uploader-btn input')
|
|
|
- input.setAttribute('accept', accept ? accept : acceptType)
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- const fileStatusText = {
|
|
|
- success: '上传成功',
|
|
|
- error: '上传失败',
|
|
|
- uploading: '上传中',
|
|
|
- paused: '已暂停',
|
|
|
- waiting: '等待上传'
|
|
|
- }
|
|
|
- const customStatus = ref('')
|
|
|
- const panelShow = ref(false)
|
|
|
- const collapse = ref(false)
|
|
|
- const uploaderRef = ref()
|
|
|
- const uploadBtnRef = ref()
|
|
|
-
|
|
|
- const uploader = computed(() => uploaderRef.value?.uploader)
|
|
|
-
|
|
|
- let customParams = {}
|
|
|
-
|
|
|
- async function onFileAdded(file) {
|
|
|
- panelShow.value = true
|
|
|
- trigger('fileAdded')
|
|
|
- // 将额外的参数赋值到每个文件上,以不同文件使用不同params的需求
|
|
|
- file.params = {
|
|
|
- ...customParams,
|
|
|
- objectType: getFileSuffix(file.name),
|
|
|
- fileType: file.fileType,
|
|
|
- }
|
|
|
- // 计算MD5
|
|
|
- const md5 = await computeMD5(file)
|
|
|
- startUpload(file, md5)
|
|
|
- }
|
|
|
-
|
|
|
- function computeMD5(file) {
|
|
|
- // 文件状态设为"计算MD5"
|
|
|
- statusSet(file.id, 'md5')
|
|
|
- // 暂停文件
|
|
|
- file.pause()
|
|
|
- // 计算MD5时隐藏”开始“按钮
|
|
|
- setResumeStyle(file.id, 'none')
|
|
|
- nextTick(() => {
|
|
|
- document.querySelector(`.custom-status-${file.id}`).innerText = '校验MD5中'
|
|
|
- })
|
|
|
- // 开始计算MD5
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- generateMD5(file, {
|
|
|
- onSuccess(md5) {
|
|
|
- statusRemove(file.id)
|
|
|
- resolve(md5)
|
|
|
- },
|
|
|
- onError() {
|
|
|
- error(`文件${file.name}读取出错,请检查该文件`)
|
|
|
- file.cancel()
|
|
|
- statusRemove(file.id)
|
|
|
- reject()
|
|
|
- }
|
|
|
- })
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- const setResumeStyle = (id, val = 'none') => {
|
|
|
- nextTick(() => {
|
|
|
- try {
|
|
|
- document.querySelector(`.file_${id} .uploader-file-resume`).style.display = val
|
|
|
- } catch (e) {}
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- // md5计算完毕,开始上传
|
|
|
- const beforeFileNum = ref(0)
|
|
|
- function startUpload(file, md5) {
|
|
|
- const fileList = uploader.value.fileList;
|
|
|
- //判断是否满足条件
|
|
|
- const result = fileList.every(({uniqueIdentifier}) => {
|
|
|
- return uniqueIdentifier !== md5
|
|
|
- })
|
|
|
- if (result) {
|
|
|
- file.uniqueIdentifier = md5
|
|
|
- setResumeStyle(file.id,'')
|
|
|
- beforeFileNum.value ++;
|
|
|
- file.resume()
|
|
|
- trigger('fileProgress', true)
|
|
|
- } else {
|
|
|
- file.cancel()
|
|
|
- error('请不要重复上传相同文件')
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- //上传完成
|
|
|
- const finishFileNum = ref(0)
|
|
|
- function onFileSuccess(rootFile, file, response, chunk) {
|
|
|
- let res = JSON.parse(response)
|
|
|
- // 服务端自定义的错误(即http状态码为200,但是是错误的情况),这种错误是Uploader无法拦截的
|
|
|
- if (res.code !== 200) {
|
|
|
- errorFileNum.value ++;
|
|
|
- error(res.msg)
|
|
|
- // 文件状态设为“失败”
|
|
|
- statusSet(file.id, 'failed')
|
|
|
- } else {
|
|
|
- finishFileNum.value ++;
|
|
|
- trigger('fileSuccess', getObjValue(res.data))
|
|
|
- }
|
|
|
- if (beforeFileNum.value === (finishFileNum.value + errorFileNum.value)) {
|
|
|
- trigger('filesChange')
|
|
|
- trigger('fileProgress', false)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function onFileProgress(rootFile, file, chunk) {
|
|
|
- console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
|
|
|
- }
|
|
|
-
|
|
|
- //上传失败
|
|
|
- const errorFileNum = ref(0)
|
|
|
- function onFileError(rootFile, file, response, chunk) {
|
|
|
- errorFileNum.value ++;
|
|
|
- error(response)
|
|
|
- trigger('fileError')
|
|
|
- }
|
|
|
-
|
|
|
- function close() {
|
|
|
- finishFileNum.value = 0
|
|
|
- beforeFileNum.value = 0
|
|
|
- errorFileNum.value = 0
|
|
|
- uploader.value.cancel()
|
|
|
- panelShow.value = false
|
|
|
- }
|
|
|
-
|
|
|
- //新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
|
|
|
- function statusSet(id, status) {
|
|
|
- const statusMap = {
|
|
|
- md5: {text: '校验MD5', bgc: '#fff'},
|
|
|
- merging: {text: '合并中', bgc: '#e2eeff'},
|
|
|
- transcoding: {text: '转码中', bgc: '#e2eeff'},
|
|
|
- failed: {text: '上传失败', bgc: '#e2eeff'}
|
|
|
- }
|
|
|
- customStatus.value = status
|
|
|
- nextTick(() => {
|
|
|
- const statusTag = document.createElement('p')
|
|
|
- statusTag.className = `custom-status-${id} custom-status`
|
|
|
- statusTag.innerText = statusMap[status].text
|
|
|
- statusTag.style.backgroundColor = statusMap[status].bgc
|
|
|
-
|
|
|
- const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
|
|
|
- statusWrap.appendChild(statusTag)
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- function statusRemove(id) {
|
|
|
- customStatus.value = ''
|
|
|
- nextTick(() => {
|
|
|
- const statusTag = document.querySelector(`.custom-status-${id}`)
|
|
|
- statusTag.remove()
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- function trigger(key, data) {
|
|
|
- Bus.emit(key, data)
|
|
|
- emit(key, data)
|
|
|
- }
|
|
|
-
|
|
|
- function error(msg) {
|
|
|
- ElNotification({
|
|
|
- title: '错误',
|
|
|
- message: msg,
|
|
|
- type: 'error',
|
|
|
- duration: 2000
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- onMounted(() => {
|
|
|
- finishFileNum.value = 0
|
|
|
- beforeFileNum.value = 0
|
|
|
- errorFileNum.value = 0
|
|
|
- nextTick(() => {
|
|
|
- let input = document.querySelector('#global-uploader-btn input')
|
|
|
- input.setAttribute('accept', acceptType)
|
|
|
- })
|
|
|
- //打开上传器
|
|
|
- Bus.on('openUploader', ({params = {}, options = {}}) => {
|
|
|
- customParams = params
|
|
|
- initOptions(options)
|
|
|
- if (uploadBtnRef.value) {
|
|
|
- uploadBtnRef.value.$el.click()
|
|
|
- }
|
|
|
- })
|
|
|
- //关闭上传器
|
|
|
- Bus.on('closeUploader', () => {
|
|
|
- close()
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- return {
|
|
|
- optionsValue,
|
|
|
- initOptions,
|
|
|
- fileStatusText,
|
|
|
- customStatus,
|
|
|
- panelShow,
|
|
|
- collapse,
|
|
|
- uploaderRef,
|
|
|
- uploadBtnRef,
|
|
|
- onFileAdded,
|
|
|
- onFileSuccess,
|
|
|
- onFileProgress,
|
|
|
- onFileError,
|
|
|
- close
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style lang="scss">
|
|
|
-@import "./style/index";
|
|
|
-</style>
|