Open klren0312 opened 10 months ago
<template> <div class="upload-block"> <template v-if="!modelValue"> <el-upload v-if="uploadStatus === 0" class="upload-resource" action="#" :multiple="false" :limit="1" :auto-upload="true" :show-file-list="false" :file-list="uploadFileList" :http-request="uploadFile" :before-upload="handleBeforeUpload" accept=".jpg,.png,.mp4,.jpeg" > <el-button link type="primary">上传</el-button> </el-upload> <div class="upload-process flex" v-else-if="uploadStatus === 1"> <el-progress style="width: 100%;" type="line" :percentage="uploadProcess"></el-progress> <el-icon @click="doPauseOrPlay(true)" v-if="!isUploadPlay"><VideoPlay /></el-icon> <el-icon @click="doPauseOrPlay(false)" v-if="isUploadPlay"><VideoPause /></el-icon> </div> </template> <div v-else>{{ fileName }}</div> </div> </template> <script setup lang="ts"> import { onMounted, onUnmounted, ref, watch } from 'vue' import { createClient } from '/@/utils/oss' import { ElMessage, UploadInstance, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus' import OSS, { Checkpoint } from 'ali-oss' defineOptions({ name: 'ResourceUpload' }) const props = defineProps<{ modelValue: string fileName: string downloadUrl: string | undefined }>() const $emit = defineEmits(['update:modelValue', 'update:fileName', 'fileChange']) const uploadStatus = ref(0) // 0-未上传 1-上传中 2-上传成功 const uploadProcess = ref(0) const uploadCheckPoint = ref<Checkpoint>() const isUploadPlay = ref(false) // 是否正在上传 const uploadPath = ref('') // 上传的路径 const theUploadFile = ref<UploadRawFile>() const uploadRef = ref<UploadInstance>() const uploadFileList = ref<UploadUserFile[]>([]) const client = ref<OSS | null | undefined>() watch(() => props.modelValue, (nv) => { if (!nv) { uploadStatus.value = 0 } }) onMounted(() => { window.addEventListener('offline', offlineHandle) }) onUnmounted(() => { window.removeEventListener('offline', offlineHandle) }) /** * 断网时,暂停上传 */ const offlineHandle = () => { if (client.value && uploadProcess.value) { (client.value as any).cancel() isUploadPlay.value = false } } /** * 上传开始/暂停 * @param isPlay true-开始 false-暂停 */ const doPauseOrPlay = (isPlay: boolean) => { if (isPlay) { resumeUploadFile() isUploadPlay.value = true } else { (client.value as any).cancel() isUploadPlay.value = false } } const uploadFile = async (options: UploadRequestOptions) => { client.value = await createClient() if (client.value) { isUploadPlay.value = true uploadStatus.value = 1 const theFile = options.file const suffix = options.file.name.split('.').pop() const uuid = new Date().getTime() + Math.random().toString(36).substr(2) const path = 'materials/resource/' + uuid + '.' + suffix uploadPath.value = path theUploadFile.value = theFile try { const result = await client.value.multipartUpload(path, theFile, { // 设置并发上传的分片数量。 parallel: 4, // 设置分片大小。默认值为1 MB,最小值为100 KB。 partSize: 1024 * 1024, headers: { 'Cache-Control': 'no-cache', 'Content-Encoding': 'utf-8', 'x-oss-forbid-overwrite': 'true' }, progress: (p: number, checkpoint: Checkpoint) => { const progress = Math.floor(p * 100) if (progress === 100) { uploadProcess.value = progress uploadStatus.value = 2 } else { uploadCheckPoint.value = checkpoint uploadProcess.value = progress } } }) if (result.res.status === 200) { $emit('update:fileName', options.file.name) $emit('update:modelValue', path) $emit('fileChange') clearCache() isUploadPlay.value = false } } catch (error) { console.error(error) if (uploadProcess.value === 0) { doUploadError() } } } else { doUploadError() } } /** * 清空缓存 */ const clearCache = () => { uploadCheckPoint.value = undefined client.value = undefined uploadPath.value = '' theUploadFile.value = undefined } /** * 上传失败操作 */ const doUploadError = () => { isUploadPlay.value = false uploadStatus.value = 0 ElMessage.error('上传失败, 请重新上传') uploadRef.value?.clearFiles() clearCache() } /** * 上传前校验文件 */ const handleBeforeUpload = (theFile: UploadRawFile) => { const suffix = theFile.name.split('.').pop() || '' const fileName = theFile.name.split('.').shift() || '' const extArr = [ 'jpg', 'png', 'mp4', 'jpeg' ] if (!extArr.includes(suffix)) { ElMessage.error('资源格式错误') return false } else if (!/^[\u4E00-\u9FA5A-Za-z0-9]+$/.test(fileName)) { // 文件名称支持中英文和数字 ElMessage.error('封面图名称只支持中英文和数字') return false } else { return true } } /** * @description 恢复上传 * @param {*} item 文件信息 * @param {*} checkpoint 分片信息 */ const resumeUploadFile = async () => { if (uploadCheckPoint.value) { try { if (uploadProcess.value < 100 && client.value && theUploadFile.value) { try { isUploadPlay.value = true const result = await client.value.multipartUpload(uploadPath.value, theUploadFile.value, { // 设置并发上传的分片数量。 parallel: 4, // 设置分片大小。默认值为1 MB,最小值为100 KB。 partSize: 1024 * 1024, headers: { 'Cache-Control': 'no-cache', 'Content-Encoding': 'utf-8', 'x-oss-forbid-overwrite': 'true' }, checkpoint: uploadCheckPoint.value, progress: (p: number, checkpoint: Checkpoint) => { const progress = Math.floor(p * 100) if (progress === 100) { uploadProcess.value = progress uploadStatus.value = 2 } else { uploadCheckPoint.value = checkpoint uploadProcess.value = progress } } }) if (result.res.status === 200) { $emit('update:fileName', theUploadFile.value.name) $emit('update:modelValue', uploadPath.value) $emit('fileChange') clearCache() isUploadPlay.value = false } } catch (error) { await resetUpload(error) } } } catch { doUploadError() } } else { doUploadError() } } /** * 报错后重置上传 */ const resetUpload = async (err: any) => { const msg = JSON.stringify(err) if (msg.indexOf('Error') !== -1) { if (client.value) { ( client.value as any).cancel() } client.value = await createClient() await resumeUploadFile() } } </script> <style lang="scss" scoped> .upload-block, .upload-process { width: 100%; } </style>