折腾:
【未解决】AntD Pro中支持剧本剧本编写时拖动排序单个对话
期间,已经实现了基本的,既支持可拖动实现排序,也支持点击单元格支持实时编辑的表格了:
接下来去优化:
- 去把两种表格的代码合并
- 把模式切换的按钮,放在table内部的底部的左边
- 且参考:
- 单选框 Radio – Ant Design
- https://2x.ant.design/components/radio-cn/
- 去换成 编辑模式 拖动模式 之间通过radio button去切换
- 把分页去掉
- 模式切换时row行高不同,需要优化成相同的行高
去优化
经过一番调试,已经:
- 合并两种表格的代码:最后再贴完整代码
- 去掉了分页:
- 加上了footer:
- 注意一定要个字符串或者函数
现在就剩下:
需要在两种模式切换时,确保高度和对齐等显示效果一致
因为现在是不一致:
需要调节成一致效果
先要去解决:
可编辑的cell的左对齐:
和不可编辑的cell的左对齐:
不一致的问题。
相关代码是:
return ( <td ref={node => (this.cell = node)} {...restProps}> {editable ? ( <EditableContext.Consumer> {(form) => { console.log("EditableContext.Consumer: form=", form) this.form = form; return ( editing ? ( <FormItem style={{ margin: 0 }}> {form.getFieldDecorator(dataIndex, { rules: [{ required: true, message: `${title} is required.`, }], initialValue: record[dataIndex], })( <Input ref={node => (this.input = node)} onPressEnter={this.save} /> )} </FormItem> ) : ( <div className={styles.editableCellValueWrap} // className={styles.nonEditableCellValueWrap} style={{ paddingRight: 5 }} onClick={this.toggleEdit} > {restProps.children} </div> ) ); }} </EditableContext.Consumer> ) : restProps.children} </td> );
和:
@import '~antd/lib/style/themes/default.less'; .editable-cell { position: relative; } // .nonEditableCellValueWrap { // padding: 5px 12px; // cursor: pointer; // background: grey; // } .editableCellValueWrap { padding: 5px 12px; // padding: 2px 4px; cursor: pointer; } // .editable-row:hover .editableCellValueWrap { // .editableRow:hover .editableCellValueWrap { .editableCellValueWrap:hover { border: 1px solid #d9d9d9; border-radius: 4px; padding: 4px 11px; // padding: 2px 4px; }
现在去调节
然后最后用:
/src/components/EditableCell/index.js
// ) : restProps.children} ) : ( <div className={styles.nonEditableCellValueWrap} > {restProps.children} </div> ) }
和:
/src/components/EditableCell/index.less
.nonEditableCellValueWrap { padding: 5px 12px; cursor: auto; color: darkcyan; }
达到效果:不可编辑的cell,左边对齐正常了,和可编辑的cell左边一致了:
然后再去解决:
拖拽模式和编辑模式之间,cell高度和padding等不同的情况
而此处注意到之前的EditableTable中是:
components = { body: { row: EditableFormRow, cell: EditableCell, }, }
注定了cell用EditableCell,其中EditableCell的render中对单个cell,指定style的
而此处的DragableTable中,没有指定cell:
components = { body: { row: DragableBodyRow, }, }
所以没法直接指定cell的style样式
所以想办法去指定
参考:
去找找,table中是否可以直接指定cell的css
rowClassName
表格行的类名
Function(record, index):string
–
好像是要找的,去看看用法举例
巧了在antd pro中真的找到了实例:
src/routes/Forms/TableForm.js
<Fragment> <Table ... rowClassName={record => { return record.editable ? styles.editable : ''; }} />
和:
/src/routes/Forms/style.less
.editable { td { padding-top: 13px !important; padding-bottom: 12.5px !important; } }
所以可以参考这个写法,去试试
然后是可以用代码:
src/components/DragableTable/index.js
return ( <Table pagination={false} footer={this.props.footer} rowClassName={(record, index) => { const curRowStyle = styles.sameWithEditableCell console.log("rowClassName: record=", record, " ,index=", index, ", curRowStyle=", curRowStyle) return curRowStyle }}
/src/components/DragableTable/index.less
@import '~antd/lib/style/themes/default.less'; .sameWithEditableCell { td { // padding: 5px 12px; // padding: 16px 18px; padding: 21px 28px !important; // color: darkcyan !important; color: darkblue !important; cursor: ns-resize !important; } }
实现,相同的效果的:
【总结】
至此,用如下代码:
在外部调用合并后的 可拖动可编辑的表格:
import React, { PureComponent } from 'react' import { connect } from 'dva' import { routerRedux } from 'dva/router' import { Col, Form, Input, Button, Card, InputNumber, Select, message, Modal, // Checkbox, } from 'antd' import PageHeaderLayout from '../../layouts/PageHeaderLayout' ... // import DragableTable from '../../components/DragableTable' // import EditableTable from '../../components/EditableTable' import DragableEditableTable from '../../components/DragableEditableTable' const FormItem = Form.Item; const InputGroup = Input.Group; const { TextArea } = Input; const { Option } = Select; @connect(({ loading, script, topic }) => ({ submitting: loading.effects['script/submitRegularForm'], script, topic, })) @Form.create() export default class ScriptCreate extends PureComponent { constructor(props) { super(props) // this.onDialogListChange = this.onDialogListChange.bind(this) // this.onEditableCellChange = this.onEditableCellChange.bind(this) // this.onDragableCheckChange = this.onDragableCheckChange.bind(this) this.syncDialogList = this.syncDialogList.bind(this) this.onTableModeChange = this.onTableModeChange.bind(this) this.state = { // dialogList: [], dialogList: this.demoItemList, // dragableChecked: false, tableMode: DragableEditableTable.TableMode.EDITABLE, ... } } /* eslint-disable */ columns = [ { title: '序号', width: "8%", editable: false, dataIndex: 'number', key: 'number', // rowKey: 'number', // fixed: 'left', render(text, record, index) { return index + 1; }, }, { width: "15%", editable: true, title: 'Speaker/Song', dataIndex: 'speakerOrSong', key: 'speakerOrSong', }, { width: "75%", editable: true, title: 'Content/Name', dataIndex: 'contentOrName', key: 'contentOrName', }, ] demoItemList = [ { key: '1', speakerOrSong: 'A', contentOrName: 'hi boy', editable: true, }, { key: '2', speakerOrSong: 'B', contentOrName: 'hello', editable: true, }, { key: '3', speakerOrSong: 'A', contentOrName: 'what are you doing?', editable: true, }, { key: '4', speakerOrSong: 'B', contentOrName: 'I am singing', editable: true, }, { key: '5', speakerOrSong: 'Song', contentOrName: 'this is a apple.mp3', editable: false, }, ] ... onTableModeChange(e) { console.log('onTableModeChange: e.target.value=', e.target.value); this.setState({ tableMode: e.target.value }) console.log("after change: this.state.tableMode=", this.state.tableMode) } syncDialogList(newDialogList){ console.log("syncDialogList: newDialogList=", newDialogList) // console.log("before change: this.state.dialogList=", this.state.dialogList) this.setState({dialogList: newDialogList}) console.log("after change: this.state.dialogList=", this.state.dialogList) } render() { const { submitting, form } = this.props; const topicList = this.props.topic.topics; const { getFieldDecorator } = this.props.form; ... return ( <PageHeaderLayout title="新建剧本" > <Card bordered={false}> ... {/* {this.buildDialog()} */} {/* { this.state.dragableChecked ? ( <DragableTable columns={this.columns} itemList={this.state.dialogList} onDragableListChange={this.onDialogListChange} /> ) : ( <EditableTable columns={this.columns} itemList={this.state.dialogList} onEditableCellChange={this.onEditableCellChange} form={this.props.form} /> ) } */} <DragableEditableTable tableMode={this.state.tableMode} columns={this.columns} itemList={this.state.dialogList} onTableDataChange={this.syncDialogList} onTableModeChange={this.onTableModeChange} form={this.props.form} /> ... </Form> </Card> </PageHeaderLayout> ); } }
而合并后的表格是:
import React from 'react' import { Radio, } from 'antd' import DragableTable from '../DragableTable' import EditableTable from '../EditableTable' const RadioButton = Radio.Button const RadioGroup = Radio.Group export default class DragableEditableTable extends React.Component { /* eslint-disable */ static TableMode = { EDITABLE: "editable", DRAGABLE: "dragable", } /* eslint-disable */ // static defaultMode = DragableEditableTable.TableMode.DRAGABLE // static defaultMode = DragableEditableTable.TableMode.EDITABLE // state = { // // mode: this.TableMode.EDITABLE, // // mode: this.defaultMode, // // mode: DragableEditableTable.defaultMode, // // columns: [], // // itemList: [], // } constructor(props){ super(props) // this.onTableModeChange = this.onTableModeChange.bind(this) // this.onTableDataChange = this.onTableDataChange.bind(this) this.radioSelectFooter = this.radioSelectFooter.bind(this) console.log("DragableEditableTable constructor: props=", props) // console.log("this.props.columns=", this.props.columns) console.log("this.props.itemList=", this.props.itemList) console.log("this.props.tableMode=", this.props.tableMode) // this.state.itemList = this.props.itemList // this.state.columns = this.props.columns // console.log("this.state.mode=", this.state.mode) } // onTableModeChange(e) { // console.log('onTableModeChange: e.target.value=', e.target.value); // this.setState({ // mode: e.target.value, // }) // } // onTableDataChange(newDialogList){ // // update current state // this.setState({dialogList: newDialogList}) // // callback parent to sync state // this.props.onTableDataChange(newDialogList) // } // radioSelectFooter(onRadioChange, defautMode) { radioSelectFooter() { return ( // <RadioGroup onChange={this.onTableModeChange} defaultValue={DragableEditableTable.defaultMode}> // <RadioGroup onChange={onRadioChange} defaultValue={defautMode}> <RadioGroup onChange={this.props.onTableModeChange} defaultValue={this.props.tableMode}> <RadioButton value={DragableEditableTable.TableMode.EDITABLE}>编辑模式</RadioButton> <RadioButton value={DragableEditableTable.TableMode.DRAGABLE}>拖动模式</RadioButton> </RadioGroup> ) } render() { console.log("DragableEditableTable render: this.props.itemList=", this.props.itemList) const commonColumns = this.props.columns const commonItemList = this.props.itemList // const commonModeSelectorFooter = this.radioSelectFooter(this.props.onTableModeChange, this.props.tableMode) const commonModeSelectorFooter = this.radioSelectFooter const commonDataChange = this.props.onTableDataChange console.log("commonColumns=", commonColumns) console.log("commonItemList=", commonItemList) console.log("commonModeSelectorFooter=", commonModeSelectorFooter) console.log("commonDataChange=", commonDataChange) return ( // { this.props.tableMode === DragableEditableTable.TableMode.DRAGABLE ? ( <DragableTable columns={commonColumns} itemList={commonItemList} footer={commonModeSelectorFooter} onDragableListChange={commonDataChange} /> ) : ( <EditableTable columns={commonColumns} itemList={commonItemList} footer={commonModeSelectorFooter} onEditableCellChange={commonDataChange} form={this.props.form} /> ) // } ) } } DragableEditableTable.defaultProps = { tableMode: DragableEditableTable.TableMode.EDITABLE, itemList: [], onTableDataChange: (newItemList) => {}, onTableModeChange: (newMode) => {}, };
内部调用的分别是:
/src/components/EditableTable/index.js
import React from 'react' import { EditableFormRow, EditableCell } from '../EditableCell' import { Table } from 'antd' export default class EditableTable extends React.Component { constructor(props){ super(props) console.log("EditableTable constructor: props=", props) console.log("this.props.columns=", this.props.columns) console.log("this.props.itemList=", this.props.itemList) console.log("this.props.onEditableCellChange=", this.props.onEditableCellChange) this.handleSaveEditableCell = this.handleSaveEditableCell.bind(this) this.state.itemList = this.props.itemList; this.state.columns = this.props.columns; } state = { columns: [], itemList: [], } // componentWillReceiveProps(nextProps){ // console.log("EditableTable componentWillReceiveProps: nextProps=", nextProps) // console.log("this.props.itemList=", this.props.itemList) // if (this.props.itemList) { // this.setState({itemList: this.props.itemList}) // console.log("updated this.state.itemList=", this.state.itemList) // } // } components = { body: { row: EditableFormRow, cell: EditableCell, }, } handleSaveEditableCell = (row) => { console.log("handleSaveEditableCell: row=", row) const newItemList = [...this.state.itemList] console.log("newItemList=", newItemList) const index = newItemList.findIndex(item => row.key === item.key) console.log("index=", index) const item = newItemList[index] console.log("item=", item) newItemList.splice(index, 1, { ...item, ...row, }) console.log("newItemList=", newItemList) this.setState({ itemList: newItemList }) console.log("after: this.state.itemList=", this.state.itemList) this.props.onEditableCellChange(this.state.itemList) // console.log("newItemList=", newItemList) // this.props.onEditableCellChange(newItemList) } render() { console.log("EditableTable render: this.state.itemList=", this.state.itemList) const curColumns = this.state.columns.map((col) => { console.log("curColumns: col=", col) if (!col.editable) { return col } return { ...col, onCell: (record) => { /* * Note: * 1. each cell is editable is set by: * first priority: cell editable by each item/cell in this.props.itemList * then check: column editable by each column in this.props.columns */ let cellIsEditable if (record.editable !== undefined) { cellIsEditable = record.editable } else { cellIsEditable = col.editable } console.log("cellIsEditable=", cellIsEditable, ", record=", record) return { record, editable: cellIsEditable, dataIndex: col.dataIndex, title: col.title, form: this.props.form, handleSave: this.handleSaveEditableCell, } }, } }) return ( <Table pagination={false} footer={this.props.footer} bordered columns={curColumns} dataSource={this.state.itemList} components={ this.components } onRow={(record, index) => ({ index, })} /> ); } } EditableTable.defaultProps = { itemList: [], onEditableCellChange: (newItemList) => {}, footer: '', };
/src/components/EditableCell/index.js
import React, { PureComponent } from 'react'; import styles from './index.less'; import { Form, Input } from 'antd'; const FormItem = Form.Item; const EditableContext = React.createContext(); const EditableRow = ({ form, index, ...props }) => ( <EditableContext.Provider value={form}> <tr {...props} /> </EditableContext.Provider> ); export const EditableFormRow = Form.create()(EditableRow); // export default class EditableCell extends PureComponent { export class EditableCell extends PureComponent { state = { editing: false, } componentDidMount() { console.log("EditableCell componentDidMount: this.props.editable", this.props.editable) if (this.props.editable) { document.addEventListener('click', this.handleClickOutside, true); } } componentWillUnmount() { if (this.props.editable) { document.removeEventListener('click', this.handleClickOutside, true); } } toggleEdit = () => { console.log("toggleEdit=") console.log("this.state.editing=", this.state.editing) const editing = !this.state.editing; this.setState({ editing }, () => { if (editing) { this.input.focus(); } }); } handleClickOutside = (e) => { const { editing } = this.state; if (editing && this.cell !== e.target && !this.cell.contains(e.target)) { this.save(); } } save = () => { console.log("save") const { record, handleSave } = this.props; console.log("record=", record) this.form.validateFields((error, values) => { console.log("form.validateFields=: error", error, ", values=", values) if (error) { return } this.toggleEdit(); handleSave({ ...record, ...values }); }); } render() { const { editing } = this.state; const { editable, dataIndex, title, record, index, // handleSave, ...restProps } = this.props; // console.log("editing=", editing, ", editable=", editable, ", record=", record) return ( <td ref={node => (this.cell = node)} {...restProps}> {editable ? ( <EditableContext.Consumer> {(form) => { console.log("EditableContext.Consumer: form=", form) this.form = form; return ( editing ? ( <FormItem style={{ margin: 0 }}> {form.getFieldDecorator(dataIndex, { rules: [{ required: true, message: `${title} is required.`, }], initialValue: record[dataIndex], })( <Input ref={node => (this.input = node)} onPressEnter={this.save} /> )} </FormItem> ) : ( <div className={styles.editableCellValueWrap} // className={styles.nonEditableCellValueWrap} style={{ paddingRight: 5 }} onClick={this.toggleEdit} > {restProps.children} </div> ) ); }} </EditableContext.Consumer> // ) : restProps.children} ) : ( <div className={styles.nonEditableCellValueWrap} > {restProps.children} </div> ) } </td> ); } }
对应less的css:
/src/components/EditableCell/index.less
@import '~antd/lib/style/themes/default.less'; .editable-cell { position: relative; } .nonEditableCellValueWrap { padding: 5px 12px; cursor: auto; // color: darkcyan; color: darkblue; } .editableCellValueWrap { padding: 5px 12px; // padding: 2px 4px; cursor: pointer; // cursor: -webkit-grab; } // .editable-row:hover .editableCellValueWrap { // .editableRow:hover .editableCellValueWrap { .editableCellValueWrap:hover { border: 1px solid #d9d9d9; border-radius: 4px; padding: 4px 11px; // padding: 2px 4px; }
可拖动的表格:
/src/components/DragableTable/index.js
import React from 'react' import { Table } from 'antd' import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' import update from 'immutability-helper' import DragableBodyRow from '../DragableBodyRow' import styles from './index.less'; @DragDropContext(HTML5Backend) export default class DragableTable extends React.Component { constructor(props){ super(props) console.log("DragableTable constructor: props=", props) console.log("this.props.columns=", this.props.columns) console.log("this.props.itemList=", this.props.itemList) console.log("this.props.onDragableListChange=", this.props.onDragableListChange) this.state.itemList = this.props.itemList; this.state.columns = this.props.columns; } state = { columns: [], itemList: [], } // componentWillReceiveProps(nextProps){ // console.log("DragableTable componentWillReceiveProps: nextProps=", nextProps) // console.log("this.props.itemList=", this.props.itemList) // if (this.props.itemList) { // this.setState({itemList: this.props.itemList}) // console.log("updated this.state.itemList=", this.state.itemList) // } // } components = { body: { row: DragableBodyRow, }, } moveRow = (dragIndex, hoverIndex) => { const { itemList } = this.state; const dragRow = itemList[dragIndex]; this.setState( update(this.state, { itemList: { $splice: [[dragIndex, 1], [hoverIndex, 0, dragRow]], }, }), ) console.log("moveRow: this.state.itemList=", this.state.itemList) this.props.onDragableListChange(this.state.itemList) } render() { console.log("DragableTable render: this.state.itemList=", this.state.itemList) return ( <Table pagination={false} footer={this.props.footer} rowClassName={(record, index) => { const curRowStyle = styles.sameWithEditableCell console.log("rowClassName: record=", record, " ,index=", index, ", curRowStyle=", curRowStyle) return curRowStyle }} bordered columns={this.state.columns} dataSource={this.state.itemList} components={ this.components } onRow={(record, index) => ({ index, moveRow: this.moveRow, })} /> ); } } DragableTable.defaultProps = { itemList: [], onDragableListChange: (dragSortedList) => {}, footer: '', };
对应less的css:
/src/components/DragableTable/index.less
@import '~antd/lib/style/themes/default.less'; .sameWithEditableCell { td { // padding: 5px 12px; // padding: 16px 18px; padding: 21px 28px !important; // color: darkcyan !important; color: darkblue !important; cursor: ns-resize !important; } }
内部的row:
/src/components/DragableBodyRow/index.js
import React from 'react'; import { DragSource, DropTarget } from 'react-dnd'; function dragDirection( dragIndex, hoverIndex, initialClientOffset, clientOffset, sourceClientOffset, ) { const hoverMiddleY = (initialClientOffset.y - sourceClientOffset.y) / 2; const hoverClientY = clientOffset.y - sourceClientOffset.y; if (dragIndex < hoverIndex && hoverClientY > hoverMiddleY) { return 'downward'; } if (dragIndex > hoverIndex && hoverClientY < hoverMiddleY) { return 'upward'; } } class BodyRow extends React.Component { render() { const { isOver, connectDragSource, connectDropTarget, moveRow, dragRow, clientOffset, sourceClientOffset, initialClientOffset, ...restProps } = this.props; const style = { ...restProps.style, cursor: 'move' }; let className = restProps.className; if (isOver && initialClientOffset) { const direction = dragDirection( dragRow.index, restProps.index, initialClientOffset, clientOffset, sourceClientOffset ); if (direction === 'downward') { className += ' drop-over-downward'; } if (direction === 'upward') { className += ' drop-over-upward'; } } return connectDragSource( connectDropTarget( <tr {...restProps} className={className} style={style} /> ) ); } } const rowSource = { beginDrag(props) { return { index: props.index, }; }, }; const rowTarget = { drop(props, monitor) { const dragIndex = monitor.getItem().index; const hoverIndex = props.index; // Don't replace items with themselves if (dragIndex === hoverIndex) { return; } // Time to actually perform the action props.moveRow(dragIndex, hoverIndex); // Note: we're mutating the monitor item here! // Generally it's better to avoid mutations, // but it's good here for the sake of performance // to avoid expensive index searches. monitor.getItem().index = hoverIndex; }, }; // const DragableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({ const DragableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({ connectDropTarget: connect.dropTarget(), isOver: monitor.isOver(), sourceClientOffset: monitor.getSourceClientOffset(), }))( DragSource('row', rowSource, (connect, monitor) => ({ connectDragSource: connect.dragSource(), dragRow: monitor.getItem(), clientOffset: monitor.getClientOffset(), initialClientOffset: monitor.getInitialClientOffset(), }))(BodyRow) ); export default DragableBodyRow
从而实现了优化:
- 合并了可拖动的DragableTable和可编辑的EditableTable
- 通过<Table pagination={false}实现了去掉分页
- 通过指定Table的footer实现了显示 radiobutton去切换 两种模式:拖动模式 分页模式
- 通过设置DragableTable的Table的rowClassName,实现了模式切换后表格的样式不变
- 尤其是cell的padding和高度
- 避免了用户在切换后模式后,觉得表格单元格高低不同而觉得太突兀
效果:
切换后:
去拖动:
转载请注明:在路上 » 【已解决】剧本编写系统中优化可拖动排序可编辑单元格的表格