diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000000000000000000000000000000000000..776484b90c8e74d31e2006bb47b54c34f1ec70ef
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,73 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution');
+
+module.exports = {
+ root: true,
+ extends: [
+ 'plugin:vue/vue3-essential',
+ 'eslint:recommended',
+ '@vue/eslint-config-typescript'
+ ],
+ parser: 'vue-eslint-parser',
+ parserOptions: {
+ ecmaVersion: 'latest'
+ },
+ rules: {
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'warn',
+ 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+ quotes: ['error', 'single', { avoidEscape: true }],
+ 'quote-props': ['warn', 'as-needed'],
+ 'comma-dangle': ['error', 'only-multiline'],
+ camelcase: ['error', { properties: 'never' }],
+ 'array-bracket-spacing': 'warn',
+ 'arrow-spacing': 'warn',
+ 'block-spacing': 'warn',
+ 'comma-spacing': 'warn',
+ 'computed-property-spacing': 'warn',
+ 'generator-star-spacing': 'warn',
+ 'key-spacing': 'warn',
+ 'keyword-spacing': 'warn',
+ 'object-curly-spacing': ['warn', 'always'],
+ 'rest-spread-spacing': 'warn',
+ 'switch-colon-spacing': 'error',
+ 'semi-spacing': 'warn',
+ 'template-curly-spacing': 'warn',
+ 'template-tag-spacing': 'warn',
+ 'yield-star-spacing': 'warn',
+ semi: ['warn', 'always'],
+ 'no-trailing-spaces': 'warn',
+ 'prefer-template': 'error',
+ 'prefer-spread': 'error',
+ 'no-var': 'error',
+ 'max-lines-per-function': ['error', {
+ max: 100,
+ skipComments: true,
+ skipBlankLines: true
+ }],
+ complexity: ['warn', 20],
+ 'max-depth': ['warn', 4],
+ 'max-len': ['warn', {
+ code: 160,
+ ignoreTemplateLiterals: true,
+ ignoreStrings: true,
+ ignorePattern: 'd="([\\s\\S]*?)"',
+ }],
+ 'default-param-last': 'off',
+ 'no-param-reassign': ['error', { props: false }],
+
+ 'vue/max-attributes-per-line': 'off',
+ 'vue/html-self-closing': ['warn', {
+ html: {
+ void: 'always',
+ normal: 'never',
+ },
+ }],
+
+ 'vue/singleline-html-element-content-newline': 'off',
+ 'vue/html-closing-bracket-newline': 'off',
+ 'vue/multiline-html-element-content-newline': 'warn',
+
+ 'no-unused-vars': 'off',
+ '@typescript-eslint/no-unused-vars': 'warn',
+ },
+};
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000000000000000000000000000000000000..9afbe5e1b84a792b4896f418f13aee76ef1c4d51
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,4 @@
+module.exports = {
+ printWidth: 160,
+ singleQuote: true,
+};
diff --git a/packages/analytics/index.html b/packages/analytics/index.html
index dd76078936216124c3ae1d6fb5d1cc8bfd5f682f..2ee13d0958d28e9520a551366c3df8c55ead754f 100644
--- a/packages/analytics/index.html
+++ b/packages/analytics/index.html
@@ -13,15 +13,24 @@
-
-
open link
-
-
123
-

-
+
+
+
open link
+
+
123
+

+
-
-
+
+
+
+
diff --git a/packages/analytics/src/events/keys.ts b/packages/analytics/src/events/_keys.ts
similarity index 82%
rename from packages/analytics/src/events/keys.ts
rename to packages/analytics/src/events/_keys.ts
index 3c02cad703c5b26118d123f7997a6d0f9d9d40b2..5ca686b6be7d8d85135e93915be25c8c2cc03440 100644
--- a/packages/analytics/src/events/keys.ts
+++ b/packages/analytics/src/events/_keys.ts
@@ -3,4 +3,5 @@ export const OpenEventKeys = {
PageBasePerformance: '$PageBasePerformance',
LCP: '$LCP',
INP: '$INP',
+ PageClick: '$PageClick',
};
diff --git a/packages/analytics/src/events/click.ts b/packages/analytics/src/events/click.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2761d54a22b6cf8cfe06dddb057c209af060518b
--- /dev/null
+++ b/packages/analytics/src/events/click.ts
@@ -0,0 +1,44 @@
+import { EventContent } from '../types';
+import { isFunction } from '../utils';
+import { OpenEventKeys } from './_keys';
+
+async function handleClick(e: MouseEvent, customData?: (event: MouseEvent) => Promise | EventContent) {
+ const { pageX, pageY } = e;
+ const { scrollLeft, scrollTop } = document.scrollingElement || document.documentElement;
+
+ const cData = isFunction(customData) ? await customData(e) : {};
+
+ return {
+ url: window.location.href,
+ pageX,
+ pageY,
+ documentScrollLeft: scrollLeft,
+ documentScrollTop: scrollTop,
+ ...cData,
+ };
+}
+
+export default {
+ event: OpenEventKeys.PageClick,
+ collector: (options?: { customData: (event: MouseEvent) => Promise; delay: Millisecond }) => {
+ const { customData, delay = 800 } = options || {};
+ return new Promise((resolve) => {
+ let debounceId = 0;
+ window.addEventListener(
+ 'click',
+ (e) => {
+ clearTimeout(debounceId);
+ debounceId = globalThis.setTimeout(async () => {
+ const data = await handleClick(e, customData);
+ console.log(data);
+
+ resolve(data);
+ }, delay);
+ },
+ {
+ capture: true,
+ }
+ );
+ });
+ },
+};
diff --git a/packages/analytics/src/events/index.ts b/packages/analytics/src/events/index.ts
index e18cc8f6ed29ce49f8051e4772624d05c4b96837..60fe28996c8b82b47cc077635107972609d82b95 100644
--- a/packages/analytics/src/events/index.ts
+++ b/packages/analytics/src/events/index.ts
@@ -1,12 +1,19 @@
-import { OpenEventKeys } from './keys';
+import { OpenEventKeys } from './_keys';
+import { EventContent } from '../types';
import { isFunction } from '../utils';
-type EventCollector = (...params: any[]) => Promise> | Record;
+export interface CollectorOptions {
+ customData?: () => Promise | EventContent;
+}
+
+type EventCollector = (options?: CollectorOptions) => Promise | EventContent;
+
interface Event {
event: string;
collector: EventCollector;
}
+
const modules: Record> = import.meta.glob(['./*.ts', '!./keys.ts'], {
eager: true,
});
@@ -26,9 +33,9 @@ export function isInnerEvent(event: string) {
return Events.has(event);
}
-export function getInnerEventData(event: string, params: any[] = []) {
+export function getInnerEventData(event: string, collectorOption?: CollectorOptions) {
const colloctor = Events.get(event);
if (isFunction(colloctor)) {
- return colloctor(...params);
+ return colloctor(collectorOption);
}
}
diff --git a/packages/analytics/src/events/inp.ts b/packages/analytics/src/events/inp.ts
index ed7fd19d33855363e2158391622a0d519a0cc471..a3641118e5b69241eacbb1f59d9e402884d1fba2 100644
--- a/packages/analytics/src/events/inp.ts
+++ b/packages/analytics/src/events/inp.ts
@@ -1,4 +1,4 @@
-import { OpenEventKeys } from './keys';
+import { OpenEventKeys } from './_keys';
import { onINP } from 'web-vitals';
export default {
@@ -7,6 +7,7 @@ export default {
return new Promise((resolve) => {
onINP((m) => {
resolve({
+ url: window.location.href,
inp: m.value,
});
});
diff --git a/packages/analytics/src/events/lcp.ts b/packages/analytics/src/events/lcp.ts
index 32b7a835a979542ddccdcc65ec508610979a10ae..397ba62e0d1014bd7f4fee7b22afc0883558e207 100644
--- a/packages/analytics/src/events/lcp.ts
+++ b/packages/analytics/src/events/lcp.ts
@@ -1,4 +1,4 @@
-import { OpenEventKeys } from './keys';
+import { OpenEventKeys } from './_keys';
import { onLCP } from 'web-vitals';
export default {
@@ -7,6 +7,7 @@ export default {
return new Promise((resolve) => {
onLCP((m) => {
resolve({
+ url: window.location.href,
lcp: m.value,
});
});
diff --git a/packages/analytics/src/events/performance.ts b/packages/analytics/src/events/performance.ts
index 96e0e58c5c11d2995a091e4e81183db115eabcdc..63630949f2bfa201730c4ffcd2c5ee9929b99434 100644
--- a/packages/analytics/src/events/performance.ts
+++ b/packages/analytics/src/events/performance.ts
@@ -1,7 +1,8 @@
-import { OpenEventKeys } from './keys';
+import { OpenEventKeys } from './_keys';
import { onFCP, onTTFB } from 'web-vitals';
interface PerformanceData {
+ url: string;
fcp: number;
ttfb: number;
load: number;
@@ -28,6 +29,7 @@ export default {
collector: () => {
return new Promise((resolve) => {
const data: PerformanceData = {
+ url: window.location.href,
fcp: -1,
ttfb: -1,
load: -1,
diff --git a/packages/analytics/src/events/pv.ts b/packages/analytics/src/events/pv.ts
index ab0e451750e11ccd5f0c5783d6436999bc898379..4d9e1b6b826fad046656c3301c441cdff28e358a 100644
--- a/packages/analytics/src/events/pv.ts
+++ b/packages/analytics/src/events/pv.ts
@@ -1,4 +1,4 @@
-import { OpenEventKeys } from './keys';
+import { OpenEventKeys } from './_keys';
export default {
event: OpenEventKeys.PV,
diff --git a/packages/analytics/src/index.ts b/packages/analytics/src/index.ts
index 6cc8e36d7f62c74aefb3d78878b37cd98473528d..f027aa8cf56cfda8006aea94b9b9994eddf7bf1f 100644
--- a/packages/analytics/src/index.ts
+++ b/packages/analytics/src/index.ts
@@ -1,12 +1,13 @@
import { Storage } from './storage';
import { whenDocumentReady, getClientByUA, isFunction, isPromise, uniqueId } from './utils';
import { Constant } from './constant';
-import { getInnerEventData } from './events';
+import { getInnerEventData, isInnerEvent, CollectorOptions } from './events';
import packageJson from '../package.json';
+import { EventContent, EventData, EventHeader, OpenAnalyticsParams, ReportRequest } from './types';
-export { OpenEventKeys } from './events/keys';
+export { OpenEventKeys } from './events/_keys';
-class StoreKey {
+class AnalyticsStoreKey {
appPrefix: string;
client: string;
session: string;
@@ -24,48 +25,7 @@ class StoreKey {
return `${Constant.OA_PREFIX}-${this.appPrefix}${key}`;
}
}
-type StoreKeyIns = InstanceType;
-
-export interface EventData {
- event: string; // 事件名
- time: number; // 事件采集时间
- data: Record; // 上报的事件数据
- sId: string; // 会话id
-}
-
-interface ReportOptions {
- immediate: boolean; // 是否立即上报
-}
-interface EventHeader {
- cId?: string; // 客户端匿名标识,清除浏览器缓存销毁
- aId?: string; // 应用id
- oa_version?: string; // OA版本
- screen_width?: number; // 屏幕宽度
- screen_height?: number; // 屏幕高度
- view_width?: number; // 视口宽度
- view_height?: number; // 视口高度
- os?: string; // 客户端操作系统
- os_version?: string; // 客户端操作系统版本
- browser?: string; // 客户端浏览器
- browser_version?: string; // 客户端浏览器版本
- device?: string; // 设备信息
- device_type?: string; // 设备类型
- device_vendor?: string; // 设备品牌
-}
-interface ReportData {
- header: EventHeader;
- body: EventData[];
-}
-type RequestFn = (data: ReportData) => Promise | void;
-
-export interface OpenAnalyticsParams {
- request: (data: ReportData) => Promise | void; // 上报数据的接口
- appKey?: string; // 采集app的key,用于区分多app上报
- immediate?: boolean; // 全局设置是否立即上报
- requestInterval?: number; //上报间隔
- maxEvents?: number;
- requestPlan?: (requestFn: () => void) => void;
-}
+type StoreKeyIns = InstanceType;
const store = new Storage(localStorage);
@@ -93,8 +53,8 @@ function initHeader(keys: StoreKeyIns, appId: string): EventHeader {
cId: client.id,
aId: appId,
oa_version: packageJson.version,
- view_width: window.innerWidth,
- view_height: window.innerHeight,
+ viewport_width: window.innerWidth,
+ viewport_height: window.innerHeight,
screen_width: window.screen.width || window.innerWidth,
screen_height: window.screen.height || window.innerHeight,
os: os.name ?? '',
@@ -127,7 +87,7 @@ function getSessionId(sKey: string) {
}
export class OpenAnalytics {
- request: RequestFn;
+ request: ReportRequest;
eventData: EventData[];
immediate: boolean;
sessionId: string = '';
@@ -151,7 +111,7 @@ export class OpenAnalytics {
this.request = params.request;
this.immediate = params.immediate ?? false;
this.appKey = params.appKey ?? '';
- this.StoreKey = new StoreKey(params.appKey);
+ this.StoreKey = new AnalyticsStoreKey(params.appKey);
this.requestInterval = params.requestInterval ?? Constant.DEFAULT_REQUEST_INTERVAL;
this.#timer = null;
this.maxEvents = params.maxEvents ?? Constant.MAX_EVENTS;
@@ -175,7 +135,7 @@ export class OpenAnalytics {
/**
* 设置header
*/
- setHeader(header: Record) {
+ setHeader(header: Record) {
Object.assign(this.header, header);
}
/**
@@ -281,21 +241,25 @@ export class OpenAnalytics {
/**
* 采集数据
* @param event 事件名
- * @param data 事件数据
+ * @param data 事件数据,如果是内部事件,则会在内部事件触发时执行
* @param options 配置
*/
- async report(event: string, data?: Record | (() => Record), options?: ReportOptions) {
- const innerData: Record = (await getInnerEventData(event)) || {};
+ async report(event: string, content?: (...opts: any[]) => Promise | EventContent, collectOptions?: CollectorOptions, immediate?: boolean) {
+ let reportData: EventContent = {};
- const outerData = isFunction(data) ? data() : data;
+ if (isInnerEvent(event)) {
+ reportData = (await getInnerEventData(event, collectOptions)) || {};
+ } else if (content) {
+ reportData = await content();
+ }
const eventData: EventData = {
event: event,
time: Date.now(),
- data: Object.assign(innerData, outerData),
+ data: reportData,
sId: this.enabled ? getSessionId(this.StoreKey.session) : '',
};
- this.collect(eventData, options?.immediate);
+ this.collect(eventData, immediate);
}
}
diff --git a/packages/analytics/src/types.ts b/packages/analytics/src/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5e4dac05c7ff2a4ac7dd2c4e6b43db711b4105b
--- /dev/null
+++ b/packages/analytics/src/types.ts
@@ -0,0 +1,43 @@
+export type EventContent = {
+ [k: string | number]: string | number | EventContent | undefined | null;
+};
+
+export interface EventData {
+ event: string; // 事件名
+ time: number; // 事件采集时间
+ data: EventContent; // 上报的事件数据
+ sId: string; // 会话id
+}
+
+export interface EventHeader {
+ cId?: string; // 客户端匿名标识,清除浏览器缓存销毁
+ aId?: string; // 应用id
+ oa_version?: string; // OA版本
+ screen_width?: number; // 屏幕宽度
+ screen_height?: number; // 屏幕高度
+ viewport_width?: number; // 视口宽度
+ viewport_height?: number; // 视口高度
+ os?: string; // 客户端操作系统
+ os_version?: string; // 客户端操作系统版本
+ browser?: string; // 客户端浏览器
+ browser_version?: string; // 客户端浏览器版本
+ device?: string; // 设备信息
+ device_type?: string; // 设备类型
+ device_vendor?: string; // 设备品牌
+}
+
+export interface ReportData {
+ header: EventHeader;
+ body: EventData[];
+}
+
+export type ReportRequest = (data: ReportData) => Promise | void;
+
+export interface OpenAnalyticsParams {
+ request: (data: ReportData) => Promise | void; // 上报数据的接口
+ appKey?: string; // 采集app的key,用于区分多app上报
+ immediate?: boolean; // 全局设置是否立即上报
+ requestInterval?: number; //上报间隔
+ maxEvents?: number;
+ requestPlan?: (requestFn: () => void) => void;
+}
diff --git a/packages/analytics/test/main.ts b/packages/analytics/test/main.ts
index 863e62d53ec7b041a26f91c5f965a4c8a17daa9c..d160fff2ede4272a7aed70c6cfa9dc1e2213c94e 100644
--- a/packages/analytics/test/main.ts
+++ b/packages/analytics/test/main.ts
@@ -8,10 +8,10 @@ const oa = new OpenAnalytics({
appKey: 'test',
request: (data) => {
console.log('request to send content', data);
- return fetch('report', {
- method: 'POST',
- body: JSON.stringify(data),
- }).then((response) => response.ok);
+ // return fetch('report', {
+ // method: 'POST',
+ // body: JSON.stringify(data),
+ // }).then((response) => response.ok);
},
// immediate: true,
});
@@ -32,14 +32,23 @@ oa.report(OpenEventKeys.PV, () => ({
oa.report(OpenEventKeys.PageBasePerformance);
oa.report(OpenEventKeys.LCP);
oa.report(OpenEventKeys.INP);
+oa.report(OpenEventKeys.PageClick, (e: MouseEvent) => {
+ const scroller = document.querySelector('.inner-screen');
+ const el = e.target as HTMLElement | null;
+ return {
+ documentScrollLeft: 123, // 覆盖默认值
+ name: el?.innerHTML, // 新增字段
+ top: scroller?.scrollTop, // 新增字段
+ };
+});
btn1?.addEventListener('click', () => {
// window.open('/');
console.log('btn1 clicked');
- oa.report('btn-click', {
+ oa.report('btn-click', () => ({
date: Date.now(),
- });
+ }));
});
btnOpen?.addEventListener('click', () => {
diff --git a/packages/analytics/test/style.css b/packages/analytics/test/style.css
index 1a8619a4bc503015381c6ec29e78d054c73bee37..738bf6b2facfd76e44632d23b346f9e2193de42f 100644
--- a/packages/analytics/test/style.css
+++ b/packages/analytics/test/style.css
@@ -1,6 +1,19 @@
#app {
- width: 800px;
+ width: 1200px;
margin: auto;
+ display: flex;
+ height: 100vh;
+ overflow: auto;
+ margin-top: 50px;
+}
+
+* {
+ margin: 0;
+}
+
+body {
+ margin: 0;
+ height: 100vh;
}
.box {
@@ -11,4 +24,27 @@
.img {
width: 50vw;
+}
+
+.screen2 {
+ height: 150vh;
+ padding: 50px;
+ width: 50%;
+ background-color: rgba(1, 125, 1, .3);
+ overflow: auto;
+}
+
+.screen {
+ height: 600px;
+ padding: 50px;
+ overflow: auto;
+}
+
+.inner-screen {
+ background-color: rgba(130, 0, 0, .1);
+ height: 800px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
}
\ No newline at end of file