效果图



平台兼容性
Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
√ |
√ |
√ |
√ |
√ |
√ |
- |
- |
- |
微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
快应用-华为 |
快应用-联盟 |
√ |
√ |
√ |
√ |
√ |
√ |
- |
√ |
√ |
√ |
√ |
属性
属性名 |
类型 |
默认值 |
说明 |
sourceList |
Array |
[] |
源数据,目前支持tree结构, |
valueKey |
String |
id |
指定 Object 中 key 的值作为节点数据id |
textKey |
String |
name |
指定 Object 中 key 的值作为节点显示内容 |
childrenKey |
String |
children |
指定 Object 中 key 的值作为节点子集 |
multiple |
Boolean |
false |
是否多选,默认单选 |
selectParent |
Boolean |
true |
是否可以选父级,默认可以 |
placeholder |
String |
|
标题 |
titleColor |
String |
标题颜色 |
confirmColor |
String |
#0055ff |
确定按钮颜色 |
cancelColor |
String |
#757575 |
取消按钮颜色 |
switchColor |
String |
#666 |
节点切换图标颜色 |
border |
Boolean |
false |
是否有分割线,默认无 |
separator |
String |
/ |
选中项之间的分隔符 |
modelValue |
String |
|
父组件传入的选中值(字符串,单选为单个ID,多选为分隔符连接的ID字符串) |
returnParentWhenChildrenAllSelected |
Boolean |
false |
子节点全选时是否返回父节点ID |
方法
方法名 |
参数 |
默认值 |
说明 |
show() |
|
|
显示选择器 |
hide() |
|
|
隐藏选择器 |
组件引用:
<template>
<tree-search v-model="formData.other" :multiple='true' placeholder="请选择疾病"
:sourceList="listData" valueKey="id" textKey="name" children="children" />
</template>
<script>
import treeSearch from "@/components/tree-search/tree-search.vue"
export default {
components: {
treeSearch
},
data() {
return {
listData: [{
id: "1",
name: "眼睑疾病",
children: [
{
id: "1-1",
name: "睑腺炎"
},
{
id: "1-2",
name: "睑板腺囊肿"
},
{
id: "1-3",
name: "睑缘炎",
children: [
{
id: "1-3-1",
name: "鳞屑性睑缘炎"
},
{
id: "1-3-2",
name: "溃疡性睑缘炎"
},
{
id: "1-3-3",
name: "眦部睑缘炎"
}
]
},
{
id: "1-4",
name: "眼睑湿疹"
}
]
},
{
id: "2",
name: "结膜疾病",
children: [
{
id: "2-1",
name: "结膜炎",
children: [
{
id: "2-1-1",
name: "细菌性结膜炎"
},
{
id: "2-1-2",
name: "细菌性结膜炎"
},
{
id: "2-1-3",
name: "衣原体性结膜炎"
}
]
},
{
id: "2-2",
name: "结膜结石"
},
{
id: "2-3",
name: "翼状胬肉"
},
{
id: "2-4",
name: "结膜下出血"
},
]
},
{
id: "3",
name: "角膜疾病",
children: [{
id: "3-1",
name: "角膜炎",
children: [{
id: "3-1-1",
name: "细菌性角膜炎"
},
{
id: "3-1-2",
name: "病毒性角膜炎"
},
{
id: "3-1-3",
name: "真菌性角膜炎"
},
{
id: "3-1-4",
name: "真菌性角膜炎"
}
]
},
{
id: "3-2",
name: "干眼综合征"
},
{
id: "3-3",
name: "暴露性角膜病变"
}
]
}
]
}
}
}
</script>
定义组件:
<template>
<view>
<!-- 输入框,显示选中的名称,点击触发树形选择器显示 -->
<uni-easyinput type="textarea" maxlength="-1" :autoHeight="true" v-model="selectedNames" :placeholder="placeholder" @focus="show()" :disabled="disabled" clearable/>
<!-- 遮罩层,点击关闭对话框 -->
<view class="tree-cover" :class="{'show':showDialog}" @tap="_cancel"></view>
<!-- 树形选择器对话框 -->
<view class="tree-dialog" :class="{'show':showDialog}">
<view class="tree-bar">
<!-- 取消按钮 -->
<view class="tree-bar-cancel" :style="{'color':cancelColor}" hover-class="hover-c" @tap="_cancel">取消</view>
<!-- 标题 -->
<view class="tree-bar-title" :style="{'color':titleColor}">{{placeholder}}</view>
<!-- 确认按钮,仅多选模式显示“确定”文字 -->
<view class="tree-bar-confirm" :style="{'color':confirmColor}" hover-class="hover-c" @tap="_confirm">
{{multiple?'确定':''}}
</view>
</view>
<view class="tree-view">
<!-- 树形列表容器,支持垂直滚动 -->
<scroll-view class="tree-list" :scroll-y="true">
<!-- 搜索输入框 -->
<uni-easyinput confirmType="search" class="uni-mt-5" suffixIcon="search" v-model="searchValue" placeholder="输入关键字进行搜索"
@iconClick="handleSearch" @change="handleSearch"></uni-easyinput>
<!-- 循环渲染树节点 -->
<block v-for="(item, index) in treeList" :key="index">
<view class="tree-item" :style="[{paddingLeft: item.level*30 + 'rpx'}]" :class="{itemBorder: border === true, show: item.isShow}">
<view class="item-label">
<!-- 节点展开/折叠图标 -->
<view class="item-icon uni-inline-item" @tap.stop="_onItemSwitch(item, index)">
<view v-if="!item.isLastLevel&&item.isShowChild" class="switch-on" :style="{'border-left-color':switchColor}"></view>
<view v-else-if="!item.isLastLevel&&!item.isShowChild" class="switch-off" :style="{'border-top-color':switchColor}"></view>
<view v-else-if="item.level === 0" class="item-last-dot" :style="{'border-top-color':switchColor}"></view>
<view v-else class="item-last-space"></view>
</view>
<!-- 节点选择区域 -->
<view class="item-flex uni-flex-item uni-inline-item" @tap.stop="_onItemSelect(item, index)">
<view class="item-check" v-if="selectParent?true:item.isLastLevel">
<!-- 部分选中状态 -->
<view class="item-check-yes" v-if="item.checkStatus==1" :class="{'radio':!multiple}" :style="{'border-color':confirmColor}">
<view class="item-check-yes-part" :style="{'background-color':confirmColor}"></view>
</view>
<!-- 完全选中状态 -->
<view class="item-check-yes" v-else-if="item.checkStatus==2" :class="{'radio':!multiple}" :style="{'border-color':confirmColor}">
<view class="item-check-yes-all" :style="{'background-color':confirmColor}"></view>
</view>
<!-- 未选中状态 -->
<view class="item-check-no" v-else :class="{'radio':!multiple}" :style="{'border-color':confirmColor}"></view>
</view>
<!-- 节点名称 -->
<view class="item-name">{{item[this.textKey]}}</view>
</view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "ba-tree-picker",
props: {
// 父组件传入的选中值(字符串,单选为单个ID,多选为分隔符连接的ID字符串)
modelValue: {
type: String,
required: true,
default: ''
},
// 节点唯一标识的字段名
valueKey: {
type: String,
default: 'id'
},
// 节点显示名称的字段名
textKey: {
type: String,
default: 'name'
},
// 子节点数组的字段名
children: {
type: String,
default: 'children'
},
// 树形数据源
sourceList: {
type: Array,
required: true,
default: () => []
},
// 输入框占位符
placeholder: {
type: String,
default: '请选择'
},
// 是否支持多选
multiple: {
type: Boolean,
default: true
},
// 是否允许选择父节点
selectParent: {
type: Boolean,
default: true
},
// 确认按钮颜色
confirmColor: {
type: String,
default: '' // 默认 #0055ff
},
// 取消按钮颜色
cancelColor: {
type: String,
default: '' // 默认 #757575
},
// 标题颜色
titleColor: {
type: String,
default: ''
},
// 节点展开/折叠图标颜色
switchColor: {
type: String,
default: '' // 默认 #666
},
// 选中项之间的分隔符
separator: {
type: String,
default: '/'
},
// 是否显示节点分割线
border: {
type: Boolean,
default: false
},
// 子节点全选时是否返回父节点ID
returnParentWhenChildrenAllSelected: {
type: Boolean,
default: false
},
// 是否不可输入
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
showDialog: false, // 控制对话框显示/隐藏
treeList: [], // 扁平化的树形数据列表
searchValue: '', // 搜索输入值
selectedIds: [], // 选中的ID列表
selectedNames: '', // 选中的名称字符串
expandedNodes: new Set(), // 存储展开的节点ID
selectedNodes: new Set(), // 存储选中的节点ID
}
},
methods: {
// 显示对话框并重置树显示状态
show() {
this.showDialog = true
this._resetTreeDisplay()
this._updateSelectedState() // 确保打开时选中状态正确
},
// 隐藏对话框并清空搜索
_hide() {
this.showDialog = false
this.searchValue = ''
this._resetTreeDisplay()
},
// 取消操作,触发取消事件并隐藏对话框
_cancel() {
this._hide()
this.$emit("cancel")
},
// 确认选择,更新选中列表并触发父组件更新
_confirm() {
this._updateSelectedState(true)
this._hide()
},
// 处理搜索逻辑,过滤显示匹配的节点
handleSearch() {
if (this.searchValue) {
this.treeList.forEach(item => {
const matchesSelf = item[this.textKey].toLowerCase().includes(this.searchValue.toLowerCase())
const matchesChild = this._checkChildMatch(item, this.searchValue.toLowerCase())
item.isShow = matchesSelf || matchesChild
if (matchesSelf || matchesChild) {
this._showParents(item) // 确保父节点显示
}
})
} else {
this._resetTreeDisplay() // 清空搜索时恢复默认显示
}
this._updateSelectedState() // 搜索后重新计算选中状态
},
// 检查子节点是否匹配搜索关键词(递归检查所有子孙节点)
_checkChildMatch(item, keyword) {
let hasMatch = false
const itemIndex = this.treeList.findIndex(i => i[this.valueKey] === item[this.valueKey])
if (itemIndex !== -1) {
for (let i = itemIndex + 1; i < this.treeList.length; i++) {
const childItem = this.treeList[i]
if (childItem.level <= item.level) break
// 检查当前子节点是否匹配
if (childItem[this.textKey].toLowerCase().includes(keyword)) {
hasMatch = true
break
}
// 递归检查子节点的子节点
if (!childItem.isLastLevel) {
const childHasMatch = this._checkChildMatch(childItem, keyword)
if (childHasMatch) {
hasMatch = true
break
}
}
}
}
return hasMatch
},
// 显示节点的父节点链,确保搜索时父节点可见
_showParents(item) {
let current = item
while (current.parentId !== -1) {
const parent = this.treeList.find(p => p[this.valueKey] === current.parentId)
if (parent) {
parent.isShow = true
parent.isShowChild = true
this.expandedNodes.add(parent[this.valueKey])
current = parent
} else {
break
}
}
},
// 重置树显示状态,恢复展开节点状态
_resetTreeDisplay() {
// 先重置所有节点状态
this.treeList.forEach(item => {
item.isShowChild = this.expandedNodes.has(item[this.valueKey]) && !item.isLastLevel
})
// 根节点默认显示
this.treeList.forEach(item => {
if (item.level === 0) {
item.isShow = true
} else {
item.isShow = false
}
})
// 确保展开节点的子节点正确显示
this.treeList.forEach(item => {
if (item.isShowChild) {
const index = this.treeList.findIndex(i => i[this.valueKey] === item[this.valueKey])
if (index !== -1) {
this._showChildNodes(item, index)
}
}
})
},
// 格式化树形数据,预加载所有子节点
_formatTreeData(list = [], level = 0, parentItem = null, isShowChild = true) {
if (!list || !Array.isArray(list)) return
// 使用更简单的插入方式:在数组末尾直接添加
let startIndex = this.treeList.length
const parentId = parentItem ? parentItem[this.valueKey] : -1
const modelValueArray = this._parseModelValue()
list.forEach(item => {
const isLastLevel = !item[this.children] || !Array.isArray(item[this.children]) || item[this.children].length === 0
const isExpanded = this.expandedNodes.has(item[this.valueKey])
let itemT = {
[this.valueKey]: item[this.valueKey],
[this.textKey]: item[this.textKey],
level,
isLastLevel,
isShow: isShowChild,
isShowChild: isExpanded && !isLastLevel,
checkStatus: 0, // 默认未选中
orCheckStatus: 0, // 初始选中状态
parentId,
children: item[this.children], // 保留原始children引用
childCount: item[this.children] ? item[this.children].length : 0,
childCheckCount: 0, // 子节点全选计数
childCheckPCount: 0 // 子节点部分选中计数
}
// 根据modelValue设置选中状态
if (modelValueArray.includes(String(itemT[this.valueKey]))) {
itemT.checkStatus = 2
itemT.orCheckStatus = 2
itemT.childCheckCount = itemT.childCount
this.selectedNodes.add(itemT[this.valueKey])
}
// 直接添加到数组末尾
this.treeList.push(itemT)
// 递归处理所有子节点(预加载)
if (itemT.children && itemT.children.length > 0) {
// 子节点是否显示应该基于当前节点是否显示,而不是展开状态
this._formatTreeData(itemT.children, level + 1, itemT, itemT.isShow)
}
})
// 递归完成后更新所有父节点的选中状态
if (level === 0) {
this.treeList.forEach(item => {
if (!item.isLastLevel) {
this._updateParentCheckStatus(item)
}
})
}
},
// 解析modelValue为数组格式
_parseModelValue() {
let modelValueArray = []
if (Array.isArray(this.modelValue)) {
modelValueArray = this.modelValue.map(String)
} else if (typeof this.modelValue === 'string' && this.modelValue.trim()) {
modelValueArray = this.modelValue.split(this.separator).map(id => id.trim()).filter(id => id)
} else if (typeof this.modelValue === 'number') {
modelValueArray = [String(this.modelValue)]
}
return modelValueArray
},
// 更新父节点的选中状态
_updateParentCheckStatus(item) {
if (item.isLastLevel) return
let childCheckCount = 0
let childCheckPCount = 0
for (let i = 0; i < this.treeList.length; i++) {
const child = this.treeList[i]
if (child.parentId === item[this.valueKey] && child.level === item.level + 1) {
if (child.checkStatus === 2) childCheckCount++
else if (child.checkStatus === 1) childCheckPCount++
}
}
item.childCheckCount = childCheckCount
item.childCheckPCount = childCheckPCount
if (childCheckCount === item.childCount && childCheckPCount === 0) {
item.checkStatus = 2
item.orCheckStatus = 2
this.selectedNodes.add(item[this.valueKey])
} else if (childCheckCount > 0 || childCheckPCount > 0) {
item.checkStatus = 1
item.orCheckStatus = 1
} else {
item.checkStatus = 0
item.orCheckStatus = 0
this.selectedNodes.delete(item[this.valueKey])
}
},
// 处理节点展开/折叠
_onItemSwitch(item, index) {
if (item.isLastLevel) return
item.isShowChild = !item.isShowChild
if (item.isShowChild) {
this.expandedNodes.add(item[this.valueKey])
// 展开时确保子节点可见
this._showChildNodes(item, index)
} else {
this.expandedNodes.delete(item[this.valueKey])
// 折叠时重置子节点显示状态,确保在搜索模式下也能正确折叠
this._onItemChildSwitch(item, index)
}
this._updateSelectedState()
},
// 显示节点的所有子节点
_showChildNodes(item, index) {
const firstChildIndex = index + 1
for (let i = firstChildIndex; i < this.treeList.length; i++) {
let childItem = this.treeList[i]
if (childItem.level <= item.level) break
if (childItem.parentId === item[this.valueKey]) {
// 在非搜索模式下,直接显示子节点
if (!this.searchValue) {
childItem.isShow = true
} else {
// 在搜索模式下,只有当子节点匹配搜索关键词或有匹配子节点时,才显示子节点
// 检查子节点是否匹配搜索关键词
const matchesSelf = childItem[this.textKey].toLowerCase().includes(this.searchValue.toLowerCase())
// 或者检查子节点是否有匹配搜索关键词的子节点
const matchesChild = this._checkChildMatch(childItem, this.searchValue.toLowerCase())
// 关键修复:只有当子节点匹配搜索时才显示
if (matchesSelf || matchesChild) {
childItem.isShow = true
}
}
// 如果子节点之前是展开状态,也显示其子节点
if (childItem.isShowChild) {
this._showChildNodes(childItem, i)
}
}
}
},
// 更新子节点显示状态
_onItemChildSwitch(item, index) {
const firstChildIndex = index + 1
for (let i = firstChildIndex; i < this.treeList.length; i++) {
let childItem = this.treeList[i]
if (childItem.level <= item.level) break
if (childItem.parentId === item[this.valueKey]) {
// 在非搜索模式下,根据父节点展开状态决定子节点显示状态
if (!this.searchValue) {
childItem.isShow = item.isShowChild
} else {
// 在搜索模式下,根据父节点展开状态和子节点匹配状态决定子节点显示状态
// 检查子节点是否匹配搜索关键词
const matchesSelf = childItem[this.textKey].toLowerCase().includes(this.searchValue.toLowerCase())
// 或者检查子节点是否有匹配搜索关键词的子节点
const matchesChild = this._checkChildMatch(childItem, this.searchValue.toLowerCase())
// 关键修复:在搜索模式下,严格控制子节点显示
if (item.isShowChild) {
// 父节点展开时,只有子节点匹配才显示
childItem.isShow = matchesSelf || matchesChild
} else {
// 父节点折叠时,无论子节点是否匹配,都隐藏
// 这样可以确保折叠时真正收起所有子节点,保持搜索结果的一致性
childItem.isShow = false
}
}
if (!item.isShowChild) {
childItem.isShowChild = false
this.expandedNodes.delete(childItem[this.valueKey])
// 递归折叠所有子节点
this._onItemChildSwitch(childItem, i)
}
}
}
},
// 处理节点选中/取消选中
_onItemSelect(item, index) {
if (!this.multiple) {
// 单选模式:仅选中当前节点
this.treeList.forEach((v, i) => {
v.checkStatus = i === index ? 2 : 0
v.orCheckStatus = v.checkStatus
v.childCheckCount = v.checkStatus === 2 ? v.childCount : 0
v.childCheckPCount = 0
if (i === index) {
this.selectedNodes.add(v[this.valueKey])
} else {
this.selectedNodes.delete(v[this.valueKey])
}
})
this.selectedIds = [item[this.valueKey]]
this.selectedNames = item[this.textKey]
this.$emit('update:modelValue', item[this.valueKey])
this._hide()
return
}
// 多选模式:切换选中状态
item.checkStatus = item.checkStatus === 0 ? 2 : 0
item.orCheckStatus = item.checkStatus
item.childCheckCount = item.checkStatus === 2 ? item.childCount : 0
item.childCheckPCount = 0
if (item.checkStatus === 2) {
this.selectedNodes.add(item[this.valueKey])
} else {
this.selectedNodes.delete(item[this.valueKey])
}
// 更新子节点
if (item.children && !item.isLastLevel) {
this._onItemChildSelect(item, index)
}
// 更新父节点
this._onItemParentSelect(item, index)
// 更新选中状态和显示名称,不跳过发出事件,确保父节点选中状态能实时更新
this._updateSelectedState()
},
// 更新子节点选中状态
_onItemChildSelect(item, index) {
for (let i = 0; i < this.treeList.length; i++) {
let childItem = this.treeList[i]
if (childItem.parentId === item[this.valueKey] && childItem.level === item.level + 1) {
// 在搜索模式下,只更新和显示匹配搜索关键词的子节点
if (this.searchValue) {
// 检查子节点是否匹配搜索关键词或有匹配子节点
const matchesSelf = childItem[this.textKey].toLowerCase().includes(this.searchValue.toLowerCase())
const matchesChild = this._checkChildMatch(childItem, this.searchValue.toLowerCase())
// 只有匹配的子节点才更新选中状态和显示
if (matchesSelf || matchesChild) {
childItem.checkStatus = item.checkStatus
childItem.orCheckStatus = item.checkStatus
childItem.childCheckCount = item.checkStatus === 2 ? childItem.childCount : 0
childItem.childCheckPCount = 0
if (item.checkStatus === 2) {
this.selectedNodes.add(childItem[this.valueKey])
} else {
this.selectedNodes.delete(childItem[this.valueKey])
}
// 递归更新子节点的子节点
this._onItemChildSelect(childItem, i)
}
} else {
// 非搜索模式下,更新所有子节点
childItem.checkStatus = item.checkStatus
childItem.orCheckStatus = item.checkStatus
childItem.childCheckCount = item.checkStatus === 2 ? childItem.childCount : 0
childItem.childCheckPCount = 0
if (item.checkStatus === 2) {
this.selectedNodes.add(childItem[this.valueKey])
} else {
this.selectedNodes.delete(childItem[this.valueKey])
}
// 递归更新子节点的子节点
this._onItemChildSelect(childItem, i)
}
}
}
},
// 更新父节点选中状态
_onItemParentSelect(item, index) {
const parentIndex = this.treeList.findIndex(itemP => itemP[this.valueKey] === item.parentId)
if (parentIndex >= 0) {
let parent = this.treeList[parentIndex]
this._updateParentCheckStatus(parent)
this._onItemParentSelect(parent, parentIndex)
}
},
// 更新选中状态和显示名称
_updateSelectedState(skipEmit = false) {
// 发出更新事件
if (skipEmit) {
this.selectedIds = []
this.selectedNames = ''
if (this.returnParentWhenChildrenAllSelected) {
// 当子节点全选时返回父节点
let currentLevel = -1
this.treeList.forEach(item => {
if (currentLevel >= 0 && item.level > currentLevel) return
if (item.checkStatus === 2) {
currentLevel = item.level
this.selectedIds.push(item[this.valueKey])
this.selectedNames = this.selectedNames
? `${this.selectedNames} ${this.separator} ${item[this.textKey]}`
: item[this.textKey]
} else {
currentLevel = -1
}
})
} else {
// 返回所有选中的子节点
this.treeList.forEach(item => {
if (item.checkStatus === 2 && (!item.children || item.isLastLevel)) {
this.selectedIds.push(item[this.valueKey])
this.selectedNames = this.selectedNames
? `${this.selectedNames} ${this.separator} ${item[this.textKey]}`
: item[this.textKey]
}
})
}
this.$emit('update:modelValue', this.multiple ? this.selectedIds.join(this.separator) : (this.selectedIds[0] || ''))
}
},
// 初始化树形结构
_initTree() {
// 重置状态
this.treeList = []
this.expandedNodes.clear()
this.selectedNodes.clear()
// 格式化树数据
this._formatTreeData(this.sourceList)
// 根据modelValue设置选中状态并展开相关节点
const modelValueArray = this._parseModelValue()
if (modelValueArray.length > 0) {
const validIds = new Set(this.treeList.map(item => String(item[this.valueKey])))
const foundNames = []
modelValueArray.forEach(id => {
if (validIds.has(id)) {
const foundItem = this.treeList.find(item => String(item[this.valueKey]) === id)
if (foundItem) {
// 设置选中状态
foundItem.checkStatus = 2
foundItem.orCheckStatus = 2
this.selectedNodes.add(id)
foundNames.push(foundItem[this.textKey])
// 确保选中节点的所有父节点都展开
let current = foundItem
while (current.parentId !== -1) {
const parent = this.treeList.find(p => p[this.valueKey] === current.parentId)
if (parent) {
this.expandedNodes.add(parent[this.valueKey])
current = parent
} else {
break
}
}
}
}
})
if (foundNames.length > 0) {
this.selectedNames = foundNames.join(` ${this.separator} `)
}
}
// 更新所有父节点的选中状态
this.treeList.forEach(item => {
if (!item.isLastLevel) {
this._updateParentCheckStatus(item)
}
})
this._resetTreeDisplay()
}
},
watch: {
// 监听sourceList变化,重新初始化树
sourceList: {
handler() {
this._initTree()
},
deep: true
},
// 监听modelValue变化,更新树状态
modelValue: {
immediate: true,
handler() {
this._initTree()
}
}
},
// 组件挂载时初始化树
mounted() {
this._initTree()
}
}
</script>
<style scoped>
/* 样式保持完全不变 */
:deep(.uni-easyinput) {
width: 100%;
position: relative;
text-align: left;
color: #333;
font-size: 14px;
}
.tree-cover {
position: fixed;
top: 0rpx;
right: 0rpx;
bottom: 0rpx;
left: 0rpx;
z-index: 100;
background-color: rgba(0, 0, 0, .4);
opacity: 0;
transition: all 0.3s ease;
visibility: hidden;
}
.tree-cover.show {
visibility: visible;
opacity: 1;
}
.tree-dialog {
position: fixed;
top: 0rpx;
right: 0rpx;
bottom: 0rpx;
left: 0rpx;
background-color: #fff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
display: flex;
flex-direction: column;
z-index: 102;
top: 20%;
transition: all 0.3s ease;
transform: translateY(100%);
}
.tree-dialog.show {
transform: translateY(0);
}
.tree-bar {
height: 90rpx;
padding-left: 25rpx;
padding-right: 25rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
border-bottom-width: 1rpx !important;
border-bottom-style: solid;
border-bottom-color: #f5f5f5;
font-size: 32rpx;
color: #757575;
line-height: 1;
}
.tree-bar-confirm {
color: #0055ff;
padding: 15rpx;
}
.tree-bar-title {
font-weight: bold;
color: #333;
}
.tree-bar-cancel {
color: #757575;
padding: 15rpx;
}
.tree-view {
flex: 1;
padding: 20rpx;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
}
.item-flex {
display: flex;
justify-content: space-between;
align-items: center;
}
.tree-list {
flex: 1;
height: 100%;
overflow: hidden;
}
.tree-item {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 1;
height: 0;
opacity: 0;
transition: 0.2s;
overflow: hidden;
}
.tree-item.show {
height: 90rpx;
opacity: 1;
}
.tree-item.showchild:before {
transform: rotate(90deg);
}
.tree-item.last:before {
opacity: 0;
}
.switch-on {
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 15rpx solid #666;
}
.switch-off {
width: 0;
height: 0;
border-bottom: 10rpx solid transparent;
border-top: 10rpx solid transparent;
border-left: 15rpx solid #666;
}
.item-last-dot {
position: absolute;
width: 10rpx;
height: 10rpx;
border-radius: 100%;
background: #666;
}
.item-last-space {
width: 10rpx;
height: 10rpx;
}
.item-icon {
width: 26rpx;
height: 26rpx;
margin-right: 8rpx;
padding-right: 20rpx;
padding-left: 20rpx;
}
.item-label {
flex: 1;
display: flex;
align-items: center;
height: 100%;
line-height: 1.2;
}
.item-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 450rpx;
}
.item-check {
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.item-check-yes,
.item-check-no {
width: 20px;
height: 20px;
border-top-left-radius: 20%;
border-top-right-radius: 20%;
border-bottom-right-radius: 20%;
border-bottom-left-radius: 20%;
border-top-width: 1rpx;
border-left-width: 1rpx;
border-bottom-width: 1rpx;
border-right-width: 1rpx;
border-style: solid;
border-color: #0055ff;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.item-check-yes-part {
width: 12px;
height: 12px;
border-top-left-radius: 20%;
border-top-right-radius: 20%;
border-bottom-right-radius: 20%;
border-bottom-left-radius: 20%;
background-color: #0055ff;
}
.item-check-yes-all {
margin-bottom: 5px;
border: 2px solid #007aff;
border-left: 0;
border-top: 0;
height: 12px;
width: 6px;
transform-origin: center;
transition: all 0.3s;
transform: rotate(45deg);
}
.item-check .radio {
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-right-radius: 50%;
border-bottom-left-radius: 50%;
}
.item-check .radio .item-check-yes-b {
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-right-radius: 50%;
border-bottom-left-radius: 50%;
}
.hover-c {
opacity: 0.6;
}
.itemBorder {
border-bottom: 1px solid #e5e5e5;
}
</style>