最新消息:20210917 已从crifan.com换到crifan.org

【已解决】Antd Pro的ReactJS中实现既可以编辑单元格值又可以拖动排序的表格

拖动 crifan 707浏览 0评论
折腾:
【未解决】AntD Pro中支持剧本剧本编写时拖动排序单个对话
期间,已实现可以拖动的表格了:
但是此Antd Pro的Reactjs中,是基于react-dnd实现的可拖动实现排序的Table的cell,是不可编辑的
此处希望可以实时编辑。
记得之前Antd Pro中Table的cell,是支持实时编辑的
需要想办法加进来这个功能,且同时注意不要和现有的react-dnd冲突了。
如果实在不行,再去考虑是否可以做成:
通过额外按钮实现:
可编辑的Table的cell
只读的但是可以拖动排序的table的cell
之前去切换,也算可以实现想要的效果。
先去试试,参考:
表格 Table – Ant Design – 可编辑行
集成进来看看效果
不过刚发现,好像可编辑行,不支持当内容超过宽度就自动换行:
而可编辑单元格,是支持的:
所以还是换成可编辑单元格试试
但是看起来两种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
效果:
去拖动:

转载请注明:在路上 » 【已解决】Antd Pro的ReactJS中实现既可以编辑单元格值又可以拖动排序的表格

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
90 queries in 0.194 seconds, using 23.33MB memory