效果图

平台兼容性

Vue2Vue3ChromeSafariapp-vueapp-nvueAndroidiOS鸿蒙
---
微信小程序支付宝小程序抖音小程序百度小程序快手小程序京东小程序鸿蒙元服务QQ小程序飞书小程序快应用-华为快应用-联盟
-
多语言暗黑模式宽屏模式
××

属性

属性名类型默认值说明
sourceListArray[]源数据,目前支持tree结构,
valueKeyStringid指定 Object 中 key 的值作为节点数据id
textKeyStringname指定 Object 中 key 的值作为节点显示内容
childrenKeyStringchildren指定 Object 中 key 的值作为节点子集
multipleBooleanfalse是否多选,默认单选
selectParentBooleantrue是否可以选父级,默认可以
placeholderString标题
titleColorString标题颜色
confirmColorString#0055ff确定按钮颜色
cancelColorString#757575取消按钮颜色
switchColorString#666节点切换图标颜色
borderBooleanfalse是否有分割线,默认无
separatorString/选中项之间的分隔符

modelValue

String

 父组件传入的选中值(字符串,单选为单个ID,多选为分隔符连接的ID字符串)

returnParentWhenChildrenAllSelectedBooleanfalse子节点全选时是否返回父节点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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95467.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95467.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95467.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

元宇宙与教育变革:沉浸式学习重构知识获取与能力培养

1 元宇宙打破传统教育的核心局限1.1 突破空间限制&#xff1a;从 “固定教室” 到 “全域学习场景”传统教育受限于物理空间&#xff0c;优质资源集中在少数学校与城市&#xff0c;而元宇宙通过 “虚拟场景复刻 跨地域实时交互”&#xff0c;将学习空间拓展至全球乃至虚拟维度…

如何在SpringBoot项目中优雅的连接多台Redis

如何在SpringBoot项目中优雅的连接多台Redis 在Spring Boot项目中&#xff0c;连接单个Redis实例是常见需求&#xff0c;但有时需要同时连接多个Redis实例&#xff08;例如&#xff0c;主Redis用于业务数据存储&#xff0c;另一个Redis用于爬虫数据缓存&#xff09;。本文将基于…

追觅科技举办2025「敢梦敢为」发布会,发布超30款全场景重磅新品

上海&#xff0c;2025年9月4日——在以「敢梦敢为」为主题的2025新品发布会上&#xff0c;追觅科技一次性发布超30款新品&#xff0c;全面涵盖智能清洁、智能家电、家庭健康与个护等核心领域。在清洁家电与大家电“高端智能生态矩阵”已然成型的当下&#xff0c;追觅科技正在迈…

去服务器化的流媒体分发:轻量级RTSP服务的技术逻辑与优势

一、设计背景&#xff1a;RTSP/RTP协议的技术根基 在流媒体传输体系中&#xff0c;RTSP&#xff08;Real-Time Streaming Protocol&#xff09; RTP/RTCP 组合被广泛认为是最经典、最标准化的解决方案。 RTSP 作为应用层协议&#xff0c;本质上是一个 远程会话控制协议。它通过…

mysql分页SQL

在 MySQL 中&#xff0c;实现分页查询通常使用 LIMIT 子句。LIMIT 可以指定返回结果的起始位置和数量&#xff0c;非常适合实现分页功能。 基本语法如下&#xff1a; SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 排序字段 [ASC|DESC] LIMIT 起始位置, 每页显示数量;说明&#x…

刷新记录:TapData Oracle 日志同步性能达 80K TPS,重塑实时同步新标准

在当前数据驱动的企业环境中&#xff0c;高效、稳定的数据同步能力已成为支撑关键业务系统的核心需求。尤其在高频变更、大量增量数据的业务场景中&#xff0c;传统的 Oracle 日志解析方案往往在吞吐能力和延迟控制方面力不从心。 随着企业全面迈入“实时化”时代&#xff0c;金…

Java全栈开发面试实战:从基础到高并发的深度解析

Java全栈开发面试实战&#xff1a;从基础到高并发的深度解析 在一次真实的面试中&#xff0c;一位拥有5年全栈开发经验的程序员&#xff0c;面对来自某互联网大厂的技术面试官&#xff0c;展现出了扎实的基础与丰富的项目经验。以下是这次面试的完整记录。 面试官开场 面试官&a…

【mac】如何在 macOS 终端中高效查找文件:五种实用方法

【mac】如何在 macOS 终端中高效查找文件&#xff1a;五种实用方法 在 macOS 上&#xff0c;终端是一个强大的工具&#xff0c;不仅可以执行命令&#xff0c;还能帮助你快速找到需要的文件。无论是按文件名、类型、大小&#xff0c;还是文件内容搜索&#xff0c;都有多种命令可…

React笔记_组件之间进行数据传递

目录父子组件传值- props父传子子传父嵌套组件传值-Context API概念React.createContext APIProvider组件正确示例错误示例消费 ContextReact.Consumer组件useContext Hook区别使用场景举例说明-用户信息状态管理-Redux父子组件传值- props 在React中父子组件传值是单向数据流…

Elixir通过Onvif协议控制IP摄像机,扩展ExOnvif的摄像头停止移动 Stop 功能

ExOnvif官方文档 在使用 Elixir 进行 IPdome 控制时&#xff0c;可以使用 ExOnvif 库。 ExOnvif官方文档中未给停止移动调用命令&#xff0c;自己按照onvif协议 Onvif协议 扩展的此项功能&#xff1b; 停止移动 Stop 在Onvif协议中&#xff0c;用于停止云台移动的操作为Stop…

spring boot autoconfigure 自动配置的类,和手工 @configuration + @bean 本质区别

它们在本质功能上都是为了向 Spring 容器注册 Bean&#xff0c;但在触发方式、加载时机、可控性和适用场景上有明显区别。可以这样理解&#xff1a;1️⃣ 核心区别对比维度Configuration Bean&#xff08;手工配置&#xff09;Spring Boot EnableAutoConfiguration / 自动配置…

论文解读 | Franka 机器人沉浸式远程操作:高斯溅射 VR 赋能的遥操框架研发与应用

研究背景 在工业制造、危险环境作业等领域&#xff0c;机器人远程操作技术是突破人类作业边界的关键手段。传统远程操作依赖2D 相机反馈与操纵杆控制&#xff0c;存在空间感知差、操作精度低、沉浸感弱等问题&#xff0c;难以满足复杂移动操作任务需求。 例如在核设施退役、灾后…

【Unity Shader学习笔记】(四)Shader编程

一、OpenGL与DirectX 这是计算机图形学中两个最核心的应用程序接口(API),它们充当了应用程序与显卡硬件之间的桥梁,让开发者能够调用GPU进行图形渲染和通用计算。 特性维度 OpenGL DirectX 主导公司 Khronos Group (原SGI) Microsoft

程序员之电工基础-初尝线扫相机

一、背景 兴趣爱好来了&#xff0c;决定研发一个产品。涉及到电工和机械等知识&#xff0c;所以记录一下相关的基础知识。本期主题是初尝线扫相机&#xff0c;虽然又回到了编程&#xff0c;但是对于我来说&#xff0c;硬件集成的经验不足&#xff0c;缺乏相机、镜头的专业知识。…

qt QWebSocket详解

1、概述 QWebSocket是Qt网络模块中的一个类&#xff0c;用于实现WebSocket协议的通信。WebSocket是一种全双工的通信协议&#xff0c;允许在客户端和服务器之间建立实时的双向通信。QWebSocket提供了对WebSocket协议的支持&#xff0c;使得开发者能够在Qt应用中方便地实现实时…

Java基础IO流全解析:常用知识点与面试高频考点汇总

Java基础IO流全解析&#xff1a;常用知识点与面试高频考点汇总 前言 IO&#xff08;Input/Output&#xff09;流是Java中处理数据传输的核心机制&#xff0c;无论是文件操作、网络通信还是数据持久化&#xff0c;都离不开IO流的身影。对于Java初学者而言&#xff0c;IO流的分类…

PDF.AI-与你的PDF文档对话

本文转载自&#xff1a;PDF.AI-与你的PDF文档对话 - Hello123工具导航 ** 一、&#x1f916; PDF.AI&#xff1a;秒懂 PDF 的智能对话助手 PDF.AI 是一款超实用的AI 文档分析工具&#xff0c;专门帮你快速搞定各种 PDF 文件。不管多长的合同、报告或论文&#xff0c;你只需上…

微软出品!这个免费开源工具集获得了GitHub 123k程序员点赞

大家晚上好&#xff0c;我是顾北&#xff0c;是一名AI应用探索者&#xff0c;当然也是GitHub开源项目收集爱好者。最近我在整理Windows效率工具时&#xff0c;发现了一个让我一晚上没睡着觉的开源项目——微软官方出品的 PowerToys&#xff0c;可谓是彻夜难眠啊。经过我两个月多…

【开题答辩全过程】以 小众商户小程序为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

Vue 3.5 重磅新特性:useTemplateRef 让模板引用更优雅、更高效!

Vue 3.5 重磅新特性:useTemplateRef 让模板引用更优雅、更高效! 目录 前言 什么是 useTemplateRef 传统 ref 的问题 useTemplateRef 的优势 基础用法 进阶用法 最佳实践 迁移指南 性能对比 注意事项 总结 前言 Vue 3.5 带来了一个激动人心的新特性 useTemplateRef,它彻底革…