【Mock 平台】为系列测试开发教程,从 0 到 1 编码带你一步步使用 Spring Boot 和 Antd React 框架完成搭建一个测试工具平台,希望作为一个实战项目对各位的测试开发学习之路有帮助,大奇一个专注测试技术干货原创与分享的家伙。
Mock 平台系统项目基本配置,我们已经完成了展示,增加、修改,这个模块的进度其实已经差不多 80%了,在本分享中将收下尾,为数据表格配置分页,实现简单搜索功能,以及删除功能,还会掌握如何自定义格式化列数据。
1.优化 Table 展示
表格形式的数据,一般未来累计超过 20 条的非常建议为其增加分页功能,在 antd 中 Table 组件有自带这个配置,但在之前的逻辑实现中特意关掉了,内部其实嵌套的也是 Pagination 分页组件,下边将详细演示内嵌和独立使用两种形式。
1.1 Pagination 组件
分页组件在数据量大且加载/渲染所有数据将花费很多时间时,可以利用分页器组件进行页码浏览,要使用它需要导入组件 import{ Pagination }from'antd';其中基础配置属性** **total总数量,会根每页条数 pageSize(默认是 10 条)计算多少页,另外还有一个重要方法 onChange指页码或 pageSize 改变时回调,function(page, pageSize)参数是改变后的页码及每页条数。
参考官方例子给项目列表 Table 下方增加此组件,为了先做个演示测试,数据固定给 50 条,并主动开启
import{ Pagination }from'antd';
/* 分页相关 */const pageChange = (page, pageSize) => { console.log(`分页变化值 Page:${page}, PageSize:${pageSize}`);}
...省略...</Table><br/> <Pagination total={100} onChange={pageChange} showSizeChanger={true} pageSizeOptions={[5,10,20,30,50]}/>
复制代码
当点击切换页,下拉选择改变每页显示的数量时候,可以直观的看到的两个参数数值的变化,如下图:
因此在实现项目表格分页上,只需要在 onChange 里请求之前文章后服务分页查询接口,并将统计的总数动态赋值,就能实现优化改造。这里参考代码有些注意的地方:
export async function searchProducts(params) { return request('/api/mock/project/search', { method: 'GET', params });}
复制代码
// const {data:useProjectList, error, loading, run: reloadProjectList} = useRequest(getProductList); const [data, setData] = useState(); const [loading, setLoading] = useState(false); const [current, setCurrent] = useState(1); const [pageSize, setPageSize] = useState(10); const [total, setTotal] = useState(); const fetchData = (name, current, pageSize) => { setLoading(true); searchProducts({name:name, current:current, pageSize: pageSize}) .then(reps => { setData(reps.data); setTotal(reps.total); setLoading(false); }); };
/* 分页相关 */ const pageChange = (page, pageSize) => { setCurrent(page); setPageSize(pageSize); fetchData('', page, pageSize); }
// 页面首次请求项目列表数据初始化 useEffect(() => { fetchData('', current, pageSize); }, []);
复制代码
<Table loading={loading} rowKey="id" pagination={false} columns={projectColumns} dataSource={data}/><br/><Pagination total={total} current={current} pageSize={pageSize} onChange={pageChange} showSizeChanger={true} pageSizeOptions={[5,10,20,30,50]}/>
复制代码
代码开发调试效果如演示 GIF
这里其实有个问题,就是当切换每页数量时候,当前选中页面还是之前的,如果条数从小条切到大,就有当前页数超出新的页数量总数情况,解决办法如果 pageSize 有变更,初始化默认页为第一页。
const pageChange = (page, size) => { // 如果改变了size 将其page设置为第一页 let tmpPage = 1; if(size !== pageSize){ tmpPage = 1 } else { tmpPage = page; } setCurrent(tmpPage); setPageSize(size); fetchData('', tmpPage, size); }
复制代码
1.2 Table 自带分页
对于 Table 组件是有内置的分页功能,其实也就是 Pagination 组件,并对其做了封装得以使用更简单
https://ant.design/components/table-cn/#components-table-demo-ajax
有了上边 1.1 基础,改造起来就简单多了,只需将 Pagination 部分内容以 JSON 赋值方式赋值给 Table 中的 pagination 即可。
<Table loading={loading} rowKey="id" pagination={ { total:total, current:current, pageSize:pageSize, onChange: pageChange, showSizeChanger:true, pageSizeOptions:[5,10,20,30,50] } } columns={projectColumns} dataSource={data}/>
复制代码
可以看到包括样式在内,自动展示了分页,并且尝试测试切换也均正常,不过我的例子中单独的变量定义的有点多,你们可以尝试参考官方 Table 远程加载数据例子再优化一版。
1.3 列表数据格式化
在表格数据展示中其中我们可以看到类型一栏显示还是数据库里的字段,所以这里说明下如何实现格式展示,方法和操作相似,使用 render 自定义请求一个方法,此方法根据类型判断返回相应文字和组件。
const formatType = (txtType) =>{ if (txtType === 'public') { return <Tag>公共项目</Tag> } else if (txtType === 'private') { return <Tag>私有项目</Tag> } else { return <Tag>未知类型</Tag> } }
// 表单列 const projectColumns = [ ... {dataIndex:"type",title:"类型", render: (text, _) => (formatType(text)), }, ... ]
复制代码
代码中实现的是将英文关键词转换成对应的中文进行显示,并用 Tag 标签包裹。
之后其他想做数据格式化展示,或多列合并一条,再或根据不同值展示不同样式等等,都可以使用此方法,当然后边有一种 ProTable 高级表格的时候,它提供多种类型数据展示提供了内置格式化,后续用到再进行详细说明。
2.搜索功能
在优化 Table 显示需求中替换带分页查询的接口时,我们预留的一个 searchName 参数,对于多数据的展示如果能有个主要信息的搜索功能才比较完美,这里利用 Grid栅格和 Form行内表单 组件实现。
https://ant.design/components/grid-cn/https://ant.design/components/form-cn/#components-form-demo-layout
Grid 用于布局,基于行(row)和列(col)来定义信息区块的外部框架,笔者因为对 CSS 不熟,不能熟练的用于 DIV 做一些样式布局,所以就常用栅格来控制行和列来达到想要的区域块布局效果。
Form 行内是表单布局一种,一行的显示样式,常用于搜索、快捷编辑等场景。
参考两个组件官方 API 说明,最终我们要实现如下的页面效果:
这里由于之前表单的操作已经实战过 Form 的用法,所以就直接给出了参考代码,其中 style 是简单样式,实现右边 6 个栅格区域按钮靠右对齐。
<Row> <Col span={18}> <Form layout="inline" onFinish={...todo实现提交代码...} > <Form.Item name="searchName" label="项目名称"> <Input></Input> </Form.Item> <Form.Item> <Button htmlType="submit">搜索</Button> </Form.Item> </Form> </Col> <Col span={6}> <Button onClick={addAction} type="primary" style={{ marginBottom: 16, marginRight: 20, float: "right" }} > 项目添加 </Button> </Col></Row>
复制代码
最后补充下表单提交的实现代码
// 搜索const onSearch = (values) => { fetchData(values.searchName, current, pageSize)};
// return 内表单修改部分<Form layout="inline" onFinish={onSearch}>
复制代码
输入一个关键点击搜索测试一下 OK,但参考代码到这里会有交互缺陷,如果给定的关键词搜索的结果也是多页,那么我在切换页面或者页数量的时候,由于之前代码 name 都写死的空值,所以又会变成全量查询,这个问题的优化留个思考题吧,看看是你会怎么做?
ps:项目源代码中有我的优化答案可以进行参考
3.项目删除功能
实现完了编辑功能,操作列里还有一个删除操作,一般为了防止删除这种危险动作是误操作,将会用到上一篇中的确认对话框知识点,不过这里将用到官方例子里的 showPromiseConfirm即确定按钮触发会有等待,明确得到返回成功后关闭对话框。
3.1 删除接口
此接口没在之前的 Mock平台-05开发:项目管理(一)后端接口 准备好,需要现实现,MVC 全部的代码如下,不再赘述 Springboot API 实现知识点,不会或者忘记的翻下系列第五篇回看下。这里需要特别强调下,这里删除是个软删除,所以要对数据库表增加一个字段 is_del (0-默认,1-删除)来做标记,实际删除就是更新此字段为删除状态。
MockProjectMapper.java
/*** 删除项目,软删除标记is_del=1* @param id* @return 影响数量 更新成功默认1*/@Update({"UPDATE mock_project SET mp_state=1 WHERE mp_id=#{id}"})Boolean removeProject(Integer id);
复制代码
MockProjectService.java
RespResult removeMockProject(Integer id);
复制代码
MockProjectServiceImpl.java
@Overridepublic RespResult removeMockProject(Integer id) {
Boolean markResult = mockProjectMapper.removeProject(id); if (markResult){ return RespResult.success(); } return RespResult.failure(RespCode.DATA_REMOVE_ERROR);}
复制代码
MockProjectController.java
@PostMapping(value = "/project/remove")public RespResult removeProject(@Param("id") Integer id){ return mockProjectService.removeMockProject(id);}
复制代码
3.2 删除交互
对于前度删除功能交互的实现,先按照流程先定义服务请求接口
export async function removeProduct(id) { return request('/api/mock/project/remove?id='+id, { method: 'POST' });}
复制代码
动态生成确认对话框需要导入 Modal 组件里的子项 confirm 主要的三个属性和两个方法
const { confirm } = Modal;import { removeProduct } from "@/pages/Project/service";....省略...
// 删除操作const deleteConfirmWithPromise = (record) => { confirm({ title: '删除确认?', icon: <ExclamationCircleOutlined />, content: `确定要删除【${record?.name}】吗?`, async onOk() { const result = await removeProduct(record.id); if(result.success){ message.success('删除项目成功!'); // 成功后刷新 fetchData(sName, current, pageSize); } else{ message.error('删除项目失败!'); } }, onCancel() {}, }); };
复制代码
表格操作栏删除按钮添加 deleteConfirmWithPromise 并把所在行数据传过去。
{dataIndex:"option",title:"操作", render: (text, record) => ( <Space> <a onClick={()=>editAction(record)}>编辑</a>+ <a onClick={()=>deleteConfirmWithPromise(record)}>删除</a> </Space>),},
复制代码
按例重启前后端服务,进行删除取消/确认/删除成功页面刷新的用例测试。
花了大篇幅在比较简单需求的项目管理上,是为务牢基础为后边核心逻辑更轻松,另外学习是一个过程,尤其是基础知识薄弱的,一定不要是看会,而是动手实战,进而有自己的想法和移花接木的本领,最终从务实角度来讲能在实际工作中业务上应用,哪怕是务虚为了 KPI、“钱途“也要做点什么。
最后预告系列分享开发实战部分终于要进入核心部分的实现,比如分类 Tree,接口管理,Mock 拦截等实战内容,欢迎长期关注哦!
评论