packages/next/src/select-table/utils.ts (198 lines of code) (raw):

import { isArr, isFn } from '@formily/shared' import { useFlatOptions } from './useFlatOptions' /** * 获取树列表某个键值的集合 * @param tree 树列表 * @param primaryKey 键名称 * @returns 键值数组集合 */ const getTreeKeys = (tree: any[], primaryKey: string) => isArr(tree) ? tree.reduce((prev, current) => { if (current?.disabled) { return prev } return [ ...prev, current[primaryKey], ...getTreeKeys(current?.children, primaryKey), ] }, []) : [] /** * 判断树列表中是否有任一 key 被选中 * @param tree 树列表 * @param selected 已选中的 keys * @param primaryKey 键名 * @returns */ const hasSelectedKey = (tree: any[], selected: any[], primaryKey: string) => { const keys = getTreeKeys(tree, primaryKey) const mergedKeys = [...keys, ...selected] const validKeys = [...new Set(mergedKeys)] return validKeys.length !== mergedKeys.length } /** * 判断列表项是否全部被选中 * @param list 一阶列表 * @param selected 当前选中的字段值集合 * @param primaryKey 键名称 * @returns 是否全部被选中 */ const isAllSelected = (list: any[], selected: any[], primaryKey: string) => { const validList = list.filter((item) => !item?.disabled) const selectedList = validList.filter((item) => selected?.includes(item[primaryKey]) ) return selectedList.length === validList.length } /** * 完善TableUI Keys(添加选中所有子元素的父元素,或移除未选中所有子元素的父元素) * @param flatDataSource 完整数据平铺列表 * @param selected 当前选中的字段值集合 * @param primaryKey 键名称 * @returns 完整的字段值集合 */ const completedKeys = ( flatDataSource: any[] = [], selected: any[], primaryKey: string ) => { let allSelectedKeys = [...selected] flatDataSource.forEach((item) => { if (item.children?.length) { // 优先递归子元素 allSelectedKeys = completedKeys( item.children, allSelectedKeys, primaryKey ) if (isAllSelected(item.children, allSelectedKeys, primaryKey)) { // 如果该元素的子元素全部选中,且该元素未禁用,则也选中该项(即包含全选子元素的父元素) if (!item?.disabled) { allSelectedKeys = [...new Set([...allSelectedKeys, item[primaryKey]])] } } else { // 如果该元素的子元素未全部选中,则移除该项 allSelectedKeys = allSelectedKeys.filter( (key) => key !== item[primaryKey] ) } } }) return allSelectedKeys } /** * 获取数列表中被选中的有效路径 * @param tree 数列表 * @param selected 当前选中的字段值集合 * @param primaryKey 键名称 * @returns 有效的树路径 */ const getSelectedPath = (tree = [], selected, primaryKey) => { const pathData = [] tree.forEach((item) => { const validChildren = getSelectedPath(item.children, selected, primaryKey) if (validChildren.length || selected?.includes(item[primaryKey])) { pathData.push({ ...item, ...(validChildren.length ? { children: validChildren } : {}), }) } }) return pathData } /** * 删除树列表的某个 key/value 键值对 * @param tree * @param key * @returns */ const deleteTreeItem = (tree: any[], key: string) => tree.map((item) => { const validItem = { ...item } delete validItem[key] if (validItem.children?.length) { validItem.children = deleteTreeItem(validItem.children, key) } return validItem }) /** * 根据 valueType 获取最终输出值 * @param keys 当前选中的 key 集合(all完整类型) * @param records 当前选中的 option 集合 * @param dataSource 数据源集合 * @param primaryKey 键名 * @param originalValueType 值输出类型 * @param originalOptionAsValue * @param mode * @param checkStrictly * @returns 最终输出的 keys 和 options */ const getOutputData = ( keys, // selected options, dataSource, primaryKey, originalValueType, originalOptionAsValue, mode, checkStrictly ) => { const valueType = checkStrictly !== false ? 'all' : originalValueType // valueType 在 Strictly 为 false 时生效 const optionAsValue = valueType === 'path' ? false : originalOptionAsValue // optionAsValue 在 path 模式不生效 let outputValue = [] let outputOptions = [] if (valueType === 'parent') { // 移除所有选中值的子值 let childrenKeys = [] options.forEach((option) => { childrenKeys = [ ...childrenKeys, ...getTreeKeys(option.children, primaryKey), ] }) outputValue = keys.filter((key) => !childrenKeys.includes(key)) outputOptions = options.filter((options) => outputValue.includes(options[primaryKey]) ) } else if (valueType === 'child') { outputValue = [...keys] outputOptions = [...options] outputOptions.forEach((option) => { // 移除当前有子值被选中的父值 if (hasSelectedKey(option.children, keys, primaryKey)) { outputValue = outputValue.filter((key) => key !== option[primaryKey]) outputOptions = outputOptions.filter( (options) => options[primaryKey] !== option[primaryKey] ) } }) } else if (valueType === 'path') { outputValue = getSelectedPath(dataSource, keys, primaryKey) outputOptions = [...options] } else { // valueType === 'all' outputValue = [...keys] outputOptions = [...options] } outputOptions = deleteTreeItem(outputOptions, '__formily_key__') outputValue = optionAsValue && valueType !== 'path' ? outputOptions : outputValue if (mode === 'single') { outputValue = outputValue[0] outputOptions = outputOptions[0] } return { outputValue, outputOptions } } /** * 根据 valueType 获取 TableUI 显示值 * @param keys 回填的数据(输出的)keys 集合 * @param flatDataSource 平铺的数据源集合 * @param primaryKey 键名称 * @param originalValueType 值输出类型 * @param originalOptionAsValue * @param mode * @param checkStrictly * @param rowKey * @returns [] TableUI keys 集合 */ const getUISelected = ( value, flatDataSource, primaryKey, originalValueType, originalOptionAsValue, mode, checkStrictly, rowKey ) => { const valueType = checkStrictly !== false ? 'all' : originalValueType // valueType 在 Strictly 为 false 时生效 const optionAsValue = valueType === 'path' ? false : originalOptionAsValue // optionAsValue 在 path 模式不生效 let keys = mode === 'single' ? [value] : isArr(value) ? value : [] keys = optionAsValue && valueType !== 'path' ? keys.map((record: any) => isFn(rowKey) ? rowKey(record) : record?.[primaryKey] ) : keys let newKeys = [] if (valueType === 'parent') { const options = flatDataSource.filter((item) => keys.includes(item[primaryKey]) ) let childrenKeys = [] options.forEach((option) => { childrenKeys = [ ...childrenKeys, ...getTreeKeys(option.children, primaryKey), ] }) newKeys = [...new Set([...keys, ...childrenKeys])] } else if (valueType === 'child') { newKeys = completedKeys(flatDataSource, keys, primaryKey) } else if (valueType === 'path') { const pathKeys = useFlatOptions(keys).map((item) => item[primaryKey]) newKeys = completedKeys(flatDataSource, pathKeys, primaryKey) } else { // valueType === 'all' newKeys = [...keys] } return newKeys } /** * 获取兼容筛选模式下是否全部选中子元素 * @param selected 已选中项 * @param dataSource 当前数据结构 * @param usableKeys 当前数据结构的可执行项 * @param checkStrictly * @param primaryKey * @returns 是否全部选中 */ const getCompatibleAllSelected = ( selected, dataSource, usableKeys, checkStrictly, primaryKey ) => { if (!usableKeys.length) { return false } // 当前模式下已选中的项 const currentSelected = selected.filter((item) => usableKeys.includes(item)) // 获取有效选中(父子模式或非父子模式) const validSelected = checkStrictly !== false ? currentSelected // 非父子模式选中项 : completedKeys(dataSource, currentSelected, primaryKey) // 父子模式选中项 // 有效选中项数量等于可执行项数量则全部选中子元素 return validSelected.length === usableKeys.length } export { hasSelectedKey, getTreeKeys, deleteTreeItem, isAllSelected, getUISelected, getOutputData, completedKeys, getCompatibleAllSelected, }