文件
hl-api-changelog/changelogs-v2/2026-09/28_frontend_团期详情返回列表弹groupBatchId格式错误确认预检以空团期ID重发-前端缺陷-管理后台.md
T

10 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 frontend 团期详情「配置」节点点浏览器返回,列表页弹「参数 groupBatchId 格式错误」:确认预检监听器缺空值守卫,离开页面瞬间以空团期 ID 重发 confirm-check admin jw(GIT) 前端缺陷 not_required not_required implemented mmg 41e42d0c6c8f323e65add238596f59fd7b433804 v2.1 2026-09-29 2026-09-28 jw 在 TEST(web.test.1814.love)从团期列表「进入团期」打开 jw测试产品 第1期(状态:配置),点浏览器返回,回到列表页即弹 400「参数 groupBatchId 格式错误,请检查后重试」。根因在 hl-ui:#8410(c37fd8ad)在团期详情页新增的 watch([canConfirmBatch, groupBatchId]) 没有照同页其它监听器写 isDetailActive 与空 id 两道守卫;离开详情页时路由先变、onDeactivated 后执行,中间这一拍 groupBatchId 塌缩为空串而页面仍算激活,该监听器随即调 loadConfirmCheck(),拼出 GET /v3/admin/order/group-batch//confirm-check,连续斜杠被合并后落进团期详情接口的 groupBatchId 位,confirm-check 转 Long 失败返 400。后端接口与数据均正常,契约不变。修法:该监听器补两道守卫(守卫放在清空 confirmCheck 之前),loadConfirmCheck 入口补空 id 兜底;与 28_8478 前端交接清单(保留 loadConfirmCheck)不冲突,可同批改。前端已交付(预检 watch 补 isDetailActive+空 id 双守卫+loadConfirmCheck 入口兜底),详见 hl-admin v2.1 提交 41e42d0c。 2026-09-28 dev-v3

团期详情点返回弹「参数 groupBatchId 格式错误」:确认预检用空团期 ID 重发了一次

服务: hl-order-service-v3(后端零改动,接口与数据均正常) 页面: 管理后台 → 团期订单 → 进入团期(团期详情)→ 离开详情页(浏览器返回 / 点顶部页签 / 点侧边栏菜单) 接口: GET /v3/admin/order/group-batch/{groupBatchId}/confirm-check(#8410 确认预检) 日期: 2026-09-28 影响范围: 仅前端。状态为「配置」的团期,每次从详情页离开都会多弹一次 400 提示;详情页签未关(页面仍在缓存)时再回到同一团期,「确认」按钮的预检置灰会失效


一、现象

  1. 团期列表点某个「配置」状态团期的「进入团期」。
  2. 点浏览器返回,回到 /order-v2/batch。
  3. 页面顶部弹出:参数 groupBatchId 格式错误,请检查后重试。
  • 只有**状态为「配置」**的团期会出现;招募、确认、出行等状态的团期来回进出不报。
  • 不限于浏览器返回,从这类团期详情跳到任何非团期详情的页面都会弹(顶部页签、侧边栏菜单、点子订单进入订单详情同理)。直接从一个团期跳到另一个团期不报。
  • 除了这一次提示,页面数据和按钮当下都正常,没有产生任何写入。

复现团期:jw测试产品 第1期(jw测试1期),/order-v2/batch/detail/2101506167098511362,状态 RESOURCE_PREPARING(配置)。


二、根因(对 hl-ui origin/v2.1 = 501c4258 源码实读)

① 离开详情页的那一拍,groupBatchId 会变成空串

src/views/order-v2/batch/detail/index.vue:

// :513
const routeBatchId = computed(() => decodeURIComponent(String(route.params.code || '')))
// :519-529 keep-alive 失活冻结(896cbbec,09-22)
const isDetailActive = ref(true)
onActivated(() => { isDetailActive.value = true })
onDeactivated(() => { isDetailActive.value = false })
const groupBatchId = computed(() =>
  isDetailActive.value ? routeBatchId.value : frozenBatchId.value
)

route 是全局路由。点返回后路由立刻变成 /order-v2/batch,route.params.code 为 undefined,routeBatchId 变成空串 ''。 09-22 的失活冻结要等 onDeactivated 执行后才生效,而 onDeactivated 在本轮渲染之后才跑;监听器(默认 flush: 'pre')在渲染之前就执行了。 所以中间有一拍:页面仍算激活(isDetailActive === true),groupBatchId 却已经是 ''。

② 同页其它监听器都挡住了这一拍,#8410 新加的这个没挡

同页主拉取监听器(:584-596)和 Tab 监听器(:597)都有两道守卫:

if (!isDetailActive.value) return
if (!id) return

#8410(c37fd8ad,09-27)新增的确认预检监听器(:922-929)一道都没有:

watch(
  [canConfirmBatch, groupBatchId],
  ([can]) => {
    confirmCheck.value = null
    if (can) loadConfirmCheck()   // 这一拍 groupBatchId === ''
  },
  { immediate: true }
)

canConfirmBatch(:896)= detail.batchStatus === 'RESOURCE_PREPARING'。离开时 store 里的详情还是这个团,所以「配置」状态的团 can 为 true,于是 loadConfirmCheck()(:905)拿空串发请求。 其它状态的团 can 为 false,不发请求,这就是只有「配置」团才报的原因。

③ 空串拼进路径后,落到了另一个接口上

src/api/orderV2GroupBatch.js:177:

http.get(`${BASE}/${String(groupBatchId)}/confirm-check`, …)
// groupBatchId = '' → GET /v3/admin/order/group-batch//confirm-check

// 在到达接口匹配前被合并成 /,实际命中的是团期详情接口 GET /v3/admin/order/group-batch/{groupBatchId},confirm-check 这个字面量被当成团期 ID 去转数字,于是返回 400「参数 groupBatchId 格式错误」。

④ 次生问题:回到同一团期后确认按钮不再置灰(按源码推演,未在浏览器实测)

离开那一拍,监听器先执行了 confirmCheck.value = null,空 ID 请求失败后 catch 不回填,预检结果就一直是 null。 详情页签没关时,页面还在缓存里;再次进入同一个团期,groupBatchId 前后相同,监听器不会再触发,预检不会重拉:

  • confirmBtnDisabled(:931)在 confirmCheck == null 时为 false,本该置灰的「确认」按钮变成可点;
  • 缺项横幅(showConfirmCheckBanner,:936)也不再显示。

点「确认」前 onConfirmBatch(:939)还会再预检一次,所以不会误提交,只是提前置灰这层提示失效了。刷新页面可恢复。

⑤ 这是 09-22 同类问题的第四个监听器

09-22 的 22_frontend_团期详情页切走后重放请求groupBatchId塌缩-前端缺陷-管理后台.md 已按这个机制修过三个子组件(load() 首行补空值守卫)。 GroupVehicleRequirementSection.vue:455-458 的注释写得很清楚:「空串拼进 URL 会让路径段消失/后缀滑位(400 参数类型错误或 404),空 id 直接不发请求」。 #8410 是在那之后新加的监听器,没有带上这道守卫。


三、后端与数据侧的核查结果(均正常,供排除用)

2026-09-28 经 web.test.1814.love(与浏览器同一入口)用 admin token 实测:

请求 结果
GET /v3/admin/order/group-batch/2101506167098511362(团期详情) 200,batchStatus=RESOURCE_PREPARING、batchLabel=1、batchName=jw测试1期
GET …/2101506167098511362/confirm-check(真实 ID) 200,ready=false、statusConfirmable=true、checkedHouseholdCount=12
GET …/group-batch//confirm-check(空 ID,即前端实际拼出的地址) code 400「参数 groupBatchId 格式错误,请检查后重试」
GET …/group-batch/confirm-check(单斜杠对照) 同上,与双斜杠返回逐字一致
GET …/group-batch/undefined/confirm-check(对照) 同上

结论:带真实 ID 时接口正常;报错只由空 ID 触发,后端不需要改。


四、修复建议

① 监听器补两道守卫(index.vue:922-929),守卫要放在清空 confirmCheck 之前:

watch(
  [canConfirmBatch, groupBatchId],
  ([can, id]) => {
    // 离开页面那一拍:路由已变、onDeactivated 未执行,id 为空串;失活期间也不重拉
    if (!isDetailActive.value || !id) return
    confirmCheck.value = null
    if (can) loadConfirmCheck()
  },
  { immediate: true }
)

守卫放在前面,离开时就不会把预检结果清空,第二节第④条的次生问题也一起消失。

② loadConfirmCheck() 入口补兜底(index.vue:905),挡住以后新增的其它调用点:

async function loadConfirmCheck() {
  const batchId = groupBatchId.value
  if (!batchId || confirmCheckLoading.value) return
  const seq = ++confirmCheckSeq
  …
}

28_8478_团期详情新增分叉进度条并替代确认缺项横幅-修改接口-管理后台.md 第四节第 6 条要在 onReporterSaved 里补调 loadConfirmCheck(),有这道兜底后同样安全。

③ 建议补的回归(batch/detail/__tests__/index.spec.js 的 #8410 用例组):

  • 详情 batchStatus=RESOURCE_PREPARING、页面激活时,把 route.params.code 改为 undefined(模拟返回列表)→ 断言 getGroupBatchConfirmCheck 没有以 '' 被调用;
  • 再触发 onDeactivated / onActivated 回到同一 ID → 断言 confirmCheck 仍是离开前的结果(ready=false 时按钮仍置灰)。

现有 #8410 用例只覆盖了进入配置节点和点「确认」前两个时机,没有模拟路由离开,所以测不出来。


五、业务边界

  • 与 28_8478 前端交接清单不冲突:8478 要去掉页头缺项横幅,但明确保留 loadConfirmCheck 预检(确认按钮置灰靠它)。本条改的正是它的触发监听器,可以同批改。
  • 修复后预检的调用时机不变:进入配置节点时调一次,点「确认」前再调一次;非配置节点不调。
  • confirm-check 的入参、出参、权限码(group-batch:confirm)都不变,见 27_8410_团期确认只读预检-新增接口-管理后台.md。
  • 本条不涉及后端改动,接口契约不变。