This commit is contained in:
Your Name
2026-04-07 18:13:03 +08:00
parent a780356908
commit fdf714f833
397 changed files with 15086 additions and 1043 deletions
@@ -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() !== ''
}
+20
View File
@@ -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
+10
View File
@@ -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)
}
+69
View File
@@ -0,0 +1,69 @@
/**
* 企微 WwLoginiframe 内授权完成后通过 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)
}