效果圖



平臺兼容性
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},// 子節點全選時是否返回父節點IDreturnParentWhenChildrenAllSelected: {type: Boolean,default: false},// 是否不可輸入disabled: {type: Boolean,default: false}},data() {return {showDialog: false, // 控制對話框顯示/隱藏treeList: [], // 扁平化的樹形數據列表searchValue: '', // 搜索輸入值selectedIds: [], // 選中的ID列表selectedNames: '', // 選中的名稱字符串expandedNodes: new Set(), // 存儲展開的節點IDselectedNodes: new Set(), // 存儲選中的節點ID}},methods: {// 顯示對話框并重置樹顯示狀態show() {this.showDialog = truethis._resetTreeDisplay()this._updateSelectedState() // 確保打開時選中狀態正確},// 隱藏對話框并清空搜索_hide() {this.showDialog = falsethis.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 || matchesChildif (matchesSelf || matchesChild) {this._showParents(item) // 確保父節點顯示}})} else {this._resetTreeDisplay() // 清空搜索時恢復默認顯示}this._updateSelectedState() // 搜索后重新計算選中狀態},// 檢查子節點是否匹配搜索關鍵詞(遞歸檢查所有子孫節點)_checkChildMatch(item, keyword) {let hasMatch = falseconst 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 = truebreak}// 遞歸檢查子節點的子節點if (!childItem.isLastLevel) {const childHasMatch = this._checkChildMatch(childItem, keyword)if (childHasMatch) {hasMatch = truebreak}}}}return hasMatch},// 顯示節點的父節點鏈,確保搜索時父節點可見_showParents(item) {let current = itemwhile (current.parentId !== -1) {const parent = this.treeList.find(p => p[this.valueKey] === current.parentId)if (parent) {parent.isShow = trueparent.isShowChild = truethis.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.lengthconst parentId = parentItem ? parentItem[this.valueKey] : -1const modelValueArray = this._parseModelValue()list.forEach(item => {const isLastLevel = !item[this.children] || !Array.isArray(item[this.children]) || item[this.children].length === 0const 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 = 2itemT.orCheckStatus = 2itemT.childCheckCount = itemT.childCountthis.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) returnlet childCheckCount = 0let childCheckPCount = 0for (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 = childCheckCountitem.childCheckPCount = childCheckPCountif (childCheckCount === item.childCount && childCheckPCount === 0) {item.checkStatus = 2item.orCheckStatus = 2this.selectedNodes.add(item[this.valueKey])} else if (childCheckCount > 0 || childCheckPCount > 0) {item.checkStatus = 1item.orCheckStatus = 1} else {item.checkStatus = 0item.orCheckStatus = 0this.selectedNodes.delete(item[this.valueKey])}},// 處理節點展開/折疊_onItemSwitch(item, index) {if (item.isLastLevel) returnitem.isShowChild = !item.isShowChildif (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 + 1for (let i = firstChildIndex; i < this.treeList.length; i++) {let childItem = this.treeList[i]if (childItem.level <= item.level) breakif (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 + 1for (let i = firstChildIndex; i < this.treeList.length; i++) {let childItem = this.treeList[i]if (childItem.level <= item.level) breakif (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 = falsethis.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 : 0v.orCheckStatus = v.checkStatusv.childCheckCount = v.checkStatus === 2 ? v.childCount : 0v.childCheckPCount = 0if (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 : 0item.orCheckStatus = item.checkStatusitem.childCheckCount = item.checkStatus === 2 ? item.childCount : 0item.childCheckPCount = 0if (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.checkStatuschildItem.orCheckStatus = item.checkStatuschildItem.childCheckCount = item.checkStatus === 2 ? childItem.childCount : 0childItem.childCheckPCount = 0if (item.checkStatus === 2) {this.selectedNodes.add(childItem[this.valueKey])} else {this.selectedNodes.delete(childItem[this.valueKey])}// 遞歸更新子節點的子節點this._onItemChildSelect(childItem, i)}} else {// 非搜索模式下,更新所有子節點childItem.checkStatus = item.checkStatuschildItem.orCheckStatus = item.checkStatuschildItem.childCheckCount = item.checkStatus === 2 ? childItem.childCount : 0childItem.childCheckPCount = 0if (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 = -1this.treeList.forEach(item => {if (currentLevel >= 0 && item.level > currentLevel) returnif (item.checkStatus === 2) {currentLevel = item.levelthis.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 = 2foundItem.orCheckStatus = 2this.selectedNodes.add(id)foundNames.push(foundItem[this.textKey])// 確保選中節點的所有父節點都展開let current = foundItemwhile (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>