樹形組件,支持搜索展示,自定義展示,支持vue2,vue3,小程序等等

效果圖

平臺兼容性

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,它徹底革…