feat(首页): 添加神策数据埋点

- 在 Header 组件中添加 Banner 点击埋点
- 在 ModelCard 组件中添加 模型介绍点击 埋点
- 在 Operating 组件中添加 操作指引 埋点
- 在 Scene 组件中添加 创建问卷 埋点
- 优化了埋点数据的结构和命名
This commit is contained in:
Huangze
2025-06-18 14:45:12 +08:00
parent 91a3988be7
commit 4cbf5f6a47
9 changed files with 352 additions and 273 deletions

View File

@@ -5,16 +5,8 @@
<div class="link-action-wrapper" @click="clickAction"> <div class="link-action-wrapper" @click="clickAction">
<router-view /> <router-view />
</div> </div>
<a-modal <a-modal centered :width="700" :visible="show" :title="title" :maskClosable="true" :closable="true"
centered wrapClassName="custom-modal" :footer="null">
:width="700"
:visible="show"
:title="title"
:maskClosable="true"
:closable="true"
wrapClassName="custom-modal"
:footer="null"
>
<template #closeIcon> <template #closeIcon>
<i class="iconfont model-close" @click="show = false">&#xe68b;</i> <i class="iconfont model-close" @click="show = false">&#xe68b;</i>
</template> </template>
@@ -26,7 +18,7 @@
<script> <script>
import locale from 'ant-design-vue/es/locale/zh_CN'; import locale from 'ant-design-vue/es/locale/zh_CN';
import 'moment/locale/zh-cn'; import 'moment/locale/zh-cn';
import { watch, onMounted,onUnmounted, ref } from 'vue'; import { watch, onMounted, onUnmounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import Loading from '@/components/layout/loading/Loading.vue'; import Loading from '@/components/layout/loading/Loading.vue';
@@ -56,7 +48,7 @@ export default {
} }
}); });
console.log(route); // console.log(route);
const setToken = () => { const setToken = () => {
if (!!route.query.token) { if (!!route.query.token) {
localStorage.setItem('plantToken', route.query.token); localStorage.setItem('plantToken', route.query.token);
@@ -117,6 +109,7 @@ export default {
* { * {
font-family: inherit; font-family: inherit;
} }
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -139,12 +132,15 @@ div {
.vxe-table--body-wrapper { .vxe-table--body-wrapper {
@extend .scroller; @extend .scroller;
} }
.ant-table-header, .ant-table-header,
.ant-table-hide-scrollbar { .ant-table-hide-scrollbar {
padding-right: 6px !important; padding-right: 6px !important;
overflow-y: hidden !important; overflow-y: hidden !important;
} }
@media screen and (min-width: 600px) { @media screen and (min-width: 600px) {
html, html,
body { body {
& *:not(svg, p, strong, em, p span, label) { & *:not(svg, p, strong, em, p span, label) {
@@ -161,6 +157,7 @@ div {
// } // }
/*<=600的设备*/ /*<=600的设备*/
@media (max-width: 600px) { @media (max-width: 600px) {
html, html,
body { body {
& *:not(p, strong, em, p span, label) { & *:not(p, strong, em, p span, label) {
@@ -168,6 +165,7 @@ div {
} }
} }
} }
strong { strong {
// font-family: "Noto Sans CJK SC Medium", "Source Han Sans CN Medium" !important; // font-family: "Noto Sans CJK SC Medium", "Source Han Sans CN Medium" !important;
font-weight: bolder !important; font-weight: bolder !important;

View File

@@ -78,34 +78,5 @@ installAntDesign(app);
app.config.globalProperties.emitter = emitter; app.config.globalProperties.emitter = emitter;
// 神策数据插件 // 神策数据插件
app.use(sensorsData(), { app.use(sensorsData(), {});
// 单页面配置,默认关闭。开启后自动监听 URL 有变化就会触发 $pageview 事件
is_track_single_page: function () {
return false;
},
scrollmap: {
// Web 视区停留
collect_url: function () {
function isCollectUrl(urls) {
return urls.some((url) => location.href.includes(url));
}
// 需要采集的页面
const urls = ['/ad/', '/share/'];
console.log(`collect_url`, isCollectUrl(urls));
// return isCollectUrl(urls);
return true;
}
},
heatmap: {
//是否开启触达图default 表示开启,自动采集 $WebStay 事件,可以设置 'not_collect' 表示关闭。
//需要 Web JS SDK 版本号大于 1.9.1
scroll_notice_map: 'default',
scroll_delay_time: 4000,
//单位秒,预置属性停留时长 event_duration 的最大值。默认5个小时也就是300分钟18000秒。
scroll_event_duration: 18000,
get_vtrack_config: true
}
});
app.use(store).use(router).mount('#app'); app.use(store).use(router).mount('#app');

View File

@@ -12,7 +12,11 @@ import { jsonpUrl } from '../config.js';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import Creative from './route.creative'; // 创作中心路由 import Creative from './route.creative'; // 创作中心路由
import MarketList from '@/views/TempMarket/components/TempMarketLayout'; import MarketList from '@/views/TempMarket/components/TempMarketLayout';
import { sensors } from '@/utils/plugins/sa';
const sa = {
register: false,
instance: window.sa || null
};
const store = useStore(); const store = useStore();
@@ -617,33 +621,23 @@ const router = createRouter({
routes: constantRoutes routes: constantRoutes
}); });
let pageStayTime = 0;
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
// 离开页面的时候结束记录时间
const duration = Date.now() - pageStayTime;
pageStayTime = 0;
const collectUrl = ['/ad/', '/share/'];
console.log(
`is collect page`,
collectUrl.some((url) => from.path.startsWith(url))
);
// 判断是否离开需要采集的页面
if (true) {
// if (collectUrl.some((url) => from.path.startsWith(url))) {
// alert(`duration: ${duration}`);
sensors.track('pageStayTime', {
duration
});
}
if (to.meta?.title) document.title = to.meta.title;
// token // token
if (!to.meta.shared && !router.options.history.state.back) { if (!to.meta.shared && !router.options.history.state.back) {
await getToken(); await getToken();
} }
// 神策数据埋点
if (!sa.register && localStorage.getItem('plantUserInfo')) {
sa.instance = window.sa;
// 检测是否使用神策的登陆
const userInfo = JSON.parse(localStorage.getItem('plantUserInfo'));
const loginID = `id-${userInfo.id}.login_team_id-${userInfo.login_team_id}`;
sa.instance.login(globalThis.btoa(loginID));
sa.register = true;
}
if (!to.meta.noRedirectLogin) { if (!to.meta.noRedirectLogin) {
if (window.self === window.top) { if (window.self === window.top) {
// window.parent.location.href = 'https://yip-uat.dctest.digitalyili.com/login'; // window.parent.location.href = 'https://yip-uat.dctest.digitalyili.com/login';
@@ -662,10 +656,10 @@ router.beforeEach(async (to, from, next) => {
next(); next();
}); });
router.afterEach((to, from) => { // router.afterEach((to, from) => {
// 页面导航结束的时候开始记录时间 // // 页面导航结束的时候开始记录时间
pageStayTime = Date.now(); // pageStayTime = Date.now();
}); // });
const getToken = async () => { const getToken = async () => {
try { try {

View File

@@ -21,13 +21,17 @@ export function sensorsData() {
// 注册页面公共属性 // 注册页面公共属性
sensors.registerPage({ sensors.registerPage({
platform: 'h5', platform: 'pc',
// 产品名称
production_name: 'ylst',
current_url: location.href, current_url: location.href,
referrer: document.referrer referrer: document.referrer
}); });
// 提供全局注入的 sensors 实例 // 提供全局注入的 sensors 实例
app.provide('sensors', sensors); app.provide('sensors', sensors);
// 注册到window中
globalThis.sa = sensors;
// 注册 saTrack 自定义指令 // 注册 saTrack 自定义指令
registerDirective(app); registerDirective(app);
@@ -40,17 +44,26 @@ export function sensorsData() {
* @param {App} app - Vue 应用实例 * @param {App} app - Vue 应用实例
*/ */
function registerDirective(app) { function registerDirective(app) {
function bindTrackListener(binding) {
return () => {
const { arg: eventName, value: properties } = binding;
if (eventName) {
sensors.track(eventName, properties);
console.warn(properties);
} else {
console.warn('[sensorsData] 事件名未提供');
}
};
}
app.directive('saTrack', { app.directive('saTrack', {
mounted(el, binding) { mounted(el, binding) {
el.addEventListener('click', () => { el.addEventListener('click', bindTrackListener(binding));
const { arg: eventName, value: properties } = binding; },
unmounted(el, binding) {
if (eventName) { // 清除绑定的事件
sensors.track(eventName, properties); el.removeEventListener('click', bindTrackListener(binding));
} else {
console.warn('[sensorsData] 事件名未提供');
}
});
} }
}); });
} }

View File

@@ -1,18 +1,18 @@
<template> <template>
<layout> <layout>
<template v-slot:top> <template v-slot:top>
<div class="left" > <div class="left">
<i class="iconfont icon-xiangzuo-moren" @click="back"></i> <i class="iconfont icon-xiangzuo-moren" @click="back"></i>
{{ bannerInfo.title }} {{ bannerInfo.title }}
</div> </div>
</template> </template>
<template v-slot:right> <template v-slot:right>
<div class="detail"> <div class="detail">
<p class="fw-900">{{bannerInfo.title}}</p> <p class="fw-900">{{ bannerInfo.title }}</p>
<p class="fs-14 time">伊调研 | {{bannerInfo.publish_time}}</p> <p class="fs-14 time">伊调研 | {{ bannerInfo.publish_time }}</p>
<!--文字--> <!--文字-->
<p v-if="bannerInfo.type === 0"> <p v-if="bannerInfo.type === 0">
<p v-html="bannerInfo.characters"></p> <p v-html="bannerInfo.characters"></p>
</p> </p>
<!--图片--> <!--图片-->
<img v-if="bannerInfo.type === 1" :src="bannerInfo.file_address" alt="" style="width: 100%;"> <img v-if="bannerInfo.type === 1" :src="bannerInfo.file_address" alt="" style="width: 100%;">
@@ -25,14 +25,11 @@
</div> </div>
<!-- <video v-if="bannerInfo.type === 2" :src="bannerInfo.file_address" style="width: 100%;"></video>--> <!-- <video v-if="bannerInfo.type === 2" :src="bannerInfo.file_address" style="width: 100%;"></video>-->
<div v-if="bannerInfo.is_display_button" class="jump"> <div class="jump">
<a-button <a-button type="primary" class="custom-button mr-12" @click="toJump">
type="primary" <span>{{ bannerInfo.button_name }}12312312</span>
class="custom-button mr-12"
@click="toJump">
<span>{{ bannerInfo.button_name }}</span>
</a-button> </a-button>
</div> </div>
</div> </div>
@@ -43,10 +40,9 @@
<script setup> <script setup>
import Layout from '@/views/ProjectManage/components/Layout.vue'; import Layout from '@/views/ProjectManage/components/Layout.vue';
import {onMounted,ref} from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter,useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { queryBanner } from '@/api/home'; import { queryBanner } from '@/api/home';
const router = useRouter();
const route = useRoute(); const route = useRoute();
const bannerInfo = ref({}); const bannerInfo = ref({});
@@ -56,12 +52,12 @@ onMounted(() => {
}); });
const getBannerDetail = () => { const getBannerDetail = () => {
console.log(route.query); console.log(route.query);
queryBanner({ code: route.query.code}).then(res=>{ queryBanner({ code: route.query.code }).then(res => {
console.log(res.data); console.log(res.data);
bannerInfo.value = res.data bannerInfo.value = res.data
}) })
}; };
const toJump = ()=>{ const toJump = () => {
// router.push({ // router.push({
// path: bannerInfo.value.url // path: bannerInfo.value.url
// }) // })
@@ -73,12 +69,14 @@ const back = () => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.detail{ .detail {
padding: 10px 20%; padding: 10px 20%;
.time{
.time {
color: #7F7F7F; color: #7F7F7F;
} }
.jump{
.jump {
text-align: center; text-align: center;
margin-top: 20px; margin-top: 20px;
} }

View File

@@ -55,7 +55,7 @@
</template> </template>
<script setup> <script setup>
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { onBeforeUnmount, onMounted, ref, inject } from 'vue';
import Swiper, { Autoplay } from 'swiper'; import Swiper, { Autoplay } from 'swiper';
import 'swiper/swiper-bundle.css'; import 'swiper/swiper-bundle.css';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -85,6 +85,7 @@ const surveyData = ref({
}) })
const bannerList = ref([]) const bannerList = ref([])
const sensors = inject('sensors')
// 获取数据 // 获取数据
const getBannerData = async () => { const getBannerData = async () => {
const res = await getBannerList() const res = await getBannerList()
@@ -168,6 +169,9 @@ const goToSlide = (index) => {
} }
// 方法 // 方法
const toBannerDetail = (item) => { const toBannerDetail = (item) => {
// 神策埋点
saTrack(item)
router.push({ router.push({
path: '/home/bannerDetail', path: '/home/bannerDetail',
query: { query: {
@@ -189,6 +193,21 @@ onBeforeUnmount(() => {
bannerSwiper = null 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);
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,17 +1,13 @@
<template> <template>
<div class="model-item" <div class="model-item" @mouseover="hoverIndex = index" @mouseleave="hoverIndex = -1"
@mouseover="hoverIndex = index" :style="{ backgroundImage: `url(${model.backgroundImage})` }">
@mouseleave="hoverIndex = -1"
:style="{ backgroundImage: `url(${model.backgroundImage})` }">
<div class="flex align-items-center"> <div class="flex align-items-center">
<img :src="model.image" alt="" style="height: 23px; margin-right: 8px" /> <img :src="model.image" alt="" style="height: 23px; margin-right: 8px" />
<p class="model-item-title">{{ model.title }}</p> <p class="model-item-title">{{ model.title }}</p>
</div> </div>
<p class="model-item-desc">{{ model.description }}</p> <p class="model-item-desc">{{ model.description }}</p>
<div <div class="view-link" @click="handleModelClick">
class="view-link" <span>{{ getText(model, index) }}</span>
@click="$emit('open')">
<span>{{ getText(model,index) }}</span>
<img v-if="hoverIndex === index && index !== 3" src="@/assets/img/home/tob.png" alt=""> <img v-if="hoverIndex === index && index !== 3" src="@/assets/img/home/tob.png" alt="">
<img v-else src="@/assets/img/home/tow.png" alt=""> <img v-else src="@/assets/img/home/tow.png" alt="">
</div> </div>
@@ -19,9 +15,9 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, inject } from 'vue'
const hoverIndex = ref(-1); const hoverIndex = ref(-1);
defineProps({ const { model,index } = defineProps({
model: { model: {
type: Object, type: Object,
required: true required: true
@@ -30,12 +26,35 @@ defineProps({
type: Number, type: Number,
} }
}) })
const getText = (model,index) => {
const emit = defineEmits(['open'])
const sensors = inject('sensors')
const getText = (model, index) => {
if (hoverIndex.value === index) { if (hoverIndex.value === index) {
return model.sort === 103 ? "敬请期待" : "去查看"; return model.sort === 103 ? "敬请期待" : "去查看";
} }
return "去查看"; return "去查看";
} }
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);
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -99,6 +118,7 @@ p {
// color: #3976D7; // color: #3976D7;
//} //}
} }
.model-item:hover { .model-item:hover {
.view-link { .view-link {
background: #fff; background: #fff;
@@ -113,13 +133,14 @@ p {
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
& span { & span {
color: #3976D7; color: #3976D7;
} }
} }
.icon{ .icon {
fill:#fff; fill: #fff;
color:#fff; color: #fff;
} }
</style> </style>

View File

@@ -2,58 +2,46 @@
<div class="operating-container"> <div class="operating-container">
<div class="top"> <div class="top">
<p class="fw-bold fs-24 title">简单4步轻松完成你的调研</p> <p class="fw-bold fs-24 title">简单4步轻松完成你的调研</p>
<!-- <p class="desc">创建编辑投放分析让洞察更简单</p>--> <!-- <p class="desc">创建编辑投放分析让洞察更简单</p>-->
</div> </div>
<div style="height: 80%"> <div style="height: 80%">
<div class="swiper-container" ref="swiperContainer"> <div class="swiper-container" ref="swiperContainer">
<div class="swiper-wrapper"> <div class="swiper-wrapper">
<div <div v-for="(item, index) in operatingList" :key="index" class="swiper-slide">
v-for="(item, index) in operatingList"
:key="index"
class="swiper-slide"
>
<div class="flex" style="justify-content: center"> <div class="flex" style="justify-content: center">
<div style="margin-right: 9%"> <div style="margin-right: 9%">
<div class="flex-start operating-item" > <div class="flex-start operating-item">
<p class="progress" :style="{'color':item.btnColor}"> <p class="progress" :style="{ 'color': item.btnColor }">
<span class="fs-36"> <span class="fs-36">
{{index+1}} {{ index + 1 }}
</span> </span>
<img src="@/assets/img/home/xiegang.png" alt=""> <img src="@/assets/img/home/xiegang.png" alt="">
<span> <span>
{{operatingList.length}} {{ operatingList.length }}
</span> </span>
</p> </p>
<div class="info"> <div class="info">
<p>{{item.title}}</p> <p>{{ item.title }}</p>
<p>{{item.desc}}</p> <p>{{ item.desc }}</p>
</div> </div>
</div> </div>
<ul class="desc-item"> <ul class="desc-item">
<li class="flex" v-for="(tips,index) in item.descList" :key="index"> <li class="flex" v-for="(tips, index) in item.descList" :key="index">
<p class="block"></p> <p class="block"></p>
{{tips}} {{ tips }}
<span class="fs-12 tag" v-if="index === 0 && item.isShowTag"> <span class="fs-12 tag" v-if="index === 0 && item.isShowTag">
新功能 新功能
</span> </span>
</li> </li>
</ul> </ul>
<a-button type="primary" @click="toSurveyInfo(item,index)"> <a-button type="primary" @click="toSurveyInfo(item, index)">
立即体验 立即体验
<img src="@/assets/img/home/tow.png" alt="" <img src="@/assets/img/home/tow.png" alt="" style="margin-left: 5px;margin-bottom: 2px">
style="margin-left: 5px;margin-bottom: 2px">
</a-button> </a-button>
</div> </div>
<div> <div>
<video <video ref="videoPlayer" class="video-player" width="820" height="420" autoplay muted playsinline
ref="videoPlayer" style="width: 100%;">
class="video-player"
width="820"
height="420"
autoplay
muted
playsinline
style="width: 100%;">
<source :src="getVideo(index)" type="video/mp4"> <source :src="getVideo(index)" type="video/mp4">
Your browser does not support the video tag. Your browser does not support the video tag.
@@ -79,7 +67,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import {ref, onMounted, onBeforeUnmount} from 'vue' import { ref, onMounted, onBeforeUnmount, inject } from 'vue'
import Swiper from 'swiper'; import Swiper from 'swiper';
import 'swiper/swiper-bundle.css'; import 'swiper/swiper-bundle.css';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -100,42 +88,44 @@ const nextImg = ref(null);
let mySwiper = null; let mySwiper = null;
const operatingList = [ const operatingList = [
{ {
title: '多种方式', title: '多种方式',
desc:'快速创建问卷', desc: '快速创建问卷',
descList:[ descList: [
'一句话AI生成问卷高效智能','20+专业场景模板,快速创建','WORD模板上传一键导入','空白创建编辑,灵活简单' '一句话AI生成问卷高效智能', '20+专业场景模板,快速创建', 'WORD模板上传一键导入', '空白创建编辑,灵活简单'
], ],
btnColor:'green', btnColor: 'green',
isShowTag:true isShowTag: true
}, },
{ {
title: '编辑质检', title: '编辑质检',
desc:'设计快速搞定', desc: '设计快速搞定',
descList:[ descList: [
'30+专业题型及模型题组,多种逻辑适配各类场景','AI多维度问卷质检快速识别问题提供建议', '30+专业题型及模型题组,多种逻辑适配各类场景', 'AI多维度问卷质检快速识别问题提供建议',
'多种主题、九宫格抽奖, 自定义问卷风格', '多人协作编辑,便捷协同快捷高效' '多种主题、九宫格抽奖, 自定义问卷风格', '多人协作编辑,便捷协同快捷高效'
], ],
btnColor:'green', btnColor: 'green',
key:'design' key: 'design'
},{ }, {
title: '精准触达', title: '精准触达',
desc:'投放轻松高效', desc: '投放轻松高效',
descList:[ descList: [
'多元样本资源精准圈选,在线一站式投放','标准链接及二维码,问卷快捷私发群发','自定义链接参数,记录多渠道样本来源','回收进度、配额进度在线实时追踪' '多元样本资源精准圈选,在线一站式投放', '标准链接及二维码,问卷快捷私发群发', '自定义链接参数,记录多渠道样本来源', '回收进度、配额进度在线实时追踪'
], ],
btnColor:'green', btnColor: 'green',
key:'accurate' key: 'accurate'
},{ }, {
title: '数据分析', title: '数据分析',
desc:'多种维度洞察', desc: '多种维度洞察',
descList:[ descList: [
'AI数据清洗无效数据在线剔除','AI智能总结调研结论一键洞察','专业模型看板,直达分析结论','个性化交叉分析,多维数据挖掘' 'AI数据清洗无效数据在线剔除', 'AI智能总结调研结论一键洞察', '专业模型看板,直达分析结论', '个性化交叉分析,多维数据挖掘'
], ],
btnColor:'green', btnColor: 'green',
key:'particulars' key: 'particulars'
}] }]
const sensors = inject('sensors')
const goPre = () => { const goPre = () => {
pauseCurrentVideo(); pauseCurrentVideo();
if (mySwiper) { if (mySwiper) {
@@ -151,7 +141,7 @@ const goNext = () => {
}; };
const pauseCurrentVideo = () => { const pauseCurrentVideo = () => {
if (mySwiper.realIndex === 0 || mySwiper.realIndex === 3){ if (mySwiper.realIndex === 0 || mySwiper.realIndex === 3) {
return return
} }
const activeIndex = mySwiper.realIndex; const activeIndex = mySwiper.realIndex;
@@ -165,15 +155,18 @@ const pauseCurrentVideo = () => {
} }
}; };
const toSurveyInfo = async (item,index) => { const toSurveyInfo = async (item, index) => {
// 调用神策埋点
saTrack(item, index)
let res = await getQueryUserSurvey() let res = await getQueryUserSurvey()
const {sn} = res.data const { sn } = res.data
const urlJson = { const urlJson = {
design: `/survey/planet/design?sn=${sn}`, design: `/survey/planet/design?sn=${sn}`,
accurate: `/survey/publish/accurate?sn=${sn}`, accurate: `/survey/publish/accurate?sn=${sn}`,
particulars: `/survey/analyse/diagram?sn=${sn}`, particulars: `/survey/analyse/diagram?sn=${sn}`,
}; };
if (index === 0){ if (index === 0) {
// 调用智能创建 // 调用智能创建
const sceneObj = { const sceneObj = {
title: '智能创建', title: '智能创建',
@@ -182,29 +175,45 @@ const toSurveyInfo = async (item,index) => {
}; };
// 通过事件向父组件发送信号,让父组件调用 Scene 组件的 createScene 方法 // 通过事件向父组件发送信号,让父组件调用 Scene 组件的 createScene 方法
emit('create-survey', sceneObj, 0); emit('create-survey', sceneObj, 0);
}else{ } else {
if (res.data.flag){ if (res.data.flag) {
await router.push({ await router.push({
path: urlJson[item.key], path: urlJson[item.key],
query: { query: {
sn, sn,
} }
}); });
}else{ } else {
message.warning('您还没有问卷,快去创建一个吧~'); message.warning('您还没有问卷,快去创建一个吧~');
} }
} }
function saTrack(record, 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);
}
} }
const getVideo = (index) => { const getVideo = (index) => {
return require(`@/assets/img/home/video${index+1}.mp4`) return require(`@/assets/img/home/video${index + 1}.mp4`)
} }
const addVideoEndedListener =()=> { const addVideoEndedListener = () => {
const videos = document.querySelectorAll('.video-player'); const videos = document.querySelectorAll('.video-player');
videos.forEach((video, index) => { videos.forEach((video, index) => {
video.addEventListener('ended', () => { video.addEventListener('ended', () => {
console.log('video已播完'+index); console.log('video已播完' + index);
if (index < operatingList.length - 1) { if (index < operatingList.length - 1) {
setTimeout(() => { setTimeout(() => {
mySwiper.slideTo(index + 1); // 自动滑动到下一页 mySwiper.slideTo(index + 1); // 自动滑动到下一页
@@ -233,7 +242,7 @@ const updateButtonImages = (currentIndex) => {
} else if (currentIndex === 3) { } else if (currentIndex === 3) {
nextImg.value.src = require('@/assets/img/home/arrow-dis.png'); nextImg.value.src = require('@/assets/img/home/arrow-dis.png');
nextImgElement.style.transform = 'rotate(180deg)'; nextImgElement.style.transform = 'rotate(180deg)';
}else{ } else {
// 其他情况恢复默认图片 // 其他情况恢复默认图片
prevImg.value.src = require('@/assets/img/home/arrow-act.png'); prevImg.value.src = require('@/assets/img/home/arrow-act.png');
nextImg.value.src = require('@/assets/img/home/arrow-act.png'); nextImg.value.src = require('@/assets/img/home/arrow-act.png');
@@ -305,79 +314,96 @@ onBeforeUnmount(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
p{ p {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.fs-36{
.fs-36 {
font-size: 36px; font-size: 36px;
} }
.fs-34{
.fs-34 {
font-size: 34px; font-size: 34px;
} }
li{
li {
list-style-type: none; list-style-type: none;
} }
.operating-container{
.operating-container {
margin-top: 20px; margin-top: 20px;
min-height: 600px; min-height: 600px;
height: 600px; height: 600px;
.top{
.top {
margin-bottom: 40px; margin-bottom: 40px;
} }
&>div:first-child { &>div:first-child {
text-align: center; text-align: center;
} }
.title{
.title {
margin-bottom: 5px; margin-bottom: 5px;
} }
.desc{
.desc {
color: #7F7F81; color: #7F7F81;
} }
.operating-item{
.progress{ .operating-item {
.progress {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
letter-spacing: 1px; letter-spacing: 1px;
& :nth-child(1){
& :nth-child(1) {
color: #70B937; color: #70B937;
font-size: 45px; font-size: 45px;
font-weight: 400; font-weight: 400;
} }
& :nth-child(2){
height: 33px; & :nth-child(2) {
height: 33px;
} }
& :nth-child(3){
& :nth-child(3) {
color: #000; color: #000;
font-size: 24px; font-size: 24px;
font-weight: 500; font-weight: 500;
} }
} }
.info{
.info {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
margin-left: 20px; margin-left: 20px;
white-space: nowrap; white-space: nowrap;
} }
} }
.desc-item{
.desc-item {
font-weight: bold; font-weight: bold;
font-size: 12px; font-size: 12px;
color: #000; color: #000;
margin-left: 5%; margin-left: 5%;
margin-bottom: 40px; margin-bottom: 40px;
width: max-content; width: max-content;
li{
li {
margin: 20px 0; margin: 20px 0;
justify-content: flex-start; justify-content: flex-start;
.block{
.block {
width: 8px; width: 8px;
height: 8px; height: 8px;
background-color: #000; background-color: #000;
margin-right: 10px; margin-right: 10px;
} }
.tag{
.tag {
font-weight: 500; font-weight: 500;
font-size: 13px; font-size: 13px;
color: #2F5EF2; color: #2F5EF2;
@@ -390,16 +416,19 @@ li{
} }
} }
} }
:deep(.el-carousel__arrow--right){
:deep(.el-carousel__arrow--right) {
border: 1px solid #C1C1C2; border: 1px solid #C1C1C2;
background: transparent; background: transparent;
color: #C1C1C2; color: #C1C1C2;
} }
:deep(.el-carousel__arrow--left){
:deep(.el-carousel__arrow--left) {
border: 1px solid #C1C1C2; border: 1px solid #C1C1C2;
background: transparent; background: transparent;
color: #C1C1C2; color: #C1C1C2;
} }
:deep(.el-carousel__container) { :deep(.el-carousel__container) {
height: 100%; height: 100%;
} }
@@ -448,15 +477,15 @@ li{
right: 0; right: 0;
} }
video{ video {
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 8px; border-radius: 8px;
object-fit: fill; object-fit: fill;
} }
//.video-player { //.video-player {
// //width: 100%; // //width: 100%;
// //height: auto; /* 自动计算高度 */ // //height: auto; /* 自动计算高度 */
// //max-height: 100%; /* 防止视频超出容器 */ // //max-height: 100%; /* 防止视频超出容器 */
// border-radius: 8px; // border-radius: 8px;
//} //}</style>
</style>

View File

@@ -2,47 +2,39 @@
<div class="scene-container "> <div class="scene-container ">
<div class="flex"> <div class="flex">
<p></p> <p></p>
<p class="fw-bold fs-24 align--center scene-title" >从场景出发满足你的各类调研需求</p> <p class="fw-bold fs-24 align--center scene-title">从场景出发满足你的各类调研需求</p>
<p class="more" @click="toModels">更多模板></p> <p class="more" @click="toModels">更多模板></p>
</div> </div>
<div class="flex mt20 sceneList"> <div class="flex mt20 sceneList">
<div v-for="(scene,index) in sceneList" <div v-for="(scene, index) in sceneList" :key="index" class="sceneItem"
:key="index" :class="{ 'fix-scene': [0, 1, 2].includes(index) }" @click="createScene(scene, index)">
class="sceneItem"
:class="{'fix-scene':[0,1,2].includes(index)}"
@click="createScene(scene,index)">
<div class="flex-start"> <div class="flex-start">
<img :src="scene.image" alt="" style="width: 28px;"> <img :src="scene.image" alt="" style="width: 28px;">
<p class="sceneItem-title" :class="{'sceneItem-title-color':index === 0}">{{scene.title}}</p> <p class="sceneItem-title" :class="{ 'sceneItem-title-color': index === 0 }">{{ scene.title }}</p>
</div> </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"> <div style="text-align: left">
<a-button <a-button v-if="index === 0" class="createBtn" type="primary">
v-if="index === 0 "
class="createBtn"
type="primary"
>
立即创建 立即创建
</a-button> </a-button>
<div v-else> <div v-else>
<a-button class="custom-button toCreate" <a-button class="custom-button toCreate" :class="{
:class="{'createG':[0,1,2].includes(index), 'createG': [0, 1, 2].includes(index),
'create-normal':[11,13,15,16,17].includes(scene.code)}" type="text"> 'create-normal': [11, 13, 15, 16, 17].includes(scene.code)
}" type="text">
<span>去创建</span> <span>去创建</span>
<!-- 'normal':[11,13,15,16,17].includes(scene.code)--> <!-- 'normal':[11,13,15,16,17].includes(scene.code)-->
<img src="@/assets/img/home/tob.png" alt="" <img src="@/assets/img/home/tob.png" alt=""
v-if="[0,1,2].includes(index) || [11,13,15,16,17].includes(scene.code)"/> 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/> <img src="@/assets/img/home/tog.png" alt="" v-else />
</a-button> </a-button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<create ref="createRef" <create ref="createRef" style="width: 1px;height: 0" @update:ai-assistant-visible="getValue">
style="width: 1px;height: 0" </create>
@update:ai-assistant-visible="getValue">
</create>
<!-- 概念测试 --> <!-- 概念测试 -->
<ConceptTest ref="conceptTestRef" /> <ConceptTest ref="conceptTestRef" />
@@ -52,9 +44,9 @@
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue'; 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 { useRouter } from 'vue-router';
import { getSceneListHome } from '@/api/home'; import { getSceneListHome } from '@/api/home';
import deep from '@/assets/img/home/deep.png' import deep from '@/assets/img/home/deep.png'
import quickImport from '@/assets/img/home/quickImport.png' import quickImport from '@/assets/img/home/quickImport.png'
@@ -73,19 +65,19 @@ const packageTestRef = ref(null)
const fixedSceneList = [ const fixedSceneList = [
{ {
title: '智能创建', title: '智能创建',
description:'AI随行调研随心', description: 'AI随行调研随心',
image:deep, image: deep,
value: 1 value: 1
}, },
{ {
title: '快捷导入', title: '快捷导入',
description:'点击上传Word文档批量导入题目一键生成问卷', description: '点击上传Word文档批量导入题目一键生成问卷',
image:quickImport, image: quickImport,
value: 2 value: 2
}, },
{ {
title: '空白创建', title: '空白创建',
description:'点击创建新的空白问卷,自定义编辑内容', description: '点击创建新的空白问卷,自定义编辑内容',
image: whiteCreate, image: whiteCreate,
value: 3 value: 3
}, },
@@ -93,16 +85,18 @@ const fixedSceneList = [
const sceneList = ref([]) const sceneList = ref([])
// ref声明需与组件ref属性值一致 // ref声明需与组件ref属性值一致
const createRef = ref() const createRef = ref()
const sensors = inject('sensors')
onMounted(() => { onMounted(() => {
getSceneList() getSceneList()
}) })
const toModels = ()=>{ const toModels = () => {
router.push({ router.push({
path: '/market' path: '/market'
}); });
} }
const getSceneList=()=>{ const getSceneList = () => {
getSceneListHome().then(res=>{ getSceneListHome().then(res => {
sceneList.value = [] sceneList.value = []
sceneList.value = fixedSceneList.concat(res.data.items) sceneList.value = fixedSceneList.concat(res.data.items)
}) })
@@ -112,14 +106,17 @@ const getSceneList=()=>{
* @param record * @param record
* @param index * @param index
*/ */
const createScene = (record,index)=>{ const createScene = (record, index) => {
if ([0, 1, 2].includes(index)){ // 调用神策埋点
saTrack(record, index)
if ([0, 1, 2].includes(index)) {
if (createRef.value) { if (createRef.value) {
// 检查 record 是否有 value 属性,如果没有则直接使用 record // 检查 record 是否有 value 属性,如果没有则直接使用 record
const sceneData = record.value !== undefined ? 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)) { } else if ([22, 36, 37, 38].includes(record.code)) {
return conceptTestRef.value.openModal({ return conceptTestRef.value.openModal({
sn: record.sn, sn: record.sn,
snList: record.other?.split(',') || [], snList: record.other?.split(',') || [],
@@ -137,20 +134,20 @@ const createScene = (record,index)=>{
snList: record.other?.split(',') || [], snList: record.other?.split(',') || [],
sceneCode: '24' sceneCode: '24'
}); });
}else if([11, 13, 15, 16, 17, 27, 45, 46].includes(record.code)){ } else if ([11, 13, 15, 16, 17, 27, 45, 46].includes(record.code)) {
if (createRef.value) { if (createRef.value) {
createRef.value.createNormalSurvey(record) createRef.value.createNormalSurvey(record)
} }
}else { } else {
Modal.confirm({ Modal.confirm({
title: () => '创建问卷', title: () => '创建问卷',
content: () => content: () =>
'需要在敏捷共创模块【共创管理-测试活动】页面选择待测试创意后再创建问卷,点击“确定”将自动跳转。', '需要在敏捷共创模块【共创管理-测试活动】页面选择待测试创意后再创建问卷,点击“确定”将自动跳转。',
onOk() { onOk() {
// 判断环境 // 判断环境
const iframeUrl = currentMode === 'prod' const iframeUrl = currentMode === 'prod'
? 'https://yip.digitalyili.com/iframe/test' ? 'https://yip.digitalyili.com/iframe/test'
: 'https://yip-uat.dctest.digitalyili.com/iframe/test'; : 'https://yip-uat.dctest.digitalyili.com/iframe/test';
console.log(currentMode, iframeUrl); console.log(currentMode, iframeUrl);
// 跳转到产品测试模块 // 跳转到产品测试模块
// top.open(iframeUrl, '_self') // top.open(iframeUrl, '_self')
@@ -170,6 +167,23 @@ 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);
}
/** /**
* 轮播图设置显隐 * 轮播图设置显隐
* @param val * @param val
@@ -194,22 +208,26 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
p{ p {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.scene-container{
.scene-container {
text-align: center; text-align: center;
} }
.scene-title{
.scene-title {
margin: 32px 0 26px 4vw; margin: 32px 0 26px 4vw;
} }
.more{
.more {
color: #70B937; color: #70B937;
cursor: pointer; cursor: pointer;
} }
.sceneList{
.sceneList {
overflow-y: hidden; overflow-y: hidden;
//width: calc(100vw - 40px); //width: calc(100vw - 40px);
overflow-x: auto; overflow-x: auto;
@@ -217,7 +235,8 @@ p{
padding-bottom: 15px; padding-bottom: 15px;
cursor: pointer; cursor: pointer;
} }
.sceneItem{
.sceneItem {
padding: 12px 12px 10px 12px; padding: 12px 12px 10px 12px;
border-radius: 7px; border-radius: 7px;
min-width: 190px; min-width: 190px;
@@ -228,18 +247,21 @@ p{
background: #fff; background: #fff;
border: 1px solid #EBEBEB; border: 1px solid #EBEBEB;
} }
.sceneItem-title{
.sceneItem-title {
font-weight: bold; font-weight: bold;
font-size: 12px; font-size: 12px;
color: #000000; color: #000000;
margin-left: 10px; margin-left: 10px;
} }
.sceneItem-title-color{
.sceneItem-title-color {
background: linear-gradient(135deg, #C352E7, #3962FF); background: linear-gradient(135deg, #C352E7, #3962FF);
background-clip: text; background-clip: text;
color: transparent; color: transparent;
} }
.sceneItem-desc{
.sceneItem-desc {
font-size: 10px; font-size: 10px;
color: #7F7F7F; color: #7F7F7F;
text-align: left; text-align: left;
@@ -247,16 +269,19 @@ p{
text-wrap: wrap; text-wrap: wrap;
min-height: 30px min-height: 30px
} }
.fix-scene{
border: 1px solid rgba(98,93,248,0.5); .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%; background-size: 100% 100%;
} }
.normal{
color:#ccc; .normal {
border: 1px solid red!important; color: #ccc;
border: 1px solid red !important;
} }
.createBtn{
.createBtn {
background-image: url("../../../assets/img/home/createbtn.png"); background-image: url("../../../assets/img/home/createbtn.png");
background-size: 100% 100%; background-size: 100% 100%;
background-color: transparent; background-color: transparent;
@@ -265,38 +290,49 @@ p{
text-align: left; text-align: left;
margin-top: 10px; margin-top: 10px;
} }
.toCreate{
.toCreate {
padding-left: 0; padding-left: 0;
img{
img {
margin-left: 5px; margin-left: 5px;
margin-bottom: 2px; margin-bottom: 2px;
} }
} }
.createG{
color: #5562FB!important; .createG {
color: #5562FB !important;
} }
.create-normal{
color: #3171f3!important; .create-normal {
color: #3171f3 !important;
} }
:deep(.ant-btn > span) { :deep(.ant-btn > span) {
font-size: 11px; font-size: 11px;
} }
/* 强制显示滚动条WebKit 内核浏览器,如 Chrome/Safari */ /* 强制显示滚动条WebKit 内核浏览器,如 Chrome/Safari */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; /* 纵向滚动条宽度 */ width: 8px;
height: 6px; /* 向滚动条度 */ /* 向滚动条度 */
height: 6px;
/* 横向滚动条高度 */
border: 1px solid red; border: 1px solid red;
margin-top: 20px; margin-top: 20px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #DFE1E7; /* 滚动条滑块颜色 */ background: #DFE1E7;
border-radius: 4px; /* 滑块圆角 */ /* 滚动条滑块颜色 */
border-radius: 4px;
/* 滑块圆角 */
} }
/* 滚动条轨道 */ /* 滚动条轨道 */
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: #fff; /* 轨道颜色 */ background: #fff;
/* 轨道颜色 */
//border: 1px solid #D8D8D8; //border: 1px solid #D8D8D8;
} }
</style> </style>