feat: 官网接入 GA4,埋首页点击与 SPA 浏览
All checks were successful
Build and Deploy Front / deploy (push) Successful in 9m42s
All checks were successful
Build and Deploy Front / deploy (push) Successful in 9m42s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1f55a6d3ba
commit
92cf28b73b
@ -10,3 +10,6 @@ VITE_API_ASSET_ORIGIN=https://api.test.ideship.com/admin
|
||||
VITE_OMS_ADMIN_URL=https://api.test.ideship.com/admin
|
||||
|
||||
VITE_WECOM_KF_URL=https://work.weixin.qq.com/kfid/kfcae09c206f6c7045a
|
||||
|
||||
# 测试站可选:填同一 GA4 或单独测试媒体资源;空则不加载
|
||||
# VITE_GA_MEASUREMENT_ID=
|
||||
|
||||
@ -11,3 +11,7 @@ VITE_API_ASSET_ORIGIN=https://api.ideship.com/admin
|
||||
VITE_OMS_ADMIN_URL=https://api.ideship.com/admin
|
||||
|
||||
VITE_WECOM_KF_URL=https://work.weixin.qq.com/kfid/kfcae09c206f6c7045a
|
||||
|
||||
# Google Analytics 4(官网 https://ideship.com)
|
||||
# 地域/设备/来源自动有;首页点击见事件 home_*
|
||||
VITE_GA_MEASUREMENT_ID=G-1XWZ7LL1PW
|
||||
|
||||
4
src/env.d.ts
vendored
4
src/env.d.ts
vendored
@ -53,4 +53,8 @@ interface ImportMetaEnv {
|
||||
readonly VITE_API_ORIGIN?: string;
|
||||
/** 静态资源 origin;本地默认测试 admin */
|
||||
readonly VITE_API_ASSET_ORIGIN?: string;
|
||||
/** 企微客服链接 */
|
||||
readonly VITE_WECOM_KF_URL?: string;
|
||||
/** Google Analytics 4 Measurement ID,如 G-XXXXXXXX;空则不加载 */
|
||||
readonly VITE_GA_MEASUREMENT_ID?: string;
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
import { initAuthKeepAlive } from '@/utils/auth-keepalive';
|
||||
import { installWeChatH5HistoryGuard } from '@/utils/device';
|
||||
import { installChunkReloadHandlers, reloadOnChunkLoadError } from '@/utils/chunkReload';
|
||||
import { initGa } from '@/utils/ga';
|
||||
import { setAuthToken } from './api/rbac';
|
||||
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||||
import { applyRbacUserToUserStore } from '@/utils/enter-system';
|
||||
@ -138,6 +139,8 @@ async function bootstrap() {
|
||||
;(window as any).__APP_MOUNTED__ = true;
|
||||
installWeChatH5HistoryGuard(router);
|
||||
initAuthKeepAlive();
|
||||
// GA4:有 VITE_GA_MEASUREMENT_ID 才加载;地域/设备等自动采集
|
||||
initGa();
|
||||
|
||||
// 登录页勿预热 VXE/Element:体积大且会注入全局 CSS,和首屏抢带宽
|
||||
if (isHomePath(entryPath) && !isH5LikePath(entryPath) && !light) {
|
||||
|
||||
@ -10,7 +10,12 @@ function setupPageGuard(router: Router) {
|
||||
setRouteEmitter(to);
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
router.afterEach((to) => {
|
||||
// SPA 页面浏览(未配置 VITE_GA_MEASUREMENT_ID 时为空操作)
|
||||
const fullPath = to.fullPath || to.path || '/';
|
||||
void import('@/utils/ga').then(({ trackPageView }) => {
|
||||
trackPageView(fullPath);
|
||||
});
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
|
||||
80
src/utils/ga.ts
Normal file
80
src/utils/ga.ts
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Google Analytics 4(gtag)
|
||||
* 通过 VITE_GA_MEASUREMENT_ID(如 G-XXXXXXXX)启用;未配置则整段空操作。
|
||||
* 地域分布、设备、来源等由 GA4 自动采集,业务点击用 trackEvent / trackSiteEvent。
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer: unknown[];
|
||||
gtag?: (...args: unknown[]) => void;
|
||||
}
|
||||
}
|
||||
|
||||
const MEASUREMENT_ID = String(import.meta.env.VITE_GA_MEASUREMENT_ID || '').trim();
|
||||
|
||||
let booted = false;
|
||||
|
||||
export function isGaEnabled(): boolean {
|
||||
return /^G-[A-Z0-9]+$/i.test(MEASUREMENT_ID);
|
||||
}
|
||||
|
||||
export function getGaMeasurementId(): string {
|
||||
return isGaEnabled() ? MEASUREMENT_ID : '';
|
||||
}
|
||||
|
||||
/** 注入 gtag 脚本并 config;可重复调用,仅首次生效 */
|
||||
export function initGa(): void {
|
||||
if (!isGaEnabled() || typeof window === 'undefined' || booted) return;
|
||||
booted = true;
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function gtag(...args: unknown[]) {
|
||||
window.dataLayer.push(args);
|
||||
};
|
||||
|
||||
window.gtag('js', new Date());
|
||||
window.gtag('config', MEASUREMENT_ID, {
|
||||
// SPA 由路由 afterEach 主动发 page_view,避免首屏重复
|
||||
send_page_view: false,
|
||||
anonymize_ip: true,
|
||||
});
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(MEASUREMENT_ID)}`;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
export function trackPageView(path: string, title?: string): void {
|
||||
if (!isGaEnabled() || !window.gtag) return;
|
||||
const pagePath = path || '/';
|
||||
window.gtag('event', 'page_view', {
|
||||
page_path: pagePath,
|
||||
page_title: title || document.title,
|
||||
page_location: typeof window !== 'undefined' ? window.location.href : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackEvent(
|
||||
name: string,
|
||||
params?: Record<string, string | number | boolean | undefined | null>,
|
||||
): void {
|
||||
if (!isGaEnabled() || !window.gtag) return;
|
||||
const cleaned: Record<string, string | number | boolean> = {};
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null || value === '') return;
|
||||
cleaned[key] = value;
|
||||
});
|
||||
}
|
||||
window.gtag('event', name, cleaned);
|
||||
}
|
||||
|
||||
/** 官网营销事件(统一带 surface,方便在探索里筛选) */
|
||||
export function trackSiteEvent(
|
||||
name: string,
|
||||
params?: Record<string, string | number | boolean | undefined | null>,
|
||||
): void {
|
||||
trackEvent(name, { surface: 'official_site', ...params });
|
||||
}
|
||||
@ -76,7 +76,7 @@
|
||||
:active-thread-id="activeThreadId"
|
||||
:routes-pending="routesPending"
|
||||
@submit="onSubmit"
|
||||
@intent="advisor.askIntent"
|
||||
@intent="onIntent"
|
||||
@human="onNav('contact')"
|
||||
@wechat="openWecom"
|
||||
@prefill="draft = $event"
|
||||
@ -150,6 +150,7 @@
|
||||
import { shouldOpenWecomDirect, WECOM_KF_URL } from './lib/site-wecom';
|
||||
import { provideSiteLiquidGlass } from './lib/site-liquid-glass';
|
||||
import { useSiteAdvisor } from './useSiteAdvisor';
|
||||
import { trackSiteEvent } from '@/utils/ga';
|
||||
import './styles/vendor.css';
|
||||
import './styles/icons.css';
|
||||
import './styles/main.css';
|
||||
@ -181,9 +182,17 @@
|
||||
function onOfficialLoginCapture(e: MouseEvent) {
|
||||
const target = e.target as Element | null;
|
||||
const anchor = target?.closest?.('a[href]') as HTMLAnchorElement | null;
|
||||
if (!anchor || anchor.classList.contains('site-nav-login')) return;
|
||||
if (!anchor) return;
|
||||
const href = anchor.getAttribute('href') || '';
|
||||
if (!href.includes('from=official-site')) return;
|
||||
trackSiteEvent('home_login_click', {
|
||||
link_role: anchor.classList.contains('site-nav-login')
|
||||
? 'nav_login'
|
||||
: anchor.classList.contains('ai-welcome-cta-link')
|
||||
? 'hero_cta'
|
||||
: 'other',
|
||||
});
|
||||
if (anchor.classList.contains('site-nav-login')) return;
|
||||
goOfficialLoginFromHref(router, href, e);
|
||||
}
|
||||
|
||||
@ -292,14 +301,22 @@
|
||||
panel.value = id;
|
||||
nav.value = id;
|
||||
syncHash(id);
|
||||
trackSiteEvent('home_panel_open', { panel_id: id });
|
||||
}
|
||||
|
||||
function openRoutes(dest?: string) {
|
||||
const destId = dest || 'all';
|
||||
advisor.clearRoutesPending();
|
||||
routesDest.value = dest || 'all';
|
||||
routesDest.value = destId;
|
||||
trackSiteEvent('home_dest_click', { dest_id: destId });
|
||||
openPanel('routes');
|
||||
}
|
||||
|
||||
function onIntent(intentId: string) {
|
||||
trackSiteEvent('home_intent_click', { intent_id: intentId });
|
||||
advisor.askIntent(intentId);
|
||||
}
|
||||
|
||||
function closePanel(id?: string) {
|
||||
if (id && panel.value && panel.value !== id) return;
|
||||
if (!panel.value) return;
|
||||
@ -314,6 +331,7 @@
|
||||
historyOpen.value = false;
|
||||
nav.value = 'home';
|
||||
syncHash('home');
|
||||
trackSiteEvent('home_nav', { nav_id: 'home' });
|
||||
advisor.startNewChat();
|
||||
}
|
||||
|
||||
@ -323,6 +341,7 @@
|
||||
onHome();
|
||||
return;
|
||||
}
|
||||
trackSiteEvent('home_nav', { nav_id: id });
|
||||
if (NAV_PANELS.includes(id as PanelId)) {
|
||||
openPanel(id as Exclude<PanelId, null>);
|
||||
return;
|
||||
@ -336,6 +355,10 @@
|
||||
const fromRoutes = panel.value === 'routes';
|
||||
closePanel();
|
||||
draft.value = '';
|
||||
trackSiteEvent('home_ask', {
|
||||
intent_id: payload.intentId,
|
||||
from_routes: fromRoutes,
|
||||
});
|
||||
advisor.ask(payload.text, { intentId: payload.intentId, fromRoutes });
|
||||
}
|
||||
|
||||
@ -343,11 +366,16 @@
|
||||
const text = draft.value.trim();
|
||||
if (!text) return;
|
||||
draft.value = '';
|
||||
trackSiteEvent('home_ask', { intent_id: 'free_text' });
|
||||
advisor.ask(text);
|
||||
}
|
||||
|
||||
function onChoice(payload: { id: string; label: string; flowId: string }) {
|
||||
draft.value = '';
|
||||
trackSiteEvent('home_choice', {
|
||||
choice_id: payload.id,
|
||||
flow_id: payload.flowId,
|
||||
});
|
||||
advisor.ask(payload.label, { kind: 'choice', flowId: payload.flowId, choice: payload.id });
|
||||
}
|
||||
|
||||
@ -357,6 +385,7 @@
|
||||
}
|
||||
|
||||
function openWecom() {
|
||||
trackSiteEvent('home_wecom_click', { entry: 'advisor' });
|
||||
if (shouldOpenWecomDirect()) {
|
||||
window.location.href = WECOM_KF_URL;
|
||||
return;
|
||||
@ -365,6 +394,7 @@
|
||||
}
|
||||
|
||||
function onTopicWechat() {
|
||||
trackSiteEvent('home_wecom_click', { entry: 'topic' });
|
||||
if (shouldOpenWecomDirect()) {
|
||||
window.location.href = WECOM_KF_URL;
|
||||
return;
|
||||
@ -376,6 +406,7 @@
|
||||
const next = lang.value === 'zh' ? 'en' : 'zh';
|
||||
lang.value = next;
|
||||
localStorage.setItem('ide-language', next);
|
||||
trackSiteEvent('home_lang_toggle', { lang: next });
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user