Vue3 通過作用域插槽實現樹形菜單嵌套組件

一、需求來源

工作中需要一種樹形菜單組件,經過兩天的構思最終通過作用域插槽實現: 此組件將每個節點(插槽名為 node)暴露出來。

通過插槽的 attributes 向當前插槽節點傳遞子項 item(數據對象)和level(層深)參數,在保持組件內部極簡的同時支持在數據模型中擴展性。基本達到比較好的封裝顆粒度,大傢可以在此基礎上無限擴展封裝具體的業務邏輯。

二、效果圖

let list = reactive(
  [{ 
    name:'1 一級菜單',
    isExpand: true,//是否展開子項
    enabled: false,//是否可以響應事件
    child:[
      { name:'1.1 二級菜單',     
        isExpand: true,
        child:[
          { name:'1.1.1 三級菜單', isExpand: true, },
        ]
      },
      { name:'1.2 二級菜單', isExpand: true, },
    ]
  },
  { 
    name:'1.1 一級菜單',
    isExpand: true,
    child:[
      { name:'1.1.1 二級菜單', isExpand: true, },
      { name:'1.1.2 二級菜單', 
        isExpand: false, 
        child:[
          { name:'1.1.2.1 三級菜單', isExpand: true, },
        ]},
    ]
  },]
);

三、使用示例(VTreeNodeDemo.vue)

<template>
  <VTreeNode 
    :list="list"
    :level="level"
  >
    <template #node="slotProps">
      <div class="tree-node">
        {{prefix(slotProps.level)}}{{slotProps.item.name}}{{sufix(slotProps.item)}}
      </div>
    </template>
  </VTreeNode>
</template>
<script setup>
import VTreeNode from '@/components/VTreeNode/VTreeNode.vue';
import { ref, reactive, watch, onMounted, } from 'vue';
let list = reactive(
  [{ 
    name:'1 一級菜單',
    isExpand: true,//是否展開子項
    enabled: false,//是否可以響應事件
    child:[
      { name:'1.1 二級菜單',     
        isExpand: true,
        child:[
          { name:'1.1.1 三級菜單', isExpand: true, },
        ]
      },
      { name:'1.2 二級菜單', isExpand: true, },
    ]
  },
  { 
    name:'1.1 一級菜單',
    isExpand: true,
    child:[
      { name:'1.1.1 二級菜單', isExpand: true, },
      { name:'1.1.2 二級菜單', 
        isExpand: false, 
        child:[
          { name:'1.1.2.1 三級菜單', isExpand: true, },
        ]},
    ]
  },]
);
const level = ref(0);
const prefix = (count) => {
  return '__'.repeat(count);
};
const sufix = (item) => {
  if (!Reflect.has(item, 'child')) {
    return '';
  }
  return ` (${item.child.length}子項)`;
};
</script>
<style scoped lang='scss'>
.tree-node{
  height: 45px;
  display: flex;
  justify-self: center;
  align-items: center;
  // background-color: green;
  border-bottom: 1px solid #e4e4e4;
}
</style>

四、源碼(VTreeNode.vue):

<template>
  <!-- <div> -->
    <div v-for="(item,index) in list" :key="index">
      <slot name="node" :item="item" :level="levelRef">
        <div>{{ item.name }}</div>
      </slot>
      <div v-show="item.child && canExpand(item)" >
        <VTreeNode :list="item.child" :level="levelRef">
          <template #node="slotProps">
            <slot name="node" :item="slotProps.item" :level="slotProps.level">
              <div>{{ slotProps.item.name }}</div>
            </slot>
          </template>
        </VTreeNode>
      </div>
    </div>
  <!-- </div> -->
</template>
<script setup>
import { ref, reactive, watch, computed, onMounted, } from 'vue';
const props = defineProps({
  list: {
    type: Array,
    default: () => [],
    validator: (val) => {
      return Array.isArray(val) && val.every(e => Reflect.has(e, 'name'));
    }
  },
  level: {
    type: Number,
    default: 0,
  }
});
const emit = defineEmits(['update:level', ])
const levelRef = computed({
  set: (newVal) => {
    if (props.level !== newVal) {
      emit("update:level", newVal);
    }
  },
  get: () => {
    const tmp = props.level + 1;
    return tmp;
  },
});
const canExpand = (item) => {
  return Reflect.has(item, 'isExpand') && item.isExpand;
};
// onMounted(() => {
//   console.log(`levelRef:${levelRef.value}`);
// });
</script>

VTreeNode.vue

VTreeNodeDemo.vue

以上就是Vue3 通過作用域插槽實現樹形菜單/嵌套組件的詳細內容,更多關於Vue3 樹形菜單嵌套組件的資料請關註WalkonNet其它相關文章!

推薦閱讀: