uni-app 埋点方案

以实现方式划分可以分为 "无埋点” 和 "手动埋点 "。

无埋点

无埋点介绍两种实现方式,分别使用 uni-app 拦截器和 vue 的 mixins。

使用无埋点方案统计,需要我们改造一下 pages.json 文件,uni-app 会自动加载 src 目录下的 pages.js 文件。

pages.json 文件不能删除,内容可以为空。

// src/pages.json

{
  "tip": "please use pages.js"
}
json
// src/pages.js

module.exports = () => ({
  pages: [
    {
      path: "pages/StartPage/StartPage",
      style: {
        navigationBarTitleText: "登录",
        navigationBarBackgroundColor: "#fff",
        navigationBarTextStyle: "white",
      },
    },
    {
      path: "pages/Dashboard/Dashboard",
      stat: true,
      module: "模块名称1",
      style: {
        navigationBarBackgroundColor: "#fff",
        navigationBarTextStyle: "white"
      },
    },
  ],
  subPackages: [
    {
      root: "packageTest",
      pages: [
        {
          path: "pages/Test/Test",
          stat: true,
          module: '模块名称2',
          style: {
            navigationBarBackgroundColor: "#fff",
            navigationBarTextStyle: "white",
            navigationBarTitleText: "模块名称2",
          },
        },
      ],
    }
  ],
});
js

pages.js 文件中,我们使用 stat 字段标识模块是否需要统计,使用 module 字段作为模块名称。

下面是使用的工具方法。

// util.js

import pages from '../../pages';

function curry(func) {
  return function curriedFn (...args) {
    if (args.length < func.length) {
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)));
      }
    }
    return func(...args);
  }
}

const joinPath = (root, item) => (item.path = `${root}/${item.path}`, item);

const arrToObj = (pages) =>
  pages.reduce((pre, cur) => (pre = { ...pre, [cur.path]: cur.module }, pre), {});


const statFilterStrategy = (item) => item.stat;

// 埋点页面统计
const getStatPages = ({ pages, subPackages }) =>
  subPackages.reduce(
    (pre, cur) =>
      pre.concat(cur?.pages.filter(statFilterStrategy).map(item => joinPath(cur.root, item)) || []),
      pages?.filter(statFilterStrategy) || []
  );

// 埋点页面对象
// { [path]: [module] }
const getStatObj = () => {
  return arrToObj(getStatPages(pages()))
}

module.exports = {
  curry,
  joinPath,
  arrToObj,
  getStatPages,
  getStatObj
}
js

uni-app 拦截器

我们可以使用 uni-app 提供的拦截器,拦截全局路由跳转。

缺点:uni-app 拦截器,不会统计页面回退,也就是说你只能统计进入当前页面,如果进入其他页面回退是无法统计到的。

代码实现如下:

// useStatIntercept.js

import { curry, getStatObj } from './utils';

// 无埋点处理
export const useStatIntercept = () => {
  const apis = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'];
  const statObj = getStatObj;

  apis.forEach(api => {
    uni.addInterceptor(api, {
      invoke(e) {
        const url = (e.url.split('?')[0]).replace(/^\//, '');

        if (statObj[url]) {
          statUpload(url, statObj[url]);
        }

        lastStat = {
          path: url,
          module: statObj[url]
        }
      }
    })
  });
}

// 手动埋点处理
export const statUpload = curry((path, module) => {
  const app = getApp();
  const { branch_id } = app?.globalData || {};

  if (!module || !branch_id) return;

  console.log('[stat upload]', module, path);

  // TODO:自定义实现上报逻辑
})
js

我们可以在 App.vue 文件中使用它。

<script>
import { useStatIntercept } from '@/utils/stat/useStatIntercept';

export default {
  onLaunch () {
    useStatIntercept(); // 用户使用记录统计
  }
}
</script>

全局 mixins

由于通过 uni-app 拦截器拦截路由,页面回退时无法统计到。所以我们还可以使用全局注册 mixins 的方式。

import { statUpload } from './useStatIntercept';
import { getStatObj } from './utils';

export default {
  onShow() {
    const { route: path } = getCurrentPages().slice(-1)[0] || {};
    const module = getStatObj()[path];

    statUpload(path, module);
  }
}
js

使用方式很简单,从 main.js 文件中导入注册即可。

// main.js
import StatMixin from '@/utils/stat/useStatMixin';

Vue.mixin(StatMixin);
js

手动埋点

手动埋点可以用来解决一些多样化的需求。假设当前页面并不只是统计进入和退出,而是需要根据页面内多个子模块的点击进行埋点。

由于我们的 statUpload 方法经过柯里化函数进行处理,所以我们可以这样使用。

<script>
import { statUpload } from '@/utils/stat/useStatIntercept';
  
// statUpload 经过柯里化处理,可以提供多次传参(默认传递当前页面路径)
const statLogger = statUpload('packageTest/pages/Test/Test');
  
export default {
  methods: {
    switch () {
      // 切换模块时,可以传递不同的名称,埋点上报
      statLogger('xxxx01');
      statLogger('xxxx02');
    }
  }
}
</script>

总结

本篇文章介绍了埋点的几种实现方案,在真实业务场景中需要我们根据实际需求去实现自己的埋点 sdk