本文于 2025年8月21日 9:37 更新,注意查看最新内容
因为有些常用网站不需要显示网页二维码,所以更新了鼠标长按关闭按钮,关闭当前页面或者当前域名的功能:
// ==UserScript== // @name 页面二维码生成器(带管理功能) // @namespace http://tampermonkey.net/ // @version 1.5 // @description 生成当前页面二维码,带完善缓存机制和长按管理隐藏功能 // @author 某知名AI // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @connect cdnjs.cloudflare.com // @require https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js // ==/UserScript== (function() { 'use strict'; // 缓存配置 - 7天有效期 const CACHE_EXPIRY_DAYS = 7; const FONT_CACHE_KEY = 'qrcodeFontCache'; const FONT_CACHE_TIMESTAMP = 'qrcodeFontTimestamp'; // 禁用设置的存储键名 const DISABLED_PAGES_KEY = 'disabledQrcodePages'; const DISABLED_DOMAINS_KEY = 'disabledQrcodeDomains'; // 所需字体文件URL const fontUrls = { woff2: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/fonts/fontawesome-webfont.woff2?v=4.7.0', woff: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/fonts/fontawesome-webfont.woff?v=4.7.0' }; // 检查缓存是否有效 function isCacheValid() { const timestamp = GM_getValue(FONT_CACHE_TIMESTAMP, 0); const now = new Date().getTime(); const expiryTime = CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000; return timestamp + expiryTime > now; } // 从缓存加载字体 function loadFromCache() { const cachedFonts = GM_getValue(FONT_CACHE_KEY, null); if (cachedFonts) { injectFontStyles(cachedFonts); return true; } return false; } // 下载字体并缓存 function downloadAndCacheFonts() { // 优先尝试woff2格式,兼容性更好 fetchFont(fontUrls.woff2, 'woff2') .catch(() => { // 如果woff2失败,尝试woff格式 return fetchFont(fontUrls.woff, 'woff'); }) .then(({data, format}) => { const fontData = {data, format}; // 存储到缓存 GM_setValue(FONT_CACHE_KEY, fontData); GM_setValue(FONT_CACHE_TIMESTAMP, new Date().getTime()); injectFontStyles(fontData); }) .catch(() => { // 所有字体加载失败时使用基础样式 fallback injectFallbackStyles(); }); } // 下载字体文件 function fetchFont(url, format) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer', onload: function(response) { if (response.status === 200 && response.response) { // 转换为base64 const base64Data = btoa( new Uint8Array(response.response).reduce( (data, byte) => data + String.fromCharCode(byte), '' ) ); resolve({data: base64Data, format}); } else { reject(new Error(`Failed to load font: ${response.status}`)); } }, onerror: function() { reject(new Error('Network error while loading font')); }, ontimeout: function() { reject(new Error('Font loading timed out')); } }); }); } // 注入字体样式 function injectFontStyles(fontData) { const style = document.createElement('style'); style.textContent = ` @font-face { font-family: 'FontAwesome'; src: url('data:application/font-${fontData.format};base64,${fontData.data}') format('${fontData.format}'); font-weight: normal; font-style: normal; } .fa { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .fa-qrcode:before { content: "\\f029"; } .fa-times:before { content: "\\f00d"; } .fa-cog:before { content: "\\f013"; } .fa-file-o:before { content: "\\f15c"; } .fa-globe:before { content: "\\f0ac"; } .fa-trash:before { content: "\\f1f8"; } /* 管理界面样式 */ .qr-disable-prompt { position: fixed; width: 250px; background-color: white; border-radius: 8px; box-shadow: 0 3px 15px rgba(0,0,0,0.2); padding: 15px; z-index: 10001; border: 1px solid #eee; } .qr-disable-prompt h4 { margin: 0 0 10px 0; padding-bottom: 8px; border-bottom: 1px solid #eee; font-size: 16px; } .qr-disable-option { display: block; width: 100%; padding: 8px 10px; text-align: left; background: none; border: none; cursor: pointer; border-radius: 4px; margin-bottom: 5px; transition: background-color 0.2s; } .qr-disable-option:hover { background-color: #f1f1f1; } .qr-disable-option i { margin-right: 8px; width: 16px; text-align: center; } .qr-prompt-divider { margin: 10px 0; border: none; border-top: 1px dashed #eee; } .qr-management-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 800px; background-color: white; border-radius: 8px; box-shadow: 0 5px 30px rgba(0,0,0,0.2); z-index: 10002; padding: 20px; max-height: 80vh; overflow-y: auto; display: none; } .qr-management-modal h3 { margin-top: 0; padding-bottom: 10px; border-bottom: 1px solid #eee; } .qr-management-section { margin-bottom: 25px; } .qr-management-section h4 { margin-bottom: 10px; color: #444; } .qr-disabled-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background-color: #f8f9fa; border-radius: 4px; margin-bottom: 8px; word-break: break-all; } .qr-remove-btn { background: none; border: none; color: #dc3545; cursor: pointer; padding: 4px 8px; border-radius: 3px; transition: background-color 0.2s; } .qr-remove-btn:hover { background-color: #ffe3e3; } .qr-empty-state { color: #666; padding: 15px; text-align: center; background-color: #f8f9fa; border-radius: 4px; } .qr-close-management { margin-top: 15px; padding: 8px 16px; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; } .qr-close-management:hover { background-color: #5a6268; } `; document.head.appendChild(style); } // 字体加载失败时的 fallback 样式 function injectFallbackStyles() { const style = document.createElement('style'); style.textContent = ` .fa-qrcode:before { content: "🔳"; } .fa-times:before { content: "✕"; } .fa-cog:before { content: "⚙️"; } .fa-file-o:before { content: "📄"; } .fa-globe:before { content: "🌐"; } .fa-trash:before { content: "🗑️"; } /* 管理界面样式 */ .qr-disable-prompt { position: fixed; width: 250px; background-color: white; border-radius: 8px; box-shadow: 0 3px 15px rgba(0,0,0,0.2); padding: 15px; z-index: 10001; border: 1px solid #eee; } .qr-disable-prompt h4 { margin: 0 0 10px 0; padding-bottom: 8px; border-bottom: 1px solid #eee; font-size: 16px; } .qr-disable-option { display: block; width: 100%; padding: 8px 10px; text-align: left; background: none; border: none; cursor: pointer; border-radius: 4px; margin-bottom: 5px; transition: background-color 0.2s; } .qr-disable-option:hover { background-color: #f1f1f1; } .qr-disable-option i { margin-right: 8px; width: 16px; text-align: center; } .qr-prompt-divider { margin: 10px 0; border: none; border-top: 1px dashed #eee; } .qr-management-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 800px; background-color: white; border-radius: 8px; box-shadow: 0 5px 30px rgba(0,0,0,0.2); z-index: 10002; padding: 20px; max-height: 80vh; overflow-y: auto; display: none; } .qr-management-modal h3 { margin-top: 0; padding-bottom: 10px; border-bottom: 1px solid #eee; } .qr-management-section { margin-bottom: 25px; } .qr-management-section h4 { margin-bottom: 10px; color: #444; } .qr-disabled-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background-color: #f8f9fa; border-radius: 4px; margin-bottom: 8px; word-break: break-all; } .qr-remove-btn { background: none; border: none; color: #dc3545; cursor: pointer; padding: 4px 8px; border-radius: 3px; transition: background-color 0.2s; } .qr-remove-btn:hover { background-color: #ffe3e3; } .qr-empty-state { color: #666; padding: 15px; text-align: center; background-color: #f8f9fa; border-radius: 4px; } .qr-close-management { margin-top: 15px; padding: 8px 16px; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; } .qr-close-management:hover { background-color: #5a6268; } `; document.head.appendChild(style); } // 禁用管理相关函数 function getDisabledPages() { return GM_getValue(DISABLED_PAGES_KEY, []); } function getDisabledDomains() { return GM_getValue(DISABLED_DOMAINS_KEY, []); } function addDisabledPage(url) { const pages = getDisabledPages(); if (!pages.includes(url)) { pages.push(url); GM_setValue(DISABLED_PAGES_KEY, pages); } } function addDisabledDomain(domain) { const domains = getDisabledDomains(); if (!domains.includes(domain)) { domains.push(domain); GM_setValue(DISABLED_DOMAINS_KEY, domains); } } function removeDisabledPage(url) { const pages = getDisabledPages().filter(page => page !== url); GM_setValue(DISABLED_PAGES_KEY, pages); return pages; } function removeDisabledDomain(domain) { const domains = getDisabledDomains().filter(d => d !== domain); GM_setValue(DISABLED_DOMAINS_KEY, domains); return domains; } function isCurrentPageDisabled() { const currentUrl = window.location.href; const disabledPages = getDisabledPages(); return disabledPages.some(page => currentUrl.startsWith(page)); } function isCurrentDomainDisabled() { const currentDomain = window.location.hostname; const disabledDomains = getDisabledDomains(); return disabledDomains.includes(currentDomain); } // 创建管理界面 function createManagementInterface(buttonContainer, overlay) { const managementModal = document.createElement('div'); managementModal.className = 'qr-management-modal'; managementModal.innerHTML = ` <h3><i class="fa fa-cog" style="margin-right:8px;"></i>二维码按钮管理</h3> <div class="qr-management-section"> <h4>已禁用二维码按钮的页面</h4> <div id="qr-disabled-pages-container"></div> </div> <div class="qr-management-section"> <h4>已禁用二维码按钮的域名</h4> <div id="qr-disabled-domains-container"></div> </div> <button class="qr-close-management">关闭管理</button> `; document.body.appendChild(managementModal); // 关闭按钮事件 managementModal.querySelector('.qr-close-management').addEventListener('click', () => { managementModal.style.display = 'none'; overlay.style.display = 'none'; }); // 渲染禁用列表 function renderDisabledLists() { const pagesContainer = managementModal.querySelector('#qr-disabled-pages-container'); const domainsContainer = managementModal.querySelector('#qr-disabled-domains-container'); // 渲染禁用页面 const disabledPages = getDisabledPages(); if (disabledPages.length === 0) { pagesContainer.innerHTML = '<div class="qr-empty-state">没有禁用任何页面</div>'; } else { pagesContainer.innerHTML = disabledPages.map(page => ` <div class="qr-disabled-item"> <span>${page}</span> <button class="qr-remove-btn" data-type="page" data-value="${page}"> <i class="fa fa-trash"></i> </button> </div> `).join(''); } // 渲染禁用域名 const disabledDomains = getDisabledDomains(); if (disabledDomains.length === 0) { domainsContainer.innerHTML = '<div class="qr-empty-state">没有禁用任何域名</div>'; } else { domainsContainer.innerHTML = disabledDomains.map(domain => ` <div class="qr-disabled-item"> <span>${domain}</span> <button class="qr-remove-btn" data-type="domain" data-value="${domain}"> <i class="fa fa-trash"></i> </button> </div> `).join(''); } // 添加删除按钮事件 managementModal.querySelectorAll('.qr-remove-btn').forEach(btn => { btn.addEventListener('click', (e) => { const type = e.target.closest('.qr-remove-btn').dataset.type; const value = e.target.closest('.qr-remove-btn').dataset.value; if (type === 'page') { removeDisabledPage(value); } else if (type === 'domain') { removeDisabledDomain(value); } renderDisabledLists(); // 如果当前页面或域名被启用,显示按钮 if ((type === 'page' && value === window.location.href) || (type === 'domain' && value === window.location.hostname)) { buttonContainer.style.display = 'flex'; } }); }); } // 显示管理界面 function showManagement() { managementModal.style.display = 'block'; overlay.style.display = 'block'; renderDisabledLists(); } return { showManagement }; } // 创建UI元素 function createUI() { // 创建背景遮罩(用于弹窗) const overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.right = '0'; overlay.style.bottom = '0'; overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'; overlay.style.zIndex = '9999'; overlay.style.display = 'none'; overlay.style.backdropFilter = 'blur(2px)'; document.body.appendChild(overlay); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.position = 'fixed'; buttonContainer.style.left = '10px'; buttonContainer.style.top = '50%'; buttonContainer.style.transform = 'translateY(-50%)'; buttonContainer.style.zIndex = '9999'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'column'; buttonContainer.style.alignItems = 'center'; // 检查当前页面或域名是否被禁用 if (isCurrentPageDisabled() || isCurrentDomainDisabled()) { buttonContainer.style.display = 'none'; } // 创建二维码按钮 const qrButton = document.createElement('button'); qrButton.innerHTML = '<i class="fa fa-qrcode"></i>'; // 创建关闭按钮 const hideButton = document.createElement('button'); hideButton.innerHTML = '<i class="fa fa-times"></i>'; hideButton.style.position = 'absolute'; hideButton.style.top = '-10px'; hideButton.style.right = '-10px'; hideButton.style.width = '24px'; hideButton.style.height = '24px'; hideButton.style.borderRadius = '50%'; hideButton.style.backgroundColor = '#f44336'; hideButton.style.color = 'white'; hideButton.style.border = 'none'; hideButton.style.cursor = 'pointer'; hideButton.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)'; hideButton.style.display = 'none'; // 默认隐藏 hideButton.style.alignItems = 'center'; hideButton.style.justifyContent = 'center'; hideButton.style.fontSize = '12px'; hideButton.title = '单击关闭,长按打开管理选项'; // 二维码按钮样式 qrButton.style.width = '40px'; qrButton.style.height = '40px'; qrButton.style.borderRadius = '8px'; qrButton.style.backgroundColor = 'rgba(255, 255, 255, 0.8)'; qrButton.style.color = '#333'; qrButton.style.border = '1px solid #ddd'; qrButton.style.cursor = 'pointer'; qrButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)'; qrButton.style.transition = 'all 0.3s ease'; qrButton.style.display = 'flex'; qrButton.style.alignItems = 'center'; qrButton.style.justifyContent = 'center'; qrButton.style.fontSize = '18px'; qrButton.title = '生成当前页面二维码'; // 创建禁用提示框 const disablePrompt = document.createElement('div'); disablePrompt.className = 'qr-disable-prompt'; disablePrompt.style.display = 'none'; disablePrompt.innerHTML = ` <h4>关闭二维码按钮</h4> <button class="qr-disable-option disable-page"> <i class="fa fa-file-o"></i>在本页关闭 </button> <button class="qr-disable-option disable-domain"> <i class="fa fa-globe"></i>在本域名关闭 </button> <hr class="qr-prompt-divider"> <button class="qr-disable-option manage-settings"> <i class="fa fa-cog"></i>管理设置 </button> `; document.body.appendChild(disablePrompt); // 按钮容器悬停效果 buttonContainer.addEventListener('mouseover', () => { qrButton.style.width = '50px'; qrButton.style.backgroundColor = 'white'; qrButton.style.boxShadow = '0 3px 8px rgba(0,0,0,0.2)'; hideButton.style.display = 'flex'; }); buttonContainer.addEventListener('mouseout', () => { // 如果提示框没显示才隐藏关闭按钮 if (disablePrompt.style.display === 'none') { qrButton.style.width = '40px'; qrButton.style.backgroundColor = 'rgba(255, 255, 255, 0.8)'; qrButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)'; hideButton.style.display = 'none'; } }); // 初始化管理界面 const management = createManagementInterface(buttonContainer, overlay); // 提示框按钮事件 disablePrompt.querySelector('.qr-disable-option.disable-page').addEventListener('click', () => { const currentUrl = window.location.href; addDisabledPage(currentUrl); buttonContainer.style.display = 'none'; disablePrompt.style.display = 'none'; alert('已在本页关闭二维码按钮,刷新页面后生效'); }); disablePrompt.querySelector('.qr-disable-option.disable-domain').addEventListener('click', () => { const currentDomain = window.location.hostname; addDisabledDomain(currentDomain); buttonContainer.style.display = 'none'; disablePrompt.style.display = 'none'; alert(`已在域名 ${currentDomain} 下关闭二维码按钮,刷新页面后生效`); }); disablePrompt.querySelector('.qr-disable-option.manage-settings').addEventListener('click', () => { disablePrompt.style.display = 'none'; management.showManagement(); }); // 长按关闭按钮逻辑 let pressTimer = null; const LONG_PRESS_DELAY = 500; // 长按判定时间:500毫秒 let longPressTriggered = false; // 标记长按是否已触发 // 鼠标按下事件 hideButton.addEventListener('mousedown', (e) => { e.stopPropagation(); longPressTriggered = false; // 重置长按状态 // 启动计时器 pressTimer = setTimeout(() => { // 长按时间达到,显示管理选项 longPressTriggered = true; // 标记长按已触发 // 显示在页面正中间 disablePrompt.style.left = '50%'; disablePrompt.style.top = '50%'; disablePrompt.style.transform = 'translate(-50%, -50%)'; // 显示弹窗 disablePrompt.style.display = 'block'; }, LONG_PRESS_DELAY); }); // 关闭按钮点击事件 hideButton.addEventListener('click', (e) => { e.stopPropagation(); // 阻止事件冒泡到二维码按钮 buttonContainer.style.display = 'none'; }); // 鼠标释放事件 hideButton.addEventListener('mouseup', (e) => { e.stopPropagation(); // 清除计时器 if (pressTimer) { clearTimeout(pressTimer); pressTimer = null; } }); // 鼠标离开按钮区域 hideButton.addEventListener('mouseleave', () => { if (pressTimer) { clearTimeout(pressTimer); pressTimer = null; } // 只有在未触发长按的情况下才关闭弹窗 if (!longPressTriggered) { disablePrompt.style.display = 'none'; } }); // 点击其他区域关闭提示框 document.addEventListener('click', (e) => { if (!disablePrompt.contains(e.target) && e.target !== hideButton && !hideButton.contains(e.target)) { disablePrompt.style.display = 'none'; longPressTriggered = false; // 重置状态 } }); // 点击遮罩关闭所有弹窗 overlay.addEventListener('click', () => { disablePrompt.style.display = 'none'; longPressTriggered = false; document.querySelector('.qr-management-modal').style.display = 'none'; overlay.style.display = 'none'; }); // 弹窗元素(延迟创建) let qrModal, qrContainer, closeButton, urlText; function initModal() { if (qrModal) return; // 创建二维码弹窗容器 qrModal = document.createElement('div'); qrModal.style.position = 'fixed'; qrModal.style.top = '0'; qrModal.style.left = '0'; qrModal.style.width = '100%'; qrModal.style.height = '100%'; qrModal.style.backgroundColor = 'rgba(0,0,0,0.7)'; qrModal.style.display = 'none'; qrModal.style.justifyContent = 'center'; qrModal.style.alignItems = 'center'; qrModal.style.zIndex = '10000'; qrModal.style.flexDirection = 'column'; qrModal.style.backdropFilter = 'blur(3px)'; // 创建二维码图片容器 qrContainer = document.createElement('div'); qrContainer.style.backgroundColor = 'white'; qrContainer.style.padding = '20px'; qrContainer.style.borderRadius = '10px'; qrContainer.style.boxShadow = '0 0 20px rgba(0,0,0,0.5)'; qrContainer.style.textAlign = 'center'; qrContainer.style.maxWidth = '90%'; qrContainer.style.transform = 'scale(0.95)'; qrContainer.style.transition = 'transform 0.3s ease'; // 创建关闭按钮 closeButton = document.createElement('button'); closeButton.innerHTML = '<i class="fa fa-times"></i> 关闭'; closeButton.style.marginTop = '20px'; closeButton.style.padding = '8px 16px'; closeButton.style.backgroundColor = '#666'; closeButton.style.color = 'white'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '5px'; closeButton.style.cursor = 'pointer'; closeButton.style.fontSize = '14px'; closeButton.style.transition = 'background-color 0.2s'; closeButton.addEventListener('mouseover', () => { closeButton.style.backgroundColor = '#333'; }); closeButton.addEventListener('mouseout', () => { closeButton.style.backgroundColor = '#666'; }); // 页面URL文本显示 urlText = document.createElement('p'); urlText.style.wordBreak = 'break-all'; urlText.style.maxWidth = '300px'; urlText.style.marginTop = '15px'; urlText.style.fontSize = '14px'; urlText.style.color = '#555'; // 组装弹窗 qrContainer.appendChild(urlText); qrModal.appendChild(qrContainer); qrModal.appendChild(closeButton); document.body.appendChild(qrModal); // 弹窗事件监听 closeButton.addEventListener('click', () => { qrModal.style.display = 'none'; }); qrModal.addEventListener('click', (e) => { if (e.target === qrModal) { qrModal.style.display = 'none'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && qrModal.style.display === 'flex') { qrModal.style.display = 'none'; } }); } // 生成二维码函数 function generateQRCode() { initModal(); // 首次点击时初始化弹窗 // 清空之前的二维码 while (qrContainer.firstChild) { if (qrContainer.firstChild.tagName === 'IMG' || qrContainer.firstChild.tagName === 'CANVAS') { qrContainer.removeChild(qrContainer.firstChild); } else { break; } } // 重置容器缩放 qrContainer.style.transform = 'scale(0.95)'; // 获取当前页面URL const currentUrl = window.location.href; urlText.textContent = currentUrl; // 生成二维码 QRCode.toCanvas(currentUrl, { width: 300, margin: 1 }, function (error, canvas) { if (error) { console.error(error); alert('生成二维码失败: ' + error.message); return; } qrContainer.insertBefore(canvas, qrContainer.firstChild); }); // 显示弹窗 qrModal.style.display = 'flex'; } // 绑定事件 qrButton.addEventListener('click', generateQRCode); // 添加到页面 buttonContainer.appendChild(qrButton); buttonContainer.appendChild(hideButton); document.body.appendChild(buttonContainer); } // 初始化流程 - 优先使用缓存 if (isCacheValid() && loadFromCache()) { // 缓存有效且加载成功,创建UI createUI(); } else { // 缓存无效或加载失败,重新下载 downloadAndCacheFonts(); // 无论字体加载结果如何,都创建UI(确保基本功能可用) createUI(); } })();
以下是原版本:
一个经常使用的网站需要搭配手机一起使用,但是由于屏蔽了右键,导致Edge的QR码无法调用,于是用AI写了一个脚本,会在每个页面左边的正中间生成一个二维码图标,点击之后可以一键生成当前网页的二维码。
// ==UserScript== // @name 页面二维码生成器(完整缓存版) // @namespace http://tampermonkey.net/ // @version 1.4 // @description 生成当前页面二维码,带完善缓存机制确保图标正常显示 // @author 某知名AI // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @connect cdnjs.cloudflare.com // @require https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js // ==/UserScript== (function() { 'use strict'; // 缓存配置 - 7天有效期 const CACHE_EXPIRY_DAYS = 7; const FONT_CACHE_KEY = 'qrcodeFontCache'; const FONT_CACHE_TIMESTAMP = 'qrcodeFontTimestamp'; // 所需字体文件URL const fontUrls = { woff2: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/fonts/fontawesome-webfont.woff2?v=4.7.0', woff: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/fonts/fontawesome-webfont.woff?v=4.7.0' }; // 检查缓存是否有效 function isCacheValid() { const timestamp = GM_getValue(FONT_CACHE_TIMESTAMP, 0); const now = new Date().getTime(); const expiryTime = CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000; return timestamp + expiryTime > now; } // 从缓存加载字体 function loadFromCache() { const cachedFonts = GM_getValue(FONT_CACHE_KEY, null); if (cachedFonts) { injectFontStyles(cachedFonts); return true; } return false; } // 下载字体并缓存 function downloadAndCacheFonts() { // 优先尝试woff2格式,兼容性更好 fetchFont(fontUrls.woff2, 'woff2') .catch(() => { // 如果woff2失败,尝试woff格式 return fetchFont(fontUrls.woff, 'woff'); }) .then(({data, format}) => { const fontData = {data, format}; // 存储到缓存 GM_setValue(FONT_CACHE_KEY, fontData); GM_setValue(FONT_CACHE_TIMESTAMP, new Date().getTime()); injectFontStyles(fontData); }) .catch(() => { // 所有字体加载失败时使用基础样式 fallback injectFallbackStyles(); }); } // 下载字体文件 function fetchFont(url, format) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer', onload: function(response) { if (response.status === 200 && response.response) { // 转换为base64 const base64Data = btoa( new Uint8Array(response.response).reduce( (data, byte) => data + String.fromCharCode(byte), '' ) ); resolve({data: base64Data, format}); } else { reject(new Error(`Failed to load font: ${response.status}`)); } }, onerror: function() { reject(new Error('Network error while loading font')); }, ontimeout: function() { reject(new Error('Font loading timed out')); } }); }); } // 注入字体样式 function injectFontStyles(fontData) { const style = document.createElement('style'); style.textContent = ` @font-face { font-family: 'FontAwesome'; src: url('data:application/font-${fontData.format};base64,${fontData.data}') format('${fontData.format}'); font-weight: normal; font-style: normal; } .fa { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .fa-qrcode:before { content: "\\f029"; } .fa-times:before { content: "\\f00d"; } `; document.head.appendChild(style); } // 字体加载失败时的 fallback 样式 function injectFallbackStyles() { const style = document.createElement('style'); style.textContent = ` .fa-qrcode:before { content: "🔳"; } .fa-times:before { content: "✕"; } `; document.head.appendChild(style); } // 创建UI元素 function createUI() { // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.position = 'fixed'; buttonContainer.style.left = '10px'; buttonContainer.style.top = '50%'; buttonContainer.style.transform = 'translateY(-50%)'; buttonContainer.style.zIndex = '9999'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'column'; buttonContainer.style.alignItems = 'center'; // 创建二维码按钮 const qrButton = document.createElement('button'); qrButton.innerHTML = '<i class="fa fa-qrcode"></i>'; // 创建关闭按钮(默认隐藏) const hideButton = document.createElement('button'); hideButton.innerHTML = '<i class="fa fa-times"></i>'; hideButton.style.position = 'absolute'; hideButton.style.top = '-10px'; hideButton.style.right = '-10px'; hideButton.style.width = '24px'; hideButton.style.height = '24px'; hideButton.style.borderRadius = '50%'; hideButton.style.backgroundColor = '#f44336'; hideButton.style.color = 'white'; hideButton.style.border = 'none'; hideButton.style.cursor = 'pointer'; hideButton.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)'; hideButton.style.display = 'none'; // 默认隐藏 hideButton.style.alignItems = 'center'; hideButton.style.justifyContent = 'center'; hideButton.style.fontSize = '12px'; hideButton.title = '彻底隐藏二维码按钮(刷新页面可恢复)'; // 二维码按钮样式 qrButton.style.width = '40px'; qrButton.style.height = '40px'; qrButton.style.borderRadius = '8px'; qrButton.style.backgroundColor = 'rgba(255, 255, 255, 0.8)'; qrButton.style.color = '#333'; qrButton.style.border = '1px solid #ddd'; qrButton.style.cursor = 'pointer'; qrButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)'; qrButton.style.transition = 'all 0.3s ease'; qrButton.style.display = 'flex'; qrButton.style.alignItems = 'center'; qrButton.style.justifyContent = 'center'; qrButton.style.fontSize = '18px'; qrButton.title = '生成当前页面二维码'; // 按钮容器悬停效果 buttonContainer.addEventListener('mouseover', () => { qrButton.style.width = '50px'; qrButton.style.backgroundColor = 'white'; qrButton.style.boxShadow = '0 3px 8px rgba(0,0,0,0.2)'; hideButton.style.display = 'flex'; }); buttonContainer.addEventListener('mouseout', () => { qrButton.style.width = '40px'; qrButton.style.backgroundColor = 'rgba(255, 255, 255, 0.8)'; qrButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)'; hideButton.style.display = 'none'; }); // 弹窗元素(延迟创建) let qrModal, qrContainer, closeButton, urlText; function initModal() { if (qrModal) return; // 创建二维码弹窗容器 qrModal = document.createElement('div'); qrModal.style.position = 'fixed'; qrModal.style.top = '0'; qrModal.style.left = '0'; qrModal.style.width = '100%'; qrModal.style.height = '100%'; qrModal.style.backgroundColor = 'rgba(0,0,0,0.7)'; qrModal.style.display = 'none'; qrModal.style.justifyContent = 'center'; qrModal.style.alignItems = 'center'; qrModal.style.zIndex = '10000'; qrModal.style.flexDirection = 'column'; qrModal.style.backdropFilter = 'blur(3px)'; // 创建二维码图片容器 qrContainer = document.createElement('div'); qrContainer.style.backgroundColor = 'white'; qrContainer.style.padding = '20px'; qrContainer.style.borderRadius = '10px'; qrContainer.style.boxShadow = '0 0 20px rgba(0,0,0,0.5)'; qrContainer.style.textAlign = 'center'; qrContainer.style.maxWidth = '90%'; qrContainer.style.transform = 'scale(0.95)'; qrContainer.style.transition = 'transform 0.3s ease'; // 创建关闭按钮 closeButton = document.createElement('button'); closeButton.innerHTML = '<i class="fa fa-times"></i> 关闭'; closeButton.style.marginTop = '20px'; closeButton.style.padding = '8px 16px'; closeButton.style.backgroundColor = '#666'; closeButton.style.color = 'white'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '5px'; closeButton.style.cursor = 'pointer'; closeButton.style.fontSize = '14px'; closeButton.style.transition = 'background-color 0.2s'; closeButton.addEventListener('mouseover', () => { closeButton.style.backgroundColor = '#333'; }); closeButton.addEventListener('mouseout', () => { closeButton.style.backgroundColor = '#666'; }); // 页面URL文本显示 urlText = document.createElement('p'); urlText.style.wordBreak = 'break-all'; urlText.style.maxWidth = '300px'; urlText.style.marginTop = '15px'; urlText.style.fontSize = '14px'; urlText.style.color = '#555'; // 组装弹窗 qrContainer.appendChild(urlText); qrModal.appendChild(qrContainer); qrModal.appendChild(closeButton); document.body.appendChild(qrModal); // 弹窗事件监听 closeButton.addEventListener('click', () => { qrModal.style.display = 'none'; }); qrModal.addEventListener('click', (e) => { if (e.target === qrModal) { qrModal.style.display = 'none'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && qrModal.style.display === 'flex') { qrModal.style.display = 'none'; } }); } // 生成二维码函数 function generateQRCode() { initModal(); // 首次点击时初始化弹窗 // 清空之前的二维码 while (qrContainer.firstChild) { if (qrContainer.firstChild.tagName === 'IMG' || qrContainer.firstChild.tagName === 'CANVAS') { qrContainer.removeChild(qrContainer.firstChild); } else { break; } } // 重置容器缩放 qrContainer.style.transform = 'scale(0.95)'; // 获取当前页面URL const currentUrl = window.location.href; urlText.textContent = currentUrl; // 生成二维码 QRCode.toCanvas(currentUrl, { width: 300, margin: 1 }, function (error, canvas) { if (error) { console.error(error); alert('生成二维码失败: ' + error.message); return; } qrContainer.insertBefore(canvas, qrContainer.firstChild); }); // 显示弹窗 qrModal.style.display = 'flex'; } // 彻底隐藏二维码按钮 function hideQRButtonCompletely() { if (buttonContainer.parentNode === document.body) { document.body.removeChild(buttonContainer); } } // 绑定事件 qrButton.addEventListener('click', generateQRCode); hideButton.addEventListener('click', hideQRButtonCompletely); // 添加到页面 buttonContainer.appendChild(qrButton); buttonContainer.appendChild(hideButton); document.body.appendChild(buttonContainer); } // 初始化流程 - 优先使用缓存 if (isCacheValid() && loadFromCache()) { // 缓存有效且加载成功,创建UI createUI(); } else { // 缓存无效或加载失败,重新下载 downloadAndCacheFonts(); // 无论字体加载结果如何,都创建UI(确保基本功能可用) createUI(); } })();
Comments | 3 条评论
可以在每一个页面加一个,这样手机可以直接扫码浏览
@云晓晨
目前逻辑就是这样的,你可以用用看,这个是我一直在用的,已经迭代了蛮多版本的,基本没什么大问题
这个不错