feat(埋点): 添加登录页面的埋点功能
在登录页面添加用户行为埋点跟踪,包括页面浏览、协议链接点击、确认按钮点击、验证码发送和登录按钮点击事件 新增useTracking composable用于统一处理埋点逻辑
Showing
2 changed files
with
101 additions
and
4 deletions
src/composables/useTracking.js
0 → 100644
| 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 | ... | ... |
-
Please register or login to post a comment