amis 页面交互行为跟踪(tracker)实战指南:从采集到上报的完整方案
2026/9/14 18:38:17 网站建设 项目流程

amis 页面交互行为跟踪(tracker)实战指南:从采集到上报的完整方案

【免费下载链接】amis前端低代码框架,通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis

amis 从 1.5.0 版本起内置了用户交互行为采集功能,通过渲染时注入的tracker函数即可统一收集 api 请求、按钮点击、弹框开关、表单修改、Tab 切换、页面生命周期等十余种行为事件。本文基于 tracker.md 文档,结合 amis-core 源码深入讲解 tracker 的接入方式、事件数据结构、各事件触发场景与上报最佳实践,读完即可在自己的 amis 应用中落地一套可分析、可检索的用户行为埋点体系。

设计思想:采集与存储解耦

amis 的 tracker 遵循一条明确的分工原则:amis 只负责采集,对行为的存储和分析都由外部实现。也就是说,amis 不内置任何埋点存储、上报队列或分析面板,它只在你提供的回调函数中抛出标准化的行为事件对象,至于如何持久化、如何聚合、如何可视化,完全由接入方根据自身的数据平台决定。

这一点从 amis-core 的源码可以得到印证:在 factory.tsx 中,默认环境的tracker是一个空函数tracker(eventTrack, props) {};而在 env.tsx 定义的RendererEnv接口中,tracker的签名固定为:

tracker: (eventTrack: EventTrack, props?: PlainObject) => void;

调用方不实现该函数时,所有事件都会被静默丢弃;一旦实现,amis 便会在各类交互节点自动调用它。这种"采集框架 + 业务侧存储"的组合,让 amis 的埋点能力既能开箱即用,又能无缝接入任意现有数据分析体系。

使用方法:通过 env 注入 tracker

在使用 amis 渲染时,第三个参数env中可以传递tracker函数。下面以 SDK 的amis.embed为例:

amis.embed( '#root', { // amis schema }, { // 这里是初始 props }, { tracker: (eventTrack, props) => { const blob = new Blob([JSON.stringify(eventTrack)], { type: 'application/json' }); navigator.sendBeacon('/tracker', blob); } } );

其中四个参数依次是:容器选择器、页面 schema、初始 props、环境配置 env。tracker就是 env 上的一个普通属性,因此无论是 SDK、React(通过<Renderer>组件的 env 属性)还是其他嵌入方式,注入方式完全一致。

示例中用到了navigator.sendBeacon上报,这是埋点场景的推荐做法:它不阻塞页面卸载、不依赖 XHR 生命周期,适合在页面关闭或跳转时兜底上报。官方文档也明确提示,"具体实现可以根据实际需求修改,比如可以收集一段时间后再批量提交等"——即可以在tracker内部做缓冲、去重、合并,再按固定时间窗口批量发送,以降低对服务端的请求压力。

tracker 与 fetcher 的关系

从源码看,tracker并不是零散地挂在每个组件上,而是被统一编织进请求链路。factory.tsx 中有这样一行:

options.fetcher = wrapFetcher(options.fetcher, options.tracker) as any;

wrapFetcher(定义于 api.ts)会对全局的 fetcher 做一层包装,在每次真实请求发出前调用 tracker 并抛出api事件。这意味着只要某个组件最终走的是统一的 fetcher 通道(包括所有组件的api/source配置、action 的 ajax 类型请求),它的请求行为就一定会被采集,无需为每个组件单独写埋点代码。

参数类型:EventTrack 的定义

tracker第一个参数eventTrack的类型为EventTrack,完整定义在 types.ts:

export interface EventTrack { /** * 事件类型 */ eventType: | 'api' | 'url' | 'link' | 'dialog' | 'drawer' | 'copy' | 'reload' | 'email' | 'prev' | 'next' | 'cancel' | 'close' | 'submit' | 'confirm' | 'reset' | 'reset-and-submit' | 'formItemChange' | 'tabChange' | 'pageLoaded' | 'pageHidden' | 'pageVisible' | string; /** * 事件数据 */ eventData?: PlainObject | Api; }

与官方文档中的定义相比,源码中的联合类型多了一个| string兜底(eventType允许任意字符串),同时eventData被声明为可选。实际接入时建议以文档列出的 21 种事件类型为准,eventData的具体结构因事件类型而异,下文逐类说明。

tracker 的第二个参数 props

tracker的第二个参数props(源码中标注为PlainObject)可以拿到触发事件组件的所有属性配置。当eventData无法区分两个行为完全相同的组件时,这个参数是重要的补充判断依据——例如通过props中的字段判断事件来自哪个表单、哪个列表页。需要注意的是,这个参数的具体内容随触发组件不同而变化,应结合具体场景谨慎使用。

区分相同的点击行为:为组件添加 id

eventData默认只包含组件的 schema 配置。当页面中存在两个配置完全相同的按钮时,例如两个"提交"按钮:

[ { "label": "提交", "primary": true }, { "label": "提交", "primary": true } ]

点击它们触发的事件内容是相同的:

{ "eventType": "submit", "eventData": { "primary": true, "label": "提交" } }

此时无法从eventData判断用户点击的是哪一个。解决办法是给组件加上id属性:

[ { "id": "button1", "label": "提交", "primary": true }, { "id": "button2", "label": "提交", "primary": true } ]

这样事件中就会包含id字段,从而精确区分:

{ "eventType": "submit", "eventData": { "id": "button1", "primary": true, "label": "提交" } }

同理,表单项的formItemChange事件中,如果表单项配置了id,也会被透传到eventData中,方便区分同名输入框。这是埋点分析中保证事件可区分性的两个核心手段之一(另一个就是使用第二个参数props)。

事件详解

以下是各事件类型的触发场景与数据示例。除了下面的文档说明,还可以打开浏览器的控制台,在debug分类下直接看到实际操作时的事件示例,方便对照调试。

api

api事件的来源有两个方面:一是各种组件的apisource配置,二是 action 里的 ajax 类型请求。以 crud 为例,GET 请求事件如下:

{ "eventType": "api", "eventData": { "method": "get", "url": "/api/mock2/sample?page=1&perPage=10", "query": { "page": 1, "perPage": 10 } } }

POST 请求事件类似:

{ "eventType": "api", "eventData": { "method": "post", "url": "/api/mock2/form/saveForm" } }

注意:为了避免信息泄露,eventData中没有包含提交数据。这一设计在源码中有明确体现:api.ts 中调用 tracker 时使用了omit(api, ['config', 'data', 'body']),即把可能包含敏感字段的configdatabody全部剔除后再抛出。如果业务确实需要提交数据的详情,可以通过第二个参数拿到:

{ tracker: (eventTrack, data) => { console.log('提交数据详情', data); const blob = new Blob([JSON.stringify(eventTrack)], { type: 'application/json' }); navigator.sendBeacon('/tracker', blob); }; }

data即本次请求的完整提交数据。基于同样的隐私考虑,建议在 api 类事件的落库策略中不记录数据提交内容(详见下文 formItemChange 的说明)。

url

url是打开外部链接事件。注意不要和link混淆link一般用于应用内相对地址的无刷新跳转,而url对应的是新开页签或整页跳转的外部链接。

Link 组件触发的示例:

{ "eventType": "url", "eventData": { "url": "https://www.baidu.com", "blank": true, "label": "百度一下,你就知道" } }

Action 组件触发的示例(Action 组件会多出level等数据):

{ "eventType": "url", "eventData": { "url": "http://www.baidu.com", "level": "success", "blank": true, "label": "打开 Baidu" } }

link

link事件主要由 Action 和 Nav 组件触发,用于应用内相对地址跳转。

Action 触发的数据:

{ "eventType": "link", "eventData": { "label": "进入介绍页", "level": "info", "link": "../index" } }

Nav 触发的数据:

{ "eventType": "link", "eventData": { "label": "Nav 2-2", "link": "?cat=2-2" } }

它们都有labellink字段,可作为这类事件的通用解析约定。

dialog

dialog事件主要由 action 触发,示例:

{ "eventType": "dialog", "eventData": { "dialog": { "title": "提示", "closeOnEsc": true, "body": "这是个简单的弹框" }, "label": "打开弹框" } }

需要注意:dialog里会包含弹框的全部 schema 配置,schema 复杂时可能导致提交数据过大,建议根据需求在落库前进行字段裁剪。

drawer

drawer事件主要由 action 触发,示例:

{ "eventType": "drawer", "eventData": { "drawer": { "position": "left", "size": "xs", "title": "提示", "body": "这是个简单的弹框" }, "label": "左侧弹出-极小框" } }

与 dialog 一样,drawer数据中会包含所有抽屉配置,可能内容过大,需要根据需求过滤后再存储。

copy

copy事件由 action 触发,在用户点击复制类 action 时上报,示例:

{ "eventType": "copy", "eventData": { "content": "http://www.baidu.com", "label": "复制一段文本" } }

reload

reload事件由 action 触发,用于记录刷新/重载类操作,示例:

{ "eventType": "reload", "eventData": { "label": "搜索", "target": "my_form.select" } }

其中target是本次 reload 的目标组件标识。

email

email事件由 action 触发,用于记录发送邮件类操作,示例:

{ "eventType": "email", "eventData": { "to": "amis@baidu.com", "cc": "baidu@baidu.com", "subject": "这是邮件主题", "body": "这是邮件正文", "label": "发送邮件" } }

prev / next

prevnext事件可能出现在两个地方:Wizard 组件里的上一步/下一步,以及 Dialog 里的上一个/下一个。示例:

{ "eventType": "next", "eventData": { "level": "info", "label": "下一个" } }

cancel

cancel是 action 里的取消事件,示例:

{ "eventType": "cancel", "eventData": { "label": "关闭" } }

close

close是 action 里的关闭事件,主要用于关闭弹框,示例:

{ "eventType": "close", "eventData": { "label": "算了" } }

submit

submit是点击提交按钮的事件。需要注意,这个事件可能还会同时触发api事件,比如表单的提交按钮会先触发submit,随后发出请求时再触发api。示例:

{ "eventType": "submit", "eventData": { "primary": true, "label": "提交" } }

confirm

confirm是 action 中的事件,主要用于确认类操作(如关闭弹框前的确认),示例:

{ "eventType": "confirm", "eventData": { "primary": true, "label": "确认" } }

reset

reset由 action 触发,主要用于重置表单数据,示例:

{ "eventType": "reset", "eventData": { "label": "重置" } }

reset-and-submit

reset-and-submit由 action 触发,会重置表单并提交数据,示例:

{ "eventType": "reset-and-submit", "eventData": { "label": "重置并提交" } }

formItemChange

formItemChange在表单项数据变化时触发——即用户在表单里输入或修改任何数据时都会触发,示例:

{ "eventType": "formItemChange", "eventData": { "name": "name", "label": "用户名", "type": "input-text", "value": "amis" } }

事件数据里主要是nametypevalue。需要注意:

  • 这个事件非常频繁,只要修改内容就会触发,落库时需要做频率控制和采样,避免数据量爆炸;
  • 有一个特例:input-password类型的字段不会触发这个事件,以避免隐私风险。这一行为在源码中有明确实现:wrapControl.tsx 中调用 tracker 前判断了type !== 'input-password',且事件携带idnamelabeltypevalue五个字段,第二个参数传的是组件自身的this.props
  • 尽管密码字段不触发该事件,在 api 事件中提交的数据里仍有可能包含隐私信息,因此官方建议对 api 类事件同样不记录数据提交内容(前文已说明 api 事件默认会剔除data/body);
  • 与 action 类似,给表单项加上id字段也会透传到这里,方便区分同名输入框:
{ "eventType": "formItemChange", "eventData": { "id": "name1", "name": "name", "label": "用户名", "type": "input-text", "value": "amis" } }

tabChange

tabChange是 Tab 切换事件,示例:

{ "eventType": "tabChange", "eventData": { "key": "tab2" } }

默认情况下key的值从0开始;如果 Tab 上设置了hash值,就会用这个值。同样,如果 Tabs 设置了id,也会输出该id方便区分。

pageHidden

pageHidden在 Tab 切换或者页面关闭时触发,可以当成用户离开页面的时间点。从源码看(RootRenderer.tsx),该事件基于浏览器的visibilitychange事件实现:当document.visibilityState === 'hidden'时抛出pageHidden。事件没有eventData,需要配合时间戳使用。

pageVisible

pageVisible在用户又切换回当前页面时触发,可以当作重新访问的开始时间。同样基于visibilitychange事件:当document.visibilityState === 'visible'时抛出(见 RootRenderer.tsx)。

注意:由于 amis 可能被嵌入到页面中,amis 无法知晓页面首次打开的时间,首次访问的开始时间需要接入方在宿主页面自行处理(例如在页面初始化时记录一次自定义事件)。

pageLoaded

pageLoaded在 Page 组件加载完成时触发,可用于收集页面首次打开的时间。使用前提是当前页面必须有 Page 组件;该事件在 2.8.0 以上版本支持(在 types.ts 的EventTrack事件类型联合中也已包含pageLoaded)。

调试与最佳实践

控制台 debug 日志

开发调试时,打开浏览器的控制台,在debug分类下可以看到实际操作时触发的事件示例,无需断点即可直观确认各类事件的字段是否符合预期。这是验证埋点配置是否生效的最快路径。

上报策略建议

综合文档与源码,落地生产环境埋点时建议关注以下几点:

  1. 批量上报tracker回调内部可以缓冲事件,按时间窗口(如每 5~10 秒)或条数阈值批量提交,降低请求量;
  2. 兜底上报:页面关闭、Tab 切换时触发的事件(如pageHidden)使用navigator.sendBeacon上报,避免请求被浏览器取消;
  3. 字段裁剪dialogdrawer等事件可能携带完整 schema,formItemChange非常频繁,都应在落库前裁剪或采样;
  4. 隐私保护:api 事件的eventData默认已剔除config/data/body,密码字段不触发formItemChange,接入方在上报链路中应继续保持这一策略,不额外记录敏感提交内容;
  5. 事件区分:对行为相同的组件(同名按钮、同名输入框)配置id,或利用第二个参数props补充上下文,确保事件可归一到具体业务组件。

总结

amis 的 tracker 是一套"一次注入、全站采集"的行为跟踪方案:接入方只需在 env 中提供一个函数,即可获得 api、url、link、dialog、drawer、copy、reload、email、prev/next、cancel/close/submit/confirm、reset、reset-and-submit、formItemChange、tabChange 以及 pageHidden/pageVisible/pageLoaded 等完整的事件流。采集逻辑内置在 amis-core 的 fetcher 包装层(api.ts)、表单包装层(wrapControl.tsx)与根渲染器(RootRenderer.tsx)中,类型定义集中在 types.ts,默认空实现位于 factory.tsx。配合id区分、批量上报、字段裁剪与隐私保护策略,即可构建一套稳定、可分析的用户行为数据体系。

【免费下载链接】amis前端低代码框架,通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询