Files
yml2213 7e197c9413 fix(frontend): H5 领取前缀整段接管,/h5 裸链接不再跳后台登录
根因:公开路径判定原先只认「带参数」形态(isH5ClaimPath 只认 /h5/<orderNo>),
裸前缀 https://<域名>/h5(后台 claimLinkBase / certLink 的基址)判定不命中,
于是落回主站 HashRouter 被后台路由接管成
https://<域名>/h5/#/admin/dashboard 后台登录页。

- utils/claim-path.ts:isClaimPath / isH5ClaimPath 改为命名空间级判定(含裸前缀与多段路径),
  参数解析仍严格;新增 resolveH5ClaimOrderNoFromInput(纯订单号 / 整条链接 / 缺协议 /
  带后台残留 hash 都能取出订单号,只有前缀时返回空串)
- pages/claim/H5ClaimEntryPage.tsx:新增 /h5 领取入口页,买家输订单号或粘贴完整链接后
  跳 /h5/<订单号>,走与分享链接同一套领取流程
- App.tsx:H5ClaimApplication 接管 /h5 整段前缀(/h5 入口页、/h5/:orderNo 领取页、
  其它子路径回入口页);useClearStrayHash 抹掉历史残留的 #/admin/dashboard
- tests/claim-path.test.ts:补齐裸前缀、相邻前缀 /h5x、粘贴链接解析等回归用例

验证:npm run check 全绿(prettier + eslint + tsc + 143 单测 + vite build)
2026-10-13 13:42:06 +08:00

119 lines
5.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import test from 'node:test'
import {
isClaimPath,
isH5ClaimPath,
resolveClaimToken,
resolveH5ClaimOrderNo,
resolveH5ClaimOrderNoFromInput,
} from '../src/utils/claim-path.ts'
test('isClaimPath:/claim 命名空间整段命中(含只有前缀、多段路径)', () => {
assert.equal(isClaimPath('/claim/abc123'), true)
assert.equal(isClaimPath('/claim/abc123/'), true)
assert.equal(isClaimPath('/claim/abc123//'), true)
// 只有前缀 / 没有 token 也要命中:否则会被主站 HashRouter 接管跳到后台登录
assert.equal(isClaimPath('/claim'), true)
assert.equal(isClaimPath('/claim/'), true)
assert.equal(isClaimPath('/claim/a/b'), true)
})
test('isClaimPath:其它路径、非路径开头的串都不算', () => {
assert.equal(isClaimPath('/redeem'), false)
assert.equal(isClaimPath('/h5/123'), false)
assert.equal(isClaimPath('/admin/claim/abc'), false)
assert.equal(isClaimPath('/claimx/abc'), false)
assert.equal(isClaimPath('/'), false)
assert.equal(isClaimPath(''), false)
assert.equal(isClaimPath(null), false)
assert.equal(isClaimPath(undefined), false)
})
test('isClaimPath:打手页是 hash 路由,pathname 为 / 或 hash 串时不命中', () => {
// 打手链接形如 https://<域名>/#/claim/<token>:pathname 是 '/',hash 才是路由
assert.equal(isClaimPath('/'), false)
assert.equal(isClaimPath('#/claim/abc123'), false)
// 路径本身带 token 时,多出来的 hash 片段会被忽略
assert.equal(isClaimPath('/claim/abc123#/claim/other'), true)
})
test('resolveClaimToken:取出并解码 token 片段', () => {
assert.equal(resolveClaimToken('/claim/abc123'), 'abc123')
assert.equal(resolveClaimToken('/claim/abc123/?x=1'), 'abc123')
assert.equal(resolveClaimToken('/claim/a%2Fb'), 'a/b')
assert.equal(resolveClaimToken('/claim/%E4%B8%AD%E6%96%87'), '中文')
// 非法百分号编码退回原始片段,交给后端判定
assert.equal(resolveClaimToken('/claim/100%'), '100%')
// 只有前缀 / 多段路径取不到 token,由页面展示「链接无效」
assert.equal(resolveClaimToken('/claim'), '')
assert.equal(resolveClaimToken('/claim/a/b'), '')
assert.equal(resolveClaimToken('/redeem'), '')
})
test('isH5ClaimPath:/h5 命名空间整段命中(含只有前缀、多段路径)', () => {
assert.equal(isH5ClaimPath('/h5/2623001922675146'), true)
assert.equal(isH5ClaimPath('/h5/2623001922675146/'), true)
// 关键回归:`https://<域名>/h5`(后台配置的链接基址)必须命领取入口,不能落回后台路由
assert.equal(isH5ClaimPath('/h5'), true)
assert.equal(isH5ClaimPath('/h5/'), true)
assert.equal(isH5ClaimPath('/h5/a/b'), true)
assert.equal(isH5ClaimPath('/h5#/admin/dashboard'), true)
})
test('isH5ClaimPath:相邻前缀与其它路径都不算', () => {
assert.equal(isH5ClaimPath('/h5x'), false)
assert.equal(isH5ClaimPath('/h5x/1'), false)
assert.equal(isH5ClaimPath('/claim/123'), false)
assert.equal(isH5ClaimPath('/admin/h5/1'), false)
assert.equal(isH5ClaimPath('/'), false)
assert.equal(isH5ClaimPath(undefined), false)
})
test('resolveH5ClaimOrderNo:只认 /h5/<一段订单号>,前缀本身与多段路径返回空串', () => {
assert.equal(resolveH5ClaimOrderNo('/h5/2623001922675146'), '2623001922675146')
assert.equal(resolveH5ClaimOrderNo('/h5/2623001922675146?from=sms'), '2623001922675146')
assert.equal(resolveH5ClaimOrderNo('/h5/ORDER-001/'), 'ORDER-001')
assert.equal(resolveH5ClaimOrderNo('/h5/%E4%B8%AD%E6%96%87'), '中文')
assert.equal(resolveH5ClaimOrderNo('/h5'), '')
assert.equal(resolveH5ClaimOrderNo('/h5/'), '')
assert.equal(resolveH5ClaimOrderNo('/h5/a/b'), '')
assert.equal(resolveH5ClaimOrderNo('/claim/123'), '')
})
test('resolveH5ClaimOrderNoFromInput:纯订单号原样返回', () => {
assert.equal(resolveH5ClaimOrderNoFromInput('2628601934580683'), '2628601934580683')
assert.equal(resolveH5ClaimOrderNoFromInput(' 2628601934580683 '), '2628601934580683')
assert.equal(resolveH5ClaimOrderNoFromInput('ORDER-001'), 'ORDER-001')
assert.equal(resolveH5ClaimOrderNoFromInput(''), '')
assert.equal(resolveH5ClaimOrderNoFromInput(null), '')
})
test('resolveH5ClaimOrderNoFromInput:粘贴整条链接时取路径里的订单号', () => {
assert.equal(
resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/h5/2628601934580683'),
'2628601934580683',
)
// 缺协议 / 带查询串 / 带后台残留 hash 都要能取出订单号
assert.equal(
resolveH5ClaimOrderNoFromInput('ks.khhao.com/h5/2628601934580683'),
'2628601934580683',
)
assert.equal(
resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/h5/2628601934580683?from=sms'),
'2628601934580683',
)
assert.equal(
resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/h5/2628601934580683#/admin/dashboard'),
'2628601934580683',
)
})
test('resolveH5ClaimOrderNoFromInput:链接里没有订单号时返回空串(不把整条 URL 当订单号)', () => {
assert.equal(resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/h5'), '')
assert.equal(resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/h5/'), '')
assert.equal(resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/h5#/admin/dashboard'), '')
// 不是 /h5 领取链接的地址同样取不到订单号
assert.equal(resolveH5ClaimOrderNoFromInput('https://ks.khhao.com/claim/abc123'), '')
})