效果展示
最后实现效果:实现首页应用列表的添加、移除以及拖拽排序的功能。
准备工作
引入Sortable插件,Sortable插件是一款轻量级的拖拽排序插件,它可以轻松实现列表、表格、以及树结构的元素的拖拽排序。
这里可利用npm安装相关依赖,命令如下。
npm install sortablejs --save
对于uniapp的项目,只需在页面引入sortablejs里面的Sortable.min.js文件即可。
拖拽实现
1. Sortable的拖拽功能是基于HTML5的Drag and Drop API实现的。这个API允许我们将元素从一个位置拖动到另一个位置,并在过程中触发一系列事件,其实,拖拽元素就是一个DOM元素。
2. 对于uniapp的项目而言,由于它并没有document,不能进行相关的DOM操作,因此官方推出了renderjs方案来解决这一问题。
首先需要绑定拖拽区域的id,然后给拖拽的元素拖拽属性data-id赋值,代表其唯一性,最后调用Sortable.min.js实现拖拽。
<view class="menue-list-item" id="sort">
<view class="list-item-one" :data-id="item.name" v-for="(item,index) in ofenlist" :key="index" >
<image :src="item.iconurl"></image>
<image class="delete-icon" src="../../static/delete.png"></image>
<view class="list-item-name">{{ item.name }}</view>
</view>
</view>
<script module='sortable' lang="renderjs">
export default {
mounted() {
this.initSortable()
},
methods: {
initSortable() {
if (typeof window.Sortable === 'function') {
this.setSortable()
} else {
const script = document.createElement('script')
script.src = 'static/js/Sortable.min.js'
script.onload = this.setSortable.bind(this)
document.head.appendChild(script)
}
},
setSortable() {
let option = {
//动画参数
animation: 300,
onEnd: (e) => {
// 拖拽完成后回调
this.$ownerInstance.callMethod('changeSort', sortable.toArray());
}
}
let sortable = Sortable.create(document.getElementById('sort'), option);
},
}
}
</script>
完整代码
以下是上述实现效果示例的完整代码,其他的拖拽事件以及拖拽动画,请前往官网查看。
<template>
<view class="menue-page">
<view class="menue-one-type">
<view class="ofen-type-name">
<view style="font-weight: bold">首页应用</view>
<view>
<button v-if="!editofen" type="primary" @click="editofen=true" size="mini">编辑</button>
<button v-if="editofen" type="primary" size="mini" @click="editofen=false">保存</button>
</view>
</view>
<!-- 使用v-show 不能删除dom id选择器 ,从而实现点击编辑后才可以拖拽-->
<view class="menue-list-item" id="sort" v-show="editofen">
<view class="list-item-one" :data-id="item.name" v-for="(item,index) in ofenlist" :key="index" @click="deleteofen(item)">
<image :src="item.iconurl"></image>
<image class="delete-icon" src="../../static/delete.png"></image>
<view class="list-item-name">{{ item.name }}</view>
</view>
</view>
<view class="menue-list-item" v-show="!editofen">
<view class="list-item-one" v-for="(item,index) in ofenlist" :key="index">
<image :src="item.iconurl"></image>
<view class="list-item-name">{{ item.name }}</view>
</view>
</view>
</view>
<view class="menue-one-type" v-for="(item,index) in menuelist" :key="index">
<view class="type-name">{{ item.typename }}</view>
<view class="menue-list-item">
<view class="list-item-one" v-for="(item2,index2) in item.childlist" :key="index2" @click="addofen(item2)">
<image :src="item2.iconurl"></image>
<!-- 判断该应用是否在首页应用列表中,从而是否显示右上角添加图标 -->
<image v-if="editofen && isofen(item2)" class="delete-icon" src="../../static/add.png"></image>
<view class="list-item-name">{{ item2.name }}</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
value: '1',
editofen: false,
// 首页应用列表
ofenlist: [
{
'name': '应用2',
'iconurl': '../../static/logo.png',
},
{
'name': '应用3',
'iconurl': '../../static/logo.png',
},
{
'name': '应用5',
'iconurl': '../../static/logo.png'
}
],
// 所有的应用(按类别)列表
menuelist: [{
'typename': '应用类别1',
'childlist': [{
'name': '应用1',
'iconurl': '../../static/logo.png',
},
{
'name': '应用2',
'iconurl': '../../static/logo.png',
},
{
'name': '应用3',
'iconurl': '../../static/logo.png',
},
{
'name': '应用4',
'iconurl': '../../static/logo.png',
},
]
},
{
'typename': '应用类别2',
'childlist': [{
'name': '应用5',
'iconurl': '../../static/logo.png',
},
{
'name': '应用6',
'iconurl': '../../static/logo.png',
},
{
'name': '应用7',
'iconurl': '../../static/logo.png',
},
{
'name': '应用8',
'iconurl': '../../static/logo.png',
}
]
}
]
}
},
onLoad() {
},
methods: {
// 拖拽后回调
changeSort(e) {
console.log(e)
},
// 添加到首页应用
addofen(e){
// 先判断应用是否已在首页应用中,在则不添加,不在则添加
if(this.isofen(e)) this.ofenlist.push(e)
},
// 从首页应用中移除
deleteofen(e){
this.ofenlist = this.ofenlist.filter(item => item!==e)
},
// 判断应用是否已在首页应用中
isofen(obj){
if(JSON.stringify(this.ofenlist).indexOf(JSON.stringify(obj)) === -1){
return true
}
return false
}
}
}
</script>
<script module='sortable' lang="renderjs">
export default {
mounted() {
this.initSortable()
},
methods: {
initSortable() {
if (typeof window.Sortable === 'function') {
this.setSortable()
} else {
const script = document.createElement('script')
script.src = 'static/js/Sortable.min.js'
script.onload = this.setSortable.bind(this)
document.head.appendChild(script)
}
},
setSortable() {
let option = {
//动画参数
animation: 300,
onEnd: (e) => {
// 拖拽完成后回调
this.$ownerInstance.callMethod('changeSort', sortable.toArray());
}
}
let sortable = Sortable.create(document.getElementById('sort'), option);
},
}
}
</script>
<style lang="scss">
page {
background-color: #f4f4f5;
}
.menue-page {
width: 100%;
.menue-search {
margin: 20rpx;
}
.menue-one-type {
margin: 20rpx;
background-color: #ffffff;
border-radius: 10rpx;
padding: 0rpx 20rpx 20rpx;
.ofen-type-name {
font-size: 30rpx;
color: #4f4f4f;
border-bottom: 1px solid #ececed;
padding: 20rpx 0rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.type-name {
font-size: 30rpx;
font-weight: bold;
color: #4f4f4f;
border-bottom: 1px solid #ececed;
padding: 20rpx 0rpx;
}
.menue-list-item {
display: flex;
flex-wrap: wrap;
.list-item-one {
width: 25%;
text-align: center;
margin-top: 35rpx;
margin-bottom: 15rpx;
position: relative;
display: inline-block;
image {
width: 65rpx;
height: 65rpx;
}
.delete-icon {
position: absolute;
top: -10px;
right: 0px;
width: 20px;
height: 20px;
}
.list-item-name {
font-size: 26rpx;
margin-top: 20rpx;
}
}
}
}
}
</style>