代码拉取完成,页面将自动刷新
对于文件上传,存储都支持分片上传了,对已经支持分片上传的存储增加分片上传功能
export function getFileTypeFromUrl(url) {
// 使用正则表达式从链接中提取文件后缀
const extension = url.split('.').pop().toLowerCase();
// 常见的视频文件后缀
const videoExtensions = ['mp4', 'avi', 'mkv', 'mov', 'wmv'];
// 常见的图片文件后缀
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
// 常见的音频文件后缀
const audioExtensions = ['mp3', 'wav', 'ogg', 'flac', 'aac'];
// 检查后缀是否属于视频、图片或音频类型
if (videoExtensions.includes(extension)) {
return 'video';
} else if (imageExtensions.includes(extension)) {
return 'image';
} else if (audioExtensions.includes(extension)) {
return 'audio';
} else {
// 如果不属于以上任何一种类型,返回文件后缀
return extension;
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
<!--
* @Author: 舒 shd_cn@163.com
* @Date: 2023-09-02 14:31:40
* @Description: 切片上传
-->
<template>
<div class="slice-upload" v-loading="loading">
<!-- 遍历显示已上传的文件列表 -->
<div class="img-list-item " :style="{ width: w, height: h }" v-for="(item, index) in fileList" :key="index">
<template v-if="item.file">
<div class="remove" @click="removeFile(item, index)" v-if="!disabled">
<el-icon size="20">
<CircleClose />
</el-icon>
</div>
<!-- 根据文件类型显示不同的内容 -->
<video v-if="getFileTypeFromUrl(item.url) === 'video'" style="width: 100%; height: 100%;" controls
:src="item.url"> 您的浏览器不支持视频播放</video>
<el-image v-if="getFileTypeFromUrl(item.url) === 'image'" @mouseover="picSrcList = [item.url]"
:preview-src-list="picSrcList" style="width: 100%; height: 100%;" :src="item.url"
fit="cover"></el-image>
<div v-else class="other ">
<el-icon size="30">
<Document />
</el-icon>
<div class="reveal">
<el-progress v-if="item?.progress < 100" :striped="true" :striped-flow="true" class="progress"
:percentage="item.progress" :color="customColors" />
</div>
</div>
</template>
<template v-else>
<div class="remove" @click="removeFile(item, index)" v-if="!disabled">
<el-icon size="20">
<CircleClose />
</el-icon>
</div>
<!-- 根据文件类型显示不同的内容 -->
<video v-if="getFileTypeFromUrl(item) === 'video'" style="width: 100%; height: 100%;" controls :src="item">
您的浏览器不支持视频播放
</video>
<el-image v-if="getFileTypeFromUrl(item) === 'image'" @mouseover="picSrcList = [item]"
:preview-src-list="picSrcList" style="width: 100%; height: 100%;" :src="item" fit="cover"></el-image>
<div v-else class="other ">
<el-icon size="30">
<Document />
</el-icon>
</div>
</template>
</div>
<!-- 文件上传组件 -->
<el-upload class="upload-holder" v-if="fileList.length < limit && !disabled" drag :accept="limitTypes"
:file-list="fileList" :disabled="disabled || fileList.length >= limit" :limit="limit"
:before-upload="beforeUpload" list-type="picture-card" :show-file-list="false" :multiple="limit > 1"
:http-request="uploadFileContinue">
<el-icon>
<Plus />
</el-icon>
<template #tip v-if="tip">
<div class="el-upload__tip">
{{ tip }}
</div>
</template>
</el-upload>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus';
import { getFileTypeFromUrl } from '@/utils/common-tools'
const props = defineProps({
// 绑定值
modelValue: {
type: [Array, String],
default: () => '',
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
// 限制文件数量
limit: {
type: Number,
default: 1,
},
// 宽度
w: {
type: String,
default: '150px'
},
// 高度
h: {
type: String,
default: '150px'
},
// 限制文件大小 单位MB
limitSize: {
type: Number,
default: 1,
},
// 限制文件类型
limitTypes: {
type: String,
default: '',
},
// 提示文字
tip: {
type: String,
default: '',
},
});
const picSrcList = ref([])
const loading = ref(false)
const { limitSize, limitTypes } = toRefs(props);
// 文件列表
const fileList = ref([]);
// 终止上传的文件列表
const abortList = ref([]);
// 限制文件大小
const limitBytes = limitSize.value * 1024 * 1024; // 将以MB为单位的文件大小限制转换为字节
const customColors = [
{ color: '#e6a23c', percentage: 30 },
{ color: '#1989fa', percentage: 60 },
{ color: '#5cb87a', percentage: 100 },
]
// 格式化文件大小
const formatSize = (bytes) => {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Byte';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
};
const emit = defineEmits([
'update:modelValue',
]);
// 监听文件列表变化,触发更新外部数据
watch(() => fileList.value, (newVal) => {
emit('update:modelValue', newVal);
}, {
deep: true,
});
watch(() => props.modelValue, (newVal) => {
console.log(newVal)
if (fileList.value.length === 0, newVal.length != 0) {
fileList.value = newVal;
}
}, {
deep: true,
immediate: true,
});
// 删除资源
const removeFile = (file, index) => {
fileList.value.splice(index, 1);
if (file.file) {
abortList.value.push(file.file);
}
};
// 上传之前的钩子函数
const beforeUpload = async (file) => {
if (fileList.value && fileList.value.length >= props.limit) {
ElMessage.error(`当前限制上传 ${props.limit} 个文件,本次选择了 ${fileList.value.length} 个文件`);
return false;
}
// const fileType = limitTypes.value.split(',')
// // 在上传之前执行的函数
// const isAllowedType = fileType.includes(file.type);
// console.log(fileType.includes(file.type))
// if (!isAllowedType && limitTypes.value.length > 0) {
// ElMessage.error(`上传文件格式不正确,请选择 ${limitTypes.value} 格式的文件`);
// return false;
// }
const isSizeValid = file.size <= limitBytes;
if (!isSizeValid) {
ElMessage.error(`上传文件大小不能超过 ${formatSize(limitSize.value)}MB`);
return false;
}
};
// 上传切片的钩子函数
const uploadFileContinue = async ({ data, file }) => {
loading.value = true
const uploadFun = useSliceUpload(file, (fileInfo) => {
loading.value = false
if (abortList.value.includes(fileInfo.file)) {
return false
}
const index = fileList.value.findIndex(item => item.file.uid === file.uid)
if (index !== -1) {
fileList.value.splice(index, 1, fileInfo);
} else {
fileList.value.push(fileInfo);
}
return true
})
await uploadFun.then(res => {
loading.value = false
}).catch(err => {
loading.value = false
console.log(err)
ElMessage.error(`上传文件出错`);
})
}
</script>
<style scoped lang="scss">
.slice-upload {
display: flex;
width: 100%;
flex-wrap: wrap;
.upload-holder {
position: relative;
display: inline-block;
}
// 视频
.img-list-item {
position: relative;
margin-right: 10px;
border-radius: 8px;
overflow: hidden;
background-color: #00000010;
.remove {
cursor: pointer;
position: absolute;
border-radius: 50%;
overflow: hidden;
right: 5px;
top: 5px;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
transition: all 0.3s;
// 高斯模糊
backdrop-filter: blur(10px);
z-index: 3;
}
.other {
width: 100%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.reveal {
width: 100%;
height: 100%;
position: absolute;
background-color: #00000020;
}
.progress {
width: 100%;
position: absolute;
bottom: 10%;
margin-left: 10%;
left: 50%;
z-index: 2;
transform: translateX(-50%);
}
}
}
.img-list-item i.del-img {
width: 20px;
height: 20px;
display: inline-block;
background: rgba(0, 0, 0, .6);
background-image: url(../assets/images/close.png);
background-size: 18px;
background-repeat: no-repeat;
background-position: 50%;
position: absolute;
top: 0;
right: 9px;
}
}
</style>
/*
* @Author: 舒 shd_cn@163.com
* @Date: 2023-09-04 09:31:02
* @Description: 切片上传
*/
import { initUpload, uploadChunk, fileMerge, abort } from '@/api/resource/oss'
const concurrentUploadLimit = 5; // 并发上传限制
const chunkSize = 10 * 1024 * 1024; // 10MB,你可以根据需要调整切片大小
// 初始化上传
const initUploadFunc = async (fileName) => {
// 调用初始化上传接口,传递文件名,返回 uploadId
try {
const response = await initUpload(fileName);
if (response.data.success) {
return response.data.data.uploadId;
} else {
ElMessage.error('初始化上传失败,请重试');
return null;
}
} catch (error) {
ElMessage.error('初始化上传失败,请重试');
return null;
}
};
// 获取当前chunk数据
const getChunkInfo = (file, index) => {
let start = index * chunkSize;
let end = Math.min(file.size, start + chunkSize);
let chunk = file.slice(start, end);
return { start, end, chunk };
};
/** 上传文件
* @param file 文件对象
* @param callback 回调函数,用于获取上传进度
* @returns return new Promise((resolve, reject) => {
* resolve(formData)
* })
*
*/
export default async function (file, callback = null) {
let formData = shallowReactive({
chunks: Math.ceil(file.size / chunkSize),
chunk: 0,
url: '',
uploadId: null,
file,
progress: 0
})
// 用于控制并发上传的队列
let uploadQueue = [];
const uploadChunkFunc = async (file, callback) => {
let hasError = false; // 用于标志是否有错误发生
let err = null
// 是否调用取消上传
let abortUpload = false;
for (let index = 0; index < formData.chunks; index++) {
if (hasError) {
// 如果已经发生错误,立即返回false,终止循环
err = '切片上传失败,请重试'
return false;
}
const { chunk } = getChunkInfo(file, index);
// 将上传切片的操作添加到队列中
uploadQueue.push(
uploadChunk(
formData.uploadId,
index + 1,
chunk
).then(res => {
// 计算上传进度
formData.progress += Math.ceil((100 / formData.chunks));
if (callback && !callback(formData)) {
!abortUpload && abort(formData.uploadId, file.name).then(res => {
abortUpload = true
})
hasError = true
err = '取消上传'
}
}).catch(error => {
ElMessage.error(`切片(${index + 1})上传失败`);
hasError = true
err = error
return false;
})
);
// 当队列长度达到并发限制或已上传完所有切片时,等待队列中的所有操作完成
if (uploadQueue.length === concurrentUploadLimit || index === formData.chunks - 1) {
await Promise.all(uploadQueue).then(res => {
uploadQueue = []
}).catch(error => {
ElMessage.error('切片上传失败,请重试');
uploadQueue = [];
hasError = true
err = error
return false;
})
}
}
return { fulfil: !hasError, err }
};
const uploadMethod = new Promise(async (resolve, reject) => {
const uploadId = await initUploadFunc(file.name);
if (uploadId !== null) {
// 初始化上传,获取 uploadId
formData.uploadId = uploadId;
if (callback && !callback(formData)) {
reject(err);
return false
}
// 上传切片
const { fulfil, err } = await uploadChunkFunc(file, callback)
if (fulfil) {
await fileMerge(formData.uploadId).then(res => {
formData.url = res.data.data.url
if (callback && !callback(formData)) {
reject(err);
return false
}
resolve(formData)
}).catch(err => {
reject(err)
})
} else {
reject(err);
}
} else {
reject('初始化上传失败,请重试');
}
})
return uploadMethod
}
import request from '@/axios';
export const getList = (current, size, params) => {
return request({
url: '/blade-resource/oss/list',
method: 'get',
params: {
...params,
current,
size,
},
});
};
export const getDetail = id => {
return request({
url: '/blade-resource/oss/detail',
method: 'get',
params: {
id,
},
});
};
export const remove = ids => {
return request({
url: '/blade-resource/oss/remove',
method: 'post',
params: {
ids,
},
});
};
export const add = row => {
return request({
url: '/blade-resource/oss/submit',
method: 'post',
data: row,
});
};
export const update = row => {
return request({
url: '/blade-resource/oss/submit',
method: 'post',
data: row,
});
};
export const enable = id => {
return request({
url: '/blade-resource/oss/enable',
method: 'post',
params: {
id,
},
});
};
// 大文件上传初始化
/**
* @description: 大文件上传初始化
* @param {*} fileName 文件名
* @returns
*/
export const initUpload = fileName => {
return request({
url: '/blade-resource/oss/endpoint/initUpload',
method: 'post',
params: {
fileName,
},
})
};
/**
* @description: 大文件上传分片文件
* @param {*} uploadId 上传id
* @param {*} chunkId 分片id
* @param {*} chunkFile 分片文件
*/
export const uploadChunk = (uploadId, chunkId, chunkFile) => {
return request({
url: '/blade-resource/oss/endpoint/uploadChunk',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
params: {
uploadId,
chunkId,
},
data: {
chunkFile,
}
})
};
/**
* @description: 大文件上传合并
* @param {*} uploadId 上传id
* @returns
*/
export const fileMerge = (uploadId) => {
return request({
url: '/blade-resource/oss/endpoint/merge',
method: 'post',
params: {
uploadId,
}
})
};
/**
* @description: 大文件上传取消
* @param {*} uploadId 上传id
* @returns
*/
export const abort = (uploadId, fileName) => {
return request({
url: '/blade-resource/oss/endpoint/abort',
method: 'post',
params: {
uploadId,
fileName
}
})
};
这是使用方式
<!-- 视频 -->
<template #resource-form="{ type }">
<slice-upload
:limitSize="100"
:limit="1"
limitTypes="video/*"
:disabled="type == 'view'"
v-model="form.resource"
></slice-upload>
</template>
我就不PR了好麻烦 直接贴代码吧 应该能看懂,有问题可以回复
登录 后才可以发表评论