uni-app 实现无限级树形结构组件,面包屑类型导航等功能

文章目录

1. 实现效果

uni-app 实现无限级树形结构组件,面包屑类型导航等功能

2. 实现功能-流程

  1. 在故障上报页面点击 故障位置 进入 选择故障位置(调用接口渲染list),
  2. 支持连续下钻选择,选择完成点击 确认 返回故障上报页面
  3. 支持面包屑路径进行点击跳转,
  4. 支持默认值数据回显

3. 实现代码

3.1 【故障上报页面】故障位置选择页面代码

这个页面其他部分太多,只贴了一部分相关的代码
通过点击 clickLoaction() 事件进行跳转到故障位置选择页面

<view class="box-line boeder-bottom">
	<u-form-item label-width="150" label="故障位置" :border-bottom="false">
		<u-input type="select" v-model="formParams.locationName" input-align="right"
			:placeholder="'请选择'" disabled @click="clickLoaction()"/>
	</u-form-item>
</view>

data 数据,存储故障位置name,故障位置id,(formParams 用于提交故障上报的完整数据)

formParams: {
	locationName: '', // 故障位置Name
	locationId: '', // 故障位置ID
	.....// 其他参数
},

点击进行跳转至故障位置选择页面,传入 locationId 参数
如果传了 locationId 就是默认选中 该位置

// 选择 位置
clickLoaction(){
	let url = `/fault/conditionFilter/treeList?locationId=${this.formParams.locationId}`
	this.navigate(url)
},
// 设置 位置,通过故障位置选择页面选择完成后返回的值,并渲染到formParams对象中
setSelectLocationData(data){
	let locationObj = JSON.parse(data);
	if(!locationObj.length) return
	this.formParams.locationName = locationObj[0].locationName
	this.formParams.locationId = locationObj[0].locationId
},

3.2 【故障位置选择】

支持连续下钻选择,选择完成点击 确认 返回故障上报页面,完整代码
breadcrumbs 组件就是list 无线级树型结构,面包屑导航的组件 。getCurrentPages 实现两个页面传值

<template>
	<view class="container container-bg" :style="{height:(height)+'px'}">
		<!--  #ifdef MP-WEIXIN -->
		<u-navbar title="选择故障位置" title-size="36" back-icon-size="36" title-color="#333" back-icon-color="#333" height="48"
			:border-bottom="false"></u-navbar>
		<!--  #endif -->
		<view class="white-bg flex-column flex-center" style="padding:0 30rpx;" :style="{height:searchHeight+'px'}">
			<u-search placeholder="请输入" clearabled="true" shape="square" :show-action="false" v-model="keyword"
				@input="refreshData">
			</u-search>
		</view>
		<view class="white-bg color-9 font28 font-family fontW-500" :style="{height:elseHeight+'px'}">
			<breadcrumbs :treeNone="list" :isCheck='true' :checkList="checkList" :props="props" :keyValue="keyValue" @sendValue="sendValue">
			</breadcrumbs>
		</view>
	</view>
</template>

<script>
	import {
		config,
		post
	} from '@/utils/network.js';
	import publicController from '@/common/publicController'
	export default {
		data() {
			return {
				height: this.windowHeightT(),
				navH: this.navHeight,
				searchHeight: uni.upx2px(102),
				elseHeight: uni.upx2px(67),
				jianju: uni.upx2px(20),
				publicController: new publicController(),
				keyword: '',
				keyValue: "locationId",
				props: {
					label: "locationName",
					children: "children",
					multiple: false,
					nodes: false
				},//list 数据obj映射。
				locationId:"",//设置的默认值
				list: [],
				selectedId: '',
				checkList:[],
			}
		},
		// 获取传入的位置id,如果存在,设置checkList值,默认选中
		onLoad(option) {
			this.locationId = option.locationId
			if(this.locationId){
				this.checkList.push({"locationId":this.locationId})
			}
			this.init()
		},
		methods: {
			// 初始化
			async init() {
				// 调用接口
				this.queryLocationTreeByID();
			},
			// 查询所有维修方式
			async queryLocationTreeByID() {
				this.list = await this.publicController.queryLocationTreeByID({});
			},
			refreshData(){
				setTimeout(() => {
					this.init()
				}, 200)
			},
			// 点击确认按钮,子组件传入的data数据,值为选择的list的item值,
			// 通过getCurrentPages返回到上一个页面,通过 prevPage.$vm.setSelectLocationData 方法传入选择的值
			sendValue(data) {
				let pages = getCurrentPages();
				let prevPage = pages[pages.length - 2];
				prevPage.$vm.setSelectLocationData(JSON.stringify(data))
				uni.navigateBack({
					delta: 1
				})
			}
		}
	}
</script>

<style lang='scss' scoped>
	// ...... 省略
</style>

3.3 【breadcrumbs】组件完整代码

<template>
	<view>
		<view class="header">
			<view class="title">
				<scroll-view scroll-x style="width: 100%;white-space: nowrap;" :scroll-left="scrollLeft">
					<view class="flex-row flex-item-center">
						<block v-for="(item,index) in tree_stack" :key="index">
							<view class="font28 font-family fontW-500"
								:class="index==tree_stack.length-1?'color-9':'colorBlue'" v-if="index==0"
								@click="backTree(item,-1)">全部</view>

							<view class="flex-row flex-item-center" @click="backTree(item,index)" v-if="index!=0">
								<i class="iconfont font24 next-icon">&#xe607;</i>
								<view class="font28 font-family fontW-500"
									:class="index==tree_stack.length-1?'color-9':'colorBlue'">
									{{item[props.label]}}
								</view>
							</view>
						</block>
					</view>
				</scroll-view>
			</view>
		</view>
		<view>
			<view class="container-list">
				<view class="common" v-for="(item, index) in tree" :key="index" @click="handleClick(item,index)">
					<label class="content">
						<view class="list-item" v-show="isCheck" @click.stop="handleClick(item,-1)">
							<view v-if="radioSelect(item)" class="iconfont font34 colorBlue">&#xe653;</view>
							<view v-else class="iconfont font34 color-c">&#xe654;</view>
						</view>
						<view class="lable-text ellipsis">{{item[props.label]}}</view>
						<view class="right"><i v-if="!item.isChild&&item.children.length>0"
								class="iconfont">&#xe635;</i>
						</view>
					</label>
				</view>
			</view>
		</view>
		<view class="bottom-container width-max">
			<uni-button :bottomHeight="bottomHeight" location='bottom' :texts="buttonTexts"
				@uniButtonClick="backConfirm">
			</uni-button>
		</view>
	</view>
</template>
<script>
	export default {
		props: {
			// list数组值
			treeNone: {
				type: Array,
				default: () => {
					return []
				}
			},
			//是否开启选中
			isCheck: {
				type: Boolean,
				default: () => {
					return false
				}
			},
			checkList: {
				type: Array,
				default: () => []
			},
			parentList: {
				type: Array,
				default: () => []
			},
			// 匹配id
			keyValue: {
				type: String,
				default: 'id',
			},
			props: {
				type: Object,
				default: () => {
					return {
						label: 'name',
						children: 'children',
						multiple: false,
						nodes: false
					}
				}
			}
		},
		watch: {
		  treeNone(val) {
			  this.tree = val;
			  this.catchTreeNone = [...val];
		  },
		  checkList(val){
			  this.newCheckList = val
		  }
		},
		computed: {
			isSelect() {
				return (item) => {
					const checkList = this.newCheckList
					if (checkList.length == 0) {
						this.props.checkStrictly ? (item.bx = 0, item.qx = 0) : ''
						return false
					}
					const i = checkList.findIndex(e => {
						return item[this.keyCode] == e[this.keyCode]
					}) > -1
					return i && !item.qx
				}
			},
			radioSelect() {
				const list = this.newCheckList
				return (item) => {
					return list.length > 0 && item[this.keyCode] == list[0][this.keyCode]
				}
			},
			keyCode() {
				return this.keyValue
			}
		},
		data() {
			return {
				height: this.windowHeightT(),
				navHeight: this.navHeight,
				bottomHeight: uni.upx2px(146),
				buttonTexts: [{
					text: '确认',
					bgClass: 'uni-button-submit'
				}],
				isre: false,
				tree: Object.freeze(this.treeNone),
				newNum: 0,
				oldNum: 0,
				catchTreeNone: [...this.treeNone],
				tree_stack: [1],
				searchResult: [],
				newCheckList: this.checkList,
				scrollLeft: 999,
				nodePathArray: []
			}
		},
		created() {
			this.Init()
		},
		methods: {
			// 初始化
			Init() {
				if (this.newCheckList.length !== 0) {
					let {
						tree_stack,
						props,
						catchTreeNone,
						newCheckList
					} = this

					this.getNodeRoute(catchTreeNone, newCheckList[0][this.keyCode])
					let arr = this.nodePathArray.reverse()
					if (arr.length == 0) return
					this.tree_stack = tree_stack.concat(arr);
					this.tree = this.tree_stack[this.tree_stack.length - 1].children;

				}
			},
			// 点击项目处理
			handleClick(item, index) {
				let children = item[this.props.children]
				if (index > -1 && children && children.length > 0) {
					this.toChildren(item)
				} else {
					this.checkbox(item, index)
				}
			},
			// 获取路径
			getPath() {
				const {
					keyCode,
					tree_stack,
					props
				} = this
				const path = [...tree_stack].map(e => {
					const item = Object.assign({}, e)
					delete item[props.children]
					return item
				})
				return path.slice(1, path.length) || []
			},
			

			// 关联下一级,选中
			chooseChild(arr, path) {
				let that = this;
				const oldPath = [...path]
				for (var i = 0, len = arr.length; i < len; i++) {
					let item = arr[i];
					if (item.isChild) {
						that.newCheckList.push({
							...item,
							path: oldPath
						})
					} else {
						const newItem = {
							...item
						}
						delete newItem[that.props.children]
						const newPath = [...oldPath, newItem]
						that.chooseChild(item.children, newPath)
					}
				}
			},

			// (tree为目标树,targetId为目标节点id) 
			getNodeRoute(tree, targetId) {
				for (let index = 0; index < tree.length; index++) {
					if (tree[index].children) {
						let endRecursiveLoop = this.getNodeRoute(tree[index].children, targetId)
						if (endRecursiveLoop) {
							this.nodePathArray.push(tree[index])
							return true
						}
					}
					if (tree[index][this.keyCode] === targetId) {
						return true
					}
				}
			},

			//单选
			checkbox(item, index) {
				const path = this.getPath()
				this.$set(this, 'newCheckList', [{
					...item,
					path
				}])
			},
			//到下一级
			toChildren(item) {
				if (item.isChild) return
				var that = this;
				uni.showLoading({
					title: '加载中'
				})
				let children = that.props.children;
				if (!item.isChild && item[children].length > 0 && !(that.tree_stack[0][this.keyCode] == item[this
					.keyCode])) {
					that.tree = item[children];
					that.tree_stack.push(item);
				}
				this.$nextTick(() => {
					uni.hideLoading()
					this.scrollLeft += 200;
				})
				if (this.props.checkStrictly) this.checkAllChoose();
			},
			checkAllChoose() {
				let o = false,
					t = true;
				this.tree.forEach((e, i) => {
					if (!e.isChild) {
						e.qx = o;
						e.bx = o;
						this.computAllNumber(e.children);
						if (this.newNum != 0 && this.oldNum != 0) {
							if (this.newNum == this.oldNum) {
								e.qx = t;
								e.bx = o;
							} else {
								e.qx = o;
								e.bx = t;
							}
						}
						if (this.newNum != 0 && this.oldNum == 0) {
							this.$set(this.tree[i], 'bx', o);
							this.$set(this.tree[i], 'qx', o);
						}
						this.$forceUpdate()
						this.newNum = 0
						this.oldNum = 0
					}
				})
			},

			computAllNumber(arr) {
				for (let j = 0; j < arr.length; j++) {
					var e = arr[j];
					this.checkSum(e[this.keyCode])
					if (e.isChild) {
						this.newNum++;
					} else {
						this.computAllNumber(e.children)
					}
				}
			},

			checkSum(id) {
				for (let i = 0; i < this.newCheckList.length; i++) {
					if (id == this.newCheckList[i][this.keyCode]) {
						this.oldNum++;
						break
					}
				}
			},

			//返回其它层
			backTree(item, index) {
				let that = this,
					tree_stack = that.tree_stack,
					max = 10000;
				if (index === -1) {
					that.tree = that.catchTreeNone
					that.tree_stack.splice(1, max)
					that.isre = false
				} else {
					if (tree_stack.length - index > 2) {
						tree_stack.forEach((item, i) => {
							if (i > index) {
								that.tree_stack.splice(i, max)
							}
						})
					} else if (index !== tree_stack.length - 1) {
						that.tree_stack.splice(tree_stack.length - 1, 1)
					}
					that.tree = item[that.props.children]
				}
				if (this.props.checkStrictly) this.checkAllChoose();
				this.$forceUpdate()
			},
			// $emit 传入选中的数据
			backConfirm() {
				this.$emit('sendValue', this.newCheckList, 'back')
			}

		},
	}
</script>


</script>
<style lang="scss" scoped>
.flex_between_center {
	display: flex;
	justify-content: space-between;
	align-items: center;
}
.checkBorder {
	border: 1px solid #ecdee4;
}
.header {
	// width: 100%;
	// position: fixed;
	// background-color: #fff;
	// z-index: 9999;
	padding: 10rpx 30rpx;
	.title {
		// height: 90rpx;
		// padding: 0 32rpx;
		// line-height: 90rpx;
		// font-size: 30rpx;
		color: #999;
	}
}
.iconclass {
	display: inline-block;
	margin: 0 12rpx;
	color: #D0D4DB;
	font-size: 28rpx;
}
.container-list {
	overflow-y: scroll;
	overflow-x: hidden;
	margin-top: 20rpx;
	// padding-bottom: 160rpx;
	// padding-top: 200rpx;
	.common {
		background-color: #fff;
		border-bottom: 1rpx solid #f4f4f4;
		// padding-left: 10rpx;
		.content {
			display: flex;
			align-items: center;
			height: 96rpx;
               padding: 0 30rpx;
			.lable-text{
				margin-left: 45rpx;
				font-size: 30rpx;
				color: #333;
				width: 500rpx;
				
			}
			.right {
				position: absolute;
				right: 30rpx;
				color: #babdc3;
				font-size: 32rpx;
			}
		}
	}
}
.active {
	color: #4297ED !important;
}
.none {
	color: #666666;
}
.icon-selected{
	color: #0095F2!important;
	font-size: 40rpx!important;
}
.icons{
	color: #0095F2!important;
	font-size: 40rpx!important;
}
.next-icon{
	margin: 0 34rpx;
}

.content-item{
	display: flex;
	position: relative;
	align-items: center;
}

.box_sizing {
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	box-sizing: border-box;
}

.bottom-container {
	position: fixed;
	bottom: 0;
	left: 0;
}
</style>
上一篇:Uni-app初体验(页面绑定数据)


下一篇:uni-app