Skip to content

Commit

Permalink
feat: 支持列排序
Browse files Browse the repository at this point in the history
  • Loading branch information
Barrior committed Jul 4, 2024
1 parent a21610a commit fc44449
Show file tree
Hide file tree
Showing 18 changed files with 714 additions and 112 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
},
"devDependencies": {
"@ant-design/icons": "^5.0.1",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/css": "^11.10.6",
"@j-lints/commitlint-config": "^0.0.2",
"@j-lints/eslint-config-ts-react": "^0.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages/form-render-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FormRender from './FormRender'
export { cij } from './cssinjs'

export default FormRender

Expand Down
8 changes: 6 additions & 2 deletions packages/search-table-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@
"url": "https://github.com/Barrior/schema-render/issues"
},
"peerDependencies": {
"antd": "^5.0.0",
"@ant-design/icons": "^5.0.0"
"@ant-design/icons": "^5.0.0",
"antd": "^5.0.0"
},
"dependencies": {
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@schema-render/search-react": "^1.4.1"
}
}
31 changes: 24 additions & 7 deletions packages/search-table-react/src/SearchTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Ref } from 'react'
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'

import { EClassNames } from './constants'
import useColumns from './hooks/useColumns'
import { useBaseColumns, useFinalColumns, useSortColumns } from './hooks/useColumns'
import useRequest from './hooks/useRequest'
import useScrollY from './hooks/useScrollY'
import useSearch from './hooks/useSearch'
Expand All @@ -16,6 +16,18 @@ import type { ITableProps } from './typings/table'

const { classNames, isPlainObject } = utils

/**
* 备注:
* rawColumns: 原始列
* baseColumns: 基于 rawColumns 处理加工的基础列,如设置列宽、处理 valueType 等
* sortColumns: 基于 baseColumns 排序后的列(排序、显示/隐藏等配置)。
* finalColumns: 基于 sortColumns 最终渲染的列,1、过滤隐藏列,2、添加序号、操作列
*
* ▕▔▔▔▔▔ 还原 ▔▔▔▔▏
* ↓ ↑
* 原始列 基础列 排序列 最终列
* rawColumns -> baseColumns -> sortColumns -> finalColumns
*/
const SearchTable = (
{
className,
Expand All @@ -39,9 +51,9 @@ const SearchTable = (
const { forceUpdate } = useForceUpdate()

// 表格列配置数据处理
const { finalColumns } = useColumns({
table,
})
const { baseColumns } = useBaseColumns({ table })
const { sortColumns, sortModalHolder, openSortModal } = useSortColumns({ baseColumns })
const { finalColumns } = useFinalColumns({ table, sortColumns })

// 表格高度计算:“一屏显示”效果
const { scrollY, updateScrollY } = useScrollY({ table, rootElemRef })
Expand All @@ -64,9 +76,6 @@ const SearchTable = (
updateScrollY,
})

// 总结栏处理
const { finalSummary } = useSummary({ table, finalColumns, summaryData })

// 搜索栏
const { searchNodeHolder, searchRef } = useSearch({
loading,
Expand All @@ -81,13 +90,18 @@ const SearchTable = (
title,
loading,
runRequest,
openSortModal,
})

// 总结栏处理
const { finalSummary } = useSummary({ table, finalColumns, summaryData })

// 组件加载完毕请求一次数据
useEffect(() => {
if (requestOnMounted) {
runRequest()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

// 开放 API
Expand Down Expand Up @@ -116,6 +130,7 @@ const SearchTable = (
updateScrollY,
}))

// 公共插槽参数
const comRenderParams = { loading }

// 表格 onChange 事件处理
Expand Down Expand Up @@ -164,6 +179,8 @@ const SearchTable = (
/>

{footer?.(comRenderParams)}

{sortModalHolder}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { HolderOutlined } from '@ant-design/icons'
import { Button, InputNumber, Switch } from 'antd'
import type { ReactNode } from 'react'

interface IColumn {
title: string
dataIndex: string
width: number
render?: (value: any, onChange: (val: any) => void) => ReactNode
algin?: 'center'
}

const columns: IColumn[] = [
{
title: '列名称',
dataIndex: 'name',
width: 200,
},
{
title: '隐藏列',
dataIndex: 'hidden',
width: 90,
render: (value: boolean, onChange) => {
return <Switch checked={value} onChange={onChange} />
},
},
{
title: '宽度',
dataIndex: 'width',
width: 90,
render: (value: number, onChange) => {
return (
<InputNumber
min={1}
value={value}
onChange={(val) => {
val ? onChange(val) : undefined
}}
/>
)
},
},
{
title: '排序',
dataIndex: 'sort',
width: 50,
algin: 'center',
render: () => (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
/>
),
},
]

export default columns
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { cij } from '@schema-render/form-render-react'

export const header = cij`
display: flex;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
border-radius: 8px 8px 0 0;
font-weight: bold;
`

export const list = cij`
max-height: calc(100vh - 260px);
overflow: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 6px;
}
&::-webkit-scrollbar-thumb:hover {
background: #999;
}
`

export const rowWrapper = cij`
border-top: 1px solid #f0f0f0;
&:first-of-type {
border-top: none;
}
`

export const rowWrapperOverlay = cij`
background-color: #fff;
transform: scale(1.05);
border: 1px solid #f0f0f0;
border-radius: 8px;
transition: 200ms ease-out;
`

export const row = cij`
display: flex;
padding: 8px 0;
background-color: #fff;
&:hover {
background-color: #fafafa;
}
`

export const col = cij`
flex-grow: 1;
display: flex;
align-items: center;
padding: 0 12px;
word-break: break-all;
`

export const footer = cij`
text-align: right;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { useMemoizedFn } from '@schema-render/core-react'
import { Button, Space } from 'antd'
import type { FC } from 'react'
import { useState } from 'react'

import type { IColumnType } from '../../typings/table'
import Sortable from '../Sortable'
import columns from './index.column'
import * as styles from './index.style'

export interface IColumnSettingContentProps {
sortColumns: IColumnType<any>[]
defaultColumns: IColumnType<any>[]
onOk?: (newSortColumns: IColumnType<any>[]) => void
}

function createInitialDataSource(columns: IColumnType<any>[]) {
return columns.map((item) => ({
id: String(item.dataIndex),
name: item.title,
hidden: item.hidden,
width: item.width,
}))
}

const ColumnSettingContent: FC<IColumnSettingContentProps> = ({
defaultColumns,
sortColumns,
onOk,
}) => {
const [dataSource, setDataSource] = useState(() => createInitialDataSource(sortColumns))

// 配置数据变更事件
const handleChange = useMemoizedFn(
(index: number, dataKey: keyof (typeof dataSource)[0], newValue: any) => {
;(dataSource as any)[index][dataKey] = newValue
setDataSource([...dataSource])
}
)

// 排序事件
const handleSortChange = useMemoizedFn((newDataSource: typeof dataSource) => {
setDataSource(newDataSource)
})

// 重置本次排序
const handleResetCurrentSetting = useMemoizedFn(() => {
setDataSource(createInitialDataSource(sortColumns))
})

// 恢复默认排序
const handleRestoreDefaultSetting = useMemoizedFn(() => {
setDataSource(createInitialDataSource(defaultColumns))
})

// 配置完成事件
const handleOk = useMemoizedFn(() => {
const newSortColumns: IColumnType<any>[] = []
dataSource.forEach(({ id, width, hidden }) => {
const col = defaultColumns.find((item) => String(item.dataIndex) === id)
if (col) {
newSortColumns.push({
...col,
width,
hidden,
})
}
})
onOk?.(newSortColumns)
})

return (
<>
<div className={styles.header}>
{columns.map((col, i) => (
<div
className={styles.col}
style={{ width: col.width, justifyContent: col.algin }}
key={i}
>
{col.title}
</div>
))}
</div>

<div className={styles.list}>
<Sortable
items={dataSource}
onChange={handleSortChange}
itemClassName={styles.rowWrapper}
overlayClassName={styles.rowWrapperOverlay}
renderItem={(item, rowIndex, sortCtx) => {
return (
<div className={styles.row} key={rowIndex}>
{columns.map((col, colIndex) => {
const val = item[col.dataIndex as keyof typeof item]

// 排序手柄
const listeners = col.dataIndex === 'sort' ? sortCtx?.listeners : {}

return (
<div
className={styles.col}
style={{ width: col.width, justifyContent: col.algin }}
key={`${rowIndex}${colIndex}`}
{...listeners}
>
{col.render
? col.render(val as never, (newValue) =>
handleChange(rowIndex, col.dataIndex as never, newValue)
)
: val}
</div>
)
})}
</div>
)
}}
/>
</div>

<div className={styles.footer}>
<Space>
<Button onClick={handleRestoreDefaultSetting}>恢复默认设置</Button>
<Button onClick={handleResetCurrentSetting}>重置本次设置</Button>
<Button type="primary" onClick={handleOk}>
确定
</Button>
</Space>
</div>
</>
)
}

export default ColumnSettingContent
Loading

0 comments on commit fc44449

Please sign in to comment.