本文于 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 条评论
可以在每一个页面加一个,这样手机可以直接扫码浏览
@云晓晨
目前逻辑就是这样的,你可以用用看,这个是我一直在用的,已经迭代了蛮多版本的,基本没什么大问题
这个不错