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

Vue3中Tree樹組件開發(fā)文檔(附使用示例)

 更新時間:2026年03月11日 11:09:46   作者:前端趙哈哈  
在Vue中樹形組件是一種用于展示層級結構數(shù)據(jù)的UI組件,常用于展示如文件目錄、組織架構等具有層級關系的數(shù)據(jù),這篇文章主要介紹了Vue3中Tree樹組件開發(fā)文檔的相關資料,需要的朋友可以參考下

一、組件概述

這個樹組件基于 Vue3 和 Composition API 構建,提供了強大的節(jié)點選擇功能,支持單選、多選模式,以及父子節(jié)點關聯(lián)或獨立的選擇邏輯。組件設計靈活,可定制性強,適用于多種場景。

二、核心功能

1. 選擇模式

  • 單選模式:每次只能選中一個節(jié)點,點擊已選中節(jié)點會取消選擇
  • 多選模式:可以同時選中多個節(jié)點,通過復選框或點擊節(jié)點進行操作

2. 父子節(jié)點關聯(lián)

  • 關聯(lián)模式:父節(jié)點選中時,所有子節(jié)點自動選中;子節(jié)點全部選中時,父節(jié)點自動選中
  • 獨立模式:父子節(jié)點選擇狀態(tài)互不影響,可單獨控制

3. 節(jié)點操作

  • 點擊節(jié)點:根據(jù)配置可觸發(fā)選擇操作
  • 展開 / 折疊:可通過圖標控制節(jié)點的展開與折疊狀態(tài)
  • 禁用節(jié)點:支持設置某些節(jié)點為禁用狀態(tài),不可選擇

三、組件結構

1. 主要組件

  • Tree.vue:樹組件主體,負責整體結構和狀態(tài)管理
  • TreeNode.vue:樹節(jié)點組件,負責單個節(jié)點的渲染和交互

2. 核心屬性

  • treeData:樹結構數(shù)據(jù),包含節(jié)點的 id、標簽、子節(jié)點等信息
  • modelValue:雙向綁定的選中節(jié)點值
  • keyMap:自定義節(jié)點數(shù)據(jù)的鍵名映射
  • config:配置項,控制多選、父子關聯(lián)等功能

四、使用示例

1. 基礎用法

<Tree
  :treeData="treeData"
  v-model="checkedKeys"
  :keyMap="keyMap"
  :config="config"
/>

2. 完整示例代碼

index.vue

<template>
  <div class="container">
    <h3>樹組件示例</h3>
    <div class="options">
      <label>
        <input type="checkbox" v-model="config.multiple" /> 多選模式
      </label>
      <label>
        <input type="checkbox" v-model="config.checkStrictly" /> 父子節(jié)點不關聯(lián)
      </label>
      <label>
        <input type="checkbox" v-model="config.expandAll" /> 全部展開
      </label>
      <label>
        <input type="checkbox" v-model="config.showCheckbox" /> 顯示復選框
      </label>
      <button @click="getAllCheckedNodes">獲取所有選中節(jié)點</button>
    </div>
    <div class="tree-wrapper">
      <Tree
        ref="treeRef"
        :treeData="treeData"
        v-model="checkedKeys"
        :keyMap="keyMap"
        :config="config"
        @node-check="handleNodeCheck"
      />
    </div>
    <div class="selected-result">
      <h4>選中結果:</h4>
      <pre>{{ checkedKeys }}</pre>
    </div>
    <div class="last-selected">
      <h4>最后操作節(jié)點:</h4>
      <pre v-if="lastSelectedNode">{{ lastSelectedNode[keyMap.id] }} - {{ lastSelectedNode[keyMap.label] }} ({{ lastSelectedStatus ? '選中' : '取消選中' }})</pre>
      <p v-else>無操作</p>
    </div>
    <div class="all-checked-nodes" v-if="allCheckedNodes.length > 0">
      <h4>所有選中節(jié)點:</h4>
      <pre>{{ allCheckedNodes }}</pre>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import Tree from './Tree.vue';

const treeRef = ref(null);
const allCheckedNodes = ref([]);

const treeData = ref([
  {
    id: 1,
    name: '一級節(jié)點1',
    children: [
      {
        id: 4,
        name: '二級節(jié)點1-1',
        children: [
          { id: 9, name: '三級節(jié)點1-1-1', disabled: true },
          { id: 10, name: '三級節(jié)點1-1-2' }
        ]
      },
      { id: 5, name: '二級節(jié)點1-2' }
    ]
  },
  {
    id: 2,
    name: '一級節(jié)點2',
    children: [
      { id: 6, name: '二級節(jié)點2-1' },
      { id: 7, name: '二級節(jié)點2-2' }
    ]
  },
  {
    id: 3,
    name: '一級節(jié)點3',
    disabled: true
  }
]);

const checkedKeys = ref([4, 7]);

const keyMap = ref({
  id: 'id',
  label: 'name',
  children: 'children',
  disabled: 'disabled'
});

const config = ref({
  multiple: true,
  checkStrictly: false,
  expandAll: false,
  showCheckbox: true
});

const lastSelectedNode = ref(null);
const lastSelectedStatus = ref(false);

const handleNodeCheck = (node, isChecked) => {
  lastSelectedNode.value = node;
  lastSelectedStatus.value = isChecked;
  console.log('節(jié)點勾選變化:', node, '選中狀態(tài):', isChecked);
};

const getAllCheckedNodes = () => {
  if (treeRef.value) {
    try {
      allCheckedNodes.value = treeRef.value.getCheckedNodes();
      console.log('所有選中節(jié)點:', allCheckedNodes.value);
    } catch (error) {
      console.error('獲取選中節(jié)點時出錯:', error);
      alert('獲取選中節(jié)點時出錯: ' + error.message);
    }
  }
};
</script>

<style>
.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.options {
  margin-bottom: 20px;
  display: flex;
  gap: 15px;
  flex-wrap: wrap;
  align-items: center;
}

button {
  padding: 6px 12px;
  background-color: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s;
}

button:hover {
  background-color: #66b1ff;
}

.tree-wrapper {
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  margin-bottom: 20px;
  max-height: 400px;
  overflow-y: auto;
}

.selected-result, .last-selected, .all-checked-nodes {
  background-color: #f9fafb;
  padding: 15px;
  border-radius: 4px;
  margin-bottom: 15px;
}
</style>

Tree.vue

<script setup>
import { computed, defineComponent, ref, watch } from 'vue'
import TreeNode from './TreeNode.vue'

const props = defineProps({
  treeData: {
    type: Array,
    required: true,
  },
  modelValue: {
    type: [Array, String, Number, null],
    default: () => [],
  },
  keyMap: {
    type: Object,
    default: () => ({
      id: 'id',
      label: 'label',
      children: 'children',
      disabled: 'disabled',
    }),
  },
  config: {
    type: Object,
    default: () => ({
      multiple: false,
      checkStrictly: false,
      expandAll: false,
      showCheckbox: true,
    }),
  },
})

const emit = defineEmits(['update:modelValue', 'nodeCheck'])

function handleCheckChange(value) {
  emit('update:modelValue', value)
}

function handleNodeCheck(node, isChecked) {
  emit('nodeCheck', node, isChecked)
}

// 獲取所有選中節(jié)點對象的方法
function getCheckedNodes() {
  const checkedIds = Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue].filter(id => id !== null)
  const result = []

  const traverse = (nodes) => {
    nodes.forEach((node) => {
      if (checkedIds.includes(node[props.keyMap.id])) {
        // 創(chuàng)建節(jié)點的副本,避免循環(huán)引用
        const cleanNode = { ...node }
        if (cleanNode.parent) {
          // 移除parent引用,避免循環(huán)
          delete cleanNode.parent
        }
        if (cleanNode[props.keyMap.children]) {
          // 遞歸處理子節(jié)點
          cleanNode[props.keyMap.children] = cleanNode[props.keyMap.children].map((child) => {
            const cleanChild = { ...child }
            delete cleanChild.parent
            return cleanChild
          })
        }
        result.push(cleanNode)
      }

      if (node[props.keyMap.children] && node[props.keyMap.children].length > 0) {
        traverse(node[props.keyMap.children])
      }
    })
  }

  traverse(props.treeData)
  return result
}

// 監(jiān)聽配置變化,當從多選切換到單選時清空選中狀態(tài)
watch(() => props.config.multiple, (newVal, oldVal) => {
  if (oldVal && !newVal) {
    // 從多選切換到單選,清空選中狀態(tài)
    emit('update:modelValue', null)
  }
})

// 提供對外方法
defineExpose({
  getCheckedNodes,
})
</script>

<template>
  <div class="tree-container">
    <ul class="tree-list">
      <TreeNode
        v-for="node in treeData"
        :key="node[keyMap.id]"
        :node="node"
        :key-map="keyMap"
        :config="config"
        :checked-keys="modelValue"
        @update:model-value="handleCheckChange"
        @node-check="handleNodeCheck"
      />
    </ul>
  </div>
</template>

  <style scoped>
  .tree-container {
  padding: 8px;
  border-radius: 4px;
}

.tree-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
</style>

TreeNode.vue

<script setup>
import { computed, defineComponent, ref, watch } from 'vue'

const props = defineProps({
  node: {
    type: Object,
    required: true,
  },
  keyMap: {
    type: Object,
    required: true,
  },
  config: {
    type: Object,
    required: true,
  },
  checkedKeys: {
    type: [Array, String, Number, null],
    default: () => [],
  },
})

const emit = defineEmits(['update:modelValue', 'nodeCheck'])

const isExpanded = ref(props.config.expandAll)
const hasChildren = computed(() => Array.isArray(props.node[props.keyMap.children]) && props.node[props.keyMap.children].length > 0)
const isDisabled = computed(() => props.node[props.keyMap.disabled] === true)

// 節(jié)點選中狀態(tài)
const isChecked = computed({
  get() {
    if (props.config.multiple) {
      return Array.isArray(props.checkedKeys) && props.checkedKeys.includes(props.node[props.keyMap.id])
    }
    else {
      return props.checkedKeys === props.node[props.keyMap.id]
    }
  },
  set(value) {
    handleCheck(value)
  },
})

// 計算子節(jié)點選中狀態(tài)
const childrenCheckedState = computed(() => {
  if (!hasChildren.value || !props.config.multiple)
    return 'none'

  const children = props.node[props.keyMap.children]
  const checkedCount = children.filter(child =>
    Array.isArray(props.checkedKeys) && props.checkedKeys.includes(child[props.keyMap.id]),
  ).length

  if (checkedCount === 0)
    return 'none'
  if (checkedCount === children.length)
    return 'all'
  return 'partial'
})

// 父子關聯(lián)邏輯
function handleCheck(newState) {
  if (isDisabled.value)
    return

  const nodeId = props.node[props.keyMap.id]
  let newCheckedKeys

  // 選中狀態(tài)切換
  const isNodeChecked = isChecked.value
  const shouldCheck = newState !== undefined ? newState : !isNodeChecked

  if (props.config.multiple) {
    // 多選模式
    newCheckedKeys = Array.isArray(props.checkedKeys) ? [...props.checkedKeys] : []

    // 多選模式且父子關聯(lián)
    if (!props.config.checkStrictly) {
      // 更新子節(jié)點
      const updateChildren = (children, checkState) => {
        children.forEach((child) => {
          if (!child[props.keyMap.disabled]) {
            const childId = child[props.keyMap.id]

            if (checkState) {
              if (!newCheckedKeys.includes(childId)) {
                newCheckedKeys.push(childId)
              }
            }
            else {
              newCheckedKeys = newCheckedKeys.filter(id => id !== childId)
            }

            if (child[props.keyMap.children] && child[props.keyMap.children].length > 0) {
              updateChildren(child[props.keyMap.children], checkState)
            }
          }
        })
      }

      // 更新父節(jié)點
      const updateParent = (node, checkState) => {
        if (!node.parent)
          return

        const parentId = node.parent[props.keyMap.id]
        const siblings = node.parent[props.keyMap.children]

        // 檢查所有兄弟節(jié)點的選中狀態(tài)
        const allSiblingsChecked = siblings.every(sib =>
          sib[props.keyMap.disabled] || newCheckedKeys.includes(sib[props.keyMap.id]),
        )

        const noSiblingsChecked = siblings.every(sib =>
          sib[props.keyMap.disabled] || !newCheckedKeys.includes(sib[props.keyMap.id]),
        )

        // 如果所有兄弟都被選中,則父節(jié)點也應被選中
        // 如果沒有兄弟被選中,則父節(jié)點也應被取消選中
        if (allSiblingsChecked) {
          if (!newCheckedKeys.includes(parentId)) {
            newCheckedKeys.push(parentId)
            emit('nodeCheck', node.parent, true)
          }
        }
        else if (noSiblingsChecked) {
          newCheckedKeys = newCheckedKeys.filter(id => id !== parentId)
          emit('nodeCheck', node.parent, false)
        }

        // 遞歸更新上層父節(jié)點
        updateParent(node.parent, checkState)
      }

      // 處理子節(jié)點
      if (hasChildren.value) {
        updateChildren(props.node[props.keyMap.children], shouldCheck)
      }

      // 處理父節(jié)點
      if (props.node.parent) {
        updateParent(props.node, shouldCheck)
      }
    }

    // 更新當前節(jié)點
    if (shouldCheck) {
      if (!newCheckedKeys.includes(nodeId)) {
        newCheckedKeys.push(nodeId)
      }
    }
    else {
      newCheckedKeys = newCheckedKeys.filter(id => id !== nodeId)
    }
  }
  else {
    // 單選模式
    newCheckedKeys = shouldCheck ? nodeId : null
  }

  emit('update:modelValue', newCheckedKeys)
  emit('nodeCheck', props.node, shouldCheck)
}

function toggleExpand() {
  if (!isDisabled.value) {
    isExpanded.value = !isExpanded.value
  }
}

function handleNodeClick() {
  if (!props.config.showCheckbox && !isDisabled.value) {
    handleCheck()
  }
}

function handleChildCheckChange(newCheckedKeys) {
  emit('update:modelValue', newCheckedKeys)
}

function handleChildNodeCheck(node, isChecked) {
  emit('nodeCheck', node, isChecked)
}

// 添加父節(jié)點引用
watch(() => props.node[props.keyMap.children], (children) => {
  if (children && children.length > 0) {
    children.forEach((child) => {
      child.parent = props.node
    })
  }
}, { immediate: true, deep: true })

watch(() => props.config.expandAll, (newVal) => {
  isExpanded.value = newVal
})
</script>

<template>
  <li class="tree-node">
    <div
      class="node-content"
      :class="{
        'node-expanded': isExpanded,
        'node-selected': isChecked,
        'node-disabled': isDisabled,
      }"
    >
      <span
        v-if="hasChildren"
        class="expand-icon"
        @click.stop="toggleExpand"
      >
        {{ isExpanded ? '?' : '?' }}
      </span>
      <input
        v-if="config.showCheckbox"
        type="checkbox"
        :checked="isChecked"
        :disabled="isDisabled"
        @change.stop="handleCheck"
      >
      <span class="node-label" @click.stop="handleNodeClick">{{ node[keyMap.label] }}</span>
    </div>
    <ul
      v-if="hasChildren && isExpanded"
      class="children-list"
    >
      <TreeNode
        v-for="child in node[keyMap.children]"
        :key="child[keyMap.id]"
        :node="child"
        :key-map="keyMap"
        :config="config"
        :checked-keys="checkedKeys"
        @update:model-value="handleChildCheckChange"
        @node-check="handleChildNodeCheck"
      />
    </ul>
  </li>
</template>

  <style scoped>
  .tree-node {
  margin: 4px 0;
}

.node-content {
  display: flex;
  align-items: center;
  padding: 4px 8px;
  border-radius: 3px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.node-content:hover:not(.node-disabled) {
  background-color: #f5f7fa;
}

.node-expanded .expand-icon {
  transform: rotate(90deg);
}

.expand-icon {
  display: inline-block;
  width: 16px;
  text-align: center;
  margin-right: 4px;
  transition: transform 0.2s;
}

.node-label {
  margin-left: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.node-selected {
  background-color: #e6f7ff;
}

.node-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.children-list {
  list-style: none;
  padding-left: 20px;
  margin: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-in-out;
}
</style>

五、配置詳解

const config = {
  multiple: true,        // 是否啟用多選模式
  checkStrictly: false,  // 是否父子節(jié)點不關聯(lián)
  expandAll: false,      // 是否全部展開
  showCheckbox: true     // 是否顯示復選框
};

3. 鍵名映射

const keyMap = {
  id: 'id',              // 節(jié)點ID的鍵名
  label: 'name',         // 節(jié)點顯示文本的鍵名
  children: 'children',  // 子節(jié)點數(shù)組的鍵名
  disabled: 'disabled'   // 禁用狀態(tài)的鍵名
};

六、事件處理

<Tree @node-check="handleNodeCheck" />
const handleNodeCheck = (node, isChecked) => {
  // node: 被操作的節(jié)點對象
  // isChecked: 當前的選中狀態(tài) (true/false)
  console.log('節(jié)點勾選變化:', node, '選中狀態(tài):', isChecked);
};

2. 獲取所有選中節(jié)點

const getAllCheckedNodes = () => {
  if (treeRef.value) {
    const nodes = treeRef.value.getCheckedNodes();
    console.log('所有選中節(jié)點:', nodes);
  }
};

七、常見問題與解決方案

1. 多選模式切換問題

  • 問題描述:從多選切換到單選時,之前的選中狀態(tài)未清除
  • 解決方案:組件已自動處理,切換模式時會清空選中狀態(tài)

2. 循環(huán)引用錯誤

  • 問題描述:調(diào)用getCheckedNodes方法時出現(xiàn)循環(huán)引用錯誤
  • 解決方案:組件內(nèi)部已處理,返回的節(jié)點數(shù)據(jù)不包含循環(huán)引用

3. 父子節(jié)點關聯(lián)邏輯

  • 問題描述:父節(jié)點與子節(jié)點的選擇狀態(tài)不一致
  • 解決方案:確保checkStrictly配置正確,關聯(lián)模式下會自動同步父子節(jié)點狀態(tài)

八、擴展與定制

1. 自定義節(jié)點樣式

可以通過修改TreeNode.vue中的樣式類來定制節(jié)點外觀,也可以添加自定義類。

2. 添加新功能

可以在組件基礎上擴展新功能,如搜索過濾、拖拽排序等。

3. 事件擴展

可以添加新的事件,如節(jié)點展開 / 折疊事件、右鍵菜單事件等。

九、性能考慮

  • 對于大型樹結構,建議實現(xiàn)虛擬滾動以提高性能
  • 避免頻繁更新整個樹數(shù)據(jù),盡量局部更新
  • 可以考慮添加節(jié)點懶加載功能,減少初始渲染壓力

到此這篇關于Vue3中Tree樹組件開發(fā)文檔的文章就介紹到這了,更多相關Vue3 Tree樹組件開發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Vue或者React項目配置@路徑別名及智能提示方案

    Vue或者React項目配置@路徑別名及智能提示方案

    這篇文章主要介紹了Vue或者React項目配置@路徑別名及智能提示方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • 基于腳手架創(chuàng)建Vue項目實現(xiàn)步驟詳解

    基于腳手架創(chuàng)建Vue項目實現(xiàn)步驟詳解

    這篇文章主要介紹了基于腳手架創(chuàng)建Vue項目實現(xiàn)步驟詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-08-08
  • 詳解Vue-cli 創(chuàng)建的項目如何跨域請求

    詳解Vue-cli 創(chuàng)建的項目如何跨域請求

    本篇文章主要介紹了詳解Vue-cli 創(chuàng)建的項目如何跨域請求 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • vue-router跳轉(zhuǎn)頁面的方法

    vue-router跳轉(zhuǎn)頁面的方法

    本篇文章主要介紹了vue-router跳轉(zhuǎn)頁面的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • Vant主題定制如何修改顏色樣式

    Vant主題定制如何修改顏色樣式

    這篇文章主要介紹了Vant主題定制如何修改顏色樣式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • 前端vue2使用國密SM4進行加密、解密具體方法

    前端vue2使用國密SM4進行加密、解密具體方法

    SM4是一種對稱加密算法,類似于AES,主要用于數(shù)據(jù)的批量加密,如文件加密、數(shù)據(jù)庫加密、網(wǎng)絡通信數(shù)據(jù)加密等,這篇文章主要給大家介紹了關于前端vue2使用國密SM4進行加密、解密的相關資料,需要的朋友可以參考下
    2024-07-07
  • Vue前端高效分包指南

    Vue前端高效分包指南

    在 Vue 項目中,隨著業(yè)務迭代,代碼體積會像冬天的羽絨服一樣越來越膨脹,如果不加以控制,單個體積過大的JS文件會導致頁面加載緩慢、用戶體驗下降,甚至影響轉(zhuǎn)化率,今天就帶大家走進前端分包的奇妙世界,用實戰(zhàn)經(jīng)驗告訴你如何優(yōu)雅地拆分代碼包,需要的朋友可以參考下
    2025-08-08
  • 淺析前端路由簡介以及vue-router實現(xiàn)原理

    淺析前端路由簡介以及vue-router實現(xiàn)原理

    路由就是用來跟后端服務器進行交互的一種方式,通過不同的路徑,來請求不同的資源,請求不同的頁面是路由的其中一種功能。這篇文章主要介紹了前端路由簡介以及vue-router實現(xiàn)原理,需要的朋友可以參考下
    2018-06-06
  • 解決修改el-pagination顯示文字的問題

    解決修改el-pagination顯示文字的問題

    這篇文章主要介紹了解決修改el-pagination顯示文字的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • vue下載二進制流圖片操作

    vue下載二進制流圖片操作

    這篇文章主要介紹了vue下載二進制流圖片操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10

最新評論

龙泉市| 富平县| 宣汉县| 靖江市| 定日县| 泉州市| 荔波县| 锡林浩特市| 大荔县| 公主岭市| 深水埗区| 文化| 三明市| 信阳市| 通城县| 吴堡县| 壶关县| 长岭县| 崇仁县| 江永县| 辉南县| 海城市| 棋牌| 广汉市| 扶沟县| 江都市| 铜川市| 兴仁县| 惠来县| 福建省| 北宁市| 桂阳县| 永修县| 玉环县| 壤塘县| 云梦县| 开化县| 丰台区| 金溪县| 彰化县| 定南县|