From 3e432d966f3403f9e5f26677182fc53e7a1f404d Mon Sep 17 00:00:00 2001 From: Jevin Date: Tue, 15 Oct 2024 15:11:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(treeSelect):=20=E6=B7=BB=E5=8A=A0cacheData?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E6=87=92=E5=8A=A0=E8=BD=BD=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../document/zh-CN/components/treeSelect.md | 114 ++++++++++++++++++ packages/component/component/tree/index.vue | 16 ++- packages/component/component/tree/tree.ts | 4 + packages/component/component/tree/useTree.ts | 28 ++++- .../treeSelect/__tests__/treeSelect.test.tsx | 110 +++++++++++++++++ .../component/component/treeSelect/index.vue | 89 ++++++++++---- .../component/treeSelect/interface.ts | 7 ++ 7 files changed, 334 insertions(+), 34 deletions(-) diff --git a/docs/src/document/zh-CN/components/treeSelect.md b/docs/src/document/zh-CN/components/treeSelect.md index e46d7de2..ac3cdd99 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 属性 ::: @@ -1360,6 +1463,7 @@ const data7 = ref([{ | searchNodeMethod | 对树节点进行筛选时执行的方法, 返回 false 则表示这个节点会被隐藏 | `Fcuntion(node, value)` | -- | -- | | lazy | 是否懒加载子节点,需与 `load` 方法结合使用 | false | | | load | 加载子树数据的方法,仅当 `lazy` 属性为 true 时生效 | function(node, resolve),node 为当前点击的节点,resolve 为数据加载完成的回调(必须调用) | | +| cacheData `2.19.0` | 预渲染懒加载节点数据 | `Array` | | ::: @@ -1374,5 +1478,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 21bc224d..b6291b70 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 d1a7cae2..25c846a0 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 86b4ceb2..bcbc69dd 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 75190839..6bdc5dcb 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 fa97d9e7..a38a1e7d 100644 --- a/packages/component/component/treeSelect/index.vue +++ b/packages/component/component/treeSelect/index.vue @@ -3,7 +3,7 @@ import "./index.less"; 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"; @@ -12,9 +12,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"; @@ -37,6 +39,7 @@ export interface TreeSelectProps { defaultExpandAll?: boolean; lazy?: boolean; load?: LoadFunction; + cacheData?: TreeSelectCacheData[]; } export interface TreeSelectEmits { @@ -63,8 +66,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(); @@ -114,28 +119,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"); } @@ -154,7 +165,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 : ""; } } }, @@ -176,7 +191,7 @@ const onClear = function () { * * @param node 当前节点 */ -const handleClick = (node: any) => { +const handleClick = (node: OriginalTreeData) => { if (!props.multiple) { dropdownRef.value.hide(); } @@ -304,6 +319,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, @@ -381,13 +417,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" >