vue 递归实现 Tree 组件带全选取消全选

7 篇文章 0 订阅

效果图
在这里插入图片描述
在这里插入图片描述

用 pinia 状态管理工具

store/common

import { defineStore, createPinia } from "pinia";

const id = "@@common";
const initialState = {
  selectList: [], // 存储选中的值方便全局获取
};
export const useCommonStore = defineStore(id, {
  state: () => ({ ...initialState }),
  getters: {},
  actions: {},
  persist: {
    enabled: true,
  },
});

Tree.vue

<template>
  <div v-for="(item, index) in newList" :key="index">
    <div class="line">
      <van-checkbox
        v-model="item.checked"
        :name="item.name"
        shape="square"
        @click="handleSelect(item)"
        >{{ item.name }}</van-checkbox
      >
      <van-icon
        v-if="item.children.length > 0"
        :name="item.show ? 'arrow-down' : 'arrow'"
        @click="handleShow(item)"
      />
    </div>
    <div v-if="item.children.length > 0" :style="{ paddingLeft: '20px' }">
      <Tree v-if="item.show" :list="item.children"></Tree>
    </div>
  </div>
</template>

<script>
export default {
  name: "Tree",
};
</script>
<script setup>
import { ref } from "vue";
import { storeToRefs } from "pinia";
import { useCommonStore } from "@/store/common";
const store = useCommonStore();
const { selectList } = storeToRefs(store);
const props = defineProps({
  list: {
    type: Object,
    default: () => [],
  },
});

const newList = ref(props.list);

// checked
function handleSelect(item, index) {
  console.log(item, index);
  // 获取选中的 name 为数组
  const indexs = selectList.value.indexOf(item.name);
  if (indexs > -1) {
    selectList.value.splice(indexs, 1);
  } else {
    selectList.value.push(item.name);
  }
}
// 展开 / 隐藏
function handleShow(item) {
  item.show = !item.show;
}
</script>

<style lang="scss" scoped>
.line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 40px;
}
:deep(.van-checkbox__label) {
  font-size: 28px;
  color: #333333;
  line-height: 40px;
}
</style>

index.vue

<Tree :list="list"></Tree>

// 数据格式
const list = ref([
  {
    name: "1",
    children: [
      {
        name: "1-1",
        children: [],
      },
    ],
  },
  {
    name: "2",
    children: [
      {
        name: "2-1",
        children: [
          {
            name: "2-1-1",
            children: [],
          },
        ],
      },
    ],
  },
  {
    name: "3",
    children: [
      {
        name: "3-1",
        children: [
          {
            name: "3-1-1",
            children: [
              {
                name: "3-1-1-1",
                children: [],
              },
            ],
          },
        ],
      },
      {
        name: "3-2-1",
        children: [],
      },
    ],
  },
]);

实现全选

tree

数据格式修改为


const list = ref([
  {
    id: 1,
    name: "1",
    parentId: 0,
    children: [
      {
        id: 11,
        name: "1-1",
        parentId: 1,
        children: [],
      },
    ],
  },
  {
    id: 2,
    name: "2",
    parentId: 0,
    children: [
      {
        id: 21,
        name: "2-1",
        parentId: 2,
        children: [
          {
            id: 211,
            name: "2-1-1",
            parentId: 21,
            children: [],
          },
        ],
      },
    ],
  },
  {
    id: 3,
    name: "3",
    parentId: 0,
    children: [
      {
        id: 31,
        name: "3-1",
        parentId: 3,
        children: [
          {
            id: 311,
            name: "3-1-1",
            parentId: 31,
            children: [
              {
                id: 3111,
                name: "3-1-1-1",
                parentId: 311,
                children: [],
              },
            ],
          },
          {
            id: 312,
            name: "3-1-2",
            parentId: 31,
            children: [],
          },
        ],
      },
      {
        id: 32,
        name: "3-2",
        parentId: 3,
        children: [],
      },
    ],
  },
]);

tree 组件修改为

<template>
  <div v-for="(item, index) in newList" :key="index">
    <div class="line">
      <van-checkbox
        v-model="item.checked"
        :name="item.id"
        shape="square"
        @click="handleSelect(item, index, props.index)"
        >{{ item.name }}</van-checkbox
      >
      <van-icon
        v-if="item.children.length > 0"
        :name="item.show ? 'arrow-down' : 'arrow'"
        @click="handleShow(item)"
      />
    </div>
    <div v-if="item.children.length > 0" :style="{ paddingLeft: '20px' }">
      <Tree
        v-if="item.show"
        :list="item.children"
        @node-click="nodeClick"
      ></Tree>
    </div>
  </div>
</template>

<script>
export default {
  name: "Tree",
};
</script>
<script setup>
import { ref, watch } from "vue";
import { storeToRefs } from "pinia";
import { useCommonStore } from "@/store/common";

const store = useCommonStore();
const { selectList } = storeToRefs(store);

const emits = defineEmits(["node-click"]);
const props = defineProps({
  list: {
    type: Object,
    default: () => [],
  },
});

const newList = ref(props.list);

watch(
  () => props.list,
  (newV) => {
    if (newV) newList.value = newV;
  }
);

// 递归:nodeClick 事件 一级下才会触发,
function nodeClick(checked, id) {
  console.log("newList.value", newList.value);
  const parent = getParent(newList.value, id);
  const arr = parent?.map((item) => item.parentId);
  // checked 当前点击状态
  if (!checked) {
    // 父级为 false
    newList.value.forEach((item) => {
      if (arr.includes(item.id)) item.checked = checked;
    });
    // 更新选中的 id 数组
    arr.forEach((item) => {
      const index = selectList.value.indexOf(item);
      if (index > -1) selectList.value.splice(index, 1);
    });
  } else {
    deepCheckParent(parent);
  }
  emits("node-click", checked, id);
}

// 验证父级下是否所有都为 true => true  否则 false
function deepCheckParent(parent) {
  parent.forEach((item) => {
    if (item?.children?.every((item) => item.checked)) {
      item.checked = true;
      selectList.value.push(item.id);
    } else {
      item.checked = false;
      const index = selectList.value.indexOf(item.id);
      if (index > -1) selectList.value.splice(index, 1);
    }
    selectList.value = [...new Set(selectList.value)];
  });
}

/**
 * 全选与反选
 * @param {*} obj
 */
function deepAllChecked(obj) {
  obj?.children?.forEach((list) => {
    list.checked = obj.checked;
    if (list.checked) {
      selectList.value.push(list.id);
    } else {
      selectList.value?.forEach((it, i) => {
        if (it == list.id) selectList.value.splice(i, 1);
      });
    }
    selectList.value = [...new Set(selectList.value)];
    deepAllChecked(list);
  });
  if (obj.children?.length <= 0) return;
}

// 复选框点击事件
function handleSelect(item) {
  // 获取选中的 id 为数组
  if (item.selected) {
    selectList.value.push(item.id);
  } else {
    let indexs = selectList.value.indexOf(item.id);
    if (indexs > -1) selectList.value.splice(indexs, 1);
  }
  selectList.value = [...new Set(selectList.value)];
  deepAllChecked(item);
  emits("node-click", item.checked, item.id);
}

/**
 * 递归: 根据当前 id 获取父级 id
 * @param {*} data2  数据
 * @param {*} nodeId2  id
 */
function getParent(data2, nodeId2) {
  var arrRes = [];
  if (data2.length == 0) {
    if (!!nodeId2) {
      arrRes.unshift(data2);
    }
    return arrRes;
  }
  let rev = (data, nodeId) => {
    for (var i = 0; i < data.length; i++) {
      let node = data[i];
      if (node.id == nodeId) {
        arrRes.unshift(node);
        rev(data2, node.parentId);
        break;
      } else {
        if (!!node.children) {
          rev(node.children, nodeId);
        } else {
          node.children = [];
        }
      }
    }
    return arrRes;
  };
  arrRes = rev(data2, nodeId2);
  return arrRes;
}

// 展开 / 隐藏
function handleShow(item) {
  item.show = !item.show;
}
</script>

<style lang="scss" scoped>
.line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 40px;
}
:deep(.van-checkbox__label) {
  font-size: 28px;
  color: #333333;
  line-height: 40px;
}
</style>

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值