源码地址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>
复制代码