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