import React from 'react' import classnames from 'classnames' import { uuid } from 'utils/util' import { fontWeightOptions, fontStyleOptions, fontFamilyOptions, fontSizeOptions, DefaultTableCellStyle } from '../constants' import { ITableHeaderConfig } from './types' import { Icon, Row, Col, Modal, Input, Button, Radio, Select, Table, message, Tooltip } from 'antd' const ButtonGroup = Button.Group const RadioGroup = Radio.Group const RadioButton = Radio.Button import { TableRowSelection, ColumnProps } from 'antd/lib/table' import ColorPicker from 'components/ColorPicker' import { fromJS } from 'immutable' import styles from './styles.less' import stylesConfig from '../styles.less' interface IHeaderConfigModalProps { visible: boolean config: ITableHeaderConfig[] onCancel: () => void onSave: (config: ITableHeaderConfig[]) => void } interface IHeaderConfigModalStates { localConfig: ITableHeaderConfig[] currentEditingConfig: ITableHeaderConfig currentSelectedKeys: string[] mapHeader: { [key: string]: ITableHeaderConfig } mapHeaderParent: { [key: string]: ITableHeaderConfig } } class HeaderConfigModal extends React.PureComponent { private headerNameInput = React.createRef() public constructor (props: IHeaderConfigModalProps) { super(props) const localConfig = fromJS(props.config).toJS() const [mapHeader, mapHeaderParent] = this.getMapHeaderKeyAndConfig(localConfig) this.state = { localConfig, currentEditingConfig: null, mapHeader, mapHeaderParent, currentSelectedKeys: [] } } public componentWillReceiveProps (nextProps: IHeaderConfigModalProps) { if (nextProps.config === this.props.config) { return } const localConfig = fromJS(nextProps.config).toJS() const [mapHeader, mapHeaderParent] = this.getMapHeaderKeyAndConfig(localConfig) this.setState({ localConfig, mapHeader, mapHeaderParent, currentSelectedKeys: [] }) } private getMapHeaderKeyAndConfig (config: ITableHeaderConfig[]): [{ [key: string]: ITableHeaderConfig }, { [key: string]: ITableHeaderConfig }] { const map: { [key: string]: ITableHeaderConfig } = {} const mapParent: { [key: string]: ITableHeaderConfig } = {} config.forEach((c) => this.traverseHeaderConfig(c, null, (cursorConfig, parentConfig) => { map[cursorConfig.key] = cursorConfig mapParent[cursorConfig.key] = parentConfig return false })) return [map, mapParent] } private moveUp = () => { const { localConfig, mapHeaderParent, currentSelectedKeys } = this.state if (currentSelectedKeys.length <= 0) { message.warning('请勾选要上移的列') return } currentSelectedKeys.forEach((key) => { const parent = mapHeaderParent[key] const siblings = parent ? parent.children : localConfig const idx = siblings.findIndex((s) => s.key === key) if (idx < 1) { return } const temp = siblings[idx - 1] siblings[idx - 1] = siblings[idx] siblings[idx] = temp }) this.setState({ localConfig: [...localConfig] }) } private moveDown = () => { const { localConfig, mapHeaderParent, currentSelectedKeys } = this.state if (currentSelectedKeys.length <= 0) { message.warning('请勾选要下移的列') return } currentSelectedKeys.forEach((key) => { const parent = mapHeaderParent[key] const siblings = parent ? parent.children : localConfig const idx = siblings.findIndex((s) => s.key === key) if (idx >= siblings.length - 1) { return } const temp = siblings[idx] siblings[idx] = siblings[idx + 1] siblings[idx + 1] = temp }) this.setState({ localConfig: [...localConfig] }) } private mergeColumns = () => { const { localConfig, mapHeader, mapHeaderParent, currentSelectedKeys } = this.state if (currentSelectedKeys.length <= 0) { message.warning('请勾选要合并的列') return } const ancestors = [] currentSelectedKeys.forEach((key) => { let cursorConfig = mapHeader[key] while (true) { if (currentSelectedKeys.includes(cursorConfig.key)) { const parent = mapHeaderParent[cursorConfig.key] if (!parent) { break } cursorConfig = parent } else { break } } if (ancestors.findIndex((c) => c.key === cursorConfig.key) < 0) { ancestors.push(cursorConfig) } }) const isTop = ancestors.every((config) => !mapHeaderParent[config.key]) if (!isTop) { message.warning('勾选的列应是当前最上级列') return } const insertConfig: ITableHeaderConfig = { key: uuid(5), headerName: `新建合并列`, alias: null, visualType: null, isGroup: true, style: { ...DefaultTableCellStyle, justifyContent: 'center' }, children: ancestors } let minIdx = localConfig.length - ancestors.length minIdx = ancestors.reduce((min, config) => Math.min(min, localConfig.findIndex((c) => c.key === config.key)), minIdx) const ancestorKeys = ancestors.map((c) => c.key) const newLocalConfig = localConfig.filter((c) => !ancestorKeys.includes(c.key)) newLocalConfig.splice(minIdx, 0, insertConfig) const [newMapHeader, newMapHeaderParent] = this.getMapHeaderKeyAndConfig(newLocalConfig) this.setState({ localConfig: newLocalConfig, mapHeader: newMapHeader, mapHeaderParent: newMapHeaderParent, currentEditingConfig: insertConfig, currentSelectedKeys: [] }, () => { this.headerNameInput.current.focus() this.headerNameInput.current.select() }) } private cancel = () => { this.props.onCancel() } private save = () => { this.props.onSave(this.state.localConfig) } private traverseHeaderConfig ( config: ITableHeaderConfig, parentConfig: ITableHeaderConfig, cb: (cursorConfig: ITableHeaderConfig, parentConfig?: ITableHeaderConfig) => boolean ) { let hasFound = cb(config, parentConfig) if (hasFound) { return hasFound } hasFound = Array.isArray(config.children) && config.children.some((c) => this.traverseHeaderConfig(c, config, cb)) return hasFound } private propChange = (record: ITableHeaderConfig, propName) => (e) => { const value = e.target ? e.target.value : e const { localConfig } = this.state const { key } = record const cb = (cursorConfig: ITableHeaderConfig) => { const isTarget = key === cursorConfig.key if (isTarget) { cursorConfig.style[propName] = value } return isTarget } localConfig.some((config) => this.traverseHeaderConfig(config, null, cb)) this.setState({ localConfig: [...localConfig] }) } private editHeaderName = (key: string) => () => { const { localConfig } = this.state localConfig.some((config) => ( this.traverseHeaderConfig(config, null, (cursorConfig) => { const hasFound = cursorConfig.key === key if (hasFound) { this.setState({ currentEditingConfig: cursorConfig }, () => { this.headerNameInput.current.focus() this.headerNameInput.current.select() }) } return hasFound }) )) } private deleteHeader = (key: string) => () => { const { localConfig, mapHeader, mapHeaderParent } = this.state localConfig.some((config) => ( this.traverseHeaderConfig(config, null, (cursorConfig) => { const hasFound = cursorConfig.key === key if (hasFound) { const parent = mapHeaderParent[cursorConfig.key] let idx if (parent) { idx = parent.children.findIndex((c) => c.key === cursorConfig.key) parent.children.splice(idx, 1, ...cursorConfig.children) } else { idx = localConfig.findIndex((c) => c.key === cursorConfig.key) localConfig.splice(idx, 1, ...cursorConfig.children) } } return hasFound }) )) const [newMapHeader, newMapHeaderParent] = this.getMapHeaderKeyAndConfig(localConfig) this.setState({ mapHeader: newMapHeader, mapHeaderParent: newMapHeaderParent, localConfig }) } private saveEditingHeaderName = () => { const value = this.headerNameInput.current.input.value if (!value) { message.warning('请输入和并列名称') return } const { localConfig, currentEditingConfig } = this.state localConfig.some((config) => ( this.traverseHeaderConfig(config, null, (cursorConfig) => { const hasFound = cursorConfig.key === currentEditingConfig.key if (hasFound) { cursorConfig.headerName = value } return hasFound }) )) this.setState({ localConfig: [...localConfig], currentEditingConfig: null }) } private columns: Array> = [{ title: '表格列', dataIndex: 'headerName', key: 'headerName', render: (_, record: ITableHeaderConfig) => { const { currentEditingConfig } = this.state const { key, headerName, alias, isGroup } = record if (!currentEditingConfig || currentEditingConfig.key !== key) { return isGroup ? ( ) : () } const { headerName: currentEditingHeaderName } = currentEditingConfig return ( <> ) } }, { title: '背景色', dataIndex: 'backgroundColor', key: 'backgroundColor', width: 60, render: (_, record: ITableHeaderConfig) => { const { style } = record const { backgroundColor } = style return ( ) } }, { title: '字体', dataIndex: 'font', key: 'font', width: 285, render: (_, record: ITableHeaderConfig) => { const { style } = record const { fontSize, fontFamily, fontColor, fontStyle, fontWeight } = style return ( <> ) } }, { title: '对齐', dataIndex: 'justifyContent', key: 'justifyContent', width: 180, render: (_, record: ITableHeaderConfig) => { const { style } = record const { justifyContent } = style return ( 左对齐 居中 右对齐 ) } }] private modalFooter = [( ), ( )] private tableRowSelection: TableRowSelection = { hideDefaultSelections: true, onChange: (selectedRowKeys: string[]) => { this.setState({ currentSelectedKeys: selectedRowKeys }) } // @FIXME data columns do not allow check // getCheckboxProps: (record) => ({ // disabled: !record.isGroup // }) } public render () { const { visible } = this.props const { localConfig, currentSelectedKeys } = this.state const rowSelection: TableRowSelection = { ...this.tableRowSelection, selectedRowKeys: currentSelectedKeys } const wrapTableCls = classnames({ [stylesConfig.rows]: true, [styles.headerTable]: true }) return (
) } } export default HeaderConfigModal