Bootstrap

【uniapp 、小程序】渐进式树形结构组件(支持回显)

效果图(右箭头用的uviewplus的u-icon ,可自行修改)
此组件只包含画框部分
在这里插入图片描述

传入的数组格式如下(一个label、一个value、一个children组成的树形数组),回显只需将此数组以及目标id传入即可,
微信小程序的话隐藏再显示一次组件就通过refs调用一次组件的getScollWidthForMpWeixin方法即可
在这里插入图片描述

源代码如下:

<template>
	<view class="next-scroll-box">
		<scroll-view class="next-scroll-title" scroll-x="true" :scroll-left="scrollViewWidth" scroll-with-animation>
			<view class="next-scroll-title-item-box" v-for="(i, e) in tabList" @click="checkTab(e)" :key="e">
				<view
					v-if="tabId >= e"
					:id="'next-' + e"
					:class="['next-scroll-title-item', tabId == e ? ' next-scroll-title-item-true' : '']">
					{{ checkList[e] ? checkList[e][labelKey] : i.title }}
					<view style="margin-left: 20rpx" v-if="tabId > e">
						<u-icon size="14" bold color="#6D809D" name="arrow-right"></u-icon>
					</view>
				</view>
			</view>
		</scroll-view>
		<scroll-view
			class="next-scroll-view_H"
			scroll-y="true"
			:scroll-into-view="scrollIntoView"
			scroll-with-animation>
			<view
				class="next-scroll-view-grid-box"
				v-if="checkBox && checkBox.length && checkBox[tabId] && checkBox[tabId].length">
				<view
					:id="`next-scroll-view-item-` + index"
					v-for="(item, index) in checkBox[tabId]"
					:key="index"
					@click="check(index)"
					:class="
						checkList && checkList[tabId] && checkList[tabId][labelKey] == item[labelKey]
							? 'next-scroll-view-item-true'
							: 'next-scroll-view-item'
					">
					{{ item[labelKey] || '' }}
				</view>
			</view>
			<view class="next-scroll-view-noBox" v-else>
				<view class="text">暂无数据</view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	export default {
		props: {
			value: {
				type: String,
				default: '',
			},
			options: {
				type: Array,
				default: () => {
					return []
				},
			},
			valueKey: {
				type: String,
				default: 'value',
			},
			labelKey: {
				type: String,
				default: 'label',
			},
			childrenKey: {
				type: String,
				default: 'children',
			},
			defaultValue: {
				// 默认值
				type: [Number, String],
				default: '',
			},
		},
		data() {
			return {
				checkBox: [],
				tabId: 0,
				checkList: [],
				checkListModel: [],
				id: 0,
				tabList: [
					{
						title: '请选择',
						id: 0,
					},
				],
				scrollViewWidth: 0,
				elWidth: 0,
				yIndex: 0,
				scrollIntoView: '',
			}
		},
		created() {},
		options: {
			styleIsolation: 'shared', // 解除样式隔离
		},
		mounted() {
			this.init()
			console.log('----------插件挂载完毕------------')
		},
		watch: {
			defaultValue: {
				immediate: true,
				handler(newVal) {
					if ((newVal || newVal == '0') && this.options.length) {
						this.handleDefaultSelected(newVal, this.options)
					}
				},
			},
		},
		computed: {
			_value() {
				return (this.checkListModel || []).map((item) => item[this.valueKey]).join(',')
			},
		},
		methods: {
			init() {
				if (!this.defaultValue && this.defaultValue != 0) {
					this.id = 0
					this.tabId = 0
					this.checkBox = []
					this.checkList = []
				}
				//初始化求出滚动的宽度
				let view = uni.createSelectorQuery().in(this).select('.next-scroll-title')
				view.boundingClientRect((rect) => {
					this.scrollViewWidth = Math.round(rect.width)
				}).exec()
				this.getData()
			},
			// 微信特调方法,初始设置scrollViewWidth
			getScollWidthForMpWeixin() {
				let view = uni.createSelectorQuery().in(this).select('.next-scroll-title')
				view.boundingClientRect((rect) => {
					this.scrollViewWidth = Math.round(rect.width)
				}).exec()

				let view2 = uni
					.createSelectorQuery()
					.in(this)
					.select('#next-' + this.tabId)
				view2
					.boundingClientRect((rect) => {
						this.elWidth = Math.round(rect.width)
					})
					.exec()

				if (this.scrollIntoView) {
					this.scrollIntoView = 'next-scroll-view-item-0'
				}

				setTimeout(() => {
					this.scrollViewWidth = this.scrollViewWidth + this.elWidth
				})

				setTimeout(() => {
					this.scrollIntoView = `next-scroll-view-item-` + this.yIndex
				}, 100)
			},
			async check(index) {
				this.$set(this.checkList, this.id, this.checkBox[this.id][index])
				const children = this.checkBox[this.id][index][this.childrenKey]
				let n = 0
				if (children && children.length) {
					n = this.id + 1
				} else {
					n = this.id
				}

				let arr = []
				for (let i = 0; i <= n; i++) {
					arr.push({
						title: '请选择',
						id: i,
					})
				}
				this.tabList = arr
				if (this.id < this.tabList.length - 1) this.id = this.id + 1
				await this.getData()
				let view = uni
					.createSelectorQuery()
					.in(this)
					.select('#next-' + this.tabId)
				view.boundingClientRect((rect) => {
					this.elWidth = Math.round(rect.width)
				}).exec()
				setTimeout(() => {
					this.scrollViewWidth = this.scrollViewWidth + this.elWidth
				})
				if (this.tabId < this.tabList.length - 1) this.tabId = this.tabId + 1
			},
			checkTab(e) {
				if (e == this.id) return
				this.id = e
				this.tabId = e
				this.checkList = this.checkList.splice(0, e)
				this.scrollIntoView = 'next-scroll-view-item-0'
				this.$emit('confirm', {
					flag: false,
				})
			},
			getResult(event) {
				if (event == 'confirm') {
					if (this.checkList.length != this.tabList.length) return
					let result = this.checkList
					this.checkListModel = result
					// #ifdef VUE2
					this.$emit('input', this._value)
					// #endif
					// #ifdef VUE3
					this.$emit('update:value', this._value)
					// #endif
					// flag为true 即已经选到了最终节点
					this.$emit('confirm', {
						value: result,
						flag: true,
					})
				}
			},
			//使用本地假数据进行加载
			async getData() {
				if (this.checkList.length === this.tabList.length) {
					this.getResult('confirm')
					// console.log('tabid------>', this.tabId)
					// console.log('id----->', this.id)
					// console.log('checkbox----->', this.checkBox)
					// console.log('checkList----->', this.checkList)
					// console.log('tabList----->', this.tabList)
					return
				}
				// 此处的flag为false是告诉父级并没有选到最终的节点
				this.$emit('confirm', {
					flag: false,
				})
				let list = []
				if (this.checkList.length) {
					var id = this.checkList[this.id - 1][this.valueKey]
					const item = this.checkBox[this.id - 1].find((item) => {
						return item[this.valueKey] == id
					})
					;(item[this.childrenKey] ? item[this.childrenKey] : []).map((e) => {
						list.push(e)
					})
					this.$set(this.checkBox, this.id, list)
				} else {
					this.options.map((e) => {
						list.push(e)
					})
					this.$set(this.checkBox, this.id, list)
				}
			},

			// 回显函数   传入value(要保证唯一)  以及总的数组
			handleDefaultSelected(code, handlerArr) {
				if (!code || !handlerArr || !Array.isArray(handlerArr) || !handlerArr.length) return
				let that = this
				function findNodeById(code, arr, path = []) {
					for (let i = 0; i < arr.length; i++) {
						if (arr[i][that.valueKey] == code) {
							return [...path, arr[i]]
						}
						if (arr[i][that.childrenKey]) {
							let result = findNodeById(code, arr[i][that.childrenKey], [...path, arr[i]])
							if (result) {
								return result
							}
						}
					}
					return null
				}
				const checkList = findNodeById(code, handlerArr)
				if (checkList === null) return
				let checkBox = [handlerArr]
				const tabList = checkList.map((item, index) => {
					if (item[that.childrenKey] && item[that.childrenKey].length > 0 && checkList.length > index + 1)
						checkBox.push(item[that.childrenKey])
					return { title: '请选择', id: index }
				})
				const id = tabList.length - 1
				const tabId = tabList.length - 1

				const yIndex = checkBox[checkBox.length - 1]?.findIndex(
					(it) => it[that.valueKey] == checkList[checkList.length - 1][that.valueKey]
				)

				that.checkList = checkList
				that.checkBox = checkBox
				that.id = id
				that.tabId = tabId
				that.tabList = tabList

				that.yIndex = yIndex
				setTimeout(() => {
					that.scrollIntoView = `next-scroll-view-item-` + yIndex
				}, 100)

				// console.log('checkList--->', checkList)
				// console.log('checkBox--->', checkBox)
				// console.log('id--->', id)
				// console.log('tabId--->', tabId)
				// console.log('tabList--->', tabList)
			},
		},
	}
</script>

<style>
	/deep/ ::-webkit-scrollbar {
		width: 0;
		height: 0;
		color: transparent;
		display: none;
	}
</style>

<style lang="scss" scoped>
	.next-scroll-box {
		width: 100%;
		height: 100%;
		background: #ffff;
		border-radius: 24rpx 24rpx 0 0;
	}

	.next-scroll-title {
		white-space: nowrap;
		width: 100%;
		height: 88rpx;
		line-height: 88rpx;
		background-color: #fafafa;
		padding: 0 20rpx;
	}

	.next-scroll-view_H {
		white-space: nowrap;
		width: 100%;
		height: 400rpx;
		line-height: 100rpx;
		background-color: #ffffff;
	}

	.next-scroll-title-item {
		position: relative;
		display: flex;
		align-items: center;
	}

	.next-scroll-title-item-box {
		display: inline-block;
		margin: 0 10rpx;
		font-size: 28rpx;
		color: #333333;
	}

	.next-scroll-title-item-true {
		font-size: 28rpx;
		font-weight: 700;
		color: #45afff;
	}

	.next-scroll-view-grid-box {
		width: calc(100% - 20rpx);
		margin: 10rpx;
		padding-bottom: 10rpx;
	}

	.next-scroll-view-noBox {
		width: 100%;
		height: 100%;
		display: flex;
		justify-content: center;
		align-items: center;

		.text {
			width: 100%;
			color: #333333;
			text-align: center;
			font-size: 28rpx;
		}
	}

	.next-scroll-view-item {
		padding: 0rpx 24rpx;
		text-align: left;
		border-radius: 6rpx;
		background: #fff;
		color: #333333;
		font-size: 28rpx;
		margin: 12rpx 4rpx;
		height: 66rpx;
		line-height: 66rpx;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.next-scroll-view-item-true {
		padding: 0rpx 24rpx;
		text-align: left;
		border-radius: 6rpx;
		color: #45afff;
		font-size: 28rpx;
		margin: 12rpx 4rpx;
		height: 66rpx;
		line-height: 66rpx;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
</style>


悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;