基於AntDesign實現的React.js自定義可編輯表格,帶翻譯功能

基於React+AntDesign實現的一個自定義的可編輯表格,主要用於數據庫表字段的編輯、帶翻譯功能,稍作修改後也可複用到其他地方。
主要包括三個文件:index.js,EditableTable.js,EditableTable.less,其中圖標是使用的阿里巴巴矢量圖標庫上面的,接入了百度翻譯的API.

效果圖:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

具體代碼如下
index.js:

import React, { Component } from "react";
import EditableTable from "./EditableTable";

class EditTable extends Component {

  onDataSourceChanged = data => {

  };

  render() {
    const selectData = [
      { label: '常用', options: ['VARCHAR', 'CHAR', 'INT', 'DATE'] },
      { label: '整數', options: ['INT', 'BIGINT', 'BIT', 'TINYINT'] },
      { label: '實數', options: ['DECIMAL', 'DOUBLE', 'FLOAT'] },
      { label: '字符串', options: ['VARCHAR', 'CHAR'] },
      { label: '二進制', options: ['BLOB', 'BINARY'] },
      { label: '日期時間', options: ['DATE', 'TIMESTAMP', 'DATETIME'] },
      { label: '空間幾何', options: ['POINT', 'LINESTRING'] },
      { label: '其他', options: ['ENUM', 'SET'] },
    ];

    const columns = [
      {
        editable: '1',
        dataIndex: 'chName',
        title: '中文名',
        type: 'input',
        width: 150,
        required: true
      }, {
        editable: '2',
        dataIndex: 'name',
        title: '字段名',
        type: 'input',
        width: 150,
        required: true
      },
      {
        editable: '3',
        dataIndex: 'type',
        title: '字段類型',
        type: 'select',
        data: selectData,
        width: 200,
        required: true
      },
      {
        editable: '4',
        dataIndex: 'primaryKey',
        title: '主鍵',
        type: 'checkbox',
        width: 50,
      }, {
        editable: '5',
        dataIndex: 'foreignKey',
        title: '外鍵',
        type: 'checkbox',
        width: 50
      }, {
        editable: '6',
        dataIndex: 'notNull',
        title: '非空',
        type: 'checkbox',
        width: 50
      }
    ];

    return <div style={{ display: 'flex', margin: '100px 200px' }}>
      <EditableTable columns={columns} rowKey={'name'} onDataSourceChanged={this.onDataSourceChanged}/>
    </div>;
  }

}

export default EditTable;

EditableTable.js:

import React from 'react';
import { Checkbox, Form, Input, message, Select, Table, Tooltip } from 'antd';
import styles from './EditableTable.less';
import md5 from "md5";
import fetchJsonp from "fetch-jsonp";


const EditableContext = React.createContext();
const { Option, OptGroup } = Select;

const EditableRow = ({ form, index, ...props }) => (
  <EditableContext.Provider value={form}>
    <tr {...props} />
  </EditableContext.Provider>
);

const EditableFormRow = Form.create()(EditableRow);

class EditableCell extends React.Component {
  state = {
    editing: false,
    checkboxes: {}
  };

  toggleEdit = () => {
    const editing = !this.state.editing;
    this.setState({ editing }, () => {
      if (editing) {
        this.input.focus();
      }
    });
  };

  save = (e, dataIndex) => {
    const { record, handleSave } = this.props;
    this.form.validateFields((error, values) => {
      if (error && error[e.currentTarget.id]) {
        return;
      }
      this.toggleEdit();
      const value = values;
      //中英互譯,如不需要翻譯,可移除此if分支
      if (dataIndex === 'chName') {
        const q = value.chName;
        if (q.trim().length === 0) {
          return;
        }
        const appid = '12345678';
        const salt = '1435660288';
        const sign = md5(appid + q + salt + '12345678');
        const url = `http://api.fanyi.baidu.com/api/trans/vip/translate?q=${q}&from=zh&to=en&appid=${appid}&salt=1435660288&sign=${sign}`;
        fetchJsonp(url)
          .then(response => response.json())
          .then(data => {
              if (data && data.trans_result) {
                const result = data.trans_result[0].dst;
                if (result) {
                  value.name = result.split(' ').join('_');
                }
                handleSave({ ...record, ...value });
              }
            }
          );
      } else {
        handleSave({ ...record, ...value });
      }
    });
  };

  onCheckboxChange = (e, record) => {
    const { checkboxes } = this.state;
    checkboxes[record.editable_row_key] = e.target.checked;
    this.setState({
      checkboxes
    });
  };

  renderCell = form => {
    this.form = form;
    const { children, dataIndex, record, title, type, data = [], required } = this.props;

    const optGroups = [];
    if (type === 'select' && data.length > 0) {
      data.map((item, index) => {
        const optGroup = <OptGroup label={item.label} key={index}>
          {item.options.map((option, index2) => {
            return <Option value={option} key={index2}>{option}</Option>;
          })}
        </OptGroup>;
        optGroups.push(optGroup);
      });
    }

    const { editing } = this.state;
    return !editing && type === 'input' ?
      (<div
        className={styles['editable-cell-value-wrap']}
        style={{ paddingRight: 24, minHeight: '30px' }}
        onClick={this.toggleEdit}
      >
        {children}
      </div>)
      : (
        <Form.Item style={{ margin: 0 }}>
          {form.getFieldDecorator(dataIndex, {
            rules: [
              {
                required: required !== undefined && required === true,
                message: `${title} 必填`,
              },
            ],
            //默認值,如果是下拉框則設置默認值爲下拉項第一項
            initialValue: type === 'select' ? data[0].options[0] : record[dataIndex],
          })(
            type === 'select' ?
              <Select
                style={{ width: '200px' }}
                ref={node => (this.input = node)}
                onPressEnter={this.save}
                onBlur={this.save}
              >
                {optGroups}
              </Select>
              : type === 'checkbox' ?
              <Checkbox
                ref={node => (this.input = node)}
                onChange={(e) => this.onCheckboxChange(e, record)}
                checked={this.state.checkboxes[record.editable_row_key]}
              />
              : <Input
                maxLength={dataIndex === 'describe' ? 100 : 40}
                ref={node => (this.input = node)}
                onPressEnter={this.save}
                onBlur={e => this.save(e, dataIndex)}
              />
          )}
        </Form.Item>
      );
  };

  render() {
    const {
      editable,
      dataIndex,
      title,
      type,
      data,
      width,
      required,
      record,
      index,
      handleSave,
      children,
      ...restProps
    } = this.props;
    return (
      <td {...restProps}>
        {editable ? (
          <EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>
        ) : (
          children
        )}
      </td>
    );
  }
}

class EditableTable extends React.Component {
  constructor(props) {
    super(props);

    const dataSource = (props.dataSource || []).map((data, idx) => {
      return {
        ...data,
        editable_row_key: idx + '_' + props.rowKey,
      };
    });

    this.state = {
      dataSource,
      count: dataSource.length,
    };
  }

  handleDelete = key => {
    const dataSource = [...this.state.dataSource];
    let newData = dataSource.filter(item => item.editable_row_key !== key);
    this.setState({ dataSource: newData });
  };

  handleAdd = () => {
    const { dataSource, count } = this.state;
    const newData = {
      'editable_row_key': count + '_' + this.props.rowKey,
    };
    this.setState({
      dataSource: [...dataSource, newData],
      count: count + 1,
    });

  };

  handleSave = row => {
    const newData = [...this.state.dataSource];
    const index = newData.findIndex(item => row.editable_row_key === item.editable_row_key);
    const item = newData[index];
    newData.splice(index, 1, {
      ...item,
      ...row,
    });
    this.setState({ dataSource: newData });

    if (this.props.onDataSourceChanged) {
      this.props.onDataSourceChanged(newData);
    }
  };

  delete = () => {
    const { selectRows = [] } = this.state;
    const dataSource = [...this.state.dataSource];

    selectRows.forEach((selectRow) => {
      dataSource.forEach((item, index) => {
        if (selectRow === item.editable_row_key) {
          dataSource.splice(index, 1);
        }
      });
    });

    this.setState({
      dataSource,
      selectRows: []
    });
  };

  /**
   * 百度在線翻譯
   */
  translate = () => {
    const { selectRows = [], dataSource = [] } = this.state;
    if (selectRows.length === 0) {
      message.error('請先選中需要翻譯的行!');
      return;
    }
    const q = [];
    selectRows.forEach((selectRow) => {
      dataSource.forEach((item) => {
        if (item.editable_row_key === selectRow && item.chName !== undefined && item.chName.trim() !== '') {
          q.push(item.chName);
        }
      });
    });
    if (q.length === 0) {
      return;
    }
    const appid = '12345678';
    const salt = '1435660288';
    const sign = md5(appid + q + salt + '12345678');


    const url = `http://api.fanyi.baidu.com/api/trans/vip/translate?q=${q}&from=zh&to=en&appid=${appid}&salt=1435660288&sign=${sign}`;

    let result;
    fetchJsonp(url)
      .then(response => response.json())
      .then(data => {
          if (data && data.trans_result) {
            result = data.trans_result[0];
            if (result) {
              const src = result.src.split(',');
              const dst = result.dst.split(',');
              dataSource.forEach((item) => {
                const index = src.findIndex(item2 => {
                  return item.chName === item2;
                });
                if (dst[index]) {
                  item.name = dst[index].split(' ').join('_');
                }
              });
              this.setState(dataSource);
            }
          }
        }
      );
  };

  render() {
    const { dataSource } = this.state;

    const components = {
      body: {
        row: EditableFormRow,
        cell: EditableCell,
      },
    };

    const columns = this.props.columns.map((col) => {

      if (!col.editable) {
        return col;
      }

      return {
        ...col,
        onCell: record => ({
          record,
          editable: col.editable,
          dataIndex: col.dataIndex,
          title: col.title,
          type: col.type,
          data: col.data,
          required: col.required,
          ellipsis: true,
          width: col.width,
          handleSave: this.handleSave,
        }),
      };
    });

    columns.push(
      {
        title: '操作',
        render: (text, record) =>
          this.state.dataSource.length >= 1 ? (
            <a onClick={() => {
              this.handleDelete(record['editable_row_key']);
            }}>
              刪除
            </a>
          ) : null,
      },
    );

    const title = (
      <div>
        <a onClick={this.handleAdd}>
          <Tooltip title={'添加字段'}>
            <img src={'/img/add.png'} width={30}/>
          </Tooltip>
        </a>
        <a onClick={this.delete} style={{ marginLeft: '10px' }}>
          <Tooltip title={'刪除字段'}>
            <img src={'/img/delete.png'} width={25}/>
          </Tooltip>
        </a>
        <a onClick={this.translate} style={{ marginLeft: '10px' }}>
          <Tooltip title={'翻譯'}>
            <img src={'/img/translate.png'} width={23}/>
          </Tooltip>
        </a>
        <a onClick={this.handleAdd} style={{ marginLeft: '10px' }}>
          <Tooltip title={'添加新單詞'}>
            <img src={'/img/word.png'} width={30}/>
          </Tooltip>
        </a>
      </div>
    );

    const rowSelection = {
      onChange: record => {
        this.setState({
          selectRows: record
        });
      }
    };

    return (
      <Table
        rowSelection={rowSelection}
        rowKey="editable_row_key"
        title={() => title}
        components={components}
        rowClassName={() => styles['editable-row']}
        size="middle"
        pagination={false}
        dataSource={dataSource}
        columns={columns}
      />
    );
  }
}

export default EditableTable;

EditableTable.less:

.editable-cell {
  position: relative;
}

.editable-cell-value-wrap {
  padding: 5px 12px;
  cursor: pointer;
}

.editable-row:hover .editable-cell-value-wrap {
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  padding: 4px 11px;
}

.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

用到了百度翻譯API,如果不用可以直接移除相關代碼,需要使用的話去百度翻譯開放平臺申請,拿到appid和密鑰,安裝antd,md5等所需依賴,就可以直接使用了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章