ant-design-vue 视频上传组件封装处理
/* * 视频上传组件 */ <template> <div> <a-upload listType="picture-card" :accept="acceptType" :action="uploadVideo" :fileList="fileList" :multiple="false" :disabled="disabled" @preview="handlePreview" :beforeUpload="beforeUpload" @change="event => handleChange(event, 'fileList')" v-decorator="['fileList', {rules:[{required: requiresUpload, message: '请上传视频!'}]}]" > <div v-if="fileList.length < limit"> <a-icon type="plus"/> </div> </a-upload> <a-modal :visible="previewVisible" :footer="null" @cancel="handleVideoCancel"> <video alt="example" style="width: 100%" :src="videoUrl" controls> 当前浏览器不支持 video 功能 </video> </a-modal> </div> </template> <script> import { FILE_DISPLAY_PREFIX, parseFileRespon, UPLOAD_URL, getVideoFrameImage } from '@/api/upload' const uidGenerator = () => { return '-' + parseInt(Math.random() * 10000 + 1, 10) } const getFileName = (path) => { if (path.lastIndexOf('\\') >= 0) { const reg = new RegExp('\\\\', 'g') path = path.replace(reg, '/') } return path.substring(path.lastIndexOf('/') + 1) } export default { name: 'UploadVideo', props: { // 是否前端进行展示缩略图(前端获取缩略图处理) showThumbnail: { type: Boolean, default: false }, // 上传文件数量限制, 默认1个 limit: { type: Number, default: 1 }, // 上传文件大小限制, 单位Mb, 默认无限制 fileSize: { type: Number, default: 0 }, // 是否必须上传验证 requiresUpload: { type: Boolean, default: false }, acceptType: { type: String, required: false, default: '.mp4, .3gp, .rmvb, .wmv, .flv, .avi' }, text: { type: String, required: false, default: '点击上传' }, // 初始视频内容 value: { type: [String, Array], required: false, default: '' }, // 组件是否能够上传, 默认支持上传 disabled: { type: Boolean, default: false } }, data () { return { fileDisplayPrefix: FILE_DISPLAY_PREFIX, parseFileRespon, // 上传地址 uploadVideo: UPLOAD_URL + '/files', // 视频预览地址 videoUrl: '', headers: {}, fileList: [], fileLoading: false, previewVisible: false } }, watch: { value (val) { // console.log('初始化视频资源路径', val) if (val instanceof Array) { this.initFileList(val.join(',')) } else { this.initFileList(val) } } }, created () { // const token = Vue.ls.get(ACCESS_TOKEN) // this.headers = { 'X-Access-Token': token } }, methods: { initFileList (paths) { if (!paths || paths.length === 0) { this.fileList = [] return } const arr = paths.split(',') if (this.showThumbnail) { for (let a = 0; a < arr.length; a++) { getVideoFrameImage(this.fileDisplayPrefix + arr[a]).then(res => { this.fileList.push({ uid: uidGenerator(), name: getFileName(arr[a]), status: 'done', url: res, videoUrl: arr[a], response: { code: 10000, status: 'history', message: arr[a], result: [ arr[a] ] } }) }).catch(e => { this.fileList.push({ uid: uidGenerator(), name: getFileName(arr[a]), status: 'done', url: e, videoUrl: arr[a], response: { code: 10000, status: 'history', message: arr[a], result: [ arr[a] ] } }) }) } } else { for (let a = 0; a < arr.length; a++) { this.fileList.push({ uid: uidGenerator(), name: getFileName(arr[a]), status: 'done', url: this.fileDisplayPrefix + arr[a], videoUrl: arr[a], response: { code: 10000, status: 'history', message: arr[a], result: [ arr[a] ] } }) } } // console.log('sss', fileList) // this.fileList = fileList }, handlePathChange () { const uploadFiles = this.fileList let path = '' if (!uploadFiles || uploadFiles.length === 0) { path = '' } const arr = [] for (var a = 0; a < uploadFiles.length; a++) { if (uploadFiles[a].response) { arr.push(uploadFiles[a].response.message) } } if (arr.length > 0) { path = arr.join(',') } this.$emit('change', path) }, // 图片上传前的校验 beforeUpload (file) { return true }, handleChange (info, listStr) { if (info.file.status) { this[listStr] = info.fileList } if (info.file.status === 'uploading') { this.fileLoading = true } else if (info.file.status === 'error') { this.$message.error(this.$t('服务器无响应,请联系管理员!')) this.fileLoading = false } else if (info.file.status === 'done') { if (info.file.response.code === 10000) { const that = this // 缩略图处理 this[listStr][info.fileList.length - 1].url = this.fileDisplayPrefix + this.parseFileRespon(info.file.response) || '' this[listStr][info.fileList.length - 1].videoUrl = this.parseFileRespon(info.file.response) || '' // 前端缩略图处理 if (this.showThumbnail) { getVideoFrameImage(that.fileList[that.fileList.length - 1].url).then(res => { that.fileList[that.fileList.length - 1].url = res that.$forceUpdate() }) } } else { this.fileList.pop() this.$message.error('上传视频失败!') } } else if (info.file.status === 'removed') { this.$emit('change', this.fileList.map(i => i.videoUrl)) } this.$forceUpdate() }, handleVideoCancel () { this.previewVisible = false const myVideo = document.getElementsByTagName('video') const arr = Array.from(myVideo) // 关闭时暂停播放 arr.forEach(item => { item.pause() }) }, // 视频文件预览 async handlePreview (file) { this.videoUrl = this.fileDisplayPrefix + file.videoUrl // console.log('路径', this.videoUrl) this.previewVisible = true }, handleDelete (file) { // 如有需要新增 删除逻辑 console.log(file) }, // 获取上传的视频地址, 以,分割 getUrls () { let urls = '' if (this.fileList.length > 0) { this.fileList.forEach(item => { if (urls === '') { urls = item.response ? this.parseFileRespon(item.response) : item.videoUrl.replaceAll('/uploads', '') } else { const urlPath = item.response ? this.parseFileRespon(item.response) : item.videoUrl.replaceAll('/uploads', '') urls += ',' + urlPath } }) } return urls }, // 获取视频标题+时长+缩略图+宽高 asyncImgChecked (file) { return new Promise((resolve, reject) => { const reader = new FileReader() // 必须用file.raw reader.readAsDataURL(file) // reader.readAsDataURL(file.raw) reader.onload = () => { // 让页面中的img标签的src指向读取的路径 const video = document.createElement('video') video.src = reader.result video.currentTime = 3 // 截取缩略图时的视频时长,一定要设置,不然大概率白屏 video.oncanplay = () => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.setAttribute('width', 100) canvas.setAttribute('height', 100) video.setAttribute('width', 100) video.setAttribute('height', 100) ctx.drawImage(video, 0, 0, video.width, video.height) const imgSrc = canvas.toDataURL('image/png') resolve({ // base64的缩略图图片路径 thumbnail: imgSrc, width: video.videoWidth, height: video.videoHeight, videoName: file.name }) } } }) } } } </script> <style scoped> </style>
附录upload.js内容
import path from './index' import { isEmpty } from '@/utils/common' import { axios } from '@/utils/request' // 上传文件地址 export const UPLOAD_URL = process.env.VUE_APP_API_BASE_URL + path.upload // 文件回显前缀 export const FILE_DISPLAY_PREFIX = path.uploads // 解析文件响应 export function parseFileRespon (response) { if (isEmpty(response) || response.code !== 10000 || response.result.length < 1) { this.$message.error(() => { return response.msg || '服务异常,请稍后再试' }) return '' } else { return response.result[0] } } /** * 1分片上传 地址 * @returns */ export function chunk () { return UPLOAD_URL + '/oss/aliyun/chunk' } /** * 2分片上传完成 * @param identifier * @returns {AxiosPromise} */ export function chunkComplete (identifier) { return axios({ url: path.upload + '/oss/aliyun/chunk/complete', method: 'post', params: { 'identifier': identifier } }) } /** * 3分片上传取消 * @param identifier * @returns {AxiosPromise} */ export function chunkAbort (identifier) { return axios({ url: path.upload + '/oss/aliyun/chunk/abort', method: 'post', params: { 'identifier': identifier } }) } /** * 自定义上传图片 * @param file * @returns {AxiosPromise} */ export function customUploadFile (file) { const data = new FormData() data.append('file', file) return axios({ url: path.upload + '/files', method: 'POST', headers: { 'Content-Type': 'multipart/form-data' }, timeout: 1800000, data: data }) } /** * 获取缩略图 * @param url 地址 * @param currentTime 缩略图取第几秒的图片 * @param width * @param height * @returns {Promise<unknown>} */ export function getVideoFrameImage (url, currentTime = 3, width = 100, height = 100) { return new Promise((resolve, reject) => { const video = document.createElement('video') video.setAttribute('crossOrigin', 'anonymous') video.setAttribute('preload', 'metadata') video.setAttribute('src', url) // 视频开始可能是黑屏状态 video.currentTime = currentTime video.addEventListener('loadeddata', function () { const canvas = document.createElement('canvas') const { videoWidth, videoHeight } = video const newHeight = height || videoHeight * (width / videoWidth) canvas.width = width canvas.height = newHeight canvas.getContext('2d').drawImage(video, 0, 0, 200, height) resolve(canvas.toDataURL('image/jpeg')) }) // 错误时的特殊处理,(资源404或不可用时触发) video.addEventListener('error', function (e) { // console.log('ss', e) reject(url) }) }) }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人