MaterialUI Datagrid 实现可编辑表格

最近遇到一个需求是需要用MaterialUI 实现可编辑表格,但Material UI比较符合并且比较完整的带有增删改查的可编辑DataGrid需要收费,于是就基于其免费版本的基础下做了一些修改。

需求和实现效果展示

需求

导入表格后,不符合要求的列做标红处理(比如必填项),可以筛选出出错的数据,可以对数据进行删除和修改,分页需可直接跳转到具体的某一页中

效果展示

截屏2023-02-28 12.03.17.png

可编辑修改

将DataGrid的编辑状态改成行编辑

editMode="row"

禁止其默认操作,如双击某行进入编辑模式,失去焦点变回不可编辑模式

 const handleRowEditStop = (params, event) => {
    event.defaultMuiPrevented = true;
  };
  const handleRowEditStart = (params, event) => {
    event.defaultMuiPrevented = true;
  };
  onRowEditStop={handleRowEditStop}
  onRowEditStart={handleRowEditStart}

processRowUpdate当用户执行停止编辑的操作时调用该道具。该道具使用两个参数调用:

  • 通过后具有新值的更新行valueSetter
  • 编辑单元格或行之前行的值
    保存行后,processRowUpdate必须返回将用于更新内部状态的行对象。返回的值用作调用 的参数apiRef.current.updateRows。
const processRowUpdate = (newRow) => {
    const updatedRow = { ...newRow, isNew: false };
    setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
    return updatedRow;
  }

删除前判断该行是否有值控制是否显示确认删除弹框

const handleDeleteClick = (id) => () => {
    const deleteRow = rows.filter((row) => row.id === id)
    let ss = {...deleteRow[0]}
    delete ss.id;
    delete ss.isNew;
    if(JSON.stringify(ss) === '{}') {
      setRows(rows.filter((row) => row.id !== id));
    } else {
      setCommonDialog({visible: true, type: 'delete', id, value: { content: '确认删除该行吗?', confirm: '确定', cancel: '取消', title: '删除'}})
    }
  };

添加新行操作

const handleAddClick = () => {
    const id = randomId();
    setRows((oldRows) => [{ id, isNew: true }, ...oldRows]);
    setRowModesModel((oldModel) => ({
      ...oldModel,
      [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' },
    }));
  };

判断导入数据正确的条数(必填项是否不为空)

 const changeRows = async (value) => {
    setRows(value);
    setRowModesModel({});
    const front = value.filter((ele)=> !personSchema.isValidSync(ele));
    const len = value?.length;
    if(front?.length === 0) {
      setCommonDialog({visible: true, value: { content: `共导入${len}条数据,${len}条正确`, title: '导入临床信息', confirm: '确定' } })
    } else {
      const frontError = front.map((ele)=>ele.id)
      const value = () => (
        <Box>
          <Box>
            共导入{len}条数据,{len - errorSet.size}条正确,<Box component='span' sx={{color: '#FF0000'}}>{frontError.length}条错误</Box>
          </Box>
          <Box sx={{marginTop: '20px'}}>错误信息已标红,具体请查看导入列表</Box>
        </Box>
      )
      setCommonDialog({visible: true, value: {content: value(), title: '导入临床信息', confirm: '确定'}})
    }
  }

使用yulp库对数据的格式进行验证

import { string, object, number, date } from 'yup'
const personSchema = object({
  batchNo: string().required('实验测序批次编号为必填项,不能为空'),
  specimenNo: string().required('样本编号为必填项,不能为空'),
  specimenIndex: string().required('样本Index标签为必填项,不能为空'),
  age: number().required('年龄为必填项,不能为空'),
  symptom: string().required('临床症状为必填项,不能为空'),
  samplingDate: date()
                  .required('采样日期为必填项,不能为空')
                  .max(new Date(), '日期不能填今日之后')
});
export default personSchema

//返回所以验证不通过的对象
 const front = value.filter((ele)=> !personSchema.isValidSync(ele)); 
// 判断单条数据是否通过验证
await personSchema.validate(editedRow,{ abortEarly: false });

对columns中各字段的处理

必填列列名前加 *
const addIcon = (value) => {
    return <Box sx={{ fontWeight: 500, color: 'rgba(0, 0, 0, 0.87)' }}><Box component="span" sx={{color: '#ff0000'}}>*</Box>{value}</Box>
  }
renderHeader: ()=>addIcon('实验测序批次编号'),
必填项未输入则标红处理
const renderRequireCell = (props) => {
    const { value, field, id } = props;
    if(specialError.get(id + '') && specialError.get(id + '')?.includes(field)) {
      return <Box className="errorBox">{value}</Box>
    }
    if(!value) {
    return <Box className="errorBox">{value}</Box>
    } else {
      return <Box>{value}</Box>
    }
  }
可编辑框渲染
const renderEditInputCell = (params) => {
    return <EditInputCell {...params} />
  }

const EditInputCell = (props) => {
  const { id, value, field, headerName } = props;
  const { renderType } = props?.colDef
  const apiRef = useGridApiContext();
  const handleChange = async (event) => {
    if(field === 'samplingDate') {
      await apiRef.current.setEditCellValue({ id, field, value: format(event, 'yyyy/M/dd') });
    } else {
      await apiRef.current.setEditCellValue({ id, field, value: event?.target?.value || event });
    }
    apiRef.current.stopCellEditMode({ id, field });
  };
  const handleNumberChange = async (event) => {
    await apiRef.current.setEditCellValue({ id, field, value: event.target.value ? event.target.value?.replace(/[^0-9 ]/,'') : '' });
    apiRef.current.stopCellEditMode({ id, field });
  };
  switch(renderType) {
    case 'date':
      return (
        <DatePicker
          inputFormat='yyyy-MM-dd'
          value={value || null}
          defaultValue = {null}
          disableFuture
          onChange={handleChange}
          className="datepicker"
          renderInput={(params) => (
            <TextField
              {...params}
              label={undefined}
              margin="dense"
              size="small"
              className={value && new Date(value).valueOf() <= new Date().valueOf() ? 'rightTextField' : 'errorTextField'}
            />
          )}
        />
      )
    case 'select':
      const { option } = props?.colDef
      return (
        <FormControl className={field}>
          <Select
            defaultValue = ""
            labelId="demo-simple-select-label"
            id="demo-simple-select"
            value={value || ''}
            size="small"
            sx={{width: 90}}
            onChange={handleChange}
          >
            {
              option.map((ele)=>(
                <MenuItem value={ele}>{ele}</MenuItem>
              ))
            }
          </Select>
      </FormControl>
      )
    case 'number':
      return <TextField value={value} className={value ? 'rightTextField' : 'errorTextField'} onChange={handleNumberChange} label='' size="small" />
    default:
      return (
        <TextField value={value} className={field === 'symptom' && !value ? 'errorTextField' : 'rightTextField' } onChange={handleChange} label='' size="small"/>
      );
  }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容