小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)

这篇具有很好参考价值的文章主要介绍了小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

效果展示

小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)

思路

  • 使用movable-area作为可移动区域,并在内部循环渲染列表项view,绑定touch事件。
  • 在mounted生命周期函数内获取区域movable-area的dom信息,记录列表项的坐标信息。
  • 在methods中定义了列表项的touchstart、touchmove和touchend事件的方法,用于实现列表项的拖拽移动和位置变更。
  • watch监听列表项数据listData的变化,并抛出事件,通知列表变更。

具体步骤

1, 在components文件夹新建healer-dragList文件夹,在healer-dragList文件夹下新建AppList.vue组件

小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)

使用movable-area创建一个可移动区域容器

movable-area 是 uniapp 的可移动区域组件。它用于定义可移动视图容器,在其内部可拖拽移动子视图。
在 movable-area 组件中,可以使用 movable-view 组件定义可移动的子视图。movable-view 必须是 movable-area 的直接子节点,不支持嵌套 movable-view。

movable-area 的属性有:

  • scale - 手势缩放比例,默认为1, 范围0~10
  • direction - 可移动方向,值有 ‘all’,‘vertical’,‘horizontal’,‘none’
    movable-view 的属性有:
  • direction - 可移动方向,同 movable-area 的 direction
  • inertia - 是否启用滚动惯性,默认false
  • outOfBounds - 超出可移动区域后,movable-view 的行为,可选值有 ‘none’、‘hidden’、‘bounce’
  • x/y - movable-view 的位置
  • damping - 阻尼系数,用于控制x或y变化的动画和过界回弹的衰减速度。取值范围[0, 1]。
  • friction - 摩擦系数,用于控制x或y变化的动画和过界回弹的摩擦力。取值范围[0, 1]。

movable-area 和 movable-view 通常搭配使用,来实现可拖拽排序的列表效果。

小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)
//AppList.vue

<template>
	<view>
		<!-- 可移动区域容器 -->
		<movable-area class="movarea" ref="areaBox" id="areaBox">
			<!-- 这块只是循环出固定内容,监听其元素touch事件获取坐标 -->
			<view class="appList">
				<view class="app-li text-blue" v-for="(appItem,index) in listData_c" :key="appItem.name"
					:id="'appLi' + index" :class="(hoverClass==='appLi'+index)?'select':''"
					@touchstart="AppLi_touchstart(index,$event)" @touchmove="AppLi_touchmove"
					@touchend="AppLi_touchend(index)">
					<uni-icons type="minus-filled" size="20" class='rightIcon' @click="dleIcon(index,appItem)"
						v-if="isEdti"></uni-icons>
					<image class="appIcon" :src="appItem.appIcon" mode="widthFix"></image>
					<text class="appName">{{appItem.appName}}</text> 
					<text class="appicon cuIcon-roundclosefill"
						:class="deleteAppID===appItem.appId && showDelete?'':'hide'" @tap="deleteAppItem(index)"></text>
				</view>
				<view class="app-li text-blue" @tap="addAppItem">
					<text class="appicon cuIcon-roundadd"></text>
				</view>
			</view>
			<!-- 滑块 -->
			<movable-view v-if="moviewShow" :animation="false" class="moveV text-blue" :x="moveX" :y="moveY"
				direction="all" :style="{ width: moveViewSize + 'px', height: 160 + 'rpx' }">
				<image class="appIcon" :src="touchItem.appIcon" mode="widthFix"></image>
				<text class="appName">{{touchItem.appName}}</text>
			</movable-view>
		</movable-area>
		<!-- 新增模态 -->
		<!-- <Modal ref="addAppItem" title="添加" @confirm="confirm">
			<InputUnify ref="addAppInput" title="名字" placeholder="请输入应用名"></InputUnify>
		</Modal> -->
	</view>
</template>

<script>
	// import InputUnify from "@/components/unify-input.vue"
	export default {
		name: "AppList",
		props: {
			listData: {
				type: Array,
				default: () => {
					return []
				}
			},
			isEdti: {
				type: Boolean,
				default: false
			}
		},
		data() {
			return {
				listData_c: this.listData, //缓存props,(不建议直接修改props)
				// CheckAppId: null,
				deleteAppID: null, //触发删除的itemID
				showDelete: false, //删除按钮状态
				IsDeleteAfter: false, //是否为删除后
				IsCancelDelete: false, //是否为取消后
				moviewShow: false, //滑块状态
				areaBoxInfo: null, //保存滑动区域盒子dom信息
				inBoxXY: {}, //鼠标在item中的坐标
				touchIndex: 0, //被移动index
				touchItem: '', //备份被移动item数据
				moveX: 0, //相对滑动盒子的坐标
				moveY: 0, //相对滑动盒子的坐标
				hoverClass: '',
				hoverClassIndex: null, //最终index
			};
		},
		watch: {
			listData_c(val) {
				this.$emit("listChange", val)
			}
		},
		computed: {
			moveViewSize() {
				if (this.areaBoxInfo && this.areaBoxInfo.width) {
					return this.areaBoxInfo.width / 5
				} else {
					return 0
				}

			}
		},
		components: {
			// InputUnify
		},
		mounted() {
			// 获取dom信息
			this.resetListDom()
		},
		methods: {
			dleIcon(a, b) {
				this.$emit("lowAppList", a);
			},
			getDomInfo(id, callBack) {
				const query = uni.createSelectorQuery().in(this);
				query.select('#' + id)
					.boundingClientRect()
					.exec(function(res) {
						callBack(res[0]);
					});
			},
			// 添加
			addAppItem() {
				this.$refs.addAppItem.ModalStatus()
			},
			confirm() {
				let appItem = {
					appId: this.listData_c.length + 1,
					appIcon: "cuIcon-pic",
					appName: this.$refs.addAppInput.value,
					appLink: ""
				};
				this.listData_c.push(appItem);
				this.$refs.addAppInput.resetVal();
				this.$nextTick(() => {
					this.resetListDom()
				});

			},
			AppLi_touchstart(index, event) {
				this.touchItem = this.listData_c[index];
				// 行为判断
				if (this.showDelete) {
					// 取消删除
					if (this.touchItem.appId != this.deleteAppID) {
						this.deleteAppID = null;
						this.showDelete = false;
						this.IsCancelDelete = true;
					}
					// 删除
					// if(this.touchItem.appId==this.deleteAppID){
					// 	this.deleteAppItem(index)
					// }
				}
				// 过时触发(touchEnd中清除此定时器)
				this.Loop = setTimeout(
					() => {
						// 触感反馈(安卓上是150毫秒,ios无短触控反馈)
						uni.vibrateShort();
						this.showDelete = true;
						this.deleteAppID = this.touchItem.appId;
						// 拖动逻辑
						//显示可移动方块
						this.moviewShow = true
						//保存当前所选择的索引
						this.touchIndex = index;
						// 设置可移动方块的初始位置为当前所选中图片的位置坐标
						this.moveX = this.listData_c[index].x;
						this.moveY = this.listData_c[index].y;
						var x = event.changedTouches[0].clientX - this.areaBoxInfo.left;
						var y = event.changedTouches[0].clientY - this.areaBoxInfo.top;
						// 保存鼠标在图片内的坐标
						this.inBoxXY = {
							x: x - this.listData_c[index].x,
							y: y - this.listData_c[index].y,
						}
					},
					500);
			},
			AppLi_touchmove(event) {
				// 每次endTouch清除startTouch删除按钮定时器
				if (this.Loop) {
					clearTimeout(this.Loop);
					this.Loop = null;
				}
				if (this.showDelete) {
					let areaBoxTop = this.areaBoxInfo.top;
					let areaBoxLeft = this.areaBoxInfo.left;
					//重置为以拖拽盒子左上角为坐标原点
					var x = event.changedTouches[0].clientX - areaBoxLeft;
					var y = event.changedTouches[0].clientY - areaBoxTop;
					this.moveX = x - this.inBoxXY.x;
					this.moveY = y - this.inBoxXY.y;

					let setIng = false;
					this.listData_c.forEach((item, idx) => {
						if (x > item.x && x < item.x + 80 && y > item.y && y < item.y + 80) {
							this.hoverClass = 'appLi' + idx
							this.hoverClassIndex = idx;
							setIng = true
						}
					});
					// 都不存在代表脱离
					if (!setIng) {
						this.hoverClass = ""
						this.hoverClassIndex = null;
					}
				}
			},
			AppLi_touchend(index) {
				if (!this.showDelete && !this.IsDeleteAfter && !this.IsCancelDelete) {
					this.getInto(this.touchItem)
				} else {
					// 为下次getInto清除状态
					this.IsDeleteAfter = false;
					this.IsCancelDelete = false;
					// 移动结束隐藏可移动方块
					if (this.hoverClassIndex != null && this.touchIndex != this.hoverClassIndex) {
						this.$set(this.listData_c, this.touchIndex, this.listData_c[this.hoverClassIndex]);
						this.$set(this.listData_c, this.hoverClassIndex, this.touchItem);
						this.showDelete = false;
						this.resetListDom()
					}
					this.touchItem = ""
					this.moviewShow = false
					this.hoverClass = ""
					this.hoverClassIndex = null;
				}

				// 每次endTouch清除startTouch删除按钮定时器
				if (this.Loop) {
					clearTimeout(this.Loop);
					this.Loop = null;
				}
			},
			deleteAppItem(index) {
				this.listData_c.splice(index, 1)
				this.showDelete = false;
				this.checkIndex = null;
				this.IsDeleteAfter = true;
				this.resetListDom()
			},
			getInto(e) {
				if (e.appName == '更多') {
					return;
				}
				if (this.isEdti) return;
				uni.navigateTo({
					url: e.appLink,
				})
			},
			resetListDom() {
				let _this = this;
				this.getDomInfo('areaBox', info => {
					_this.areaBoxInfo = info;
					// 设置区域内所有图片的左上角坐标
					_this.listData_c.forEach((item, idx) => {
						_this.getDomInfo('appLi' + idx, res => {
							item.x = res.left - info.left;
							item.y = res.top - info.top;
						});
					});
				});
			},
			boxClick() {
				this.deleteAppID = null;
				this.showDelete = false;
			}
		}
	}
</script>

<style lang="scss" scoped>
	.rightIcon {
		position: absolute;
		right: 5rpx;
		top: -10rpx;
	}

	.movarea {
		width: 100%;
		height: auto;
	}

	.appList {
		width: 100%;
		display: flex;
		flex-wrap: wrap;
	}

	.app-li {
		width: 20%;
		// height: 160rpx;
		text-align: center;
		display: flex;
		flex-direction: column;
		justify-content: space-around;
		position: relative;
		margin-bottom: 30rpx;

		.appIcon {
			font-size: 60rpx;
			width: 50%;
			margin: 0 auto;
		}

		.appName {
			font-size: 24rpx;
		}

		.cuIcon-roundadd {
			font-size: 60rpx;
			color: #CCCCCC;
		}

		.cuIcon-roundclosefill {
			position: absolute;
			top: 12rpx;
			right: 12rpx;
			font-size: 36rpx;
			z-index: 2;

			&.hide {
				display: none;
			}
		}
	}

	.moveV {
		opacity: 0.8;
		z-index: 999;
		width: 100rpx;
		height: 160rpx;
		box-sizing: border-box;
		text-align: center;
		display: flex;
		flex-direction: column;
		justify-content: space-around;
		padding: 20rpx;

		.appIcon {
			font-size: 60rpx;
			width: 100%;
		}

		.appName {
			font-size: 24rpx;
		}
	}

	.select {
		// transform: scale(1.3);
		border-radius: 16rpx;
		border: 1px dashed #C0C0C0;
		color: #C0C0C0;
	}
</style>
2, 在所需页面引用AppList.vue组件
<template>
	<view class="content">
		<!-- appList start-->
		<view class="topText">
			点击下方【编辑】按钮,可调整首页功能展示
			长按图标可调整首页图标展示顺序
		</view>
		<view class="title">
			首页已展示功能
		</view>
		<view class="" style="padding: 0 30rpx;">
			<AppList :listData="appListData" @listChange="listChange" @lowAppList='lowAppListData' :isEdti='isEdti'>
			</AppList>
		</view>
		<view class="title">
			其他功能
		</view>
		<view style="padding: 0 30rpx;">
			<view class="appList">
				<view class="app-li text-blue" v-for="(appItem,index) in autherData" :key="appItem.name">
					<uni-icons type="plus-filled" size="20" class='rightIcon' @tap="addIcon(index)" v-if="isEdti">
					</uni-icons>
					<image class="appIcon" :src="appItem.appIcon" mode="widthFix" @tap="goAuther(appItem)"></image>
					<text class="appName">{{appItem.appName}}</text>
					<text class="appicon cuIcon-roundclosefill"
						:class="deleteAppID===appItem.appId && showDelete?'':'hide'" @tap="deleteAppItem(index)"></text>
				</view>
				<view class="app-li text-blue" @tap="addAppItem">
					<text class="appicon cuIcon-roundadd"></text>
				</view>
			</view>
		</view>
		<!-- appList end-->
		<view class="btmBox" v-if="isEdti" @click="setMenuStor">
			完成
		</view>
		<view class="btmBox" v-else @click="isEdti=!isEdti">
			编辑
		</view>
	</view>
</template>
<script>
	import AppList from "@/components/healer-dragList/AppList.vue"
	export default {
		data() {
			return {
				isEdti: false,
				//这里写你自己页面路由信息
				appListData: [{
						appId: 0,
						appName: '示例菜单跳转页面',
						appIcon: '/static/img/category/invitation.png',
						appLink: "/pagesA/inviteAgents/inviteAgents"
					}],
			}
		},
		components: {
			AppList,
		},
		onLoad() {

		},
		onShow() {
			if (!uni.getStorageSync('MENU_DATA')) {
				// this.getUseInfoData()
				console.log('')
			} else {
				let data = uni.getStorageSync('MENU_DATA')
				this.appListData = JSON.parse(data)
			}
			if (!uni.getStorageSync('MENU_BTM_DATA')) {
				// this.getUseInfoData()
				console.log('')
			} else {
				let data = uni.getStorageSync('MENU_BTM_DATA')
				this.autherData = JSON.parse(data)
			}
		},
		methods: {
			goAuther(e) {
				if (e.appName == '更多') {
					return;
				}
				try {
					uni.navigateTo({
						url: e.appLink
					})
				} catch (err) {
					uni.showToast({
						title: '当前模块正在开发...',
						icon: 'none'
					})
				}
			},
			setMenuStor() {
				this.isEdti = false
				uni.setStorageSync('MENU_DATA', JSON.stringify(this.appListData))
				uni.setStorageSync('MENU_BTM_DATA', JSON.stringify(this.autherData))
			},
			//菜单上到下
			lowAppListData(e) {
				if (this.appListData[e].appName == '更多') {
					uni.showToast({
						title: '更多不能被移出首页',
						icon: 'none'
					})
					return
				}
				this.autherData.push(this.appListData[e])
				this.appListData.splice(e, 1)
			},
			addIcon(index) {
				if (this.appListData.length == 10) {
					uni.showToast({
						title: '首页菜单不能大于10个',
						icon: 'none',
						duration: 2000
					})
					return;
				}
				this.appListData.push(this.autherData[index])
				this.autherData.splice(index, 1)
			},
			listChange(option) {
				console.log("listChange", option)
			}
		}
	}
</script>

<style scoped lang="scss">
	.rightIcon {
		position: absolute;
		right: 5rpx;
		top: -10rpx;
	}

	.btmBox {
		position: absolute;
		bottom: 0;
		width: 100%;
		height: 80rpx;
		line-height: 80rpx;
		background: #427ce7;
		text-align: center;
		color: white;
	}

	.appList {
		width: 100%;
		display: flex;
		flex-wrap: wrap;
	}

	.app-li {
		width: 20%;
		// height: 160rpx;
		text-align: center;
		display: flex;
		flex-direction: column;
		justify-content: space-around;
		position: relative;
		margin-bottom: 30rpx;

		.appIcon {
			font-size: 60rpx;
			width: 50%;
			margin: 0 auto;
		}

		.appName {
			font-size: 24rpx;
		}

		.cuIcon-roundadd {
			font-size: 60rpx;
			color: #CCCCCC;
		}

		.cuIcon-roundclosefill {
			position: absolute;
			top: 12rpx;
			right: 12rpx;
			font-size: 36rpx;
			z-index: 2;

			&.hide {
				display: none;
			}
		}
	}

	.topText {
		color: #427ce7;
		font-size: 30rpx;
		text-align: center;
		padding: 50rpx;
	}

	.content {
		background-color: #ffffff;
	}

	.title {
		padding: 30rpx;
		color: #808fb4;
	}
</style>

总结

以上代码实现了uniapp在小程序端实现菜单拖拽排序,以及显示隐藏指定菜单功能,有点小bug,需要原始代码的可以给我私信留言。当然有更简单的办法 uni-app切片工具也可以实现拖拽排序、菜单排序、导航排序等更多功能!文章来源地址https://www.toymoban.com/news/detail-419245.html

到了这里,关于小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • uniapp小程序Editor组件封装及使用

    封装editor组件,放三个文件夹 editor.vue文件是主要封装代码 editor-icon.css文件样式 到此封装就完毕了,以下是单个页面引入!!!! //导入地址 最后效果如下,编辑器一共四排,这边隐藏了一排,可以上下滑动  

    2024年02月09日
    浏览(38)
  • uniapp微信小程序自定义封装分段器。

    话不多说先上效果 这里我用的是cil框架 vue3 下面贴代码 组价代码: 父组件使用方法:

    2024年02月04日
    浏览(40)
  • uniapp实现微信小程序隐私协议组件封装

    uniapp实现微信小程序隐私协议组件封装。 隐私协议封装组件后快速调用。 diy-privacy/diy-privacy

    2024年02月07日
    浏览(41)
  • 小程序自定义拖动排序(uniapp)

    1.用到的组件和事件 1)组件movable-area,movable-view 2) @touchend(拖动离开事件),movable-view的onChange事件,自定义克隆事件dragChange,自定义顶置事件overheadChange 注意:给每个盒子设定固定高度 过程: 点击拖动:设置可移动区域,循环数组,movable-view写要移动的盒子(用点击事件

    2024年02月11日
    浏览(31)
  • 【微信小程序-原生开发】列表 - 拖拽排序(官方组件 movable-area 和 movable-view 的用法)

    实现逻辑详见代码的注释 需要根据各项的内容,调整或动态生成 ITEM_HEIGHT 值 因 movable-view 是绝对定位,不方便实现水平居中,所以设定 width: 100%; 占满宽度

    2024年02月11日
    浏览(82)
  • 【uniapp】中 微信小程序实现echarts图表组件的封装

     插件地址:echarts-for-uniapp - DCloud 插件市场 图例: 一、uniapp 安装   二、文件夹操作 将 node_modules 下的 uniapp-echarts 文件夹复制到 components 文件夹下  当前不操作此步骤的话,运行 - 运行到小程序模拟器 - 微信开发者工具 时,echarts图表显示不出来 原因:运行到小程序打包过

    2024年02月12日
    浏览(47)
  • uniapp小程序手写封装popup弹窗message提醒框组件

    在写uniapp小程序的时候,弹窗提醒经常会用到,虽然弹窗的组件很多,但是通常别人封装好的弹窗组件自定义度不高,很难匹配自己的ui需求。所以本篇文章教大家封装自己的弹窗提醒组件 取消和确认按钮的点击事件使用$emit传事件。中间的文字部分接收父组件的传值。 css大

    2024年02月11日
    浏览(52)
  • uniapp小程序,H5,Editor组件封装、使用及回显(vue3)

    官网链接:editor | uni-app官网 (dcloud.net.cn)

    2024年02月01日
    浏览(42)
  • 【uniapp】小程序自定义一个通用的返回按钮组件

    左边箭头,右边文字可以自定义,但是不要太长,太长可以自己改 .back的width值,改宽一点。 用这个组件的时候首先要在pages.json里把导航栏变成自定义的: 组件:backPages.vue 使用的时候需要传按钮名:backtext = ‘返回或者自定义’ 需要import引入组件并注册components才行,示例

    2024年02月12日
    浏览(46)
  • uniapp微信小程序自定义弹窗组件

    写一个能够复用的弹窗组件,内容包括: \\\"标题\\\",\\\"图片\\\",\\\"描述内容\\\",\\\"按钮\\\". 且按钮可能会有多中功能(比如: 点击按钮可能只是关闭弹窗,或者关闭弹窗并跳转) 1.创建弹窗组件 popup-view 1.小编这里考虑组件全局都有可能会用到,就在主包的 components 文件夹下创建popup-view组件 2.组件内

    2024年02月04日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包