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

news2024/11/15 18:05:56

效果展示

在这里插入图片描述

思路

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

具体步骤

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

在这里插入图片描述

使用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 通常搭配使用,来实现可拖拽排序的列表效果。

在这里插入图片描述
//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切片工具也可以实现拖拽排序、菜单排序、导航排序等更多功能!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/439786.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

偶数社区投稿丨OushuDB学习实践系列(一):开一家超市

大家好&#xff0c;我是镜镜呀&#xff0c;也是一名技术开发人员。本系列内容&#xff0c;也将由技术点出发&#xff0c;从数据库的使用、实践开始&#xff0c;逐步增加对整体的认知&#xff0c;由点及面&#xff0c;真正理解 OushuDB、数据湖仓一体在技术上的变革&#xff0c;…

@Configuration 和 @Component 注解的区别

一句话概括就是 Configuration 中所有带 Bean 注解的方法都会被动态代理&#xff0c;因此调用该方法返回的都是同一个实例。 理解&#xff1a;调用Configuration类中的Bean注解的方法&#xff0c;返回的是同一个示例&#xff1b; 而调用Component类中的Bean注解的方法&#x…

【社区图书馆】携程架构与实践图书

发这篇博文主要是想学习一下携程的架构。携程出了一本《携程架构实践》&#xff0c;无奈现在还没开源。看京东价大概109元人民币。如果看到次博文的网友能发我一本《携程架构实践》pdf。不胜感谢。或者实体书籍也行。其实我不怎么需要实体书籍&#xff0c;因为技术的东西很快会…

使用 IDEA 远程 Debug 调试

背景 有时候我们需要进行远程的debug&#xff0c;本文研究如何进行远程debug&#xff0c;以及使用 IDEA 远程debug的过程中的细节。看完可以解决你的一些疑惑。 配置 远程debug的服务&#xff0c;以springboot微服务为例。首先&#xff0c;启动springboot需要加上特定的参数。…

小白下载以后打不开怎么解决

我们重装系统时&#xff0c;大家会遇到各种各样的问题&#xff0c;现在有一些想用小白一键重装系统工具来帮助自己完成系统重装时&#xff0c;却发现我们打不开这个工具&#xff0c;现在不知道其原因有哪些&#xff0c;那么大家今天就来告诉小伙伴们小白一键重装官网下载以后打…

计算机概述

计算机&#xff1a; 硬件&#xff1a; CPU&#xff08;Central Processing Unit&#xff0c;中央处理器&#xff09;靠大脑思考&#xff0c;电脑靠CPU来运算、控制。硬盘&#xff08;Hard Disk Drive&#xff09; 计算机最主要的存储设备&#xff0c;容量大&#xff0c;断电数…

创新,阿里首发微服务实施手册我粉了,原来微服务还可以这样玩

微服务 相信大家在网上会看到很多帖子把分布式跟微服务放在一起讨论。确实&#xff0c;微服务就是一种分布式架构的设计方法。但是&#xff0c;在微服务概念还没有出现之前&#xff0c;分布式这个概念并不能引起人们的强烈关注&#xff0c;如果说自己擅长分布式架构设计&#…

TCP,TCP 连接建立,TCP 连接断开,Socket 编程

目录 TCP基本认识 TCP 头格式有哪些&#xff1f; 为什么需要 TCP 协议&#xff1f; TCP 工作在哪一层&#xff1f; 什么是 TCP &#xff1f; 什么是 TCP 连接&#xff1f; #如何唯一确定一个 TCP 连接呢&#xff1f; UDP 和 TCP 有什么区别呢&#xff1f;分别的应用场景是…

【教学类-34-02】拼图(英文大写字母)3*2格子(中班主题《个别化拼图》偏艺术-美术)

作品展示&#xff1a; 背景需求 3*4块运动拼图对幼儿有点难&#xff08;不熟悉图案内容、拼图数量多&#xff09; 1、选择幼儿相对熟悉的的字母&#xff08;26个&#xff0c;基本满足28人&#xff09; 2、拼图数量&#xff1a;2*36块 3、做的小一点&#xff0c;一张2份&am…

【软件测试面试】全网最全,自动化测试面试题总结大全(付答案)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试题1&#xff…

企企通项目入选《2023爱分析·供应链和采购数字化最佳实践案例》

近日&#xff0c;“2023爱分析供应链和采购数字化最佳实践案例”评选活动落下帷幕&#xff0c;主要围绕实践领先性、案例创新性、应用成熟度、价值创造四个维度对候选实践案例进行评选。企企通凭借自身丰富的采购供应链解决方案经验&#xff0c;以“迈金科技采购数字化管理平台…

美洽迈入“生成式AI时代”,开启智能客服新纪元!

生成式 AI&#xff0c;当前科技领域最热门的话题之一。 它指的是一类人工智能模型&#xff0c;可以根据输入的数据&#xff0c;自动生成新的数据或者输出一些有用的信息&#xff0c;例如&#xff1a;文本、图像、音频、视频等等。 在实际应用中&#xff0c;生成式AI可以帮助人…

“COMSOL电化学系列,锂离子电池仿真,燃料电池仿真

背景&#xff1a; 电化学仿真技术通过对电池微观行为进行研究&#xff0c;明晰电池内部多现象机理&#xff0c;并将其数值化&#xff0c;通过数值方法实现对物理特征联合计算&#xff0c;建立完整的电池模型。COMSOL Multiphysics具有强大的多物理场全耦合仿真分析功能、高效的…

Django框架介绍及搭建开发环境

介绍 是一个开放源代码的web应用框架&#xff0c;由python编写。 与常规的MVC架构不同&#xff0c;为MTV架构。 MTV Model(模型)&#xff1a;负责业务对象和数据库的关系映射&#xff08;ORM&#xff09;。 Template(视图)&#xff1a;负责把页面展示给用户&#xff08;htm…

Win32API之实现远程线程注入(九)

什么是注入 注入是一种在不知情或未经许可的情况下向其他进程中注入模块并试图执行它们的技术 常见的注入方式有&#xff1a;远程线程注入、APC注入、消息钩子注入、注册表注入、导入表注入、输入法注入等等 什么是远程线程注入 远程线程注入是一种技术&#xff0c;可以将一…

前端vue之根据内容生成二维码

1.实现这功能需要借助第三方插件,首先下载安装插件: 我在终端直接下载 npm install vue-qr --save2.在需要的.vue文件中引入并注册组件 import VueQr from vue-qr; export default {components: {VueQr,},3.在需要用的位置使用该二维码标签 <vue-qr :logo-src"logoS…

自动控制原理模拟卷6

自动控制原理模拟题六 Question1 已知控制系统的信号流图如下图所示: 求控制系统传递函数 C ( s ) / R ( s ) C(s)/R(s) C(s)

[mars3d] 学习

今天整体说下mars3d&#xff0c;集成问题之后在说&#xff0c;先说下概念性的东西&#xff1b; 一、设置地球的参数 下载他们的示例&#xff0c;如果不是特别的要求&#xff0c;可以直接使用他们的 confign 进行加载&#xff1b; 如果有什么特别的设置的&#xff0c;可以通过…

Chapter6-可靠性优先的使用场景

6.1 顺序消息 顺序消息是指消息的消费顺序和产生顺序相同&#xff0c;在有些业务逻辑下&#xff0c;必须保证顺序 。 比如订单的生成 、付款、发货&#xff0c;这 3 个消息必须按顺序处理才行。顺序消息分为全局顺序消息和部分顺序消息&#xff0c;全局顺序消息指某个 Topic 下…

centos7.6部署ELK集群(一)之elasticsearch7.7.0集群部署

32.3. 部署es7.7.0 32.3.1. 下载es&#xff08;各节点都做&#xff09; wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.7.0-linux-x86_64.tar.gz 32.3.2. 解压至安装目录&#xff08;各节点都做&#xff09; tar -xvf elasticsearch-7.7.0-li…