新增
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
/** 发现糖尿病患病史:纯数字展示时补「年」,含文字(如半年、1年)则原样 */
|
||||
export function formatDiabetesDiscoveryDisplay(v: unknown): string {
|
||||
if (v == null || v === '') return '—'
|
||||
const s = String(v).trim()
|
||||
if (s === '') return '—'
|
||||
return /^\d+$/.test(s) ? `${s}年` : s
|
||||
}
|
||||
|
||||
export function isDiabetesDiscoveryFilled(v: unknown): boolean {
|
||||
if (v == null) return false
|
||||
return String(v).trim() !== ''
|
||||
}
|
||||
@@ -10,6 +10,10 @@ import { clearAuthInfo, getToken } from '../auth'
|
||||
import feedback from '../feedback'
|
||||
import { Axios } from './axios'
|
||||
import type { AxiosHooks } from './type'
|
||||
import { isBrowserOnWecomBindOAuthLanding } from '../wecomBindGuard'
|
||||
|
||||
const BIND_WORK_WECHAT_PATH = '/bind-work-wechat'
|
||||
let lastWorkWechatBindRedirectAt = 0
|
||||
|
||||
// 处理axios的钩子函数
|
||||
const axiosHooks: AxiosHooks = {
|
||||
@@ -65,6 +69,22 @@ const axiosHooks: AxiosHooks = {
|
||||
clearAuthInfo()
|
||||
router.push(PageEnum.LOGIN)
|
||||
return Promise.reject()
|
||||
case RequestCodeEnum.NEED_BIND_WORK_WECHAT:
|
||||
// 回跳瞬间 currentRoute 可能还是 /,误判后 replace 无 query,地址栏 code 被清 → 绑定永远不请求
|
||||
if (
|
||||
router.currentRoute.value.path === BIND_WORK_WECHAT_PATH ||
|
||||
isBrowserOnWecomBindOAuthLanding()
|
||||
) {
|
||||
return Promise.reject(data)
|
||||
}
|
||||
{
|
||||
const now = Date.now()
|
||||
if (now - lastWorkWechatBindRedirectAt > 400) {
|
||||
lastWorkWechatBindRedirectAt = now
|
||||
void router.replace(BIND_WORK_WECHAT_PATH).catch(() => {})
|
||||
}
|
||||
}
|
||||
return Promise.reject(data)
|
||||
case RequestCodeEnum.OPEN_NEW_PAGE:
|
||||
window.location.href = data.url
|
||||
return data
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 判断浏览器地址栏是否处于「企微绑定页 OAuth 回调」(带 code)。
|
||||
* 用于避免 axios 在 router.currentRoute 尚未同步完成时执行 router.replace('/bind-work-wechat') 把 query 整段清掉。
|
||||
*/
|
||||
export function isBrowserOnWecomBindOAuthLanding(): boolean {
|
||||
if (typeof window === 'undefined') return false
|
||||
const { pathname, search } = window.location
|
||||
if (!pathname.includes('bind-work-wechat')) return false
|
||||
return /(?:^|[?&])code=/.test(search)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 企微 WwLogin:iframe 内授权完成后通过 postMessage 把「完整回调 URL」交给父窗口,
|
||||
* 随后官方脚本会执行 window.location.href = url 触发整页刷新。
|
||||
* 部分环境下刷新后地址栏没有 code,Vue 不会走 URL 分支 → bindWorkWechat 从不发起。
|
||||
* 在 window 上以 capture 监听 message,抢先解析 code 并 stopImmediatePropagation,避免整页跳转。
|
||||
*/
|
||||
|
||||
const DEFAULT_STATE = 'bind_wxwork'
|
||||
|
||||
function isWecomPostMessageOrigin(origin: string): boolean {
|
||||
return origin.includes('work.weixin.qq.com') || origin.includes('tencent.com')
|
||||
}
|
||||
|
||||
function parseOAuthAccept(
|
||||
params: URLSearchParams,
|
||||
wxBindState: string
|
||||
): { code: string } | null {
|
||||
const wxCode = (params.get('code') || '').trim()
|
||||
if (!wxCode) return null
|
||||
const wxState = (params.get('state') ?? '').trim()
|
||||
const stateOk =
|
||||
wxState === wxBindState ||
|
||||
wxState === 'admin_bind_wx' ||
|
||||
wxState.toLowerCase() === wxBindState.toLowerCase()
|
||||
const bindFlagOk = params.get('bind_wxwork') === '1'
|
||||
const implicitOk = wxState === ''
|
||||
if (!(stateOk || bindFlagOk || implicitOk)) return null
|
||||
return { code: wxCode }
|
||||
}
|
||||
|
||||
export type WecomOauthCaptureOptions = {
|
||||
/** 回调 URL 的 pathname 须包含此片段,如 bind-work-wechat、user/setting */
|
||||
pathIncludes: string
|
||||
lockKey: string
|
||||
wxBindState?: string
|
||||
onCode: (code: string) => void | Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns 卸载时调用以 removeEventListener
|
||||
*/
|
||||
export function attachWecomOAuthMessageCapture(options: WecomOauthCaptureOptions): () => void {
|
||||
const wxBindState = options.wxBindState ?? DEFAULT_STATE
|
||||
const handler = (event: MessageEvent) => {
|
||||
if (!isWecomPostMessageOrigin(event.origin)) return
|
||||
const data = event.data
|
||||
if (typeof data !== 'string' || !/^https?:\/\//i.test(data)) return
|
||||
let u: URL
|
||||
try {
|
||||
u = new URL(data)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
if (u.host !== window.location.host) return
|
||||
if (!u.pathname.includes(options.pathIncludes)) return
|
||||
|
||||
const parsed = parseOAuthAccept(new URLSearchParams(u.search), wxBindState)
|
||||
if (!parsed) return
|
||||
if (sessionStorage.getItem(options.lockKey) === parsed.code) return
|
||||
|
||||
event.stopImmediatePropagation()
|
||||
sessionStorage.setItem(options.lockKey, parsed.code)
|
||||
void Promise.resolve(options.onCode(parsed.code)).finally(() => {
|
||||
sessionStorage.removeItem(options.lockKey)
|
||||
})
|
||||
}
|
||||
window.addEventListener('message', handler, true)
|
||||
return () => window.removeEventListener('message', handler, true)
|
||||
}
|
||||
Reference in New Issue
Block a user