feat: 新增神策数据埋点和广告页面功能

This commit is contained in:
Huangzhe
2025-06-24 22:43:31 +08:00
parent acc02c5a75
commit b5642d0d3d
6 changed files with 114 additions and 97 deletions

View File

@@ -15,29 +15,44 @@ import './assets/css/main.scss';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import { sensorsData } from './utils/plugins/sa';
import { isCollectUrl } from './utils/url/tools';
import { sensorsData } from '@/utils/plugins/sa';
const app = createApp(App);
if (import.meta.env.VITE_APP_ENV !== 'production') {
const vconsole = new VConsole();
/* const vconsole = */ new VConsole();
// app.use(vconsole);
}
declare global {
interface Window {
sa: any;
onAndroidBack: (() => void) | null;
appBridge?: any;
}
}
const sa = {
register: false,
instance: window.sa || null
};
// 定义路由是否可以返回的判断
const routerCanGoBack = () => {
const position = router.options.history.state?.position;
return typeof position === 'number' && position > 0;
};
router.beforeEach((to, from, next) => {
// 神策数据埋点
if (!sa.register && sessionStorage.getItem('userInfo')) {
sa.instance = window.sa;
// 检测是否使用神策的登陆
const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '');
sa.instance.setOnceProfile({ loginID: userInfo.userCode });
sa.register = true;
}
if (to.meta?.title) document.title = to.meta.title as string;
if (to.query.digitalYiliToken) {
@@ -58,5 +73,10 @@ router.beforeEach((to, from, next) => {
app.use(createPinia());
app.use(router);
// 神策数据插件
app.use(sensorsData());
app.use(sensorsData(), {
// 测试环境
server_url: 'https://digitaldmo.yili.com/sa?project=sensorstest'
// 正式环境
// server_url: 'https://digitaldmo.yili.com/sa?project=YIP'
});
app.mount('#app');

View File

@@ -2,7 +2,6 @@ import { createRouter, createWebHistory } from 'vue-router';
import layout from '@/layouts/index.vue';
import Design from '@/views/Design/Index.vue';
import Redirect from '@/layouts/redirect.vue';
import type { title } from 'process';
import { getWXShareConfig } from '@/utils/share';
const router = createRouter({

View File

@@ -1,60 +0,0 @@
import type { App } from 'vue';
import sensors from '@/assets/js/sa-sdk-javascript/dist/web/sensorsdata.es6.js';
// 定义神策数据插件选项接口
interface SensorsDataOptions {
// 可以根据需要添加具体的选项属性
[key: string]: any;
}
export function sensorsData() {
return {
install(app: App, options?: SensorsDataOptions) {
// console.log(`sensorsData install`);
sensors.init({
// server_url: '数据接收地址',
show_log: true,
// 单页面配置,默认关闭。开启后自动监听 URL 有变化就会触发 $pageview 事件
is_track_single_page: false,
use_client_time: true,
send_type: 'beacon',
heatmap: {
//是否开启点击图default 表示开启,自动采集 $WebClick 事件,可以设置 'not_collect' 表示关闭。
clickmap: 'not_collect',
//是否开启触达图not_collect 表示关闭,不会自动采集 $WebStay 事件,可以设置 'default' 表示开启。
scroll_notice_map: 'not_collect'
},
...options
});
// sensors.quick('autoTrack'); //用于采集 $pageview 事件。
// 注册公共属性
sensors.registerPage({
platform: 'h5',
current_url: location.href,
referrer: document.referrer
});
// app.config.globalProperties.$sensorsData = sensors;
app.provide('sensors', sensors);
// 注册 sa 点击跟踪指令
registerDirective(app);
}
};
}
/**
* 注册 sa 点击跟踪指令
* @param app 应用实例
*/
function registerDirective(app: App) {
app.directive('saTrack', {
mounted(el, binding) {
el.addEventListener('click', () => {
// console.log(`sa track`, binding);
sensors.track(binding.arg as string, binding.value);
});
}
});
}

View File

@@ -0,0 +1,71 @@
import type { App } from 'vue';
import sensors from './dist/v2/sensorsdata.es6';
/**
* 神策数据 SDK
*/
export type Sensors = typeof sensors;
/**
* 创建神策数据插件
*/
export function sensorsData() {
return {
install(app: App, options: any) {
// 初始化神策 SDK
sensors.init({
show_log: true,
is_track_single_page: false,
use_client_time: true,
send_type: 'beacon',
heatmap: {
clickmap: 'not_collect',
scroll_notice_map: 'not_collect'
},
...(options || {})
});
// 注册页面公共属性
sensors.registerPage({
product_name: '伊调研',
platform_type: 'PC'
});
// 提供全局注入的 sensors 实例
app.provide('sensors', sensors);
// 注册到window中
(globalThis as any).sa = sensors;
// 注册 saTrack 自定义指令
registerDirective(app);
}
};
}
/**
* 注册 saTrack 指令,用于点击埋点
* @param {App} app - Vue 应用实例
*/
function registerDirective(app: App) {
function bindTrackListener(binding: any) {
return () => {
const { value: properties } = binding;
sensorsTrack(properties);
};
}
app.directive('sensorsTrack', {
mounted(el, binding) {
el.addEventListener('click', bindTrackListener(binding));
},
unmounted(el, binding) {
// 清除绑定的事件
el.removeEventListener('click', bindTrackListener(binding));
}
});
}
function sensorsTrack(properties: any) {
sensors.track('YiliResearch_PageClick', properties);
}
export { sensors, sensorsTrack };

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { fetchBanners } from '@/hooks/request/banner';
import { computed, inject } from 'vue';
import { sensorsTrack } from '@/utils/plugins/sa';
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter();
@@ -11,8 +12,6 @@ const bannerInfo = computed(() => {
});
// 当前是否处于分享页面
const hasShare = defineModel<boolean>('hasShare', { default: false });
// 上层注入的神策对象
const sensors = inject<any>('sensors');
function handleButtonClick() {
saTrack(bannerInfo.value);
@@ -24,16 +23,11 @@ function handleButtonClick() {
}
function saTrack(record: any) {
const config = {
eventName: 'ClickBanner',
properties: {
page: '落地页',
module: 'ad',
position: 'banner点击_' + record.code,
clickTime: new Date().toLocaleString().toString()
}
};
sensors.track(config.eventName, config.properties);
sensorsTrack({
page_name: 'APP落地页',
model_name: record?.code || '',
button_name: '立即体验'
});
}
/**
* 在挂载之后重新修改 html title 内容
@@ -55,13 +49,13 @@ setTimeout(() => {
<!-- banner内容 -->
<article>
<!-- 根据banner的类型使用不同的方式渲染 -->
<section class="banner-text" v-if="bannerInfo?.type === 0">
<section v-if="bannerInfo?.type === 0" class="banner-text">
<p>{{ bannerInfo.synopsis }}</p>
</section>
<div class="banner-image" v-else-if="bannerInfo?.type === 1">
<div v-else-if="bannerInfo?.type === 1" class="banner-image">
<el-image fit="cover" loading="lazy" :src="bannerInfo.file_address" />
</div>
<section class="banner-video" v-else-if="bannerInfo?.type === 2">
<section v-else-if="bannerInfo?.type === 2" class="banner-video">
<video width="100%" height="auto" controls>
<source :src="bannerInfo.file_address" type="video/mp4" />
</video>

View File

@@ -2,7 +2,8 @@
import { useRouter } from 'vue-router';
import { bannerInfo } from '@/views/AD/hooks/useAD';
import { fetchBanners } from '@/hooks/request/banner';
import { computed, inject } from 'vue';
import { computed } from 'vue';
import { sensorsTrack } from '@/utils/plugins/sa';
const { banners } = fetchBanners();
@@ -10,15 +11,13 @@ const router = useRouter();
// const defineBanners = defineModel('banners');
// 如果定义了 banner 那么 banners 就不再初始化
// defineBanners.value && updateBanners(defineBanners.value);
const borderRadius = defineModel('borderRadius');
const borderRadius = defineModel<number>('borderRadius');
// 是否启用平铺展示
const stack = defineModel<boolean>('stack', { default: true });
// 上级传递的 banners
const bannersList = defineModel<any[]>('banners');
// 是否存在显示显示数量
const limit = defineModel<number>('limit');
// 上层注入的神策对象
const sensors = inject<any>('sensors');
function handleBannerClick(banner: any) {
// 把对应的信息给 AD 的 hooks
@@ -29,17 +28,11 @@ function handleBannerClick(banner: any) {
}
function saTrack(record: any) {
const config = {
eventName: 'ClickBanner',
properties: {
page: '首页',
module: 'Banner',
position: 'Banner点击',
imgID: record.code,
clickTime: new Date().toLocaleString().toString()
}
};
sensors.track(config.eventName, config.properties);
sensorsTrack({
page_name: 'APP首页',
model_name: record?.code || '',
button_name: '轮播图'
});
}
const currentBanners = computed(() => {
@@ -52,7 +45,7 @@ const currentBanners = computed(() => {
<template>
<div class="slider-container">
<van-swipe :autoplay="5000" indicator-color="white" v-if="stack">
<van-swipe v-if="stack" :autoplay="5000" indicator-color="white">
<van-swipe-item v-for="banner in currentBanners" :key="banner.code">
<el-image
class="img"