基于vue-draggable 實(shí)現(xiàn)三級(jí)拖動(dòng)排序效果
之前項(xiàng)目中需要用到拖動(dòng)排序,就去網(wǎng)上找資料,本來最開始是想用jquery-ui里的拖動(dòng)的,后面發(fā)現(xiàn)不符合我的預(yù)期也不知道能不能跟vue.js兼容,后面我試過了,單個(gè)的可以但是層級(jí)太多就不一樣了。
廢話少說直接上代碼


先看數(shù)據(jù)結(jié)構(gòu),和頁面的呈現(xiàn),等會(huì)再來上代碼。
這就是三層結(jié)構(gòu)渲染出來的圖。那個(gè)海錨一樣的東西是可以點(diǎn)擊的,點(diǎn)擊后會(huì)出現(xiàn)當(dāng)前類型所帶的產(chǎn)品。等會(huì)會(huì)說的
我們現(xiàn)在來看下我實(shí)現(xiàn)后的拖動(dòng)效果,如下

所有父類型里面的產(chǎn)品拖動(dòng)如下

控制臺(tái)的打印

好了,放了那么多圖,數(shù)據(jù)結(jié)構(gòu)也發(fā)了。接下來我們來上代碼和思路。
先上html的代碼,這里我的頁面是jsp,但是不影響html兼容,項(xiàng)目中途接手,很古老的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('通知', '異常數(shù)據(jù)', '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) {
//排序產(chǎn)品類型
_this.listProductType[i].productTypes = _this.listProductType[i].productTypes.sort((one, two) => {
return one.displaySeq - two.displaySeq;
})
;
//排序產(chǎn)品
_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('拖動(dòng)前的索引:' + evt.oldIndex);
console.log('拖動(dòng)后的索引:' + 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('拖動(dòng)前的索引:' + evt.oldIndex);
console.log('拖動(dòng)后的索引:' + 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是我剛剛發(fā)過的代碼文件。
Vue.component('vue-draggable', vuedraggable)
這句話顯得尤為重要。注冊(cè)成vue的組件,雖然它本身就是vue的一個(gè)組件了。

當(dāng)然最后我們會(huì)進(jìn)行排序后的順序的保存。這里就不得不說vue的雙向綁定了,你對(duì)象只要在頁面改變位置,在內(nèi)存地址里的位置順序也會(huì)被改變的,所有我們只需要再次將整個(gè)對(duì)象回傳就行。后臺(tái)去解析保存,當(dāng)然這種方式我覺得會(huì)很繁瑣。比如:我貼個(gè)獲取數(shù)據(jù)的代碼
/**
* 獲取排序數(shù)據(jù)
* * @param merchantId
* @return
*/
public List<SortProductTypeVo> treeSorting(Long merchantId) {
//獲取所有的連接項(xiàng)
List<ProductTypeRef> typeRefList = productTypeRefService.findAll();
//獲取所有的產(chǎn)品
Map<Long, ProductVo> productList = productService.sortFindProduct(merchantId).stream().collect(
Collectors.toMap(w -> w.getId(), w -> w));
//最上級(jí)父級(jí)
List<SortProductTypeVo> parentList = byParentProduct(merchantId, 0);
//平均未分類
List<SortProductTypeVo> typeList = byParentProduct(merchantId, 1);
//
//獲取產(chǎn)品類型和產(chǎn)品關(guān)聯(lián)
Map<Long, List<ProductTypeRef>> parentIdChildrenMap = typeRefList.stream().filter(productTypeRef -> productTypeRef.getProductTypeId() != null).collect(Collectors.groupingBy(ProductTypeRef::getProductTypeId));
parentList.forEach(p -> {
//篩選第二級(jí)菜單
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 -> {
//獲取產(chǎn)品和產(chǎn)品類型之間的連接關(guān)系
List<ProductTypeRef> l = parentIdChildrenMap.getOrDefault(d.getId(), new ArrayList<>());
//排序產(chǎn)品關(guān)聯(lián)就相當(dāng)于產(chǎn)品排序
l.sort((q, b) -> Integer.compare(q.getDisplaySeq(), b.getDisplaySeq()));
//根據(jù)排序產(chǎn)品關(guān)聯(lián)去找到產(chǎn)品
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()));//數(shù)組中過濾空的產(chǎn)品
// 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語法,可能還有需要改進(jìn)的地方。反正目前來說,功能是實(shí)現(xiàn)了。
其實(shí)本來想代碼一點(diǎn)點(diǎn)講解的,奈何實(shí)在是有事。
關(guān)于怎么讓3級(jí)菜單組件相互拖動(dòng),你只需要在父級(jí)相互拖動(dòng)這里就能找到答案,

加上這個(gè)屬性就行,理論上。我沒試過,因?yàn)槲覒校琱hhh
總結(jié)
以上所述是小編給大家介紹的基于vue-draggable 實(shí)現(xiàn)三級(jí)拖動(dòng)排序效果,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
Vue頁面內(nèi)公共的多類型附件圖片上傳區(qū)域并適用折疊面板(示例代碼)
本文中實(shí)現(xiàn)的附件上傳區(qū)域支持超多類型附件分類型上傳,并且可根據(jù)特定條件具體展示某些類型的附件上傳,本文給大家分享Vue頁面內(nèi)公共的多類型附件圖片上傳區(qū)域并適用折疊面板的示例代碼,需要的朋友參考下吧2021-12-12
VUE2.0 ElementUI2.0表格el-table自適應(yīng)高度的實(shí)現(xiàn)方法
在開發(fā)中,需要表格控件根據(jù)瀏覽器高度進(jìn)行調(diào)整,固定表頭,本文主要介紹了VUE2.0 ElementUI2.0表格el-table自適應(yīng)高度的實(shí)現(xiàn)方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-11-11
vue router-link 默認(rèn)a標(biāo)簽去除下劃線的實(shí)現(xiàn)
這篇文章主要介紹了vue router-link 默認(rèn)a標(biāo)簽去除下劃線的實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11
Electron+vue3項(xiàng)目使用SQLite3數(shù)據(jù)庫詳細(xì)步驟(超詳細(xì))
基于vue實(shí)現(xiàn)循環(huán)滾動(dòng)列表功能
springboot+vue+對(duì)接支付寶接口+二維碼掃描支付功能(沙箱環(huán)境)

