import { TablePipeline } from '../pipeline' import { makeRecursiveMapper, mergeCellProps, collectNodes, isLeafNode, isSelectColumn } from '../../utils' import type { ArtColumn, CellProps } from '../../interfaces' export const FILL_COLUMN_CODE = '$_fill_column_&' const stateKey = 'columnDrag' const SCROLL_SIZE = 30 function disableSelect(event) { event.preventDefault() } export interface ColumnDragOptions { onColumnDragStopped?: (columnMoved: boolean, columns: ArtColumn[]) => void, onColumnDragStart?: (startColumn: ArtColumn) => void } function sortColumns(columns: any[], sort: any) { const res = new Array(columns.length) const lastColumns = [...columns] while (columns.length) { const cloumn = columns.pop() res[sort[cloumn.code]] = cloumn } if (res.filter(Boolean).length !== lastColumns.length) { return lastColumns } return res } function stopClickPropagation(e) { e.stopPropagation() } export function columnDrag(opts: ColumnDragOptions = {}) { return (pipeline: TablePipeline) => { const { cloumnsTranslateData } = pipeline.getStateAtKey(stateKey, {} as any) const columns = pipeline.getColumns() const tableBody = pipeline.ref.current.domHelper && pipeline.ref.current.domHelper.tableBody // if (cloumnsSortData) { // columns = sortColumns(columns, cloumnsSortData) // } pipeline.columns(columns.filter(column => column)) return pipeline.mapColumns( makeRecursiveMapper((col, recursiveFlatMapInfo) => { const { path, isLeaf } = recursiveFlatMapInfo const style: any = cloumnsTranslateData ? { transition: '.3s', transform: `translate3d(${cloumnsTranslateData[col.code]}px, 0px, 0px)` } : {} const prevGetCellProps = col.getCellProps // !col.code: 选择列 col.lock: 固定列 不允许拖拽 if (col.lock || !col.code) return col return { ...col, getCellProps(value: any, record: any, rowIndex: number): CellProps { const prevCellProps = prevGetCellProps?.(value, record, rowIndex) return mergeCellProps(prevCellProps, { style: style as any }) }, headerCellProps: mergeCellProps(col.headerCellProps, { onMouseDown: !isLeaf || path.length > 1 ? undefined : (e) => { if (e.button !== 0 || !e.currentTarget.contains(e.target as HTMLElement)) { return } window.addEventListener('selectstart', disableSelect) let columnMoved = false const columns = pipeline.getColumns() let { cloumnsTranslateData } = pipeline.getStateAtKey(stateKey, {} as any) const cloumnsSortData = {} columns.forEach((item, index) => { cloumnsSortData[item.code] = index }) let currentTarget = e.currentTarget as HTMLElement const rect = (e.currentTarget as HTMLElement).parentElement.getClientRects()[0] const startX = rect.left const mouseDownClientX = e.clientX const mouseDownClientY = e.clientY let moveData = [] const allColumns = collectNodes(columns) const tableBodyClientRect = tableBody.getBoundingClientRect() const startScrollLeft = pipeline.ref.current.domHelper.tableBody.scrollLeft const updateScrollPosition = (client) => { const { clientX } = client const { left, width } = tableBodyClientRect if (clientX + SCROLL_SIZE >= left + width) { pipeline.ref.current.domHelper.tableBody.scrollLeft += SCROLL_SIZE } if (clientX - SCROLL_SIZE <= left) { pipeline.ref.current.domHelper.tableBody.scrollLeft -= SCROLL_SIZE } } function handleMouseMove(e) { const client = { clientX: e.clientX, clientY: e.clientY } const scrollDistance = pipeline.ref.current.domHelper.tableBody.scrollLeft - startScrollLeft const leftPosition = startX - scrollDistance // 表头最左边起点 updateScrollPosition(client) if (e.clientX - leftPosition < 20) { return } else { e.stopPropagation() } document.body.style.userSelect = 'none' currentTarget.style.cursor = 'move' // 重置位置信息 cloumnsTranslateData = {} allColumns.forEach((item) => { cloumnsTranslateData[item.code] = 0 }) // 计算平移位置 let replaceIndex = 0 let totalWitdth = getColumnWidth(columns[replaceIndex]) while (totalWitdth < e.clientX - leftPosition && replaceIndex < columns.length - 1) { replaceIndex++ totalWitdth += getColumnWidth(columns[replaceIndex]) } // 需要取最新startIndex, 不能直接用makeRecursiveMapper提供的startIndex(因为map时还没添加选择列、充满列等后面use添加的列) let startIndex columns.forEach((column, index) => { if (column.code === col.code) { startIndex = index } }) const optionColumn = columns[startIndex] let index = replaceIndex if (startIndex > replaceIndex) { // 左移 while (index < startIndex) { const { code, lock, width, children } = columns[index] if (enableMove({ code, lock })) { cloumnsTranslateData[code] += optionColumn.width if (isLeafNode(columns[index])) { cloumnsTranslateData[optionColumn.code] -= width } else { cloumnsTranslateData[optionColumn.code] -= getColumnWidth(columns[index]) moveAllChildren(children, cloumnsTranslateData, optionColumn.width) } columnMoved = true } index++ } } else if (startIndex < replaceIndex) { // 右移 while (startIndex < index) { const { code, lock, width, children } = columns[index] if (enableMove({ code, lock })) { cloumnsTranslateData[code] -= optionColumn.width if (isLeafNode(columns[index])) { cloumnsTranslateData[optionColumn.code] += width } else { cloumnsTranslateData[optionColumn.code] += getColumnWidth(columns[index]) moveAllChildren(children, cloumnsTranslateData, optionColumn.width, true) } columnMoved = true } index-- } } window.requestAnimationFrame(() => { pipeline.setStateAtKey(stateKey, { cloumnsTranslateData }) moveData = [startIndex, replaceIndex] }) } function handleMouseUp(e) { document.body.removeEventListener('mousemove', handleMouseMove) document.body.removeEventListener('mouseup', handleMouseUp) window.removeEventListener('selectstart', disableSelect) if (_isMoveWhenClicking(mouseDownClientX, mouseDownClientY, e.clientX, e.clientY)) { e.stopPropagation() // 存在移动就阻止冒泡 currentTarget.addEventListener('click', stopClickPropagation) // 阻止列头点击事件,防止拖动后触发列头过滤事件 } window.requestAnimationFrame(() => { // 取消阻止列头点击事件 currentTarget.removeEventListener('click', stopClickPropagation) currentTarget = null const [startIndex, replaceIndex] = moveData const optionColumn = columns[startIndex] let index = replaceIndex if (startIndex > replaceIndex) { // 左移 while (index < startIndex) { const { code, lock } = columns[index] if (enableMove({ code, lock })) { cloumnsSortData[code] += 1 cloumnsSortData[optionColumn.code] -= 1 columnMoved = true } index++ } } else if (startIndex < replaceIndex) { // 右移 while (index > startIndex) { const { code, lock } = columns[index] if (enableMove({ code, lock })) { cloumnsSortData[code] -= 1 cloumnsSortData[optionColumn.code] += 1 columnMoved = true } index-- } } const { onColumnDragStopped } = opts // 拖拽结束返回列顺序 if (onColumnDragStopped) { const newColumns = sortColumns(columns, cloumnsSortData).filter((column) => column.code !== FILL_COLUMN_CODE && !isSelectColumn(column)) // TODO drag需要在resize之后use,否则这里返回的列对应的宽度不是拖拽后的 onColumnDragStopped(columnMoved, newColumns) } pipeline.setStateAtKey(stateKey, { cloumnsTranslateData: null }) }) document.body.style.userSelect = '' currentTarget.style.opacity = '' currentTarget.style.cursor = '' } const { onColumnDragStart } = opts onColumnDragStart && onColumnDragStart(col) document.body.addEventListener('mousemove', handleMouseMove) document.body.addEventListener('mouseup', handleMouseUp) }, style }) } }) ) } } function enableMove({ lock, code }) { return code && code !== FILL_COLUMN_CODE && !lock } function getColumnWidth(col: ArtColumn): number { if (col.children) { return col.children.reduce((acc, col) => { return acc + getColumnWidth(col) }, 0) } return col.width } function moveAllChildren(cols: ArtColumn[], cloumnsTranslateData, width: number, isMinus?: boolean) { cols.forEach(col => { const { code, children } = col const movedWidth = cloumnsTranslateData[code] ?? 0 cloumnsTranslateData[code] = movedWidth + (isMinus ? -width : width) if (!isLeafNode(col)) { moveAllChildren(children, cloumnsTranslateData, width) } }) } function _isMoveWhenClicking(mouseDownClientX: number, mouseDownClientY: number, mouseUpClientX: number, mouseUpClientY: number): boolean { const xDiff = mouseUpClientX - mouseDownClientX const yDiff = mouseUpClientY - mouseDownClientY // 鼠标点按和松开的偏移量大于5px,认为存在移动 if (Math.sqrt(xDiff * xDiff + yDiff * yDiff) > 5) { return true } return false }