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>
<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>
<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>
<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>
<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>
)
}