基于vue-draggable 实现三级拖动排序效果
之前项目中需要用到拖动排序,就去网上找资料,本来最开始是想用jquery-ui里的拖动的,后面发现不符合我的预期也不知道能不能跟vue.js兼容,后面我试过了,单个的可以但是层级太多就不一样了。
废话少说直接上代码
先看数据结构,和页面的呈现,等会再来上代码。
这就是三层结构渲染出来的图。那个海锚一样的东西是可以点击的,点击后会出现当前类型所带的产品。等会会说的
我们现在来看下我实现后的拖动效果,如下
所有父类型里面的产品拖动如下
控制台的打印
好了,放了那么多图,数据结构也发了。接下来我们来上代码和思路。
先上html的代码,这里我的页面是jsp,但是不影响html兼容,项目中途接手,很古老的jsp我也没办法
<%@ page contenttype="text/html;charset=utf-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:set var="ctx" value="${pagecontext.request.contextpath}"/> <link rel="stylesheet" href="${ctx}/res/ifw/plugins/datatables/datatables.bootstrap.css" rel="external nofollow" /> <style> [v-cloak] { display: none; } .flip-list-move { transition: transform 0.5s; } .handle { float: right; padding-top: 2px; padding-bottom: 8px; } .no-move { transition: transform 0s; } .ghost { opacity: 0.5; background: #c8ebfb; } .list-group { min-height: 20px; } .list-group-item { cursor: move; } .list-group-item i { cursor: pointer; } </style> <div id="vuesort" class="box box-darkness"> <div class="box-header with-border"> <h3 class="box-title">排序</h3> <div class="box-tools pull-right"> <button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i> </button> </div> </div> <div class="box-body" style="position: relative"> <div class="col-md-3"> <ul id="main-nav1" class="nav nav-tabs nav-stacked"> <draggable class="list-group" tag="ul" v-model="listproducttype":move="getdata" @update="datadragend"> <transition-group type="transition" :name="'flip-list'"> <li class="list-group-item" v-for="(element,index) in listproducttype" :key="element.id"> <a :href="'#'+forid(element.uuid)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="nav-header collapsed" data-toggle="collapse"><i v-show="element.productlist.length>0" aria-hidden="true" :class="{'fa fa-anchor':isactive,'glyphicon glyphicon-pushpin':!isactive}" @click="submenu"></i></a> {{element.name}} <i class="fa fa-align-justify handle" v-show="element.producttypes.length>0" @click="showleve2(index)"></i> <template v-if="element.productlist.length>0"> <ur :id="forid(element.uuid)" class="nav nav-list collapse secondmenu"> <draggable class="list-group" tag="ul":move="getdata" v-model="element.productlist" @update="datadragends"> <transition-group type="transition" :name="'flip-list'"> <li class="list-group-item" v-for="e in element.productlist" :key="e.id"> <a> {{e.name}}</a> </li> </transition-group> </draggable> </ur> </template> </li> </transition-group> </draggable> </ul> </div> <div class="col-md-3" v-show="one.producttypes.length>0&&showone"> <span><h3 style="color: red">--->>>{{one.name}}</h3></span> <ul id="main-nav2" class="nav nav-tabs nav-stacked"> <draggable class="list-group" tag="ul" v-model="one.producttypes" :move="getdata"@update="datadragend"> <transition-group type="transition" :name="'flip-list'"> <li class="list-group-item" v-for="(element,index) in one.producttypes" :key="element.id"> <a :href="'#'+forid(element.uuid)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="nav-header collapsed" data-toggle="collapse"><i v-show="element.productlist.length>0" aria-hidden="true" :class="{'fa fa-anchor':isactive,'glyphicon glyphicon-pushpin':!isactive}" @click="submenu"></i></a> {{element.name}} <i class="fa fa-align-justify handle" v-show="element.producttypes.length>0" @click="showleve3(index)"></i> <template v-if="element.productlist.length>0"> <ur :id="forid(element.uuid)" class="nav nav-list collapse secondmenu"> <draggable class="list-group" tag="ul":move="getdata" v-model="element.productlist" @update="datadragends"> <transition-group type="transition" :name="'flip-list'"> <li class="list-group-item" v-for="e in element.productlist" :key="e.id"> <a> {{e.name}}</a> </li> </transition-group> </draggable> </ur> </template> </li> </transition-group> </draggable> </ul> </div> <div class="col-md-3" v-show="two.producttypes.length>0&&showtwo"> <span><h3 style="color: red">--->>>{{two.name}}</h3></span> <ul id="main-nav3" class="nav nav-tabs nav-stacked"> <draggable class="list-group" tag="ul" v-model="two.producttypes":move="getdata" @update="datadragend"> <transition-group type="transition" :name="'flip-list'"> <li class="list-group-item" v-for="(element,index) in two.producttypes" :key="element.id"> <a :href="'#'+forid(element.uuid)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="nav-header collapsed" data-toggle="collapse"><i v-show="element.productlist.length>0" aria-hidden="true" :class="{'fa fa-anchor':isactive,'glyphicon glyphicon-pushpin':!isactive}" @click="submenu"></i></a> {{element.name}} <template v-if="element.productlist.length>0"> <ur :id="forid(element.uuid)" class="nav nav-list collapse secondmenu"> <draggable class="list-group":move="getdata" tag="ul" v-model="element.productlist" @update="datadragends"> <transition-group type="transition" :name="'flip-list'"> <li class="list-group-item" v-for="e in element.productlist" :key="e.id"> <a> {{e.name}}</a> </li> </transition-group> </draggable> </ur> </template> </li> </transition-group> </draggable> </ul> </div> </div> <div class="box-footer"> <button type="button" class="btn btn-darkness pull-right" id="dosearch" style="margin-left: 20px;"@click="save">保存 </button> <button type="button" class="btn btn-default pull-right" @click="reset" id="resetsearch">重置</button> </div> </div> <script type="text/javascript" src="${ctx}/res/js/vue/vue.js"></script> <!-- cdnjs :: sortable (https://cdnjs.com/) --> <script src="${ctx}/res/js/vue/sortable.min.js"></script> <!-- cdnjs :: vue.draggable (https://cdnjs.com/) --> <script src="${ctx}/res/js/vue/vuedraggable.umd.min.js"></script> <script type="text/javascript" src="${ctx}/res/js/sort/combinationsort.js"></script>
接下来是js。
vue.component('vue-draggable', vuedraggable) var vm = new vue({ el: '#vuesort', data: { isactive: true, queryobject: {}, listproducttype: [], showson: false, index: 0, one: {producttypes: []}, two: {producttypes: []}, showone: false, showtwo: false }, methods: { init: function () { var _this = this; $.ajax({ url: '../../mt/combinationsort/sortingdata', data: null, type: 'post', contenttype: "application/json", datatype: 'json', success: function (data) { if (data.success = true) { if (data.dataobject.length == 0) { util.alert('通知', '异常数据', 'info'); return; } _this.listproducttype = data.dataobject; } console.log(data) } }) }, reset: function () { var _this = this; _this.listproducttype = _this.listproducttype.sort((one, two) => { return one.displayseq - two.displayseq; }) ; for (var i in _this.listproducttype) { //排序产品类型 _this.listproducttype[i].producttypes = _this.listproducttype[i].producttypes.sort((one, two) => { return one.displayseq - two.displayseq; }) ; //排序产品 _this.listproducttype[i].productlist = _this.listproducttype[i].productlist.sort((one, two) => { return one.displayseq - two.displayseq; }) ; for (var a in _this.listproducttype[i].producttypes) { _this.listproducttype[i].producttypes[a].producttypes = _this.listproducttype[i].producttypes[a].producttypes.sort((one, two) => { return one.displayseq - two.displayseq; }) ; _this.listproducttype[i].producttypes[a].productlist = _this.listproducttype[i].producttypes[a].productlist.sort((one, two) => { return one.displayseq - two.displayseq; }) ; for (var c in _this.listproducttype[i].producttypes[a].producttypes) { _this.listproducttype[i].producttypes[a].producttypes[c].productlist = _this.listproducttype[i].producttypes[a].producttypes[c].productlist.sort((one, two) => { return one.displayseq - two.displayseq; }) ; } } } }, datadragend: function (evt) { console.log('拖动前的索引:' + evt.oldindex); console.log('拖动后的索引:' + evt.newindex); var obj = evt.item; obj.style.backgroundcolor = '#fff'; }, submenu: function () { var _this = this; if (_this.isactive) _this.isactive = false; else _this.isactive = true; if (_this.showson) _this.showson = false; else _this.showson = true; }, datadragends: function (evt) { console.log('拖动前的索引:' + evt.oldindex); console.log('拖动后的索引:' + evt.newindex); var obj = evt.item; obj.style.backgroundcolor = '#fff'; }, forid: function (index) { return "uuid_" + index }, showleve2: function (index) { var _this = this; _this.index = index; // if (_this.one.producttypes.length > 0) _this.one.producttypes = []; // else _this.one = _this.listproducttype[index]; console.log(_this.one) if (_this.showone) { _this.showone = false; _this.showtwo = false; } else _this.showone = true; }, showleve3: function (index) { var _this = this; // if (_this.two.producttypes.length > 0) _this.two.producttypes = []; // else _this.two = _this.listproducttype[_this.index].producttypes[index]; console.log(_this.two.producttypes) if (_this.showtwo) _this.showtwo = false; else _this.showtwo = true; }, getdata: function (event) { console.log("下来了"); var obj = event.dragged; obj.style.backgroundcolor = '#11cc17'; }, save: function () { var _this = this; util.confirm('提示', '您确定要保存排序吗?', function (isok) { if (isok) { console.log(_this.listproducttype); $.ajax({ type: "post", url: "../../mt/combinationsort/savesortingdata", data: {list: json.stringify(_this.listproducttype)}, success: function (json) { console.log(json); } }); util.alert("提示", '保存成功', 'info'); } }, 'info'); } }, created: function () { var _this = this; _this.init(); // _this.heartbeat(); } });
最重要的是这几行代码
然后是使用vue把vuedraggable模块引入,上面图最下面的js是我刚刚发过的代码文件。
vue.component('vue-draggable', vuedraggable)
这句话显得尤为重要。注册成vue的组件,虽然它本身就是vue的一个组件了。
当然最后我们会进行排序后的顺序的保存。这里就不得不说vue的双向绑定了,你对象只要在页面改变位置,在内存地址里的位置顺序也会被改变的,所有我们只需要再次将整个对象回传就行。后台去解析保存,当然这种方式我觉得会很繁琐。比如:我贴个获取数据的代码
/** * 获取排序数据 * * @param merchantid * @return */ public list<sortproducttypevo> treesorting(long merchantid) { //获取所有的连接项 list<producttyperef> typereflist = producttyperefservice.findall(); //获取所有的产品 map<long, productvo> productlist = productservice.sortfindproduct(merchantid).stream().collect( collectors.tomap(w -> w.getid(), w -> w)); //最上级父级 list<sortproducttypevo> parentlist = byparentproduct(merchantid, 0); //平均未分类 list<sortproducttypevo> typelist = byparentproduct(merchantid, 1); // //获取产品类型和产品关联 map<long, list<producttyperef>> parentidchildrenmap = typereflist.stream().filter(producttyperef -> producttyperef.getproducttypeid() != null).collect(collectors.groupingby(producttyperef::getproducttypeid)); parentlist.foreach(p -> { //筛选第二级菜单 list<sortproducttypevo> districtsone = typelist.stream().filter(sortproducttypevo -> sortproducttypevo.getparenttypeid().equals(p.getid())).collect(collectors.tolist()); districtsone.foreach(a -> { //第三层菜单 list<sortproducttypevo> districtstwo = typelist.stream().filter(producttype -> producttype.getparenttypeid().equals(a.getid())).collect(collectors.tolist()); districtstwo.stream().foreach(d -> { //获取产品和产品类型之间的连接关系 list<producttyperef> l = parentidchildrenmap.getordefault(d.getid(), new arraylist<>()); //排序产品关联就相当于产品排序 l.sort((q, b) -> integer.compare(q.getdisplayseq(), b.getdisplayseq())); //根据排序产品关联去找到产品 d.setproductlist(l.stream().map(e -> { productvo products = productlist.get(e.getproductid()); if (null != products) products.setdisplayseq(e.getdisplayseq()); return products; }).collect(collectors.tolist()).stream().filter(s -> s != null).collect(collectors.tolist()));//数组中过滤空的产品 // d.setproducttyperefs(parentidchildrenmap.getordefault(d.getid(), new arraylist<>())); }); list<producttyperef> l = parentidchildrenmap.getordefault(a.getid(), new arraylist<>()); l.sort((q, b) -> integer.compare(q.getdisplayseq(), b.getdisplayseq())); a.setproductlist(l.stream().map(c -> { productvo products = productlist.get(c.getproductid()); if (null != products) products.setdisplayseq(c.getdisplayseq()); return products; }).collect(collectors.tolist()).stream().filter(s -> s != null).collect(collectors.tolist())); districtstwo.sort((q, b) -> integer.compare(q.getdisplayseq(), b.getdisplayseq())); a.setproducttypes(districtstwo); // a.setproducttyperefs(parentidchildrenmap.getordefault(a.getid(), new arraylist<>())); }); list<producttyperef> l = parentidchildrenmap.getordefault(p.getid(), new arraylist<>()); l.sort((q, b) -> integer.compare(q.getdisplayseq(), b.getdisplayseq())); p.setproductlist(l.stream().map(a -> { productvo products = productlist.get(a.getproductid()); if (null != products) products.setdisplayseq(a.getdisplayseq()); return products; }).collect(collectors.tolist()).stream().filter(s -> s != null).collect(collectors.tolist())); // p.setproducttyperefs(parentidchildrenmap.getordefault(p.getid(), new arraylist<>())); districtsone.sort((q, b) -> integer.compare(q.getdisplayseq(), b.getdisplayseq())); p.setproducttypes(districtsone); }); parentlist.sort((q, b) -> integer.compare(q.getdisplayseq(), b.getdisplayseq())); return parentlist; }
jdk8语法,可能还有需要改进的地方。反正目前来说,功能是实现了。
其实本来想代码一点点讲解的,奈何实在是有事。
关于怎么让3级菜单组件相互拖动,你只需要在父级相互拖动这里就能找到答案,
加上这个属性就行,理论上。我没试过,因为我懒,hhhh
总结
以上所述是小编给大家介绍的基于vue-draggable 实现三级拖动排序效果,希望对大家有所帮助
推荐阅读
-
基于Dapper实现分页效果 支持筛选、排序、结果集总数等
-
基于vue-draggable 实现三级拖动排序效果
-
基于Vue2实现简易的省市区县三级联动组件效果
-
Vue 基于 vuedraggable 实现选中、拖拽、排序效果
-
基于Mui中picker选择器实现省市县三级联动效果中setData方法传值的问题解决办法
-
基于Dapper实现分页效果 支持筛选、排序、结果集总数等
-
基于JQuery的列表拖动排序实现代码_jquery
-
基于jquery的一行代码轻松实现拖动效果_jquery
-
用dragsort +bootstrap+php实现 table拖动自动保持排序,前面加序号及时更新效果
-
基于jquery实现省市区三级联动效果_jquery