需求:pm要求实现一个记录用户停留这个页面时长,后台记录,通过这些记录总结用户的习惯。比较坑的就是pm又提出了第二个需求,记录页面中某一个图表浏览时长。无语了,第一反应是不可能实现第二个需求。

我们系统环境:vue,听到第一个需求的时候,脑子里有一个想法,应该不难实现,就是定时器的功能。第二个需求懵了,想象脑壳疼,挺麻烦的。聪明的我还是奋战几天实现了?。

需求1 :记录页面浏览时长

思路:全局设置一个定时器和记录时间变量。路由导航后置钩子函数中,判断时间>0,向后台发送请求,并清空时间变量。

main.js文件

window.millisecond = 0

// 这个位置放置需要注意,初始化第一次进入页面时候,开启定时器。较少记录时间误差。
window.timer = setInterval(function () {
    window.millisecond += 1
}, 1)


router.afterEach((route, to) => {
    // 排除快速切换
    if (window.millisecond !== 0) {
        eventLog({ eventCode: 'GL-00002', remark: JSON.stringify({ route: to.path, browseTime: window.millisecond + 'ms' }) })
    }
    window.millisecond = 0
    loadingBar.finish()
})

// 特别需要注意的一点,定时器及时清理。
 clearInterval(window.timer)

需求2: 记录页面中某一个图形浏览时间

思路:这个功能代码在多个页面中都会使用,因此采用vue中混入(mixin),提高代码利用率。

技术难点1:定时器开启时间节点和关闭时间节点。

技术难点2: 如何定位图表在可视区域内。

技术难点3: 定时器利用和销毁。

技术难点4: 跳转页面前,如何处理图表记录时间。这次出现了两种情况,也头疼。

import { eventLog } from '@/common/request'
export default {
    data() {
        return {
            timeArray: [], // 定时器id数组
            scrollObj: {}, // 外层滚动视图
            time: {}, // 定时器
            browseTime: 0, // 可视区域浏览时间
            targetDiv: {}, // 在可视区域图表元素
            minBrowseTime: 5, // 最下停留时间,
            routePath: '', // 路由地址
        }
    },
    mounted() {
        for (let i = 0; i < 1000; i++) {
            if (i !== window.timer) {
                clearInterval(i)
            }
        }
        console.log(window.timer, this.time, '开始')
    },
    methods: {
        /**
         * @param {Array} targetDivArray 一个页面中需要记录图表数组
         * { name: '图表名称', isShow: false, id: '图表id' }
         * @param {String} routePath 路由地址
         */
        browseTimeStart(targetDivArray, routePath) {
            this.routePath = routePath
            this.scrollObj = document.getElementsByClassName('page-loadmore-wrapper')[0]
            this.scrollObj.removeEventListener('scroll', null, false)
            this.browseTime = 0
            clearInterval(this.time)
            this.time = setInterval(() => {
                this.browseTime += 1
            }, 1000)
            this.timeArray.push(this.time)
            // 为了解决跳转到一个页面时候,如果不滚动一下,我们就无法开启定时器,不能精准记录停留时间
            this.scrollObj.scrollTop = 1
            this.scrollObj.addEventListener('scroll', () => {
                // 为了解决,在A页面跳转到B页面,A页面没有销毁情况,在返回到A页面,
                // this.scrollObjTop计算出现了偏差,所以又重新获取this.scrollObj对象
                this.scrollObj = document.getElementsByClassName('page-loadmore-wrapper')[0]
                for (let i = 0; i < targetDivArray.length; i++) {
                    let targetElement = document.getElementById(targetDivArray[i].id)
                    if (targetElement === null) {
                        targetDivArray[i].isShow = true
                        this.targetDiv = targetDivArray[i]
                        return
                    }
                    let height = targetElement.offsetHeight
                    let top = targetElement.getBoundingClientRect().top
                    let scrollObjTop = this.scrollObj.getBoundingClientRect().top
                    let screenHeight = Math.ceil(this.$store.state.wrapperHeight)
                    let upperLimit = screenHeight - height + scrollObjTop + 100
                    let lowerLimit = scrollObjTop - 80
                    // 难点在于这个公式总结,图表距离顶部高度>(外层滚动容器距离顶部高度 - 图表顶部80)
                    // 这个80可以动态调控,定义图表顶部不可见区域高度超80定义为不在可视化区域,发送后台请求,时间清空
                    // 图表距离顶部高度<(可视化区域高度 - 图表高度 + 外层滚动容器距离顶部高度 + 图表底部100)
                    // 这个100可以动态调控,定义图表底部不可见区域高度超100定义为不在可视化区域,发送后台请求,时间清空
                    if (lowerLimit < top && top < upperLimit) {
                        // console.log('+++++++++', targetDivArray[i].name)
                        this.targetDiv = targetDivArray[i]
                        if (!targetDivArray[i].isShow) {
                            targetDivArray[i].isShow = true
                            this.browseTime = 0
                        }
                    } else {
                        if (this.browseTime > this.minBrowseTime && targetDivArray[i].isShow) {
                            targetDivArray[i].isShow = false
                            eventLog({ eventCode: 'GL-00003-TIMER', remark: JSON.stringify({ route: this.$route.path, browseTime: this.browseTime + 's', name: targetDivArray[i].name }) })
                            // num = 0
                            this.browseTime = 0
                            this.targetDiv = {}
                        }
                        targetDivArray[i].isShow = false
                    }
                }
            }, false)
        },
        /**
         * 加了keep-alive,跳转新页面,该页面不回走destroyed钩子函数,
         * 需要我们在路由地址变化监听函数中手动调用这个方法
         * 发送图表停留请求
         */
        browseTimeEnd() {
            if (Object.keys(this.targetDiv).length > 0 && this.browseTime > this.minBrowseTime) {
                eventLog({ eventCode: 'GL-00003-TIMER', remark: JSON.stringify({ route: this.routePath, browseTime: this.browseTime + 's', name: this.targetDiv.name }) })
                this.browseTime = 0
            }
            clearInterval(this.time)
            this.timeArray.splice(0, 1)
        }
    },
    /**
     * 页面销毁时候,需要想后台发送有效的图表停留时长,并且销毁定时器,关闭滚动监控
     */
    destroyed() {
        if (typeof (this.scrollObj) !== 'undefined') {
            this.browseTimeEnd()
            this.scrollObj.removeEventListener('scroll', null, false)
        }
    },
}

 

Logo

前往低代码交流专区

更多推荐