|
|
@@ -128,7 +128,7 @@ export const Rooter = () => {
|
|
|
console.log('XHR拦截器已启用,将拦截', INTERCEPT_DOMAIN, '的请求并重定向到当前域');
|
|
|
})();
|
|
|
`}} />
|
|
|
- {/* 鼠标事件录制和回放功能 */}
|
|
|
+ {/* 鼠标事件录制和回放功能
|
|
|
<script dangerouslySetInnerHTML={{ __html: `
|
|
|
// 鼠标事件录制和回放功能
|
|
|
(function() {
|
|
|
@@ -458,7 +458,7 @@ export const Rooter = () => {
|
|
|
|
|
|
console.log('鼠标事件录制和回放功能已启用');
|
|
|
})();
|
|
|
- `}} />
|
|
|
+ `}} /> */}
|
|
|
<script dangerouslySetInnerHTML={{ __html: `
|
|
|
// 页面加载时将数据写入会话存储
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
|
@@ -768,6 +768,78 @@ export const Rooter = () => {
|
|
|
<span></span>
|
|
|
</div>
|
|
|
<div id="app"></div>
|
|
|
+
|
|
|
+ <script dangerouslySetInnerHTML={{ __html: `
|
|
|
+ (function() {
|
|
|
+ // 1. 固定 record-id 生成(复用之前逻辑,确保双窗口元素ID一致)
|
|
|
+ function getFixedRecordId(el) {
|
|
|
+ const tag = el.tagName.toLowerCase();
|
|
|
+ const contentHash = el.textContent.trim().split('').reduce((h, c) => h + c.charCodeAt(0), 0) || '0';
|
|
|
+ let parentPath = '';
|
|
|
+ let current = el.parentElement;
|
|
|
+ while (current && current.tagName) {
|
|
|
+ parentPath += \`-\${current.tagName.toLowerCase()}-\${Array.from(current.children).indexOf(el)}\`;
|
|
|
+ el = current;
|
|
|
+ current = current.parentElement;
|
|
|
+ }
|
|
|
+ return \`rec-\${tag}-\${contentHash}\${parentPath}\`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 注入固定 record-id(劫持 Vue 元素创建)
|
|
|
+ // Vue 2 适配
|
|
|
+ if (window.Vue && Vue.prototype._c) {
|
|
|
+ const originalCreateElement = Vue.prototype._c;
|
|
|
+ Vue.prototype._c = function(tag, data = {}, children, normalizationType) {
|
|
|
+ const vnode = originalCreateElement.call(this, tag, data, children, normalizationType);
|
|
|
+ const originalMounted = vnode.hook?.insert || vnode.hook?.mounted;
|
|
|
+ vnode.hook = vnode.hook || {};
|
|
|
+ vnode.hook.insert = vnode.hook.mounted = function(el) {
|
|
|
+ originalMounted?.call(this, el);
|
|
|
+ if (!el.hasAttribute('record-id')) el.setAttribute('record-id', getFixedRecordId(el));
|
|
|
+ };
|
|
|
+ return vnode;
|
|
|
+ };
|
|
|
+ }
|
|
|
+ console.debug('window.Vue', window.Vue)
|
|
|
+ // Vue 3 适配
|
|
|
+ if (window.Vue && Vue.createVNode) {
|
|
|
+ const originalCreateVNode = Vue.createVNode;
|
|
|
+ Vue.createVNode = function(type, props = {}, children, patchFlag, dynamicProps, shapeFlag) {
|
|
|
+ const vnode = originalCreateVNode.call(this, type, props, children, patchFlag, dynamicProps, shapeFlag);
|
|
|
+ const originalMounted = vnode.mount;
|
|
|
+ vnode.mount = function(el) {
|
|
|
+ const result = originalMounted?.call(this, el) || el;
|
|
|
+ const targetEl = result.el || result;
|
|
|
+ if (targetEl && !targetEl.hasAttribute('record-id')) targetEl.setAttribute('record-id', getFixedRecordId(targetEl));
|
|
|
+ return result;
|
|
|
+ };
|
|
|
+ return vnode;
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. Broadcast Channel 同步点击
|
|
|
+ const bc = new BroadcastChannel('vue-click-sync'); // 同一频道名实现通信
|
|
|
+
|
|
|
+ // 监听自身点击,发送到另一个窗口
|
|
|
+ document.addEventListener('click', (e) => {
|
|
|
+ const recordId = e.target.getAttribute('record-id');
|
|
|
+ if (recordId) {
|
|
|
+ bc.postMessage({ type: 'click', recordId }); // 发送点击的 record-id
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 接收其他窗口的点击消息,模拟点击
|
|
|
+ bc.onmessage = (e) => {
|
|
|
+ if (e.data.type === 'click') {
|
|
|
+ const target = document.querySelector(\`[record-id="\${e.data.recordId}"]\`);
|
|
|
+ if (target) {
|
|
|
+ // 模拟原生点击(含冒泡)
|
|
|
+ target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ })();
|
|
|
+ `}} />
|
|
|
</body>
|
|
|
</html>
|
|
|
|