584 lines
15 KiB
Plaintext
584 lines
15 KiB
Plaintext
|
|
<template>
|
|||
|
|
<view class="cl-tree" :class="[pt.className]">
|
|||
|
|
<cl-tree-item
|
|||
|
|
v-for="item in data"
|
|||
|
|
:key="item.id"
|
|||
|
|
:item="item"
|
|||
|
|
:level="0"
|
|||
|
|
:pt="props.pt"
|
|||
|
|
></cl-tree-item>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts" setup>
|
|||
|
|
import { computed, watch, ref, type PropType } from "vue";
|
|||
|
|
import type { ClTreeItem, ClTreeNodeInfo } from "../../types";
|
|||
|
|
import { first, isEmpty, isEqual, parsePt } from "@/cool";
|
|||
|
|
|
|||
|
|
defineOptions({
|
|||
|
|
name: "cl-tree"
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
pt: {
|
|||
|
|
type: Object as PropType<any>,
|
|||
|
|
default: () => ({})
|
|||
|
|
},
|
|||
|
|
// 绑定值
|
|||
|
|
modelValue: {
|
|||
|
|
type: [Array, String, Number] as PropType<any | null>,
|
|||
|
|
default: null
|
|||
|
|
},
|
|||
|
|
// 树形结构数据
|
|||
|
|
list: {
|
|||
|
|
type: Array as PropType<ClTreeItem[]>,
|
|||
|
|
default: () => []
|
|||
|
|
},
|
|||
|
|
// 节点图标
|
|||
|
|
icon: {
|
|||
|
|
type: String,
|
|||
|
|
default: "arrow-right-s-fill"
|
|||
|
|
},
|
|||
|
|
// 展开图标
|
|||
|
|
expandIcon: {
|
|||
|
|
type: String,
|
|||
|
|
default: "arrow-down-s-fill"
|
|||
|
|
},
|
|||
|
|
// 是否严格的遵循父子不互相关联
|
|||
|
|
checkStrictly: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
},
|
|||
|
|
// 是否可以选择节点
|
|||
|
|
checkable: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: true
|
|||
|
|
},
|
|||
|
|
// 是否允许多选
|
|||
|
|
multiple: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const emit = defineEmits(["update:modelValue", "change"]);
|
|||
|
|
|
|||
|
|
// 定义透传类型
|
|||
|
|
type PassThrough = {
|
|||
|
|
className?: string;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 计算样式穿透对象
|
|||
|
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
|||
|
|
|
|||
|
|
// 树数据
|
|||
|
|
const data = ref<ClTreeItem[]>(props.list);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 优化的节点搜索算法,使用Map缓存提升查找性能
|
|||
|
|
* 创建节点索引映射,O(1)时间复杂度查找节点
|
|||
|
|
*/
|
|||
|
|
const nodeMap = computed(() => {
|
|||
|
|
// 创建一个Map用于存储节点信息,key为节点id,value为节点信息对象
|
|||
|
|
const map = new Map<string | number, ClTreeNodeInfo>();
|
|||
|
|
|
|||
|
|
// 递归遍历所有节点,构建节点与父节点的映射关系
|
|||
|
|
function buildMap(nodes: ClTreeItem[], parent: ClTreeItem | null = null): void {
|
|||
|
|
for (let i: number = 0; i < nodes.length; i++) {
|
|||
|
|
const node = nodes[i]; // 当前节点
|
|||
|
|
|
|||
|
|
// 将当前节点的信息存入map,包含节点本身、父节点、在父节点中的索引
|
|||
|
|
map.set(node.id, { node, parent, index: i } as ClTreeNodeInfo);
|
|||
|
|
|
|||
|
|
// 如果当前节点有子节点,则递归处理子节点
|
|||
|
|
if (node.children != null && node.children.length > 0) {
|
|||
|
|
buildMap(node.children, node);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从根节点开始构建映射
|
|||
|
|
buildMap(data.value);
|
|||
|
|
return map; // 返回构建好的Map
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据key查找节点信息
|
|||
|
|
* @param key 节点id
|
|||
|
|
* @returns 节点信息对象或null
|
|||
|
|
*/
|
|||
|
|
function findNodeInfo(key: string | number): ClTreeNodeInfo | null {
|
|||
|
|
const result = nodeMap.value.get(key); // 从Map中查找节点信息
|
|||
|
|
return result != null ? result : null; // 找到则返回,否则返回null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取指定节点的所有祖先节点
|
|||
|
|
* @param key 节点id
|
|||
|
|
* @returns 祖先节点数组(从根到最近父节点顺序)
|
|||
|
|
*/
|
|||
|
|
function getAncestors(key: string | number): ClTreeItem[] {
|
|||
|
|
const result: ClTreeItem[] = []; // 用于存储祖先节点
|
|||
|
|
let nodeInfo = findNodeInfo(key); // 当前节点信息
|
|||
|
|
|
|||
|
|
// 循环查找父节点,直到根节点
|
|||
|
|
while (nodeInfo != null && nodeInfo.parent != null) {
|
|||
|
|
result.unshift(nodeInfo.parent); // 将父节点插入到数组前面
|
|||
|
|
nodeInfo = findNodeInfo(nodeInfo.parent.id); // 查找父节点信息
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result; // 返回祖先节点数组
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新所有节点的选中状态(用于批量操作后的状态同步)
|
|||
|
|
*/
|
|||
|
|
function updateAllCheckStates(): void {
|
|||
|
|
// 递归更新每个节点的选中和半选状态
|
|||
|
|
function updateNodeStates(nodes: ClTreeItem[]): void {
|
|||
|
|
for (let i: number = 0; i < nodes.length; i++) {
|
|||
|
|
const node = nodes[i]; // 当前节点
|
|||
|
|
const children = node.children != null ? node.children : []; // 子节点数组
|
|||
|
|
|
|||
|
|
if (children.length == 0) {
|
|||
|
|
// 叶子节点,重置半选状态
|
|||
|
|
node.isHalfChecked = false;
|
|||
|
|
continue; // 跳过后续处理
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 先递归处理子节点
|
|||
|
|
updateNodeStates(children);
|
|||
|
|
|
|||
|
|
// 统计子节点的选中和半选数量
|
|||
|
|
let checkedCount = 0; // 选中数量
|
|||
|
|
let halfCheckedCount = 0; // 半选数量
|
|||
|
|
|
|||
|
|
for (let j = 0; j < children.length; j++) {
|
|||
|
|
if (children[j].isChecked == true) {
|
|||
|
|
checkedCount++;
|
|||
|
|
} else if (children[j].isHalfChecked == true) {
|
|||
|
|
halfCheckedCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据子节点状态更新当前节点状态
|
|||
|
|
if (checkedCount == children.length) {
|
|||
|
|
// 全部选中
|
|||
|
|
node.isChecked = true;
|
|||
|
|
node.isHalfChecked = false;
|
|||
|
|
} else if (checkedCount > 0 || halfCheckedCount > 0) {
|
|||
|
|
// 部分选中或有半选
|
|||
|
|
node.isChecked = false;
|
|||
|
|
node.isHalfChecked = true;
|
|||
|
|
} else {
|
|||
|
|
// 全部未选中
|
|||
|
|
node.isChecked = false;
|
|||
|
|
node.isHalfChecked = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从根节点开始递归更新
|
|||
|
|
updateNodeStates(data.value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新指定节点的所有祖先节点的选中状态
|
|||
|
|
* @param key 节点id
|
|||
|
|
*/
|
|||
|
|
function updateAncestorsCheckState(key: string | number): void {
|
|||
|
|
const ancestors = getAncestors(key); // 获取所有祖先节点
|
|||
|
|
|
|||
|
|
// 从最近的父节点开始向上更新
|
|||
|
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|||
|
|
const ancestor = ancestors[i]; // 当前祖先节点
|
|||
|
|
const children = ancestor.children != null ? ancestor.children : ([] as ClTreeItem[]); // 子节点数组
|
|||
|
|
|
|||
|
|
if (children.length == 0) continue; // 没有子节点则跳过
|
|||
|
|
|
|||
|
|
let checkedCount = 0; // 选中数量
|
|||
|
|
let halfCheckedCount = 0; // 半选数量
|
|||
|
|
|
|||
|
|
// 统计子节点的选中和半选数量
|
|||
|
|
for (let j = 0; j < children.length; j++) {
|
|||
|
|
if (children[j].isChecked == true) {
|
|||
|
|
checkedCount++;
|
|||
|
|
} else if (children[j].isHalfChecked == true) {
|
|||
|
|
halfCheckedCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据子节点状态更新当前祖先节点状态
|
|||
|
|
if (checkedCount == children.length) {
|
|||
|
|
// 全部选中
|
|||
|
|
ancestor.isChecked = true;
|
|||
|
|
ancestor.isHalfChecked = false;
|
|||
|
|
} else if (checkedCount > 0 || halfCheckedCount > 0) {
|
|||
|
|
// 部分选中或有半选
|
|||
|
|
ancestor.isChecked = false;
|
|||
|
|
ancestor.isHalfChecked = true;
|
|||
|
|
} else {
|
|||
|
|
// 全部未选中
|
|||
|
|
ancestor.isChecked = false;
|
|||
|
|
ancestor.isHalfChecked = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取指定节点的所有子孙节点
|
|||
|
|
* 优化:使用队列实现广度优先遍历,避免递归栈溢出
|
|||
|
|
* @param key 节点id
|
|||
|
|
* @returns 子孙节点数组
|
|||
|
|
*/
|
|||
|
|
function getDescendants(key: string | number): ClTreeItem[] {
|
|||
|
|
const nodeInfo = findNodeInfo(key); // 查找节点信息
|
|||
|
|
if (nodeInfo == null || nodeInfo.node.children == null) {
|
|||
|
|
return []; // 节点不存在或无子节点,返回空数组
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 存储所有子孙节点
|
|||
|
|
const result: ClTreeItem[] = [];
|
|||
|
|
|
|||
|
|
// 队列用于广度优先遍历
|
|||
|
|
const queue: ClTreeItem[] = [];
|
|||
|
|
|
|||
|
|
// 将子节点添加到队列中
|
|||
|
|
for (let i = 0; i < nodeInfo.node.children.length; i++) {
|
|||
|
|
queue.push(nodeInfo.node.children[i]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 广度优先遍历所有子孙节点
|
|||
|
|
while (queue.length > 0) {
|
|||
|
|
const node = queue.shift(); // 取出队首节点
|
|||
|
|
|
|||
|
|
if (node == null) break; // 队列为空则结束
|
|||
|
|
|
|||
|
|
result.push(node); // 将当前节点加入结果数组
|
|||
|
|
|
|||
|
|
// 如果有子节点,继续加入队列
|
|||
|
|
if (node.children != null && node.children.length > 0) {
|
|||
|
|
for (let i = 0; i < node.children.length; i++) {
|
|||
|
|
queue.push(node.children[i]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result; // 返回所有子孙节点
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清空所有节点的选中状态
|
|||
|
|
*/
|
|||
|
|
function clearChecked(): void {
|
|||
|
|
// 遍历所有节点,将 isChecked 和 isHalfChecked 设为 false
|
|||
|
|
nodeMap.value.forEach((info: ClTreeNodeInfo) => {
|
|||
|
|
info.node.isChecked = false;
|
|||
|
|
info.node.isHalfChecked = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置指定节点的选中状态
|
|||
|
|
* @param key 节点id
|
|||
|
|
* @param flag 是否选中
|
|||
|
|
*/
|
|||
|
|
function setChecked(key: string | number, flag: boolean): void {
|
|||
|
|
const nodeInfo = findNodeInfo(key); // 查找节点信息
|
|||
|
|
if (nodeInfo == null) return; // 节点不存在则返回
|
|||
|
|
|
|||
|
|
// 非多选模式下,清空所有选中状态
|
|||
|
|
if (!props.multiple) {
|
|||
|
|
clearChecked();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置当前节点选中状态
|
|||
|
|
nodeInfo.node.isChecked = flag;
|
|||
|
|
|
|||
|
|
// 多选模式下处理
|
|||
|
|
if (props.multiple) {
|
|||
|
|
// 非严格模式下处理父子联动
|
|||
|
|
if (!props.checkStrictly) {
|
|||
|
|
// 设置所有子孙节点的选中状态
|
|||
|
|
const descendants = getDescendants(key);
|
|||
|
|
for (let i = 0; i < descendants.length; i++) {
|
|||
|
|
descendants[i].isChecked = flag;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新所有祖先节点的状态
|
|||
|
|
updateAncestorsCheckState(key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量设置节点选中状态
|
|||
|
|
* @param keys 需要设置为选中的节点id数组
|
|||
|
|
*/
|
|||
|
|
function setCheckedKeys(keys: (string | number)[]): void {
|
|||
|
|
// 遍历所有需要选中的节点
|
|||
|
|
for (let i = 0; i < keys.length; i++) {
|
|||
|
|
const key: string | number = keys[i];
|
|||
|
|
const nodeInfo = findNodeInfo(key); // 查找节点信息
|
|||
|
|
|
|||
|
|
if (nodeInfo != null) {
|
|||
|
|
nodeInfo.node.isChecked = true; // 设置为选中
|
|||
|
|
|
|||
|
|
// 非严格模式下同时设置所有子孙节点为选中状态
|
|||
|
|
if (!props.checkStrictly) {
|
|||
|
|
const descendants = getDescendants(key);
|
|||
|
|
for (let j = 0; j < descendants.length; j++) {
|
|||
|
|
descendants[j].isChecked = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 非严格模式下更新所有相关节点的状态
|
|||
|
|
if (!props.checkStrictly) {
|
|||
|
|
updateAllCheckStates();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有选中节点的keys
|
|||
|
|
* @returns 选中节点id数组
|
|||
|
|
*/
|
|||
|
|
function getCheckedKeys(): (string | number)[] {
|
|||
|
|
const result: (string | number)[] = []; // 存储选中节点id
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 递归收集所有选中节点的id
|
|||
|
|
* @param nodes 当前遍历的节点数组
|
|||
|
|
*/
|
|||
|
|
function collectCheckedKeys(nodes: ClTreeItem[]): void {
|
|||
|
|
for (let i = 0; i < nodes.length; i++) {
|
|||
|
|
const node = nodes[i];
|
|||
|
|
|
|||
|
|
if (node.isChecked == true) {
|
|||
|
|
result.push(node.id); // 收集选中节点id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (node.children != null) {
|
|||
|
|
collectCheckedKeys(node.children); // 递归处理子节点
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
collectCheckedKeys(data.value); // 从根节点开始收集
|
|||
|
|
return result; // 返回所有选中节点id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有半选中节点的keys
|
|||
|
|
* @returns 半选节点id数组
|
|||
|
|
*/
|
|||
|
|
function getHalfCheckedKeys(): (string | number)[] {
|
|||
|
|
const result: (string | number)[] = []; // 存储半选节点id
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 递归收集所有半选节点的id
|
|||
|
|
* @param nodes 当前遍历的节点数组
|
|||
|
|
*/
|
|||
|
|
function collectHalfCheckedKeys(nodes: ClTreeItem[]): void {
|
|||
|
|
for (let i = 0; i < nodes.length; i++) {
|
|||
|
|
const node = nodes[i];
|
|||
|
|
|
|||
|
|
if (node.isHalfChecked == true) {
|
|||
|
|
result.push(node.id); // 收集半选节点id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (node.children != null) {
|
|||
|
|
collectHalfCheckedKeys(node.children); // 递归处理子节点
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
collectHalfCheckedKeys(data.value); // 从根节点开始收集
|
|||
|
|
return result; // 返回所有半选节点id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置指定节点的展开状态
|
|||
|
|
* @param key 节点id
|
|||
|
|
* @param flag 是否展开
|
|||
|
|
*/
|
|||
|
|
function setExpanded(key: string | number, flag: boolean): void {
|
|||
|
|
const nodeInfo = findNodeInfo(key); // 查找节点信息
|
|||
|
|
if (nodeInfo == null) return; // 节点不存在则返回
|
|||
|
|
|
|||
|
|
nodeInfo.node.isExpand = flag; // 设置节点的展开状态
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量设置节点展开状态
|
|||
|
|
* @param keys 需要展开的节点id数组
|
|||
|
|
*/
|
|||
|
|
function setExpandedKeys(keys: (string | number)[]): void {
|
|||
|
|
// 设置指定节点为展开状态
|
|||
|
|
for (let i = 0; i < keys.length; i++) {
|
|||
|
|
const nodeInfo = findNodeInfo(keys[i]);
|
|||
|
|
|
|||
|
|
if (nodeInfo != null) {
|
|||
|
|
nodeInfo.node.isExpand = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有展开节点的keys
|
|||
|
|
* @returns 展开节点id数组
|
|||
|
|
*/
|
|||
|
|
function getExpandedKeys(): (string | number)[] {
|
|||
|
|
const result: (string | number)[] = []; // 存储展开节点id
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 递归收集所有展开节点的id
|
|||
|
|
* @param nodes 当前遍历的节点数组
|
|||
|
|
*/
|
|||
|
|
function collectExpandedKeys(nodes: ClTreeItem[]): void {
|
|||
|
|
for (let i = 0; i < nodes.length; i++) {
|
|||
|
|
const node = nodes[i];
|
|||
|
|
|
|||
|
|
if (node.isExpand == true) {
|
|||
|
|
result.push(node.id); // 收集展开节点id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (node.children != null) {
|
|||
|
|
collectExpandedKeys(node.children); // 递归处理子节点
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
collectExpandedKeys(data.value); // 从根节点开始收集
|
|||
|
|
return result; // 返回所有展开节点id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 展开所有节点
|
|||
|
|
*/
|
|||
|
|
function expandAll(): void {
|
|||
|
|
// 遍历所有节点,如果有子节点则设置为展开
|
|||
|
|
nodeMap.value.forEach((info: ClTreeNodeInfo) => {
|
|||
|
|
if (info.node.children != null && info.node.children.length > 0) {
|
|||
|
|
info.node.isExpand = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 收起所有节点
|
|||
|
|
*/
|
|||
|
|
function collapseAll() {
|
|||
|
|
// 遍历所有节点,将isExpand设为false
|
|||
|
|
nodeMap.value.forEach((info: ClTreeNodeInfo) => {
|
|||
|
|
info.node.isExpand = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 同步绑定值
|
|||
|
|
*/
|
|||
|
|
/**
|
|||
|
|
* 同步绑定值到外部
|
|||
|
|
* 当内部选中状态变化时,更新外部的modelValue,并触发change事件
|
|||
|
|
*/
|
|||
|
|
function syncModelValue() {
|
|||
|
|
// 如果树数据为空,则不更新绑定值
|
|||
|
|
if (isEmpty(data.value)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前所有选中的key
|
|||
|
|
const checkedKeys = getCheckedKeys();
|
|||
|
|
|
|||
|
|
// 如果外部modelValue为null,或当前选中key与外部modelValue不一致,则更新
|
|||
|
|
if (props.modelValue == null || !isEqual(checkedKeys, props.modelValue!)) {
|
|||
|
|
// 如果多选,直接传递数组;否则只传第一个选中的key
|
|||
|
|
const value = props.multiple ? checkedKeys : first(checkedKeys);
|
|||
|
|
|
|||
|
|
emit("update:modelValue", value); // 通知外部更新modelValue
|
|||
|
|
emit("change", value); // 触发change事件
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 同步外部modelValue到内部选中状态
|
|||
|
|
* 当外部modelValue变化时,更新内部选中状态,并保持与外部一致
|
|||
|
|
*/
|
|||
|
|
function syncCheckedState() {
|
|||
|
|
// 如果外部modelValue为null,则不处理
|
|||
|
|
if (props.modelValue == null) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前所有选中的key
|
|||
|
|
const checkedKeys = getCheckedKeys();
|
|||
|
|
|
|||
|
|
// 如果当前选中key与外部modelValue不一致,则进行同步
|
|||
|
|
if (!isEqual(checkedKeys, props.modelValue!)) {
|
|||
|
|
if (Array.isArray(props.modelValue)) {
|
|||
|
|
setCheckedKeys(props.modelValue!); // 多选时,设置所有选中key
|
|||
|
|
} else {
|
|||
|
|
setChecked(props.modelValue!, true); // 单选时,设置单个选中key
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
syncModelValue(); // 同步绑定值到外部
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监听props.list变化,同步到内部数据
|
|||
|
|
watch(
|
|||
|
|
computed(() => props.list),
|
|||
|
|
(val: ClTreeItem[]) => {
|
|||
|
|
data.value = val;
|
|||
|
|
|
|||
|
|
// 检查选中状态
|
|||
|
|
syncCheckedState();
|
|||
|
|
},
|
|||
|
|
{ immediate: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 监听modelValue变化
|
|||
|
|
watch(
|
|||
|
|
computed(() => [props.modelValue ?? 0]),
|
|||
|
|
() => {
|
|||
|
|
syncCheckedState();
|
|||
|
|
},
|
|||
|
|
{ immediate: true, deep: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 监听树数据变化
|
|||
|
|
watch(
|
|||
|
|
data,
|
|||
|
|
() => {
|
|||
|
|
// 自动更新选中状态
|
|||
|
|
if (!props.checkStrictly && props.multiple) {
|
|||
|
|
updateAllCheckStates();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新绑定值
|
|||
|
|
syncModelValue();
|
|||
|
|
},
|
|||
|
|
{ deep: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
defineExpose({
|
|||
|
|
icon: computed(() => props.icon),
|
|||
|
|
expandIcon: computed(() => props.expandIcon),
|
|||
|
|
checkStrictly: computed(() => props.checkStrictly),
|
|||
|
|
checkable: computed(() => props.checkable),
|
|||
|
|
multiple: computed(() => props.multiple),
|
|||
|
|
clearChecked,
|
|||
|
|
setChecked,
|
|||
|
|
setCheckedKeys,
|
|||
|
|
getCheckedKeys,
|
|||
|
|
getHalfCheckedKeys,
|
|||
|
|
setExpanded,
|
|||
|
|
setExpandedKeys,
|
|||
|
|
getExpandedKeys,
|
|||
|
|
expandAll,
|
|||
|
|
collapseAll
|
|||
|
|
});
|
|||
|
|
</script>
|