123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- 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<IHeaderConfigModalProps, IHeaderConfigModalStates> {
- private headerNameInput = React.createRef<Input>()
- 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<ColumnProps<any>> = [{
- 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 ? (
- <span className={styles.tableEditCell}>
- <label>{alias || headerName}</label>
- <Icon type="edit" onClick={this.editHeaderName(key)} />
- <Icon type="delete" onClick={this.deleteHeader(key)} />
- </span>
- ) : (<label>{alias || headerName}</label>)
- }
- const { headerName: currentEditingHeaderName } = currentEditingConfig
- return (
- <>
- <Input
- ref={this.headerNameInput}
- size="small"
- className={styles.tableInput}
- defaultValue={currentEditingHeaderName}
- />
- <Button
- type="primary"
- size="small"
- onClick={this.saveEditingHeaderName}
- >
- 确定
- </Button>
- </>
- )
- }
- }, {
- title: '背景色',
- dataIndex: 'backgroundColor',
- key: 'backgroundColor',
- width: 60,
- render: (_, record: ITableHeaderConfig) => {
- const { style } = record
- const { backgroundColor } = style
- return (
- <Row type="flex" justify="center">
- <Col>
- <ColorPicker
- className={stylesConfig.color}
- value={backgroundColor}
- onChange={this.propChange(record, 'backgroundColor')}
- />
- </Col>
- </Row>
- )
- }
- }, {
- title: '字体',
- dataIndex: 'font',
- key: 'font',
- width: 285,
- render: (_, record: ITableHeaderConfig) => {
- const { style } = record
- const { fontSize, fontFamily, fontColor, fontStyle, fontWeight } = style
- return (
- <>
- <Row gutter={8} type="flex" align="middle" className={stylesConfig.rowBlock}>
- <Col span={14}>
- <Select
- size="small"
- className={stylesConfig.colControl}
- placeholder="字体"
- value={fontFamily}
- onChange={this.propChange(record, 'fontFamily')}
- >
- {fontFamilyOptions}
- </Select>
- </Col>
- <Col span={6}>
- <Select
- size="small"
- className={stylesConfig.colControl}
- placeholder="文字大小"
- value={fontSize}
- onChange={this.propChange(record, 'fontSize')}
- >
- {fontSizeOptions}
- </Select>
- </Col>
- <Col span={4}>
- <ColorPicker
- className={stylesConfig.color}
- value={fontColor}
- onChange={this.propChange(record, 'fontColor')}
- />
- </Col>
- </Row>
- <Row gutter={8} type="flex" align="middle" className={stylesConfig.rowBlock}>
- <Col span={12}>
- <Select
- size="small"
- className={stylesConfig.colControl}
- value={fontStyle}
- onChange={this.propChange(record, 'fontStyle')}
- >
- {fontStyleOptions}
- </Select>
- </Col>
- <Col span={12}>
- <Select
- size="small"
- className={stylesConfig.colControl}
- value={fontWeight}
- onChange={this.propChange(record, 'fontWeight')}
- >
- {fontWeightOptions}
- </Select>
- </Col>
- </Row>
- </>
- )
- }
- }, {
- title: '对齐',
- dataIndex: 'justifyContent',
- key: 'justifyContent',
- width: 180,
- render: (_, record: ITableHeaderConfig) => {
- const { style } = record
- const { justifyContent } = style
- return (
- <RadioGroup size="small" value={justifyContent} onChange={this.propChange(record, 'justifyContent')}>
- <RadioButton value="flex-start">左对齐</RadioButton>
- <RadioButton value="center">居中</RadioButton>
- <RadioButton value="flex-end">右对齐</RadioButton>
- </RadioGroup>
- )
- }
- }]
- private modalFooter = [(
- <Button
- key="cancel"
- size="large"
- onClick={this.cancel}
- >
- 取 消
- </Button>
- ), (
- <Button
- key="submit"
- size="large"
- type="primary"
- onClick={this.save}
- >
- 保 存
- </Button>
- )]
- private tableRowSelection: TableRowSelection<ITableHeaderConfig> = {
- 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<ITableHeaderConfig> = {
- ...this.tableRowSelection,
- selectedRowKeys: currentSelectedKeys
- }
- const wrapTableCls = classnames({
- [stylesConfig.rows]: true,
- [styles.headerTable]: true
- })
- return (
- <Modal
- title="表头样式与分组"
- width={1000}
- maskClosable={false}
- footer={this.modalFooter}
- visible={visible}
- onCancel={this.cancel}
- onOk={this.save}
- >
- <div className={stylesConfig.rows}>
- <Row gutter={8} className={stylesConfig.rowBlock} type="flex" align="middle">
- <Col span={4}>
- <Button type="primary" onClick={this.mergeColumns}>合并</Button>
- </Col>
- <Col span={19}>
- <Row gutter={8} type="flex" justify="end" align="middle">
- <ButtonGroup>
- <Button onClick={this.moveUp}><Icon type="arrow-up" />上移</Button>
- <Button onClick={this.moveDown}>下移<Icon type="arrow-down" /></Button>
- </ButtonGroup>
- </Row>
- </Col>
- <Col span={1}>
- <Row type="flex" justify="end">
- <Tooltip
- title="表格数据列请在外部拖拽以更改顺序"
- >
- <Icon type="info-circle" />
- </Tooltip>
- </Row>
- </Col>
- </Row>
- </div>
- <div className={wrapTableCls}>
- <Row gutter={8} className={stylesConfig.rowBlock}>
- <Col span={24}>
- <Table
- bordered={true}
- pagination={false}
- columns={this.columns}
- dataSource={localConfig}
- rowSelection={rowSelection}
- />
- </Col>
- </Row>
- </div>
- </Modal>
- )
- }
- }
- export default HeaderConfigModal
|