Browse Source

✨ feat(renderer): 添加双窗口点击同步功能

- 注释掉鼠标事件录制和回放功能代码块
- 实现固定record-id生成逻辑,确保双窗口元素ID一致性
- 添加Vue 2和Vue 3版本的元素创建劫持,自动注入record-id属性
- 使用Broadcast Channel API实现跨窗口通信
- 添加点击事件监听与转发机制,实现双窗口元素点击同步

🐛 fix(renderer): 修复元素ID生成逻辑

- 优化getFixedRecordId函数,确保不同窗口相同元素生成一致ID
- 添加contentHash计算,提高ID唯一性
- 改进parentPath生成逻辑,包含父元素层级信息
yourname 1 month ago
parent
commit
f081601a56
1 changed files with 74 additions and 2 deletions
  1. 74 2
      src/server/renderer.tsx

+ 74 - 2
src/server/renderer.tsx

@@ -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>