hookehuyr

feat(埋点): 添加登录页面的埋点功能

在登录页面添加用户行为埋点跟踪,包括页面浏览、协议链接点击、确认按钮点击、验证码发送和登录按钮点击事件
新增useTracking composable用于统一处理埋点逻辑
1 +/*
2 + * @Date: 2025-12-24 13:50:03
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-12-24 14:19:52
5 + * @FilePath: /mlaj/src/composables/useTracking.js
6 + * @Description: 埋点 Composable, 模拟埋点接口请求
7 + */
8 +/**
9 + * 模拟埋点接口请求
10 + * @param {Object} data 埋点数据
11 + * @returns {Promise}
12 + */
13 +const mockTrackRequest = (data) => {
14 + return new Promise((resolve) => {
15 + // 模拟网络延迟
16 + setTimeout(() => {
17 + resolve({ code: 200, message: 'success' })
18 + }, 300)
19 + })
20 +}
21 +
22 +/**
23 + * 埋点 Composable
24 + * @returns {Object} 包含埋点方法的对象
25 + */
26 +export function useTracking() {
27 + /**
28 + * 上报埋点事件
29 + * @param {string} eventType - 事件类型 (如 'page_view', 'click', 'custom', 'error')
30 + * @param {string} actionName - 动作名称 (如 'login_btn_click', 'banner_view')
31 + * @param {Object} [extraData={}] - 额外参数 (业务相关的数据)
32 + */
33 + const trackEvent = async (eventType, actionName, extraData = {}) => {
34 + // 获取当前页面路径 (简单获取,如果在组件setup中使用window.location)
35 + const pagePath = window.location.pathname + window.location.hash
36 +
37 + const payload = {
38 + event_type: eventType,
39 + action_name: actionName,
40 + page_path: pagePath,
41 + timestamp: Date.now(),
42 + extra_data: extraData,
43 + // 这里可以扩展更多公共参数,如:
44 + // device_id: '...',
45 + // user_id: '...',
46 + // platform: 'h5'
47 + }
48 +
49 + try {
50 + await mockTrackRequest(payload)
51 + } catch (error) {
52 + console.error('埋点上报失败:', error)
53 + }
54 + }
55 +
56 + /**
57 + * 快捷方法:页面浏览埋点
58 + * @param {string} [pageName] - 页面名称
59 + * @param {Object} [extraData] - 额外参数
60 + */
61 + const trackPageView = (pageName, extraData = {}) => {
62 + trackEvent('page_view', 'view', {
63 + page_name: pageName,
64 + ...extraData
65 + })
66 + }
67 +
68 + /**
69 + * 快捷方法:点击事件埋点
70 + * @param {string} elementName - 元素名称/动作名称
71 + * @param {Object} [extraData] - 额外参数
72 + */
73 + const trackClick = (elementName, extraData = {}) => {
74 + trackEvent('click', elementName, extraData)
75 + }
76 +
77 + return {
78 + trackEvent,
79 + trackPageView,
80 + trackClick
81 + }
82 +}
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
50 点击“下一步”,即表示您同意我们的 50 点击“下一步”,即表示您同意我们的
51 <br /> 51 <br />
52 <span class="text-[#FFDD01] cursor-pointer hover:opacity-80 transition-opacity" 52 <span class="text-[#FFDD01] cursor-pointer hover:opacity-80 transition-opacity"
53 - @click="showAgreement = true">《美乐爱觉宇宙用户协议》</span> 53 + @click="showAgreement = true; trackClick('view_agreement_link')">《美乐爱觉宇宙用户协议》</span>
54 </div> 54 </div>
55 </FrostedGlass> 55 </FrostedGlass>
56 </div> 56 </div>
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
74 </div> 74 </div>
75 <div class="p-4 border-t border-gray-100 bg-white safe-area-bottom"> 75 <div class="p-4 border-t border-gray-100 bg-white safe-area-bottom">
76 <van-button block round type="primary" color="linear-gradient(to right, #FFDD01, #E5C600)" 76 <van-button block round type="primary" color="linear-gradient(to right, #FFDD01, #E5C600)"
77 - class="!text-white !font-bold !h-[44px]" @click="showAgreement = false"> 77 + class="!text-white !font-bold !h-[44px]" @click="showAgreement = false; trackClick('agreement_confirm_btn')">
78 我已阅读并同意 78 我已阅读并同意
79 </van-button> 79 </van-button>
80 </div> 80 </div>
...@@ -84,11 +84,12 @@ ...@@ -84,11 +84,12 @@
84 </template> 84 </template>
85 85
86 <script setup> 86 <script setup>
87 -import { ref, computed } from 'vue' 87 +import { ref, computed, onMounted } from 'vue'
88 -import { useRouter } from 'vue-router' 88 +import { useRouter, useRoute } from 'vue-router'
89 import { showToast } from 'vant' 89 import { showToast } from 'vant'
90 import { useTitle } from '@vueuse/core' 90 import { useTitle } from '@vueuse/core'
91 import { smsAPI } from '@/api/common' 91 import { smsAPI } from '@/api/common'
92 +import { useTracking } from '@/composables/useTracking'
92 93
93 const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png' 94 const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png'
94 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png' 95 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png'
...@@ -98,6 +99,14 @@ const $route = useRoute() ...@@ -98,6 +99,14 @@ const $route = useRoute()
98 const $router = useRouter() 99 const $router = useRouter()
99 useTitle($route.meta.title) 100 useTitle($route.meta.title)
100 101
102 +// TAG: 埋点
103 +const { trackPageView, trackClick } = useTracking()
104 +
105 +onMounted(() => {
106 + // TAG: 埋点, 登录页面加载
107 + trackPageView('login_page')
108 +})
109 +
101 // Form Data 110 // Form Data
102 const phone = ref('') 111 const phone = ref('')
103 const code = ref('') 112 const code = ref('')
...@@ -112,6 +121,9 @@ let timer = null ...@@ -112,6 +121,9 @@ let timer = null
112 * @description 发送验证码 121 * @description 发送验证码
113 */ 122 */
114 const handleSendCode = async () => { 123 const handleSendCode = async () => {
124 + // TAG: 埋点, 获取验证码按钮点击
125 + trackClick('send_code_btn', { phone: phone.value })
126 +
115 if (!phone.value) { 127 if (!phone.value) {
116 showToast('请输入手机号') 128 showToast('请输入手机号')
117 return 129 return
...@@ -150,6 +162,9 @@ const handleSendCode = async () => { ...@@ -150,6 +162,9 @@ const handleSendCode = async () => {
150 * @description 登录/下一步 162 * @description 登录/下一步
151 */ 163 */
152 const handleLogin = () => { 164 const handleLogin = () => {
165 + // TAG: 埋点, 登录/下一步按钮点击
166 + trackClick('login_next_btn', { phone: phone.value })
167 +
153 if (!phone.value) { 168 if (!phone.value) {
154 showToast('请输入手机号') 169 showToast('请输入手机号')
155 return 170 return
......