feat: 首页模块包含模型卡片、操作指引和场景组件添加神策埋点

This commit is contained in:
2025-06-24 22:04:12 +08:00
parent 4cbf5f6a47
commit 9852bcc14e
7 changed files with 194 additions and 179 deletions

View File

@@ -78,5 +78,10 @@ installAntDesign(app);
app.config.globalProperties.emitter = emitter;
// 神策数据插件
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.use(store).use(router).mount('#app');

View File

@@ -632,9 +632,8 @@ router.beforeEach(async (to, from, next) => {
sa.instance = window.sa;
// 检测是否使用神策的登陆
const userInfo = JSON.parse(localStorage.getItem('plantUserInfo'));
const loginID = `id-${userInfo.id}.login_team_id-${userInfo.login_team_id}`;
globalThis.sa.setOnceProfile({loginID:userInfo.id});
sa.instance.login(globalThis.btoa(loginID));
sa.register = true;
}

View File

@@ -21,13 +21,9 @@ export function sensorsData() {
// 注册页面公共属性
sensors.registerPage({
platform: 'pc',
// 产品名称
production_name: 'ylst',
current_url: location.href,
referrer: document.referrer
product_name: '伊调研',
platform_type: 'PC'
});
// 提供全局注入的 sensors 实例
app.provide('sensors', sensors);
// 注册到window中
@@ -48,16 +44,11 @@ function registerDirective(app) {
return () => {
const { arg: eventName, value: properties } = binding;
if (eventName) {
sensors.track(eventName, properties);
console.warn(properties);
} else {
console.warn('[sensorsData] 事件名未提供');
}
sensorsTrack(properties);
};
}
app.directive('saTrack', {
app.directive('sensorsTrack', {
mounted(el, binding) {
el.addEventListener('click', bindTrackListener(binding));
},
@@ -68,4 +59,7 @@ function registerDirective(app) {
});
}
export { sensors };
function sensorsTrack(properties){
sensors.track("YiliResearch_PageClick", properties);
}
export { sensors,sensorsTrack };

View File

@@ -9,7 +9,11 @@
<p class="banner-block-slogan">实时链接消费者的深度洞察及日常信息采集工具</p>
<div class="banner-block-desc">
已支持
<CountTo :end="surveyStats.user_count" :use-comma="true" :key="surveyStats.user_count" />
<CountTo
:end="surveyStats.user_count"
:use-comma="true"
:key="surveyStats.user_count"
/>
用户 | 发起
<CountTo :end="surveyStats.survey_count" :use-comma="true" />
次调研 | 有效回收
@@ -18,12 +22,16 @@
</div>
</div>
<!-- 动态 Banner 列表 -->
<div v-for="(item, index) in bannerList" :key="index" class="swiper-slide banner-content"
:style="{ backgroundImage: 'url(' + item.banner_address + ')' }">
<p class="banner-content-title" :class="{ 'white': index === 0 }">
<div
v-for="(item, index) in bannerList"
:key="index"
class="swiper-slide banner-content"
:style="{ backgroundImage: 'url(' + item.banner_address + ')' }"
>
<p class="banner-content-title" :class="{ white: index === 0 }">
{{ item.title }}
</p>
<p class="banner-content-desc" :class="{ 'white': index === 0 }">
<p class="banner-content-desc" :class="{ white: index === 0 }">
{{ item.synopsis }}
</p>
<p v-if="index === 0" class="banner-content-btn" @click="toBannerDetail(item)">
@@ -34,16 +42,25 @@
<!-- 左右按钮 -->
<div class="custom-swiper-button-prev" ref="prevBtn" @click="goPre">
<img src="@/assets/img/home/arrow-act.png" alt="上一张" style="transform: rotate(180deg)">
<img
src="@/assets/img/home/arrow-act.png"
alt="上一张"
style="transform: rotate(180deg)"
/>
</div>
<div class="custom-swiper-button-next" ref="nextBtn" @click="goNext">
<img src="@/assets/img/home/arrow-act.png" alt="下一张">
<img src="@/assets/img/home/arrow-act.png" alt="下一张" />
</div>
<!-- 分页器 -->
<div class="custom-pagination">
<span v-for="(item, index) in 3" :key="'dot' + index" class="custom-pagination-bullet"
:class="{ 'active': activeIndex === index }" @click="goToSlide(index)"></span>
<span
v-for="(item, index) in 3"
:key="'dot' + index"
class="custom-pagination-bullet"
:class="{ active: activeIndex === index }"
@click="goToSlide(index)"
></span>
</div>
</div>
</div>
@@ -62,18 +79,19 @@ import { useRouter } from 'vue-router';
import CountTo from '@/components/CountTo.vue';
import { getBannerList, getDynamicDataStatistics, getQuestionnaireStatistics } from '@/api/home';
import Charts from './Charts.vue';
import { sensors, sensorsTrack } from '@/utils/plugins/sa';
const router = useRouter()
const router = useRouter();
// 必须注册模块
Swiper.use([Autoplay])
Swiper.use([Autoplay]);
const swiperContainer = ref(null);
let bannerSwiper = null
const activeIndex = ref(0)
const prevBtn = ref(null)
const nextBtn = ref(null)
let bannerSwiper = null;
const activeIndex = ref(0);
const prevBtn = ref(null);
const nextBtn = ref(null);
// 数据部分
const surveyStats = ref({})
const surveyStats = ref({});
const surveyData = ref({
survey_count: 0,
survey_count_collect: 0,
@@ -82,25 +100,24 @@ const surveyData = ref({
survey_count_edit_ratio: 0,
survey_count_end: 0,
survey_count_end_ratio: 0
})
const bannerList = ref([])
});
const bannerList = ref([]);
const sensors = inject('sensors')
// 获取数据
const getBannerData = async () => {
const res = await getBannerList()
bannerList.value = res.data.banner_list
}
const res = await getBannerList();
bannerList.value = res.data.banner_list;
};
const getStatics = async () => {
const res = await getDynamicDataStatistics()
surveyStats.value = res.data
}
const res = await getDynamicDataStatistics();
surveyStats.value = res.data;
};
const getQuesStatics = async () => {
const res = await getQuestionnaireStatistics()
surveyData.value = res.data
}
const res = await getQuestionnaireStatistics();
surveyData.value = res.data;
};
// function readyLoop(){
// initSwiper(true)
@@ -141,12 +158,12 @@ const initSwiper = (loop = false) => {
on: {
slideChange: () => {
if (bannerSwiper) {
activeIndex.value = bannerSwiper.realIndex
activeIndex.value = bannerSwiper.realIndex;
}
},
},
})
}
}
}
});
};
const goPre = () => {
if (bannerSwiper) {
@@ -163,50 +180,44 @@ const goNext = () => {
const goToSlide = (index) => {
if (bannerSwiper) {
// 如果使用 loop: true则需用 slideToLoop
bannerSwiper.slideToLoop(index)
activeIndex.value = index
bannerSwiper.slideToLoop(index);
activeIndex.value = index;
}
}
};
// 方法
const toBannerDetail = (item) => {
// 神策埋点
saTrack(item)
saTrack(item);
router.push({
path: '/home/bannerDetail',
query: {
code: item.code
}
})
}
});
};
onMounted(async () => {
await getBannerData()
await getStatics()
await getQuesStatics()
initSwiper()
})
await getBannerData();
await getStatics();
await getQuesStatics();
initSwiper();
});
onBeforeUnmount(() => {
if (bannerSwiper) {
bannerSwiper.destroy()
bannerSwiper = null
bannerSwiper.destroy();
bannerSwiper = null;
}
})
});
function saTrack(record) {
const config = {
eventName: "ClickBanner",
properties: {
page: "首页",
module: "Banner",
position: "查看详情",
title: record.code,
clickTime: new Date().toLocaleString().toString()
}
}
sensors.track(config.eventName, config.properties);
const properties = {
page_name: 'PC首页',
model_name: '轮播图',
button_name: `${record.title}`
};
sensorsTrack(properties);
}
</script>
@@ -237,7 +248,7 @@ function saTrack(record) {
height: 100%;
.banner-block {
background-image: url("../../../assets/img/home/banner.png");
background-image: url('../../../assets/img/home/banner.png');
background-repeat: no-repeat;
background-size: 100% 100%;
width: 100%;
@@ -252,7 +263,7 @@ function saTrack(record) {
left: calc(16%);
font-weight: bold;
font-size: 30px;
color: #1F1E64;
color: #1f1e64;
line-height: 32px;
}
@@ -261,7 +272,7 @@ function saTrack(record) {
top: calc(54%);
left: calc(4.4%);
font-size: 17px;
color: #696C9F;
color: #696c9f;
letter-spacing: 2px;
font-weight: bold;
@@ -269,7 +280,7 @@ function saTrack(record) {
background: #fff;
border: 1px solid rgba(31, 30, 100, 0.55);
font-size: 16px;
color: #1F1E64;
color: #1f1e64;
line-height: 23px;
text-align: left;
font-style: normal;
@@ -457,7 +468,7 @@ function saTrack(record) {
border: none;
display: block;
//opacity: .48;
background-color: #8B97C3;
background-color: #8b97c3;
width: 6px;
height: 6px;
border-radius: 50%;
@@ -469,7 +480,7 @@ function saTrack(record) {
border: none;
display: block;
//opacity: .48;
background-color: #F5F6F9;
background-color: #f5f6f9;
width: 18px;
height: 6px;
border-radius: 10px;

View File

@@ -15,6 +15,7 @@
</template>
<script setup>
import { sensorsTrack } from '@/utils/plugins/sa';
import { ref, inject } from 'vue'
const hoverIndex = ref(-1);
const { model,index } = defineProps({
@@ -28,7 +29,6 @@ const { model,index } = defineProps({
})
const emit = defineEmits(['open'])
const sensors = inject('sensors')
const getText = (model, index) => {
if (hoverIndex.value === index) {
return model.sort === 103 ? "敬请期待" : "去查看";
@@ -38,21 +38,16 @@ const getText = (model, index) => {
function handleModelClick() {
emit('open')
console.log(model);
saTrack(model)
function saTrack(record) {
const config = {
eventName: "IntroduceModel",
properties: {
page: "首页",
module: "模型介绍",
position: record.title,
buttonName: "去查看",
clickTime: new Date().toLocaleString().toString()
}
}
sensors.track(config.eventName, config.properties);
const properties = {
page_name: 'PC首页',
model_name: '调研类型模块',
button_name: `${record.title}`
};
sensorsTrack(properties)
}
}
</script>

View File

@@ -73,6 +73,7 @@ import 'swiper/swiper-bundle.css';
import { useRouter } from 'vue-router';
import { getQueryUserSurvey } from '@/api/home';
import { message } from 'ant-design-vue';
import { sensorsTrack } from '@/utils/plugins/sa';
const router = useRouter();
const emit = defineEmits(['create-survey']);
@@ -124,8 +125,6 @@ const operatingList = [
key: 'particulars'
}]
const sensors = inject('sensors')
const goPre = () => {
pauseCurrentVideo();
if (mySwiper) {
@@ -188,20 +187,13 @@ const toSurveyInfo = async (item, index) => {
}
}
function saTrack(record, index) {
function saTrack(_, index) {
const operates = ["创建", "设计","投放","分析"]
// 操作引导埋点
const config = {
eventName: "OperatingGuide",
properties: {
page: "首页",
module: "操作指引",
position: operates[index]+"问卷",
buttonName: "立即体验",
clickTime: new Date().toLocaleString().toString()
}
}
sensors.track(config.eventName, config.properties);
sensorsTrack({
page_name: 'PC首页',
model_name: '操作指引',
button_name: operates[index]+"问卷"
});
}
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="scene-container ">
<div class="scene-container">
<div class="flex">
<p></p>
<p class="fw-bold fs-24 align--center scene-title">从场景出发满足你的各类调研需求</p>
@@ -7,33 +7,47 @@
</div>
<div class="flex mt20 sceneList">
<div v-for="(scene, index) in sceneList" :key="index" class="sceneItem"
:class="{ 'fix-scene': [0, 1, 2].includes(index) }" @click="createScene(scene, index)">
<div
v-for="(scene, index) in sceneList"
:key="index"
class="sceneItem"
:class="{ 'fix-scene': [0, 1, 2].includes(index) }"
@click="createScene(scene, index)"
>
<div class="flex-start">
<img :src="scene.image" alt="" style="width: 28px;">
<p class="sceneItem-title" :class="{ 'sceneItem-title-color': index === 0 }">{{ scene.title }}</p>
<img :src="scene.image" alt="" style="width: 28px" />
<p class="sceneItem-title" :class="{ 'sceneItem-title-color': index === 0 }">
{{ scene.title }}
</p>
</div>
<p class="sceneItem-desc" :style="{ 'minHeight': index === 0 ? 'auto' : '30px' }">{{ scene.description }}</p>
<p class="sceneItem-desc" :style="{ minHeight: index === 0 ? 'auto' : '30px' }">
{{ scene.description }}
</p>
<div style="text-align: left">
<a-button v-if="index === 0" class="createBtn" type="primary">
立即创建
</a-button>
<a-button v-if="index === 0" class="createBtn" type="primary"> 立即创建 </a-button>
<div v-else>
<a-button class="custom-button toCreate" :class="{
'createG': [0, 1, 2].includes(index),
'create-normal': [11, 13, 15, 16, 17].includes(scene.code)
}" type="text">
<a-button
class="custom-button toCreate"
:class="{
createG: [0, 1, 2].includes(index),
'create-normal': [11, 13, 15, 16, 17].includes(scene.code)
}"
type="text"
>
<span>去创建</span>
<!-- 'normal':[11,13,15,16,17].includes(scene.code)-->
<img src="@/assets/img/home/tob.png" alt=""
v-if="[0, 1, 2].includes(index) || [11, 13, 15, 16, 17].includes(scene.code)" />
<img
src="@/assets/img/home/tob.png"
alt=""
v-if="[0, 1, 2].includes(index) || [11, 13, 15, 16, 17].includes(scene.code)"
/>
<img src="@/assets/img/home/tog.png" alt="" v-else />
</a-button>
</div>
</div>
</div>
</div>
<create ref="createRef" style="width: 1px;height: 0" @update:ai-assistant-visible="getValue">
<create ref="createRef" style="width: 1px; height: 0" @update:ai-assistant-visible="getValue">
</create>
<!-- 概念测试 -->
@@ -45,22 +59,23 @@
<script setup>
import { onMounted, ref, inject } from 'vue';
import create from '@/views/ProjectManage/create/Index.vue'
import create from '@/views/ProjectManage/create/Index.vue';
import { useRouter } from 'vue-router';
import { getSceneListHome } from '@/api/home';
import deep from '@/assets/img/home/deep.png'
import quickImport from '@/assets/img/home/quickImport.png'
import whiteCreate from '@/assets/img/home/whiteCreate.png'
import deep from '@/assets/img/home/deep.png';
import quickImport from '@/assets/img/home/quickImport.png';
import whiteCreate from '@/assets/img/home/whiteCreate.png';
import ConceptTest from '@/views/ProjectManage/create/presets/concept/ConceptTest.vue';
import PackageTest from '@/views/ProjectManage/create/presets/package/PackageTest.vue';
import TasteTest from '@/views/ProjectManage/create/presets/taste/TasteTest.vue';
import { Modal } from 'ant-design-vue';
import { currentMode } from '@/config';
import { sensorsTrack } from '@/utils/plugins/sa';
const router = useRouter()
const router = useRouter();
const conceptTestRef = ref(null);
const tasteTestRef = ref(null)
const packageTestRef = ref(null)
const tasteTestRef = ref(null);
const packageTestRef = ref(null);
// const
const fixedSceneList = [
{
@@ -80,27 +95,26 @@ const fixedSceneList = [
description: '点击创建新的空白问卷,自定义编辑内容',
image: whiteCreate,
value: 3
},
]
const sceneList = ref([])
}
];
const sceneList = ref([]);
// ref声明需与组件ref属性值一致
const createRef = ref()
const sensors = inject('sensors')
const createRef = ref();
onMounted(() => {
getSceneList()
})
getSceneList();
});
const toModels = () => {
router.push({
path: '/market'
});
}
};
const getSceneList = () => {
getSceneListHome().then(res => {
sceneList.value = []
sceneList.value = fixedSceneList.concat(res.data.items)
})
}
getSceneListHome().then((res) => {
sceneList.value = [];
sceneList.value = fixedSceneList.concat(res.data.items);
});
};
/**
* 创建问卷
* @param record
@@ -108,13 +122,13 @@ const getSceneList = () => {
*/
const createScene = (record, index) => {
// 调用神策埋点
saTrack(record, index)
saTrack(record, index);
if ([0, 1, 2].includes(index)) {
if (createRef.value) {
// 检查 record 是否有 value 属性,如果没有则直接使用 record
const sceneData = record.value !== undefined ? record.value : record;
createRef.value.createCustom(sceneData)
createRef.value.createCustom(sceneData);
}
} else if ([22, 36, 37, 38].includes(record.code)) {
return conceptTestRef.value.openModal({
@@ -136,7 +150,7 @@ const createScene = (record, index) => {
});
} else if ([11, 13, 15, 16, 17, 27, 45, 46].includes(record.code)) {
if (createRef.value) {
createRef.value.createNormalSurvey(record)
createRef.value.createNormalSurvey(record);
}
} else {
Modal.confirm({
@@ -166,22 +180,28 @@ const createScene = (record, index) => {
}
});
}
}
};
function saTrack(record,index) {
const config = {
eventName: "SurveyCreate",
properties: {
page: "首页",
module: "创建问卷",
// 排除智能创建、快捷导入、空白创建,其他都是模板创建
position: [0,1,2].includes(index) ? record.title: `模板创建-${record.title}`,
// 第一个默认是智能创建
buttonName: index === 0 ? "立即创建" : "去创建",
clickTime: new Date().toLocaleString().toString()
}
}
sensors.track(config.eventName, config.properties);
function saTrack(record, index) {
// const config = {
// eventName: 'SurveyCreate',
// properties: {
// page: '首页',
// module: '创建问卷',
// // 排除智能创建、快捷导入、空白创建,其他都是模板创建
// position: [0, 1, 2].includes(index) ? record.title : `模板创建-${record.title}`,
// // 第一个默认是智能创建
// buttonName: index === 0 ? '立即创建' : '去创建',
// clickTime: new Date().toLocaleString().toString()
// }
// };
const properties = {
page_name: 'PC首页',
model_name: '调研类型模块',
button_name: `${record.title}`
};
sensorsTrack(properties);
}
/**
@@ -190,21 +210,21 @@ function saTrack(record,index) {
*/
const getValue = (val) => {
// 获取元素并转换为数组
const containers = Array.from(document.getElementsByClassName('el-carousel__container'))
const indicators = Array.from(document.getElementsByClassName('el-carousel__indicators'))
const allElements = [...containers, ...indicators]
const containers = Array.from(document.getElementsByClassName('el-carousel__container'));
const indicators = Array.from(document.getElementsByClassName('el-carousel__indicators'));
const allElements = [...containers, ...indicators];
// allElements.forEach(item => {
// if (item) {
// item.style.zIndex = val ? '-1' : 'unset'
// }
// })
}
};
// 使用 defineExpose 显式暴露方法,使其可以被外部组件通过 ref 访问
defineExpose({
createScene
})
});
</script>
<style scoped lang="scss">
@@ -219,11 +239,10 @@ p {
.scene-title {
margin: 32px 0 26px 4vw;
}
.more {
color: #70B937;
color: #70b937;
cursor: pointer;
}
@@ -245,7 +264,7 @@ p {
min-height: 125px;
margin-right: 10px;
background: #fff;
border: 1px solid #EBEBEB;
border: 1px solid #ebebeb;
}
.sceneItem-title {
@@ -256,23 +275,23 @@ p {
}
.sceneItem-title-color {
background: linear-gradient(135deg, #C352E7, #3962FF);
background: linear-gradient(135deg, #c352e7, #3962ff);
background-clip: text;
color: transparent;
}
.sceneItem-desc {
font-size: 10px;
color: #7F7F7F;
color: #7f7f7f;
text-align: left;
margin-top: 10px;
text-wrap: wrap;
min-height: 30px
min-height: 30px;
}
.fix-scene {
border: 1px solid rgba(98, 93, 248, 0.5);
background-image: url("../../../assets/img/home/sceneBg.png");
background-image: url('../../../assets/img/home/sceneBg.png');
background-size: 100% 100%;
}
@@ -282,7 +301,7 @@ p {
}
.createBtn {
background-image: url("../../../assets/img/home/createbtn.png");
background-image: url('../../../assets/img/home/createbtn.png');
background-size: 100% 100%;
background-color: transparent;
border: none;
@@ -301,7 +320,7 @@ p {
}
.createG {
color: #5562FB !important;
color: #5562fb !important;
}
.create-normal {
@@ -323,7 +342,7 @@ p {
}
::-webkit-scrollbar-thumb {
background: #DFE1E7;
background: #dfe1e7;
/* 滚动条滑块颜色 */
border-radius: 4px;
/* 滑块圆角 */