You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
13 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import {
SettingOutlined,
VerticalAlignMiddleOutlined,
VerticalAlignTopOutlined,
VerticalAlignBottomOutlined
} from '@ant-design/icons-vue'
import { Checkbox, Tree, Popover, Tooltip, Space } from 'ant-design-vue'
import 'ant-design-vue/es/checkbox/style/index.less'
import 'ant-design-vue/es/tree/style/index.less'
import 'ant-design-vue/es/popover/style/index.less'
import 'ant-design-vue/es/tooltip/style/index.less'
import 'ant-design-vue/es/space/style/index.less'
import type { TableColumnType } from 'ant-design-vue'
import type { DataNode } from 'ant-design-vue/es/tree'
import { genColumnKey } from '../../utils'
import type { ProColumns } from '../../typing'
import './index.less'
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface'
import type { FunctionalComponent, PropType } from 'vue'
import { useIntl } from '#/provider'
import { getPrefixCls } from '#/layout/RouteContext'
import type { VueKey, VueNode } from '#/types'
import type { ColumnsState } from '#/table/container'
import { useContainer } from '#/table/container'
import { computed, ref, watchEffect } from 'vue'
import omit from 'ant-design-vue/es/_util/omit'
type ColumnSettingProps<T = any> = {
columns: TableColumnType<T>[]
draggable?: boolean
checkable?: boolean
extra?: VueNode
checkedReset?: boolean
listsHeight?: number
}
const ToolTipIcon: FunctionalComponent<{
title: string
columnKey: string | number
show: boolean
fixed: 'left' | 'right' | undefined
}> = ({ title, show, columnKey, fixed }, { slots }) => {
const { columnsMap, setColumnsMap } = useContainer()!
if (!show) {
return null
}
return (
<Tooltip title={title}>
<span
onClick={e => {
e.stopPropagation()
e.preventDefault()
const config = columnsMap.value[columnKey] || {}
const disableIcon = typeof config.disable === 'boolean' && config.disable
if (disableIcon) return
const columnKeyMap = {
...columnsMap.value,
[columnKey]: { ...config, fixed } as ColumnsState
}
setColumnsMap(columnKeyMap)
}}
>
{slots.default?.()}
</span>
</Tooltip>
)
}
const CheckboxListItem: FunctionalComponent<{
columnKey: string | number
className?: string
title?: VueNode
fixed?: boolean | 'left' | 'right'
hasParent?: boolean // 等于 react 版本的 isLeaf 属性,由于 ant-design-vue 会默认给 isLeaf 属性赋值,这里需要一个额外的属性来处理
}> = ({ columnKey, hasParent, title, className, fixed }) => {
const intl = useIntl()
const dom = (
<span class={`${className}-list-item-option`}>
<ToolTipIcon
columnKey={columnKey}
fixed="left"
title={intl.getMessage('tableToolBar.leftPin', '固定在列首')}
show={fixed !== 'left'}
>
<VerticalAlignTopOutlined />
</ToolTipIcon>
<ToolTipIcon
columnKey={columnKey}
fixed={undefined}
title={intl.getMessage('tableToolBar.noPin', '不固定')}
show={!!fixed}
>
<VerticalAlignMiddleOutlined />
</ToolTipIcon>
<ToolTipIcon
columnKey={columnKey}
fixed="right"
title={intl.getMessage('tableToolBar.rightPin', '固定在列尾')}
show={fixed !== 'right'}
>
<VerticalAlignBottomOutlined />
</ToolTipIcon>
</span>
)
return (
<span class={`${className}-list-item`} key={columnKey}>
<div class={`${className}-list-item-title`}>{title}</div>
{!hasParent ? dom : null}
</span>
)
}
const CheckboxList = defineComponent({
name: 'CheckboxList',
props: {
list: {
type: Array as PropType<(ProColumns<any> & { index?: number })[]>,
default: undefined
},
className: { type: String, default: undefined },
title: { type: String, default: undefined },
draggable: { type: Boolean, default: undefined },
checkable: { type: Boolean, default: undefined },
showTitle: { type: Boolean, default: true },
listHeight: { type: Number, default: 280 }
},
setup(props) {
const { columnsMap, setColumnsMap, sortKeyColumns, setSortKeyColumns } = useContainer()!
const show = computed(() => props.list && props.list.length > 0)
const treeDataConfig = computed(() => {
if (!show.value) return {}
const checkedKeys: string[] = []
const loopData = (data: any[], parentConfig?: ColumnsState): DataNode[] =>
data.map(({ key, children, ...rest }) => {
const columnKey = genColumnKey(key, rest.index)
const config = columnsMap.value[columnKey || 'null'] || { show: true }
if (config.show !== false && parentConfig?.show !== false && !children) {
checkedKeys.push(columnKey)
}
const item: DataNode = {
key: columnKey,
...omit(rest, ['className']),
selectable: false,
disabled: config.disable === true,
disableCheckbox:
typeof config.disable === 'boolean' ? config.disable : config.disable?.checkbox,
hasParent: parentConfig ? true : undefined
}
if (children) {
item.children = loopData(children, config)
}
return item
})
return { list: loopData(props.list as any), keys: checkedKeys }
})
/** 移动到指定的位置 */
const move = (id: VueKey, targetId: VueKey, dropPosition: number) => {
const newMap = { ...columnsMap.value }
// @ts-ignore
const newColumns = [...sortKeyColumns.value]
const findIndex = newColumns.findIndex(columnKey => columnKey === id)
const targetIndex = newColumns.findIndex(columnKey => columnKey === targetId)
const isDownWord = dropPosition > findIndex
if (findIndex < 0) return
const targetItem = newColumns[findIndex]
newColumns.splice(findIndex, 1)
if (dropPosition === 0) {
newColumns.unshift(targetItem)
} else {
newColumns.splice(isDownWord ? targetIndex : targetIndex + 1, 0, targetItem)
}
// 重新生成排序数组
newColumns.forEach((key, order) => {
newMap[key] = { ...(newMap[key] || {}), order }
})
// 更新数组
setColumnsMap(newMap)
setSortKeyColumns(newColumns)
}
/** 选中反选功能 */
const onCheckTree = (e: any) => {
const columnKey = e.node.key
const newSetting = { ...columnsMap.value[columnKey] }
newSetting.show = e.checked
setColumnsMap({
...columnsMap.value,
[columnKey]: newSetting
})
}
return () => {
if (!show.value) {
return null
}
const listDom = (
<Tree
itemHeight={24}
draggable={
props.draggable &&
!!treeDataConfig.value.list?.length &&
treeDataConfig.value.list?.length > 1
}
checkable={props.checkable}
onDrop={info => {
const dropKey = info.node.key
const dragKey = info.dragNode.key
const { dropPosition, dropToGap } = info
const position = dropPosition === -1 || !dropToGap ? dropPosition + 1 : dropPosition
move(dragKey, dropKey, position)
}}
blockNode
onCheck={(_, e) => onCheckTree(e)}
checkedKeys={treeDataConfig.value.keys}
showLine={false}
height={props.listHeight}
treeData={treeDataConfig.value.list}
>
{{
title: (_node: DataNode) => {
const node = { ..._node, children: undefined }
return <CheckboxListItem className={props.className} {...node} columnKey={node.key} />
}
}}
</Tree>
)
return (
<>
{props.showTitle && <span class={`${props.className}-list-title`}>{props.title}</span>}
{listDom}
</>
)
}
}
})
const GroupCheckboxList: FunctionalComponent<{
localColumns: (ProColumns<any> & { index?: number })[]
className?: string
draggable: boolean
checkable: boolean
listsHeight?: number
}> = ({ localColumns, className, draggable, checkable, listsHeight }) => {
const rightList: (ProColumns<any> & { index?: number })[] = []
const leftList: (ProColumns<any> & { index?: number })[] = []
const list: (ProColumns<any> & { index?: number })[] = []
const intl = useIntl()
localColumns.forEach(item => {
/** 不在 setting 中展示的 */
if (item.hideInSetting) {
return
}
const { fixed } = item
if (fixed === 'left') {
leftList.push(item)
return
}
if (fixed === 'right') {
rightList.push(item)
return
}
list.push(item)
})
const showRight = rightList && rightList.length > 0
const showLeft = leftList && leftList.length > 0
return (
<div
class={[
`${className}-list`,
{
[`${className}-list-group`]: showRight || showLeft
}
]}
>
<CheckboxList
title={intl.getMessage('tableToolBar.leftFixedTitle', '固定在左侧')}
list={leftList}
draggable={draggable}
checkable={checkable}
className={className}
listHeight={listsHeight}
/>
{/* 如果没有任何固定不需要显示title */}
<CheckboxList
list={list}
draggable={draggable}
checkable={checkable}
title={intl.getMessage('tableToolBar.noFixedTitle', '不固定')}
showTitle={showLeft || showRight}
className={className}
listHeight={listsHeight}
/>
<CheckboxList
title={intl.getMessage('tableToolBar.rightFixedTitle', '固定在右侧')}
list={rightList}
draggable={draggable}
checkable={checkable}
className={className}
listHeight={listsHeight}
/>
</div>
)
}
const ColumnSetting: FunctionalComponent<ColumnSettingProps> = (
props: ColumnSettingProps,
{ slots }
) => {
const columnRef = ref({})
const counter = useContainer()!
const localColumns: TableColumnType &
{
index?: number
fixed?: any
key?: any
}[] = props.columns
const { checkedReset = true } = props
const { columnsMap, setColumnsMap, clearPersistenceStorage } = counter
watchEffect(() => {
if (counter.propsRef.value?.columnsState?.value) {
columnRef.value = JSON.parse(
JSON.stringify(counter.propsRef.value?.columnsState?.value || {})
)
}
})
/**
* 设置全部选中,或全部未选中
*
* @param show
*/
const setAllSelectAction = (show = true) => {
const columnKeyMap = {}
const loopColumns = (columns: any) => {
columns.forEach(({ key, fixed, index, children }: any) => {
const columnKey = genColumnKey(key, index)
if (columnKey) {
// @ts-ignore
columnKeyMap[columnKey] = {
show,
fixed
}
}
if (children) {
loopColumns(children)
}
})
}
loopColumns(localColumns)
setColumnsMap(columnKeyMap)
}
/** 全选和反选 */
const checkedAll = (e: CheckboxChangeEvent) => {
if (e.target.checked) {
setAllSelectAction()
} else {
setAllSelectAction(false)
}
}
/** 重置项目 */
const clearClick = () => {
clearPersistenceStorage?.()
setColumnsMap(columnRef.value)
}
// 未选中的 key 列表
const unCheckedKeys = computed(() =>
Object.values(columnsMap.value).filter(
// @ts-ignore
value => !value || value.show === false
)
)
// 是否已经选中
const indeterminate = computed(
() => unCheckedKeys.value.length > 0 && unCheckedKeys.value.length !== localColumns.length
)
const intl = useIntl()
const className = getPrefixCls('pro-table-column-setting')
return (
<Popover
arrowPointAtCenter
title={
<div class={`${className}-title`}>
<Checkbox
indeterminate={indeterminate.value}
checked={
unCheckedKeys.value.length === 0 && unCheckedKeys.value.length !== localColumns.length
}
onChange={e => checkedAll(e)}
>
{intl.getMessage('tableToolBar.columnDisplay', '列展示')}
</Checkbox>
{checkedReset ? (
<a onClick={clearClick} class={`${className}-action-rest-button`}>
{intl.getMessage('tableToolBar.reset', '重置')}
</a>
) : null}
{props?.extra ? (
<Space size={12} align="center">
{props.extra}
</Space>
) : null}
</div>
}
overlayClassName={`${className}-overlay`}
trigger="click"
placement="bottomRight"
content={
<GroupCheckboxList
checkable={props.checkable ?? true}
draggable={props.draggable ?? true}
className={className}
localColumns={localColumns}
listsHeight={props.listsHeight}
/>
}
>
{slots.default?.() || (
<Tooltip title={intl.getMessage('tableToolBar.columnSetting', '列设置')}>
<SettingOutlined />
</Tooltip>
)}
</Popover>
)
}
export default ColumnSetting