From f87e1358e6811a6f83ad6acabb4c2b44f6bcd645 Mon Sep 17 00:00:00 2001 From: sallerli1 Date: Thu, 20 Oct 2022 11:36:03 +0800 Subject: [PATCH] fix(pro:transfer): strategy parent with flat-target-data not working --- .../__snapshots__/proTransfer.spec.ts.snap | 4 +- .../src/composables/useTreeDataStrategy.ts | 161 ++++++++++++------ .../composables/useTreeDataStrategyContext.ts | 21 ++- .../src/wrapper/TreeTransferWrapper.tsx | 58 +++---- 4 files changed, 147 insertions(+), 97 deletions(-) diff --git a/packages/pro/transfer/__tests__/__snapshots__/proTransfer.spec.ts.snap b/packages/pro/transfer/__tests__/__snapshots__/proTransfer.spec.ts.snap index 718c6d090..96c3c1284 100644 --- a/packages/pro/transfer/__tests__/__snapshots__/proTransfer.spec.ts.snap +++ b/packages/pro/transfer/__tests__/__snapshots__/proTransfer.spec.ts.snap @@ -1448,7 +1448,7 @@ exports[`ProTransfer > tree transfer render work 1`] = ` class="ix-transfer-header-label" > - 待选 (18) + 待选 (27) @@ -3113,7 +3113,7 @@ exports[`ProTransfer > tree transfer render work 1`] = ` class="ix-transfer-header-label" > - 已选 (2) + 已选 (3) diff --git a/packages/pro/transfer/src/composables/useTreeDataStrategy.ts b/packages/pro/transfer/src/composables/useTreeDataStrategy.ts index 443548c6a..e6f9961da 100644 --- a/packages/pro/transfer/src/composables/useTreeDataStrategy.ts +++ b/packages/pro/transfer/src/composables/useTreeDataStrategy.ts @@ -15,29 +15,20 @@ import { type ComputedRef, type Ref, ref, watch } from 'vue' import { type CascaderStrategy } from '@idux/components/cascader' import { combineTrees, filterTree, flattenTree, genFlattenedTreeKeys, traverseTree } from '../utils' -import { useTreeDataStrategyContext } from './useTreeDataStrategyContext' +import { type TreeDataStrategyContext, useTreeDataStrategyContext } from './useTreeDataStrategyContext' export function useTreeDataStrategies( props: ProTransferProps, childrenKey: ComputedRef, cascadeStrategy: ComputedRef, ): { - dataKeyMap: Map> - parentKeyMap: Map - dataStrategy: Ref>> + context: TreeDataStrategyContext + mergedDataStrategy: Ref>> } { - const { cachedTargetData, dataKeyMap, parentKeyMap, dataStrategy } = useTreeDataStrategyContext(props, childrenKey) - + const context = useTreeDataStrategyContext(props, childrenKey) const getMergedDataStrategy = () => ({ - ...dataStrategy, - ...createStrategy( - childrenKey, - cachedTargetData, - parentKeyMap, - dataKeyMap, - cascadeStrategy.value, - props.flatTargetData, - ), + ...context.baseDataStrategy, + ...createStrategy(context, childrenKey, cascadeStrategy.value, props.flatTargetData), }) const mergedDataStrategy = ref>>(getMergedDataStrategy()) watch([cascadeStrategy, childrenKey], () => { @@ -45,9 +36,8 @@ export function useTreeDataStrategies( }) return { - dataKeyMap, - parentKeyMap, - dataStrategy: mergedDataStrategy, + context, + mergedDataStrategy, } } @@ -122,8 +112,10 @@ function createSeparateDataSourceFn( childrenKey: ComputedRef, cachedTargetData: Ref[]>, cascaderStrategy: CascaderStrategy, - flatTargetData: boolean | 'all' = false, + targetDataCount: Ref, ): Exclude>['separateDataSource'], undefined> { + const targetDataKeySet = new Set() + const getFilterFn = ( selectedKeySet: Set, getKey: GetKeyFn, @@ -144,18 +136,23 @@ function createSeparateDataSourceFn( } })() - return (data, isSource) => { - if (isSource || (cascaderStrategy !== 'off' && !flatTargetData)) { - return filterTree(data, childrenKey.value, (item, parent) => filterFn(item, parent, isSource), 'or') - } - - return flattenTree( + return (data, isSource) => + filterTree( data, childrenKey.value, - item => ({ ...item, children: undefined }), - flatTargetData !== 'all' && cascaderStrategy !== 'off', - ).filter(item => selectedKeySet.has(getKey(item))) - } + (item, parent) => { + const filterRes = filterFn(item, parent, isSource) + + // set targetDataKeySet to collect targetData count later + // we collect this during filter process to avoid unecessary traveral + if (!isSource && filterRes) { + targetDataKeySet.add(getKey(item)) + } + + return filterRes + }, + 'or', + ) } return (data, _, selectedKeySet, getKey) => { @@ -164,6 +161,9 @@ function createSeparateDataSourceFn( const newTargetData = filterData(data, false) const previousTargetData = filterData(cachedTargetData.value, false) + targetDataCount.value = targetDataKeySet.size + targetDataKeySet.clear() + // combine new data with previous data // beacause we intend to cache selected data after dataSource changes const targetData = combineTrees(newTargetData, previousTargetData, childrenKey.value, getKey) @@ -176,36 +176,77 @@ function createSeparateDataSourceFn( } } -function createStrategy( +function createSeparateDataSourceFnWithFlatten( childrenKey: ComputedRef, cachedTargetData: Ref[]>, - parentKeyMap: Map, - dataKeyMap: Map>, + cascaderStrategy: CascaderStrategy, + targetDataCount: Ref, + flatTargetData: boolean | 'all' = false, +): Exclude>['separateDataSource'], undefined> { + const fn = createSeparateDataSourceFn(childrenKey, cachedTargetData, cascaderStrategy, targetDataCount) + + return (...args) => { + const { sourceData, targetData } = fn(...args) + + return { + sourceData, + targetData: flattenTargetTree(targetData, childrenKey.value, cascaderStrategy, flatTargetData), + } + } +} + +function flattenTargetTree( + data: TreeTransferData[], + childrenKey: C, + cascaderStrategy: CascaderStrategy, + flatTargetData: boolean | 'all', +) { + if (cascaderStrategy !== 'off' && !flatTargetData) { + return data + } + + return flattenTree( + data, + childrenKey, + item => ({ ...item, children: undefined }), + flatTargetData !== 'all' && cascaderStrategy !== 'off', + ) +} + +function createStrategy( + context: TreeDataStrategyContext, + childrenKey: ComputedRef, cascaderStrategy: CascaderStrategy, flatTargetData: boolean | 'all', ) { switch (cascaderStrategy) { case 'parent': - return createParentStrategy(childrenKey, cachedTargetData, dataKeyMap, flatTargetData) + return createParentStrategy(context, childrenKey, flatTargetData) case 'child': - return createChildStrategy(childrenKey, cachedTargetData, parentKeyMap, flatTargetData) + return createChildStrategy(context, childrenKey, flatTargetData) case 'off': - return createOffStrategy(childrenKey, cachedTargetData, flatTargetData) + return createOffStrategy(context, childrenKey, flatTargetData) case 'all': default: - return createAllStrategy(childrenKey, cachedTargetData, parentKeyMap, flatTargetData) + return createAllStrategy(context, childrenKey, flatTargetData) } } function createAllStrategy( + context: TreeDataStrategyContext, childrenKey: ComputedRef, - cachedTargetData: Ref[]>, - parentKeyMap: Map, flatTargetData: boolean | 'all', ): TransferDataStrategyProp> { + const { cachedTargetData, parentKeyMap, targetDataCount } = context return { genDisabledKeys: (data, getKey) => genDisabledKeys(data, getKey, childrenKey.value, true), - separateDataSource: createSeparateDataSourceFn(childrenKey, cachedTargetData, 'all', flatTargetData), + separateDataSource: createSeparateDataSourceFnWithFlatten( + childrenKey, + cachedTargetData, + 'all', + targetDataCount, + flatTargetData, + ), getAllSelectedKeys: (selected, data, selectedKeySet, disabledKeySet, getKey) => getAllSelectedKeys(selected, data, selectedKeySet, disabledKeySet, getKey, childrenKey.value), remove: (keys, selectedKeySet) => commonRemoveFn(keys, selectedKeySet, parentKeyMap), @@ -213,11 +254,12 @@ function createAllStrategy( } function createChildStrategy( + context: TreeDataStrategyContext, childrenKey: ComputedRef, - cachedTargetData: Ref[]>, - parentKeyMap: Map, flatTargetData: boolean | 'all', ): TransferDataStrategyProp> { + const { cachedTargetData, parentKeyMap, targetDataCount } = context + return { genDisabledKeys: (data, getKey) => genDisabledKeys(data, getKey, childrenKey.value, true), getAllSelectedKeys(selected, data, selectedKeySet, disabledKeySet, getKey) { @@ -237,19 +279,24 @@ function createChildStrategy( return keys }, - separateDataSource: createSeparateDataSourceFn(childrenKey, cachedTargetData, 'child', flatTargetData), + separateDataSource: createSeparateDataSourceFnWithFlatten( + childrenKey, + cachedTargetData, + 'child', + targetDataCount, + flatTargetData, + ), remove: (keys, selectedKeySet) => commonRemoveFn(keys, selectedKeySet, parentKeyMap), } } function createParentStrategy( + context: TreeDataStrategyContext, childrenKey: ComputedRef, - cachedTargetData: Ref[]>, - dataKeyMap: Map>, flatTargetData: boolean | 'all', ): TransferDataStrategyProp> { - const separateDataSource = createSeparateDataSourceFn(childrenKey, cachedTargetData, 'parent', flatTargetData) - let targetData: TreeTransferData[] = [] + const { cachedTargetData, dataKeyMap, targetDataCount } = context + const separateDataSource = createSeparateDataSourceFn(childrenKey, cachedTargetData, 'parent', targetDataCount) return { genDisabledKeys: (data, getKey) => genDisabledKeys(data, getKey, childrenKey.value, true), @@ -287,9 +334,11 @@ function createParentStrategy( return keys }, separateDataSource(data, _, selectedKeySet, getKey) { - const separatedData = separateDataSource(data, _, selectedKeySet, getKey) - targetData = separatedData.targetData - return separatedData + const { sourceData, targetData } = separateDataSource(data, _, selectedKeySet, getKey) + return { + sourceData, + targetData: flattenTargetTree(targetData, childrenKey.value, 'parent', flatTargetData), + } }, append(keys, selectedKey, getKey) { const newKeySet = new Set([...selectedKey, ...keys]) @@ -317,7 +366,7 @@ function createParentStrategy( } // it is not possible to get the correct selected keys from bottom to top - traverseTree(targetData, childrenKey.value, (item, parents) => { + traverseTree(cachedTargetData.value, childrenKey.value, (item, parents) => { if (keySet.has(getKey(item))) { const keysInChain = [item, ...parents].map(getKey) const selectedKeyIdx = keysInChain.findIndex(key => newKeySet.has(key)) @@ -351,13 +400,21 @@ function createParentStrategy( } function createOffStrategy( + context: TreeDataStrategyContext, childrenKey: ComputedRef, - cachedTargetData: Ref[]>, flatTargetData: boolean | 'all', ): TransferDataStrategyProp> { + const { cachedTargetData, targetDataCount } = context + return { genDisabledKeys: (data, getKey) => genDisabledKeys(data, getKey, childrenKey.value, false), - separateDataSource: createSeparateDataSourceFn(childrenKey, cachedTargetData, 'off', flatTargetData), + separateDataSource: createSeparateDataSourceFnWithFlatten( + childrenKey, + cachedTargetData, + 'off', + targetDataCount, + flatTargetData, + ), getAllSelectedKeys: (selected, data, selectedKeySet, disabledKeySet, getKey) => getAllSelectedKeys(selected, data, selectedKeySet, disabledKeySet, getKey, childrenKey.value), } diff --git a/packages/pro/transfer/src/composables/useTreeDataStrategyContext.ts b/packages/pro/transfer/src/composables/useTreeDataStrategyContext.ts index 3380f7aa0..97b1e5c9c 100644 --- a/packages/pro/transfer/src/composables/useTreeDataStrategyContext.ts +++ b/packages/pro/transfer/src/composables/useTreeDataStrategyContext.ts @@ -13,16 +13,20 @@ import { type ComputedRef, type Ref, onUnmounted, ref } from 'vue' import { filterTree, genFlattenedTreeKeys, traverseTree } from '../utils' -export function useTreeDataStrategyContext( - props: ProTransferProps, - childrenKey: ComputedRef, -): { +export interface TreeDataStrategyContext { + baseDataStrategy: TransferDataStrategyProp> dataKeyMap: Map> parentKeyMap: Map cachedTargetData: Ref[]> - dataStrategy: TransferDataStrategyProp> -} { + targetDataCount: Ref +} + +export function useTreeDataStrategyContext( + props: ProTransferProps, + childrenKey: ComputedRef, +): TreeDataStrategyContext { const cachedTargetData = ref(props.defaultTargetData ?? []) as Ref[]> + const targetDataCount = ref(0) const dataKeyMap: Map> = new Map() const parentKeyMap: Map = new Map() @@ -33,10 +37,11 @@ export function useTreeDataStrategyContext( }) return { + cachedTargetData, + targetDataCount, dataKeyMap, parentKeyMap, - cachedTargetData, - dataStrategy: { + baseDataStrategy: { genDataKeys: (data, getKey) => { return new Set(genFlattenedTreeKeys(data, childrenKey.value, getKey)) }, diff --git a/packages/pro/transfer/src/wrapper/TreeTransferWrapper.tsx b/packages/pro/transfer/src/wrapper/TreeTransferWrapper.tsx index 016836760..3ebe1e0f9 100644 --- a/packages/pro/transfer/src/wrapper/TreeTransferWrapper.tsx +++ b/packages/pro/transfer/src/wrapper/TreeTransferWrapper.tsx @@ -5,6 +5,7 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { TreeDataStrategyContext } from '../composables/useTreeDataStrategyContext' import type { CascaderStrategy } from '@idux/components/cascader' import type { GetKeyFn } from '@idux/components/utils' @@ -33,13 +34,11 @@ export default defineComponent({ const cascaderStrategy = computed(() => props.treeProps?.cascaderStrategy ?? 'all') const childrenKey = computed(() => props.treeProps?.childrenKey ?? 'children') - const { dataKeyMap, expandedKeysContext, dataStrategy } = useTreeContext( - props, - childrenKey, - cascaderStrategy, - getKey, - targetKeys, - ) + const { + dataStrategyContext: { dataKeyMap, targetDataCount }, + expandedKeysContext, + mergedDataStrategy, + } = useTreeContext(props, childrenKey, cascaderStrategy, getKey, targetKeys) const { dataSource, loadSourceChildren, loadTargetChildren } = useTransferData( props, getKey, @@ -67,29 +66,13 @@ export default defineComponent({ ) } - const renderTranferTreeHeaderLabel = (params: { isSource: boolean }) => { + const renderTranferTreeHeaderLabel = (params: { isSource: boolean; data: TreeTransferData[] }) => { if (slots.headerLabel) { return slots.headerLabel(params) } - const isSource = params.isSource - const label = isSource ? transferLocale.toSelect : transferLocale.selected - - let count = 0 - if (isSource) { - dataKeyMap?.forEach((item, key) => { - if (!targetKeySet.value.has(key) && (!item[childrenKey.value] || item[childrenKey.value]!.length <= 0)) { - ++count - } - }) - } else { - targetKeys.value?.forEach(key => { - const item = dataKeyMap?.get(key) - if (item && (!item[childrenKey.value] || item[childrenKey.value]!.length <= 0)) { - ++count - } - }) - } + const label = params.isSource ? transferLocale.toSelect : transferLocale.selected + const count = params.isSource ? (dataKeyMap?.size ?? 0) - targetDataCount.value : targetDataCount.value return `${label} (${count})` } @@ -98,7 +81,7 @@ export default defineComponent({ const transferProps = { dataSource: dataSource.value, defaultTargetData: props.defaultTargetData, - dataStrategy: dataStrategy.value as TransferDataStrategyProp, + dataStrategy: mergedDataStrategy.value as TransferDataStrategyProp, value: targetKeys.value, sourceSelectedKeys: props.sourceSelectedKeys, targetSelectedKeys: props.targetSelectedKeys, @@ -136,23 +119,28 @@ export default defineComponent({ }, }) -function useTreeContext( +function useTreeContext( props: ProTransferProps, - childrenKey: ComputedRef, + childrenKey: ComputedRef, cascadeStrategy: ComputedRef, getKey: ComputedRef, targetKeys: ComputedRef, ): { - dataKeyMap?: Map> - expandedKeysContext?: TreeExpandedKeysContext - dataStrategy: Ref>> + dataStrategyContext: TreeDataStrategyContext + expandedKeysContext: TreeExpandedKeysContext + mergedDataStrategy: Ref>> } { - const { dataKeyMap, parentKeyMap, dataStrategy } = useTreeDataStrategies(props, childrenKey, cascadeStrategy) + const { context: dataStrategyContext, mergedDataStrategy } = useTreeDataStrategies( + props, + childrenKey, + cascadeStrategy, + ) + const { parentKeyMap, dataKeyMap } = dataStrategyContext const expandedKeysContext = useTreeExpandedKeys(props, childrenKey, getKey, targetKeys, parentKeyMap, dataKeyMap) return { - dataKeyMap, + dataStrategyContext, expandedKeysContext, - dataStrategy, + mergedDataStrategy, } }