<template>
<view :class=“{ ‘next-tree’: !toEject, ‘next-tree-eject’: toEject }” v-if=“showTree”>
<view v-if=“toEject” class=“next-tree-mask” :class=“{ show: showTree }” @tap=“_cancel(1)”></view>
<view class=“next-tree-cnt” :class=“{ show: showTree }”>
<view class=“next-tree-bar” v-if=“needBtn”>
<view class=“next-tree-bar-cancel” :style=“{ color: cancelColor }” hover-class=“hover-c” @tap=“_cancel”
>取消</view
>
<view class=“next-tree-bar-title” :style=“{ color: titleColor }”>{{ title }}</view>
<view class=“next-tree-bar-confirm” :style=“{ color: confirmColor }” hover-class=“hover-c” @tap=“_confirm(1)”
>确定</view
>
</view>
<slot></slot>
<view class=“next-tree-view” :style=“{ height: boxHeight, minHeight: ‘112px’ }”>
<scroll-view class=“next-tree-view-sc” :scroll-y=“true” :style=“{ minHeight: ‘112px’ }”>
<block v-for=“(item, index) in treeList” :key=“index”>
<view
class=“next-tree-item”
:style=“[
{
marginLeft: item.rank * 25 + ‘px’,
zIndex: item.rank * -1 + 50
}
]”
:class=“{
border: border === true,
show: item.show,
last: item.lastRank,
showchild: item.showChild,
open: item.open
}”
>
<view
class=“next-tree-label”
:class=“{
checked: item.checked,
disabled: item.disabled
}”
@tap.stop=“_treeItemTap(item, index)”
>
{{ item.name }}
</view>
<view
class=“next-tree-check”
@tap.stop=“_treeItemSelect(item, index)”
v-if=“selectParent ? true : item.lastRank”
>
<view class=“next-tree-check-yes” v-if=“item.checked” :class=“{ radio: !multiple }”>
<!-- <text class=“icon-text”>✔</text> -->
<uni-icons :type=“checkIcon” color=“#0f56d5” />
</view>
<view class=“next-tree-check-no” v-else :class=“{ radio: !multiple }”></view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
<view v-if=“!toEject” class=“next-tree-mask-eject” :class=“{ show: showTree }” @tap=“_cancel(1)”></view>
</view>
</template>
<script>
/**
zy-tree 树组件
@description 树组件
@property {Array} treeData 组件数据{needShowChild: true,当前项与父级展开,当前项子级不展开
needOpen: true,当前项子级与父级展开,checked: true默认选中的值,选中的值父级自动打开}
@property {String} valueKey 选中值键名
@property {String} labelKey 展示值键名
@property {String} childrenKey 子类数据键名
@property {String} title 标题
@property {Boolean} multiple 是否可以多选
@property {Boolean} selectParent = 是否可以选父级
@property {Boolean} foldAll = 折叠时关闭所有已经打开的子集,再次打开时需要一级一级打开
@property {String} confirmColor = 确定按钮颜色
@property {String} cancelColor = 取消按钮颜色
@property {String} titleColor = 标题颜色
@property {String} currentIcon = 展开时候的icon
@property {String} checkIcon = 选中时候的icon
@property {String} defaultIcon = 折叠时候的icon
@property {String} lastIcon = 没有子集的icon
@property {Boolean} border = 是否有分割线
@property {Boolean} needBtn = 是否有操作按钮
@property {Boolean} toEject = 是否是从底部弹出
@property {String} boxHeight = 是否是从底部弹出时组件可选区域的高度默认112px
@property {Boolean} clickMask = 点击遮罩层关闭组件
@event {Function} 弹窗关闭触发的方法
@event {Function} 数据选中或有按钮时点击‘确定’触发的方法
@event {Function} 可通过ref调用组件内方法,进行组件的展示
@event {Function} 可通过ref调用组件内方法,进行组件的关闭但不触发cancel事件
*/
export default {
name: ‘ZyTree’,
props: {
treeData: {
type: Array,
default: function () {
return []
}
},
valueKey: {
type: String,
default: ‘id’
},
labelKey: {
type: String,
default: ‘label’
},
childrenKey: {
type: String,
default: ‘children’
},
title: {
type: String,
default: ‘’
},
multiple: {
// 是否可以多选
type: Boolean,
default: true
},
selectParent: {
//是否可以选父级
type: Boolean,
default: true
},
foldAll: {
//折叠时关闭所有已经打开的子集,再次打开时需要一级一级打开
type: Boolean,
default: false
},
confirmColor: {
// 确定按钮颜色
type: String,
default: ‘#0f56d5’
},
cancelColor: {
// 取消按钮颜色
type: String,
default: ‘#0f56d5’
},
titleColor: {
// 标题颜色
type: String,
default: ‘’
},
currentIcon: {
// 展开时候的icon
type: String,
default: ‘folder’
},
checkIcon: {
// 选中时候的icon
type: String,
default: ‘checkmarkempty’
},
defaultIcon: {
// 折叠时候的icon
type: String,
default: ‘folder’
},
lastIcon: {
// 没有子集的icon
type: String,
default: ‘’
},
border: {
// 是否有分割线
type: Boolean,
default: true
},
needBtn: {
// 是否有操作按钮
type: Boolean,
default: false
},
toEject: {
// 是否是从底部弹出
type: Boolean,
default: false
},
boxHeight: {
// 是否是从底部弹出时组件可选区域的高度默认100%
type: String,
default: ‘500px’
},
clickMask: {
// 点击遮罩层关闭组件
type: Boolean,
default: true
}
},
data() {
return {
showTree: false,
treeList: [],
selectIndex: -1,
childNums: [],
rt: [],
rtName: []
}
},
computed: {},
methods: {
_show() {
this.showTree = true
this._confirm()
},
_hide() {
this.showTree = false
},
_cancel(val) {
if (val == 1 && !this.clickMask) return
this._hide()
this.$emit(‘cancel’, ‘’)
},
_confirm(val) {
this.rt = []
this.rtName = []
// 处理所选数据
this.treeList.forEach((item, i) => {
if (item.id == 100 && item.checked) {
return this.confirmEach(this.treeData)
}
if (item.checked && item.parentId && item.parentId != ‘100’) {
this.rt.push(item.id)
this.rtName.push(item.name)
}
})
if (val == 1) {
this._cancel()
}
this.$emit(‘confirm’, { id: this.rt, name: this.rtName })
},
confirmEach(list) {
list.forEach((item, index) => {
if (item.isDy) {
this.rtName.push(item.name)
this.rt.push(item.id)
}
if (item.children && item.children.length > 0) this.confirmEach(item.children)
})
},
//扁平化树结构
_renderTreeList(list = [], rank = 0, parentId = [], parents = []) {
list.forEach(item => {
this.treeList.push({
id: item[this.valueKey],
name: item[this.labelKey],
source: item,
parentIdNub: item.parentId,
parentId, // 父级id数组
parents, // 父级id数组
rank, // 层级
showChild: false, //子级是否显示
needShowChild: item.needShowChild ? item.needShowChild : false, //子级是否显示
open: false, //是否打开
needOpen: item.needOpen ? item.needOpen : false, //是否打开
show: rank === 0, // 自身是否显示
hideArr: [],
orChecked: item.checked ? item.checked : false,
checked: item.checked ? item.checked : false,
disabled: item.disabled ? item.disabled : false
})
if (Array.isArray(item[this.childrenKey]) && item[this.childrenKey].length > 0) {
let parentid = […parentId],
parentArr = […parents],
childrenid = []
delete parentArr.children
parentid.push(item[this.valueKey])
parentArr.push({
[this.valueKey]: item[this.valueKey],
[this.labelKey]: item[this.labelKey]
})
this._renderTreeList(item[this.childrenKey], rank + 1, parentid, parentArr)
} else {
this.treeList[this.treeList.length - 1].lastRank = true
}
})
},
_defaultSelect() {
this.treeList.forEach(v => {
if (v.checked) {
this._handleCheckedNode(v)
}
if (v.needOpen) {
this._handleOpenNode(v)
}
if (v.needShowChild) {
this._handleShowChildNode(v)
}
})
},
_handleCheckedNode(v) {
this.treeList.forEach(v2 => {
if (v.parentId.toString().indexOf(v2.parentId.toString()) >= 0) {
v2.show = true
if (v.parentId.includes(v2.id)) {
v2.showChild = true
v2.open = true
}
}
})
},
_handleOpenNode(v) {
v.showChild = true
v.open = true
v.show = true
this.treeList.forEach(v2 => {
if (v2.parentId.toString().indexOf(v.id.toString()) >= 0) {
v2.show = true
if (v2.parentId.includes(v.id)) {
v2.showChild = true
v2.open = true
}
}
if (v.parentId.includes(v2.id)) {
v2.showChild = true
v2.open = true
}
})
},
_handleShowChildNode(v) {
this.treeList.forEach(v2 => {
if (v.parentId.toString().indexOf(v2.parentId.toString()) >= 0) {
v2.show = true
if (v.parentId.includes(v2.id)) {
v2.showChild = true
v.showChild = true
}
}
if (v.id === v2.id) {
v.showChild = false
v.open = false
}
})
},
// 点击
_treeItemTap(item, index) {
if (item.disabled) return
if (item.lastRank === true) {
//点击最后一级时触发事件
this.treeList[index].checked = !this.treeList[index].checked
this._fixMultiple(index)
return
}
let list = this.treeList
let id = item.id
if (item.showChild && !item.open) {
item.showChild = false
item.open = false
} else {
item.showChild = !item.showChild
}
item.open = item.showChild ? true : !item.open
list.forEach((childItem, i) => {
if (item.showChild === false) {
//隐藏所有子级
if (!childItem.parentId.includes(id)) {
return
}
if (!this.foldAll) {
if (childItem.lastRank !== true && !childItem.open) {
childItem.showChild = false
}
// 为隐藏的内容添加一个标记
if (childItem.show) {
childItem.hideArr[item.rank] = id
}
} else {
if (childItem.lastRank !== true) {
childItem.showChild = false
}
}
childItem.show = false
} else {
// 打开子集
if (childItem.parentId[childItem.parentId.length - 1] === id) {
childItem.show = true
}
// 打开被隐藏的子集
if (childItem.parentId.includes(id) && !this.foldAll) {
if (childItem.hideArr[item.rank] === id) {
childItem.show = true
if (childItem.open && childItem.showChild) {
childItem.showChild = true
} else {
childItem.showChild = false
}
childItem.hideArr[item.rank] = null
}
}
}
})
if (!this.needBtn) {
this._confirm()
}
},
_treeItemSelect(item, index) {
if (item.disabled) return
this.treeList[index].checked = !this.treeList[index].checked
// 选父级, 子级自动全选
this.syncChecked(this.treeList, item.id, this.treeList[index].checked)
if (item.rank > 0) {
item.parentId.forEach((pid, indexP) => {
const parent = this.treeList.filter(i => i.id === pid)
const childNum = parent.length > 0 ? parent[0].childNum : 0
if (this.childNums[pid] === undefined) {
this.childNums[pid] = 1
} else if (this.childNums[pid] < childNum) {
this.childNums[pid]++
}
})
//子级选择/选满/取消选择, 父级往上同步状态
this.setAncestors(item.parentId, this.treeList[index].checked)
}
this._fixMultiple(index)
if (!this.needBtn) {
this._confirm()
}
},
syncChecked(trees, pid, checked) {
trees.forEach((item, index) => {
if (item.disabled) return
if (item.parentId.includes(pid)) {
this.treeList[index].checked = checked
this.syncChecked(trees, item.id, checked)
} else if (item.children !== undefined) {
this.syncChecked(item.children, pid, checked)
}
})
},
setAncestors(pids, checked) {
this.treeList.forEach((item, index) => {
if (pids.includes(item.id)) {
if (checked && this.childNums[item.id] !== undefined && item.childNum === this.childNums[item.id]) {
// 子级全部选中, 父级才选中
this.treeList[index].checked = true
} else {
this.treeList[index].checked = false
}
this.setAncestors(item.parentId, checked)
}
})
},
// _treeItemSelect(item, index) {
// this.treeList[index].checked = !this.treeList[index].checked
// this._fixMultiple(index)
// },
// 处理单选多选
_fixMultiple(index) {
if (!this.multiple) {
// 如果是单选
this.treeList.forEach((v, i) => {
if (i != index) {
this.treeList[i].checked = false
} else {
if (this.treeList[i].checked) {
this.treeList[i].checked = true
} else {
this.treeList[i].checked = false
}
}
})
}
},
// 重置数据
_reTreeList() {
this.treeList.forEach((v, i) => {
this.treeList[i].checked = v.orChecked
})
},
_initTree(treeData = this.treeData) {
this.treeList = []
this._renderTreeList(treeData)
this.$nextTick(() => {
this._defaultSelect(treeData)
})
}
},
mounted() {
console.log(‘----------next-tree组件完成挂载demo------------’)
this._initTree()
this.$watch(
() => this.treeData,
list => {
this._initTree(list)
},
{ deep: true, immediate: true }
)
this.$watch(
() => [this.multiple, this.selectParent],
() => {
if (this.treeData.length) {
this._reTreeList()
}
},
{ deep: true, immediate: true }
)
}
}
</script>
<style lang=“scss”>
@import ‘./style.scss’;
</style>
改动当前组件next-tree-view内的内容,让其左右布局做多三列每一列,默认选中展开每一列的第一项,