diff --git a/packages/pro/table/src/composables/useColumns.ts b/packages/pro/table/src/composables/useColumns.ts index 80d776cec..dd1d8dbf0 100644 --- a/packages/pro/table/src/composables/useColumns.ts +++ b/packages/pro/table/src/composables/useColumns.ts @@ -7,14 +7,18 @@ import { type ComputedRef, computed, watch } from 'vue' -import { type VKey, callEmit, useState } from '@idux/cdk/utils' +import { type VKey, callEmit, filterTree, useState } from '@idux/cdk/utils' import { ɵGetColumnKey } from '@idux/components/table' import { type ProTableConfig } from '@idux/pro/config' import { type ProTableColumn, type ProTableProps } from '../types' -import { loopColumns } from '../utils' export interface ColumnsContext { + checkedColumnKeys: ComputedRef<{ + start: VKey[] + center: VKey[] + end: VKey[] + }> mergedColumns: ComputedRef setMergedColumns: (columns: ProTableColumn[]) => void mergedColumnMap: ComputedRef> @@ -25,19 +29,58 @@ export interface ColumnsContext { export function useColumns(props: ProTableProps, config: ProTableConfig): ColumnsContext { const originalColumns = computed(() => props.columns) const [mergedColumns, setMergedColumns] = useState(mergeColumns(originalColumns.value, config)) - const mergedColumnMap = computed(() => { + + const mergedContext = computed(() => { const map = new Map() - loopColumns(mergedColumns.value, column => map.set(column.key!, column)) - return map + const checkedKeys = { + start: [] as VKey[], + center: [] as VKey[], + end: [] as VKey[], + } + + const displayColumns = filterTree( + mergedColumns.value as (ProTableColumn & { children?: ProTableColumn[] })[], + 'children', + (column, parents) => { + const key = column.key! + map.set(key, column) + + if (column.visible === false || parents.some(parent => parent.visible === false)) { + return false + } + + if ( + !column.children?.length && + column.layoutable !== false && + parents.every(parent => parent.layoutable !== false) + ) { + if (isFixed('start', column, parents)) { + checkedKeys.start.push(key) + } else if (isFixed('end', column, parents)) { + checkedKeys.end.push(key) + } else { + checkedKeys.center.push(key) + } + } + + return true + }, + 'and', + ) as ProTableColumn[] + + return { map, checkedKeys, displayColumns } }) - const displayColumns = computed(() => getDisplayColumns(mergedColumns.value)) + + const mergedColumnMap = computed(() => mergedContext.value.map) + const checkedColumnKeys = computed(() => mergedContext.value.checkedKeys) + const displayColumns = computed(() => mergedContext.value.displayColumns) watch(originalColumns, columns => setMergedColumns(mergeColumns(columns, config))) watch(mergedColumns, newColumns => callEmit(props.onColumnsChange, newColumns)) const resetColumns = () => setMergedColumns(mergeColumns(originalColumns.value, config)) - return { mergedColumns, setMergedColumns, mergedColumnMap, displayColumns, resetColumns } + return { checkedColumnKeys, mergedColumns, setMergedColumns, mergedColumnMap, displayColumns, resetColumns } } function mergeColumns(columns: ProTableColumn[], config: ProTableConfig, parentKey?: VKey) { @@ -54,21 +97,6 @@ function convertMergeColumn(column: ProTableColumn, config: ProTableConfig, pare return mergeColumn } -function getDisplayColumns(columns: ProTableColumn[]): ProTableColumn[] { - const result: ProTableColumn[] = [] - columns.forEach(column => { - if (column.visible === false) { - return - } - if ('children' in column && column.children) { - const newChildren = getDisplayColumns(column.children) - if (newChildren.length === 0) { - return - } - result.push({ ...column, children: newChildren }) - } else { - result.push(column) - } - }) - return result +function isFixed(fixed: 'start' | 'end', column: ProTableColumn, parents: ProTableColumn[]) { + return column.fixed === fixed || parents.some(parent => parent.fixed === fixed) } diff --git a/packages/pro/table/src/contents/LayoutToolContent.tsx b/packages/pro/table/src/contents/LayoutToolContent.tsx index 10afa2ba3..dc1ee8bfe 100644 --- a/packages/pro/table/src/contents/LayoutToolContent.tsx +++ b/packages/pro/table/src/contents/LayoutToolContent.tsx @@ -5,23 +5,38 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { TreeDragDropOptions } from '@idux/components/tree' + import { computed, defineComponent, inject, normalizeClass } from 'vue' -import { callEmit, useControlledProp } from '@idux/cdk/utils' +import { type VKey, callEmit, filterTree, mapTree, useControlledProp } from '@idux/cdk/utils' import { ɵInput } from '@idux/components/_private/input' import { IxButton } from '@idux/components/button' import { IxCheckbox } from '@idux/components/checkbox' +import { IxEmpty } from '@idux/components/empty' import LayoutToolTree from './LayoutToolTree' import { proTableToken } from '../token' -import { type ProTableColumn, proTableLayoutToolContentProps } from '../types' -import { loopColumns } from '../utils' +import { type ProTableColumn, type ProTableColumnBase, proTableLayoutToolContentProps } from '../types' + +enum ToolTreeKey { + start, + center, + end, +} export default defineComponent({ props: proTableLayoutToolContentProps, setup(props) { - const { locale, mergedPrefixCls, mergedColumns, setMergedColumns, mergedColumnMap, resetColumns } = - inject(proTableToken)! + const { + locale, + mergedPrefixCls, + mergedColumns, + checkedColumnKeys, + setMergedColumns, + mergedColumnMap, + resetColumns, + } = inject(proTableToken)! // 判断是否有子节点,处理tree展开节点样式 const hasChildren = computed(() => mergedColumns.value.length !== mergedColumnMap.value.size) @@ -31,6 +46,29 @@ export default defineComponent({ const [searchValue, setSearchValue] = useControlledProp(props, 'searchValue') + const filteredColumns = computed(() => { + const lowerCasedSearchValue = searchValue.value?.toLowerCase() + + return filterTree( + mergedColumns.value as (ProTableColumn & { children?: ProTableColumn[] })[], + 'children', + column => { + if (column.layoutable === false) { + return false + } + + if (!lowerCasedSearchValue) { + return true + } + + const { title } = column as ProTableColumnBase + + return title ? title.toLowerCase().includes(lowerCasedSearchValue) : false + }, + ) as ProTableColumn[] + }) + const groupedColumns = computed(() => groupColumns(filteredColumns.value)) + const handleInput = (evt: Event) => { const inputValue = (evt.target as HTMLInputElement).value setSearchValue(inputValue) @@ -42,27 +80,92 @@ export default defineComponent({ } const handleCheckAll = (checked: boolean) => { - loopColumns(mergedColumns.value, column => { - // use child cascaderStrategy,undefined or true - if (!column.children && column.changeVisible !== false) { - column.visible = checked - } - }) - handleFixedChange() + const newColumns = mapTree( + mergedColumns.value as (ProTableColumn & { children?: ProTableColumn[] })[], + 'children', + (column, parents) => { + const newColumn = { ...column } as ProTableColumn + + if ( + !column.children?.length && + column.changeVisible !== false && + column.layoutable !== false && + parents.every(parent => parent.layoutable !== false) + ) { + newColumn.visible = checked + } + + return newColumn + }, + ) + + setMergedColumns(newColumns) } - const handleFixedChange = () => { - const { startColumns, centerColumns, endColumns } = groupColumns(mergedColumns.value) - setMergedColumns([...startColumns, ...centerColumns, ...endColumns]) + const handleFixedChange = (changedColumn: ProTableColumn, fixed: 'start' | 'end' | undefined) => { + const newColumns = mapTree( + mergedColumns.value as (ProTableColumn & { children?: ProTableColumn[] })[], + 'children', + (column, parents) => { + const newColumn = { ...column } as ProTableColumn + + if (column.key === changedColumn.key || parents.some(parent => parent.key === changedColumn.key)) { + newColumn.fixed = fixed + } + + return newColumn + }, + ) + + setMergedColumns(newColumns) } - const handleVisibleChange = () => { - setMergedColumns([...mergedColumns.value]) + const handleVisibleChange = (showedKeySet: Set, hiddenKeys: Set) => { + const newColumns = mapTree( + mergedColumns.value as (ProTableColumn & { children?: ProTableColumn[] })[], + 'children', + column => { + const newColumn = { ...column } as ProTableColumn + if (showedKeySet.has(column.key!)) { + newColumn.visible = true + } + + if (hiddenKeys.has(column.key!)) { + newColumn.visible = false + } + + return newColumn + }, + ) + + setMergedColumns(newColumns) } - return () => { + const handleDrop = (key: ToolTreeKey, options: TreeDragDropOptions) => { + const { dragNode, dropNode, dropType } = options + + const dragKey = dragNode?.key + const dropKey = dropNode?.key + if (!dragKey || !dropKey) { + return + } + const { startColumns, centerColumns, endColumns } = groupColumns(mergedColumns.value) + const processedColumns = [startColumns, centerColumns, endColumns].reduce((res, columns, index) => { + if (index === key) { + return res.concat(getDropColumns(columns, dragKey, dropKey, dropType)) + } + + return res.concat(columns) + }, [] as ProTableColumn[]) + + setMergedColumns(processedColumns) + } + + return () => { + const { startColumns, centerColumns, endColumns } = groupedColumns.value + const hasStartFixed = startColumns.length > 0 const hasEndFixed = endColumns.length > 0 const withFixed = hasStartFixed || hasEndFixed @@ -75,15 +178,6 @@ export default defineComponent({ }) const { placeholder, all, reset, startPinTitle, noPinTitle, endPinTitle } = locale.table.layout - const handleDrop = (key: 'start' | 'center' | 'end', columns: ProTableColumn[]) => { - if (key === 'start') { - setMergedColumns([...columns, ...centerColumns, ...endColumns]) - } else if (key === 'center') { - setMergedColumns([...startColumns, ...columns, ...endColumns]) - } else { - setMergedColumns([...startColumns, ...centerColumns, ...columns]) - } - } const settingLength = mergedColumns.value.length const hiddenLength = hiddenColumns.value.length @@ -111,41 +205,48 @@ export default defineComponent({ )} -
- {hasStartFixed && ( - - )} - {(!withFixed || centerColumns.length > 0) && ( - - )} - {hasEndFixed && ( - - )} -
+ {filteredColumns.value.length ? ( +
+ {hasStartFixed && ( + handleDrop(ToolTreeKey.start, options)} + onFixedChange={handleFixedChange} + onVisibleChange={handleVisibleChange} + /> + )} + {(!withFixed || centerColumns.length > 0) && ( + handleDrop(ToolTreeKey.center, options)} + onFixedChange={handleFixedChange} + onVisibleChange={handleVisibleChange} + /> + )} + {hasEndFixed && ( + handleDrop(ToolTreeKey.end, options)} + onFixedChange={handleFixedChange} + onVisibleChange={handleVisibleChange} + /> + )} +
+ ) : ( + + )} ) } @@ -170,3 +271,51 @@ function groupColumns(mergedColumns: ProTableColumn[]) { return { startColumns, endColumns, centerColumns } } + +function getDropColumns(columns: ProTableColumn[], dragKey: VKey, dropKey: VKey, dropType: string | undefined) { + const newColumns = [...columns] + // 原数据中移除被拖拽的节点 + let dragColumn: ProTableColumn + findTargetColumn(newColumns, dragKey, (column, index, columns) => { + dragColumn = column + columns.splice(index, 1) + }) + + if (dropType === 'inside') { + findTargetColumn(newColumns, dropKey, item => { + // 添加到头部 + ;(item as ProTableColumnBase).children!.unshift(dragColumn) + }) + } else { + let targetIndex: number + let targetColumns: ProTableColumn[] = [] + + findTargetColumn(newColumns, dropKey, (_, index, columns) => { + targetIndex = index + targetColumns = columns + }) + + if (dropType === 'before') { + targetColumns.splice(targetIndex!, 0, dragColumn!) + } else { + targetColumns.splice(targetIndex! + 1, 0, dragColumn!) + } + } + return newColumns +} + +function findTargetColumn( + columns: ProTableColumn[], + key: VKey, + callback: (column: ProTableColumn, index: number, columns: ProTableColumn[]) => void, +) { + for (let index = 0; index < columns.length; index++) { + const column = columns[index] + if (column.key! === key) { + return callback(column, index, columns) + } + if ('children' in column && column.children) { + findTargetColumn(column.children, key, callback) + } + } +} diff --git a/packages/pro/table/src/contents/LayoutToolTree.tsx b/packages/pro/table/src/contents/LayoutToolTree.tsx index edd6a842d..9a915e0d9 100644 --- a/packages/pro/table/src/contents/LayoutToolTree.tsx +++ b/packages/pro/table/src/contents/LayoutToolTree.tsx @@ -5,46 +5,52 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type PropType, computed, defineComponent, inject } from 'vue' +import type { ProTableColumn } from '../types' +import type { VKey } from '@idux/cdk/utils' + +import { type PropType, defineComponent, inject } from 'vue' -import { type VKey, useKey } from '@idux/cdk/utils' import { IxIcon } from '@idux/components/icon' import { IxTree, type TreeDragDropOptions, type TreeProps } from '@idux/components/tree' import { proTableToken } from '../token' -import { type ProTableColumn, type ProTableColumnBase } from '../types' -import { getColumnTitle, loopColumns } from '../utils' +import { getColumnTitle } from '../utils' export default defineComponent({ props: { columns: { type: Array as PropType, required: true }, + checkedKeys: { type: Array as PropType, required: true }, searchValue: { type: String, default: undefined }, title: { type: String, default: undefined }, - onDrop: { type: Function, required: true }, - onFixedChange: { type: Function, required: true }, - onVisibleChange: { type: Function, required: true }, + onDrop: { type: Function as PropType<(options: TreeDragDropOptions) => void>, required: true }, + onFixedChange: { + type: Function as PropType<(column: ProTableColumn, fixed: 'start' | 'end' | undefined) => void>, + required: true, + }, + onVisibleChange: { + type: Function as PropType<(showedKeys: Set, hiddenKeys: Set) => void>, + required: true, + }, }, setup(props) { - const key = useKey() const { locale, mergedPrefixCls } = inject(proTableToken)! - const checkedKeys = computed(() => getCheckedKeys(props.columns)) + const onCheckedChange = (checkedKeys: VKey[]) => { + const visibleKeySet = new Set(checkedKeys) + const oldVisibleKeys = new Set(props.checkedKeys) - const onCheck = (checked: boolean, column: ProTableColumn) => { - if (!column.children) { - column.visible = checked - } else { - loopColumns(column.children, child => { - if (!child.children && child.changeVisible !== false) { - child.visible = checked - } - }) - } - props.onVisibleChange() + checkedKeys.forEach(key => { + if (oldVisibleKeys.has(key)) { + visibleKeySet.delete(key) + oldVisibleKeys.delete(key) + } + }) + + props.onVisibleChange(visibleKeySet, oldVisibleKeys) } const onDrop = (options: TreeDragDropOptions) => { - const { dragNode, dropNode, dropType } = options + const { dragNode, dropNode } = options const dragKey = dragNode?.key const dropKey = dropNode?.key @@ -52,44 +58,41 @@ export default defineComponent({ return } - const columns = getDropColumns(props.columns, dragKey, dropKey, dropType) - props.onDrop!(key, columns) + props.onDrop!(options) } const onFixedChange = (fixed: 'start' | 'end' | undefined, column: ProTableColumn, evt: MouseEvent) => { evt.preventDefault() - column.fixed = fixed - loopColumns(column.children, child => { - child.fixed = fixed - }) - props.onFixedChange() + evt.stopImmediatePropagation() + props.onFixedChange(column, fixed) } return () => { - const dataSource = props.columns.filter(column => column.layoutable !== false) // 不要显示空状态 - if (dataSource.length === 0) { + if (props.columns.length === 0) { return } - const { searchValue, title } = props + const prefixCls = `${mergedPrefixCls.value}-layout-tool-tree` + const { columns, searchValue, title } = props const { startPin, endPin, noPin } = locale.table.layout const treeProps: TreeProps = { blocked: true, - cascaderStrategy: 'all', + cascaderStrategy: 'child', checkable: true, - checkedKeys: checkedKeys.value, + checkedKeys: props.checkedKeys, checkOnClick: true, draggable: true, - dataSource, + dataSource: columns, disabled: disableColumn, empty: '', childrenKey: 'children', getKey: 'key', labelKey: 'title', searchValue, - onCheck, + searchFn: () => true, + onCheckedChange, onDrop, } @@ -138,8 +141,6 @@ export default defineComponent({ }, } - const prefixCls = `${mergedPrefixCls.value}-layout-tool-tree` - return (
{title &&
{title}
} @@ -161,71 +162,3 @@ function disableColumn(column: ProTableColumn) { drag: changeIndex === false, } } - -function getCheckedKeys(columns: ProTableColumn[]) { - const keys: VKey[] = [] - findDisplayKeys(columns, keys) - return keys -} - -function findDisplayKeys(columns: ProTableColumn[], keys: VKey[]) { - for (let index = 0; index < columns.length; index++) { - const column = columns[index] - if (column.visible === false) { - continue - } - if ('children' in column && column.children) { - findDisplayKeys(column.children, keys) - } else { - keys.push(column.key!) - } - } -} - -function getDropColumns(columns: ProTableColumn[], dragKey: VKey, dropKey: VKey, dropType: string | undefined) { - const newColumns = [...columns] - // 原数据中移除被拖拽的节点 - let dragColumn: ProTableColumn - findTargetColumn(newColumns, dragKey, (column, index, columns) => { - dragColumn = column - columns.splice(index, 1) - }) - - if (dropType === 'inside') { - findTargetColumn(newColumns, dropKey, item => { - // 添加到头部 - ;(item as ProTableColumnBase).children!.unshift(dragColumn) - }) - } else { - let targetIndex: number - let targetColumns: ProTableColumn[] = [] - - findTargetColumn(newColumns, dropKey, (_, index, columns) => { - targetIndex = index - targetColumns = columns - }) - - if (dropType === 'before') { - targetColumns.splice(targetIndex!, 0, dragColumn!) - } else { - targetColumns.splice(targetIndex! + 1, 0, dragColumn!) - } - } - return newColumns -} - -function findTargetColumn( - columns: ProTableColumn[], - key: VKey, - callback: (column: ProTableColumn, index: number, columns: ProTableColumn[]) => void, -) { - for (let index = 0; index < columns.length; index++) { - const column = columns[index] - if (column.key! === key) { - return callback(column, index, columns) - } - if ('children' in column && column.children) { - findTargetColumn(column.children, key, callback) - } - } -} diff --git a/packages/pro/table/src/types.ts b/packages/pro/table/src/types.ts index cfd91d0d5..b65daa13a 100644 --- a/packages/pro/table/src/types.ts +++ b/packages/pro/table/src/types.ts @@ -74,6 +74,7 @@ export interface ProTableColumnBase ProTableColumnLayoutConfig { copyable?: boolean editable?: boolean + title?: string tooltip?: string | TooltipProps tooltipIcon?: string diff --git a/packages/pro/table/src/utils/index.ts b/packages/pro/table/src/utils/index.ts index 957c644cb..f2e0a9e5f 100644 --- a/packages/pro/table/src/utils/index.ts +++ b/packages/pro/table/src/utils/index.ts @@ -10,18 +10,6 @@ import { type ProTableLocale } from '@idux/pro/locales' import { type ProTableColumn } from '../types' -export function loopColumns(columns: ProTableColumn[] | undefined, callback: (column: ProTableColumn) => void): void { - if (!columns || columns.length === 0) { - return - } - columns.forEach(column => { - callback(column) - if ('children' in column) { - loopColumns(column.children!, callback) - } - }) -} - export function getColumnTitle(column: ProTableColumn, locale: ProTableLocale): string { const { title, type } = column if (title) {