【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。

简介: 【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。


【sgUploadTray】上传托盘自定义组件,可实时查看上传列表进度

特性:

  1. 可以全屏
  2. 可以还原尺寸、拖拽调整托盘尺寸
  3. 可以最小化
  4. 可以回到右下角默认位置
  5. 支持删除队列数据

sgUploadTray源码

<template>
    <div :class="$options.name" :show="show" :size="size" :style="style">
        <div class="upload-list">
            <div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader">
                <div class="left">
                    <div class="title"><span>上传队列:{{ uploadList.length }}个文件</span></div>
                </div>
                <div class="right" @mousedown.stop>
                    <div v-if="size !== 'lg' && showRightBottomBtn" @click.stop="toRightBottomPosition" title="回到原来的位置">
                        <i class="el-icon-bottom-right"></i>
                    </div>
                    <div v-if="size !== 'mn'" @click.stop="size = 'mn'" title="最小化">
                        <i class="el-icon-minus"></i>
                    </div>
                    <div v-if="size !== 'md'" @click.stop="size = 'md'" title="还原">
                        <i :class="size === 'lg' ? 'el-icon-copy-document' : 'el-icon-d-caret'"></i>
                    </div>
                    <div v-if="size !== 'lg'" @click.stop="size = 'lg'" title="全屏">
                        <i class="el-icon-full-screen"></i>
                    </div>
                    <div @click.stop="close">
                        <i class="el-icon-close"></i>
                    </div>
                </div>
            </div>
            <div class="file-list">
                <ul>
                    <li v-for="(a, i) in uploadList" :key="i">
                        <div class="left">
                            <span class="name" :title="a.name">{{ a.name }}</span>
                            <el-tag class="size" size="mini">{{ getSize(a.size) }}</el-tag>
                            <el-progress class="progress" :percentage="a.percent"></el-progress>
                        </div>
                        <div class="right">
                            <span class="tip" :color="a.color">{{ a.tip }}</span>
                            <el-button class="remove-icon-btn" type="primary" icon="el-icon-close" size="mini" plain circle
                                @click.stop="remove(a)"></el-button>
                        </div>
                    </li>
                </ul>
            </div>
        </div>
        <!-- 拖拽移动窗体 -->
        <sgDragMove :data="dragMoveDoms" :cursor="{
            grab: 'default', grabbing: 'default'
        }" nearPadding="50" :disabled="size === 'lg' && disabledDragMove" @dragStart="$emit(`dragStart`, dragMoveDoms)"
            @dragging="showRightBottomBtn = true; $emit(`dragging`, dragMoveDoms)" @dragEnd="$emit(`dragEnd`, dragMoveDoms)"
            mousemoveNearSide />
        <!-- 拖拽改变窗体尺寸 -->
        <sgDragSize v-if="resizeable_" :disabled="size === 'lg'" @dragStart="disabledDragMove = true" @dragging="draggingSize" @dragEnd="disabledDragMove = false" :minWidth="600" :minHeight="56" />
    </div>
</template>
<script>
import sgDragMove from "@/vue/components/admin/sgDragMove";
import sgDragSize from "@/vue/components/admin/sgDragSize";
export default {
    name: 'sgUploadTray',
    components: {
        sgDragMove,
        sgDragSize,
    },
    data() {
        return {
            style_bk: null,
            style: {},
            resizeable_: true,
            disabledDragMove: false,//屏蔽移动
            show: false,
            showRightBottomBtn: false,
            size: 'md',//lg全屏、md普通、mn最小
            uploadList: [],
            dragMoveDoms: [
                /* {
                    canDragDom: elementDOM,//可以拖拽的位置元素
                    moveDom: elementDOM,//拖拽同步移动的元素
                } */
            ],//可以拖拽移动的物体
        }
    },
    props: ["data", "value", 'resizeable'],
    watch: {
        value: {
            handler(d) {
                this.show = d
            }, deep: true, immediate: true,
        },
        show: {
            handler(d) {
                this.$emit(`input`, d);
            }, deep: true, immediate: true,
        },
        data: {
            handler(d) {
                this.uploadList = d;
            }, deep: true, immediate: true,
        },
        resizeable: {
            handler(newValue, oldValue) {
                this.resizeable_ = newValue === '' || newValue;
            },
            deep: true,//深度监听
            immediate: true,//立即执行
        },
        size: {
            handler(newValue, oldValue) {
                switch (newValue) {
                    case 'lg':
                    case 'mn':
                        this.style_bk = JSON.parse(JSON.stringify(this.style));
                        delete this.style.width, delete this.style.height;
                        break;
                    case 'md':
                        this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk)));
                        break;
                }
            },
            deep: true,//深度监听
            immediate: true,//立即执行
        },
    },
    mounted() {
        this.dragMoveDoms = [
            {
                canDragDom: this.$refs.header,//托盘的头部可以拖拽
                moveDom: this.$el,//拖拽的时候,整个上传列表一起跟随移动
            }
        ];
    },
    methods: {
        draggingSize({ style }) {
            this.disabledDragMove = true;
            this.style = style;
        },
        /*  dragSizeStart(d) {
             this.disabledDragMove = true;
         },
         dragSizeEnd(d) {
             this.disabledDragMove = false;
         }, */
        toRightBottomPosition(d) {
            this.showRightBottomBtn = false;
            let rect = this.$el.getBoundingClientRect();
            this.$el.style = {
                left: innerWidth - rect.width + "px",
                top: innerHeight - rect.height + "px",
            }
        },
        dblclickHeader(d) {
            switch (this.size) {
                case 'lg':
                    this.size = 'md';
                    break;
                case 'md':
                    this.size = 'mn';
                    break;
                case 'mn':
                    this.size = 'md';
                    break;
                default:
            }
        },
        close(d) {
            let stopUploadList = this.uploadList.filter(v => v.percent < 100 && (v.type !== 'error' && v.type !== 'success'));
            if (stopUploadList && stopUploadList.length) {
                this.$confirm(`您还有正在上传中的文件,确定要取消吗?`, `提示`, { dangerouslyUseHTMLString: true, confirmButtonText: `确定`, cancelButtonText: `取消`, type: "warning" }).then(() => {
                    this.show = false;
                    this.$emit(`stopUpload`, stopUploadList);
                }).catch(() => {
                });
            } else {
                this.show = false;
            }
        },
        remove(d) {
            if (d.type === 'error' || d.type === 'success') {
                this.uploadList.splice(this.uploadList.findIndex(v => v.uid == d.uid), 1)
            } else if (d.percent < 100) {
                this.$confirm(`${d.name}正在上传中,确定要取消吗?`, `提示`, { dangerouslyUseHTMLString: true, confirmButtonText: `确定`, cancelButtonText: `取消`, type: "warning" }).then(() => {
                    this.$emit(`stopUpload`, [d]);
                }).catch(() => {
                });
            } else {
                this.uploadList.splice(this.uploadList.findIndex(v => v.uid == d.uid), 1)
            }
        },
        getSize(d) {
            return this.$g.getSize(d);
        },
    }
};
</script>
<style lang="scss" scoped>
.sgUploadTray {
    /*禁止选中文本*/
    user-select: none;
    $width: 600px; //托盘宽度
    $listMaxHeight: $width; //托盘宽度
    $rightWidth: 200px; //右侧宽度
    $sizeWidth: 100px; //文件大小宽度宽度
    $progressWidth: 50px; //进度条宽度
    $tipWidth: 100px; //提示文本宽度
    // ----------------------------------------
    z-index: 999999; //根据情况自己拿捏
    position: fixed;
    right: 10px;
    bottom: 10px;
    width: $width;
    background-color: white;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    border-radius: 4px;
    overflow: hidden;
    border: 1px solid #eee;
    font-size: 14px;
    display: none;
    &[show] {
        display: block;
    }
    &[size="lg"] {
        left: 0 !important;
        top: 0 !important;
        width: 100vw;
        height: 100vh;
        transition: none;
    }
    &[size="md"] {
        width: $width;
        height: revert;
    }
    &[size="mn"] {
        width: $width;
        height: 56px;
    }
    .upload-list {
        display: flex;
        flex-direction: column;
        box-sizing: border-box;
        padding-bottom: 20px;
        width: 100%;
        .header {
            flex-shrink: 0;
            font-size: 16px;
            font-weight: bold;
            width: 100%;
            box-sizing: border-box;
            padding: 20px;
            /*从上往下线性渐变背景*/
            background: linear-gradient(#409EFF11, white);
            color: #409EFF;
            display: flex;
            justify-content: space-between;
            align-items: center;
            .left {
                flex-grow: 1;
                .title {}
            }
            .right {
                display: flex;
                align-items: center;
                justify-content: flex-end;
                flex-shrink: 0;
                pointer-events: auto;
                &>* {
                    margin-left: 10px;
                    cursor: pointer;
                    i {
                        pointer-events: none;
                    }
                    &:hover {
                        opacity: 0.618;
                    }
                }
            }
        }
        .file-list {
            width: 100%;
            flex-grow: 1;
            overflow-y: auto;
            max-height: $listMaxHeight;
            box-sizing: border-box;
            padding: 0 20px;
            ul {
                width: 100%;
                li {
                    line-height: 1.6;
                    box-sizing: border-box;
                    padding: 10px;
                    border-radius: 4px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    width: 100%;
                    .left {
                        display: flex;
                        align-items: center;
                        .name {
                            margin-right: 10px;
                            max-width: $width - $sizeWidth - $progressWidth - $rightWidth - 20px;
                            overflow: hidden;
                            white-space: nowrap;
                            text-overflow: ellipsis;
                        }
                        .size {
                            margin-right: 10px;
                            max-width: $sizeWidth;
                            /*单行省略号*/
                            overflow: hidden;
                            white-space: nowrap;
                            text-overflow: ellipsis;
                        }
                        .progress {
                            max-width: $progressWidth;
                        }
                    }
                    .right {
                        display: flex;
                        align-items: center;
                        justify-content: flex-end;
                        max-width: $rightWidth;
                        .tip {
                            margin-right: 10px;
                            max-width: $tipWidth;
                            overflow: hidden;
                            white-space: nowrap;
                            text-overflow: ellipsis;
                            &[color="red"] {
                                color: #F56C6C;
                            }
                            &[color="green"] {
                                color: #67C23A;
                            }
                            &[color="blue"] {
                                color: #409EFF;
                            }
                        }
                        .btns {
                            .remove-icon-btn {
                                width: 20px;
                                height: 20px;
                            }
                        }
                    }
                    // cursor: pointer;
                    &:hover {
                        background-color: #409EFF11;
                        color: #409EFF;
                    }
                }
            }
        }
    }
}
</style>

应用

<template>
    <div class="sg-body">
        <el-upload ref="uploadFolder" :show-file-list="false" :headers="headers" :action="actionURL"
            :before-upload="beforeUpload" :on-success="uploadSuccess" :on-error="uploadError" :on-exceed="exceed"
            multiple />
        <el-button type="primary" @click="uploadBtn.click()">点击上传文件夹</el-button>
        <!-- 上传托盘(右下角) -->
        <sgUploadTray v-model="showUploadTray" :data="uploadList" @stopUpload="stopUpload" resizeable/>
    </div>
</template>
<script>
import sgUploadTray from "@/vue/components/admin/sgUploadTray";
export default {
    components: {
        sgUploadTray,
    },
    data() {
        return {
            //上传相关变量----------------------------------------
            headers: { kkToken: localStorage.token, }, //获取token(注意仔细看后端接受token的字段名是不是叫做“token”)
            actionURL: `${this.$d.API_ROOT_URL}/customer/importCustomerData`,
            fmt: this.$global.resourceTypes.flatMap(v => v.suffixs).filter(Boolean),//所有后缀名 
            dur: 100,
            percent: 100,
            uploadBtn: null,//上传按钮
            uploadList: [],
            showUploadTray: false,
            // ----------------------------------------
        }
    },
    mounted(d) {
        this.$nextTick(() => {
            this.uploadBtn = this.$refs.uploadFolder.$children[0].$refs.input;
            this.uploadBtn.webkitdirectory = true;//让el-upload支持上传文件夹
        })
    },
    methods: {
        // 上传文件----------------------------------------------------------------
        showFakeLoading(file) {
            file = this.uploadList.find(v => v.uid == file.uid);
            clearInterval(file.interval);
            file.percent = 0;
            file.interval = setInterval(() => {
                file.percent++;
                file.percent >= 99 && this.hideFakeLoading(file);
            }, this.dur);
        },
        hideFakeLoading(file, { type, tip, color } = {}) {
            file = this.uploadList.find(v => v.uid == file.uid);
            clearInterval(file.interval);
            switch (type) {
                case 'error':
                    file.percent = 0;
                    break;
                case 'success':
                default:
                    file.percent = 100;
            }
            type && (file.type = type);
            tip && (file.tip = tip);
            color && (file.color = color);
        },
        exceed(file, fileList) {
            this.$message.error("上传文件数量太大,分散上传吧!");
        },
        stopUpload(d) {
            this.$refs.uploadFolder.abort();
            //console.log(`取消上传`, d);
        },
        //文件上传之前
        beforeUpload(file, id) {
            this.uploadList.unshift({
                interval: false,
                uid: file.uid,
                percent: 0,//加载进度
                name: file.name,
                size: file.size,
                type: file.type,
                webkitRelativePath: file.webkitRelativePath,
                type: '',
                tip: '',
                color: '',
            });
            this.showUploadTray = true;
            // 判断是不是特定的格式________________________
            let isFile = this.fmt.includes(file.name.toLocaleLowerCase().split(".").pop());
            const maxSize = 50; //限制大小
            const isAllowSize = file.size / 1024 / 1024 <= maxSize;
            isFile || this.$message.error("上传文件只能是" + this.fmt + "格式");
            isAllowSize || this.$message.error("上传文件大小不能超过" + maxSize + "MB");
            let allowUpload = isFile && isAllowSize;
            allowUpload ? this.showFakeLoading(file) : this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
            return allowUpload; //若返回false则停止上传
        },
        //上传成功
        uploadSuccess(response, file, fileList) {
            if (response.data && response.data.key) {
                // 下载失败原因的描述文件
                this.$d.customer_downloadImportCustomerExcel({ key: response.data.key }, {
                    s: (d) => {
                        this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
                        this.$g.downloadFile(d, `${file.name}-上传失败原因`, '.xls');
                        this.$message.error(`${file.name}-上传失败,请查看失败原因`);
                        // this.initList();//刷新列表
                        //console.log('上传失败', response, file, fileList);
                    }
                });
            } else if (response.success) {
                // 上传成功了
                this.hideFakeLoading(file, { type: 'success', tip: "上传成功", color: "green" });
                this.$message.success(`“${file.name}上传成功`);
                // this.initList();//刷新列表
                //console.log('上传成功', response, file, fileList);
            } else {
                // 其他失败原因
                this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
                // this.$message.error(response.msg);
                //console.log('上传失败', response, file, fileList);
            }
        },
        //上传失败
        uploadError(err, file, fileList) {
            this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
            this.$message.error("上传失败");
            //console.log('上传失败', err, file, fileList);
        },
        // ----------------------------------------
    },
};
</script> 
<style lang="scss" scoped>
.sg-body {
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    overflow-y: auto;
}
</style>

这里面用到的sgDragMove组件在这里

image.png


相关文章
|
11月前
|
JavaScript
Fastadmin列表的多图预览(一行代码)
Fastadmin列表的多图预览(一行代码)
169 0
element用户上传头像组件带大图预览,和删除功能
element用户上传头像组件带大图预览,和删除功能
|
3天前
|
移动开发 JavaScript 前端开发
大事件项目22_个人中心,更换头像,选择后预览
大事件项目22_个人中心,更换头像,选择后预览
|
2月前
|
搜索推荐
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
|
2月前
|
API
【sgUpload_v2】自定义组件:升级自定义上传组件,支持拖拽上传、弹窗上传单个或多个文件/文件夹,并且获取文件夹结构路径树,然后在右下角出现上传托盘。
【sgUpload_v2】自定义组件:升级自定义上传组件,支持拖拽上传、弹窗上传单个或多个文件/文件夹,并且获取文件夹结构路径树,然后在右下角出现上传托盘。
|
2月前
|
存储 资源调度 JavaScript
vue上传文件时显示上传进度
vue上传文件时显示上传进度
101 0
|
2月前
|
JavaScript
点击导出所选数据(原生js)
点击导出所选数据(原生js)
17 0
|
8月前
|
JavaScript
js仿通知栏新消息实时推送更新效果
js仿通知栏新消息实时推送更新效果
53 0
|
11月前
|
小程序 JavaScript 容器
小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)
movable-area 是 uniapp 的可移动区域组件。它用于定义可移动视图容器,在其内部可拖拽移动子视图。
408 0
|
11月前
|
JavaScript 定位技术 API
vue加载地图,在tab切换到其他页面再切换回来的时候,地图不显示了
vue加载地图,在tab切换到其他页面再切换回来的时候,地图不显示了
798 0