|
|
@@ -1,11 +1,11 @@
|
|
|
<template>
|
|
|
- <view class="cl-tree">
|
|
|
+ <view class="cl-tree" :class="[pt.className]">
|
|
|
<cl-tree-item
|
|
|
v-for="item in data"
|
|
|
:key="item.id"
|
|
|
:item="item"
|
|
|
:level="0"
|
|
|
- :pt="pt"
|
|
|
+ :pt="props.pt"
|
|
|
></cl-tree-item>
|
|
|
</view>
|
|
|
</template>
|
|
|
@@ -13,6 +13,7 @@
|
|
|
<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"
|
|
|
@@ -23,6 +24,11 @@ const props = defineProps({
|
|
|
type: Object as PropType<any>,
|
|
|
default: () => ({})
|
|
|
},
|
|
|
+ // 绑定值
|
|
|
+ modelValue: {
|
|
|
+ type: [Array, String, Number] as PropType<any | null>,
|
|
|
+ default: null
|
|
|
+ },
|
|
|
// 树形结构数据
|
|
|
list: {
|
|
|
type: Array as PropType<ClTreeItem[]>,
|
|
|
@@ -38,18 +44,33 @@ const props = defineProps({
|
|
|
type: String,
|
|
|
default: "arrow-down-s-fill"
|
|
|
},
|
|
|
- // 是否显示复选框
|
|
|
- showCheckbox: {
|
|
|
+ // 是否严格的遵循父子不互相关联
|
|
|
+ checkStrictly: {
|
|
|
type: Boolean,
|
|
|
default: false
|
|
|
},
|
|
|
- // 是否严格的遵循父子不互相关联
|
|
|
- checkStrictly: {
|
|
|
+ // 是否可以选择节点
|
|
|
+ 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);
|
|
|
|
|
|
@@ -266,19 +287,27 @@ 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.checkStrictly) {
|
|
|
- // 设置所有子孙节点的选中状态
|
|
|
- const descendants = getDescendants(key);
|
|
|
- for (let i = 0; i < descendants.length; i++) {
|
|
|
- descendants[i].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);
|
|
|
+ // 更新所有祖先节点的状态
|
|
|
+ updateAncestorsCheckState(key);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -386,11 +415,6 @@ function setExpanded(key: string | number, flag: boolean): void {
|
|
|
* @param keys 需要展开的节点id数组
|
|
|
*/
|
|
|
function setExpandedKeys(keys: (string | number)[]): void {
|
|
|
- // 先重置所有节点为收起状态
|
|
|
- nodeMap.value.forEach((info: ClTreeNodeInfo) => {
|
|
|
- info.node.isExpand = false;
|
|
|
- });
|
|
|
-
|
|
|
// 设置指定节点为展开状态
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
const nodeInfo = findNodeInfo(keys[i]);
|
|
|
@@ -445,29 +469,96 @@ function expandAll(): void {
|
|
|
/**
|
|
|
* 收起所有节点
|
|
|
*/
|
|
|
-function collapseAll(): void {
|
|
|
+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) {
|
|
|
+ // 自动更新选中状态
|
|
|
+ if (!props.checkStrictly && props.multiple) {
|
|
|
updateAllCheckStates();
|
|
|
}
|
|
|
+
|
|
|
+ // 更新绑定值
|
|
|
+ syncModelValue();
|
|
|
},
|
|
|
{ deep: true }
|
|
|
);
|
|
|
@@ -475,8 +566,9 @@ watch(
|
|
|
defineExpose({
|
|
|
icon: computed(() => props.icon),
|
|
|
expandIcon: computed(() => props.expandIcon),
|
|
|
- showCheckbox: computed(() => props.showCheckbox),
|
|
|
checkStrictly: computed(() => props.checkStrictly),
|
|
|
+ checkable: computed(() => props.checkable),
|
|
|
+ multiple: computed(() => props.multiple),
|
|
|
clearChecked,
|
|
|
setChecked,
|
|
|
setCheckedKeys,
|