uniapp实现目录树效果,异步加载数据

uniapp目录树

  • 父组件调用目录树组件
html 复制代码
<DaTreeVue2
   :data="treeData"
   :field="{
      label: 'name',
      key: 'id',
   }"
   @change="handleTreeChange"
   :defaultCheckedKeys="addressDefaultCheckedKey"
   :defaultExpandedKeys="addressDefaultCheckedKey"
   :loadApi="getTreeData"
   ref="addressRef"
   loadMode
/>
  • 父组件方法
javascript 复制代码
handleTreeChange(allSelectedKeys, currentItem, parentKeys) {
  this.addressDefaultCheckedKey = parentKeys;
}

// 获取树
    async getTreeData(code) {
      let result = {};
      await this.$u.api.jobPost
        .getseekTreeData({
          parentCode: code,
        })
        .then((res) => {
          result = res.map((it) => {
            if (it.isParent) {
              it.children = [];
              return it;
            } else {
              return it;
            }
          });
        });

      return result;
    }
  • 子组件
javascript 复制代码
<template>
	<view class="da-tree" :style="{'--theme-color': themeColor}">
		<scroll-view class="da-tree-scroll" :scroll-y="true" :scroll-x="false">
			<view class="da-tree-item" :class="{'is-show': item.show}"
				:style="{paddingLeft: item.level * indent + 'rpx'}" v-for="item in datalist" :key="item.key">
				<view v-if="item.showArrow" class="da-tree-item__icon" @click="handleExpandedChange(item)">
					<view :class="['da-tree-item__icon--arr','is-loading']" v-if="loadLoading && item.loading" />
					<view :class="['da-tree-item__icon--arr','is-expand', {'is-right':!item.expand}]" v-else />
				</view>
				<view v-else class="da-tree-item__icon" />
				<view class="da-tree-item__checkbox"
					:class="[`da-tree-item__checkbox--${checkboxPlacement}`,{'is--disabled': item.disabled}]"
					v-if="showCheckbox" @click="handleCheckChange(item)">
					<view class="da-tree-item__checkbox--icon da-tree-checkbox-checked"
						v-if="item.checkedStatus === isCheckedStatus" />
					<view class="da-tree-item__checkbox--icon da-tree-checkbox-indeterminate"
						v-else-if="item.checkedStatus === halfCheckedStatus" />
					<view class="da-tree-item__checkbox--icon da-tree-checkbox-outline" v-else />
				</view>
				<view class="da-tree-item__checkbox"
					:class="[`da-tree-item__checkbox--${checkboxPlacement}`,{'is--disabled': item.disabled}]"
					v-if="!showCheckbox && showRadioIcon" @click="handleRadioChange(item)">
					<view class="da-tree-item__checkbox--icon da-tree-radio-checked"
						v-if="item.checkedStatus === isCheckedStatus" />
					<view class="da-tree-item__checkbox--icon da-tree-radio-indeterminate"
						v-else-if="item.checkedStatus === halfCheckedStatus" />
					<view class="da-tree-item__checkbox--icon da-tree-radio-outline" v-else />
				</view>
				<view class="da-tree-item__label" :class="'da-tree-item__label--'+item.checkedStatus"
					@click="handleLabelClick(item)">{{ item.label }} <text class="da-tree-item__label--append"
						v-if="item.append">{{ item.append }}</text></view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	import {
		unCheckedStatus,
		halfCheckedStatus,
		isCheckedStatus,
		deepClone,
		getAllNodeKeys,
		getAllNodes
	} from './utils'

	export default {
		name: 'DaTree',
		props: {
			/**
			 * 树的数据
			 */
			data: {
				type: Array,
				default: () => [],
			},
			/**
			 * 主题色
			 */
			themeColor: {
				type: String,
				default: '#007aff',
			},
			/**
			 * 默认选中的节点,注意单选时为单个key,多选时为key的数组
			 */
			defaultCheckedKeys: {
				type: [Array, String, Number],
				default: null,
			},
			/**
			 * 选择框的位置,可选 left/right
			 */
			checkboxPlacement: {
				type: String,
				default: 'left',
			},
			/**
			 * 是否默认展开全部
			 */
			defaultExpandAll: {
				type: Boolean,
				default: false,
			},
			/**
			 * 默认展开的节点
			 */
			defaultExpandedKeys: {
				type: Array,
				default: null,
			},
			/**
			 * 子项缩进距离,默认40,单位rpx
			 */
			indent: {
				type: Number,
				default: 40,
			},
			/**
			 * 字段对应内容,默认为 {label: 'label',key: 'key', children: 'children', disabled: 'disabled', append: 'append'}
			 */
			field: {
				type: Object,
				default: null,
			},
			/**
			 * 是否开启多选,默认单选
			 */
			showCheckbox: {
				type: Boolean,
				default: false,
			},
			/**
			 * 是否显示单选图标,默认显示
			 */
			showRadioIcon: {
				type: Boolean,
				default: true,
			},
			/**
			 * 单选时只允许选中末级,默认可随意选中
			 */
			onlyRadioLeaf: {
				type: Boolean,
				default: false,
			},
			/**
			 * 多选时,是否执行父子不关联的任意勾选,默认父子关联
			 */
			checkStrictly: {
				type: Boolean,
				default: false,
			},
			/**
			 * 为 true 时,空的 children 数组会显示展开图标
			 */
			loadMode: {
				type: Boolean,
				default: false,
			},
			/**
			 * 异步加载接口
			 */
			loadApi: {
				type: Function,
				default: null,
			},
			/**
			 * 是否渲染禁用值
			 */
			checkedDisabled: {
				type: Boolean,
				default: false,
			},
			/**
			 * 是否返回已禁用的但已选中的key
			 */
			packDisabledkey: {
				type: Boolean,
				default: true,
			},
			/**
			 * 选择时是否展开当前已选的所有下级节点,默认不展开
			 */
			expandChecked: {
				type: Boolean,
				default: false,
			},
		},
		data() {
			return {
				unCheckedStatus,
				halfCheckedStatus,
				isCheckedStatus,
				/** 原始的树数据 */
				dataRef: [],
				/** 处理后的一维树项数据 */
				datalist: [],
				/** 处理后的以key为键值的树项数据 */
				datamap: {},
				/** 默认的展开数据 */
				expandedKeys: [],
				/** 默认的已选数据 */
				checkedKeys: null,
				/** 加载状态 */
				loadLoading: false,
			}
		},
		watch: {
			defaultExpandedKeys: {
				// deep: true,
				immediate: true,
				handler: function(v) {
					if (v?.length) {
						this.expandedKeys = v
					} else {
						this.expandedKeys = []
					}

					// if (v) this.checkInitData(this.datalist)
				},
			},
			defaultCheckedKeys: {
				// deep: true,
				immediate: true,
				handler: function(v) {
					if (this.showCheckbox) {
						if (v?.length) {
							this.checkedKeys = v
						} else {
							this.checkedKeys = []
						}
					} else {
						if (v || v === 0) {
							this.checkedKeys = v
						} else {
							this.checkedKeys = null
						}
					}
					// this.checkInitData(this.datalist)
				},
			},
			data: {
				deep: true,
				immediate: true,
				handler: function(v) {
					this.dataRef = deepClone(v)
					setTimeout(() => {
						this.initData()
					}, 36)
				},
			},
		},
		methods: {
			/**
			 * 初始化数据结构
			 */
			initData() {
				const data = deepClone(this.dataRef)
				this.datalist = []
				this.datamap = {}

				// clean tree
				this.handleTreeData(data)
				// flat tree
				this.datalist = this.checkInitData(this.datalist)

				// console.log('init datalist', this.datalist)
				// console.log('init datamap', this.datamap)
			},

			/**
			 * 转换为节点数据
			 * @param data
			 * @param parent
			 * @param level
			 */
			handleTreeData(data = [], parent = null, level = 0, insertIndex = -1, secondIndex) {

				return data.reduce((prev, cur, index) => {

					const key = cur[this.field?.key || 'key']
					const children = cur[this.field?.children || 'children'] || null
					const newItem = this.createNewItem(cur, index, parent, level)
					this.datamap[key] = newItem

					if (insertIndex > -1) {

						this.datalist.splice(insertIndex + 1, 0, newItem)
						parent.children.push(newItem)
						if (newItem.parentKeys?.length) {
							newItem.parentKeys.forEach(k => {
								this.datamap[k].childrenKeys = [...this.datamap[k].childrenKeys,
									newItem
									.key
								]
							})
						}
					} else {
						this.datalist.push(newItem)
					}

					const hasChildren = children && children.length > 0
					if (hasChildren) {
						const childrenData = this.handleTreeData(children, newItem, level + 1)
						newItem.children = childrenData
						const childrenKeys = childrenData.reduce((p, k) => {
							const keys = k.childrenKeys
							p.push(...keys, k.key)
							return p
						}, [])
						newItem.childrenKeys = childrenKeys
					}
					newItem.positionCode = cur.positionCode
					// newItem.parentCodes = cur.parentCodes.split(',')
					prev.push(newItem)
					return prev
				}, [])
			},

			/**
			 * 创建节点
			 * @param item
			 * @param index
			 * @param parent
			 * @param level
			 */
			createNewItem(item, index, parent, level) {
				const key = item[this.field?.key || 'key']
				const label = item[this.field?.label || 'label']
				const children = item[this.field?.children || 'children'] || null
				const append = item[this.field?.append || 'append'] || null
				let disabled = item[this.field?.disabled || 'disabled'] || false
				const hasChildren = children && children.length > 0
				const hasEmptyChildren = children && children.length === 0
				let showArrow = true
				let isLeaf = !hasChildren
				let expand = this.defaultExpandAll

				if (this.loadMode && hasEmptyChildren) {
					isLeaf = false
					expand = false
					showArrow = true
				}

				if (disabled) {
					showArrow = false
				}

				if (hasChildren) {
					showArrow = true
				} else {
					if (this.loadMode && hasEmptyChildren && !disabled) {
						showArrow = true
					} else {
						showArrow = false
					}
				}

				if (!isLeaf && !this.showCheckbox && this.onlyRadioLeaf) {
					disabled = true
					showArrow = true
				}

				const parentKey = parent ? parent.key : null
				const show = this.defaultExpandAll ? true : level === 0

				const newItem = {
					key,
					parentKey,
					label,
					append,
					isLeaf,
					showArrow,
					level,
					expand,
					show,
					disabled,
					loading: false,
					indexs: [index],
					checkedStatus: unCheckedStatus,
					parentKeys: [],
					childrenKeys: [],
					children: [],
					originItem: item,
				}

				if (parent) {
					newItem.parentKeys = [parent.key, ...parent.parentKeys]
					newItem.indexs = [...parent.indexs, index]
				}

				return newItem
			},

			/**
			 * 处理初始化内容
			 * @param list
			 */
			checkInitData(list) {
				let checkedKeyList = null
				let expandedKeyList = []
				if (this.showCheckbox) {
					checkedKeyList = [...new Set(this.checkedKeys || [])]
				} else {
					checkedKeyList = this.checkedKeys || null
				}

				this.handleCheckState(list, checkedKeyList)

				// 处理初始展开
				expandedKeyList = [...new Set(this.expandedKeys || [])]
				if (!this.defaultExpandAll) {
					this.handleExpandState(list, expandedKeyList, true)
				}

				return list
			},

			/**
			 * 处理选中
			 * @param list
			 * @param checkedKeyList
			 */
			handleCheckState(list, checkedKeyList, checked = true) {
				if (this.showCheckbox) {
					for (let i = 0; i < list.length; i++) {
						const item = list[i]
						if (checkedKeyList?.includes(item.key)) {
							if (this.checkedDisabled || !item.disabled) {
								this.handleExpandParentNode(item, checked)
								this.checkTheChecked(item, checked)
							}
						}
					}
				} else {
					// 单选
					for (let i = 0; i < list.length; i++) {
						const item = list[i]
						if (item.key === checkedKeyList && !item.disabled) {
							if (this.checkedDisabled || !item.disabled) {
								this.checkTheRadio(item, checked)
								break
							}
						}
					}
				}
			},

			/**
			 * 校验多选节点
			 * @param item
			 * @param checked
			 */
			checkTheChecked(item, checked = true) {
				const {
					childrenKeys,
					parentKeys,
					disabled = false
				} = item
				if (!this.checkedDisabled && disabled) return
				// 当前
				item.checkedStatus = checked ? isCheckedStatus : unCheckedStatus

				if (!this.checkStrictly) {
					// 子类
					childrenKeys.forEach(k => {
						const childrenItem = this.datamap[k]
						childrenItem.checkedStatus = (!this.checkedDisabled && childrenItem.disabled) ?
							childrenItem.checkedStatus : item.checkedStatus
					})

					// 父类
					parentKeys.forEach(k => {
						const parentItem = this.datamap[k]
						parentItem.checkedStatus = this.getParentCheckedStatus(parentItem)
					})
				}
			},

			/**
			 * 校验单选节点
			 * @param item
			 */
			checkTheRadio(item, checked) {
				// console.log('item',item);
				// console.log('checked',checked);
				const {
					parentKeys,
					isLeaf,
					disabled = false
				} = item
				if (!this.checkedDisabled && disabled) return

				// 限制末节点选中,但当前非末节点
				if (this.onlyRadioLeaf && !isLeaf) {
					console.error(`DaTree: 限制了末节点选中,当前[${item.label}]非末节点`)
					return
				}

				if (this.datalist?.length) {
					for (let i = 0; i < this.datalist.length; i++) {
						const k = this.datalist[i]
						k.checkedStatus = unCheckedStatus
					}
				}

				parentKeys.forEach(k => {
					const parentItem = this.datamap[k]
					parentItem.checkedStatus = this.getParentCheckedStatus(parentItem, checked)
				})

				// 当前
				if (checked === true) {
					item.checkedStatus = isCheckedStatus
				}
				if (checked === false) {
					item.checkedStatus = unCheckedStatus
				}
				// item.checkedStatus = unCheckedStatus  
			},

			/**
			 * 处理父节点展开
			 * @param item
			 * @param expand
			 */
			handleExpandParentNode(item, expand = true) {
				if (!expand) return

				if (item?.parentKeys?.length) {
					item.parentKeys.forEach(pk => {
						if (!this.datamap[pk].expand) {
							this.datamap[pk].expand = true
						}
					})
				}
			},

			/**
			 * 处理节点展开
			 * @param list
			 * @param expandedKeyList
			 * @param expand
			 */
			handleExpandState(list, expandedKeyList, expand = true) {
				for (let i = 0; i < list.length; i++) {
					const item = list[i]
					if (expand === true) {
						// 处理展开
						if (expandedKeyList?.includes(item.key)) {
							item.expand = true
							this.handleExpandParentNode(item, true)
						}
					} else {
						// 处理收起
						if (expandedKeyList?.includes(item.key)) {
							item.expand = false
							if (item?.childrenKeys?.length) {
								item.childrenKeys.forEach(ck => {
									this.datamap[ck].expand = false
									this.datamap[ck].show = false
								})
							}
						}
					}
				}

				for (let i = 0; i < list.length; i++) {
					const item = list[i]
					if (item.level > 0) {
						const parentItem = this.datamap[item.parentKey]
						if (parentItem) {
							if (parentItem.expand && parentItem.show) {
								item.show = true
							}
						}
					}
				}
			},

			/**
			 * 点击选框
			 * @param item
			 */
			handleCheckChange(item) {
				const {
					childrenKeys,
					parentKeys,
					checkedStatus,
					isLeaf,
					originItem = null,
					disabled = false
				} = item
				if (!this.showCheckbox) return
				if (disabled) return

				// 当前
				item.checkedStatus = checkedStatus === isCheckedStatus ? unCheckedStatus : isCheckedStatus

				// 子类
				if (!this.checkStrictly) {
					if (this.expandChecked) {
						item.show = true
						item.expand = childrenKeys?.length > 0 || isLeaf
					}

					childrenKeys.forEach(k => {
						const childrenItem = this.datamap[k]
						childrenItem.checkedStatus = childrenItem.disabled ? childrenItem.checkedStatus : item
							.checkedStatus

						if (this.expandChecked) {
							childrenItem.show = true
							childrenItem.expand = childrenItem?.childrenKeys?.length > 0 || childrenItem.isLeaf
						}
					})
				} else {
					if (this.expandChecked) {
						console.error(`DaTree: 多选时,当 checkStrictly 为 true 时,不支持选择自动展开子节点属性(expandChecked)`)
					}
				}

				// 父类
				if (!this.checkStrictly) {
					parentKeys.forEach(k => {
						const parentItem = this.datamap[k]
						parentItem.checkedStatus = this.getParentCheckedStatus(parentItem)
					})
				}

				const hasCheckedKeys = []
				for (let i = 0; i < this.datalist.length; i++) {
					const k = this.datalist[i]
					if (k.checkedStatus === isCheckedStatus) {
						if ((this.packDisabledkey && k.disabled) || !k.disabled) {
							hasCheckedKeys.push(k.key)
						}
					}
				}

				this.checkedKeys = [...hasCheckedKeys]

				this.$emit('change', hasCheckedKeys, originItem)
			},

			/**
			 * 点击单选
			 * @param item
			 */
			handleRadioChange(item) {
				const {
					parentKeys,
					checkedStatus,
					key,
					originItem = null,
					disabled = false
				} = item
				if (this.showCheckbox) return
				if (this.onlyRadioLeaf) {
					this.handleExpandedChange(item)
				}
				if (disabled) return

				// 重置所有选择
				if (this.datalist?.length) {
					for (let i = 0; i < this.datalist.length; i++) {
						const k = this.datalist[i]
						k.checkedStatus = unCheckedStatus
					}
				}

				parentKeys.forEach(k => {
					const parentItem = this.datamap[k]
					parentItem.checkedStatus = this.getParentCheckedStatus(parentItem)
				})

				// 当前
				item.checkedStatus = checkedStatus === isCheckedStatus ? unCheckedStatus : isCheckedStatus

				this.checkedKeys = key
				this.$emit('change', key, originItem, parentKeys)
			},

			/**
			 * 点击标签
			 */
			handleLabelClick(item) {
				if (this.showCheckbox) {
					this.handleCheckChange(item)
				} else {
					this.handleRadioChange(item)
				}
			},

			/**
			 * 点击展开收起
			 * @param item
			 */
			async handleExpandedChange(item) {
				const {
					expand,
					originItem = null,
					loading = false
				} = item
				if (this.loadLoading && loading) return

				this.checkExpandedChange(item)

				// 异步
				item.expand = !expand
				let currentItem = null
				if (!this.showCheckbox && this.onlyRadioLeaf && this.loadMode) {
					console.error(`DaTree: 单选时,当 onlyRadioLeaf 为 true 时不支持动态数据`)
				} else {
					currentItem = await this.loadExpandNode(item)
				}

				this.$emit('expand', item, !expand, currentItem || originItem || null)
			},

			/**
			 * 检查展开状态
			 * @param item
			 */
			checkExpandedChange(item) {
				const {
					expand,
					childrenKeys,
					children = null
				} = item

				if (expand) {
					if (childrenKeys?.length) {
						childrenKeys.forEach(k => {
							if (this.datamap[k]) {
								this.datamap[k].show = false
								this.datamap[k].expand = false
							}
						})
					}
				} else {
					if (children?.length) {
						const childrenKeys = children.map(k => k.key)
						childrenKeys.forEach(k => {
							if (this.datamap[k]) {
								this.datamap[k].show = true
							}
						})
					}
				}
			},

			/**
			 * 加载异步数据
			 * @param item
			 */
			async loadExpandNode(item) {
				const {
					expand,
					key,
					children = null
				} = item
				if (expand && this.loadMode && (!children || children.length === 0)) {
					if (typeof this.loadApi === 'function') {
						this.expandedKeys.push(key)
						this.loadLoading = true
						item.loading = true
						const currentNode = deepClone(item)
						const apiRes = await this.loadApi(currentNode.positionCode)
						item.originItem = apiRes || null
						if (apiRes?.length) {
							const insertIndex = this.datalist.findIndex(k => k.key === item.key)
							this.handleTreeData(apiRes, item, item.level + 1, insertIndex)
							this.datalist = this.checkInitData(this.datalist)
						} else {
							// 加载后无数据就移除展开图标
							item.expand = false
							item.isLeaf = true
							item.showArrow = false
						}

						this.loadLoading = false
						item.loading = false
					}
				} else {
					const eki = this.expandedKeys.findIndex(k => k === key)
					if (eki >= 0) {
						this.expandedKeys.splice(eki, 1)
					}
				}
				return item
			},

			/**
			 * 获取父类的选中状态
			 * @param item
			 */
			getParentCheckedStatus(item, checked = true) {
				console.log(item);
				console.log(checked);
				if (!item) {
					return unCheckedStatus
				}

				if (!this.checkedDisabled && item.disabled) {
					return item.checkedStatus || unCheckedStatus
				}

				// 单选时,父类永远为半选
				// checked为false  unCheckedStatus取消选择
				if (!this.showCheckbox && checked == true) {
					// console.log('半半');
					return halfCheckedStatus
				}

				if (!this.showCheckbox && checked === false) {
					// console.log('add');
					return unCheckedStatus
				}

				const {
					children
				} = item
				// 子类全选中
				const childrenCheckedAll = children.every(k => k.checkedStatus === isCheckedStatus)
				if (childrenCheckedAll) {
					return isCheckedStatus
				}

				// 子类全不选中
				const childrenUncheckedAll = children.every(k => k.checkedStatus === unCheckedStatus)
				if (childrenUncheckedAll) {
					return unCheckedStatus
				}

				return halfCheckedStatus
			},
			/**
			 * 返回已选的 key
			 */
			getCheckedKeys() {
				return getAllNodeKeys(this.datalist, 'checkedStatus', isCheckedStatus, this.packDisabledkey)
			},
			/**
			 * 根据key设置已选
			 * @param keys 多选时为key的数组,单选时为key
			 * @param checked 多选时为key的数组,单选时为key
			 */
			setCheckedKeys(keys, checked = true) {
				// console.log('--==========================',keys);
				// console.log('--==========================',checked);
				if (!Array.isArray(keys) && this.showCheckbox) {
					console.error('DaTree: setCheckedKeys 第一个参数非数组,传入的是:', keys)
					return
				}
				const list = this.datalist
				if (checked === false) {
					// console.log('11111111111111111111111111111111111111111111111111111111111111111111111');
					let newCheckedKeys
					if (this.showCheckbox) {
						newCheckedKeys = []
						for (let i = 0; i < this.checkedKeys.length; i++) {
							const ck = this.checkedKeys[i]
							if (!keys.includes(ck)) {
								newCheckedKeys.push(ck)
							}
						}
						newCheckedKeys = [...new Set(newCheckedKeys)]
					} else {
						// 单选时,必须至少勾选一个,所以单选不支持取消选中。
						// newCheckedKeys = null
						newCheckedKeys = keys
					}
					this.checkedKeys = newCheckedKeys
					// console.log(this.checkedKeys);
					console.log(list);
					this.handleCheckState(list, keys, false)
				} else {
					this.handleCheckState(list, keys, true)

					if (this.showCheckbox) {
						this.checkedKeys = [...new Set([...(this.checkedKeys || []), ...(keys || [])])]
						this.handleExpandState(list, keys, true)
					} else {
						// 单选时如果为数组则拿第一个
						if (Array.isArray(keys)) {
							keys = keys[0]
						}
						this.checkedKeys = keys || null
						this.handleExpandState(list, [keys], true)
					}
				}
			},
			/**
			 * 返回半选的 key
			 */
			getHalfCheckedKeys() {
				return getAllNodeKeys(this.datalist, 'checkedStatus', halfCheckedStatus, this.packDisabledkey)
			},
			/**
			 * 返回已展开的 key
			 */
			getExpandedKeys() {
				return getAllNodeKeys(this.datalist, 'expand', true)
			},
			/**
			 * 根据key展开/收起
			 * @param keys key的数组
			 * @param expand true为展开/false为收起
			 */
			setExpandedKeys(keys, expand = true) {
				if (!Array.isArray(keys)) {
					console.error('DaTree: setExpandedKeys 第一个参数非数组,传入的是:', keys)
					return
				}
				const list = this.datalist
				if (expand === false) {
					const newExpandedKeys = []
					for (let i = 0; i < this.expandedKeys.length; i++) {
						const ek = this.expandedKeys[i]
						if (!keys.includes(ek)) {
							newExpandedKeys.push(ek)
						}
					}
					this.expandedKeys = [...new Set(newExpandedKeys)]
					this.handleExpandState(list, keys, false)
				} else {
					this.datalist.forEach(k => {
						if (keys.includes(k.key)) {
							if (k.parentKeys?.length) {
								k.parentKeys.forEach(pk => {
									const parentItem = this.datamap[pk]
									parentItem.expand = true
									parentItem.show = true
									if (parentItem.children?.length) {
										const pkcs = parentItem.children.map(k => k.key)
										pkcs.forEach(pkc => {
											this.datamap[pkc].show = true
										})
									}
								})
							}
							k.show = true
							this.handleExpandedChange(k)
						}
					})
				}
			},
			/**
			 * 返回已选的节点
			 */
			getCheckedNodes() {
				return getAllNodes(this.datalist, 'checkedStatus', isCheckedStatus, this.packDisabledkey)
			},
			/**
			 * 返回半选的节点
			 */
			getHalfCheckedNodes() {
				return getAllNodes(this.datalist, 'checkedStatus', halfCheckedStatus, this.packDisabledkey)
			},
			/**
			 * 返回已展开的节点
			 */
			getExpandedNodes() {
				return getAllNodes(this.datalist, 'expand', true)
			},
		},
	}
</script>

<style lang="scss" scoped>
	@font-face {
		font-family: 'iconfont';
		/* Project id  */
		src: url('data:application/octet-stream;base64,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') format('truetype');
	}

	.da-tree {
		width: 100%;
		height: 100%;

		&-scroll {
			width: 100%;
			height: 100%;
		}

		&-item {
			display: flex;
			align-items: center;
			height: 0;
			padding: 0;
			overflow: hidden;
			font-size: 28rpx;
			line-height: 1;
			visibility: hidden;
			opacity: 0;
			transition: opacity 0.2s linear;

			&.is-show {
				height: auto;
				padding: 12rpx 24rpx;
				visibility: visible;
				opacity: 1;
			}

			&__icon {
				display: flex;
				align-items: center;
				justify-content: center;
				width: 40rpx;
				height: 40rpx;
				overflow: hidden;

				&--arr {
					position: relative;
					display: flex;
					align-items: center;
					justify-content: center;
					width: 32rpx;
					height: 32rpx;

					&::after {
						position: relative;
						z-index: 1;
						overflow: hidden;
						/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
						font-family: 'iconfont' !important;
						font-size: 32rpx;
						font-style: normal;
						color: #999;
						-webkit-font-smoothing: antialiased;
						-moz-osx-font-smoothing: grayscale;
					}

					&.is-expand {
						&::after {
							content: '\e604';
						}
					}

					&.is-right {
						transform: rotate(-90deg);
					}

					&.is-loading {
						animation: IconLoading 1s linear 0s infinite;

						&::after {
							content: '\e7f1';
						}
					}
				}
			}

			&__checkbox {
				width: 40rpx;
				height: 40rpx;
				overflow: hidden;

				&--left {
					order: 0;
				}

				&--right {
					order: 1;
				}

				&--icon {
					position: relative;
					display: flex;
					align-items: center;
					justify-content: center;
					width: 40rpx;
					height: 40rpx;

					&::after {
						position: relative;
						top: 0;
						left: 0;
						z-index: 1;
						overflow: hidden;
						/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
						font-family: 'iconfont' !important;
						font-size: 32rpx;
						font-style: normal;
						-webkit-font-smoothing: antialiased;
						-moz-osx-font-smoothing: grayscale;
					}

					&.da-tree-checkbox-outline::after {
						color: #bbb;
						content: '\ead5';
					}

					&.da-tree-checkbox-checked::after {
						color: var(--theme-color, #007aff);
						content: '\ead4';
					}

					&.da-tree-checkbox-indeterminate::after {
						color: var(--theme-color, #007aff);
						content: '\ebce';
					}

					&.da-tree-radio-outline::after {
						color: #bbb;
						content: '\ecc5';
					}

					&.da-tree-radio-checked::after {
						color: var(--theme-color, #007aff);
						content: '\ecc4';
					}

					&.da-tree-radio-indeterminate::after {
						color: var(--theme-color, #007aff);
						content: '\ea4f';
					}
				}

				&.is--disabled {
					cursor: not-allowed;
					opacity: 0.35;
				}
			}

			&__label {
				flex: 1;
				margin-left: 4rpx;
				color: #555;

				&--2 {
					color: var(--theme-color, #007aff);
				}

				&--append {
					font-size: 60%;
					opacity: 0.6;
				}
			}
		}
	}

	@keyframes IconLoading {
		0% {
			transform: rotate(0deg);
		}

		100% {
			transform: rotate(360deg);
		}
	}
</style>
  • utils.js文件
javascript 复制代码
// @ts-nocheck
/** 未选 */
export const unCheckedStatus = 0
/** 半选 */
export const halfCheckedStatus = 1
/** 选中 */
export const isCheckedStatus = 2

/**
 * 深拷贝内容
 * @param originData 拷贝对象
 * @author crlang(https://crlang.com)
 */
export function deepClone(originData) {
  const type = Object.prototype.toString.call(originData)
  let data
  if (type === '[object Array]') {
    data = []
    for (let i = 0; i < originData.length; i++) {
      data.push(deepClone(originData[i]))
    }
  } else if (type === '[object Object]') {
    data = {}
    for (const prop in originData) {
      // eslint-disable-next-line no-prototype-builtins
      if (originData.hasOwnProperty(prop)) { // 非继承属性
        data[prop] = deepClone(originData[prop])
      }
    }
  } else {
    data = originData
  }
  return data
}

/**
 * 获取所有指定的节点
 * @param type
 * @param value
 * @author crlang(https://crlang.com)
 */
export function getAllNodes(list, type, value, packDisabledkey = true) {
  if (!list || list.length === 0) {
    return []
  }

  const res = []
  for (let i = 0; i < list.length; i++) {
    const item = list[i]
    if (item[type] === value) {
      if ((packDisabledkey && item.disabled) || !item.disabled) {
        res.push(item)
      }
    }
  }

  return res
}

/**
 * 获取所有指定的key值
 * @param type
 * @param value
 * @author crlang(https://crlang.com)
 */
export function getAllNodeKeys(list, type, value, packDisabledkey = true) {
  if (!list || list.length === 0) {
    return []
  }

  const res = []
  for (let i = 0; i < list.length; i++) {
    const item = list[i]
    if (item[type] === value) {
      if ((packDisabledkey && item.disabled) || !item.disabled) {
        res.push(item.key)
      }
    }
  }

  return res
}
相关推荐
老李不敲代码3 小时前
榕壹云无人共享系统:基于SpringBoot+MySQL+UniApp的物联网共享解决方案
spring boot·物联网·mysql·微信小程序·uni-app·软件需求
—Qeyser5 小时前
用 Deepseek 写的uniapp血型遗传查询工具
前端·javascript·ai·chatgpt·uni-app·deepseek
codingandsleeping5 小时前
HTTP1.0、1.1、2.0 的区别
前端·网络协议·http
Monly215 小时前
Uniapp: 大纲
uni-app
天天扭码7 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫8 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
拉不动的猪8 小时前
设计模式之------策略模式
前端·javascript·面试
旭久8 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc8 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf