埋点方案主要流程
1、 在 main.js 文件中生成 capol-log-uuid 埋点会话唯一id,并存入sessionStorage中
router.afterEach((to, from, next)=>{//优先取url上面携带的埋点idconst 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功能类型val0按钮点击1页面初始2文件下载3文件浏览4文件分享5文件上传6选择模板7进度浏览8管控事件9图模关联10问题导出
logType(日志类型)
功能类型key功能类型val0工具埋点点击1框架埋点初始2文件项目文件下载3文件分享文件下载4管控文件下载5项目文件浏览6分享文件浏览7管控文件浏览8图纸文件浏览9项目文件分享10项目文件上传11管控文件上传12选择模板埋点13进度浏览埋点14进度管控埋点15图模关联埋点16问题导出埋点
source(来源端)
来源key来源Val0pc1android2ios3wx4Capol3D5Capol2D
指令埋点详细说明
实现方案
1、在需要埋点的元素上绑定 v-capol-log 指令,监听元素点击事件,当元素点击时,自动触发添加埋点方法,如果 v-capol-log 修饰符idFlag为true,在添加埋点成功后,在该点击元素上添加capol-log-element 类名、data-id(记录此次埋点id)、data-type(记录此次埋点类型)
<buttontype="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
}
//获取回话唯一id
function 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
}
//获取菜单code
function 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
}
}
版权归原作者 UpUp_seaboy 所有, 如有侵权,请联系我们删除。