React 模板封装之 BaseTable

  • 前言
  • 一、基础模板 BaseTable
  • 二、使用案例
  • 三、API 使用指南
  • 四、源代码
  • 五、总结

前言

前面有写过几篇 React 组件封装的文章。今天来记录下 React 模板封装之基础模板 BaseTable。

组件与模板
组件相信大家都知道,只要是相同的框架内,组件可以用于任何项目中。比如 antd 的 table 组件,只要有表格的场景都可以使用 table 组件。而模板是对组件的功能进一步拓展,使其应用于某种特定的业务流程中。比如具备展示功能的 table 表格只需要使用 table 组件即可。如果是具备增删改查功能的 table 表格则需要进一步拓展 table 的功能使其成为一个模板。

一、基础模板 BaseTable

模板说明:

用户只需要配置参数,就可以实现具备增删改查的 table 表格。

效果展示:

react EditableProTable的字段的dependencies react base table_Boo


使用场景:

特别适合后台管理系统。很多后台管理系统有大部分是由表格构成,并且在实际开发中,大部分的表格并不只是纯展示,而是具备增删改查一套功能,比如由后台维护轮播图,必定会提供对轮播图进行增加、修改、删除等功能。

二、使用案例

参数说明

  1. modalList :配置接口对应的字段数据集。
  2. actions:配置接口功能,如配置增删改查接口(url、method、data等)。
  3. showDetail:当编辑或者新增时以页面形式打开而不是弹框。
  4. 文中有涉及到的 queryData 参数和 delData 参数,分别表示查询的额外字段和删除的额外字段。额外之外指的是独立于业务场景之外的。比如在查询的时候要需要传一个系统版本或者设备信息等。这两个参数也可以理解为是预留参数,几乎用不到。

从以下的案例可知,除了配置之外,只需要写一行代码即可实现如上图的具备增删改查的文章管理功能。

import React from "react";
import BaseTable from "../../../../template/BaseTable/index";
const modalList = [
  {
    label: "文章ID",
    field: "id",
    renderType: "Input",
    visible: false,
    writable: true,
    isUpdate: true,
    updateField: "id",
  },
  {
    label: "文章标题",
    field: "title",
    renderType: "Input",
    visible: true,
    require: true,
    isUpdate: true,
    updateField: "title",
    isSearch: true,
    searchField: "title",
    updateWritable: true,
    updateVisible: true,
  },
  {
    label: "所属期刊",
    require: true,
    field: "periodicalName",
    require: true,
    updateField: "periodicalId",
    renderType: "Select",
    visible: true,
    writable: true,
    isUpdate: true,
    updateField: "periodicalId",
    updateWritable: true,
    updateVisible: true,
    isSearch: true,
    searchField: "periodicalId",
    keyValueField: ["periodicalId", "periodicalName"],
    sourceApi: { url: "/web/weeksPeriodical/periodicalList", method: "get" },
  },
  {
    label: "所属栏目",
    require: true,
    field: "columnName",
    updateField: "columnId",
    isSearch: true,
    searchField: "columnId",
    renderType: "Select",
    require: true,
    visible: true,
    writable: true,
    isUpdate: true,
    updateField: "columnId",
    updateWritable: true,
    updateVisible: true,
    keyValueField: ["columnId", "columnName"],
    sourceApi: {
      url: "/admin/weeksPeriodicalColumn/columnList",
      method: "post",
      data: { periodicalId: 0 },
    },
  },
  {
    label: "更新时间",
    field: "updateTime",
    renderType: "Picker",
    format: "YYYY-MM-DD",
    visible: true,
    isUpdate: false,
  },
  {
    label: "文章内容",
    field: "content",
    updateField: "content",
    writable: true,
    renderType: "Editor",
    visible: false,
    width: 250,
    isUpdate: true,
    updateField: "content",
    updateWritable: true,
    updateVisible: true,
  },
  {
    label: "文章概述",
    field: "summarize",
    updateField: "summarize",
    writable: true,
    renderType: "Textarea",
    require: true,
    visible: false,
    width: 360,
    isUpdate: true,
    updateField: "summarize",
    updateWritable: true,
    updateVisible: true,
  },
  {
    label: " 外部链接",
    field: "url",
    updateField: "url",
    writable: true,
    renderType: "Input",
    visible: false,
    width: 250,
    isUpdate: true,
    updateField: "url",
    updateWritable: true,
    help: "请输入有效的url",
    updateVisible: true,
  },
  {
    label: "排序",
    field: "sort",
    require: true,
    updateField: "sort",
    writable: true,
    renderType: "InputNumber",
    require: true,
    visible: true,
    isUpdate: true,
    updateField: "sort",
    updateWritable: true,
    updateVisible: true,
  },
];
const actions = [
  {
    status: "update",
    name: "编辑",
    url: "/admin/weeksArticle/update",
    method: "post",
    showColumn: true,
  },
  {
    status: "copy",
    name: "复制链接",
    url: "",
    method: "",
    linkField: "detailPageUrl",
    showColumn: true,
  },
  {
    status: "add",
    name: "新增",
    url: "/admin/weeksArticle/add",
    method: "post",
    showColumn: false,
  },
  {
    status: "query",
    name: "查询",
    url: "/admin/weeksArticle/listByPage",
    method: "post",
    extraField: "list",
    showColumn: false,
  },
];
function Article(){
    return (
        <BaseTable actions={actions} modalList={modalList} showDetail={true} />
      );
}
export default Article;

三、API 使用指南

modalList :配置接口对应的字段数据集

属性

说明

类型

默认值

label

表格的标题,新增或者编辑时的字段标签

String


field

接口对应的字段

String


renderType

表格渲染的组件类型

Array


updateRenderType

新增/编辑时渲染的组件类型,如:表格列表中使用的 Switch 组件,新增/编辑时使用 Radio 组件

Array


require

是否校验必填字段

Boolean


visible

表格列表中是否可见

Boolean


isUpdate

是否作为更新字段

Boolean


updateField

接口中对应的更新字段,一般与 field相同

String


updateVisible

更新字段是否可见

Boolean


updateWritable

更新字段是否可编辑

Boolean


keyValueField

子节点

Array


sourceApi

配置接口的参数,如单选或者多选时需要调接口获取数据

Object


actions:配置功能接口

属性

说明

类型

默认值

status

接口功能枚举值,枚举值目前有:add/del/update/query/export/import/copy,需要那种功能就填写对应的枚举值,不可更改

String


name

接口功能名称,与status对应分别是增/删/改/查/导入/导出/复制,与status不同的是名称是可以更改的,比如表示增加时可以写“新增”或者“增加”

Function


url

接口url地址

Array


method

接口请求方式

String


extraField

额外字段,可配置查询时数据集字段。比如查询接口返回形式为{code:0,msg:“成功”,data:{list:[{id:1}]} 就需要配置 list 字段。

Array


linkField

复制时对应的链接地址字段

String


showColumn

是否显示在列

Boolean


showDetail:当编辑或者新增时以页面形式打开而不是弹框。

属性

说明

类型

默认值

showDetail

当编辑或者新增时以页面形式打开而不是弹框

Boolean


四、源代码

在 BaseTable 目录中包含如下文件。

index.js

import React, { useEffect, useState, useImperativeHandle, useRef } from "react";
import { Table, Modal, message } from "antd";
import DetailView from "./detail";
import SearchByCondition from "./searchByCondition";
import dealWithColumns from "./dealWithColumns";
import { delAPI, addAPI, editAPI, getListAPI,request } from "../commonApi";
import { generate} from "../../utils/tools";
import ModalBox from "./modalBox";
import copy from 'copy-to-clipboard';
import {
  EditOutlined,
  CopyOutlined,
  PlusOutlined,
  DeleteOutlined,
  InstagramOutlined
} from '@ant-design/icons';
let { confirm } = Modal;
function evaluateList(setDataSource, props,setCount) {
  if (props.queryData) {
    getList(setCount,setDataSource, props, props.queryData);
  } else {
    getList(setCount,setDataSource, props, {});
  }
}
function dealWithData(treeData, keys) {
  return generate(treeData,  keys["topId"],  keys);
}
function getList(setCount,setDataSource, props, record, initData = "") {
  let { actions, modalList } = props; 
  let action = actions.filter((item) => item.status === "query")[0];
  getListAPI(action, modalList, record, initData, (state) => {
    if (props.isTreeTable) {
      setDataSource(dealWithData(state.dataSource, props.keys));
    } else {
      setDataSource(state.dataSource);
      if(setCount){
        setCount(state.count);
      }
    }   
  });
}
function summary(count) {
  return (
    <div style={{ marginLeft: "25px", color: "#1890ff" }}>
      一共【{count}】条数据
    </div>
  );
}
//页码改变
function onChangePage(queryOption,props,setCount,setDataSource, setCurrent, pageSize, current) {
  let option = queryOption.current.option||{}; 
  setCurrent(current);
  getList(setCount,setDataSource, props, option, { pageCount: current, pageSize }, current);
}
//打开添加模态框
function openAddModal(
  props,
  setShowDetail,
  setShowModal,
  setTitle,
  setOption,
  item,
  record
) {
  const { showDetail } = props;
 
  if (showDetail) {
    setShowDetail(true);
  } else {
    setShowModal(true);
  }
  var keys = Object.keys(record);
  //判断record不是空对象
  if (keys && keys.length>0) {
    setTitle("插入");
    if (item.data && Array.isArray(item.data)) {
      item.data.map((item1) => {
        let field = item1.field;
        let updateField = item1.updateField;
        setOption((data) => {
          data[updateField] = record[field];         
          return JSON.parse(JSON.stringify(data));
        });
      });
    }
  } else {
    setTitle("新增");
    setOption({});
  }
}
function lookDetail(setShowDetail, setOption, item,record) {
  setShowDetail(true);
  setOption((data) => {
    for (let i in record) {
      data[i] = record[i];
    }
    return JSON.parse(JSON.stringify(data));
  });
}
//打开编辑模态框
function openEditModal(
  props,
  setShowDetail,
  setShowModal,
  setTitle,
  setOption,
  item,
  record
) {
  const { editCB, showDetail } = props;
  if (editCB) {
    editCB(record, item, this);
  }
  setTitle("编辑");
  if (showDetail) {
    setShowDetail(true);
  } else {
    setShowModal(true);
  }
  setOption((data) => {
    for (let i in record) {
      data[i] = record[i];
    }
    return JSON.parse(JSON.stringify(data));
  });
}
//删除

function delRecord(props, setDataSource,setCount, item,record) {
  const { modalList, delData } = props;
  confirm({
    title: "Are you sure delete this task?",
    content: "Some descriptions",
    okText: "Yes",
    okType: "danger",
    cancelText: "No",
    onOk: () => {
      delAPI(item, modalList, { ...record, ...delData }, () => {
        message.success("删除成功");
        evaluateList(setDataSource, props,setCount);
      });
    },
    onCancel() {},
  });
}
function submit(
  title,
  option,
  showDetail,
  current,
  props,
  setCurrent,
  pageSize,
  setShowDetail,
  setShowModal,
  setDataSource,
  setCount
) {
  const { actions, modalList } = props;
  let action = {};
  if (title === "新增") {
    action = actions.filter((item) => item.status === "add")[0];
    addAPI(action, modalList, option, () => {
      setShowDetail(false);
      setShowModal(false);
      setCurrent(1);
      evaluateList(setDataSource, props,setCount);
      message.success("添加成功");
    });
  } else if (title === "插入") {
    action = actions.filter((item) => item.status === "insert")[0];
    editAPI(action, modalList, option, () => {
      setShowModal(false);
      if (showDetail) {
        setShowDetail(false);
        getList(setCount,setDataSource, props, {}, { pageCount:current, pageSize }, current);
      } else {
        getList(setCount,setDataSource, props, {}, { pageCount:current, pageSize }, current);
      }
      message.success("插入成功");
    });
  }else if (title === "编辑") {
    action = actions.filter((item) => item.status === "update")[0];
    editAPI(action, modalList, option, () => {
      setShowModal(false);
      if (showDetail) {
        setShowDetail(false);
        getList(setCount,setDataSource, props, {}, { pageCount:current, pageSize }, current);
      } else {
        getList(setCount,setDataSource, props, {}, { pageCount:current, pageSize }, current);
      }
      message.success("编辑成功");
    });
  }
}

function dealWithActions(modalList, actions) {
  let isSearch = false;
  for (let i of modalList) {
    if (i.isSearch) {
      isSearch = i.isSearch;
    }
  }
  if (isSearch) {
    return actions;
  } else {
    return actions.filter(
      (item) => item.status !== "query" && !item.showColumn
    );
  }
}
function goBack(setCount,setShowDetail, setDataSource, props) {
  setShowDetail(false);
  getList(setCount,setDataSource, props, {});
}
function coppyRecord(item,record){
  copy(record[item['linkField']]);
  message.success('复制成功');
}
function openPage(item,record){
  window.open(record[item['linkField']]);
}
function dealWithList(
  operateList,
  props,
  setShowDetail,
  setShowModal,
  setTitle,
  setOption,
  setDataSource,
  setCount
) {
  operateList.map((item) => {
    if (item.status === "update") {
      item.type = "primary";
      item.icon = <EditOutlined />;
      item.do = openEditModal.bind(
        this,
        props,
        setShowDetail,
        setShowModal,
        setTitle,
        setOption
      );
    } else if (item.status === "del") {
      item.type = "danger";
      item.icon = <DeleteOutlined />;
      item.do = delRecord.bind(this, props, setDataSource,setCount);
    } else if (item.status === "copy") {
      item.type = "";
      item.icon = <CopyOutlined />;
      item.do = coppyRecord.bind(this);
    } else if (item.status === "preview") {
      item.type = "dashed";
      item.icon = <InstagramOutlined />;
      item.do = openPage.bind(this);
    }    
    else if (item.status === "insert") {
      item.type = "primary";
      item.icon = <PlusOutlined/>;
      item.do = openAddModal.bind(
        this,
        props,
        setShowDetail,
        setShowModal,
        setTitle,
        setOption
      );
    } else {
      item.type = "primary";
      item.icon = "folder-open";
      item.do = lookDetail.bind(this, setShowDetail, setOption);
    }
  });
  return operateList;
}
async function getDataForOptions (item,setRefresh){
   let {url,method,data} = item.sourceApi;
   let res = await request({
    url,
    method,
    ...data
});
if (res.code !== '0') return;
let result = res.data;
result = result.map((item)=>{
    return {
            title:item.name,
            key:item.id,
            ...item
    }
});
result = generate(result,  0, { id: "id", parentId: "parentId", name: "name"});
item.options = result;
setRefresh(refresh=>!refresh)
}
function BaseTable(props, ref) {
  let pageCount = 1,
    pageSize = 10;
  let [title, setTitle] = useState("");
  let [dataSource, setDataSource] = useState([]);
  let [showDetail, setShowDetail] = useState(false);
  let [showModal, setShowModal] = useState(false);
  let [count, setCount] = useState(0);
  let [current, setCurrent] = useState(1);
  let [option, setOption] = useState({});
  let [refresh, setRefresh] = useState(0);
  let queryOption = useRef(null);
  let {
    modalList,
    actions,
    rowSelection,
    components,
    onRow,
    pagination,
    onExpand,
    expandedRowRender,
    onExpandedRowsChange,
    expandedRowKeys,
    defaultExpandedRowKeys,
    labelCol,
    wrapperCol    
  } = props;
  let columns = [];
  let columnsList = modalList.filter((item) => item.visible);
  let columnsUpdateList = modalList.filter(
    (item) => item.isUpdate && item.updateVisible
  );
  let actionsColumn = actions.filter((item) => item.showColumn);
  let actionsQuery = dealWithActions(modalList, actions);
  if (modalList.length > 0) {
    columns = dealWithColumns(
      columnsList,
      dealWithList(
        actionsColumn,
        props,
        setShowDetail,
        setShowModal,
        setTitle,
        setOption,
        setDataSource,
        setCount
      ),
      setRefresh
    );
  };
  useEffect(() => {
    evaluateList(setDataSource, props,setCount);
    let needRequest = modalList.some(item=>item.doApi);
    if(needRequest){
      let item = modalList.filter(item=>item.doApi)[0];
      getDataForOptions(item,setRefresh)
    }    
  }, []);
  useImperativeHandle(ref, () => ({
    dataSource,
    setDataSource,
  }));
  return (
    <div className="cm-bc-white">
      {showDetail ? (
        <div className="cm-p-02">
          <div
            onClick={() => goBack(setCount,setShowDetail, setDataSource, props)}
            className="cm-flex cm-ai-bl cm-cursor-p cm-border-bottom-ddd cm-ptb-01 cm-c-666"
          >            
            <span className="cm-mr-01 cm-img-01"><</span>
            <span>返回</span>
          </div>
          <div className="cm-c-333 cm-fw-bold cm-fs-020 cm-mtb-01">{title}</div>
          <DetailView
            option={option}
            title={title}
            modalList={columnsUpdateList}
            submit={() =>
              submit(
                title,
                option,
                showDetail,
                current,
                props,
                setCurrent,
                pageSize,
                setShowDetail,
                setShowModal,
                setDataSource,
                setCount
              )
            }
          />
        </div>
      ) : (
        <div className="cm-p-02">
          <div className="cm-flex cm-mb-02 cm-jc-sb">
            <SearchByCondition
              actions={actionsQuery}
              modalList={modalList}
              option={option}
              ref={queryOption}
              setCurrent={setCurrent}
              getList={getList.bind(this, setCount,setDataSource, props)}
              openAddModal={openAddModal.bind(
                this,
                props,
                setShowDetail,
                setShowModal,
                setTitle,
                setOption
              )}
            />
          </div>
          {expandedRowKeys ? (
            <Table
              columns={columns}
              rowSelection={rowSelection}
              dataSource={dataSource}
              components={components}
              onExpand={onExpand}
              expandedRowRender={expandedRowRender}
              expandedRowKeys={expandedRowKeys || []}
              defaultExpandedRowKeys={defaultExpandedRowKeys || []}
              onExpandedRowsChange={onExpandedRowsChange}
              onRow={onRow}
              rowKey={(record) => record[modalList[0].field]}
              footer={() => (count ? summary(count) : null)}
              pagination={
                pagination === false
                  ? pagination
                  : {
                      current: current,
                      onChange: onChangePage.bind(
                        this,
                        queryOption,
                        props,
                        setCount,                       
                        setDataSource,                       
                        setCurrent,
                        pageSize                     
                      ),
                      total: count,
                    }
              }
            />
          ) : (
            <Table
              columns={columns}
              rowSelection={rowSelection}
              dataSource={dataSource}
              components={components}
              onExpand={onExpand}
              expandedRowRender={expandedRowRender}
              onExpandedRowsChange={onExpandedRowsChange}
              onRow={onRow}
              rowKey={(record) => record[modalList[0].field]}
              footer={() => (count ? summary(count) : null)}
              pagination={
                pagination === false
                  ? pagination
                  : {
                      current: current,
                      onChange: onChangePage.bind(
                        this,
                        queryOption,
                        props,
                        setCount,                       
                        setDataSource,                       
                        setCurrent,
                        pageSize      
                      ),
                      total: count,
                    }
              }
            />
          )}
        </div>
      )}
     <ModalBox
        title={title}
        visible={showModal}
        labelCol={labelCol}
        wrapperCol={wrapperCol}
        onOk={() =>
          submit(
            title,
            option,
            showDetail,
            current,
            props,
            setCurrent,
            pageSize,
            setShowDetail,
            setShowModal,
            setDataSource
          )
        }
        option={option}
        onCancel={() => setShowModal(false)}
        setRefresh={setRefresh}
        modalList={columnsUpdateList}
      />        
    </div>
  );
}

export default React.forwardRef(BaseTable);

detail.js

import React,{useState} from 'react';
import { RenderModal } from '../component';
import {
    Form,
    Button
} from 'antd' 
function DetailView(props){
    let modalList = props.modalList;
    let {option, title} = props;     
    const [refresh,setRefresh] = useState(false);
    return (
        <div>
            <Form  labelAlign="right"
                   labelCol={{ span: 6 }}
                   wrapperCol={{ span: 14 }}
            >
                <div className="cm-flex cm-jc-sa">
                    <div className="cm-flex-column">
                        {modalList.map((item, index) => {
                            if (item.renderType !== "Editor") {
                                if(item.updateRenderType){
                                    item.renderType = item.updateRenderType
                                }
                                return <Form.Item label={item.label} 
                                required={item.require}                                  
                            
                                key={index} style={{display: "flex"}}>
                                    {RenderModal(item, option,setRefresh)}
                                </Form.Item>
                            }
                        })}
                        {title !== '查看' ?
                            <Button
                                style={{alignSelf:"center"}}
                                onClick={props.submit}
                                type="primary"
                            >
                                提交
                            </Button> : null
                        }
                    </div>
                    <div>
                        {modalList.map((item, index) => {
                            if (item.renderType === "Editor") {
                                return <div>{RenderModal(item, option,setRefresh)}</div>
                            }
                        })}
                    </div>
                </div>
            </Form>
        </div>
    )
}
export default DetailView;

dealWithColumns.js

import React from "react";
import { Button, Tag, Popover, Switch, message } from "antd";
import { request } from "../commonApi";
import moment from "moment";
import "moment/locale/zh-cn";
const dateFormat = "YYYY-MM-DD";
let count = 1;
function dealWithColumns(columns, operateList, setRefresh) {
  var newCol = [];
  columns.map((item) => {
    let render = (text) => {
      if (item.label === "序号") {
        return <span>{count++}</span>;
      }
      if (Array.isArray(text) && item.keyValueField) {
        return (
          <div>
            {text.map((item1, index) => {
              return (
                <span key={index}>{item1[item.keyValueField[1]]} </span>
              );
            })}
          </div>
        );
      } else {
        return <span>{text}</span>;
      }
    };
    let renderType = item.renderType;
    if (renderType === "Upload") {
      render = (text) => (
        <img src={text} style={{ width: "160px", height: "90px" }} alt="" />
      );
    }
    if (renderType === "Radio" && Array.isArray(item.radioOptions)) {
      render = (text) => <div>{getName(item.radioOptions, Number(text))}</div>;
    } else if (renderType === "Tag") {
      render = (text) => getTag(item.radioOptions, Number(text));
    } else if (renderType === "Popover") {
      render = (text, record) => (
        <Popover content={record[item.linkField]} title="Link">
          <Tag color="blue">
            <a href={record[item.linkField]}>{record[item.field]}</a>
          </Tag>
        </Popover>
      );
    } else if (renderType === "Switch") {
      render = (text, record) => (
        <Switch
          checked={!!record[item.field]}
          onChange={(isShow) =>
            updateBannerStatus(isShow, record, item, setRefresh)
          }
        />
      );
    } else if (renderType === "Picker") {
      render = (text, record) => moment(text).format(item.format || dateFormat);
    } else if (renderType === "MultipleField") {
      render = (text, record) => {    
          console.log(record[item.field]);    
        return (
          <div>
            {record[item.field] && Array.isArray(record[item.field])&&record[item.field].map((item1, index) => {
                return (
                  <span key={index}>
                    {item1[item.keyValueField[1]]}
                    <span>
                      {record[item.field].length - 1 !== index ? item.splitKey : " "}
                    </span>
                  </span>
                );
              })}
          </div>
        );
      };
    }
    newCol.push({
      title: item.label,
      dataIndex: item.field,
      key: item.field,
      width: item.width,
      render,
    });
  });
  if (operateList && operateList.length && operateList.length > 0) {
    newCol.push({
      title: "操作",
      dataIndex: "button",
      render: (text, record) =>
        operateList.map((item, index) => {
          if (item.status === "copy") {
            let text = record[item.linkField];
            return (
              <Popover content={record[item.linkField]} title="Link">
                <Button
                  type={item.type}
                  icon={item.icon}
                  key={index}
                  className="cm-mr-02 btn"
                  onClick={() => item.do(item, record)}
                >
                  {item.name}
                </Button>
              </Popover>
            );
          } else {
            return (
              <Button
                type={item.type}
                icon={item.icon}
                key={index}
                className="cm-mr-02"
                onClick={() => item.do(item, record)}
              >
                {item.name}
              </Button>
            );
          }
        }),
    });
  }
  count = 1;
  return newCol;
}
async function updateBannerStatus(isShow, record, item, setRefresh) {
  let { id } = record;
  let { url, method } = item.sourceApi;
  //为了让编辑之后的弹框数据更新,所以重新赋值
  record.isShow = Number(isShow);
  isShow = Number(isShow);
  let res = await request({
    url,
    method,
    data: {
      isShow,
      id,
    },
  });
  if (res.code !== "0") return;
  setRefresh((refresh) => !refresh);
  //message.success(isShow === 1 ? '启用成功' : '禁用成功')
}
function getName(arr, val) {
  for (let i = 0; i < arr.length; i++) {
    let item = arr[i];
    if (item.key == val) {
      return item.label;
    }
  }
}
function getTag(arr, val) {
  for (let i = 0; i < arr.length; i++) {
    let item = arr[i];
    if (item.key == val) {
      return <Tag color={item.color}>{item.label}</Tag>;
    }
  }
}
export default dealWithColumns;

modalBox.js

import React from 'react';
import { RenderModal } from '../component';
import {
    Modal,
    Form
} from 'antd' 
function ModalBox(props){
    let {modalList,visible, title,option,onOk,onCancel,setRefresh,labelCol,wrapperCol} = props;     
    return (
        <Modal
          title={title}
          visible={visible}
          onOk={onOk}
          onCancel={onCancel}
        >
          <Form
            labelAlign="right"
            labelCol={{ span: labelCol||8 }}
            wrapperCol={{ span: wrapperCol||16 }}
          >
            {modalList.map((item, index) => {             
              return (
                <Form.Item
                  required={item.require}
                  label={item.label}
                  key={index}
                  help={item.help}
                >
                  {RenderModal(item, option,setRefresh)}
                </Form.Item>
              );
            })}
          </Form>
        </Modal>
    )
}
export default ModalBox;

searchByCondition.js

import React, { useState,useImperativeHandle} from "react";
import { BASE_URL,getToken,downloadFile } from "utils";
import { Button, Form, Upload, message } from "antd";
import { RenderModal } from "../component";
import "moment/locale/zh-cn";
import { exportAPI } from "../commonApi";
function onChangeImport(info) {
  if (info.file.status !== "uploading") {
    console.log(info.file, info.fileList);
  }
  if (info.file.status === "done") {
    message.success("文件导入成功");
    queryFinish.bind(this);
  } else if (info.file.status === "error") {
    message.error("文件导入失败");
  }
}
function exportFinish(item, record,modalList) {
  exportAPI(item, modalList, record, (res) => {
    //掉钉钉接口
    //变量拼接的命名
    if (Array.isArray(item.title.field)) {
      var timeRange = [];
      item.title.field.map((item) => {
        timeRange.push(record[item]);
      });
      downloadFile(res.result, item.title.text, timeRange);
    } else {
      downloadFile(res.result, item.title.text);
    }
  });
}
function queryFinish(props,item, record) {
  let { getList,setCurrent } = props;
  setCurrent(1);
  getList(record);
}
function resetFinish(props, setOption) {
  let { getList,setCurrent } = props;
  setCurrent(1);
  getList({});
  setOption({});
}
function dealWithList(operateList, props, setOption) {
  let { openAddModal } = props;
  let actions = [];
  operateList.map((item) => {
    if (item.status === "add") {
      item.type = "primary";
      item.do = openAddModal;
      actions.push(item);
    }
    if (item.status === "query") {
      item.type = "dashed";
      //点查询时当前页数为1
      item.do = queryFinish.bind(this, props);
      actions.push(item);
      //如果有查询,自动添加重置
      actions.push({
        type: "danger",
        name: "重置",
        do: resetFinish.bind(this, props, setOption),
      });
    }
    if (item.status === "import") {
      actions.push(item);
    }
    if (item.status === "export") {
      item.type = "dashed";
      item.do = exportFinish.bind(props.modalList);
      actions.push(item);
    }
  });
  return actions;
}
function SearchByCondition(props,ref) {
  let modalList = props.modalList.filter((item) => item.isSearch);
  let headers = { DING_TOKEN: getToken() };
  const [option, setOption] = useState({});
  const [refresh,setRefresh] = useState(false);
  let actions = dealWithList(props.actions, props, setOption); //处理不同的button按钮样式
  useImperativeHandle(ref, () => ({
    option
  }));
  return (
    <div>
      {modalList.length > 0 ? (
        <div className="cm-mb-01">
          <Form layout="inline">
            {modalList.map((item, index) => {
              return (
                <Form.Item label={item.label} key={index}>
                  {RenderModal(item, option,setRefresh)}
                </Form.Item>
              );
            })}
          </Form>
        </div>
      ) : null}
      {actions.map((item, index) => {
        if (item.status === "import") {
          return (
            <Upload
              key={index}
              name="file"
              showUploadList={false}
              action={BASE_URL + item.url}
              accept="excel"
              headers={headers}
              onChange={(info) => onChangeImport(info)}
            >
              <Button style={{ background: "#52C41A", color: "#fff" }}>
                导入
              </Button>
            </Upload>
          );
        } else {
          return (
            <Button
              type={item.type}
              key={index}
              className="cm-mr-01"
              onClick={() => item.do(item, option)}
            >
              {item.name}
            </Button>
          );
        }
      })}
    </div>
  );
}
export default React.forwardRef(SearchByCondition);

以上可能有部分关联的JS文件没贴出来,因文件太多,就不一一贴出,需要的请留言。

五、总结

注意1:
配置 modalList 时一定要将唯一key放在第一行。

注意2:
使用该模板时,后台查询接口默认返回格式为:{code:0,msg:"",data:[]}。如若返回如下格式:{code:0,msg:"",data:{count:1,list:[{id:1}]}}。则在配置查询功能时需要额外配置 extraField 字段为 “list”。

注意3:
判断接口是否成功是通过 code 为 0,默认取的数据集字段为 data。