上周生产车间的同事在生产管理系统里用坯料管理模块的导入功能,连着撞见两个怪事:一是导入成功后表格不刷新,得手动按 F5 才能看到新导入的坯料;二是导入失败时只弹个"导入失败"的静态提示,既不说失败原因,也没法下载失败明细去排查。
接到反馈我们很快定位到根因,最后就改了前端一个文件把两件事都解决了,后端接口一个字没动,上线也是零回滚。这个修复的决策过程,比修 bug 本身更值得记一笔。
复现之后,根因一眼就清楚了
先复现:选一个格式错误的坯料 Excel 点导入,果然只弹静态失败提示,没有下载入口;选一个正确的文件导入,接口返回 200,但表格没刷新。
然后翻导入组件的代码 src/views/billet/components/billet-import.vue,handleConfirm 的逻辑很简单:把文件拼进 formData 调后端接口,调完之后就什么都不做了------既没触发表格刷新的 emits 事件,也没处理接口返回的 errorDownloadPath 和 tip 字段,更没做自定义错误提示。再翻后端接口文档才发现,这个导入接口早就支持返回失败文件下载路径和失败原因提示,只是前端一直没接这部分逻辑,等于后端的能力完全被闲置了。
为什么只改前端
定位到根因,团队先讨论了两种方案:改后端接口把失败提示和下载逻辑放后端处理,或者只改前端把接口返回的字段接起来。最后选了只改前端,理由很实在:
后端接口已经把需要的字段全都返回了,不存在后端逻辑缺失,改后端纯属重复造轮子;只改前端的话改动面极小,就一个组件的一个函数,review 成本低,上线风险几乎为零;要是改后端,还得协调后端排期、联调,至少多花 2-3 天,而前端当天就能提测。
这也印证了之前踩过的一个坑:遇到功能类问题先别急着动后端,先看现有接口是不是已经支持对应能力,很多时候问题只是前端没把逻辑接全。
最小改动具体怎么落
最终只给 handleConfirm 加了 4 行核心逻辑,原有的提交、错误处理结构一字没动:
diff
async function handleConfirm(): Promise<void> {
formData.append('file', fileList.value[0].raw as File)
submitLoading()
try {
+ // 等待接口返回,获取后续需要的错误信息
+ const res = await billetImport(formData)
+ // 导入完成后触发父组件刷新表格数据
+ emits('getTableList')
+ // 如果接口返回了失败文件下载路径,弹出带下载按钮的提示
+ if (res.data?.errorDownloadPath) {
+ ElMessageBox({
+ title: '导入失败',
+ message: h('div', null, [
+ h('div', null, res.data?.tip),
+ // 自定义下载按钮,点击后触发下载逻辑
+ h('button', { style: "color:#5688E8;cursor:pointer", id: "messageBtn" }, '点击下载导入失败信息')
+ ])
+ })
+ }
} catch (error) {
// 原有错误处理逻辑保持不变
ElMessage.error('导入失败,请稍后重试')
} finally {
submitLoading()
}
}
逻辑很直白:调完接口先拿返回结果,触发表格刷新,再判断有没有失败文件下载路径,有就弹个带自定义下载按钮的提示框。那个下载按钮的点击事件可以后续单独迭代绑定,这次先保证核心逻辑跑通,正好符合"最小改动解决核心问题"。
三个场景测完就敢提测
改完只测了 3 个核心场景就提测:正常导入成功的场景确认表格自动刷新、不用再手动 F5;导入失败的场景确认弹出带下载按钮的提示、失败原因能正常展示;边界场景比如没选文件就点确认,原有错误提示没被破坏。测试同学只跑了导入相关用例,没发现回归,第二天上线,生产侧反馈问题完全解决。
回头看,这件 bug 根子不在技术难度,而在"后端能力前端没接"。以后遇到功能类反馈,我会先翻一遍接口文档确认能力边界,再决定改哪一层------能省掉大把没必要的联调时间。