我们公司管理后台项目是使用Element-ui组件,这次需求产品要求上传的图片组允许拖拽排序,我就想用vue-draggable插件了,但是相信Element-ui的el-upload组件封装的很好,我这种菜鸟级别的前端哪里敢动,所以我就想着上传依然用el-upload,但是把上传组件的展示图片隐藏,自己根据组件的上传之后拿到的url链接自己形成图片数组,然后展示层由我们自己来写UI和vue-draggable的拖拽,话不多说,上代码,直接莽!
emmm...先来两张效果图吧
<template> <div class="com-image-drag"> <div class="button-list"> <el-button @click="openDrag" v-if="!drag_open" :disabled="banner_list.length <= 1" type="text" size="small" class="operation-success" ></el-button> <el-button @click="save" v-if="drag_open" type="text" size="small" class="operation-success" ></el-button> <el-button @click="cancle" v-if="drag_open" type="text" size="small" class="operation-error" ></el-button> </div> <div class="image-list"> <!-- 拖拽层 --> <div class="list-wrap" v-show="drag_open"> <draggable v-model="banner_list" :options="{ animation: 150, ghostClass: 'sortable-ghost', chosenClass: 'chosenClass', scroll: true, scrollSensitivity: 200 }" > <div class="image-item" v-for="($item, $index) in banner_list" :key="$index" :style="{ backgroundImage: `url(${$item.url})` }" ></div> </draggable> </div> <!-- 展示层 --> <div class="list-wrap" v-show="!drag_open"> <div class="image-item" v-for="($item, $index) in banner_list" :key="$index" :style="{ backgroundImage: `url(${$item.url})` }" @mouseover.prevent="$item.is_hover = true" @mouseleave.prevent="$item.is_hover = false" > <div class="label" v-show="!$item.is_hover"> <i class="el-icon-upload-success el-icon-check icon-success"></i> </div> <div class="mask" v-show="$item.is_hover"> <i class="el-icon-delete bin" @click="deleteImage($index)"></i> </div> </div> <el-upload v-show="limit == 0 || banner_list.length < limit" list-type="picture-card" name="file" class="upload-machine" :disabled="drag_open" :action="action()" :on-error="onError" :on-success="onSuccess" :before-upload="beforeUpload" :show-file-list="false" :multiple="multiple" enctype="multipart/form-data" ></el-upload> </div> </div> </div> </template> <script> /** * @author LeeYunxiang * @description 为了方便上传图片组件可拖拽排序,不改变饿了么插件的逻辑,只做视图层的展示 * @param {Array} list 图片数组 * @param {Number} limit 最多可上传几张图片 * @param {Function} action 上传接口地址 * @param {Boolean} multiple 是否批量上传 * @param {Function} beforeUpload 上传之前的回调,用于校验 * @param {Function} onSuccess 上传成功的回调函数 * @param {Function} onError 上传失败的回调函数 */ import draggable from "vuedraggable"; export default { name: "ComImageShow", components: { draggable }, props: { list: { type: Array }, limit: { type: Number, default: 0 }, multiple: { type: Boolean, default: false }, action: { type: Function, default: () => {} }, beforeUpload: { type: Function, default: () => {} }, onError: { type: Function, default: () => {} }, onSuccess: { type: Function, default: () => {} } }, data() { return { banner_list: [], //拖拽插件不建议直接改变父组件的传值,所以另建一个新数组 file_list: [], //保存开启拖拽之前排序的数组 drag_open: false //拖拽开启开关 }; }, methods: { // 删除图片 deleteImage(i) { this.banner_list.splice(i, 1); this.$emit("update", this.banner_list.map(item => item.url)); }, // 开启拖拽 openDrag() { this.file_list = JSON.parse(JSON.stringify(this.banner_list)); //数组深拷贝 this.drag_open = true; }, // 取消拖拽 cancle() { this.banner_list = this.file_list; this.drag_open = false; }, // 拖拽保存 save() { this.$emit("update", this.banner_list.map(item => item.url)); this.drag_open = false; } }, mounted() { // 初始数组拷贝 this.banner_list = this.list.map(url => { let obj = { url: url, is_hover: false }; return obj; }); }, watch: { // 监听父组件传值改变 list(arr) { if (arr.length > this.limit && this.limit != 0) { this.$message.warning(`当前最多可上传${this.limit}张图片`); return false; } this.banner_list = arr.map(url => { let obj = { url: url, is_hover: false }; return obj; }); } } }; </script> <style lang="sass" scoped> .com-image-drag &:after display: block clear: both content: "" .image-list float: left &:after display: block clear: both content: "" .list-wrap float: left .image-item width: 148px height: 148px position: relative margin-right: 10px margin-bottom: 10px border: 1px solid #c0ccda background-size: 100% 100% border-radius: 6px float: left overflow: hidden cursor: pointer .label width: 46px height: 26px background-color: #13ce66 color: #FFFFFF transform: rotate(45deg) text-align: center position: absolute right: -17px top: -7px .icon-success transform: rotate(-45deg) .mask width: 100% height: 100% border-radius: 6px background-color: rgba(0, 0, 0, 0.5) position: relative .bin color: #FFFFFF font-size: 20px position: absolute left: 45% top: 43% .upload-machine float: left </style>
调用例子
<template> <image-drag :list="file_list" :multiple="true" :action="uploadUrl" :on-error="uploadError" :on-success="bannerPicSuccess" :before-upload="beforeAvatarUpload" @update="updateFile"> </image-drag> </template> <script> import ImageDrag from "@/components/common/ComImageDrag"; import { Loading } from "element-ui"; export default { components: { ImageDrag }, data() { return { banner_list: [], //ele用的 file_list: [], //自己用的 bargain: { share_image: "" }, number: "" }; }, methods: { goBack() { this.$router.go(-1); }, // 上传图片路径 uploadUrl() { return `${process.env.VUE_APP_API_ROOT}upload`; }, // 图片长传-之前 beforeAvatarUpload(file) { let self = this; let type_arr = ["image/jpeg", "image/png"]; let type = file.type; if (!type_arr.includes(type)) { this.$message.error("图片格式不正确,只支持jpg和png类型图片"); return false; } const is_size = new Promise((resolve, reject) => { let width = 400; let height = 320; let img = new Image(); img.src = window.URL.createObjectURL(file); img.onload = () => { let valid = img.width === width && img.height === height; if (valid) { Loading.service({ fullscreen: true, text: "图片上传中,请稍后" }); resolve(file); } else { self.$message.error("请上传400*320px大小的图片!"); reject(); } }; }); return is_size; }, // Banner图-成功 bannerPicSuccess(res) { this.bargain.share_image = res.data; Loading.service({ fullscreen: true }).close(); this.file_list.push(res.data); }, // Banner图片上传报错 uploadError() { this.$message.error("上传失败,请重新上传"); Loading.service({ fullscreen: true }).close(); }, updateFile(val) { this.file_list = val; console.log(this.file_list); } } }; </script>