首页 » 前端 » ElementUI » 正文

el-upload 上传并生成缩略图

发布者:站点默认
2020/01/1 浏览数(1,422) 分类:ElementUI el-upload 上传并生成缩略图已关闭评论

用法

<template>
    <VideoUploader v-model="video" :width="400" :height="300" @cover="saveVideoCover" />
    <img :src="cover" v-if="cover" />
</template>
<script>
export default {
    data() {
        return {
            video: null, // 视频地址
            cover: null, // 封面地址
        };
    },
    watch: {
        // 删除视频时清除封面图
        video(n) {
            if (!n) {
                this.cover = null;
            }
        },
    },
    methods: {
        saveVideoCover(data) {
            this.cover = data.url;
        }
    }
}
</script>

VideoUploader.vue

<template>
    <div class="uploader" :class="{ uploaded: files.length >= limit }" :style="style">
        <el-upload class="upload-button" :limit="limit" :multiple="multiple" :http-request="upload" :file-list="fileList" :accept="accept" :before-upload="fileCheck" :on-remove="remove" :show-file-list="false" action="null" drag v-if="!loading">
            <div class="placeholder">
                <i :class="icon" class="uploader-icon"></i>
                <div class="el-upload__text" v-html="placeholder"></div>
            </div>
        </el-upload>
        <div class="progress" v-if="loading">
            <div class="tip" v-if="progress.loaded == progress.total">等待转存…</div>
            <template v-else>
                <div class="percentage">{{ progress.percentage || '--' }}</div>
                <div class="size">{{ progress.loaded || '--' }} / {{ progress.total || '--' }}</div>
            </template>
            <el-button @click="cancelUpload" size="mini">取消</el-button>
        </div>
        <div class="files" v-if="files.length">
            <div class="file" v-for="(item, i) in files" :key="item.url">
                <video :poster="item.poster || item.cover || item.img || item.image || item.imageUrl" muted controls crossorigin="anonymous" ref="videoPlayer">
                    <source :src="item.url" :type="item.type || 'video/mp4'" />
                </video>
                <div class="control">
                    <div @click="capture" class="button capture" title="提取当前画面为视频封面" v-if="buildCover">
                        <i class="el-icon-camera-solid"></i>
                    </div>
                    <div @click="remove(item)" class="button remove" title="删除此视频文件">
                        <i class="el-icon-delete"></i>
                    </div>
                </div>
            </div>
        </div>
        <canvas ref="screenshot" style="display: none"></canvas>
    </div>
</template>

<script>
import { operation as operationApi } from '@/api/file';
import axios from 'axios';

const CancelToken = axios.CancelToken;
let CancelUpload = () => {};

export default {
    props: {
        // v-model
        value: {
            type: String,
            default: null,
        },
        icon: {
            type: String,
            default: 'el-icon-upload',
        },
        placeholder: {
            type: String,
            default: '将视频拖到此处,或<em>点击上传</em>',
        },
        width: {
            type: Number,
            default: 360,
        },
        height: {
            type: Number,
            default: 200,
        },
        limit: {
            type: Number,
            default: 1,
        },
        buildCover: {
            type: Boolean,
            default: false,
        },
    },
    data() {
        return {
            files: [],
            accept: '.mp4, .ogg, .webm',
            progress: {
                total: null,
                loaded: null,
                percentage: null,
            },
            loading: false,
        };
    },
    computed: {
        fileList() {
            let self = this;
            let list = self.files.map((v) => {
                let name = v.url.match(/[^/]+\.\w+$/i);
                name = (name && name[0]) || '文件';
                name = name.substr(-16);
                return {
                    ...v,
                    name: v.name || name,
                };
            });
            return list;
        },
        // 允许多选时,el-upload 仍是一张一张上传而不是一次多张
        multiple() {
            let self = this;
            return self.limit > 1;
        },
        style() {
            let self = this;
            let style = {
                width: self.width + 'px',
                height: self.height + 'px',
            };
            return style;
        },
    },
    methods: {
        // 截取视频当前播放帧为封面
        capture() {
            let self = this;
            const video = self.$refs['videoPlayer'][0];
            const canvas = self.$refs['screenshot'];
            const ctx = canvas.getContext('2d');
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            console.log('capture.intro', { video, canvas, size: { width: canvas.width, height: canvas.height }, files: self.files });
            const STATUS = {
                initializing: '正在生成',
                uploading: '正在上传',
                ok: '完成',
                error: '错误',
                正在生成: 'initializing',
                正在上传: 'uploading',
                完成: 'ok',
                错误: 'error',
            };
            self.$emit('cover', { status: STATUS.正在生成, STATUS });
            // 绘制当前视频帧到canvas上
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, video.videoWidth, video.videoHeight);
            ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
            const loading = self.$loading({
                lock: true,
                text: '正在生成封面',
                background: 'rgba(0, 0, 0, 0.7)',
            });
            // 将canvas转换为图片 // const image = canvas.toDataURL('image/png');
            try {
                canvas.toBlob((blob) => {
                    console.log('capture.blob', blob, 1 ? canvas.toDataURL('image/png') : '');
                    // 上传
                    let form = new FormData();
                    let image = new File([blob], 'screenshot.png', { type: 'image/png' });
                    form.append('file', image); // append 存在则追加,set 存在则覆盖
                    operationApi
                        .upload(form, {
                            onUploadProgress: function (progressEvent) {
                                self.$emit('cover', { status: STATUS.正在上传, progressEvent, STATUS });
                                console.log('封面上传中', progressEvent.loaded, progressEvent.total);
                                // self.progress.total = Math.floor(progressEvent.total / 1024 / 1024) + 'MB';
                                // self.progress.loaded = Math.floor(progressEvent.loaded / 1024 / 1024) + 'MB';
                                // self.progress.percentage = Math.floor((progressEvent.loaded / progressEvent.total) * 100) + '%';
                            },
                            cancelToken: new CancelToken(function (cancel) {
                                CancelUpload = cancel;
                            }),
                        })
                        .then((res) => {
                            console.log('封面上传.then', res);
                            self.$emit('cover', { status: STATUS.完成, url: res && res.url, STATUS });
                        })
                        .catch((error) => {
                            console.log('封面上传.catch', error);
                            self.$emit('cover', { status: STATUS.错误, error, STATUS });
                        })
                        .finally(() => {
                            loading.close();
                        });
                });
            } catch (e) {
                console.log('capture.error', e);
                self.$message.error(`提取封面出错:${e.message}`);
                loading.close();
            }
        },
        // self.files 变动时更新父组件的 v-model
        emit() {
            let self = this;
            let value = null;
            if (self.files.length == 1) {
                value = self.files[0].url;
            } else if (self.files.length == 0) {
                value = null;
            } else {
                value = self.files;
            }
            self.$emit('input', value);
        },
        remove(file, fileList = []) {
            let self = this;
            try {
                let list = self.files;
                for (let i = 0; i < list.length; i++) {
                    const image = list[i];
                    if (file.url == image.url) {
                        list.splice(i, 1);
                        break;
                    }
                }
            } catch (e) {
                console.log('移除时出错了', e, file, fileList);
            }
            self.emit();
            return true;
        },
        upload(params) {
            let self = this;
            var file = params.file;
            var form = new FormData();
            form.append('file', file); // append 存在则追加,set 存在则覆盖
            self.loading = true;
            operationApi
                .upload(form, {
                    onUploadProgress: function (progressEvent) {
                        self.progress.total = Math.floor(progressEvent.total / 1024 / 1024) + 'MB';
                        self.progress.loaded = Math.floor(progressEvent.loaded / 1024 / 1024) + 'MB';
                        self.progress.percentage = Math.floor((progressEvent.loaded / progressEvent.total) * 100) + '%';
                    },
                    cancelToken: new CancelToken(function (cancel) {
                        CancelUpload = cancel;
                    }),
                })
                .then((res) => {
                    if (Array.isArray(res) && res.length > 0) {
                        res.forEach((img) => {
                            self.files.push(img);
                        });
                    } else {
                        self.files.push(res);
                    }
                    self.emit();
                    if (self.buildCover) {
                        self.$nextTick(() => {
                            // 视频上传后自动生成封面
                            const video = self.$refs['videoPlayer'][0];
                            video.addEventListener('loadeddata', function () {
                                console.log('视频上传完成,第一帧已经加载完毕,准备提取封面图…');
                                // self.capture(); // 注释原因:部分视频第一帧黑屏,往后播放一下再截图
                                let delay = 0.5; // 视频进度移至 00:00.5
                                video.currentTime = Math.min(delay, video.duration);
                            });
                            video.addEventListener('seeked', (event) => {
                                if (!self.coverCaptured) {
                                    self.coverCaptured = true;
                                    self.capture();
                                }
                            });
                            video.addEventListener('error', function (error) {
                                console.log('视频加载遇到错误', error);
                            });
                            // video.addEventListener('canplay', function (res) {
                            //     console.log('媒体数据已经有足够的数据(至少播放数帧)可供播放时触发。这个事件对应CAN_PLAY的readyState。', res);
                            // });
                        });
                    }
                    console.group('uploaded');
                    console.log('params:', params);
                    console.log('uploaded:', res);
                    console.groupEnd();
                })
                .catch((e) => {
                    console.log('upload fail:', e);
                })
                .finally(() => {
                    self.loading = false;
                    self.progress.loaded = null;
                    self.progress.total = null;
                    self.progress.percentage = null;
                });
        },
        cancelUpload() {
            try {
                CancelUpload();
            } catch (e) {
                console.log('CancelUpload', e);
            }
        },
        fileCheck(file) {
            let self = this;
            let size = file.size;
            const maxSize = 200 * 1024 * 1024;
            let passed = size <= maxSize;
            if (!passed) {
                self.$message.error('文件大小不可超过200MB');
            }
            return passed;
        },
        // bind v-model value to el-upload
        fill(v) {
            let self = this;
            if (v) {
                self.files = [{ url: v }];
            } else {
                self.files = [];
            }
        },
        // success(response, file, fileList) {
        //   console.log('uploader.success', response, file, fileList);
        // },
        // error(err, file, fileList) {
        //   console.log('uploader.error', err, file, fileList);
        // },
        // progress(event, file, fileList) {
        //   console.log('uploader.progress', event, file, fileList);
        // },
        // change(file, fileList) {
        //   console.log('uploader.change', file, fileList);
        // },
        // beforeUpload(file) {
        //   console.log('uploader.beforeUpload', file);
        // },
        // beforeRemove(file, fileList) {
        //   console.log('uploader.beforeRemove', file, fileList);
        // },
    },
    watch: {
        value: function (v, o) {
            let self = this;
            if (v != o) {
                self.fill(v);
            }
            // console.log('[avatarUploader.vue@watch] value:', v, 'from:', o);
        },
    },
    mounted() {
        let self = this;
        let v = self.value;
        self.fill(v);
        // console.log('[avatarUploader.vue@mounted] value:', self.value);
    },
};
</script>

<style lang="scss" scoped>
::v-deep.uploader {
    width: 360px;
    height: 200px;
    border-radius: 6px;
    overflow: hidden;
    background-color: #f3f3f3;
    position: relative;
    &.uploaded {
        &:hover .button {
            background: hsla(0, 0%, 0%, 0.3);
        }
        .upload-button {
            display: none;
        }
        .files {
            width: 100%;
            height: 100%;
            .file {
                width: 100%;
                height: 100%;
                position: relative;
                video {
                    width: 100%;
                    height: 100%;
                    background-color: #000;
                }
                .control {
                    position: absolute;
                    right: 10px;
                    top: 10px;
                    line-height: 1;
                    .button {
                        display: inline-block;
                        width: 20px;
                        height: 20px;
                        line-height: 20px;
                        text-align: center;
                        color: #fff;
                        cursor: pointer;
                        border-radius: 2px;
                        &:hover {
                            background-color: #000;
                        }
                        &.capture {
                            margin-right: 10px;
                        }
                    }
                }
            }
        }
    }
    .upload-button {
        width: 100%;
        height: 100%;
        .el-upload {
            width: 100%;
            height: 100%;
            .el-upload-dragger {
                display: flex;
                justify-content: center;
                align-items: center;
                width: auto;
                height: 100%;
                .placeholder {
                    .uploader-icon {
                        font-size: 67px;
                        color: #c0c4cc;
                        margin: 0 0 16px;
                    }
                    .el-upload__text {
                        color: #606266;
                        font-size: 14px;
                        text-align: center;
                        em {
                            color: #5473e8;
                            font-style: normal;
                        }
                    }
                }
            }
        }
    }
    .progress {
        text-align: center;
        display: inline-block;
        position: absolute;
        z-index: 1;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        padding: 1em;
        line-height: 1;
        .percentage {
            font-size: 24px;
            line-height: 1.4;
        }
        .tip,
        .size {
            margin-bottom: 0.5em;
        }
    }
}
</style>
点击返回顶部
  1. 留言
  2. 联系方式