feat: 新增神策数据埋点和广告页面功能
This commit is contained in:
28
src/main.ts
28
src/main.ts
@@ -15,29 +15,44 @@ import './assets/css/main.scss';
|
|||||||
import 'swiper/css';
|
import 'swiper/css';
|
||||||
import 'swiper/css/navigation';
|
import 'swiper/css/navigation';
|
||||||
import 'swiper/css/pagination';
|
import 'swiper/css/pagination';
|
||||||
import { sensorsData } from './utils/plugins/sa';
|
import { sensorsData } from '@/utils/plugins/sa';
|
||||||
import { isCollectUrl } from './utils/url/tools';
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
if (import.meta.env.VITE_APP_ENV !== 'production') {
|
if (import.meta.env.VITE_APP_ENV !== 'production') {
|
||||||
const vconsole = new VConsole();
|
/* const vconsole = */ new VConsole();
|
||||||
// app.use(vconsole);
|
// app.use(vconsole);
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
sa: any;
|
||||||
onAndroidBack: (() => void) | null;
|
onAndroidBack: (() => void) | null;
|
||||||
appBridge?: any;
|
appBridge?: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sa = {
|
||||||
|
register: false,
|
||||||
|
instance: window.sa || null
|
||||||
|
};
|
||||||
|
|
||||||
// 定义路由是否可以返回的判断
|
// 定义路由是否可以返回的判断
|
||||||
const routerCanGoBack = () => {
|
const routerCanGoBack = () => {
|
||||||
const position = router.options.history.state?.position;
|
const position = router.options.history.state?.position;
|
||||||
return typeof position === 'number' && position > 0;
|
return typeof position === 'number' && position > 0;
|
||||||
};
|
};
|
||||||
router.beforeEach((to, from, next) => {
|
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.meta?.title) document.title = to.meta.title as string;
|
||||||
|
|
||||||
if (to.query.digitalYiliToken) {
|
if (to.query.digitalYiliToken) {
|
||||||
@@ -58,5 +73,10 @@ router.beforeEach((to, from, next) => {
|
|||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.use(router);
|
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');
|
app.mount('#app');
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { createRouter, createWebHistory } from 'vue-router';
|
|||||||
import layout from '@/layouts/index.vue';
|
import layout from '@/layouts/index.vue';
|
||||||
import Design from '@/views/Design/Index.vue';
|
import Design from '@/views/Design/Index.vue';
|
||||||
import Redirect from '@/layouts/redirect.vue';
|
import Redirect from '@/layouts/redirect.vue';
|
||||||
import type { title } from 'process';
|
|
||||||
import { getWXShareConfig } from '@/utils/share';
|
import { getWXShareConfig } from '@/utils/share';
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
71
src/utils/plugins/sa/index.ts
Normal file
71
src/utils/plugins/sa/index.ts
Normal 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 };
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { fetchBanners } from '@/hooks/request/banner';
|
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';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -11,8 +12,6 @@ const bannerInfo = computed(() => {
|
|||||||
});
|
});
|
||||||
// 当前是否处于分享页面
|
// 当前是否处于分享页面
|
||||||
const hasShare = defineModel<boolean>('hasShare', { default: false });
|
const hasShare = defineModel<boolean>('hasShare', { default: false });
|
||||||
// 上层注入的神策对象
|
|
||||||
const sensors = inject<any>('sensors');
|
|
||||||
|
|
||||||
function handleButtonClick() {
|
function handleButtonClick() {
|
||||||
saTrack(bannerInfo.value);
|
saTrack(bannerInfo.value);
|
||||||
@@ -24,16 +23,11 @@ function handleButtonClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saTrack(record: any) {
|
function saTrack(record: any) {
|
||||||
const config = {
|
sensorsTrack({
|
||||||
eventName: 'ClickBanner',
|
page_name: 'APP落地页',
|
||||||
properties: {
|
model_name: record?.code || '',
|
||||||
page: '落地页',
|
button_name: '立即体验'
|
||||||
module: 'ad',
|
});
|
||||||
position: 'banner点击_' + record.code,
|
|
||||||
clickTime: new Date().toLocaleString().toString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sensors.track(config.eventName, config.properties);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 在挂载之后重新修改 html title 内容
|
* 在挂载之后重新修改 html title 内容
|
||||||
@@ -55,13 +49,13 @@ setTimeout(() => {
|
|||||||
<!-- banner内容 -->
|
<!-- banner内容 -->
|
||||||
<article>
|
<article>
|
||||||
<!-- 根据banner的类型使用不同的方式渲染 -->
|
<!-- 根据banner的类型使用不同的方式渲染 -->
|
||||||
<section class="banner-text" v-if="bannerInfo?.type === 0">
|
<section v-if="bannerInfo?.type === 0" class="banner-text">
|
||||||
<p>{{ bannerInfo.synopsis }}</p>
|
<p>{{ bannerInfo.synopsis }}</p>
|
||||||
</section>
|
</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" />
|
<el-image fit="cover" loading="lazy" :src="bannerInfo.file_address" />
|
||||||
</div>
|
</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>
|
<video width="100%" height="auto" controls>
|
||||||
<source :src="bannerInfo.file_address" type="video/mp4" />
|
<source :src="bannerInfo.file_address" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { bannerInfo } from '@/views/AD/hooks/useAD';
|
import { bannerInfo } from '@/views/AD/hooks/useAD';
|
||||||
import { fetchBanners } from '@/hooks/request/banner';
|
import { fetchBanners } from '@/hooks/request/banner';
|
||||||
import { computed, inject } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { sensorsTrack } from '@/utils/plugins/sa';
|
||||||
|
|
||||||
const { banners } = fetchBanners();
|
const { banners } = fetchBanners();
|
||||||
|
|
||||||
@@ -10,15 +11,13 @@ const router = useRouter();
|
|||||||
// const defineBanners = defineModel('banners');
|
// const defineBanners = defineModel('banners');
|
||||||
// 如果定义了 banner , 那么 banners 就不再初始化
|
// 如果定义了 banner , 那么 banners 就不再初始化
|
||||||
// defineBanners.value && updateBanners(defineBanners.value);
|
// defineBanners.value && updateBanners(defineBanners.value);
|
||||||
const borderRadius = defineModel('borderRadius');
|
const borderRadius = defineModel<number>('borderRadius');
|
||||||
// 是否启用平铺展示
|
// 是否启用平铺展示
|
||||||
const stack = defineModel<boolean>('stack', { default: true });
|
const stack = defineModel<boolean>('stack', { default: true });
|
||||||
// 上级传递的 banners
|
// 上级传递的 banners
|
||||||
const bannersList = defineModel<any[]>('banners');
|
const bannersList = defineModel<any[]>('banners');
|
||||||
// 是否存在显示显示数量
|
// 是否存在显示显示数量
|
||||||
const limit = defineModel<number>('limit');
|
const limit = defineModel<number>('limit');
|
||||||
// 上层注入的神策对象
|
|
||||||
const sensors = inject<any>('sensors');
|
|
||||||
|
|
||||||
function handleBannerClick(banner: any) {
|
function handleBannerClick(banner: any) {
|
||||||
// 把对应的信息给 AD 的 hooks
|
// 把对应的信息给 AD 的 hooks
|
||||||
@@ -29,17 +28,11 @@ function handleBannerClick(banner: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saTrack(record: any) {
|
function saTrack(record: any) {
|
||||||
const config = {
|
sensorsTrack({
|
||||||
eventName: 'ClickBanner',
|
page_name: 'APP首页',
|
||||||
properties: {
|
model_name: record?.code || '',
|
||||||
page: '首页',
|
button_name: '轮播图'
|
||||||
module: 'Banner',
|
});
|
||||||
position: 'Banner点击',
|
|
||||||
imgID: record.code,
|
|
||||||
clickTime: new Date().toLocaleString().toString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sensors.track(config.eventName, config.properties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentBanners = computed(() => {
|
const currentBanners = computed(() => {
|
||||||
@@ -52,7 +45,7 @@ const currentBanners = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="slider-container">
|
<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">
|
<van-swipe-item v-for="banner in currentBanners" :key="banner.code">
|
||||||
<el-image
|
<el-image
|
||||||
class="img"
|
class="img"
|
||||||
|
|||||||
Reference in New Issue
Block a user