diff --git a/docs/src/document/zh-CN/components/treeSelect.md b/docs/src/document/zh-CN/components/treeSelect.md
index 3b362ed0bee347a471040f9a884c5f6e270d8648..37a59ccac42f6643596f9cf9fc28b976c4aac721 100644
--- a/docs/src/document/zh-CN/components/treeSelect.md
+++ b/docs/src/document/zh-CN/components/treeSelect.md
@@ -1339,6 +1339,109 @@ const data7 = ref([{
:::
+::: title 渲染懒加载数据
+:::
+
+::: demo tree 组件 使用 `cacheData` 预渲染懒加载的数据
+
+
+
+
+
+
+
+
+
+
+
+:::
+
::: title Tree Select 属性
:::
@@ -1363,6 +1466,7 @@ const data7 = ref([{
| replace-fields | 替换 data 中`id` `title` `children` 字段名 | -- | -- | `{id: "id", title: "title", children: "children"}` |
| default-expand-all | 是否展开所有 `tree` 节点 | `boolean` | -- | `false` |
| teleport-props | 继承至 dropdown 组件,下拉面板 `传递` 属性 | `object` | `{to: 'body', disabled: false}` | `vue teleport` 组件 | `2.19.0` |
+| cacheData | 预渲染懒加载节点数据 | `Array` | | | `2.19.4` |
:::
@@ -1377,5 +1481,15 @@ const data7 = ref([{
:::
+::: title types
+:::
+
+```ts
+interface TreeSelectCacheData {
+ label: string;
+ value: StringOrNumber;
+}
+```
+
::: previousNext transition
:::
diff --git a/packages/component/component/tree/index.vue b/packages/component/component/tree/index.vue
index 21bc224d1d9e310fb03845abc1f632589e9ceaa6..b6291b7067207353067e588b46d64dab07d647cd 100644
--- a/packages/component/component/tree/index.vue
+++ b/packages/component/component/tree/index.vue
@@ -72,8 +72,9 @@ const initStatus = ref(false);
const loadNodeList = () => {
let { tree: _tree, nodeList: _nodeList } = useTree(
- Object.assign({}, props, { replaceFields: _replaceFields.value }),
- emit
+ props,
+ emit,
+ _replaceFields.value
);
tree.value = _tree;
nodeList.value = _nodeList.value;
@@ -148,7 +149,10 @@ onMounted(() => {
});
function handleClick(node: TreeData) {
- const originNode = tree.value.getOriginData(node[_replaceFields.value.id]);
+ const originNode = tree.value.getOriginData(
+ node[_replaceFields.value.id]
+ ) as OriginalTreeData;
+
if (props.isSelect) {
emit("update:selectedKey", node[_replaceFields.value.id]);
}
@@ -156,7 +160,11 @@ function handleClick(node: TreeData) {
}
const handleCheckChange = (node: TreeData, checked: boolean) => {
- emit("check-change", node, checked);
+ const originNode = tree.value.getOriginData(
+ node[_replaceFields.value.id]
+ ) as OriginalTreeData;
+
+ emit("check-change", originNode, checked);
};
const selectedKey = ref(props.selectedKey);
diff --git a/packages/component/component/tree/tree.ts b/packages/component/component/tree/tree.ts
index d1a7cae2ce8128d17428501d9d0f8b32da5d0a8b..25c846a07e8286eea81f7d572628a76100ce6507 100644
--- a/packages/component/component/tree/tree.ts
+++ b/packages/component/component/tree/tree.ts
@@ -220,6 +220,10 @@ class Tree {
return currentState;
}
+ refreshData(data: Array) {
+ return (this.treeData = data);
+ }
+
getData() {
return this.treeData;
}
diff --git a/packages/component/component/tree/useTree.ts b/packages/component/component/tree/useTree.ts
index 86b4ceb2d9555e504f4216cbc4629b911ce4859c..bcbc69dd5bba6f2dd8cd745581dcbd885e5f55d1 100644
--- a/packages/component/component/tree/useTree.ts
+++ b/packages/component/component/tree/useTree.ts
@@ -1,23 +1,28 @@
import type { TreeEmits, TreeProps, ReplaceFieldsOptions } from "./tree.type";
import type { ComputedRef } from "vue";
-import { computed } from "vue";
+import { computed, watch } from "vue";
import { Tree, TreeData } from "./tree";
export declare type UseTree = (
props: TreeProps,
- emit: TreeEmits
+ emit: TreeEmits,
+ replaceFields: ReplaceFieldsOptions
) => {
tree: Tree;
nodeList: ComputedRef;
};
-export const useTree: UseTree = (props: TreeProps, emit: TreeEmits) => {
+export const useTree: UseTree = (
+ props: TreeProps,
+ emit: TreeEmits,
+ replaceFields: ReplaceFieldsOptions
+) => {
const tree = new Tree(
{
nodeMap: new Map(),
originMap: new Map(),
- replaceFields: props.replaceFields as ReplaceFieldsOptions,
+ replaceFields: replaceFields as ReplaceFieldsOptions,
showCheckbox: props.showCheckbox ?? false,
checkedKeys: props.checkedKeys ?? [],
expandKeys: props.expandKeys ?? [],
@@ -34,6 +39,21 @@ export const useTree: UseTree = (props: TreeProps, emit: TreeEmits) => {
return nodes;
});
+ watch(
+ () => props.checkedKeys,
+ () => {
+ const { id } = replaceFields;
+ const nodes = nodeList.value;
+ tree.treeForeach(nodes, (child: TreeData) => {
+ if (!props.checkedKeys?.includes(child[id])) {
+ child.isChecked = false;
+ }
+ });
+
+ tree.refreshData(nodes);
+ }
+ );
+
return {
tree,
nodeList,
diff --git a/packages/component/component/treeSelect/__tests__/treeSelect.test.tsx b/packages/component/component/treeSelect/__tests__/treeSelect.test.tsx
index 75190839793ead457598da2493b0e2c8eff13f4c..6bdc5dcb89f7f93511385c5a239434ee05a0b009 100644
--- a/packages/component/component/treeSelect/__tests__/treeSelect.test.tsx
+++ b/packages/component/component/treeSelect/__tests__/treeSelect.test.tsx
@@ -6,6 +6,7 @@ import type { VueWrapper } from "@vue/test-utils";
import LayTreeSelect from "../index.vue";
import LayInput from "../../input/index.vue";
+import LayTagInput from "../../tagInput/index.vue";
describe("LayTreeSelect", () => {
test("LayTreeSelect 基础使用", async () => {
@@ -130,4 +131,113 @@ describe("LayTreeSelect", () => {
await nextTick();
expect(InputVm.props("modelValue")).toBe("title-1");
});
+
+ test("LayTreeSelect cacheData", async () => {
+ const wrapper = mount({
+ setup() {
+ const value1 = ref(11);
+ const cacheData = ref([{ label: "title-1-1", value: 11 }]);
+ const data = ref([
+ {
+ title: "title-1",
+ id: 1,
+ children: [
+ {
+ title: "title-1-1",
+ id: 11,
+ },
+ {
+ title: "title-1-2",
+ id: 12,
+ },
+ ],
+ },
+ {
+ title: "title-2",
+ id: 2,
+ children: [
+ {
+ title: "title-2-1",
+ id: 21,
+ },
+ {
+ title: "title-2-2",
+ id: 22,
+ },
+ ],
+ },
+ ]);
+
+ return () => (
+
+ (value1.value = val as string | number)
+ }
+ >
+ );
+ },
+ }) as VueWrapper;
+ await nextTick();
+
+ const InputVm = wrapper.findComponent(LayInput);
+ expect(InputVm.props("modelValue")).toBe("title-1-1");
+ });
+
+ test("LayTreeSelect cacheData multiple", async () => {
+ const wrapper = mount({
+ setup() {
+ const value1 = ref([1, 11]);
+ const cacheData = ref([{ label: "title-1-1", value: 11 }]);
+ const data = ref([
+ {
+ title: "title-1",
+ id: 1,
+ children: [
+ {
+ title: "title-1-1",
+ id: 11,
+ },
+ {
+ title: "title-1-2",
+ id: 12,
+ },
+ ],
+ },
+ {
+ title: "title-2",
+ id: 2,
+ children: [
+ {
+ title: "title-2-1",
+ id: 21,
+ },
+ {
+ title: "title-2-2",
+ id: 22,
+ },
+ ],
+ },
+ ]);
+
+ return () => (
+
+ );
+ },
+ }) as VueWrapper;
+ await nextTick();
+
+ const TagInputVm = wrapper.findComponent(LayTagInput);
+ const tagInputValue = TagInputVm.props("modelValue")?.map(
+ (data: any) => data!.value
+ );
+ expect(tagInputValue).toEqual([1, 11]);
+ });
});
diff --git a/packages/component/component/treeSelect/index.vue b/packages/component/component/treeSelect/index.vue
index f9f3cbbb4262e95b23e51ba6ba5d89c047cd54ee..da66fff24e34d6cfc20bbaa49fb07e8fba220d7d 100644
--- a/packages/component/component/treeSelect/index.vue
+++ b/packages/component/component/treeSelect/index.vue
@@ -5,7 +5,7 @@ import type { DropdownTeleportProps } from "../dropdown/interface";
import { StyleValue, computed, ref, watch, useSlots, provide } from "vue";
import { useDebounceFn } from "@vueuse/core";
import { getNode } from "../../utils";
-import { TreeSelectSize } from "./interface";
+import type { TreeSelectSize, TreeSelectCacheData } from "./interface";
import { LayIcon } from "@layui/icons-vue";
import LayInput from "../input/index.vue";
import LayTagInput from "../tagInput/index.vue";
@@ -14,9 +14,11 @@ import LayTree from "../tree/index.vue";
import useProps from "./index.hooks";
import { fillFieldNames } from "../tree/utils";
import {
+ StringOrNumber,
ReplaceFieldsOptionsOptional,
LoadFunction,
SearchNodeMethodType,
+ OriginalTreeData,
} from "../tree/tree.type";
import { LAYUI_TREE_SELECT } from "./useTreeSelect";
@@ -40,6 +42,7 @@ export interface TreeSelectProps {
replaceFields?: ReplaceFieldsOptionsOptional;
defaultExpandAll?: boolean;
teleportProps?: DropdownTeleportProps;
+ cacheData?: TreeSelectCacheData[];
}
export interface TreeSelectEmits {
@@ -66,8 +69,10 @@ const props = withDefaults(defineProps(), {
return node.title.includes(value);
},
defaultExpandAll: false,
+ cacheData: () => [],
});
+const _cacheData = ref(props.cacheData);
const { size } = useProps(props);
const treeData = ref();
@@ -117,28 +122,34 @@ watch(
const { id, title } = _replaceFields.value;
if (props.multiple) {
try {
- multipleValue.value = checkedKeys.value.map((value: any) => {
- let node: any = getNode(
- treeOriginData.value || props.data,
- value,
- _replaceFields.value
- );
-
- if (node) {
- node.label = node[title];
- node.value = node[id];
- node.closable = !node.disabled;
- }
-
- if (node == undefined) {
- node = {
- label: value,
- value: value,
- closable: true,
- };
- }
- return node;
- });
+ multipleValue.value = checkedKeys.value
+ .map((value: any) => {
+ let node: any = getNode(
+ treeOriginData.value || props.data,
+ value,
+ _replaceFields.value
+ );
+
+ if (node) {
+ node.label = node[title];
+ node.value = node[id];
+ node.closable = !node.disabled;
+ }
+
+ const cacheData = _cacheData.value.find(
+ (date) => value === date.value
+ );
+
+ if (cacheData) {
+ node = {
+ label: cacheData.label,
+ value: cacheData.value,
+ closable: true,
+ };
+ }
+ return node;
+ })
+ .filter(Boolean);
} catch (e) {
throw new Error("v-model / model-value is not an array type");
}
@@ -157,7 +168,11 @@ watch(
if (node) {
singleValue.value = node[title];
} else {
- singleValue.value = "";
+ const cacheData = _cacheData.value.find(
+ (date) => selectedValue.value === date.value
+ );
+
+ singleValue.value = cacheData ? cacheData.label : "";
}
}
},
@@ -179,7 +194,7 @@ const onClear = function () {
*
* @param node 当前节点
*/
-const handleClick = (node: any) => {
+const handleClick = (node: OriginalTreeData) => {
if (!props.multiple) {
dropdownRef.value.hide();
}
@@ -307,6 +322,27 @@ const setInputEl = (el: HTMLInputElement) => {
}
};
+const handleUpdateCheckedKeys = (data: Array) => {
+ _cacheData.value.forEach((_data) => {
+ const node: any = getNode(
+ treeOriginData.value,
+ _data.value,
+ _replaceFields.value
+ );
+
+ if (!node && !data.includes(_data.value)) {
+ data.push(_data.value);
+ } else {
+ _cacheData.value.splice(
+ _cacheData.value.findIndex((data) => data.value === _data.value),
+ 1
+ );
+ }
+ });
+
+ checkedKeys.value = data;
+};
+
provide(LAYUI_TREE_SELECT, {
inputEl,
setInputEl,
@@ -386,13 +422,14 @@ provide(LAYUI_TREE_SELECT, {
:show-checkbox="multiple"
:check-strictly="checkStrictly"
v-model:selectedKey="selectedValue"
- v-model:checkedKeys="checkedKeys"
+ :checkedKeys="checkedKeys"
:tail-node-icon="!hasTitleSlot"
:replaceFields="_replaceFields"
:defaultExpandAll="defaultExpandAll"
:lazy="lazy"
:load="load"
:searchNodeMethod="searchNodeMethod"
+ @update:checked-keys="handleUpdateCheckedKeys"
@node-click="handleClick"
>
diff --git a/packages/component/component/treeSelect/interface.ts b/packages/component/component/treeSelect/interface.ts
index 80e371262956194269bd798d4dbb44e0a7fd92cc..da0b8dff83f69f409915ac53f7fab2cae269c9c7 100644
--- a/packages/component/component/treeSelect/interface.ts
+++ b/packages/component/component/treeSelect/interface.ts
@@ -1 +1,8 @@
+import type { StringOrNumber } from "../tree/tree.type";
+
export type TreeSelectSize = "lg" | "md" | "sm" | "xs";
+
+export interface TreeSelectCacheData {
+ label: string;
+ value: StringOrNumber;
+}