埋点方案主要流程
1、 在 main.js 文件中生成 capol-log-uuid 埋点会话唯一id,并存入sessionStorage中
router.afterEach((to, from, next) => { //优先取url上面携带的埋点id const uuid = to.query.logId ? to.query.logId : Utils.uuid() console.log('进入页面,生成回话id') sessionStorage.setItem('capol-log-uuid',uuid)})
2、在 utils 文件夹下添加 commonLog.js 公共埋点方法类,提供3个方法:
添加埋点函数:CapolLog.pointAdd(dynamicInfo, el)更新埋点函数:CapolLog.pointUpdate(id, type,updateData)更新埋点辅助函数:CapolLog.pointUpdateHelper(event,operateResultBool)3、封装v-capol-log指令,监听元素点击事件,触发埋点
//通用按钮埋点指令Vue.directive('capol-log', { inserted(el, binding) { //按钮点击执行事件 const handleClick = (el, binding) => { //修饰符 const idFlag = binding.modifiers.idFlag ? 1 : 0; //绑定值 const data = binding.value //埋点外部传参对象 const dynamicInfo = { idFlag, ...data } CapolLog.pointAdd(dynamicInfo,el) } const wrappedClickEvent = function (event) { handleClick(el, binding, event) } el.addEventListener('click', Utils.debounce(wrappedClickEvent, 300)) el.handleClick = handleClick }, unbind(el, binding) { const handleClick = el.handleClick el.removeEventListener('click', handleClick) delete el.handleClick },})
4、将埋点公共方法添加到Vue.prototype原型对象中,手动调用,触发埋点
startTask(event) { //模拟接口响应时间2s setTimeout(() => { this.$CapolLog .pointUpdateHelper(event, true) .then((res) => { console.log(res) }) .catch((error) => { console.error(error) }) }, 2000) this.getMajorData() this.showStartTaskDialog = true },
埋点接口传参说明
添加埋点参数
字段 | 是否必填 | 备注说明 |
---|---|---|
source | 是 | 来源端 |
functionType | 是 | 功能类型 |
logType | 是 | 日志类型 |
info | 是 | 埋点基本信息 |
uuid | 是 | 回话唯一值 |
menuCode | 否 | 菜单编码 |
buttonName | 否(点击按钮为必填) | 按钮名称(应该带上下文) |
privateData | 是 | 私有参数json字符串 |
idFlag | 是 | id值是否需要返回,后续用于更新 |
更新埋点参数
字段 | 是否必填 | 备注说明 |
---|---|---|
functionType | 是 | 功能类型(和添加埋点时传参保持一致) |
updateData | 是 | 更新埋点参数json字符串(必须包含id字段,其他为更新参数) |
部分参数枚举说明
functionType(功能类型)
功能类型key | 功能类型val |
---|---|
0 | 按钮点击 |
1 | 页面初始 |
2 | 文件下载 |
3 | 文件浏览 |
4 | 文件分享 |
5 | 文件上传 |
6 | 选择模板 |
7 | 进度浏览 |
8 | 管控事件 |
9 | 图模关联 |
10 | 问题导出 |
logType(日志类型)
功能类型key | 功能类型val |
---|---|
0 | 工具埋点点击 |
1 | 框架埋点初始 |
2 | 文件项目文件下载 |
3 | 文件分享文件下载 |
4 | 管控文件下载 |
5 | 项目文件浏览 |
6 | 分享文件浏览 |
7 | 管控文件浏览 |
8 | 图纸文件浏览 |
9 | 项目文件分享 |
10 | 项目文件上传 |
11 | 管控文件上传 |
12 | 选择模板埋点 |
13 | 进度浏览埋点 |
14 | 进度管控埋点 |
15 | 图模关联埋点 |
16 | 问题导出埋点 |
source(来源端)
来源key | 来源Val |
---|---|
0 | pc |
1 | android |
2 | ios |
3 | wx |
4 | Capol3D |
5 | Capol2D |
指令埋点详细说明
实现方案
1、在需要埋点的元素上绑定 v-capol-log 指令,监听元素点击事件,当元素点击时,自动触发添加埋点方法,如果 v-capol-log 修饰符idFlag为true,在添加埋点成功后,在该点击元素上添加capol-log-element 类名、data-id(记录此次埋点id)、data-type(记录此次埋点类型)
<button type="button" class="el-button el-button--default el-button--small is-plain capol-log-element" data-id="1929582931271722" data-type="按钮点击">发起审批任务</span></button>
2、在点击事件相关业务执行完毕后,通过点击事件的event对象拿到元素上记录的埋点id、埋点类型type,将操作结果(‘“操作成功” || “操作失败”)作为更新参数作为updateData参数,调用CapolLog.pointUpdate(id, type,updateData)更新埋点方法实现埋点更新。后台将根据初次埋点时间、更新埋点时间,计算出****此次点击事件的响应时间responseTime。
使用方案
1、绑定指令
<el-button v-capol-log.idFlag="{ functionType: 0, logType: 0, buttonName: '发起审批任务-成功审查', privateData: { functionName: '发起审批任务', menuName: '成果审查',projectId:123,enterpriseId:456 }, }" plain @click="startTask" >发起审批任务 </el-button>
指令传参说明:
字段 | 是否必填 | 备注说明 |
---|---|---|
idFlag | 否 | 指令修饰符,后续需要更新埋点必传 |
functionType | 是 | 功能类型,根据枚举说明表传对应下标即可(eg:按钮点击传0) |
logType | 是 | 日志类型,根据枚举说明表传对应下标即可(eg:工具埋点点击传0) |
buttonName | 是 | 按钮名称,需要带上下文(eg:发起审批任务-成功审查) |
privateData | 是 | 私有参数对象,functionName(按钮名称)、menuName(菜单名称)这两个字段必传,其他字段按需传递即可 |
menuCode | 否 | 菜单编码,不传优先取左侧导航栏高亮菜单code,取不到取当前路由code |
2、点击事件业务执行完毕后,调用**this.$CapolLog.pointUpdateHelper(event,operateResultBool)**更新埋点(如果需要)
startTask(event) { //模拟接口响应时间2s setTimeout(() => { this.$CapolLog .pointUpdateHelper(event, true) .then((res) => { console.log(res) }) .catch((error) => { console.error(error) }) }, 2000) this.getMajorData() this.showStartTaskDialog = true },
页面初始埋点详细说明
待后续更新…
主要实现代码
commonLog.js(通用埋点方法)
import API from '../api'import router from '../router'import Utils from './utils'import store from '../store'import { getDeviceInfo } from './getDeviceInfo'// 获取功能类型function getFunctionType(val) { const List = { 0: '按钮点击', 1: '页面初始', 2: '文件下载', 3: '文件浏览', 4: '文件分享', 5: '文件上传', 6: '选择模板', 7: '进度浏览', 8: '管控事件', 9: '图模关联', 10: '问题导出', } return List[val] || ''}// 获取日志类型function getLogType(val) { const List = { 0: '工具埋点点击', 1: '框架埋点初始', 2: '项目文件下载', 3: '分享文件下载', 4: '管控文件下载', 5: '项目文件浏览', 6: '分享文件浏览', 7: '管控文件浏览', 8: '图纸文件浏览', 9: '项目文件分享', 10: '项目文件上传', 11: '管控文件上传', 12: '选择模板埋点', 13: '进度浏览埋点', 14: '进度管控埋点', 15: '图模关联埋点', 16: '问题导出埋点', } return List[val] || ''}// 获取来源端// function getScource(val) {// const List = {// 0: 'pc', //WEB端// 1: 'android', //安卓端// 2: 'ios', //苹果端// 3: 'wx', //小程序端// 4: 'Capol3D', //速建端// 5: 'Capol2D', //速绘端// }// return List[val] || ''// }// 获取基本埋点信息function getBasicInfo() { const basicInfo = getDeviceInfo(null, router.history.current) console.log(basicInfo, 'basicInfo') return basicInfo}//获取回话唯一idfunction getUUID() { let uuid = sessionStorage.getItem('capol-log-uuid') if (!uuid) { uuid = Utils.uuid() sessionStorage.setItem('capol-log-uuid', uuid) } return uuid}//按照后台要求 将对象转成json字符串function getJSONObj(obj) { if (typeof obj === 'object' && obj !== null) { return JSON.stringify(obj).replace(/"/g, "'") } else { return '' }}// 扁平化系统菜单数组function flatterArray(arr, finalArr = []) { if (arr.length === 0) return finalArr for (let i = 0; i < arr.length; i++) { const item = arr[i] if (item.children && item.children.length) { flatterArray(item.children, finalArr) } else { finalArr.push(item) } } return finalArr}//获取菜单codefunction getMenuCode() { //默认取路由名称 let code = router.currentRoute.name || '' let menuList = store.getters.menuList || [] if (menuList.length === 0) { const queryObj = Utils.getURLParameters(decodeURIComponent(window.location.href)) const { proId, enterpriseId } = queryObj menuList = JSON.parse(localStorage.getItem('pro_menu_list_' + proId + '_' + enterpriseId)) } //从系统菜单列表中取code if (menuList.length) { const flatterMenuList = flatterArray(menuList) const activeMenuText = document.querySelector( '.sidebar-el-menu .el-menu-item.is-active .item span' )?.innerHTML const activeMenu = flatterMenuList.find((el) => el.title === activeMenuText) if (activeMenu) { code = activeMenu.code } } return code}//埋点方法类export default class CapolLog { /** * 通用添加埋点函数 * @param dynamicInfo 添加埋点的参数 (必填) * @param dynamicInfo 必填项说明:functionType(功能类型);logType(日志类型);privateData(埋点私有参数);idFlag(是否需要后台返回此次埋点id,用于后续更新) * @param el dom元素,如果有传,将往dom元素上添加埋点信息 (非必填) * @returns {Promise} 添加埋点的状态 */ static pointAdd = (dynamicInfo, el) => { let { functionType, source, logType, menuCode, buttonName, privateData, idFlag } = dynamicInfo const params = { source: source || 'pc', //来源端 functionType: getFunctionType(functionType), //功能类型 logType: getLogType(logType), //日志类型 info: getJSONObj(getBasicInfo()), //埋点基本信息 uuid: getUUID(), //回话唯一值 menuCode: menuCode || getMenuCode(), //菜单编码 (非必填) buttonName, //按钮名称 //私有参数json字符串 privateData: getJSONObj(privateData), idFlag: idFlag || 0, //id值是否需要返回 } return new Promise((resolve, reject) => { API.log .capolAddLog(params) .then((res) => { if (res.status === 200) { if (res.data && el) { el.classList.add('capol-log-element') el.setAttribute('data-id', res.data) el.setAttribute('data-type', getFunctionType(functionType)) } resolve(res) } else { reject(res) } }) .catch((error) => { reject(error) }) }) } /** * 更新埋点通用函数 * @param id 此次需要更新的埋点id (必填) * @param type 功能类型 eg('按钮点击') (必填) * @param updateData 更新的私有参数 (必填) * @returns {Promise} 返回更新埋点的状态 */ static pointUpdate = (id, type, updateData) => { const params = { functionType: type, updateData: getJSONObj({ id, ...updateData, }), } return new Promise((resolve, reject) => { API.log .capolUpdateLog(params) .then((res) => { if (res.status === 200) { resolve(res) } else { reject(res) } }) .catch((error) => { reject(error) }) }) } /** * 按钮点击更新埋点辅助函数 * @param event 点击事件的 event 对象 (必填) * @param operateResultBool 点击事件的操作结果 true || false (必填) * @returns {updatePromise} 返回更新埋点的状态 */ static pointUpdateHelper = async (event, operateResultBool) => { if (!event || operateResultBool === undefined) { return Promise.reject(new Error('更新埋点参数缺失')) } const updateData = { operateResult: operateResultBool ? '操作成功' : '操作失败', } console.log(event?.target.closest('.capol-log-element')) const targetBtn = event?.target.closest('.capol-log-element') //返回更新埋点的状态 let updatePromise if (targetBtn) { const id = targetBtn.dataset.id const type = targetBtn.dataset.type updatePromise = await this.pointUpdate(id, type, updateData) } else { updatePromise = Promise.reject(new Error('获取不到更新埋点id')) } return updatePromise }}