效果图



平台兼容性
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>