- add category/tag caching and optimistic updates - add stats dashboard aggregation and batched tracking - add undo delete snackbar and group read-only view mode - improve app-ready timeout handling and refresh guards
141 lines
2.8 KiB
TypeScript
141 lines
2.8 KiB
TypeScript
/**
|
|
* 前端埋点工具
|
|
* 记录用户关键操作和错误信息
|
|
*/
|
|
|
|
import { request } from './request'
|
|
|
|
interface TrackEvent {
|
|
event: string
|
|
page?: string
|
|
data?: Record<string, any>
|
|
}
|
|
|
|
/** 上报队列(批量上报,减少请求) */
|
|
let eventQueue: TrackEvent[] = []
|
|
let flushTimer: number | null = null
|
|
const FLUSH_INTERVAL = 500 // 500ms 攒批
|
|
const MAX_QUEUE_SIZE = 10 // 10 条立即发送
|
|
|
|
/** 获取当前页面路径 */
|
|
function getCurrentPage(): string {
|
|
const pages = getCurrentPages()
|
|
if (pages.length > 0) {
|
|
return `/${pages[pages.length - 1].route}`
|
|
}
|
|
return '/unknown'
|
|
}
|
|
|
|
/** 批量上报事件 */
|
|
async function flushEvents() {
|
|
if (eventQueue.length === 0) return
|
|
|
|
const events = [...eventQueue]
|
|
eventQueue = []
|
|
|
|
try {
|
|
await request({
|
|
url: '/track',
|
|
method: 'POST',
|
|
data: { events }
|
|
})
|
|
} catch {
|
|
// 上报失败不处理,避免影响用户体验
|
|
}
|
|
}
|
|
|
|
/** 添加事件到队列 */
|
|
function addToQueue(event: TrackEvent) {
|
|
eventQueue.push(event)
|
|
|
|
// 队列满时立即上报
|
|
if (eventQueue.length >= MAX_QUEUE_SIZE) {
|
|
flushEvents()
|
|
return
|
|
}
|
|
|
|
// 设置定时上报
|
|
if (!flushTimer) {
|
|
flushTimer = setTimeout(() => {
|
|
flushTimer = null
|
|
flushEvents()
|
|
}, FLUSH_INTERVAL) as unknown as number
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 追踪页面访问
|
|
*/
|
|
export function trackPageView(pagePath?: string) {
|
|
addToQueue({
|
|
event: 'page_view',
|
|
page: pagePath || getCurrentPage()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 追踪用户操作
|
|
* @param action 操作名称,如 'click_save', 'submit_form'
|
|
* @param data 附加数据
|
|
*/
|
|
export function trackAction(action: string, data?: Record<string, any>) {
|
|
addToQueue({
|
|
event: 'action',
|
|
page: getCurrentPage(),
|
|
data: { action, ...data }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 追踪错误
|
|
*/
|
|
export function trackError(error: Error | string, extra?: Record<string, any>) {
|
|
const errorInfo = typeof error === 'string'
|
|
? { message: error }
|
|
: { message: error.message, stack: error.stack?.slice(0, 500) }
|
|
|
|
addToQueue({
|
|
event: 'error',
|
|
page: getCurrentPage(),
|
|
data: { ...errorInfo, ...extra }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 追踪 API 请求失败
|
|
*/
|
|
export function trackApiError(url: string, status: number, message: string) {
|
|
addToQueue({
|
|
event: 'api_error',
|
|
page: getCurrentPage(),
|
|
data: { url, status, message }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 设置全局错误监听
|
|
*/
|
|
export function setupErrorTracking() {
|
|
// Vue 错误处理
|
|
uni.onError((err: string) => {
|
|
trackError(err, { type: 'global' })
|
|
})
|
|
|
|
// 未处理的 Promise rejection
|
|
// #ifdef H5
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
trackError(String(event.reason), { type: 'unhandledrejection' })
|
|
})
|
|
}
|
|
// #endif
|
|
}
|
|
|
|
export default {
|
|
trackPageView,
|
|
trackAction,
|
|
trackError,
|
|
trackApiError,
|
|
setupErrorTracking
|
|
}
|