diff --git a/.env b/.env index 09f6da3..31833e2 100644 --- a/.env +++ b/.env @@ -1,7 +1,8 @@ -# H5:true 时在页面加载 vConsole(浙政钉内可看 Network/Log);稳定上线后改为 false +# H5:true 时在页面加载 vConsole #VITE_VCONSOLE=true VITE_VCONSOLE=false +#VITE_ZZD_TRACK_DISABLE=true #是否是TOC应用 VITE_IS_TOC=true diff --git a/src/App.vue b/src/App.vue index 78ba379..14d7e83 100644 --- a/src/App.vue +++ b/src/App.vue @@ -172,10 +172,20 @@ export default { } const { tryZzdEntryLogin } = await import("@/sheep/utils/request") await tryZzdEntryLogin() + const { onZzdAppLaunchTrack } = await import("@/sheep/utils/zzd-aplus-track") + await onZzdAppLaunchTrack() } catch (e) { console.warn("[App.onLaunch] tryZzdEntryLogin", e) } }, + async onShow() { + try { + const { onZzdAppShowTrack } = await import("@/sheep/utils/zzd-aplus-track") + await onZzdAppShowTrack() + } catch (e) { + console.warn("[App.onShow] zzd track", e) + } + }, methods: { async _initTabbar() { const launchParam = getLaunchParam() diff --git a/src/main.ts b/src/main.ts index 5e7005b..c6c4932 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,9 +7,11 @@ import tmUi from "./uni_modules/tm-ui" // #ifdef H5 import VConsole from "vconsole" +import { installZzdNavigateTrack } from "@/sheep/utils/zzd-aplus-track" if (import.meta.env.VITE_VCONSOLE === "true") { new VConsole() } +installZzdNavigateTrack() // #endif export function createApp() { diff --git a/src/pages/user/zzdSmsVerify.vue b/src/pages/user/zzdSmsVerify.vue index b98e1b6..447198e 100644 --- a/src/pages/user/zzdSmsVerify.vue +++ b/src/pages/user/zzdSmsVerify.vue @@ -68,6 +68,8 @@ async function confirm() { uni.setStorageSync(tokenKey, r.data) const { data: userData } = await userApi.userInfo() uni.setStorageSync(userInfoKey, userData) + // const { onZzdLoginSuccessTrack } = await import("@/sheep/utils/zzd-aplus-track") + // void onZzdLoginSuccessTrack() uni.showToast({ title: "登录成功", icon: "none" }) setTimeout(() => { uni.reLaunch({ url: "/pages/tabbar/index" }) diff --git a/src/sheep/utils/request.ts b/src/sheep/utils/request.ts index ccc8178..b69201f 100644 --- a/src/sheep/utils/request.ts +++ b/src/sheep/utils/request.ts @@ -272,6 +272,8 @@ async function zzdLoginCore(): Promise<"ok" | "sms" | "failed"> { uni.setStorageSync(TOKEN_KEY, res.data) const { data: userData } = await userApi.userInfo() uni.setStorageSync(USER_INFO_KEY, userData) + const { onZzdLoginSuccessTrack } = await import("./zzd-aplus-track") + void onZzdLoginSuccessTrack() return "ok" } if (res?.msg) { @@ -295,6 +297,8 @@ async function zzdLoginCore(): Promise<"ok" | "sms" | "failed"> { uni.setStorageSync(TOKEN_KEY, res.data) const { data: userData } = await userApi.userInfo() uni.setStorageSync(USER_INFO_KEY, userData) + const { onZzdLoginSuccessTrack } = await import("./zzd-aplus-track") + void onZzdLoginSuccessTrack() return "ok" } else { uni.removeStorageSync(TOKEN_KEY) diff --git a/src/sheep/utils/zzd-aplus-track.ts b/src/sheep/utils/zzd-aplus-track.ts new file mode 100644 index 0000000..9869ce1 --- /dev/null +++ b/src/sheep/utils/zzd-aplus-track.ts @@ -0,0 +1,206 @@ +/** + * 浙政钉 H5 平台埋点(WpkReporter 稳定性监控 + aplus 通用采集 SDK) + * 对接文档:《应用开发参数(H5应用)智慧后勤》 + */ +import { APPENV } from "../constant" +import { storageConstant } from "../constant" +import { getAppInfo } from "./app" + +declare global { + interface Window { + aplus_queue?: Array<{ action: string; arguments?: unknown[] }> + wpkReporter?: new (config: Record) => { + installAll: () => void + } + _wpk?: unknown + } +} + +const WPK_SCRIPT = "https://wpkgate-emas.ding.zj.gov.cn/static/wpk-jssdk.1.0.2/wpkReporter.js" +const APLUS_SCRIPT = "https://alidt.alicdn.com/alilog/mlog/aplus_cloud.js" +const DEV_ID_KEY = "zzd_aplus_dev_id" + +const APLUS_SAPP_ID = "55360" +const APLUS_SAPP_NAME = "pjx_zhhqyht" + +const WPK_CONFIG = { + bid: "pjx_zhhqyht_zzdpro", + signkey: "1234567890abcdef", + gateway: "https://wpkgate-emas.ding.zj.gov.cn", +} + +let initialized = false +let initPromise: Promise | null = null +let navigateInterceptorInstalled = false +let lastPvAt = 0 +const PV_DEBOUNCE_MS = 800 + +function isTrackDisabled(): boolean { + return import.meta.env.VITE_ZZD_TRACK_DISABLE === "true" +} + +function getAplusQueue(): NonNullable { + if (!window.aplus_queue) { + window.aplus_queue = [] + } + return window.aplus_queue +} + +function pushAplus(action: string, ...args: unknown[]) { + getAplusQueue().push({ action, arguments: args }) +} + +function loadScript(src: string, id?: string): Promise { + return new Promise((resolve, reject) => { + if (id && document.getElementById(id)) { + resolve() + return + } + const el = document.createElement("script") + el.async = true + el.src = src + if (id) el.id = id + el.crossOrigin = "anonymous" + el.onload = () => resolve() + el.onerror = () => reject(new Error(`load script failed: ${src}`)) + document.head.appendChild(el) + }) +} + +function getAplusAppId(): string { + const u = navigator.userAgent + const isAndroid = u.indexOf("Android") > -1 + const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) + if (isAndroid) return "28302650" + if (isIOS) return "28328447" + return "47130293" +} + +function getOrCreateDevId(): string { + try { + let id = uni.getStorageSync(DEV_ID_KEY) + if (id) return String(id) + id = + typeof crypto !== "undefined" && crypto.randomUUID + ? crypto.randomUUID() + : `dev-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` + uni.setStorageSync(DEV_ID_KEY, id) + return id + } catch { + return `dev-${Date.now()}` + } +} + +function initWpkReporter(): void { + try { + const Wpk = window.wpkReporter + if (!Wpk) return + const wpk = new Wpk({ ...WPK_CONFIG }) + wpk.installAll() + window._wpk = wpk + } catch (err) { + console.error("[zzd-aplus-track] WpkReporter init fail", err) + } +} + +function initAplusMeta(): void { + const rhost = "alog-api.ding.zj.gov.cn" + pushAplus("aplus.setMetaInfo", "aplus-rhost-v", rhost) + pushAplus("aplus.setMetaInfo", "aplus-rhost-g", rhost) + pushAplus("aplus.setMetaInfo", "appId", getAplusAppId()) + pushAplus("aplus.setMetaInfo", "aplus-waiting", "MAN") +} + +async function doInit(): Promise { + if (typeof window === "undefined") return false + try { + await loadScript(WPK_SCRIPT) + initWpkReporter() + await loadScript(APLUS_SCRIPT, "beacon-aplus") + initAplusMeta() + initialized = true + return true + } catch (e) { + console.warn("[zzd-aplus-track] init failed", e) + return false + } +} + +/** 浙政钉环境下初始化 SDK(幂等) */ +export async function ensureZzdTrackReady(): Promise { + if (typeof window === "undefined" || isTrackDisabled()) return false + const appInfo = await getAppInfo() + if (appInfo !== APPENV.ZZD) return false + if (initialized) return true + if (!initPromise) { + initPromise = doInit() + } + return initPromise +} + +/** 手动上报 PV(单页应用路由切换后调用) */ +export async function trackZzdSendPv(extra?: Record): Promise { + if (!(await ensureZzdTrackReady())) return + const now = Date.now() + if (now - lastPvAt < PV_DEBOUNCE_MS) return + lastPvAt = now + pushAplus("aplus.sendPV", { is_auto: false }, { + sapp_id: APLUS_SAPP_ID, + sapp_name: APLUS_SAPP_NAME, + ...(extra || {}), + }) +} + +/** 从本地缓存同步用户信息到 aplus(登录成功后调用) */ +export async function trackZzdUserFromStorage(): Promise { + if (!(await ensureZzdTrackReady())) return + const user = uni.getStorageSync(storageConstant.USERINFO) + const sysUser = user?.sysUser + if (!sysUser?.userId) return + + const userId = String(sysUser.userId) + const nick = sysUser.name || sysUser.nickname || sysUser.username || userId + const devId = getOrCreateDevId() + + pushAplus("aplus.setMetaInfo", "_hold", "BLOCK") + pushAplus("aplus.setMetaInfo", "_user_nick", nick) + pushAplus("aplus.setMetaInfo", "_user_id", userId) + pushAplus("aplus.setMetaInfo", "_dev_id", devId) + pushAplus("aplus.setMetaInfo", "_hold", "START") +} + +/** App 启动:初始化 + 首屏 PV + 用户信息 */ +export async function onZzdAppLaunchTrack(): Promise { + if (!(await ensureZzdTrackReady())) return + await trackZzdSendPv() + await trackZzdUserFromStorage() +} + +/** App onShow:补发 PV 与用户信息(从后台切回等) */ +export async function onZzdAppShowTrack(): Promise { + if (!(await ensureZzdTrackReady())) return + await trackZzdSendPv() + await trackZzdUserFromStorage() +} + +/** 登录成功并写入 user_info 后调用 */ +export async function onZzdLoginSuccessTrack(): Promise { + if (!(await ensureZzdTrackReady())) return + await trackZzdUserFromStorage() + await trackZzdSendPv() +} + +const NAVIGATE_METHODS = ["navigateTo", "redirectTo", "reLaunch", "switchTab", "navigateBack"] as const + +/** 注册 uni 路由拦截,页面跳转成功后上报 PV */ +export function installZzdNavigateTrack(): void { + if (typeof uni === "undefined" || navigateInterceptorInstalled) return + navigateInterceptorInstalled = true + NAVIGATE_METHODS.forEach((method) => { + uni.addInterceptor(method, { + success() { + void trackZzdSendPv() + }, + }) + }) +}