用 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>