trackEventMixins.js 8.31 KB
/* eslint-disable */

/*
 * @ description: 埋点上报系统,包含曝光
 * @ author: huzhiming
 * @ date: 2019-12-09 15:00:27
 * @ version: v1.0.0
 * 调用示例:

1: 引入相关文件
2-1: 页面曝光配置步骤
2-2: 普通按钮坑位点击
2-3: 列表坑位点击+曝光

======  1: 引入相关文件及对象 ======
import trackEventMixins from '@/pages/trackEventMixins'
mixins: [trackEventMixins],
data() {
  return {
    yasList: [...Array(20).keys()].map((item, index) => ({
      id: index
    }))
  }
},

methods: {
  buy() {
    console.log('我来自于@click点击,在此处负责执行相关业务逻辑代码!!');
  },
  // 注意:定义同名函数覆盖重写 曝光默认行为
  // dispatchExposure(data) {
  //   console.log(data);
  // }
},


======  2-1: 页面曝光配置步骤 ======
computed: {
  // 页面曝光数据配置,名称不可修改 activated钩子触发
  pageExposure() {
    return {
      params: {
        appop: '页面曝光上报Api',
        param: {
          name: `我是页面曝光传参`
        }
      }
    }
  },
}

======  2-2: 普通按钮坑位点击 ======
<div v-trackClick="{ props: 'YAS_BUY_CLICK_DATA' }" @click="buy">购买</div>

computed: {
  // 普通按钮坑位点击,数据配置 【名称可自定义,与对应指令 props 保持一致即可】
  YAS_BUY_CLICK_DATA() {
    return {
      params: {
        appop: 'yasBuyParamsApi-test',
        param: {
          name: '我是对象传参测试'
        }
      }
    }
  },
}

======  2-3: 列表坑位点击+曝光 ======
<div
  ref="exposureRef"
  v-trackExposure="{ props: 'YAS_ITEM_EXPOSURE_DATA', index }"
  v-trackClick="{ props: 'YAS_ITEM_CLICK_DATA', index }"
  v-for="(item, index) in [1,2,3,4,5]" :key="index"
>{{item}}</index>

其中:
ref="exposureRef" 固定标识 不可修改
两个指令中的 props对应vue组件实例内计算属性 index 坑位位置(必须)

computed: {
  // 列表坑位曝光,数据配置 【名称可自定义,与对应指令 props 保持一致即可】
  YAS_ITEM_EXPOSURE_DATA() {
    return this.yasList.map((el,index)=>({
      P_NAME: '',
      TYPE_ID: '',
      TAB_ID: '',
      TAB_NAME: '',
      P_PARAM: '',
      I_INDEX: index,
      PRD_ID: 12233,
      PRD_TYPE: '曝光列表项目' + index
    }))
  },
  // 列表坑位点击,数据配置 【名称可自定义,与对应指令 props 保持一致即可】
  YAS_ITEM_CLICK_DATA() {
    return this.yasList.map((el,index)=>{
      return {
        params: {
          appop: 'YAS_ITEM_CLICK_DATAApi-test',
          param: {
            name: '点击了列表项目' + index
          }
        }
      }
    });
  }
}
*/

import { debounce, throttle, get } from 'lodash';

let currentList = []; // 定义曝光记录,防止重复曝光
let offsetTop = 0; // 距离视窗顶部距离
let vm = null; // 供指令内使用
export default {
  data () {
    return {};
  },
  created () {
    vm = this; // 指令依赖vue实例
  },
  mounted () {},
  activated () {
    if (this.$refs.exposureRef) {
      this.$refs.exposureRef.forEach((element, index) => {
        element.dataset.trackid = `t_${index}`
        element.dataset.index = index
      });

      this.$nextTick(() => {
        window.addEventListener('scroll', debounce(throttle(this.handleScroll, 500), 500));
        this.handleScroll(); // 首屏触发 曝光上报
      });
    }

    /* 如果实例计算属性有此对象,则进行页面曝光上报 */
    if (this.pageExposure && Object.keys(this.pageExposure).length > 0) {
      this.$store.dispatch('reportYas', this.pageExposure);
    }
  },
  deactivated () {
    window.removeEventListener('scroll', null);
  },

  // beforeRouteEnter(to, from, next) {},
  // beforeRouteUpdate(to, from, next) {},
  // beforeRouteLeave(to, from, next) {},
  destroyed () {
    window.removeEventListener('scroll', null);
    currentList = [], vm = null, offsetTop = null;
  },
  methods: {
    handleScroll () {
      let prevTop = offsetTop;
      let direction = 'up';

      let oldList = currentList.map((el) => ({
        id: el.id,
        visible: el.visible,
        index: el.index
      }));

      const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
      let exposureRef = this.$refs.exposureRef
      currentList = exposureRef.filter((el, index) => {
        const Rect = el.getBoundingClientRect();
        const top = el.getBoundingClientRect().top;
        const elHeight = Rect.height;
        const isInviewport = (top > 0 || top + elHeight > 0) ? top <= viewPortHeight : false; // 垂直方向 是否在视窗内

        if (index === 0) {
          offsetTop = top;
        }

        el.dataset.visible = isInviewport;

        // console.log(`窗口宽度:${viewPortHeight},元素距离可视基准线top值:${top},是否在可视区域内:${top + elHeight < 0 ? false : top <= viewPortHeight}`);
        return isInviewport;
      });

      currentList = currentList.map(el => ({
        id: el.dataset.trackid,
        visible: el.dataset.visible,
        index: Number(el.dataset.index),
        exposure: el.YAS_EXPOSURE_DATA // 注意:数据来源于 指令绑定 el.exposure = xxx
      }));

      // console.log('%c上次值:\n', 'color:#f00;', JSON.stringify(oldList), 'length', oldList.length);
      // console.log('%c当前值:\n', 'color:#f00;', JSON.stringify(currentList), 'length', currentList.length);

      // 计算滚动方向
      if (prevTop!=0) {
        if (prevTop >= offsetTop) {
          direction = 'up'
        } else {
          direction = 'down'
        }
      }

      // console.log('滚动方向:', direction);

      // 防止重复上报逻辑
      let newAddItemList = [];
      let _tmp = currentList;

      if (direction == 'up') {
        // 保留结尾
        let endIndex = 0;

        if (oldList.length>0) {
          endIndex = _tmp.findIndex(el=>{
            return el.id === oldList[oldList.length - 1].id
          })
          endIndex+=1
        }

        newAddItemList = _tmp.slice(endIndex);

      } else {
        // 保留开头

        let endIndex = 0;

        if (oldList.length > 0) {
          endIndex = _tmp.findIndex(el => {
            return el.id == oldList[0].id
          })
        }

        newAddItemList = _tmp.slice(0, endIndex)
      }

      // console.log('%c新加入元素节点:\n', 'color:#f00;', JSON.stringify(newAddItemList), currentList.length == oldList.length);

      let reportList = newAddItemList.filter(el => el.visible == 'true'); // 筛选出可见的元素组成新集合,需要上报的数据集合

      console.log('%c筛选出可见的元素组成新集合,进行曝光事件上报,需要上报的数据元素为:\n', 'color:#f00;', JSON.stringify(reportList))

      if (reportList.length > 0) {
        reportList = reportList.map(el => el.exposure)
        this.dispatchExposure(reportList);
      }
    },

    // 触发曝光事件 数据上报, 可组件内定义 同名函数覆盖
    dispatchExposure (reportList) {
      this.$store.dispatch('reportYas', {
        params: {
          param: { DATA: reportList },
          appop: 'XY_UFO_SHOW_EVENT'
        }
      });
    },

    // 触发点击事件 数据上报
    dispatchClick (target) {
      if (target.YAS_CLICK_DATA) {
        console.log('我只负责点击事件上报:', JSON.stringify(target.YAS_CLICK_DATA));
        // this.$store.dispatch('reportYas', this[target.YAS_CLICK_DATA]);
      }
    }
  },
  computed: {},
  watch: {},
  components: {},
  directives: {
    // 坑位点击 上报数据绑定
    trackClick: {
      bind(el, binding) {
        const { value } = binding;

        if (value.props && value.props != '' && typeof value.index !== 'number') {
          el.YAS_CLICK_DATA = vm[value.props]
        } else {
          el.YAS_CLICK_DATA = vm[value.props][value.index]
        }

        // 加入防抖和节流
        el.addEventListener('click', debounce(throttle(vm.dispatchClick.bind(vm, el), 500), 200), false);
      },
      unbind (el, binding){
        el.removeEventListener('click', null);
      }
    },

    // 坑位曝光事件 上报数据绑定
    trackExposure: {
      bind (el, binding) {
        const { value } = binding;

        if (value.props && value.props!='') {
          el.YAS_EXPOSURE_DATA = vm[value.props][value.index]
        }
      }
    }
  }
};