您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
对PC端嘉立创EDA专业版进行触摸适配,以及显示FPS等功能增强
// ==UserScript== // @name 嘉立创EDA专业版增强脚本 // @namespace http://tampermonkey.net/ // @version 1.6 // @description 对PC端嘉立创EDA专业版进行触摸适配,以及显示FPS等功能增强 // @author github@xiaowine // @match https://pro.lceda.cn/editor* // @require https://cdn.jsdelivr.net/gh/hammerjs/hammer.js@ff687ea0daa3c806b9accd2ecb1a46165ea3c00a/hammer.min.js // @grant GM.getValue // @grant GM.setValue // @grant GM.registerMenuCommand // @run-at document-end // @license GPL // @noframes // ==/UserScript== // 配置管理类 class ConfigManager { static DEFAULT_CONFIG = { enablePan: false, enablePinch: false, enablePress: false, showFPS: false, showCoupon: false, lastUpdateTime: '', // 添加最后更新时间 couponData: null // 添加优惠券数据 }; constructor () { this.config = null; this.listeners = new Set(); this.menuCommands = new Map(); // 存储菜单命令的引用 this.menuItems = [ { key: 'enablePan', text: '启用触摸拖动' }, { key: 'enablePinch', text: '启用触摸缩放' }, { key: 'enablePress', text: '启用触摸长按' }, { key: 'showFPS', text: '显示FPS' }, { key: 'showCoupon', text: '获取商城优惠' } ]; } // 加载配置 async load () { try { const savedConfig = await GM.getValue('touchConfig', null); this.config = savedConfig ? JSON.parse(savedConfig) : ConfigManager.DEFAULT_CONFIG; } catch (error) { console.error('加载配置失败:', error); this.config = ConfigManager.DEFAULT_CONFIG; } return this.config; } // 保存配置 async save () { await GM.setValue('touchConfig', JSON.stringify(this.config)); this.notifyListeners(); } // 切换配置项 async toggleSetting (key) { if (this._updating) return; // 防止重复触发 this.config[key] = !this.config[key]; await this.save(); await this.updateMenu(key); } // 获取配置 getConfig () { return this.config; } // 注册(不可用)配置变更监听器 addChangeListener (listener) { this.listeners.add(listener); } // 移除配置变更监听器 removeChangeListener (listener) { this.listeners.delete(listener); } // 通知所有监听器 notifyListeners () { this.listeners.forEach(listener => listener(this.config)); } // 初始化菜单 async initMenu () { // 确保先清理旧菜单 await this.unregisterAllMenus(); for (const { key, text } of this.menuItems) { const command = await GM.registerMenuCommand( `${this.config[key] ? '✅' : '❌'} ${text}`, () => this.toggleSetting(key) ); this.menuCommands.set(key, command); } } // 更新菜单项显示 async updateMenu (key) { // 防止重复更新 if (this._updating) return; this._updating = true; try { const item = this.menuItems.find(item => item.key === key); if (item) { // 先注销所有旧的菜单命令 await this.unregisterAllMenus(); // 重新注册(不可用)所有菜单命令 await this.initMenu(); } } finally { this._updating = false; } } // 注销所有菜单命令 async unregisterAllMenus () { for (const command of this.menuCommands.values()) { if (command) { await GM.unregisterMenuCommand(command); } } this.menuCommands.clear(); } } // TouchEventHandler类修改 class TouchEventHandler { constructor (targetElement, configManager) { this.targetElement = targetElement; this.hammer = null; this.scaleThreshold = 0.1; this.scrollSensitivity = 0.1; this.lastScale = 1; this.configManager = configManager; // 监听配置变更 this.configManager.addChangeListener(() => { this.destroy(); this.init(); }); } init () { if (!this.targetElement) { console.log("未找到目标元素"); return false; } console.log("目标元素已获取,初始化Hammer.js:", this.targetElement); // 修改Hammer初始化方式 this.hammer = new Hammer.Manager(this.targetElement, { touchAction: 'none', inputClass: Hammer.TouchInput }); // 添加识别器 this.hammer.add(new Hammer.Pan({ direction: Hammer.DIRECTION_ALL })); this.hammer.add(new Hammer.Pinch()); this.hammer.add(new Hammer.Press({ time: 500 })); this.addEventListeners(); return true; } getSVGCenter () { const rect = this.targetElement.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2, }; } addEventListeners () { console.log("添加Hammer.js事件监听"); const config = this.configManager.getConfig(); if (config.enablePan) { this.hammer.on('panstart', (ev) => { console.log('Pan start triggered:', ev); const mouseDownEvent = new MouseEvent("mousedown", { bubbles: true, cancelable: true, button: 1, clientX: ev.center.x, clientY: ev.center.y }); this.targetElement.dispatchEvent(mouseDownEvent); }); this.hammer.on('panmove', (ev) => { console.log('Pan move:', { x: ev.center.x, y: ev.center.y }); const mouseMoveEvent = new MouseEvent("mousemove", { bubbles: true, cancelable: true, button: 1, clientX: ev.center.x, clientY: ev.center.y, }); this.targetElement.dispatchEvent(mouseMoveEvent); }); } if (config.enablePinch) { this.hammer.on('pinch', (ev) => { console.log('Pinch:', { scale: ev.scale, lastScale: this.lastScale }); const scaleDiff = ev.scale - this.lastScale; if (Math.abs(scaleDiff) > this.scaleThreshold) { const center = this.getSVGCenter(); console.log('Pinch threshold reached:', { scaleDiff, center }); const scrollEvent = new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: scaleDiff > 0 ? -10 : 10, clientX: center.x, clientY: center.y, }); this.targetElement.dispatchEvent(scrollEvent); this.lastScale = ev.scale; } }); } if (config.enablePress) { this.hammer.on('press', (ev) => { const rightClickEvent = new MouseEvent("contextmenu", { bubbles: true, cancelable: true, button: 2, clientX: ev.center.x, clientY: ev.center.y, }); this.targetElement.parentNode.dispatchEvent(rightClickEvent); }); } } destroy () { if (this.hammer) { console.log("正在移除Hammer.js事件监听"); this.hammer.destroy(); this.hammer = null; } console.log("Hammer.js事件监听已移除"); } } // UI增强处理类 class UIEnhancer { constructor (configManager) { this.configManager = configManager; this.fpsCounter = { element: null, frameCount: 0, previousTimestamp: 0, animationFrameHandle: null, currentFps: 0, interval: 1000, originalText: '' // 保存原始用户名 }; this.configManager.addChangeListener(() => { if (!this.configManager.getConfig().showFPS) { console.log('UIEnhancer: Config changed, updating UI'); this.startFPSCounter(); } else { this.stopFPSCounter(); } }); } init () { this.findElements(); if (!this.configManager.getConfig().showFPS) return; console.log('UIEnhancer: Starting initialization'); this.startFPSCounter(); } findElements () { console.log('UIEnhancer: Finding username element'); // 查找用户名span元素 const usernameSpan = document.querySelector('#loginUsername span'); if (usernameSpan) { console.log('UIEnhancer: Username element found'); this.fpsCounter.element = usernameSpan; this.fpsCounter.originalText = usernameSpan.textContent; } else { console.log('UIEnhancer: Username element not found'); } } updateFps () { const currentTimestamp = Date.now(); this.fpsCounter.frameCount++; if (currentTimestamp > this.fpsCounter.interval + this.fpsCounter.previousTimestamp) { this.fpsCounter.currentFps = Math.round( (this.fpsCounter.frameCount * 1000) / (currentTimestamp - this.fpsCounter.previousTimestamp) ); this.fpsCounter.frameCount = 0; this.fpsCounter.previousTimestamp = currentTimestamp; if (this.fpsCounter.element) { console.log('UIEnhancer: FPS updated to', this.fpsCounter.currentFps); this.fpsCounter.element.textContent = `FPS: ${this.fpsCounter.currentFps}`; } } this.fpsCounter.animationFrameHandle = requestAnimationFrame(() => this.updateFps()); } startFPSCounter () { console.log('UIEnhancer: Starting FPS counter'); if (!this.fpsCounter.animationFrameHandle && this.fpsCounter.element) { this.fpsCounter.frameCount = 0; this.fpsCounter.previousTimestamp = Date.now(); this.updateFps(); } } stopFPSCounter () { console.log('UIEnhancer: Stopping FPS counter'); if (this.fpsCounter.animationFrameHandle) { cancelAnimationFrame(this.fpsCounter.animationFrameHandle); this.fpsCounter.animationFrameHandle = null; this.fpsCounter.currentFps = 0; if (this.fpsCounter.element) { this.fpsCounter.element.textContent = this.fpsCounter.originalText; } } } destroy () { if (!this.configManager.getConfig().showFPS) return; console.log('UIEnhancer: Destroying'); this.stopFPSCounter(); } } class DiscountHandler { constructor (configManager) { this.configManager = configManager; this.runTimeUrl = 'https://szlcsc-help.xiaowine.cc/run_time.txt'; this.couponUrl = 'https://szlcsc-help.xiaowine.cc/simple_coupon_details.json'; this.configManager.addChangeListener(() => { if (!this.configManager.getConfig().showCoupon) return console.log('DiscountHandler: Config changed'); this.checkForUpdates(); this.updateCouponDialog(); this.listener(); }); } async init () { if (!this.configManager.getConfig().showCoupon) return console.log('DiscountHandler: Starting initialization'); await this.checkForUpdates(); // Watch for dialog creation if (this.observer) { this.observer.disconnect(); } this.observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.id === 'chooseDeviceDialogContainer_dialog_box') { this.updateCouponDialog(); this.listener(); this.observer.disconnect(); this.observer = null; } }); }); }); this.observer.observe(document.body, { childList: true, subtree: true }); setInterval(() => { this.checkForUpdates(); }, 60000); } addCouponData (shopItems) { try { Array.from(shopItems).forEach(item => { const observer = new MutationObserver((_mutations, obs) => { let category = null let brandHrefId = null const categoryElement = item.querySelector('.two-tit_6gtEt a:nth-child(2)'); if (categoryElement) { category = categoryElement?.getAttribute('title'); } const brandElement = item.querySelector('.l02_zb_ms1h6 .brand-name'); if (brandElement) { const brandHref = brandElement?.getAttribute('href'); brandHrefId = brandHref?.match(/\/brand\/(\d+)\.html/)?.[1] || null; } if (category || brandHrefId) { obs.disconnect(); } if (category && brandHrefId) { const couponData = this.configManager.getConfig().couponData; if (couponData && couponData[brandHrefId]) { const couponContainer = item.querySelector('.l02_yb_3cN-b'); if (couponContainer) { const couponListItem = document.createElement('li'); const couponLink = document.createElement('a'); couponLink.href = `https://list.szlcsc.com/brand/${brandHrefId}.html`; couponLink.target = '_blank'; couponLink.innerHTML = couponData[brandHrefId]["coupon_name"]; couponLink.style.color = 'red'; couponLink.style.fontSize = '12px'; couponListItem.appendChild(couponLink); couponContainer.appendChild(couponListItem); const discountListItem = document.createElement('li'); discountListItem.innerHTML = `优惠券: ${couponData[brandHrefId]["min_order_amount"]}-${couponData[brandHrefId]["coupon_amount"]}`; discountListItem.style.color = 'red'; discountListItem.style.fontSize = '12px'; couponContainer.appendChild(discountListItem); } } } }); // Start observing the item for DOM changes observer.observe(item, { childList: true, subtree: true }); }); } catch (error) { console.error('处理商品数据时出错:', error); } } listener () { const config = { childList: true, subtree: true }; const shopListObserver = new MutationObserver(mutations => { const addedItems = mutations .filter(mutation => mutation.type === 'childList') .flatMap(mutation => Array.from(mutation.addedNodes)) .filter(node => node.tagName === 'DIV' && !node.className) .flatMap(div => Array.from(div.children)) .filter(Boolean); if (addedItems.length > 0) { console.log('检测到新商品列表项:', addedItems.length); this.addCouponData(addedItems); } }); const shopList = document.getElementById('shop_list'); if (shopList) { shopListObserver.observe(shopList, config); console.log('商品列表观察器已启动'); } else { console.warn('未找到商品列表容器'); } } async checkForUpdates () { try { const response = await fetch(this.runTimeUrl + '?' + Date.now()); const currentTime = await response.text(); const config = this.configManager.getConfig(); console.log('Checking update time:', currentTime, 'vs', config.lastUpdateTime); if (currentTime !== config.lastUpdateTime) { console.log('New update available, fetching coupon data...'); const couponResponse = await fetch(this.couponUrl + '?' + Date.now()); const couponData = await couponResponse.json(); // 更新配置 config.lastUpdateTime = currentTime; config.couponData = couponData; await this.configManager.save(); console.log('Coupon data updated:', couponData); this.updateCouponDialog(currentTime); } else { console.log('No updates available'); } } catch (error) { console.error('Error checking for updates:', error); } } updateCouponDialog () { const time = this.configManager.getConfig().lastUpdateTime console.log('Updating coupon dialog with time:', time); const dialog = document.querySelector('.header_left_title_Hlg2C'); if (dialog) { console.log('Found dialog element'); // 检查是否已经添加了时间信息 const lastUpdateTime = `|优惠信息更新时间: ${time}` if (!dialog.innerHTML.includes('|')) { dialog.innerHTML += lastUpdateTime; console.log('Added time div to dialog'); } else { dialog.innerHTML = dialog.innerHTML.split('|')[0] + lastUpdateTime; console.log('Time div already exists'); } } else { console.log('Dialog element not found'); } } } // 主程序入口 (async function () { "use strict"; if (window.isScriptLoaded) { return; } window.isScriptLoaded = true; // 初始化配置管理器 const configManager = new ConfigManager(); await configManager.load(); await configManager.initMenu(); // 初始化UI增强 const uiEnhancer = new UIEnhancer(configManager); uiEnhancer.init(); const discountHandler = new DiscountHandler(configManager); discountHandler.init(); document.documentElement.style.touchAction = "none"; window.addEventListener("popstate", parseTabParams); let touchHandler = null; let lastUrl = ""; /** * 移除现有的事件监听器 */ function removeEventListeners () { if (touchHandler) { touchHandler.destroy(); touchHandler = null; return true; } return false; } /** * 初始化触摸事件处理 * @param {Element} element - 要监听触摸事件的目标元素 */ function initTouchHandler (element) { // 先移除现有的事件监听 removeEventListeners(); if (element) { console.log("正在初始化触摸处理器,目标元素:", element); touchHandler = new TouchEventHandler(element, configManager); const success = touchHandler.init(); if (success) { // 添加触摸调试信息 // element.addEventListener('touchstart', (e) => console.log('Native touchstart:', e), false); console.log("触摸事件处理已初始化"); return true; } } return false; } /** * 解析 URL 中的 tab 参数并匹配 div 和 iframe */ function parseTabParams () { if (configManager.getConfig().enablePan && configManager.getConfig().enablePinch && configManager.getConfig().enablePress) { const currentUrl = window.location.href; // 如果 URL 没变,则不执行解析 if (currentUrl === lastUrl) { console.log("URL 未变化,跳过执行"); return; } // 更新上次的 URL 记录 lastUrl = currentUrl; console.log("检测到 URL 变化:", currentUrl); // 解析 tab 参数 const hash = window.location.hash; const tabMatch = hash.match(/tab=([^&#]*)/); if (tabMatch) { const tabList = tabMatch[1].split("|"); console.log("Tab 参数解析为列表:", tabList); // 过滤出以 "*" 开头的项,并去除 "*" const starredTabs = tabList .filter((tab) => tab.startsWith("*")) .map((tab) => tab.substring(1)); if (starredTabs.length > 0) { console.log('以 "*" 开头的项:', starredTabs); // 调用单独的函数解析 div 和 iframe const rootElement = parseDivAndIframe(starredTabs); if (rootElement) { console.log("解析成功,初始化触摸事件处理"); initTouchHandler(rootElement); } else { console.log("未找到符合条件的元素,无法初始化触摸事件"); } } } else { console.log("未找到 tab 参数"); } } } /** * 在 #tabbar_bodies 下查找 div 元素,并匹配 uuid * 如果找到符合条件的 div,则继续查找其内部的 iframe,获取 #root 或 #canvas * @param {Array} starredTabs - 以 "*" 开头的 tab 列表(去除了 "*") * @returns {Element|boolean} 如果找到匹配的元素返回该元素,未找到返回 false */ function parseDivAndIframe (starredTabs) { // 获取 #tabbar_bodies 下的所有 div const divs = document.querySelectorAll("#tabbar_bodies div"); if (divs.length === 0) { console.log("未找到任何 div"); return false; } // 遍历 div 并匹配 uuid for (let div of divs) { const uuid = div.getAttribute("uuid"); // 获取 uuid 属性 if (uuid && starredTabs.includes(uuid)) { console.log("匹配的 div:", div); // 查找 div 内的 iframe const iframe = div.querySelector("iframe"); if (iframe) { console.log("找到匹配的 iframe:", iframe); // 尝试访问 iframe 的内容 try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; // 查找 #root 或 #canvas let rootElement = iframeDoc.querySelector("#root"); if (!rootElement) { rootElement = iframeDoc.querySelector("#canvas"); } if (rootElement) { console.log("找到目标元素:", rootElement); return rootElement; } else { console.log("未找到 #root 或 #canvas"); } } catch (error) { console.error("无法访问 iframe 内容:", error); } } else { console.log("匹配的 div 内未找到 iframe"); } } } return false; // 如果没有找到匹配的 div 和 iframe } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址