源碼地址javascript
最近在作一個基於vue的後臺管理項目。平時項目進度統計就在上禪道上進行。so~ 而後領導就感受這個拖拽效果還行,能不能加到我們項目裏面。 既然領導發話,那就開幹。。css
全部技術:vue + vuedraggablehtml
拖動的實現基於 vuedraggable的插件開發。前端
主頁爲兩欄流式佈局,每個組件能夠在上下拖動,也能夠左右拖動。vue
這塊佈局爲最爲普通的兩欄佈局,這裏採用flex佈局。左邊自適應,右邊爲固定寬。html5
.layout-container {
display: flex;
.left {
flex: 1;
margin-right: 40px;
}
.right {
width: 550px;
}
}
複製代碼
這裏使用 vuedraggable
插件。須要在組件裏面引入使用。draggable
至關於拖拽容器,這塊很明顯須要兩個拖拽的容器。因此分別在 .left
.right
中添加兩個拖拽容器。在默認狀況下,這裏已經能夠進行拖拽了。插件的效果仍是很強大。java
<div class="layout-container">
<!--左欄-->
<div class="left">
<draggable
v-bind="dragOptions"
class="list-group"
:list="item"
>
// ... 拖拽元素或組件
</draggable>
</div>
<!--右欄-->
<div class="right">
<draggable
v-bind="dragOptions"
class="list-group"
:list="item"
>
// ... 拖拽元素或組件
</draggable>
</div>
</div>
<script>
import draggable from "vuedraggable";
export default {
components: {draggable},
computed: {
dragOptions() {
return {
animation: 30,
handle: ".drag-handle",
group: "description",
ghostClass: "ghost",
chosenClass: "sortable",
forceFallback: true
};
}
}
};
</script>
複製代碼
可是, 和我想要的效果仍是相差一點。git
這塊只須要配置相關的配置項就能夠比較簡單。 左右拖動須要給拖拽容器指定相同的 group
屬性。指定標題元素拖動須要配置 handle
爲可拖動元素的選擇器名稱。github
下面簡單介紹下經常使用的配置項:數據庫
採用相關配置以下:
computed: {
dragOptions() {
return {
animation: 30,
handle: ".drag-handle",
group: "description",
ghostClass: "ghost",
chosenClass: "sortable",
forceFallback: true
};
}
}
複製代碼
在拖動的時候,咱們須要作三個事情。拖動時,拖動元素只顯示標題欄,兩欄內列表只顯示標題元素以及將要移動的位置變灰。
拖動元素只顯示標題欄: 在默認狀況下,會開啓 html5
元素的拖動效果。這裏明顯不須要。forceFallback
改成 false
則能夠關閉 html5
的默認效果。順便經過 chosenClass: "sortable"
修改拖動元素class 類名。直接用css進行隱藏
.sortable {
.component-box {
display: none;
height: 0;
}
}
複製代碼
兩欄內列表只顯示標題元素 這裏我藉助兩個事件實現。
<div class="layout-container" :class="{drag:dragging}">
//...
</div>
複製代碼
data() {
return {
dragging: false
};
},
methods: {
onStart() {
this.dragging = true;
},
onEnd() {
this.dragging = false;
}
}
複製代碼
.drag {
.component-box {
display: none;
}
}
複製代碼
在開始拖動的時候給 .layout-container
添加 .drag
的 class 名。拖動結束時,移除class名。
將要移動的位置變灰
這裏須要用到上面 ghostClass: "ghost"
配置項。並添加相應的css。
.ghost {
.drag-handle {
background: rgb(129, 168, 187);
}
}
複製代碼
好了基本已經實現了。。。
接下來就是數據的動態展現了。 這裏須要vue中的動態組件了。。附上官方文檔鏈接點擊查看。
而後裏面每一個拖動的元素的內容都寫成組件,搭配動態組件實現自由拖動。
// 將所用組件引入
import {
timeline,
calendar,
welcome,
carousel,
imgs,
KonList
} from "@/components/DragComponents";
components: {
draggable,
timeline,
calendar,
welcome,
carousel,
imgs,
KonList
}
複製代碼
配合 v-for
對數據進行循環,而後進行動態展現。
<component :is="element.name"/>
複製代碼
這塊涉及到數據格式相關的,能夠直接看文末的代碼。。。 這裏就就不展開說了。。
在拖動結束後,咱們須要將拖動的順序緩存在前端,當下次進入後,能夠繼續使用拖動後的數據。
// 獲取新的佈局
getLayout() {
let myLayout = JSON.parse(window.localStorage.getItem("kon"));
if (!myLayout || Object.keys(myLayout).length === 0)
myLayout = this.layout;
const newLayout = {};
for (const side in myLayout) {
newLayout[side] = myLayout[side].map(i => {
return this.componentList.find(c => c.id === i);
});
}
this.mainData = newLayout;
},
// 設置新的佈局
setLayout() {
const res = {};
for (const side in this.mainData) {
const item = this.mainData[side].map(i => i.id);
res[side]=item;
}
window.localStorage.setItem("kon", JSON.stringify(res));
}
複製代碼
這樣我只須要在 mounted
中獲取新的佈局。。
mounted() {
this.getLayout();
}
複製代碼
在拖動結束後,設置新的佈局
onEnd() {
this.dragging = false;
this.setLayout();
}
複製代碼
在項目中,仍是建議配合後端進行用戶佈局的數據存儲,每次拖動後將新的佈局數據請求接口保存在數據庫,同時存入緩存中。當再次進入頁面的時候,讀取緩存中的數據,沒有的話請求後端的接口拿到用戶的佈局,而後再次存入緩存中。有的話直接讀取緩存中的數據。
其實上面的效果也不是特別難,簡單花點時間,看看相關文檔,就能作出來,,記錄在掘金上面,只是想和你們分享個人思路。同時但願和你們一塊兒交流,一塊兒進步。
生活不易,你們加油
附上源碼: 項目地址
<template>
<div :class="{drag:dragging}">
<div class="layout-container">
<div :class="key" v-for="(item, key) in mainData" :key="key">
<draggable v-bind="dragOptions" class="list-group" :list="item" @end="onEnd" @start="onStart" >
<transition-group name="list">
<div class="list-group-item" v-for="(element, index) in item" :key="index">
<div class="drag-handle">{{ element.title }}</div>
<div class="component-box">
<component :is="element.name"/>
</div>
</div>
</transition-group>
</draggable>
</div>
</div>
</div>
</template>
<script> import draggable from "vuedraggable"; import { timeline, calendar, welcome, carousel, imgs, KonList } from "@/components/DragComponents"; export default { components: { draggable, timeline, calendar, welcome, carousel, imgs, KonList }, data() { return { dragging: false, componentList: [ { name: "KonList", title: "追番地址", id: "5" }, { name: "imgs", title: "五月最強新番", id: "4" }, { name: "timeline", title: "日程組件", id: "2" }, { name: "carousel", title: "走馬燈組件", id: "1" }, { name: "calendar", title: "日曆組件", id: "3" } ], layout: { left: ["5", "4"], right: ["2", "1", "3"] }, mainData: {} }; }, computed: { dragOptions() { return { animation: 30, handle: ".drag-handle", group: "description", ghostClass: "ghost", chosenClass: "sortable", forceFallback: true }; } }, mounted() { this.getLayout(); }, methods: { onStart() { this.dragging = true; }, onEnd() { this.dragging = false; this.setLayout(); }, getLayout() { let myLayout = JSON.parse(window.localStorage.getItem("kon")); if (!myLayout || Object.keys(myLayout).length === 0) myLayout = this.layout; const newLayout = {}; for (const side in myLayout) { newLayout[side] = myLayout[side].map(i => { return this.componentList.find(c => c.id === i); }); } this.mainData = newLayout; }, setLayout() { const res = {}; for (const side in this.mainData) { const item = this.mainData[side].map(i => i.id); res[side]=item; } window.localStorage.setItem("kon", JSON.stringify(res)); } } }; </script>
<style lang="scss" scoped> .layout-container { height: 100%; display: flex; .left { flex: 1; margin-right: 40px; } .right { width: 550px; } .list-group-item { margin-bottom: 20px; border-radius: 6px; overflow: hidden; background: #fff; } .component-box { padding: 20px; } .drag-handle { cursor: move; height: 40px; line-height: 40px; color: #fff; font-weight: 700; font-size: 16px; padding: 0 20px; background: #6cf; } } .drag { .component-box { display: none; } } .list-enter-active { transition: all .3s linear; } .list-enter, .list-leave-to { opacity: .5; } .sortable { .component-box { display: none; height: 0; } } .list-group { > span { display: block; min-height: 20px; } } .ghost { .drag-handle { background: rgb(129, 168, 187); } } </style>
複製代碼