之前做 课设项目 时,因为DDL突然提前而又找不到数据表格的模板,于是就自己用纯HTML手写了一个表格页面,实现CRUD增删改查。实现过程记录于 Django框架下SQLite3数据库CRUD增删改查的编写。
这次因为是做 毕设项目,因此还是比较认真的找了个比较完整的模板 flatlogic/react-material-admin,再根据这个模板进行修改。
写在前面:
如果你只是想找到一个MUITable的CRUD实现模板,请直接参考 Medium - Material-Table AutoFocus on Row Add 中的 App.js,实现简单效果也更好。
MUI的文档很乱,尤其DataTable资料非常少,请用Table实现。
表格模板 中只从前端页面实现了数据的查看与删除,因此修改和添加都要加入,并且还要连接这些功能与后端接口的连接。
MUITable/MUIDataTable/MUIGrid 如果只用来实现查看功能调用的代码还是比较简单的。如果不打算做些进阶操作,MUI标签中写入data,columns属性就行了。
const datatableData = [
["Joe James", "Example Inc.", "Yonkers", "NY"],
["John Walsh", "Example Inc.", "Hartford", "CT"],
["Bob Herm", "Example Inc.", "Tampa", "FL"],
];
export default function Tables() {
const classes = useStyles();
return (
<MUIDataTable
title="Employee List"
data={datatableData}
columns={["Name", "Company", "City", "State"]}
options={{
filterType: "checkbox",
}}
/>
);
}
但是我查了下资料,并没有看到其他人有讲到MUIDataTable 的CRUD实现的示例,有人有在Issue提过要一个CRUD的example但是被踩了回来。既然没找到,那我就自己写好了,而且MUIDataTable/MUIGrid都是MUITable变种,因此有些接口也许还能互通。
查看数据
查看的实现比较简单,因为模板内也有这个功能。因此只要把datatableData的内容设置为请求接口返回的结果,再渲染一下就大功告成。
MUI大部分模板都是使用Hook,使用useState声明变量(相当于Component的state),useEffect调用接口(相当于componentDidMount)。获得数据后简单判断下是否请求成功,成功则使用set方法设置数据。「使用set(包括setState)都会自动触发渲染。」
export default function Tables() {
const classes = useStyles();
// new line start
var [datatableData, setDatatableData] = useState([]);
useEffect(() => {
axios.post('/api/url', { data })
.then(res => {
if (res.status === 200 && Object.keys(res.request.response).length > 0) {
const data = JSON.parse(res.request.response);
setDatatableData(data);
}
})
}, [])
// new line end
return (
<MUIDataTable
title="Employee List"
data={datatableData}
columns={["Name", "Company", "City", "State"]}
options={{
filterType: "checkbox",
}}
/>
);
}
删除数据
模板在选中行后会出现删除按钮,并且可以成功删除。但是一旦刷新页面,数据就又恢复了。因此还需要连接接口,让数据真的被删除。
查找资料后在option中,发现有个名为onRowsDelete的属性,刚好可以满足此需求。data参数内的data.Index属性为网页列表的位置,通过index找到此行数据的id,进行删除。如果批量选中,则data.data为多个Object。
export default function Tables() {
const classes = useStyles();
var [datatableData, setDatatableData] = useState([]);
useEffect(() => {
axios.post('/api/url', { data })
.then(res => {
if (res.status === 200 && Object.keys(res.request.response).length > 0) {
const data = JSON.parse(res.request.response);
setDatatableData(data);
}
})
}, [])
return (
<MUIDataTable
title="Employee List"
data={datatableData}
// new line start
// ID段数据对于删除数据来说很重要
columns={["id", "Name", "Company", "City", "State"]}
// new line end
options={{
filterType: "checkbox",
// new line start
onRowsDelete: (data) => {
for (var i = 0; i < data.data.length; ++i) {
var index = data.data[i].dataIndex;
var id = datatableData[index].id;
axios.post('/api/url', { id: id })
.then(res => {
if (res.request.response !== "Succeed.") {
console.log(res.request.response);
}
})
}
}
// new line end
}}
/>
);
}
修改数据
修改数据就没找到比较好的方案。我设想是可以类似删除,在选中后出现编辑按钮,点击编辑变为可编辑状态,之后再点保存。就像 Medium - Material-Table AutoFocus on Row Add 实现的那样。
不过mui table的example里面是要你再新建一列来写交互按钮,包括修改/保存/添加/删除。这些功能的按钮,有些已经能在Toolbar上看到了,因此感觉mui的文档也是挺迷的。新功能倒是开发了很多,文档却没怎么维护,翻来翻去就是拿几篇基础文。
那我只能把每个可修改的column全都变为可编辑状态,并且随着内容的变化动态更新,然后在点击保存后传修改命令给后端接口。
P.s.「懒得自己写column点击编辑按钮状态再变可编辑,因为column option的重写量比较大,因此就没有这么操作(虽然在添加数据时发现还是跑不掉)。在option内写customBodyRender/customBodyRenderLite就能完成这个操作。」
P.s. 我是直接在OnChange修改state内的值(因为这么写比较短),这个用法不被推荐会报warning,推荐还是用set。
export default function Tables() {
const classes = useStyles();
var [datatableData, setDatatableData] = useState([]);
// new line start
var [columns, setColumns] = useState([
// ID段数据对于修改数据来说很重要
{
name: "id",
label: "ID",
options: {
display: "excluded",
},
},
// 如果是可修改数据,需要重写customBodyRender变为可编辑
{
name: "name",
label: "Name",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
const rowId = tableMeta.rowIndex;
return (
<FormControlLabel
value={value}
control={<TextField value={value} />}
onChange={e => {
updateValue(e.target.value);
columns.datatableData[rowId].username = e.target.value;
}}
/>
)
}
}
},
// 如果不可修改,不用写customBodyRender
{
name: "company",
label: "Company",
},
// City, State类似就不写了
// 编辑按钮
{
name: "edit",
label: "编辑",
options: {
filter: false,
sort: false,
empty: true,
customBodyRenderLite: (dataIndex) => {
return (
<Button
variant="contained"
color="primary"
size="large"
startIcon={<SaveIcon />}
// updateRow是调用axios更新数据的函数,函数在代码段结尾
onClick={() => updateRow(datatableData[dataIndex])}
>
保存
</Button>
);
}
}
},
]);
// new line end
useEffect(() => {
axios.post('/api/url', { data })
.then(res => {
if (res.status === 200 && Object.keys(res.request.response).length > 0) {
const data = JSON.parse(res.request.response);
setDatatableData(data);
}
})
}, [])
return (
<MUIDataTable
title="Employee List"
data={datatableData}
columns={columns}
options={{
filterType: "checkbox",
onRowsDelete: (data) => {
for (var i = 0; i < data.data.length; ++i) {
var index = data.data[i].dataIndex;
var id = datatableData[index].id;
axios.post('/api/url', { id: id })
.then(res => {
if (res.request.response !== "Succeed.") {
console.log(res.request.response);
}
})
}
}
}}
/>
);
}
// new line start
// #################################################################
function updateRow(row) {
var data;
if (row.name !== null) {
data = {
id: row.id,
name: row.name
}
axios.post('/api/url', data)
.then(res => {
if (res.request.response !== "Succeed.") {
console.log(res.request.response);
}
})
}
}
// new line end
添加数据
在上一部使用重写column的customBodyRender,实现了编辑数据并且请求修改功能。添加数据则设想在Toolbar上加入一个添加按钮,点击在首行出现空行用于填写数据,填写内容后点击添加。
P.s. 有些不可修改的数据段需要具备写功能用于添加,因此这边需要写一个if来区分是调用更新还是添加,判断条件定为是否有ID。(还没被添加到数据库的数据无ID)。
export default function Tables() {
const classes = useStyles();
var [datatableData, setDatatableData] = useState([]);
var [columns, setColumns] = useState([
{
name: "id",
label: "ID",
options: {
display: "excluded",
},
},
// 原本可编辑的数据段不用修改
{
name: "name",
label: "Name",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
const rowId = tableMeta.rowIndex;
return (
<FormControlLabel
value={value}
control={<TextField value={value} />}
onChange={e => {
updateValue(e.target.value);
columns.datatableData[rowId].username = e.target.value;
}}
/>
)
}
}
},
// 不可编辑的数据段需要判断是否为添加,是则可编辑。条件为是否有ID
{
name: "company",
label: "Company",
// new line start
options: {
customBodyRender: (value, tableMeta, updateValue) => {
const rowId = tableMeta.rowIndex;
if ('id' in datatableData[rowId]) {
return (
<FormLabel>
{value}
</FormLabel>
)
} else {
return (
<FormControlLabel
value={value}
control={<TextField value={value} />}
onChange={e => {
updateValue(e.target.value);
datatableData[rowId].company = e.target.value;
}}
/>
)
}
}
}
// new line end
},
// City, State类似就不写了
// 编辑按钮也要做判断,判断是修改功能还是添加功能。
{
name: "edit",
label: "编辑",
options: {
filter: false,
sort: false,
empty: true,
customBodyRenderLite: (dataIndex) => {
// new line start
if ('id' in datatableData[dataIndex]) {
return (
<Button
variant="contained"
color="primary"
size="large"
startIcon={<SaveIcon />}
onClick={() => updateRow(datatableData[dataIndex])}
>
保存
</Button>
);
} else {
return (
<Button
variant="contained"
color="secondary"
size="large"
startIcon={<PublishIcon />}
onClick={() => {
// appendRow是调用axios添加数据的函数,函数在代码段结尾
appendRow(datatableData[dataIndex]);
// 添加数据后用set重新渲染效果不好,因此强制重载刷新表格(不推荐)
window.location.reload();
}}
>
添加
</Button>
);
}
// new line end
}
}
},
]);
useEffect(() => {
axios.post('/api/url', { data })
.then(res => {
if (res.status === 200 && Object.keys(res.request.response).length > 0) {
const data = JSON.parse(res.request.response);
setDatatableData(data);
}
})
}, [])
return (
<MUIDataTable
title="Employee List"
data={datatableData}
columns={columns}
options={{
filterType: "checkbox",
onRowsDelete: (data) => {
for (var i = 0; i < data.data.length; ++i) {
var index = data.data[i].dataIndex;
var id = datatableData[index].id;
axios.post('/api/url', { id: id })
.then(res => {
if (res.request.response !== "Succeed.") {
console.log(res.request.response);
}
})
}
}
}}
/>
);
}
// #################################################################
function updateRow(row) {
var data;
if (row.name !== null) {
data = {
id: row.id,
name: row.name,
company: row.company,
...
}
axios.post('/api/url', data)
.then(res => {
if (res.request.response !== "Succeed.") {
console.log(res.request.response);
}
})
}
}
// new line start
function appendRow(row) {
var data = {
name: row.name,
company: row.company,
...
}
axios.post('/api/url', data)
.then(res => {
if (res.request.response !== "Succeed.") {
alert(res.request.response);
}
})
}
// new line end
总结
这个MUIDataTable实现CRUD增删改查的写法也不是最好的写法,其中有很多问题,如:
- 手动修改数据内容而不用set修改;
- 无编辑按钮让文本从标签变为编辑状态,而是直接可编辑;
- 添加数据后强制刷新(数据量大情况下重载耗时很长);
- 因为没用set渲染时已经删掉的数据可能还会跑出来;
- 点击按钮反馈不强(无加载或是其他动画);
- 代码写的也不优雅(一开始还行,发现要重写那么多customBodyRender后就破罐破摔了)。
但是总算是完成MUIDataTable CRUD功能的基本实现了。
参考资料: