【技术难题】el-table的全局数据排序实现示例,不受分页影响,以及异步请求带来的页面渲染问题
参考链接:https://blog.csdn.net/qq_35770559/article/details/131183121
问题代码
编辑页面detail.vue
<el-form title="列表信息" name="detail"><el-form><el-form-item><el-buttontype="cyan"icon="el-icon-plus"size="mini"@click="openProduct">选择商品</el-button></el-form-item></el-form><el-tableref="inBillDetailTabless":data="addup == '2' ? dataList : addList"max-height="450"stripev-loading="loading"@sort-change="sortChange"><el-table-column align="center" label="商品sku" width="180"prop="skuCode"><!-- <template slot-scope="scope"><el-form :model="scope.row"><el-form-item prop="skuCode"><el-inputv-model="scope.row.skuCode"clearable:disabled="disabled"></el-input></el-form-item></el-form></template> --></el-table-column><el-table-column align="center" label="商品名称" width="180"prop="skuName"><el-table-columnlabel="操作"fixed="right"align="center"class-name="small-padding fixed-width"><template slot-scope="scope"><el-buttonsize="mini"type="text"@click="delDetail(scope.$index, scope.row)">删除</el-button></template></el-table-column></el-table>调用事件:
// 排序改变sortChange(row) {// 显示加载状态,提升用户体验this.loading = true;const { column, prop, order } = row//column表示选中排序列的详细信息,prop表示选中的拍序列,order表示选中拍序列的排序规则// 其他代码逻辑if (!order) return; // 无排序时直接返回if(order){console.log("===allMySort", row)allMySort({items:this.form.items,orderBy:order}).then((res) => {console.log(" res.data",JSON.stringify( res))// this.$nextTick()this.form.items = res.data;//根据addup类型更新列表// 列表查询方法if (this.addup == "1") {this.total=this.form.items.lengththis.addList = this.form.items.slice((this.queryParams.pageNum - 1) * this.queryParams.pageSize,this.queryParams.pageNum * this.queryParams.pageSize);}//if (this.addup == "1") this.form.items=array;if (this.addup == "2"){this.dataList= this.form.items;}// 隐藏加载状态this.loading = false;}) .catch((error) => {console.error("排序请求失败", error);this.loading = false;});}this.$nextTick()// this.$forceUpdate();}},
js请求:
//全局排序export function allSort(params) {return request({url: '/ipwms/gyp/check/allMySort',method: 'post',data: params})
}
后台全局排序方法:controller
/*** 全局排序方法*/@Log(title = "全局排序方法 ", businessType = BusinessType.OTHER)@PostMapping(value = "/allMySort")public AjaxResult allSort(@RequestBody TW06CheckDGypSortEntity tw06CheckDGypList) throws IOException {return AjaxResult.success(tW06CheckGypService.allSort(tw06CheckDGypList));}
后端service:
/*** 全局排序方法*/@Overridepublic List<TW06CheckDGyp> allMySort(TW06CheckDGypSortEntity tw06CheckDGypEntityList) {log.info("============allMySort-start============>{}",JSONUtil.toJsonStr(tw06CheckDGypEntityList));if(ObjectUtil.isNotEmpty(tw06CheckDGypEntityList.getOrderBy())){if("ascending".equals(tw06CheckDGypEntityList.getOrderBy())){log.info("============allMySort-ascending============");Collections.sort(tw06CheckDGypEntityList.getItems(),new CustomComparator());}else if("descending".equals(tw06CheckDGypEntityList.getOrderBy())) {log.info("============allMySort-ascending============");Collections.sort(tw06CheckDGypEntityList.getItems(),new CustomComparator().reversed());}}log.info("============allMySort-end============>{}",JSONUtil.toJsonStr(tw06CheckDGypEntityList));return tw06CheckDGypEntityList.getItems();}/*public static void main(String[] args) {List<String> strings = new ArrayList<>();strings.add("03");strings.add("CQ01-01-01-1-101");strings.add("CQ01-01-01-1-90");strings.add("CQ01-01-01-1-100");strings.add("CQ01-01-01-100-100");strings.add("CQ01-01-01-2-100");strings.add("CQ01-01-01-2-100");strings.add("CQ01-01-01-2-001");strings.add("CQ01-01-01-2-002");strings.add("CQ01-01-01-2-020");strings.add("CQ01-01-01-1-2");strings.add("CQ01-02-01-1-2");strings.add("AQ01-01-01-1-3");strings.add("DQ01-01-01-1-4");strings.add("DZ01-01-01-1-1");strings.add("BC01-01-01-1-1");strings.add("BC08-01-01-1-1");strings.add("BZ01-01-01-1-1");strings.add("CZ01-01-01-1-1");strings.add("YK-01-01-1-1");strings.add("CS1-01-03-01");strings.add("测试_2_13");strings.add("退货库位");strings.add("测试库位10-3");strings.add("测试库位2-1");strings.add("测试库位2-2");Collections.sort(strings);System.out.println(strings);System.out.println("================");Collections.sort(strings,new CustomComparator());System.out.println(strings);}
*//*** 自定义比较器*/static class CustomComparator implements Comparator<TW06CheckDGyp> {private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");@Overridepublic int compare(TW06CheckDGyp gyp1, TW06CheckDGyp gyp2) {String s1 = gyp1.getStockLoc();String s2 = gyp2.getStockLoc();int len1 = s1.length();int len2 = s2.length();int i = 0, j = 0;while (i < len1 && j < len2) {char c1 = s1.charAt(i);char c2 = s2.charAt(j);if (Character.isDigit(c1) && Character.isDigit(c2)) {// 提取连续的数字部分并转为整数比较String numStr1 = extractNumber(s1, i);String numStr2 = extractNumber(s2, j);int num1 = Integer.parseInt(numStr1);int num2 = Integer.parseInt(numStr2);if (num1 != num2) {return num1 - num2;}// 跳过已比较的数字部分i += numStr1.length();j += numStr2.length();} else {// 非数字部分按字符比较if (c1 != c2) {return c1 - c2;}i++;j++;}}// 处理剩余字符return len1 - len2;}private String extractNumber(String s, int start) {Matcher matcher = NUMBER_PATTERN.matcher(s);if (matcher.find(start) && matcher.start() == start) {return matcher.group();}return "";}}
后台实现全局排序的主要逻辑是:
/*public static void main(String[] args) {List<String> strings = new ArrayList<>();strings.add("03");strings.add("CQ01-01-01-1-101");strings.add("CQ01-01-01-1-90");strings.add("CQ01-01-01-1-100");strings.add("CQ01-01-01-100-100");strings.add("CQ01-01-01-2-100");strings.add("CQ01-01-01-2-100");strings.add("CQ01-01-01-2-001");strings.add("CQ01-01-01-2-002");strings.add("CQ01-01-01-2-020");strings.add("CQ01-01-01-1-2");strings.add("CQ01-02-01-1-2");strings.add("AQ01-01-01-1-3");strings.add("DQ01-01-01-1-4");strings.add("DZ01-01-01-1-1");strings.add("BC01-01-01-1-1");strings.add("BC08-01-01-1-1");strings.add("BZ01-01-01-1-1");strings.add("CZ01-01-01-1-1");strings.add("YK-01-01-1-1");strings.add("CS1-01-03-01");strings.add("测试_2_13");strings.add("退货库位");strings.add("测试库位10-3");strings.add("测试库位2-1");strings.add("测试库位2-2");Collections.sort(strings);System.out.println(strings);System.out.println("================");Collections.sort(strings,new CustomComparator());System.out.println(strings);}
*//*** 自定义比较器*/static class CustomComparator implements Comparator<TW06CheckDGyp> {private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");@Overridepublic int compare(TW06CheckDGyp gyp1, TW06CheckDGyp gyp2) {String s1 = gyp1.getStockLoc();String s2 = gyp2.getStockLoc();int len1 = s1.length();int len2 = s2.length();int i = 0, j = 0;while (i < len1 && j < len2) {char c1 = s1.charAt(i);char c2 = s2.charAt(j);if (Character.isDigit(c1) && Character.isDigit(c2)) {// 提取连续的数字部分并转为整数比较String numStr1 = extractNumber(s1, i);String numStr2 = extractNumber(s2, j);int num1 = Integer.parseInt(numStr1);int num2 = Integer.parseInt(numStr2);if (num1 != num2) {return num1 - num2;}// 跳过已比较的数字部分i += numStr1.length();j += numStr2.length();} else {// 非数字部分按字符比较if (c1 != c2) {return c1 - c2;}i++;j++;}}// 处理剩余字符return len1 - len2;}private String extractNumber(String s, int start) {Matcher matcher = NUMBER_PATTERN.matcher(s);if (matcher.find(start) && matcher.start() == start) {return matcher.group();}return "";}}
通过以上代码就能在点击某列排序时,实现整个数据的排序,之后再在前端收到排序后的响应结果,然后再进行手动分割分页。这样就实现了分页情况下对全局数据的排序。
需要特别注意的一个点:我最开始前端请求后端的代码是这样写的:也就是我手动更新响应结果是在请求的then方法之外的,因为是异步请求,所以此时就出现了,响应结果还没有返回,页面就已经渲染完了,因为在前端开发中,当我们发起一个后端请求时,请求是异步进行的,不会阻塞 JavaScript 的执行。因此,代码会继续执行,而不会等待后端响应。所以导致了每次点击后出现的是上一次排序的结果,但是其实请求每次返回的排序数据是没问题的
// 排序改变sortChange(row) {// 显示加载状态,提升用户体验this.loading = true;const { column, prop, order } = row//column表示选中排序列的详细信息,prop表示选中的拍序列,order表示选中拍序列的排序规则// 其他代码逻辑if (!order) return; // 无排序时直接返回if(order){console.log("===allSort", row)allSort({items:this.form.items,orderBy:order}).then((res) => {console.log(" res.data",JSON.stringify( res))// this.$nextTick()this.form.items = res.data;}) .catch((error) => {console.error("排序请求失败", error);this.loading = false;});}//根据addup类型更新列表// 列表查询方法if (this.addup == "1") {this.total=this.form.items.lengththis.addList = this.form.items.slice((this.queryParams.pageNum - 1) * this.queryParams.pageSize,this.queryParams.pageNum * this.queryParams.pageSize);}//if (this.addup == "1") this.form.items=array;if (this.addup == "2"){this.dataList= this.form.items;}// 隐藏加载状态this.loading = false;this.$nextTick()// this.$forceUpdate();}}
问题分析
分析一下问题:
用户在使用 Element UI 的 el-table 进行自定义排序时,遇到了一个时序问题。具体来说:
用户点击排序三角时,页面没有立即更新
当取消排序时,页面显示了上一次排序的结果
在 sortChange 方法中,调用后端接口后,页面先显示了上一次的排序结果,然后才输出 console.log 信息
用户猜测这是因为在数据请求返回之前,前端已经进行了页面渲染。
这是一个典型的异步操作时序问题。在前端开发中,当我们发起一个后端请求时,请求是异步进行的,不会阻塞 JavaScript 的执行。因此,代码会继续执行,而不会等待后端响应。
问题在于:
allSort 是一个异步 API 调用,使用了 .then() 处理响应
在 API 调用之后,不管响应是否返回,代码继续执行,立即更新了 addList 和 dataList
由于此时 form.items 还没有被 API 响应更新,所以使用的是旧数据
当 API 响应返回后,form.items 被更新,但此时 addList 和 dataList 并没有重新计算
这就是为什么用户看到的是上一次的排序结果,因为在后端响应返回之前,前端已经使用旧数据进行了渲染。
我需要为用户提供一个解决方案,解决这个异步时序问题,确保在 API 响应返回后再更新视图
你遇到的排序更新延迟问题,确实是由于异步请求与DOM更新的时序不一致导致的。在你的sortChange
方法中:
allSort
是一个异步API请求(返回Promise)- 无论API是否返回,代码会立即执行后续的
addList
和dataList
更新 - 此时
form.items
尚未被API响应更新,导致使用旧数据渲染页面 - 当API响应返回后,
form.items
被更新,但addList
和dataList
未重新计算
解决方案
需要将视图更新逻辑放在API响应的回调函数中,确保使用最新数据渲染页面:
sortChange(row) {const { column, prop, order } = row;if (!order) return; // 无排序时直接返回console.log("===allSort", row);// 显示加载状态,提升用户体验this.loading = true;allSort({ items: this.form.items, orderBy: order }).then((res) => {console.log("res.data", JSON.stringify(res));// 更新数据this.form.items = res.data;// 根据addup类型更新列表if (this.addup === "1") {this.total = this.form.items.length;this.addList = this.form.items.slice((this.queryParams.pageNum - 1) * this.queryParams.pageSize,this.queryParams.pageNum * this.queryParams.pageSize);} else if (this.addup === "2") {this.dataList = this.form.items;}// 隐藏加载状态this.loading = false;}).catch((error) => {console.error("排序请求失败", error);this.loading = false;});
}
关键改进说明
-
异步操作统一处理:
- 将所有视图更新逻辑放在
then
回调中,确保使用API返回的最新数据 - 添加
loading
状态,避免用户在等待时误操作
- 将所有视图更新逻辑放在
-
移除无效的
$nextTick
:- 原代码中的
$nextTick()
没有实际作用,因为它在API请求之外执行 - 现在更新逻辑都在API响应回调中,自然触发Vue的响应式更新
- 原代码中的
-
错误处理:
- 添加
catch
块处理请求失败的情况 - 无论请求成功与否,都要重置
loading
状态
- 添加
优化建议
-
添加加载状态:
在表格上方添加加载提示,提升用户体验:<el-table :data="addup == '2' ? dataList : addList"max-height="450"stripev-loading="loading" // 添加加载状态@sort-change="sortChange" >
-
防抖处理:
避免用户快速点击排序时发起多次请求:import { debounce } from 'lodash'; // 需要安装lodashexport default {data() {return {// 其他数据...debouncedSort: null}},created() {// 初始化防抖函数,300ms内多次点击只执行一次this.debouncedSort = debounce(this.sortChange, 300);},methods: {sortChange(row) {// 原有逻辑...}} }
-
优化表格更新策略:
当数据量较大时,使用key
属性优化渲染:<el-table-column v-for="(column, index) in tableColumns" :key="column.prop"// 其他属性... >
通过以上修改,排序更新将与API响应同步,确保用户看到的是最新的排序结果,同时提升页面交互体验。