|
|
@@ -128,9 +128,9 @@ export const Rooter = () => {
|
|
|
console.log('XHR拦截器已启用,将拦截', INTERCEPT_DOMAIN, '的请求并重定向到当前域');
|
|
|
})();
|
|
|
`}} />
|
|
|
- {/* 鼠标事件拦截功能 */}
|
|
|
+ {/* 鼠标事件录制和回放功能 */}
|
|
|
<script dangerouslySetInnerHTML={{ __html: `
|
|
|
- // 鼠标事件拦截功能
|
|
|
+ // 鼠标事件录制和回放功能
|
|
|
(function() {
|
|
|
// 定义要拦截的鼠标事件类型
|
|
|
const mouseEvents = [
|
|
|
@@ -139,6 +139,11 @@ export const Rooter = () => {
|
|
|
'contextmenu', 'wheel'
|
|
|
];
|
|
|
|
|
|
+ // 录制状态
|
|
|
+ let isRecording = false;
|
|
|
+ let recordedEvents = [];
|
|
|
+ let recordingStartTime = 0;
|
|
|
+
|
|
|
// 保存原始的事件监听器添加方法
|
|
|
const originalAddEventListener = EventTarget.prototype.addEventListener;
|
|
|
|
|
|
@@ -147,16 +152,36 @@ export const Rooter = () => {
|
|
|
// 如果是鼠标事件,包装监听器
|
|
|
if (mouseEvents.includes(type)) {
|
|
|
const wrappedListener = function(event) {
|
|
|
- // 使用console.debug输出鼠标事件信息
|
|
|
- console.debug('鼠标事件拦截:', {
|
|
|
- type: event.type,
|
|
|
- target: event.target?.tagName || 'unknown',
|
|
|
- className: event.target?.className || 'none',
|
|
|
- id: event.target?.id || 'none',
|
|
|
- x: event.clientX,
|
|
|
- y: event.clientY,
|
|
|
- timestamp: Date.now()
|
|
|
- });
|
|
|
+ // 录制事件
|
|
|
+ if (isRecording) {
|
|
|
+ const eventData = {
|
|
|
+ type: event.type,
|
|
|
+ target: {
|
|
|
+ tagName: event.target?.tagName || 'unknown',
|
|
|
+ className: event.target?.className || 'none',
|
|
|
+ id: event.target?.id || 'none',
|
|
|
+ selector: getElementSelector(event.target)
|
|
|
+ },
|
|
|
+ x: event.clientX,
|
|
|
+ y: event.clientY,
|
|
|
+ timestamp: Date.now() - recordingStartTime,
|
|
|
+ detail: getEventDetail(event)
|
|
|
+ };
|
|
|
+ recordedEvents.push(eventData);
|
|
|
+
|
|
|
+ // 使用console.debug输出鼠标事件信息
|
|
|
+ console.debug('鼠标事件录制:', eventData);
|
|
|
+ } else {
|
|
|
+ console.debug('鼠标事件拦截:', {
|
|
|
+ type: event.type,
|
|
|
+ target: event.target?.tagName || 'unknown',
|
|
|
+ className: event.target?.className || 'none',
|
|
|
+ id: event.target?.id || 'none',
|
|
|
+ x: event.clientX,
|
|
|
+ y: event.clientY,
|
|
|
+ timestamp: Date.now()
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
// 调用原始监听器
|
|
|
return listener.call(this, event);
|
|
|
@@ -170,7 +195,268 @@ export const Rooter = () => {
|
|
|
return originalAddEventListener.call(this, type, listener, options);
|
|
|
};
|
|
|
|
|
|
- console.log('鼠标事件拦截器已启用,将拦截所有鼠标事件并输出到console.debug');
|
|
|
+ // 获取元素选择器
|
|
|
+ function getElementSelector(element) {
|
|
|
+ if (!element || !element.tagName) return '';
|
|
|
+
|
|
|
+ let selector = element.tagName.toLowerCase();
|
|
|
+ if (element.id) {
|
|
|
+ selector += '#' + element.id;
|
|
|
+ }
|
|
|
+ if (element.className && typeof element.className === 'string') {
|
|
|
+ selector += '.' + element.className.split(' ').join('.');
|
|
|
+ }
|
|
|
+ return selector;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取事件详细信息
|
|
|
+ function getEventDetail(event) {
|
|
|
+ const detail = {};
|
|
|
+
|
|
|
+ switch (event.type) {
|
|
|
+ case 'click':
|
|
|
+ case 'dblclick':
|
|
|
+ case 'mousedown':
|
|
|
+ case 'mouseup':
|
|
|
+ detail.button = event.button;
|
|
|
+ detail.buttons = event.buttons;
|
|
|
+ break;
|
|
|
+ case 'wheel':
|
|
|
+ detail.deltaX = event.deltaX;
|
|
|
+ detail.deltaY = event.deltaY;
|
|
|
+ detail.deltaZ = event.deltaZ;
|
|
|
+ detail.deltaMode = event.deltaMode;
|
|
|
+ break;
|
|
|
+ case 'contextmenu':
|
|
|
+ detail.preventDefault = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 录制控制函数
|
|
|
+ window.startRecording = function() {
|
|
|
+ isRecording = true;
|
|
|
+ recordedEvents = [];
|
|
|
+ recordingStartTime = Date.now();
|
|
|
+ console.log('开始录制鼠标事件...');
|
|
|
+ updateControlPanel();
|
|
|
+ };
|
|
|
+
|
|
|
+ window.stopRecording = function() {
|
|
|
+ isRecording = false;
|
|
|
+ console.log('停止录制,共录制了 ' + recordedEvents.length + ' 个事件');
|
|
|
+ updateControlPanel();
|
|
|
+ };
|
|
|
+
|
|
|
+ window.playRecording = function() {
|
|
|
+ if (recordedEvents.length === 0) {
|
|
|
+ console.log('没有录制的事件可以播放');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('开始播放录制的事件...');
|
|
|
+
|
|
|
+ recordedEvents.forEach((eventData, index) => {
|
|
|
+ setTimeout(() => {
|
|
|
+ replayEvent(eventData);
|
|
|
+ }, eventData.timestamp);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ window.clearRecording = function() {
|
|
|
+ recordedEvents = [];
|
|
|
+ console.log('已清除所有录制的事件');
|
|
|
+ updateControlPanel();
|
|
|
+ };
|
|
|
+
|
|
|
+ window.saveRecording = function() {
|
|
|
+ const dataStr = JSON.stringify(recordedEvents, null, 2);
|
|
|
+ const dataBlob = new Blob([dataStr], {type: 'application/json'});
|
|
|
+ const url = URL.createObjectURL(dataBlob);
|
|
|
+ const a = document.createElement('a');
|
|
|
+ a.href = url;
|
|
|
+ a.download = 'mouse-recording-' + new Date().toISOString().replace(/:/g, '-') + '.json';
|
|
|
+ document.body.appendChild(a);
|
|
|
+ a.click();
|
|
|
+ document.body.removeChild(a);
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+ console.log('录制数据已保存');
|
|
|
+ };
|
|
|
+
|
|
|
+ window.loadRecording = function(event) {
|
|
|
+ const file = event.target.files[0];
|
|
|
+ if (!file) return;
|
|
|
+
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = function(e) {
|
|
|
+ try {
|
|
|
+ recordedEvents = JSON.parse(e.target.result);
|
|
|
+ console.log('录制数据已加载,共 ' + recordedEvents.length + ' 个事件');
|
|
|
+ updateControlPanel();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载录制数据失败:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.readAsText(file);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 回放事件
|
|
|
+ function replayEvent(eventData) {
|
|
|
+ let targetElement;
|
|
|
+
|
|
|
+ // 尝试通过选择器找到目标元素
|
|
|
+ if (eventData.target.selector) {
|
|
|
+ targetElement = document.querySelector(eventData.target.selector);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果找不到元素,尝试通过坐标找到最近的元素
|
|
|
+ if (!targetElement && eventData.x && eventData.y) {
|
|
|
+ targetElement = document.elementFromPoint(eventData.x, eventData.y);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (targetElement) {
|
|
|
+ const event = createEventFromData(eventData, targetElement);
|
|
|
+ targetElement.dispatchEvent(event);
|
|
|
+ console.debug('回放事件:', eventData.type, '目标:', eventData.target.selector);
|
|
|
+ } else {
|
|
|
+ console.warn('无法找到目标元素:', eventData.target.selector);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据事件数据创建事件对象
|
|
|
+ function createEventFromData(eventData, targetElement) {
|
|
|
+ let event;
|
|
|
+
|
|
|
+ switch (eventData.type) {
|
|
|
+ case 'click':
|
|
|
+ event = new MouseEvent('click', {
|
|
|
+ clientX: eventData.x,
|
|
|
+ clientY: eventData.y,
|
|
|
+ button: eventData.detail?.button || 0,
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 'mousedown':
|
|
|
+ case 'mouseup':
|
|
|
+ event = new MouseEvent(eventData.type, {
|
|
|
+ clientX: eventData.x,
|
|
|
+ clientY: eventData.y,
|
|
|
+ button: eventData.detail?.button || 0,
|
|
|
+ buttons: eventData.detail?.buttons || 0,
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 'mousemove':
|
|
|
+ event = new MouseEvent('mousemove', {
|
|
|
+ clientX: eventData.x,
|
|
|
+ clientY: eventData.y,
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 'wheel':
|
|
|
+ event = new WheelEvent('wheel', {
|
|
|
+ clientX: eventData.x,
|
|
|
+ clientY: eventData.y,
|
|
|
+ deltaX: eventData.detail?.deltaX || 0,
|
|
|
+ deltaY: eventData.detail?.deltaY || 0,
|
|
|
+ deltaZ: eventData.detail?.deltaZ || 0,
|
|
|
+ deltaMode: eventData.detail?.deltaMode || 0,
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ event = new MouseEvent(eventData.type, {
|
|
|
+ clientX: eventData.x,
|
|
|
+ clientY: eventData.y,
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return event;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新控制面板状态
|
|
|
+ function updateControlPanel() {
|
|
|
+ const panel = document.getElementById('mouse-recorder-panel');
|
|
|
+ if (panel) {
|
|
|
+ const status = panel.querySelector('.recorder-status');
|
|
|
+ const count = panel.querySelector('.event-count');
|
|
|
+
|
|
|
+ if (status) {
|
|
|
+ status.textContent = isRecording ? '录制中...' : '已停止';
|
|
|
+ status.style.color = isRecording ? '#ff4444' : '#666';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (count) {
|
|
|
+ count.textContent = recordedEvents.length + ' 个事件';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建控制面板
|
|
|
+ function createControlPanel() {
|
|
|
+ const panel = document.createElement('div');
|
|
|
+ panel.id = 'mouse-recorder-panel';
|
|
|
+ panel.style.cssText = \`
|
|
|
+ position: fixed;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 15px;
|
|
|
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
+ font-size: 12px;
|
|
|
+ z-index: 10000;
|
|
|
+ min-width: 200px;
|
|
|
+ \`;
|
|
|
+
|
|
|
+ panel.innerHTML = \`
|
|
|
+ <div style="margin-bottom: 10px; font-weight: bold; border-bottom: 1px solid #eee; padding-bottom: 5px;">
|
|
|
+ 鼠标事件录制器
|
|
|
+ </div>
|
|
|
+ <div style="margin-bottom: 10px;">
|
|
|
+ <span>状态: </span>
|
|
|
+ <span class="recorder-status" style="color: #666;">已停止</span>
|
|
|
+ </div>
|
|
|
+ <div style="margin-bottom: 10px;">
|
|
|
+ <span>事件数: </span>
|
|
|
+ <span class="event-count" style="color: #666;">0 个事件</span>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px;">
|
|
|
+ <button onclick="window.startRecording()" style="padding: 5px 10px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer;">开始录制</button>
|
|
|
+ <button onclick="window.stopRecording()" style="padding: 5px 10px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer;">停止录制</button>
|
|
|
+ <button onclick="window.playRecording()" style="padding: 5px 10px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;">播放</button>
|
|
|
+ <button onclick="window.clearRecording()" style="padding: 5px 10px; background: #FF9800; color: white; border: none; border-radius: 3px; cursor: pointer;">清除</button>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; gap: 5px;">
|
|
|
+ <button onclick="window.saveRecording()" style="padding: 5px 10px; background: #9C27B0; color: white; border: none; border-radius: 3px; cursor: pointer; flex: 1;">保存</button>
|
|
|
+ <label style="padding: 5px 10px; background: #607D8B; color: white; border: none; border-radius: 3px; cursor: pointer; text-align: center; flex: 1;">
|
|
|
+ 加载
|
|
|
+ <input type="file" accept=".json" onchange="window.loadRecording(event)" style="display: none;">
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ \`;
|
|
|
+
|
|
|
+ document.body.appendChild(panel);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 页面加载完成后创建控制面板
|
|
|
+ if (document.readyState === 'loading') {
|
|
|
+ document.addEventListener('DOMContentLoaded', createControlPanel);
|
|
|
+ } else {
|
|
|
+ createControlPanel();
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('鼠标事件录制和回放功能已启用');
|
|
|
})();
|
|
|
`}} />
|
|
|
<script dangerouslySetInnerHTML={{ __html: `
|