- 页面形态状态(列表/独立表单页/独立详情页)在实例创建时锁定,避免随路由变化切换导致弹窗残留 - 模板中形态相关所有判定改为使用锁定状态变量,保证渲染一致性 - 修改 detailContainer、crudContainer、pageFormOption 等方法使用锁定状态,避免缓存实例 option 变形 - 新增 closeInnerDialogs 方法,在组件 deactivated 和 beforeUnmount 生命周期关闭所有 append-to-body 弹窗 - 解决标签页 keep-alive 机制下弹窗 Teleport 持续遗留导致界面异常的问题 - 对运单管理页面多处弹窗和视图控制做统一修复,提升页面稳定性和体验
7.6 KiB
2026-09-18
修复:改成独立整页后点左侧菜单仍弹出旧弹窗(loading-manage / project-apply)
现象:配载单管理、项目管理已改为「新增/编辑/详情走独立整页」,但从独立页返回或直接点左侧菜单时,页面上仍会盖出一个老的 el-dialog(新增项目管理 / 配载单)。
根因(已读 Vue runtime-core 源码确认):
src/router/tab.js按tabKey(默认 fullPath)为每个标签建一个具名 wrapper 组件放进wrapperMap,src/page/index/layout.vue用<keep-alive :include="$store.getters.tagsKeep">缓存 —— 每个标签页一个实例,全部缓存不销毁。- 组件被 deactivate 后仍会随
$route(全局响应式)重新渲染;此时isStandalonePage变 false,<component :is="isStandalonePage ? 'div' : 'el-dialog'">由 div 翻回el-dialog,而v-model(projectBox/dialogVisible)仍是 true,append-to-body使弹窗 Teleport 到 body。 KeepAlive.deactivate调move(vnode, storageContainer, ..., moveType=OUT),但TeleportImpl.move(moveTeleport)忽略传入的 moveType、一律按 REORDER 处理 → teleport 出去的 DOM 不会被搬回 storage container,永久残留在document.body上并显示出来。
修法(不回退 tab.js 的 return wrapper 提交 c1c4fa1,改修根因):
- 独立页容器固定为
<div v-if="isXxxPage">,不再按路由回退成el-dialog。 - 页内其它
append-to-body二级弹窗加deactivated()/beforeUnmount()→closeInnerDialogs()把 v-model 置 false。
改动文件:
src/views/business/loading-manage.vue:容器改<div v-if="isStandalonePage">;删dialogVisible、dialogTitle、resetLoadingDialog、clearLoadingDialog、页脚弹窗分支(含「清空」);watch.$route简化;新增closeInnerDialogs()(routeChange / commonAddress / candidateSearch)+deactivated/beforeUnmount。src/views/business/project-apply.vue:容器改<div v-if="isProjectFormPage">;删projectFormContainer/projectFormContainerProps、projectBox、resetProjectDialog、handleCancelProject;closeProjectForm简化为直接 push 列表;新增closeInnerDialogs()(changeRecordDetail / 附件文档预览 / 图片预览 / 选人)+deactivated/beforeUnmount。
校验:dev server(2889) curl 两个 .vue 及其 scoped style 模块,均 HTTP 200(500 才是编译失败)。vite build 因沙箱敏感内容保护会在 facelogin.vue 中断,未用。
遗留同类风险(未改,已告知用户):business/components/waybill-manage-page.vue(detailContainer 在 PageDetail / el-dialog 间切)、settlement/components/{pre,formal}-settlement-editor.vue、transport-reconciliation-editor.vue(pageMode ? 'div':'el-dialog')、business/components/waybill-import-dialog.vue(standalone/createPage)。统一修法:容器类型在 created() 锁定,不随路由/prop 翻转。
追加修复:运单详情弹窗残留(waybill-manage-page.vue)
用户复现路径:打开 /business/waybill-manage/detail?id=xxx → 点「配载管理」菜单 → 页面上又冒出运单详情弹窗。与上面同一根因:detailContainer() 随 isStandaloneWaybillDetailPage 在 PageDetail / el-dialog 间翻转,:append-to-body="!isStandaloneWaybillDetailPage" 随之为 true,v-model="detailBox" 仍 true → Teleport 进 body 搬不回来。
改动(src/views/business/components/waybill-manage-page.vue):
- data 新增三个锁定标志
formPageLocked/detailPageLocked/formModeLocked,在created()一次性从isStandaloneWaybillFormPage/isStandaloneWaybillDetailPage/$route.query.mode取值。 - 模板里所有形态判定(根 class、
v-if="!detailPageLocked"的列表容器、form-page-title、status、独立表单页按钮块、分页v-show、:append-to-body、detail-page class、内容v-if="detailBox || detailPageLocked"、独立页 footer / 弹窗 footer slot)全部改读锁定标志。 detailContainer()/crudContainer()/pageFormOption()(含boxType)改读锁定标志,避免缓存实例的 option 变形。- 新增
closeInnerDialogs()(关 detailBox、里程补录、附件文档/图片预览、变更路线、常用地址、流程图、运输路线/地址/站点/地图、常用货物、货物导入、过程配置、Excel 导入)+deactivated()/beforeUnmount()。
关键验证:onDeactivated 在子组件里也会触发 —— apiLifecycle 的 injectHook 会向上遍历父链,发现 KeepAlive 父级就把钩子注入到根实例(runtime-core 的 injectToKeepAliveRoot),所以写在子组件 waybill-manage-page 上的 deactivated 有效。
校验:dev server curl 该 .vue 与 scoped style 均 200。仍未 commit。
未提交:上述改动 + 上一轮 loading-manage 独立页改造均未 commit。
排查(未改码):配载单详情页反复弹「运单管理不存在」
用户截图:打开 /business/loading-manage/detail?id=2097607162718441474&name=配载单详情,连续弹出 6-7 条红色「运单管理不存在」,标签栏堆了一串「运单管理详情」。
文案来源:前端 src 里没有这句话,是后端 BusinessException 的 msg,被 src/axios.js 的响应拦截器(status !== 200 → ElMessage.error(message))统一弹出。
主因(通道 A):views/business/waybill-manage-detail.vue 只有 23 行,写了 :detail-id="$route.query.id" —— 直接绑全局 $route,没有路径守卫。waybill-manage-page.vue 的 detailId watcher 是 immediate 且对任何非空 id 都 openDetail({id})。于是:
- 之前打开过的每个运单详情标签(
/business/waybill-manage/detail?id=A、?id=B…)各是一个被 keep-alive 缓存的独立实例; - 跳到配载单详情后,这些实例被 deactivate 但仍随
$route重渲染 →$route.query.id变成配载单 id; - prop 变化 →
openDetail({id: 配载单id})→getDetail(配载单id)去查运单 → 后端「运单管理不存在」→ 拦截器弹一次。几个旧标签就几条提示; - 且被污染实例的
isStandaloneWaybillDetailPage仍是 live computed(false) →openDetail走$router.push分支 → 又生成新的「运单管理详情」标签(标签栏那串的来源)。
同理风险:waybill-manage.vue:6 与 transport-plan.vue:6 的 :detail-id="$route.query.detailId"(business-crud-page.vue:7356 会 push /business/loading-manage?detailId=id,正好会污染运单列表实例)。
次因(通道 B):loading-manage.vue 的 restoreWaybillRows 会用 waybillIdsJson 里的每个运单 id 并发 getWaybillDetail(id),若历史运单已被删/失效,也会报同一句文案且并发多条。单条 .catch(() => null) 挡不住拦截器已弹出的提示。
区分方法:Network 里看报错请求的 id —— 等于 2097607162718441474(配载单 id)→ 通道 A;是别的数字(运单 id)→ 通道 B。或先关掉所有「运单管理详情」标签再复现,通道 A 会消失。
待确认的修法(用户要求先报不改):
waybill-manage-page.vue:created()锁routePathLocked = this.$route.path,detailIdwatcher 首行加if (this.$route.path !== this.routePathLocked) return;。openDetail()的 push 分支加同样守卫(防其它调用路径)。- 宿主 view 绑定加路径守卫(
waybill-manage-detail.vue/waybill-manage.vue/transport-plan.vue)双保险。 - 可选:
getWaybillDetail支持静默模式,供restoreWaybillRows用,避免历史脏数据刷屏。