Commit 6e184a80 by yuzhenWang

分享上报,合并代码解决冲突

parents 851add20 658e8104
......@@ -3,6 +3,89 @@
<head>
<meta charset="UTF-8" />
<script>
// ============ 真机调试:vConsole 控制台 ============
// 默认开启:公众号/微信里打开 CFFP 即出现右下角绿色 vConsole 按钮,无需带参数
// 关闭:URL 追加 ?vconsole=0(本机记住,之后不再自动开);恢复:?vconsole=1
// 上线前如需全量关闭:把下面 VCONSOLE_DEFAULT_ON 改成 false
(function() {
try {
var VCONSOLE_DEFAULT_ON = false;
var qs = (location.search || '') + '&' + (location.hash || '');
var m = qs.match(/[?&]vconsole=(1|true|0|false)/i);
var offFlag = localStorage.getItem('__vconsole_off') === '1';
var on = false;
if (m) {
// URL 显式指定优先级最高
if (/^(1|true)$/i.test(m[1])) {
on = true;
localStorage.removeItem('__vconsole_off');
} else {
on = false;
localStorage.setItem('__vconsole_off', '1');
}
} else {
// 无参数:默认开启,但尊重本机上次的手动关闭
on = VCONSOLE_DEFAULT_ON && !offFlag;
}
if (!on) return;
// vConsole 加载前的日志先缓存,就绪后补打出来
var buf = [], MAX = 300, replayed = false;
var methods = ['log', 'info', 'warn', 'error'];
methods.forEach(function(name) {
var orig = console[name] ? console[name].bind(console) : function() {};
console[name] = function() {
try {
if (!replayed && buf.length < MAX) {
buf.push([name, Array.prototype.slice.call(arguments)]);
}
} catch (e) {}
orig.apply(null, arguments);
};
});
function replay() {
try {
var list = buf;
buf = [];
replayed = true;
list.forEach(function(it) {
(console[it[0]] || console.log).apply(console, it[1]);
});
} catch (e) {}
}
function boot() {
try {
window.vConsole = new window.VConsole({
theme: 'light',
maxLogNumber: 1000,
onReady: function() {
replay();
console.log('%c[vConsole] 已就绪 → 分享后在 Log 里看 [Tracker] 日志', 'color:#1565c0;font-weight:bold');
}
});
} catch (e) {
console.error('[vConsole] 初始化失败', e);
}
}
function load(src, onError) {
var s = document.createElement('script');
s.src = src;
s.onload = boot;
s.onerror = onError || function() { console.warn('[vConsole] 脚本加载失败: ' + src); };
document.head.appendChild(s);
}
// 本地优先(微信内更稳),失败回退 CDN
load('./static/js/vconsole.min.js', function() {
load('https://cdn.jsdelivr.net/npm/vconsole@3.15.1/dist/vconsole.min.js');
});
} catch (e) {
console.warn('[vConsole] 加载器异常', e);
}
})();
// ============ vConsole 结束 ============
</script>
<script>
// ============ 新增:解析 URL 参数并设置状态栏占位 ============
//解决项目以h5的方式内嵌到app的webview中,手机状态栏和页面顶部内容重叠的问题
(function() {
......
......@@ -319,6 +319,7 @@
import customSegmentedControl from '@/components/customSegmentedControl/customSegmentedControl.vue';
import wx from 'weixin-js-sdk'
import { previewImages } from '@/util/previewImage.js';
import { trackShare } from '@/util/tracker/manual.js';
export default {
name: 'businessCard',
......@@ -835,6 +836,11 @@
cardId: this.cardId,
commonId: this.userId
}
// 手动埋点:上报用户分享操作
trackShare(shareInfo.title, '', {
sharePage: shareInfo.sharePage,
channel: 'wechat-miniprogram'
})
wx.miniProgram.postMessage({
data: {
type: 'share',
......
......@@ -270,6 +270,10 @@
import ShareGuide from '@/components/ShareGuide/ShareGuide.vue';
import wx from 'weixin-js-sdk'
import { previewImages } from '@/util/previewImage.js';
import { trackShare } from '@/util/tracker/manual.js';
export default {
components: {
UniShareWx,
......@@ -493,6 +497,20 @@
shareCode: shareCode,
isSaveShare: '1', //是否存贮分享便于统计分享数据
}
// 手动埋点:上报用户分享操作
trackShare(shareInfo.title, '', {
shareCode: shareInfo.shareCode,
fileId: shareInfo.fileId,
sharePage: shareInfo.sharePage,
channel: 'wechat-miniprogram'
})
// 手动埋点:上报用户分享操作
trackShare(shareInfo.title, '', {
shareCode: shareInfo.shareCode,
fileId: shareInfo.fileId,
sharePage: shareInfo.sharePage,
channel: 'wechat-miniprogram'
})
wx.miniProgram.postMessage({
data: {
type: 'share',
......
......@@ -158,6 +158,8 @@
import UQRCode from 'uqrcodejs';
import wx from 'weixin-js-sdk'
import { elementToImage } from '@/util/htmlToImage';
import { trackShare } from '@/util/tracker/manual.js';
export default {
name: 'courselist',
props: {
......@@ -405,6 +407,13 @@
shareCode:shareCode,
isSaveShare:'1',//是否存贮分享便于统计分享数据
}
// 手动埋点:上报用户分享操作
trackShare(shareInfo.title, '', {
shareCode: shareInfo.shareCode,
fileId: shareInfo.fileId,
sharePage: shareInfo.sharePage,
channel: 'wechat-miniprogram'
})
wx.miniProgram.postMessage({
data: {
type: 'share',
......@@ -414,6 +423,7 @@
})
return
}
this.getshareData2(shareCode, jumptime, this.shareItem)
......
......@@ -127,6 +127,7 @@
import wx from 'weixin-js-sdk'
import restrictedTip from '@/components/commonPopup/restrictedTip.vue';
import { nanoid } from 'nanoid';
import { trackShare } from '@/util/tracker/manual.js';
export default {
components: {
sharePosterPop,
......@@ -369,6 +370,14 @@
sharePage:'applyFranchise',//分享出去得页面,
}
// 手动埋点:上报用户分享操作
trackShare(shareInfo.title, '', {
invitationCode: this.invitationCode,
inviteUserId: this.inviteUserId,
shareId:this.shareId,
sharePage: shareInfo.sharePage,
channel: 'wechat-miniprogram'
})
wx.miniProgram.postMessage({
data: {
type: 'share',
......
This source diff could not be displayed because it is too large. You can view the blob instead.
// fiveshare.ts
import api from "../api/api";
import { shareURL } from "@/environments/environment";
import { trackShare } from "./tracker/manual.js";
let userInfo = { name: '' };
if (uni.getStorageSync('cffp_userInfo')) {
......@@ -173,7 +174,11 @@ export function setWechatShare(data: any) {
// 在微信浏览器中执行
if (isWechatBrowser()) {
const shareData = getShareData(data);
// 手动埋点:仅在传入真实分享数据时上报(无参调用属于重置默认分享配置,不计分享行为)
if (data) {
trackShare(shareData.title, '', { channel: 'h5-wechat' });
}
// 确保 SDK 已加载
ensureWechatSDK().then(() => {
// @ts-ignore
......@@ -238,6 +243,8 @@ export function hshare(data: any, url: string) {
// ===== 降级方案 =====
function fallbackShare(data: any) {
console.log('📋 降级方案:复制链接', data.link);
try {
// @ts-ignore
if (typeof uni !== 'undefined' && uni.setClipboardData) {
......
......@@ -97,11 +97,12 @@
// 旧版的拦截器
import {apiURL,cffpURL,sfpUrl} from "../environments/environment";
import {apiURL,cffpURL,sfpUrl,scrmUrl} from "../environments/environment";
import api from "@/api/api";
import * as sso from "@/util/sso";
// 白名单,不需要携带token就允许被访问的接口
const whiteApiList = [`${apiURL}/authorize/obtainToken`,
`${scrmUrl}/scrm-api/log/client/batch/save`,
`${apiURL}/authorize/checkToken`,
`${cffpURL}/user/loginVerification`,
`${apiURL}/appVersion/checkIsUpdate`,
......
......@@ -203,6 +203,16 @@ function buildInfo(el, e) {
}
}
/** 输入元素是否已脱离文档或不可见(页面切换时旧页输入框失焦、临时复制元素等) */
function isDetachedOrHidden(el) {
if (!el.isConnected) return true
if (el.getClientRects && el.getClientRects().length === 0) return true
// uni-app H5:隐藏页面的 uni-page 容器为 display:none
const page = el.closest && el.closest('uni-page')
if (page && page.getClientRects && page.getClientRects().length === 0) return true
return false
}
export function setupDomCollector(tracker) {
if (typeof document === 'undefined') return
......@@ -242,6 +252,10 @@ export function setupDomCollector(tracker) {
const inputEl = isInputEl(el)
// ★★ 输入元素不可见/已脱离文档时不算用户操作:
// 页面切换瞬间旧页输入框失焦会晚于新页 onShow,不拦会错误归因到新页面
if (inputEl && isDetachedOrHidden(el)) return
// ★★★ 输入框的 click 一律跳过(聚焦/点选不算"操作完成")★★★
if (e.type === 'click' && (inputEl || (el.closest && el.closest('uni-input, uni-textarea, .van-field')))) return
......
export default {
enabled: false, // 全局开关(可由后端配置下发动态修改)
debug: true, // 控制台输出上报明细(排查用;确认无误后可关闭)
projectCode: 'CFFP',
api: '/api/log/batch',
batchSize: 10, // 攒多少条触发上报
......
......@@ -24,6 +24,8 @@ import {
export default class Tracker {
constructor(cfg) {
this.cfg = cfg
this.debug = cfg.debug !== false // ← 控制台输出上报明细
this._lastPayload = null // ← 最近一次请求体(供控制台排查)
this.pageMetaMap = cfg.pageMetaMap || {} // ← 持有映射表
this._enabled = !!cfg.enabled
this.queue = []
......@@ -299,6 +301,20 @@ export default class Tracker {
this.queue.push(item)
if (this.queue.length > this.cfg.maxQueue) this.queue.shift()
if (this.debug) {
console.log(
`%c[Tracker] 入队 ${eventCode} | ${item.actionTargetName}`,
'color:#2e7d32;font-weight:bold',
{
funcCode, moduleCode, pageUrl: item.pageUrl,
actionTargetType: item.actionTargetType,
actionTarget: item.actionTarget,
extraParams: extra,
queue: this.queue.length
}
)
}
if (this.queue.length >= this.cfg.batchSize) this.flush()
else this._schedule()
}
......@@ -325,12 +341,20 @@ export default class Tracker {
const batch = this.queue.splice(0, this.cfg.maxBatch)
let ok = false
if (this.debug) {
console.log(
`%c[Tracker] 上报 ${batch.length} 条 → ${this.cfg.api}`,
'color:#1565c0;font-weight:bold',
batch.map(i => ({ event: i.eventCode, func: i.funcCode, target: i.actionTargetName }))
)
}
try {
await this._send(batch)
ok = true
this._retry = 0
} catch (e) {
this._retry++
if (this.debug) console.warn(`[Tracker] 上报异常,第 ${this._retry} 次重试`, e && e.message)
if (this._retry <= this.cfg.maxRetry) {
// 回滚到队头,指数退避
this.queue.unshift(...batch)
......@@ -353,6 +377,10 @@ export default class Tracker {
visitorCode: this.visitorCode,
saveRequestList: list
}
this._lastPayload = payload
if (this.debug) {
console.log('%c[Tracker] 请求体', 'color:#1565c0', JSON.parse(JSON.stringify(payload)))
}
return new Promise((resolve, reject) => {
uni.request({
url: this.cfg.api,
......@@ -361,10 +389,23 @@ export default class Tracker {
header: { 'Content-Type': 'application/json' },
timeout: 10000,
success: (res) => {
if (this.debug) {
const pass = res.statusCode >= 200 && res.statusCode < 300
console.log(
`%c[Tracker] ${pass ? '✅ 上报成功' : '❌ 上报失败'} HTTP ${res.statusCode}`,
`color:${pass ? '#2e7d32' : '#c62828'};font-weight:bold`,
res.data
)
}
if (res.statusCode >= 200 && res.statusCode < 300) resolve()
else reject(new Error('HTTP ' + res.statusCode))
},
fail: reject
fail: (err) => {
if (this.debug) {
console.error('[Tracker] ❌ 请求发送失败(接口地址/跨域/网络问题)', this.cfg.api, err)
}
reject(err)
}
})
})
}
......@@ -377,6 +418,9 @@ export default class Tracker {
visitorCode: this.visitorCode,
saveRequestList: this.queue.splice(0)
}
if (this.debug) {
console.warn('[Tracker] 页面隐藏,走 sendBeacon 兜底上报(控制台看不到响应,属正常)', payload.saveRequestList.length, '条')
}
// #ifdef H5
if (sendBeaconSafe(this.cfg.api, payload)) return
// #endif
......
......@@ -13,6 +13,41 @@ export function initTracker(options = {}) {
_instance = new Tracker(config)
bindTracker(_instance)
// #ifdef H5
// 控制台调试入口:__tracker.info() / queue() / last() / flush() / debug(false)
if (typeof window !== 'undefined') {
window.__tracker = {
instance: _instance,
info: () => ({
enabled: _instance.enabled,
api: config.api,
sessionId: _instance.sessionId,
visitorCode: _instance.visitorCode,
queue: _instance.queue.length,
clientType: _instance.clientType,
page: _instance.currentPage
}),
queue: () => JSON.parse(JSON.stringify(_instance.queue)),
last: () => (_instance._lastPayload ? JSON.parse(JSON.stringify(_instance._lastPayload)) : null),
flush: () => { _instance.flush(true); return '已触发立即上报,看下方 [Tracker] 日志' },
// 不点分享也能验证「前端→后端」链路是否通
testShare: (title = '调试分享', link = location.href) => {
_instance.trackFunc('share_to_friend', { shareTitle: title, shareLink: link }, {
eventCode: 'share',
actionTargetType: 'button',
actionTarget: 'share',
actionTargetName: `(分销)${title}-->${link}`,
remark: '分销分享'
})
_instance.flush(true)
return '已发送一条 share 测试事件'
},
debug: (on = true) => { _instance.debug = !!on; return `debug = ${_instance.debug}` }
}
console.log('%c[Tracker] 控制台入口已就绪 → __tracker.info() / __tracker.queue() / __tracker.last() / __tracker.flush()', 'color:#1565c0')
}
// #endif
// 延迟初始化,避免抢占首屏资源
setTimeout(() => {
// #ifdef H5 || APP-PLUS
......
......@@ -15,7 +15,7 @@
* trackEvent(FUNCTIONS.WECHAT_LOGIN, { target: 'login_phone_success', name: '手机号快捷登录成功' }) // eventCode 由 funcCode 推导
*/
import { useTracker } from './index'
import { EVENTS } from './track-events'
import { EVENTS, FUNCTIONS } from './track-events'
function _report(funcCode, extra, overrides) {
try {
......@@ -105,3 +105,33 @@ export function trackView(funcCode, target, name, extra = {}, targetType = 'page
remark: `查看${name}`
})
}
/**
* 分享类:拉起分享(小程序 postMessage / H5 jssdk 分享 / 降级复制链接)
* actionTargetName 口径:(分销)标题(链接不拼进名称,只在 extraParams.shareLink 里)
* @param {*} title 分享标题(shareInfo.title)
* @param {*} link 分享链接(shareInfo.link)
* @param {Object} extra 额外扩展字段(如 shareCode / fileId / sharePage)
*/
export function trackShare(title, link, extra = {}) {
const t = title === undefined || title === null ? '' : String(title)
const l = link === undefined || link === null ? '' : String(link)
trackEvent(FUNCTIONS.SHARE_TO_FRIEND, {
target: 'share',
name: `(分销)${t}`,
targetType: 'button',
eventCode: EVENTS.SHARE,
extra: Object.assign({ shareTitle: t }, l ? { shareLink: l } : {}, extra),
remark: '分销分享'
})
// 分享是关键事件:立即上报,不等 5s 批量窗口
try {
const inst = useTracker()
if (inst && inst.enabled) {
console.log('%c[Tracker] 分享事件 → 立即上报', 'color:#2e7d32;font-weight:bold', { title: t, link: l, extra })
inst.flush(true)
}
} catch (e) {
console.warn('[Tracker] 分享事件立即上报失败', e)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment