analytics-front-sdk.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /**
  2. * 平台埋点 SDK(uni-app / Vue 通用)
  3. *
  4. * 使用方式:
  5. * 1. 复制到前端项目 utils/analytics.js
  6. * 2. 初始化:Analytics.init({ baseUrl: 'https://api.xxx.com', getToken: () => uni.getStorageSync('token') })
  7. * 3. 上报:Analytics.report('MERCHANT_VIEW', { merchantId: 1001 })
  8. *
  9. * 注意:请勿再调用旧接口 POST /track/event
  10. */
  11. let config = {
  12. baseUrl: '',
  13. getToken: () => '',
  14. channel: '',
  15. city: '',
  16. };
  17. function uuid() {
  18. return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  19. const r = (Math.random() * 16) | 0;
  20. const v = c === 'x' ? r : (r & 0x3) | 0x8;
  21. return v.toString(16);
  22. });
  23. }
  24. function getDeviceType() {
  25. // #ifdef APP-PLUS
  26. const platform = uni.getSystemInfoSync().platform;
  27. if (platform === 'ios') return 'IOS';
  28. if (platform === 'android') return 'ANDROID';
  29. // #endif
  30. // #ifdef MP-WEIXIN
  31. return 'MINI_PROGRAM';
  32. // #endif
  33. return 'H5';
  34. }
  35. function request(url, data) {
  36. return new Promise((resolve, reject) => {
  37. uni.request({
  38. url: `${config.baseUrl}${url}`,
  39. method: 'POST',
  40. data,
  41. header: {
  42. 'Content-Type': 'application/json',
  43. Authorization: config.getToken() || '',
  44. },
  45. success: (res) => {
  46. if (res.data && res.data.success) {
  47. resolve(res.data);
  48. } else {
  49. reject(res.data || res);
  50. }
  51. },
  52. fail: reject,
  53. });
  54. });
  55. }
  56. const Analytics = {
  57. init(options = {}) {
  58. config = { ...config, ...options };
  59. },
  60. /**
  61. * 统一埋点上报
  62. * @param {string} scene 场景码,如 MERCHANT_VIEW
  63. * @param {object} payload 业务参数
  64. */
  65. report(scene, payload = {}) {
  66. const body = {
  67. scene,
  68. eventId: uuid(),
  69. userId: payload.userId,
  70. merchantId: payload.merchantId,
  71. targetId: payload.targetId || payload.contentId,
  72. contentType: payload.contentType,
  73. amount: payload.amount,
  74. durationMs: payload.durationMs,
  75. deviceType: payload.deviceType || getDeviceType(),
  76. channel: payload.channel || config.channel,
  77. city: payload.city || config.city,
  78. };
  79. return request('/analytics/front/report', body);
  80. },
  81. /** 批量上报 */
  82. batch(events) {
  83. const list = (events || []).map((item) => ({
  84. scene: item.scene,
  85. eventId: uuid(),
  86. userId: item.userId,
  87. merchantId: item.merchantId,
  88. targetId: item.targetId || item.contentId,
  89. contentType: item.contentType,
  90. amount: item.amount,
  91. durationMs: item.durationMs,
  92. deviceType: item.deviceType || getDeviceType(),
  93. channel: item.channel || config.channel,
  94. city: item.city || config.city,
  95. }));
  96. return request('/analytics/front/batch', { events: list });
  97. },
  98. /** 在线心跳,durationMs 为距上次心跳的毫秒数 */
  99. heartbeat(durationMs, extra = {}) {
  100. return request('/analytics/front/heartbeat', {
  101. durationMs,
  102. deviceType: extra.deviceType || getDeviceType(),
  103. channel: extra.channel || config.channel,
  104. city: extra.city || config.city,
  105. });
  106. },
  107. /** App 启动 */
  108. onLaunch(extra = {}) {
  109. return this.report('APP_LAUNCH', extra);
  110. },
  111. /** 商家浏览埋点 */
  112. merchantView(data) {
  113. return request('/analytics/front/merchant/view', {
  114. eventId: data.eventId || uuid(),
  115. merchantId: data.merchantId,
  116. shopType: data.shopType,
  117. visitUv: data.visitUv,
  118. visitPv: data.visitPv,
  119. userId: data.userId,
  120. });
  121. },
  122. /** 访问商户(委托 merchantView) */
  123. onMerchantView(merchantId, extra = {}) {
  124. return this.merchantView({
  125. merchantId,
  126. shopType: extra.shopType,
  127. visitUv: extra.visitUv != null ? extra.visitUv : 0,
  128. visitPv: extra.visitPv != null ? extra.visitPv : 1,
  129. userId: extra.userId,
  130. eventId: extra.eventId,
  131. });
  132. },
  133. /** 内容互动(点赞/评论/收藏),increment 默认 +1 */
  134. onContentInteract(contentId, contentType, extra = {}) {
  135. return this.contentInteract({
  136. contentId,
  137. contentType,
  138. increment: extra.increment != null ? extra.increment : 1,
  139. userId: extra.userId,
  140. eventId: extra.eventId,
  141. });
  142. },
  143. /** 内容互动埋点 */
  144. contentInteract(data) {
  145. return request('/analytics/front/content/interact', {
  146. eventId: data.eventId || uuid(),
  147. contentId: data.contentId,
  148. contentType: data.contentType,
  149. increment: data.increment,
  150. userId: data.userId,
  151. });
  152. },
  153. /** AI 请求上报 */
  154. aiRequest(data) {
  155. return request('/analytics/front/ai-request', {
  156. requestId: data.requestId || uuid(),
  157. apiName: data.apiName,
  158. apiUrl: data.apiUrl,
  159. responseDurationMs: data.responseDurationMs,
  160. isTimeout: data.isTimeout,
  161. });
  162. },
  163. /** 用户注册埋点 */
  164. userRegister(data) {
  165. return request('/analytics/front/user/register', {
  166. eventId: data.eventId || uuid(),
  167. userId: data.userId,
  168. userPhone: data.userPhone,
  169. registerTime: data.registerTime,
  170. channel: data.channel,
  171. city: data.city,
  172. });
  173. },
  174. /** 用户登录埋点 */
  175. userLogin(data) {
  176. return request('/analytics/front/user/login', {
  177. eventId: data.eventId || uuid(),
  178. userId: data.userId,
  179. firstLaunchTime: data.firstLaunchTime,
  180. lastActiveTime: data.lastActiveTime,
  181. city: data.city,
  182. deviceName: data.deviceName,
  183. });
  184. },
  185. /** 用户登出埋点 */
  186. userLogout(data) {
  187. return request('/analytics/front/user/logout', {
  188. eventId: data.eventId || uuid(),
  189. userId: data.userId,
  190. firstLaunchTime: data.firstLaunchTime,
  191. lastActiveTime: data.lastActiveTime,
  192. city: data.city,
  193. deviceName: data.deviceName,
  194. onlineDurationMin: data.onlineDurationMin,
  195. });
  196. },
  197. /** AI 对话结束埋点 */
  198. aiChatEnd(data) {
  199. return request('/analytics/front/ai-chat/end', {
  200. eventId: data.eventId || uuid(),
  201. chatId: data.chatId,
  202. userId: data.userId,
  203. startTime: data.startTime,
  204. messageCount: data.messageCount,
  205. aiResponseDurationMs: data.aiResponseDurationMs,
  206. });
  207. },
  208. /** 内容发布埋点 */
  209. contentPublish(data) {
  210. return request('/analytics/front/content/publish', {
  211. eventId: data.eventId || uuid(),
  212. contentId: data.contentId,
  213. contentType: data.contentType,
  214. authorType: data.authorType,
  215. authorId: data.authorId,
  216. publishTime: data.publishTime,
  217. });
  218. },
  219. /** 启动心跳定时器,默认60秒 */
  220. startHeartbeat(intervalMs = 60000) {
  221. if (this._heartbeatTimer) {
  222. clearInterval(this._heartbeatTimer);
  223. }
  224. let last = Date.now();
  225. this._heartbeatTimer = setInterval(() => {
  226. const now = Date.now();
  227. this.heartbeat(now - last).catch(() => {});
  228. last = now;
  229. }, intervalMs);
  230. },
  231. stopHeartbeat() {
  232. if (this._heartbeatTimer) {
  233. clearInterval(this._heartbeatTimer);
  234. this._heartbeatTimer = null;
  235. }
  236. },
  237. };
  238. export default Analytics;