npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hx-web-tracker

v0.1.1

Published

Lightweight web tracking SDK (SLS client) extracted from growth project

Readme

hx-web-tracker

轻量级前端埋点 SDK,基于阿里云 SLS(Simple Log Service)日志服务,提供自动化的用户行为追踪能力。

核心特性:

  • 🎯 自动采集用户行为:点击、输入、页面浏览、HTTP 请求
  • 📦 批量上报与离线缓存:支持本地持久化,网络恢复后自动重试
  • 🔐 安全的数据脱敏:内置字段掩码策略,保护敏感信息
  • 🎨 丰富的上下文增强:自动附加会话、用户、设备、视图等上下文信息
  • 🔗 请求追踪与动作关联:支持 trace_id 和 action_id 串联前后端日志

源码结构: 核心代码位于 core/(追踪引擎)、collectors/(行为采集器)、context/(上下文管理)三个目录。

安装

npm i hx-web-tracker

快速集成示例

在你的项目里新增一个本地封装(例如 src/utils/logger.ts),负责初始化、注册采集器、提供 track/setView/refreshUser 等入口。下面是一份可直接拷贝的示例,包含必要的 JSDoc 说明,按注释替换占位符即可开箱使用。

import type { ViewInfo, UserInfo } from 'hx-web-tracker';
import {
  init as trackerInit,
  track as coreTrack,
  setContextView,
  refreshContextUser,
  setTraceId,
  registerCollector,
  createClickCollector,
  createInputCollector,
  createRequestCollector,
  createViewCollector,
} from 'hx-web-tracker';
import router from '@/router';
import axios from '@/utils/request'; // 替换为你的 axios 实例

const ENV = import.meta.env;
const LOG_ENDPOINT = '<你的 SLS endpoint>'; // 例:cn-beijing.log.aliyuncs.com
const LOG_PROJECT = '<你的 project>';
const LOG_LOGSTORE = ENV.MODE === 'development' ? '<test_logstore>' : '<prod_logstore>';
const LOG_TOPIC = '<可选 topic>';

/**
 * 获取临时凭证(请接入你们的鉴权服务,不要硬编码 AK/SK)。
 */
async function fetchCredential() {
  return {
    accessKeyId: '',
    accessKeySecret: '',
    securityToken: '',
    expireAt: Date.now() + 3600_000,
  };
}

/**
 * 兼容 Vue Router 3(currentRoute 为对象)与 4(currentRoute 为 ref)。
 * 将路由对象转换为视图信息,用于 view_start/view_end。
 */
function unwrapRoute(route: any) {
  if (!route || typeof route !== 'object') return route;
  return 'value' in route ? (route as any).value : route;
}

function toRouteInfo(route: any): ViewInfo {
  const r = unwrapRoute(route);
  if (!r || typeof r !== 'object') {
    return {
      id: typeof window !== 'undefined' ? window.location.pathname : 'unknown',
    };
  }
  const fullPath = r.fullPath ?? r.path ?? 'unknown';
  const metaTitle = r.meta?.title_name ?? r.meta?.title;
  return {
    id: String(fullPath),
    name: metaTitle ? String(metaTitle) : undefined,
    title: metaTitle ? String(metaTitle) : undefined,
    path: r.path ? String(r.path) : undefined,
  };
}

/**
 * 追加自定义字段:页面 URL/标题与视口大小。
 * page_title 优先取 document.title,page_url 优先用视图 id/path 构造。
 */
function createCustomFieldResolvers() {
  return [
    (context: { view?: ViewInfo }) => {
      const hasWindow = typeof window !== 'undefined';
      const hasDocument = typeof document !== 'undefined';
      const viewportWidth = hasWindow && typeof window.innerWidth === 'number' ? window.innerWidth : -1;
      const viewportHeight = hasWindow && typeof window.innerHeight === 'number' ? window.innerHeight : -1;
      const view = context.view ?? {};
      const viewTitle = (view.title ?? view.name) as string | undefined;
      const docTitle = hasDocument && typeof document.title === 'string' ? document.title : '';
      const pageTitle =
        docTitle.trim().length > 0
          ? docTitle
          : typeof viewTitle === 'string' && viewTitle.trim().length > 0
            ? viewTitle
            : '';

      const viewId = typeof view.id === 'string' ? view.id : '';
      const viewPath = typeof view.path === 'string' ? view.path : '';
      const isAbsoluteUrl = /^https?:\/\//i.test(viewId);
      const pageUrl =
        isAbsoluteUrl && viewId
          ? viewId
          : hasWindow && viewPath
            ? `${window.location.origin}${viewPath}`
            : hasWindow && viewId
              ? `${window.location.origin}${viewId}`
              : hasWindow
                ? window.location.href
                : viewId || viewPath;

      return {
        page_url: pageUrl,
        page_title: pageTitle,
        viewport_width: viewportWidth,
        viewport_height: viewportHeight,
      };
    },
  ];
}

let initialized = false;
let initPromise: Promise<void> | null = null;
let manualViewSetter: ((view: ViewInfo) => void) | null = null;

function registerBuiltInCollectors() {
  registerCollector('click', createClickCollector(), { priority: 80, override: true });
  registerCollector('input', createInputCollector({ maskStrategy: 'none' }), { priority: 70, override: true });
  registerCollector(
    'request',
    createRequestCollector({
      instrumentFetch: true,
      instrumentXHR: true,
      axiosInstances: [axios as any],
      ignoredUrls: [/\\.js$/i, /\\.css$/i, /\\.map$/i, /__vite_ping/],
    }),
    { priority: 60, override: true },
  );
  registerCollector(
    'view',
    createViewCollector({
      router,
      getRouteInfo: toRouteInfo, // 兼容 v3/v4 的 currentRoute/afterEach 入参
      registerManualSetter: (setter) => {
        manualViewSetter = setter;
      },
    }),
    { priority: 90, override: true },
  );
}

/**
 * 初始化埋点 SDK(幂等);可在应用入口直接调用,或在首次 track/send 前懒加载。
 */
export async function initTracker(): Promise<void> {
  if (initialized) return;
  if (initPromise) return initPromise;
  registerBuiltInCollectors();
  initPromise = trackerInit({
    endpoint: LOG_ENDPOINT,
    project: LOG_PROJECT,
    logstore: LOG_LOGSTORE,
    topic: LOG_TOPIC,
    debug: !!ENV.DEV,
    fetchCredential,
    context: {
      getUserInfo: async (): Promise<UserInfo> => ({ id: '', name: '', role: '' }), // TODO: 返回真实用户
      resolveView: () => toRouteInfo(router.currentRoute),
      customFieldResolvers: createCustomFieldResolvers(),
    },
  })
    .then(() => {
      initialized = true;
    })
    .catch((error) => {
      initPromise = null;
      throw error;
    });
  return initPromise;
}

/**
 * 统一导出的 tracker 对象,便于业务侧调用。
 */
export const tracker = {
  /**
   * 手动初始化,可选(track 时会自动懒加载)。
   */
  init: initTracker,

  /**
   * 上报业务事件。
   */
  track(eventType: string, payload?: Record<string, unknown>) {
    return initTracker().then(() => {
      coreTrack({ event_type: eventType, payload });
    });
  },

  /**
   * 更新当前视图(若需手动埋点页面切换)。
   */
  setView(view: ViewInfo) {
    return initTracker().then(() => {
      if (manualViewSetter) {
        manualViewSetter(view);
      } else {
        setContextView(view);
      }
    });
  },

  /**
   * 刷新用户上下文(登录/切换用户后调用)。
   */
  refreshUser(user?: Partial<UserInfo>) {
    return initTracker().then(() => refreshContextUser(user as UserInfo));
  },

  /**
   * 设置 traceId 以串联前后端日志。
   */
  setTraceId,
};

使用方式:

  • 在应用入口(如 main.ts)调用 void tracker.init(),或在首次 tracker.track() 前懒加载。
  • 路由已自动注册视图埋点,如需手动控制页面视图可调用 tracker.setView({ id, title, path })
  • 登录/切换用户后调用 tracker.refreshUser(user) 刷新上下文。
  • 需要链路追踪时调用 tracker.setTraceId(traceId)

埋点字段说明

通用字段

每条日志都会包含以下通用字段:

| 字段 | 描述 | 示例值 | 备注 | |------|------|--------|------| | timestamp | 格式化时间字符串,格式为 YYYY-MM-DD HH:mm:ss | 2025-09-22 10:54:22 | 用于页面展示,24 小时制,无毫秒 | | timestamp_ms | 事件产生时的 Unix 毫秒时间戳 | 1758509662127 | 供后端做时间排序/计算 | | topic | 日志主题标签,SDK 初始化时配置 | web-tracker | 用于给上报数据分类 | | event_type | 事件类型标识 | ui_click / http_request / view_start 等 | 详见下方事件类型说明 |

event_type 详细说明

| 事件类型 | 说明 | 触发场景 | |---------|------|----------| | session_start | 会话启动 | SDK 初始化时自动落一次,标记新会话启动 | | view_start / view_end | 页面浏览 | 路由切换或手动调用 setView 时记录页面进入/离开及停留时长 | | ui_click | 点击行为 | 内置采集器捕捉左键/右键/双击/长按等行为 | | ui_input | 表单输入 | 输入框/文本域/下拉框失焦时,读取当前值(默认忽略 password/hidden/file),做截断或脱敏后上报 | | http_request | 网络请求 | Axios/fetch/XHR 请求结束时落一条,包含耗时、状态码、错误信息等 | | adjust_column_width | 表格列宽调整 | 通用表格列宽调整成功时手动上报,记录表名/字段/新宽度 | | filter_change | 筛选条件变更 | 筛选项变动时手动上报,覆盖静态筛选面板和动态筛选面板 | | download_table | 表格导出 | 表格导出操作时手动上报,附带导出表名与查询条件 | | no_permission | 无权限访问 | 无权限页面被访问时手动上报,用于排查权限配置 | | custom_event | 自定义事件 | tracker.send 若未传 event_name 会退回到此默认值(保留能力,目前未实际使用) |

自动附加的上下文字段

每个事件都会自动附加以下上下文信息:

会话上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_session_id | 当前浏览器会话 ID | "6501c4-6b6c" | 基于 sessionStorage,30 分钟无操作会刷新 | | ctx_session_start | 本次会话开始的毫秒时间戳 | 1762805400000 | 与 ctx_elapsed_sec 配合判断会话持续时长 | | ctx_window_id | 当前标签页 ID | "w-98f2..." | 同一账号多标签页同时操作时用于区分 | | ctx_timestamp | 事件捕获时间(毫秒) | 1762809111574 | 若网络阻塞,可与 timestamp_ms 对照 | | ctx_elapsed_sec | 会话开始到事件触发的秒数 | 3200 | 用于分析用户停留时长 | | ctx_event_id | 埋点事件唯一 ID | "a1b2c3...-1762809111574-42" | 由会话 ID + 时间戳 + 计数器拼接 |

页面上下文

路由存在时自动携带的页面视图信息:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_view_id | 当前视图唯一标识(通常为路由 fullPath) | "/settings/profile?tab=basic" | 必带,包含查询参数 | | ctx_view_name | 路由 meta.title 或手动设置的页面名称 | "用户设置" | 若未配置标题则为空 | | ctx_view_path | 路由 path(不含查询参数) | "/settings/profile" | 便于做权限/菜单映射 |

用户上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_user_id | 当前登录用户 ID | "u_123" | 用户唯一标识 | | ctx_user_name | 用户姓名 | "张三" | 空字符串表示尚未拿到用户信息 | | ctx_user_role | 用户角色名称 | "admin" | 按业务侧返回 | | ctx_user_department | 用户所属部门 | "研发部门" | 后端若无该字段则为空 |

设备上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_device_user_agent | 完整 UA 字符串 | "Mozilla/5.0 (...) Chrome/128.0.0.0 Safari/537.36" | 用于兼容性诊断。UA 是 "User-Agent",浏览器在请求网页时发送给服务器的一段标识字符串,里面包含浏览器类型、版本、操作系统、设备等信息 | | ctx_device_browser_name | 浏览器名称 | "Chrome" | 基于 UA 解析 | | ctx_device_browser_version | 浏览器版本 | "128.0.0.0" | 基于 UA 解析 | | ctx_device_browser_engine | 浏览器渲染内核 | "Blink" | 区分 WebKit/Gecko 等 | | ctx_device_os_name | 操作系统名称 | "macOS" | 移动端会返回 Android、iOS 等 | | ctx_device_os_version | 操作系统版本 | "14.5.1" | 若 UA 无法解析则为 unknown | | ctx_device_device_type | 终端类型 | "desktop" | 支持 mobile/tablet/tv | | ctx_device_screen_width | 屏幕宽度(px) | 1440 | 取自 window.screen.width,无法获取时为 -1 | | ctx_device_screen_height | 屏幕高度(px) | 900 | 取自 window.screen.height,无法获取时为 -1 | | ctx_device_language | 浏览器首选语言 | "zh-CN" | 可用于语言偏好分析 |

追踪上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | trace_id | 全局追踪 ID | "f4b2c8a1-9d3e-4f12-b7a6-1e8c9d0f5a2b" | 关联前后端日志,由 setTraceId() 设置 | | action_id | 当前用户操作的唯一编号 | "d3a9c1f4-7b2c-4c5e-9a10-2f3e8c5d6b7a" | 点击触发后,在行动存活期内的事件/请求都会带上,用于把"同一波操作"串起来 | | action_type | 行动类型 | "click" / "form_submit" | 示例:click(由点击自动生成),业务手动 startAction('submit') 时会出现对应类型 | | action_meta | 行动元信息(JSON 结构) | "{\"track_id\":\"search_btn\",\"track_name\":\"查询\",\"tag\":\"BUTTON\"}" | 便于知道是哪个控件触发了这次行动 |

扩展上下文

自定义字段解析器(customFieldResolvers)可以添加额外的上下文信息,字段前缀为 ctx_extra_*

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_extra_page_url | 事件触发时页面的完整 URL | "https://example.com/path?query=1#hash" | 包含协议、域名、路径、查询参数和 hash,方便后端还原用户的实际访问地址 | | ctx_extra_page_title | 当前页面设置的浏览器显示标题 | "用户管理 - 后台系统" | 依赖浏览器 document.title | | ctx_extra_viewport_width | 浏览器可视区域宽度 | 1440 | 记录事件发生时的视口宽度(像素) | | ctx_extra_viewport_height | 浏览器可视区域高度 | 900 | 记录事件发生时的视口高度(像素) |

自动采集事件详情

SDK 会自动采集以下用户行为事件,无需手动调用:

会话事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | session_start | SDK 初始化完成 | 会话开始标记 |

页面浏览事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | view_start | 进入新页面/路由 | view_id, view_name, view_path | | view_end | 离开当前页面 | view_id, duration_ms(停留时长) |

用户交互事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | ui_click | 点击/长按/右键/双击 | click_type, track_id, track_name, element_tag, page_x, page_y | | ui_input | 输入框失焦 | field_name, field_type, value_masked, value_length, form_name |

ui_click 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | click_type | 点击类型 | "left" / "right" / "double" / "longpress" | 区分不同的点击行为 | | element_tag | 触发元素的标签名 | "BUTTON" / "DIV" / "A" | 大写形式 | | element_id | 元素的 id 属性 | "submitBtn" / "" | 无 id 时为空字符串 | | element_classes | 元素 class 列表 | "el-button primary" | 以空格拼接的完整 class 字符串 | | page_x | 点击点相对页面左上角的 X 坐标 | 842 | 单位为像素 | | page_y | 点击点相对页面顶端的 Y 坐标 | 412 | 单位为像素 | | text_mask | 目标元素文本内容的脱敏结果 | "保存修改" / "操作按钮..." | 最多 120 字符,超出截断并追加 ... | | track_id | 元素上 data-track-id 的值 | "btn-save" / "" | 用于区分被点击元素,业务侧可自定义设置 | | track_name | 元素展示文案或 data-track-name | "保存修改" | 供业务阅读,优先取 data-track-name,否则取元素文本 |

ui_input 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | field_name | 失焦输入控件的名称/定位信息 | "account_name" / "email" / "search_keyword" | 优先取 data-track-field,其次 name/id/placeholder | | field_type | 表单控件类型 | "text" / "select" / "textarea" / "checkbox" | 区分不同类型的输入控件 | | form_name | 输入控件所在表单的 name 或 id | "login_form" / "user_profile_form" / "" | 用于关联同一表单内的多个字段,无表单时为空 | | value_masked | 当前值的脱敏结果 | "***" / "[length:8]" / "option1,option2" | 根据 mask 策略脱敏。多选值用逗号连接 | | previous_masked | 上次上报时脱敏后的旧值 | "***" / "[length:5]" / "" | 用于对比值是否发生变化,首次上报时为空 | | value_length | 当前值长度 | 8 / 15 / 2 | 字符串长度或数组项数,帮助判断输入内容的规模 |

网络请求事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | http_request | 发起 HTTP 请求(fetch/XHR/axios) | method, url, status, duration_ms, success, response_size |

http_request 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | request_id | 每次请求埋点的唯一标识 | "e7f3b9dc-2a16-4d1d-9f2d-5de8a2e1c42b" | 用于串联同一条日志,类型为 string | | method | HTTP 请求方法,表示这条 http_request 埋点对应的接口调用类型 | "GET" / "POST" / "PUT" / "DELETE" | 值来源于实际请求,已自动转成大写;常见包括 GET、POST、PUT、DELETE、PATCH、OPTIONS,若业务里发起其他方法(如 HEAD、TRACE)也会按原样上报 | | url | 请求的完整地址 | "https://api.example.com/report?date=2025-09-10" | 包含协议、域名、路径和查询参数 | | status | HTTP 状态码 | 200 / 401 / 500 | 请求返回的 HTTP 状态码,请求失败时为 0 | | duration_ms | 请求耗时(毫秒) | 352 | 自发出请求到收到响应的总耗时,等价于 Network 面板里显示的 Time | | success | 请求是否成功 | true / false | 请求是否视为成功(状态码在 200–399 且未抛错),类型为 boolean | | request_body_digest | 对本次请求 body 的简化摘要 | "{\"name\":\"test\",\"status\"..." | 默认取原始内容或其 JSON 字符串,并在 200 个字符后截断,超出部分以 ... 结尾。帮助排查接口调用时传了哪些核心参数,同时通过截断/掩码降低敏感数据泄露风险 | | response_digest | 响应体的摘要 | "{\"result\":\"ok\",\"list\":[...]}" / "" / "[unserializable]" | 默认截取前 200 个字符,超长追加 ...,类型为 string 或 null | | response_size | 响应体大小估算 | 1250 / undefined | 单位是字节(字符数)。优先级:encoded > decoded > transfer > content-length > 估算。类型为 number 或 undefined(无法计算时) | | response_size_transfer | 传输总大小(含头,压缩后) | 1856 | 单位是字节。来源 Performance API 的 transferSize。跨域无 TAO 或缓存命中时可能为 0/undefined | | response_size_encoded | 压缩后的响应体大小 | 1720 | 单位是字节。Performance 的 encodedBodySize。压缩请求时与 decoded 不同;未压缩时它们相等 | | response_size_decoded | 解压后的响应体大小 | 4096 | 单位是字节。Performance 的 decodedBodySize。可视为原始体积;缓存命中仍可能有值 | | response_size_content_length | 响应头中的 Content-Length 值 | 1720 | 单位是字节。chunked/流式或未返回该头时为空;通常代表压缩后体积 | | response_size_source | 大小取值来源 | "performance" / "header" | 可取 "performance" 或 "header";缺失表示无法确定 | | response_compressed | 是否启用压缩 | true / false | 根据 Content-Encoding 判断。无法读头时为空 | | response_content_encoding | Content-Encoding 值 | "gzip" / "br" / "deflate" | 无法读头或未压缩则为空 | | response_cache_hit | 是否疑似缓存命中 | true / false | 启发式标记(transferSize=0 且有 body size),非严格判定 | | error_message | 请求失败时的错误信息 | "Network Error" / "timeout of 5000ms exceeded" | 仅在请求失败时出现。请求失败(网络错误、状态码非 2xx/3xx、Axios reject 等)时,会尝试提取 error.message、响应体中的报错内容等写入,帮助后端排查接口异常。请求成功时该字段为空或不存在 | | trace_id | 全局追踪 ID | "f4b2c8a1-9d3e-4f12-b7a6-1e8c9d0f5a2b" | 通过请求头 x-trace-id 发送,与 payload 中的 trace_id 对应,便于后端链路关联 | | action_id | 当前操作 ID | "d3a9c1f4-7b2c-4c5e-9a10-2f3e8c5d6b7a" | 通过请求头 x-action-id 发送,与 payload 中的 action_id 对应。同一行动内的请求头值一致 |

手动埋点

除了自动采集,你也可以手动上报自定义事件:

// 简单事件
tracker.track('button_click', {
  button_name: 'signup',
  button_text: '立即注册'
});

// 复杂业务事件
tracker.track('order_submit', {
  order_id: 'ORD_123456',
  amount: 99.99,
  currency: 'CNY',
  item_count: 3
});

表格列宽/拖拽埋点示例(通用写法)

SDK 不强制表格埋点,以下为通用示例,字段请替换为你的业务含义:

VXE Table 列宽拖拽(vxe-table

<!-- template -->
<vxe-table @column-resizable-change="handleResize" ...>
  ...
</vxe-table>
import tracker from '@/utils/logger'; // 你的业务封装

const handleResize = ({ column }: { column: any }) => {
  tracker.track('adjust_column_width', {
    table: 'example_table', // 自定义表名
    field: column?.property || column?.field || '',
    field_label: column?.title || '',
    width: column?.resizeWidth,
  });
};

VXE Table 行拖拽(Sortable)

import Sortable from 'sortablejs';
import tracker from '@/utils/logger';

const rowDrop = () => {
  const $table = tableRef.value;
  Sortable.create($table.$el.querySelector('.vxe-table--body-wrapper tbody'), {
    handle: '.drag-btn',
    onEnd({ oldIndex, newIndex }) {
      tracker.track('table_row_reorder', {
        table: 'example_table',
        from_index: oldIndex,
        to_index: newIndex,
        row_id: rows[oldIndex]?.id,
      });
    },
  });
};

Element Plus el-table 列宽拖拽

<!-- template -->
<el-table :data="rows" border @header-dragend="handleHeaderDragend">
  ...
</el-table>
import tracker from '@/utils/logger';

const handleHeaderDragend = (newWidth: number, oldWidth: number, column: any) => {
  tracker.track('adjust_column_width', {
    table: 'example_table',
    field: column?.property || column?.prop || '',
    field_label: column?.label || '',
    width: newWidth,
    old_width: oldWidth,
  });
};

以上仅为示例,请用你的表名/业务字段替换占位符。

完整事件示例

{
  "event_type": "ui_click",
  "timestamp": "2024-05-20 10:24:11",
  "timestamp_ms": "1716181451000",
  "click_type": "left",
  "track_id": "ad-card-cta",
  "track_name": "立即报名",
  "element_tag": "BUTTON",
  "element_id": "signup-btn",
  "element_classes": "btn btn-primary",
  "text_mask": "立即报名",
  "page_x": 812,
  "page_y": 364,
  "ctx_session_id": "6501c4-6b6c",
  "ctx_session_start": "1716178251000",
  "ctx_window_id": "a7f3e9-2c1d",
  "ctx_timestamp": "1716181451000",
  "ctx_elapsed_sec": "3200",
  "ctx_event_id": "6501c4-6b6c-1716181451000-42",
  "ctx_view_id": "/campaign/list",
  "ctx_view_name": "Campaign List",
  "ctx_view_path": "/campaign/list",
  "ctx_user_id": "u_123",
  "ctx_user_name": "张三",
  "ctx_user_role": "admin",
  "ctx_device_browser_name": "Chrome",
  "ctx_device_browser_version": "124.0.6367.118",
  "ctx_device_os_name": "macOS",
  "action_id": "f4b2c8a1-9d3e-4f12-b7a6-1e8c9d0f5a2b",
  "action_type": "click"
}

💡 提示: 完整的字段定义和数据契约请参考 operation-log-reference.mdx

关键配置说明

  • endpoint/project/logstore/fetchCredential:必填,由使用方提供,勿硬编码敏感值到 SDK 内。
  • router:用于路由级视图埋点;非路由视图可调用 setView
  • axiosInstances / instrumentFetch / instrumentXHR:启用请求埋点并传入实例。
  • ignoredUrls:可过滤静态资源或不需埋点的接口。
  • context.getUserInfo/refreshContextUser:提供当前用户信息,可加掩码处理。

发布提示

  • 发布前检查 .npmignore/files 确保只带源码与类型;无敏感配置。
  • 可在 CI 中加入 git-secrets/敏感信息扫描。
  • 根据需要补充构建脚本(rollup/tsc)与类型打包。