From 5664a1a616df498cba58b9a8e63a91ac0ba96bab Mon Sep 17 00:00:00 2001 From: zhengyiming <540361168@qq.com> Date: 星期二, 01 七月 2025 13:44:55 +0800 Subject: [PATCH] feat: add rrweb --- .env.development | 2 package.json | 1 src/utils/record.ts | 91 ++++++++++++++++++++++++++++++ src/views/Home/Home.vue | 11 +++ pnpm-lock.yaml | 47 +++++++++++++++ 5 files changed, 151 insertions(+), 1 deletions(-) diff --git a/.env.development b/.env.development index 870a9ef..65872e0 100644 --- a/.env.development +++ b/.env.development @@ -2,7 +2,7 @@ VITE_PORT = 8698 # 寮�鍙戠幆澧冭鍙栭厤缃枃浠惰矾寰� -VITE_PUBLIC_PATH = /front/ +VITE_PUBLIC_PATH = / # 寮�鍙戠幆澧冧唬鐞� VITE_PROXY_DOMAIN = /api diff --git a/package.json b/package.json index 8700bd6..fb05b23 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "pinia": "^2.2.4", "qs": "^6.11.0", "rgb-hex": "^4.0.0", + "rrweb": "2.0.0-alpha.4", "semver": "^7.6.3", "senin-help": "latest", "senin-vue": "latest", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b8c375..f65b995 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,6 +139,9 @@ rgb-hex: specifier: ^4.0.0 version: 4.0.0 + rrweb: + specifier: 2.0.0-alpha.4 + version: 2.0.0-alpha.4 semver: specifier: ^7.6.3 version: 7.6.3 @@ -3627,6 +3630,10 @@ dev: true optional: true + /@rrweb/types@2.0.0-alpha.18: + resolution: {integrity: sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==} + dev: false + /@sindresorhus/merge-streams@2.3.0: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} @@ -3949,6 +3956,10 @@ /@types/ali-oss@6.16.11: resolution: {integrity: sha512-/AyemPZy93ZXGzEokMsoPFgjH37snpzH4X/fwans/n63HLaCleriCG3PyrkHCPkgHEc9vj9Uo6paqsBN3vJ3OA==} dev: true + + /@types/css-font-loading-module@0.0.7: + resolution: {integrity: sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==} + dev: false /@types/eslint@7.29.0: resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==} @@ -5182,6 +5193,10 @@ engines: {node: '>=10.0.0'} dev: false + /@xstate/fsm@1.6.5: + resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==} + dev: false + /@ywwlmm/openapi@0.0.3: resolution: {integrity: sha512-zbQMq2LnMX2IJmEJIG12IeggQCEm1sn7E+S7TH6yCLaYOERf8ARA22GJUmjmYaLUD9iKgb8yl2ohO3PSI6aMdw==} dependencies: @@ -5782,6 +5797,11 @@ /balanced-match@2.0.0: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + + /base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + dev: false /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -8502,6 +8522,10 @@ resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: reusify: 1.0.4 + + /fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + dev: false /file-entry-cache@4.0.0: resolution: {integrity: sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==} @@ -13408,6 +13432,29 @@ resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} dev: false + /rrdom@0.1.7: + resolution: {integrity: sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==} + dependencies: + rrweb-snapshot: 2.0.0-alpha.4 + dev: false + + /rrweb-snapshot@2.0.0-alpha.4: + resolution: {integrity: sha512-KQ2OtPpXO5jLYqg1OnXS/Hf+EzqnZyP5A+XPqBCjYpj3XIje/Od4gdUwjbFo3cVuWq5Cw5Y1d3/xwgIS7/XpQQ==} + dev: false + + /rrweb@2.0.0-alpha.4: + resolution: {integrity: sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==} + dependencies: + '@rrweb/types': 2.0.0-alpha.18 + '@types/css-font-loading-module': 0.0.7 + '@xstate/fsm': 1.6.5 + base64-arraybuffer: 1.0.2 + fflate: 0.4.8 + mitt: 3.0.0 + rrdom: 0.1.7 + rrweb-snapshot: 2.0.0-alpha.4 + dev: false + /rtc-ai-denoiser@1.1.7: resolution: {integrity: sha512-53e/4a4lT96K004mqDnLDE+upNSpBLRMfFgYCeIw3Gvuw9F17nxLP5v8MOVLly4/Epomxkx4SXrOFJJMxD2pIw==} dev: false diff --git a/src/utils/record.ts b/src/utils/record.ts new file mode 100644 index 0000000..1c4c87e --- /dev/null +++ b/src/utils/record.ts @@ -0,0 +1,91 @@ +import * as rrweb from 'rrweb'; +import { guid } from './common'; +import { eventWithTime, listenerHandler } from '@rrweb/types'; +import { storageLocal } from './storage'; + +export class Recorder { + // 鐢熸垚浼氳瘽ID锛堢敤浜庢爣璇嗕竴娆℃姇淇濇祦绋嬶級 + sessionId = guid(); + // 瀛樺偍褰曞埗浜嬩欢鐨勬暟缁� + events = []; + + stopFn: () => void; + + // 鍒濆鍖栧綍鍒跺櫒 + init() { + this.stopFn = rrweb.record({ + emit: (event) => { + // 鏀堕泦浜嬩欢锛堝彲閫夋嫨瀹炴椂鍙戦�佸埌鍚庣锛� + this.events.push(event); + + // 瀹氭湡鍙戦�佷簨浠跺埌鍚庣锛堥伩鍏嶅唴瀛樺崰鐢ㄨ繃澶э級 + if (this.events.length % 100 === 0) { + this.sendEventsToBackend(this.events.splice(0, 100), this.sessionId); + } + }, + // 鍙�夐厤缃細蹇界暐鏁忔劅鍏冪礌 + blockClass: 'rr-block', // 涓烘晱鎰熷厓绱犳坊鍔犳class + ignoreClass: 'rr-ignore', // 瀹屽叏蹇界暐鐨勫厓绱� + }); + } + + // 缁撴潫褰曞埗锛堜緥濡傛彁浜ゆ姇淇濊〃鍗曟椂锛� + stopRecordingAndSave() { + this.stopFn?.(); + // 鍙戦�佸墿浣欎簨浠跺埌鍚庣 + if (this.events.length > 0) { + this.sendEventsToBackend(this.events, this.sessionId); + } + // 瀛樺偍浼氳瘽ID锛堝叧鑱斿埌鐢ㄦ埛鎶曚繚璁板綍锛� + this.saveSessionIdToUserRecord(this.sessionId); + } + + saveSessionIdToUserRecord(sessionId: string) {} + + async sendEventsToBackend(events: eventWithTime[], sessionId: string) { + try { + const body = JSON.stringify({ + sessionId, + events, + timestamp: new Date().toISOString(), + }); + console.log('body: ', body); + const storageEvents = storageLocal.getItem(`events`) || {}; + storageEvents[sessionId] = events; + storageLocal.setItem(`events`, storageEvents); + // await fetch('/api/recordings', { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: , + // }); + } catch (error) { + console.error('Failed to send recording events:', error); + // 鍙疄鐜版湰鍦板瓨鍌ㄤ綔涓洪檷绾ф柟妗� + } + } + + async replaySession(sessionId: string) { + // 浠庡悗绔幏鍙栧綍鍒朵簨浠� + // const response = await fetch(`/api/recordings/${sessionId}`); + // const { events } = await response.json(); + const storageEvents = storageLocal.getItem(`events`) || {}; + if (!storageEvents[sessionId]) { + return; + } + + // 鍒濆鍖栧洖鏀惧櫒 + const player = new rrweb.Replayer(storageEvents[sessionId], { + root: document.getElementById('replay-container'), + }); + + // 鍙�夛細鎺у埗鍥炴斁閫熷害 + player.play(1.0); // 1鍊嶉�� + + // 鍙�夛細鐩戝惉鍥炴斁浜嬩欢 + player.on('play', () => console.log('Replay started')); + player.on('pause', () => console.log('Replay paused')); + player.on('finish', () => console.log('Replay finished')); + } +} diff --git a/src/views/Home/Home.vue b/src/views/Home/Home.vue index 0568350..3bc4ade 100644 --- a/src/views/Home/Home.vue +++ b/src/views/Home/Home.vue @@ -155,6 +155,7 @@ import dayjs from 'dayjs'; import _ from 'lodash'; import InsureInstructionsDialog from './components/InsureInstructionsDialog.vue'; +// import { Recorder } from '@/utils/record'; defineOptions({ name: 'Home', @@ -262,13 +263,23 @@ }; const state = reactive({ ...BaseState }); +// const recorder = ref(new Recorder()); onMounted(async () => { await getList(); state.loading = false; handleOpenInstructions(); + + // setTimeout(() => { + // // recorder.value.init(); + // recorder.value.replaySession('9cb24e5a-0423-4dcd-abd5-fa7a4117cadc'); + // }, 3000); }); +// onUnmounted(() => { +// recorder.value.stopRecordingAndSave(); +// }); + const { getDataSource: getList, proTableProps, -- Gitblit v1.9.1