/** * 平台埋点 SDK(uni-app / Vue 通用) * * 使用方式: * 1. 复制到前端项目 utils/analytics.js * 2. 初始化:Analytics.init({ baseUrl: 'https://api.xxx.com', getToken: () => uni.getStorageSync('token') }) * 3. 上报:Analytics.report('MERCHANT_VIEW', { merchantId: 1001 }) * * 注意:请勿再调用旧接口 POST /track/event */ let config = { baseUrl: '', getToken: () => '', channel: '', city: '', }; function uuid() { return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } function getDeviceType() { // #ifdef APP-PLUS const platform = uni.getSystemInfoSync().platform; if (platform === 'ios') return 'IOS'; if (platform === 'android') return 'ANDROID'; // #endif // #ifdef MP-WEIXIN return 'MINI_PROGRAM'; // #endif return 'H5'; } function request(url, data) { return new Promise((resolve, reject) => { uni.request({ url: `${config.baseUrl}${url}`, method: 'POST', data, header: { 'Content-Type': 'application/json', Authorization: config.getToken() || '', }, success: (res) => { if (res.data && res.data.success) { resolve(res.data); } else { reject(res.data || res); } }, fail: reject, }); }); } const Analytics = { init(options = {}) { config = { ...config, ...options }; }, /** * 统一埋点上报 * @param {string} scene 场景码,如 MERCHANT_VIEW * @param {object} payload 业务参数 */ report(scene, payload = {}) { const body = { scene, eventId: uuid(), userId: payload.userId, merchantId: payload.merchantId, targetId: payload.targetId || payload.contentId, contentType: payload.contentType, amount: payload.amount, durationMs: payload.durationMs, deviceType: payload.deviceType || getDeviceType(), channel: payload.channel || config.channel, city: payload.city || config.city, }; return request('/analytics/front/report', body); }, /** 批量上报 */ batch(events) { const list = (events || []).map((item) => ({ scene: item.scene, eventId: uuid(), userId: item.userId, merchantId: item.merchantId, targetId: item.targetId || item.contentId, contentType: item.contentType, amount: item.amount, durationMs: item.durationMs, deviceType: item.deviceType || getDeviceType(), channel: item.channel || config.channel, city: item.city || config.city, })); return request('/analytics/front/batch', { events: list }); }, /** 在线心跳,durationMs 为距上次心跳的毫秒数 */ heartbeat(durationMs, extra = {}) { return request('/analytics/front/heartbeat', { durationMs, deviceType: extra.deviceType || getDeviceType(), channel: extra.channel || config.channel, city: extra.city || config.city, }); }, /** App 启动 */ onLaunch(extra = {}) { return this.report('APP_LAUNCH', extra); }, /** 商家浏览埋点 */ merchantView(data) { return request('/analytics/front/merchant/view', { eventId: data.eventId || uuid(), merchantId: data.merchantId, shopType: data.shopType, visitUv: data.visitUv, visitPv: data.visitPv, userId: data.userId, }); }, /** 访问商户(委托 merchantView) */ onMerchantView(merchantId, extra = {}) { return this.merchantView({ merchantId, shopType: extra.shopType, visitUv: extra.visitUv != null ? extra.visitUv : 0, visitPv: extra.visitPv != null ? extra.visitPv : 1, userId: extra.userId, eventId: extra.eventId, }); }, /** 内容互动(点赞/评论/收藏),increment 默认 +1 */ onContentInteract(contentId, contentType, extra = {}) { return this.contentInteract({ contentId, contentType, increment: extra.increment != null ? extra.increment : 1, userId: extra.userId, eventId: extra.eventId, }); }, /** 内容互动埋点 */ contentInteract(data) { return request('/analytics/front/content/interact', { eventId: data.eventId || uuid(), contentId: data.contentId, contentType: data.contentType, increment: data.increment, userId: data.userId, }); }, /** AI 请求上报 */ aiRequest(data) { return request('/analytics/front/ai-request', { requestId: data.requestId || uuid(), apiName: data.apiName, apiUrl: data.apiUrl, responseDurationMs: data.responseDurationMs, isTimeout: data.isTimeout, }); }, /** 用户注册埋点 */ userRegister(data) { return request('/analytics/front/user/register', { eventId: data.eventId || uuid(), userId: data.userId, userPhone: data.userPhone, registerTime: data.registerTime, channel: data.channel, city: data.city, }); }, /** 用户登录埋点 */ userLogin(data) { return request('/analytics/front/user/login', { eventId: data.eventId || uuid(), userId: data.userId, firstLaunchTime: data.firstLaunchTime, lastActiveTime: data.lastActiveTime, city: data.city, deviceName: data.deviceName, }); }, /** 用户登出埋点 */ userLogout(data) { return request('/analytics/front/user/logout', { eventId: data.eventId || uuid(), userId: data.userId, firstLaunchTime: data.firstLaunchTime, lastActiveTime: data.lastActiveTime, city: data.city, deviceName: data.deviceName, onlineDurationMin: data.onlineDurationMin, }); }, /** AI 对话结束埋点 */ aiChatEnd(data) { return request('/analytics/front/ai-chat/end', { eventId: data.eventId || uuid(), chatId: data.chatId, userId: data.userId, startTime: data.startTime, messageCount: data.messageCount, aiResponseDurationMs: data.aiResponseDurationMs, }); }, /** 内容发布埋点 */ contentPublish(data) { return request('/analytics/front/content/publish', { eventId: data.eventId || uuid(), contentId: data.contentId, contentType: data.contentType, authorType: data.authorType, authorId: data.authorId, publishTime: data.publishTime, }); }, /** 启动心跳定时器,默认60秒 */ startHeartbeat(intervalMs = 60000) { if (this._heartbeatTimer) { clearInterval(this._heartbeatTimer); } let last = Date.now(); this._heartbeatTimer = setInterval(() => { const now = Date.now(); this.heartbeat(now - last).catch(() => {}); last = now; }, intervalMs); }, stopHeartbeat() { if (this._heartbeatTimer) { clearInterval(this._heartbeatTimer); this._heartbeatTimer = null; } }, }; export default Analytics;