折腾:
【未解决】AntD Pro中支持剧本剧本编写时拖动排序单个对话
期间,已实现可以拖动的表格了:
但是此Antd Pro的Reactjs中,是基于react-dnd实现的可拖动实现排序的Table的cell,是不可编辑的
此处希望可以实时编辑。
记得之前Antd Pro中Table的cell,是支持实时编辑的
需要想办法加进来这个功能,且同时注意不要和现有的react-dnd冲突了。
如果实在不行,再去考虑是否可以做成:
通过额外按钮实现:
可编辑的Table的cell
和
只读的但是可以拖动排序的table的cell
之前去切换,也算可以实现想要的效果。
先去试试,参考:
集成进来看看效果
不过刚发现,好像可编辑行,不支持当内容超过宽度就自动换行:
而可编辑单元格,是支持的:
所以还是换成可编辑单元格试试
但是看起来两种row:
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) );
和:
const EditableRow = ({ form, index, ...props }) => ( <EditableContext.Provider value={form}> <tr {...props} /> </EditableContext.Provider> );
貌似很难合并到一起啊
看来要单独切换: 可拖动状态 和 可编辑状态
不过要先去合并进来 可以编辑的cell
【已解决】Reactjs的Antd Pro中实现表格中支持可编辑的单元格
然后再去想办法,实现在可编辑和可拖动模式之前切换
【总结】
最终,用如下代码实现了 编辑模式 和 拖拽模式 之间切换:
/src/routes/Script/ScriptCreate.js
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 MongodbFileList from './MongodbFileList' import DragableTable from '../../components/DragableTable' import EditableTable from '../../components/EditableTable' 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.state = { // dialogList: [], dialogList: this.demoItemList, dragableChecked: false, speakers: [], contents: [], audioIDs:[], modalVisible: false, first_level_topic: [], isSubmitting: false, isDisableSubmit: false, } } /* 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, }, ] componentDidMount() { ... } ... handleAddDialog(){ console.log("handleAddDialog") const curDialogList = this.state.dialogList console.log("curDialogList=", curDialogList) let newDialogList = curDialogList const curDialogNum = curDialogList.length const newDialogNum = curDialogNum + 1 let newDialog = { key: `${newDialogNum}`, speakerOrSong: `speaker ${newDialogNum}`, contentOrName: `content ${newDialogNum}`, } console.log("newDialog=", newDialog) newDialogList.push(newDialog) console.log("newDialogList=", newDialogList) this.setState({ dialogList: newDialogList}) console.log("this.state.dialogList=", this.state.dialogList) // Note: due to use redux manage state, so componentWillReceiveProps not work // so here need forceUpdate to update ui this.forceUpdate() } onDialogListChange(newDialogList){ console.log("onDialogListChange: 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) } onEditableCellChange(newDialogList){ console.log("onEditableCellChange: 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) } ... onDragableCheckChange(e) { console.log('onDragableCheckChange: e.target.checked=', e.target.checked); this.setState({ dragableChecked: e.target.checked, }) } render() { const { submitting, form } = this.props; const topicList = this.props.topic.topics; const { getFieldDecorator } = this.props.form; const firstLevelOptions = Object.keys(topicList).map(first => <Option key={first}>{first}</Option>); const secondLevelOptions = this.state.first_level_topic.map(second => <Option key={second}>{second}</Option>); ... return ( <PageHeaderLayout title="新建剧本" > <Card bordered={false}> <Form style={{ marginTop: 8 }} hideRequiredMark> ... </FormItem> {/* {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} /> ) } <FormItem {...submitFormLayout} style={{ marginTop: 32 }}> <Checkbox style={{ marginLeft: "1%" }} checked={this.state.dragableChecked} onChange={this.onDragableCheckChange} > 开启拖拽模式 </Checkbox> ... </FormItem> </Form> </Card> </PageHeaderLayout> ); } }
可编辑:
/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 bordered columns={curColumns} dataSource={this.state.itemList} components={ this.components } onRow={(record, index) => ({ index, })} /> ); } } EditableTable.defaultProps = { itemList: [], onEditableCellChange: (newItemList) => {}, };
/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} </td> ); } }
/src/components/EditableCell/index.less
@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/DragableTable/index.js
import React from 'react' import { DragDropContext } from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' import update from 'immutability-helper' import DragableBodyRow from '../DragableBodyRow' import { Table } from 'antd' @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 bordered columns={this.state.columns} dataSource={this.state.itemList} components={ this.components } onRow={(record, index) => ({ index, moveRow: this.moveRow, })} /> ); } } DragableTable.defaultProps = { itemList: [], onDragableListChange: (dragSortedList) => {}, };
/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
效果:
去拖动: