最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Uniapp+Vue3樹形選擇器示例代碼

 更新時(shí)間:2026年04月17日 09:09:01   作者:大陽光男孩  
文章主要介紹了使用UniApp+Vue3進(jìn)行跨平臺(tái)應(yīng)用開發(fā)的優(yōu)勢,包括性能提升、開發(fā)體驗(yàn)優(yōu)化、多端適配能力以及官方支持和生態(tài)完善,通過示例代碼展示了如何利用該組合進(jìn)行高效開發(fā),感興趣的朋友跟隨小編一起看看吧

想要開發(fā)一套代碼就能在多端運(yùn)行的應(yīng)用,用 UniApp + Vue 3 的組合,是目前開發(fā)效率和性能兼得的好選擇。

它不僅讓跨平臺(tái)開發(fā)更高效,也讓代碼本身更現(xiàn)代化、更健壯。下面這份全面的開發(fā)指南,希望能幫你快速上手。

為什么推薦 UniApp + Vue 3?

UniApp + Vue 3 的組合優(yōu)勢可以概括為以下幾點(diǎn):

維度核心優(yōu)勢
?? 性能與效率Vue3版本默認(rèn)使用 Vite 構(gòu)建工具,項(xiàng)目冷啟動(dòng)速度相比Webpack版本可提升 3-5倍。配合 Vue 3 更快的響應(yīng)式系統(tǒng)和更小的運(yùn)行時(shí)體積-,應(yīng)用啟動(dòng)和交互響應(yīng)都更快。
?? 先進(jìn)開發(fā)體驗(yàn)Vue 3 的 組合式 API (Composition API) 能實(shí)現(xiàn)更優(yōu)雅的邏輯復(fù)用與代碼組織,告別邏輯碎片化-。配合 TypeScript,大型項(xiàng)目開發(fā)和維護(hù)更加可靠。
?? 多端適配能力得益于強(qiáng)大的 “條件編譯” 機(jī)制,可以為不同平臺(tái)(如微信小程序、App、H5)編寫專有代碼,在實(shí)現(xiàn)完全跨端的同時(shí),也能進(jìn)行精細(xì)化的平臺(tái)優(yōu)化。
?? 生態(tài)與未來這是官方支持的默認(rèn)技術(shù)棧,當(dāng)前 UniApp 已全面擁抱 Vue 3-,未來也會(huì)持續(xù)更新。社區(qū)主流UI庫(如 uView Plus)和工具鏈都已提供成熟支持-。

下面通過示例代碼給大家詳細(xì)講解,如下所示:

<template>
  <view class="tree-select">
    <!-- 已選標(biāo)簽區(qū)域 + 全選按鈕 -->
    <view v-if="showSelectedTags" class="selected-area">
      <view class="selected-tags">
        <view v-for="item in selectedNodes" :key="item[nodeKey]" class="tag">
          <text class="tag-text">{{ item[labelField] }}</text>
          <text class="tag-close" @click.stop="removeTag(item)">×</text>
        </view>
      </view>
      <view class="action-buttons">
        <view v-if="showSelectAll" class="select-all-btn" @click="toggleSelectAll">
          {{ isAllSelected ? '取消全選' : '全選' }}
        </view>
        <view v-if="selectedNodes.length" class="clear-btn" @click="clearAll">清空</view>
      </view>
    </view>
    <!-- 樹形區(qū)域 -->
    <scroll-view class="tree-scroll" scroll-y>
      <view v-if="flatList.length === 0" class="empty-tip">暫無數(shù)據(jù)</view>
      <view
        v-for="item in flatList"
        :key="item[nodeKey]"
        class="tree-item"
        :style="{ paddingLeft: (item._level * 32 + 24) + 'rpx' }"
      >
        <!-- 展開/折疊圖標(biāo) -->
        <view v-if="item._hasChildren" class="expand-icon" :class="{ expanded: item._expanded }" @click.stop="toggleExpand(item)">
          {{ item._expanded ? '∨' : '>' }}
        </view>
        <view v-else class="expand-placeholder"></view>
        <!-- 復(fù)選框 -->
        <view class="checkbox" :class="{ checked: item._checked, indeterminate: item._indeterminate }" @click.stop="toggleCheck(item)">
          <text v-if="item._checked">?</text>
          <text v-else-if="item._indeterminate">—</text>
        </view>
        <!-- 標(biāo)簽 -->
        <text class="node-label" :class="{ disabled: item[disabledField] }" @click.stop="toggleCheck(item)">
          {{ item[labelField] }}
        </text>
      </view>
    </scroll-view>
  </view>
</template>
<script setup>
	import { ref, computed, watch, onMounted } from 'vue'
	const props = defineProps({
	  modelValue: { type: Array, default: () => [] },
	  treeData: { type: Array, default: () => [] },
	  nodeKey: { type: String, default: 'id' },
	  labelField: { type: String, default: 'label' },
	  childrenField: { type: String, default: 'children' },
	  disabledField: { type: String, default: 'disabled' },
	  expandAll: { type: Boolean, default: false },
	  showSelectedTags: { type: Boolean, default: true },
	  checkStrictly: { type: Boolean, default: false },   // false: 父子聯(lián)動(dòng); true: 獨(dú)立選擇
	  showSelectAll: { type: Boolean, default: true }     // 是否顯示全選按鈕
	})
	const emit = defineEmits(['update:modelValue', 'change'])
	// 內(nèi)部數(shù)據(jù)
	const nodeMap = new Map()
	let allNodes = []
	const flatList = ref([])
	const selectedSet = ref(new Set(props.modelValue))
	// ---------- 構(gòu)建節(jié)點(diǎn)樹 ----------
	function buildChildren(parent, childrenList, level) {
	  for (const raw of childrenList) {
		const node = { ...raw }
		node._level = level
		node._parent = parent
		node._checked = false
		node._indeterminate = false
		node._expanded = props.expandAll ? true : false
		const grandChildren = node[props.childrenField]
		const hasChildren = Array.isArray(grandChildren) && grandChildren.length > 0
		node._hasChildren = hasChildren
		node._children = hasChildren ? grandChildren : []
		nodeMap.set(node[props.nodeKey], node)
		if (hasChildren) {
		  buildChildren(node, grandChildren, level + 1)
		}
	  }
	}
	function initData() {
	  nodeMap.clear()
	  const roots = []
	  for (const raw of props.treeData) {
		const node = { ...raw }
		node._level = 0
		node._parent = null
		node._checked = false
		node._indeterminate = false
		node._expanded = props.expandAll ? true : true
		const childrenRaw = node[props.childrenField]
		const hasChildren = Array.isArray(childrenRaw) && childrenRaw.length > 0
		node._hasChildren = hasChildren
		node._children = hasChildren ? childrenRaw : []
		nodeMap.set(node[props.nodeKey], node)
		roots.push(node)
		if (hasChildren) {
		  buildChildren(node, childrenRaw, 1)
		}
	  }
	  allNodes = Array.from(nodeMap.values())
	  // 根據(jù) modelValue 初始化選中狀態(tài)
	  const keys = props.modelValue || []
	  keys.forEach(key => {
		const node = nodeMap.get(key)
		if (node && !node[props.disabledField]) {
		  if (props.checkStrictly) {
			setChecked(node, true)
		  } else {
			setCheckedCascade(node, true)
		  }
		}
	  })
	  // 更新半選狀態(tài)(僅在聯(lián)動(dòng)模式下)
	  if (!props.checkStrictly) {
		const nodesByLevel = [...allNodes].sort((a,b) => b._level - a._level)
		for (const node of nodesByLevel) {
		  if (node._parent) updateIndeterminate(node._parent)
		}
	  }
	  updateFlatList()
	  emitChange()
	}
	function updateFlatList() {
	  const visible = []
	  function dfs(node) {
		visible.push(node)
		if (node._expanded && node._children.length) {
		  for (const childRaw of node._children) {
			const child = nodeMap.get(childRaw[props.nodeKey])
			if (child) dfs(child)
		  }
		}
	  }
	  for (const node of allNodes) {
		if (node._level === 0 && node._parent === null) {
		  dfs(node)
		}
	  }
	  flatList.value = visible
	}
	function toggleExpand(node) {
	  node._expanded = !node._expanded
	  updateFlatList()
	}
	function setChecked(node, checked) {
	  node._checked = checked
	  if (checked) {
		selectedSet.value.add(node[props.nodeKey])
	  } else {
		selectedSet.value.delete(node[props.nodeKey])
	  }
	}
	function setCheckedCascade(node, checked) {
	  setChecked(node, checked)
	  node._indeterminate = false
	  if (node._children.length) {
		for (const childRaw of node._children) {
		  const child = nodeMap.get(childRaw[props.nodeKey])
		  if (child) setCheckedCascade(child, checked)
		}
	  }
	}
	function updateIndeterminate(node) {
	  if (props.checkStrictly) return
	  const children = node._children
	  if (!children.length) {
		node._indeterminate = false
		return
	  }
	  let checkedCount = 0, indeterminateCount = 0
	  for (const childRaw of children) {
		const child = nodeMap.get(childRaw[props.nodeKey])
		if (child) {
		  if (child._checked) checkedCount++
		  if (child._indeterminate) indeterminateCount++
		}
	  }
	  if (checkedCount === children.length) {
		if (!node._checked) setChecked(node, true)
		node._indeterminate = false
	  } else if (checkedCount === 0 && indeterminateCount === 0) {
		if (node._checked) setChecked(node, false)
		node._indeterminate = false
	  } else {
		if (node._checked) setChecked(node, false)
		node._indeterminate = true
	  }
	}
	function updateAncestors(node) {
	  if (props.checkStrictly) return
	  let p = node._parent
	  while (p) {
		updateIndeterminate(p)
		p = p._parent
	  }
	}
	function toggleCheck(node) {
	  if (node[props.disabledField]) return
	  if (props.checkStrictly) {
		const newVal = !node._checked
		setChecked(node, newVal)
		node._indeterminate = false
	  } else {
		const newVal = !node._checked
		setCheckedCascade(node, newVal)
		updateAncestors(node)
	  }
	  updateFlatList()
	  emitChange()
	}
	// 全選/取消全選(統(tǒng)一處理兩種模式)
	function toggleSelectAll() {
	  const shouldSelect = !isAllSelected.value
	  // 獲取所有可選節(jié)點(diǎn)(未禁用)
	  const selectableNodes = allNodes.filter(n => !n[props.disabledField])
	  if (props.checkStrictly) {
		// 嚴(yán)格模式:直接設(shè)置每個(gè)節(jié)點(diǎn)的選中狀態(tài)
		for (const node of selectableNodes) {
		  setChecked(node, shouldSelect)
		  node._indeterminate = false
		}
	  } else {
		// 聯(lián)動(dòng)模式:為避免重復(fù)級(jí)聯(lián),先清除所有選中,再設(shè)置根節(jié)點(diǎn)的選中狀態(tài)(級(jí)聯(lián)會(huì)帶動(dòng)子節(jié)點(diǎn))
		// 但更高效的方式是直接設(shè)置所有節(jié)點(diǎn)的 _checked 為 shouldSelect,然后重新計(jì)算半選狀態(tài)。
		// 因?yàn)槁?lián)動(dòng)模式下全選時(shí),所有節(jié)點(diǎn)都應(yīng)該選中,且沒有半選狀態(tài)。
		// 直接設(shè)置所有節(jié)點(diǎn) _checked 和 _indeterminate
		for (const node of allNodes) {
		  node._checked = shouldSelect
		  node._indeterminate = false
		}
		// 更新 selectedSet
		selectedSet.value.clear()
		if (shouldSelect) {
		  for (const node of allNodes) {
			if (node._checked) selectedSet.value.add(node[props.nodeKey])
		  }
		}
		// 由于直接設(shè)置了所有節(jié)點(diǎn),無需再調(diào)用 setCheckedCascade,但需要更新選中集合
		// 直接調(diào)用 emitChange 即可
		emitChange()
		updateFlatList()
		return
	  }
	  emitChange()
	  updateFlatList()
	}
	// 判斷是否全選(所有可選節(jié)點(diǎn)都被選中)
	const isAllSelected = computed(() => {
	  const selectableNodes = allNodes.filter(n => !n[props.disabledField])
	  if (selectableNodes.length === 0) return false
	  return selectableNodes.every(n => n._checked)
	})
	function emitChange() {
	  const keys = []
	  for (const node of allNodes) {
		if (node._checked) keys.push(node[props.nodeKey])
	  }
	  selectedSet.value.clear()
	  keys.forEach(k => selectedSet.value.add(k))
	  emit('update:modelValue', keys)
	  const selectedObjs = keys.map(k => nodeMap.get(k)).filter(Boolean)
	  emit('change', keys, selectedObjs)
	}
	function removeTag(node) {
	  if (props.checkStrictly) {
		setChecked(node, false)
		node._indeterminate = false
	  } else {
		setCheckedCascade(node, false)
		updateAncestors(node)
	  }
	  updateFlatList()
	  emitChange()
	}
	function clearAll() {
	  for (const node of allNodes) {
		node._checked = false
		node._indeterminate = false
	  }
	  updateFlatList()
	  emitChange()
	}
	const selectedNodes = computed(() => {
	  return Array.from(selectedSet.value)
		.map(key => nodeMap.get(key))
		.filter(Boolean)
	})
	watch(() => props.treeData, () => {
	  initData()
	}, { deep: true, immediate: true })
	watch(() => props.modelValue, (newVal) => {
	  const newSet = new Set(newVal)
	  if (newSet.size !== selectedSet.value.size ||
		  !Array.from(newSet).every(k => selectedSet.value.has(k))) {
		initData()
	  }
	}, { deep: true })
	onMounted(() => {
	  initData()
	})
</script>
<style lang="scss" scoped>
	.tree-select {
	  width: 100%;
	  background: #fff;
	  border-radius: 12rpx;
	  overflow: hidden;
	  .selected-area {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		padding: 20rpx 24rpx;
		border-bottom: 1rpx solid #eee;
		background: #fafafa;
		.selected-tags {
		  flex: 1;
		  display: flex;
		  flex-wrap: wrap;
		  gap: 16rpx;
		  .tag {
			display: inline-flex;
			align-items: center;
			background: #e8f4ff;
			border-radius: 8rpx;
			padding: 8rpx 16rpx;
			font-size: 24rpx;
			color: #2979ff;
			.tag-text {
			  max-width: 200rpx;
			  overflow: hidden;
			  text-overflow: ellipsis;
			  white-space: nowrap;
			}
			.tag-close {
			  margin-left: 8rpx;
			  font-size: 32rpx;
			  line-height: 1;
			  color: #999;
			  font-weight: bold;
			  &:active { color: #666; }
			}
		  }
		}
		.action-buttons {
		  display: flex;
		  gap: 20rpx;
		  .select-all-btn, .clear-btn {
			padding: 8rpx 16rpx;
			font-size: 24rpx;
			color: #2979ff;
			background: #e8f4ff;
			border-radius: 8rpx;
			&:active { opacity: 0.7; }
		  }
		  .clear-btn {
			color: #999;
			background: #f0f0f0;
		  }
		}
	  }
	  .tree-scroll {
		max-height: 500rpx;
		overflow-y: auto;
	  }
	  .empty-tip {
		text-align: center;
		padding: 60rpx 0;
		color: #999;
		font-size: 28rpx;
	  }
	  .tree-item {
		display: flex;
		align-items: center;
		padding: 20rpx 0;
		border-bottom: 1rpx solid #f5f5f5;
		.expand-placeholder {
		  width: 48rpx;
		  height: 48rpx;
		  flex-shrink: 0;
		}
		.expand-icon {
		  width: 48rpx;
		  height: 48rpx;
		  flex-shrink: 0;
		  display: flex;
		  align-items: center;
		  justify-content: center;
		  font-size: 32rpx;
		  color: #666;
		  &.expanded {
			transform: rotate(0deg);
		  }
		}
		.checkbox {
		  width: 40rpx;
		  height: 40rpx;
		  flex-shrink: 0;
		  border-radius: 6rpx;
		  margin-right: 16rpx;
		  display: flex;
		  align-items: center;
		  justify-content: center;
		  font-size: 28rpx;
		  font-weight: bold;
		  background: #fff;
		  border: 2rpx solid #ddd;
		  &.checked {
			background: #2979ff;
			border-color: #2979ff;
			color: #fff;
		  }
		  &.indeterminate {
			background: #2979ff;
			border-color: #2979ff;
			color: #fff;
			font-size: 32rpx;
		  }
		}
		.node-label {
		  flex: 1;
		  font-size: 28rpx;
		  color: #333;
		  &.disabled {
			color: #ccc;
		  }
		}
	  }
	}
</style>

使用代碼

<template>
  <TreeSelect
    v-model="selectedIds"
    :tree-data="menuTree"
    node-key="id"
    label-field="name"
    children-field="children"
    :check-strictly="true"
    :show-select-all="true"
  />
</template>
<script setup>
import { ref } from 'vue'
import TreeSelect from "@/component/TreeSelect.vue";
const menuTree = ref([
  { id: 1, name: '總部', children: [
    { id: 11, name: '研發(fā)部' },
    { id: 12, name: '市場部', children: [
      { id: 121, name: '廣告組' }
    ]}
  ]},
  { id: 2, name: '分公司', children: [
    { id: 21, name: '銷售部' }
  ]}
])
const selectedIds = ref([1]);
</script>

到此這篇關(guān)于Uniapp+Vue3樹形選擇器示例代碼的文章就介紹到這了,更多相關(guān)Uniapp Vue樹形選擇器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue3使用echarts繪制儀表盤

    Vue3使用echarts繪制儀表盤

    這篇文章主要為大家學(xué)習(xí)介紹了Vue3如何使用echarts實(shí)現(xiàn)繪制儀表盤,文中的示例代碼積極學(xué)習(xí),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-07-07
  • Vue3+Element Plus項(xiàng)目中使用html2canvas截圖方式

    Vue3+Element Plus項(xiàng)目中使用html2canvas截圖方式

    這篇文章主要介紹了Vue3+Element Plus項(xiàng)目中使用html2canvas截圖方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2026-03-03
  • 使用vue腳手架(vue-cli)搭建一個(gè)項(xiàng)目詳解

    使用vue腳手架(vue-cli)搭建一個(gè)項(xiàng)目詳解

    這篇文章主要介紹了vue腳手架(vue-cli)搭建項(xiàng)目,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • Vue組件如何設(shè)置Props實(shí)例詳解

    Vue組件如何設(shè)置Props實(shí)例詳解

    props主要用于組件的傳值,他的工作就是為了接收外面?zhèn)鬟^來的數(shù)據(jù),與data、el、ref是一個(gè)級(jí)別的配置項(xiàng),下面這篇文章主要給大家介紹了關(guān)于Vue組件如何設(shè)置Props的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • 解決vue.js在編寫過程中出現(xiàn)空格不規(guī)范報(bào)錯(cuò)的問題

    解決vue.js在編寫過程中出現(xiàn)空格不規(guī)范報(bào)錯(cuò)的問題

    下面小編就為大家?guī)硪黄鉀Qvue.js在編寫過程中出現(xiàn)空格不規(guī)范報(bào)錯(cuò)的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • Vue 解決路由過渡動(dòng)畫抖動(dòng)問題(實(shí)例詳解)

    Vue 解決路由過渡動(dòng)畫抖動(dòng)問題(實(shí)例詳解)

    這篇文章主要介紹了Vue 解決路由過渡動(dòng)畫抖動(dòng)問題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Vue動(dòng)畫事件詳解及過渡動(dòng)畫實(shí)例

    Vue動(dòng)畫事件詳解及過渡動(dòng)畫實(shí)例

    通過 Vue.js 的過渡系統(tǒng),可以在元素從 DOM 中插入或移除時(shí)自動(dòng)應(yīng)用過渡效果。Vue.js 會(huì)在適當(dāng)?shù)臅r(shí)機(jī)為你觸發(fā) CSS 過渡或動(dòng)畫,你也可以提供相應(yīng)的 JavaScript 鉤子函數(shù)在過渡過程中執(zhí)行自定義的 DOM 操作
    2019-02-02
  • Create?vite理解Vite項(xiàng)目創(chuàng)建流程及代碼實(shí)現(xiàn)

    Create?vite理解Vite項(xiàng)目創(chuàng)建流程及代碼實(shí)現(xiàn)

    這篇文章主要為大家介紹了Create?vite理解Vite項(xiàng)目創(chuàng)建流程及代碼實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • 詳解Vue中的keep-alive

    詳解Vue中的keep-alive

    這篇文章主要為大家介紹了Vue中的keep-alive,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • 前端利用vue實(shí)現(xiàn)導(dǎo)入和導(dǎo)出功能詳細(xì)代碼

    前端利用vue實(shí)現(xiàn)導(dǎo)入和導(dǎo)出功能詳細(xì)代碼

    最近項(xiàng)目中讓實(shí)現(xiàn)一個(gè)導(dǎo)入導(dǎo)出Excel的功能,下面這篇文章主要給大家介紹了關(guān)于前端利用vue實(shí)現(xiàn)導(dǎo)入和導(dǎo)出功能的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-09-09

最新評(píng)論

北碚区| 壤塘县| 沙坪坝区| 东辽县| 高邑县| 阿拉善盟| 巴彦淖尔市| 沭阳县| 昌图县| 什邡市| 南澳县| 台南县| 闽清县| 边坝县| 镇安县| 宝丰县| 蓝山县| 红原县| 电白县| 察雅县| 龙南县| 兴山县| 墨竹工卡县| 余江县| 凤台县| 三台县| 迭部县| 乃东县| 合水县| 自贡市| 四平市| 咸丰县| 当阳市| 三都| 长垣县| 大安市| 镇康县| 晋城| 福贡县| 静海县| 白山市|