react-antd实现可编辑表格,可动态增加行和列

https://ant.design/components/table-cn/#components-table-demo-edit-cell

正常状态:


image.png

编辑状态:


image.png

image.png

核心:难点在如果columns放在state里面的话,是不能在state里面再存一个state的,这里我们用useEffect订阅事件,举例单元格是否可编辑的转化:如果我们在columns里面用state控制单元格是否可编辑,那么做法是定义一个state:

const [isCellEditing,setIsCellEditing] = useState(false)

然后在拿到后端数据的时候我们

setEditTableColumns((preState) => ([{
                        title: (
                            <div style={{ marginLeft: '4px' }}>
                                <Tooltip title={element.desc}>
                                    {element.name}
                                </Tooltip>
                            </div>
                        ),
                        dataIndex: element.name,
                        width: 150,
                        desc: element.desc,
                        editable: isCellEditing //注意这里
                    },
                    ...preState
                    ]))

这样的话改变isCellEditing 的状态并不能改变表格可编辑的状态
我们通过订阅UseEffect,在点击编辑后,遍历更新所有列中的editable字段,使得表格编辑状态发生改变

    //订阅编辑状态
    useEffect(() => {
        if (isEditTable) {
            let dataArray = editTableColumns.filter(() => true)
            for (const element of dataArray) {
                element.title = (
                    <div className="between">
                        <div style={{ marginLeft: '4px' }}>
                            <Tooltip title={element.desc}>
                                {element.dataIndex}
                            </Tooltip>
                        </div>
                        &nbsp;&nbsp;&nbsp;&nbsp;
                        <div >
                            <Popover
                                overlayClassName='popover-no-padding'
                                content={
                                    <div>
                                        <div
                                            name={element.dataIndex}
                                            desc={element.desc}
                                            onClick={(e) => {
                                                setEditKey(
                                                    {
                                                        name: e.currentTarget.getAttribute('name'),
                                                        desc: e.currentTarget.getAttribute('desc')
                                                    }
                                                )
                                            }}
                                            className="popover-content-edit"
                                        >
                                            修改
                                        </div>
                                        <div name={element.dataIndex} onClick={(e) => { setDeleteKey(e.currentTarget.getAttribute('name')) }} className="popover-content-delete">删除</div>
                                    </div>
                                }
                                trigger='click'
                                placement='bottom'
                            >
                                <EditOutlined onClick={() => { }} />
                            </Popover >
                        </div>
                    </div >
                )
                element.editable = true
            }
            setEditTableColumns(dataArray)
        } else {
            let dataArray = editTableColumns.filter(() => true)
            for (const element of dataArray) {
                element.title = (
                    <div style={{ marginLeft: '4px' }}>
                        <Tooltip title={element.desc}>
                            {element.dataIndex}
                        </Tooltip>
                    </div>
                )
                element.editable = false
            }
            setEditTableColumns(dataArray)
        }
    }, [isEditTable])

全部代码:

import { CloseOutlined, EditOutlined, PlusOutlined, SaveOutlined } from "@ant-design/icons";
import { Button, Form as AntForm, Input as AntInput, Modal, Table, Tooltip } from "antd";
import React, { useContext, useEffect, useRef, useState } from "react";
const EditableContext = React.createContext(null)
const EditableRow = ({ index, ...props }) => {
    const [form] = AntForm.useForm()
    return (
        <AntForm form={form} component={false}>
            <EditableContext.Provider value={form}>
                <tr {...props} />
            </EditableContext.Provider>
        </AntForm>
    )
}

const EditableCell = ({
    title,
    editable,
    children,
    dataIndex,
    record,
    handleSave,
    ...restProps
}) => {
    const [editing, setEditing] = useState(false)
    const inputRef = useRef(null)
    const form = useContext(EditableContext)
    useEffect(() => {
        if (editing) {
            inputRef.current.focus()
        }
    }, [editing])

    const toggleEdit = () => {
        setEditing(!editing)
        form.setFieldsValue({
            [dataIndex]: record[dataIndex]
        })
    }

    const save = async () => {
        try {
            const values = await form.validateFields()
            toggleEdit()
            handleSave({ ...record, ...values })
        } catch (errInfo) {
            console.log('Save failed:', errInfo)
        }
    }

    let childNode = children

    if (editable) {
        childNode = editing ? (
            <AntForm.Item
                style={{
                    margin: 0
                }}
                name={dataIndex}
            >
                <AntInput ref={inputRef} onPressEnter={save} onBlur={save} />
            </AntForm.Item>
        ) : (
            <div
                className='editable-interface-cell-value-wrap'
                style={{
                    paddingRight: 24
                }}
                onClick={toggleEdit}
            >
                {children}
            </div>
        )
    }

    return <td {...restProps}>{childNode}</td>
}
export function EditTable() {
    const [dataSource, setDataSource] = useState([])
    const [count, setCount] = useState(0)
    const [addColModal, setAddColModal] = useState(false)
    const [colTitle, setcolTitle] = useState({ name: '', desc: '' })
    const [isLoading, setIsLoading] = useState(false)
    const [editTableColumns, setEditTableColumns] = useState([])
    const [deleteKey, setDeleteKey] = useState(null)
    const [editKey, setEditKey] = useState({ name: '', desc: '' })
    function getCaseDetail() {
        //回调函数
        const successcallback = (res) => {
            //将列名置空
            setEditTableColumns([])
            for (const element of data) {
                setEditTableColumns((preState) => ([{
                    title: (
                        <div style={{ marginLeft: '4px' }}>
                            <Tooltip title={element.desc}>
                                {element.name}
                            </Tooltip>
                        </div>
                    ),
                    dataIndex: element.name,
                    width: 150,
                    desc: element.desc,
                    editable: false
                },
                ...preState
                ]))
            }
            let dataArray = []
            for (let i = 0, length = data.length; i < length; i++) {
                let element = data[i]
                element.key = `fhialda-${i}-o`//这里随便给一个key,保持唯一即可
                dataArray.push(element)
            }
            setDataSource(dataArray)
        }
        调取接口请求数据,拿到后端传来的columns和dataSource
    }
    useEffect(() => {
        //页面加载后调取数据
        getCaseDetail()
    }, [])
    //订阅编辑状态
    useEffect(() => {
        if (isEditTable) {
            let dataArray = editTableColumns.filter(() => true)
            for (const element of dataArray) {
                element.title = (
                    <div className="between">
                        <div style={{ marginLeft: '4px' }}>
                            <Tooltip title={element.desc}>
                                {element.dataIndex}
                            </Tooltip>
                        </div>
                        &nbsp;&nbsp;&nbsp;&nbsp;
                        <div >
                            {/* 这里自己利用popover造了一个轮子 */}
                            <Popover
                                content={
                                    <div>
                                        <div
                                            name={element.dataIndex}
                                            desc={element.desc}
                                            onClick={(e) => {
                                                setEditKey(
                                                    {
                                                        name: e.currentTarget.getAttribute('name'),
                                                        desc: e.currentTarget.getAttribute('desc')
                                                    }
                                                )
                                            }}
                                        >
                                            修改
                                        </div>
                                        <div name={element.dataIndex} onClick={(e) => { setDeleteKey(e.currentTarget.getAttribute('name')) }} className="popover-content-delete">删除</div>
                                    </div>
                                }
                                trigger='click'
                                placement='bottom'
                            >
                                <EditOutlined onClick={() => { }} />
                            </Popover >
                        </div>
                    </div >
                )
                element.editable = true
            }
            setEditTableColumns(dataArray)
        } else {
            let dataArray = editTableColumns.filter(() => true)
            for (const element of dataArray) {
                element.title = (
                    <div style={{ marginLeft: '4px' }}>
                        <Tooltip title={element.desc}>
                            {element.dataIndex}
                        </Tooltip>
                    </div>
                )
                element.editable = false
            }
            setEditTableColumns(dataArray)
        }
    }, [isEditTable])
    //订阅删除列
    useEffect(() => {
        if (deleteKey !== null) {
            //删列名
            const columnsData = [...editTableColumns]
            setEditTableColumns(columnsData.filter((item) => item.dataIndex !== deleteKey))
            //删数据
            let dataSourceData = dataSource.filter(() => true)
            for (let element of dataSourceData) {
                delete element[deleteKey]
            }
            setDataSource(dataSourceData)
        }
    }, [deleteKey])
    //订阅编辑列
    useEffect(() => {
        if (editKey.name !== '') {
            setcolTitle({ name: editKey.name, desc: editKey.desc })
            setAddColModal(true)
        }
    }, [editKey])
    //关闭弹窗的时候清除数据
    useEffect(() => {
        if (addColModal === false) {
            setcolTitle({ name: '', desc: '' })
            setDeleteKey(null)
            setEditKey({ name: '', desc: '' })
        }
    }, [addColModal])
    const components = {
        body: {
            row: EditableRow,
            cell: EditableCell
        }
    }
    const columns = editTableColumns.map((col) => {
        if (!col.editable) {
            return col
        }
        return {
            ...col,
            onCell: (record) => ({
                record,
                editable: col.editable,
                dataIndex: col.dataIndex,
                title: col.title,
                handleSave: handleSave
            })
        }
    })
    const handleDelete = (key) => {
        const editTableDataSource = [...dataSource]
        setDataSource(editTableDataSource.filter((item) => item.key !== key))

    }
    const handleAdd = () => {
        //注意这里新增时key不能和原有数据重复
        const newData = {
            key: count
        }
        if (dataSource.length === 0) {
            setDataSource([newData])
        } else {
            setDataSource([...dataSource, newData])
        }
        setCount(count + 1)
    }
    const handleSave = (row) => {
        const newData = [...dataSource]
        const index = newData.findIndex((item) => row.key === item.key)
        const item = newData[index]
        newData.splice(index, 1, { ...item, ...row })
        setDataSource(newData)
    }
    function changeCol() {
        if (editKey.name === '') {
            setEditTableColumns((preState) => ([
                {
                    title: (
                        <div className="between">
                            <div style={{ marginLeft: '4px' }}>
                                <Tooltip title={colTitle.desc}>
                                    {colTitle.name}
                                </Tooltip>
                            </div>
                            &nbsp;&nbsp;&nbsp;&nbsp;
                            <div >
                                <Popover
                                    content={
                                        <div>
                                            <div
                                                name={colTitle.name}
                                                desc={colTitle.desc}
                                                onClick={(e) => {
                                                    setEditKey(
                                                        {
                                                            name: e.currentTarget.getAttribute('name'),
                                                            desc: e.currentTarget.getAttribute('desc')
                                                        }
                                                    )
                                                }}
                                            >
                                                修改
                                            </div>
                                            <div name={colTitle.name} onClick={(e) => { setDeleteKey(e.currentTarget.getAttribute('name')) }} >删除</div>
                                        </div>
                                    }
                                    trigger='click'
                                    placement='bottom'
                                >
                                    <EditOutlined onClick={() => { }} />
                                </Popover >
                            </div>
                        </div >
                    ),
                    dataIndex: colTitle.name,
                    width: '150px',
                    desc: colTitle.desc,
                    editable: true
                },
                ...preState
            ]))
            let dataArrayData = dataSource.filter(() => true)
            for (let element of dataArrayData) {
                element[colTitle.name] = ''
            }
            setDataSource(dataArrayData)
        } else {
            let dataArrayCol = editTableColumns.filter(() => true)
            for (let element of dataArrayCol) {
                if (element.dataIndex === editKey.name) {
                    element.dataIndex = colTitle.name
                    element.desc = colTitle.desc
                    element.title = (
                        <div className="between">
                            <div style={{ marginLeft: '4px' }}>
                                <Tooltip title={element.desc}>
                                    {element.dataIndex}
                                </Tooltip>
                            </div>
                            &nbsp;&nbsp;&nbsp;&nbsp;
                            <div >
                                <Popover
                                    content={
                                        <div>
                                            <div
                                                name={element.dataIndex}
                                                desc={element.desc}
                                                onClick={(e) => {
                                                    setEditKey(
                                                        {
                                                            name: e.currentTarget.getAttribute('name'),
                                                            desc: e.currentTarget.getAttribute('desc')
                                                        }
                                                    )
                                                }}
                                                className="popover-content-edit"
                                            >
                                                修改
                                            </div>
                                            <div name={element.dataIndex} onClick={(e) => { setDeleteKey(e.currentTarget.getAttribute('name')) }} >删除</div>
                                        </div>
                                    }
                                    trigger='click'
                                    placement='bottom'
                                >
                                    <EditOutlined onClick={() => { }} />
                                </Popover >
                            </div>
                        </div >
                    )
                }
            }
            setEditTableColumns(dataArrayCol)
            //修改数据中的key
            if (colTitle.name !== editKey.name) {
                let dataArrayData = dataSource.filter(() => true)
                for (let element of dataArrayData) {
                    element[colTitle.name] = element[editKey.name]
                    delete element[editKey.name]
                }
                setDataSource(dataArrayData)
            }
        }
        setAddColModal(false)


    }
    function cancelEdit() {
        setIsEditTable(() => false)
        getCaseDetail()
    }
    function getColumns() {
        let columnsData = []
        columnsData = columns.filter((item) => true)
        columnsData.push({
            title: '操作',
            dataIndex: 'operator',
            width: '15%',
            render: (_, record) => (
                <Button disabled={!isEditTable} type='link' onClick={() => handleDelete(record.key)}>
                    删除
                </Button>
            )
        })
        return columnsData

    }
    function saveParam() {
        //回调函数
        const successcallback = (res) => {
            setIsLoading(false)
        }
        setIsLoading(true)
        点击保存按钮将数据传到后端即可
    }
    return (
        <div className="edit-page-param">
            <div style={{ textAlign: right }}>
                <div>
                    {
                        isEditTable ? (
                            <div>
                                <Button icon={<PlusOutlined />} onClick={handleAdd} type='primary' style={{ marginBottom: 6, marginRight: 20 }}>
                                    新增一行
                                </Button>
                                <Button style={{ marginBottom: 6, marginRight: 20 }} type='primary' onClick={() => { setAddColModal(true) }}>
                                    添加参数
                                </Button>
                                <Button icon={<CloseOutlined />} onClick={() => { cancelEdit() }} type='default' style={{ marginBottom: 6, marginRight: 20 }}>
                                    取消
                                </Button>
                                <Button loading={isLoading} icon={<SaveOutlined />} style={{ marginBottom: 6 }} type='primary' onClick={() => { saveParam() }}>
                                    保存
                                </Button>
                            </div>
                        ) : (
                            <Button icon={<EditOutlined />} onClick={() => { setIsEditTable((preState) => true) }} type='primary' style={{ marginBottom: 6, marginRight: 20 }}>
                                编辑
                            </Button>
                        )
                    }
                </div>
            </div >
            <div >
                <Table
                    components={components}
                    rowClassName={() => 'editable-row'}
                    bordered
                    dataSource={dataSource}
                    columns={getColumns()}
                    pagination={false}
                />
            </div>
            <Modal
                visible={addColModal}
                maskClosable={false}
                closable={false}
                destroyOnClose
                cancelText='取消'
                okText='确认'
                onCancel={() => { setAddColModal(false) }}
                onOk={() => { changeCol() }}
            >
                {/* 表单不是ant的表单,使用时请自己转化成ant */}
                <Form labelPlacement='top'  initialValues={colTitle}>
                    <Form.Item label='列名' field='name' rules={{ required: true, message: '列名为必填' }}>
                        <Input placeholder="列名只可由数字、字母、下划线组成" onChange={(e) => { setcolTitle((preState) => ({ ...preState, name: e.target.value || '' })) }} />
                    </Form.Item>
                    <Form.Item label='描述' field='desc'>
                        <HiInput onChange={(e) => { setcolTitle((preState) => ({ ...preState, desc: e.target.value || '' })) }} />
                    </Form.Item>
                </Form>

            </Modal>
        </div>
    )
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容