MoreRSS

site iconlklog | 龙鲲修改

马拉松爱好者,喜欢捣腾新鲜的事物。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

lklog | 龙鲲的 RSS 预览

利用花生壳搭建云电脑服务器

2025-08-31 17:20:30

本文于 2025年8月31日 5:20 更新,注意查看最新内容

之前参加运营商活动,领了1台免费的云电脑和一台低价的云电脑,前段时间解决了云电脑自动休眠的问题,就闲置了下来,最近想着有没有什么方法可以盘活两台云电脑的使用,于是就发现了花生壳的反向穿透服务,经过折腾已实现云电脑当服务器使用,这里简单记录一下,以备日后查阅。

由于云电脑本身没有分配公网IP,所以即使配置相应环境,也无法直接在公网进行访问,这里需要用到花生壳的反向穿透服务。

首先下载宝塔Windows版,安装服务器环境,并新建站点,确保本地浏览器可以直接访问站点(由于云电脑没有公网IP,而宝塔自动生成的面板地址又是IP+路径,这里需要替换为缺省地址127.0.0.1)。

然后注册花生壳账号,并下载软件。在内网穿透中设置相关参数,最后访问花生壳提供的域名即可。

PS:花生壳的账号需要实名认证,并且在试用期过后,需要支付6元进行认证,有效期2099年,除此之外没有其他费用,另外内网穿透后的配置仅适合个人使用(例如月流量1G、带宽1M等)。

利用花生壳搭建云电脑服务器最先出现在龙鲲博客

记录第一次发传真

2025-08-27 18:13:28

本文于 2025年8月27日 6:13 更新,注意查看最新内容

前段时间因为邮寄了解到了传真,于是想有机会尝试一下,最近碰巧有机会,于是进行了尝试,这里简单记录一下,以备日后查阅。

传真的形式主要分为两种,一种是网络传真,一种是传统传真。网络传真顾名思义就是利用互联网来发送传真(个人感觉本质还是传统传真),传统传真就是用原始的非话电信业务来进行传真,但目前不管是网络传真还是机械传真,他们的应用场景都比较有限。

因为家里没有传真机,所以在某宝上找了代发服务(建议选择有营业执照的店铺),价格在5元/页。大概流程是将需要传真的文件扫描后通过邮件发送到对方邮箱,然后等待发送完成,成功后会有一份发送报告。

这里可能有人问了,发送传真这么贵,它有什么优势和好处。个人感觉在不考虑成本的情况下,传真的唯一优势在于,若需要发送的对象未提供电子渠道,能够立马投递给对方。

即使考虑成本,在一些申请事项中(一般申请表+身份证明,总页数在两页),也比较划算。

劣势就在于,若采用传真发送,由于是代发,文件内容可能存在泄露的风险,建议在身份证明上,注明文字标识,表明仅用于此次事项,以及不如EMS特快和挂号信的推定效力要强,所以若使用传真发送,务必电话确认。

记录第一次发传真最先出现在龙鲲博客

不说话知道你姓啥在线版(附源码)

2025-08-19 23:16:06

本文于 2025年8月19日 11:35 更新,注意查看最新内容

在B站看到一个关于算命的纪录片,里面有一个大家经常见到的街头小游戏,叫不说话知道你姓啥,大概就是给你几张卡片,每张卡片上的名字若干,然后你选一张有你姓氏的,然后把一本小册子盖住地上一块含有你名字的区域,盖住的区域名字大概有十来个,对方就能知道你姓啥。感觉还挺有意思的,于是去了解了一下,原理是用二进制实现了一个算法,于是用AI也写了一个类似的在线版,大家有兴趣可以玩一下。最后,相信科学,反对迷信。

https://lab.lklog.cn/caixingshi/

源码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>猜姓氏</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #6e8efb, #a777e3);
            min-height: 100vh;
            padding: 20px;
            color: #333;
        }
        
        .container {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
            padding: 35px;
            max-width: 1200px;
            margin: 0 auto;
            text-align: center;
        }
        
        h1 {
            color: #4a2bca;
            margin-bottom: 25px;
            font-size: 2.5rem;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
            position: relative;
            display: inline-block;
        }
        
        h1::after {
            content: '';
            position: absolute;
            bottom: -10px;
            left: 25%;
            width: 50%;
            height: 4px;
            background: linear-gradient(90deg, transparent, #4a2bca, transparent);
            border-radius: 2px;
        }
        
        .instructions {
            margin-bottom: 30px;
            font-size: 1.2rem;
            line-height: 1.6;
            color: #555;
            padding: 0 20px;
        }
        
        .highlight {
            background: linear-gradient(120deg, #a777e3, #6e8efb);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            font-weight: bold;
        }
        
        /* 统一网格容器样式 - 一行显示3个 */
        .card-container, .large-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 25px;
            margin-bottom: 35px;
        }
        
        /* 卡片和区域统一样式 - 确保完全一致 */
        .card, .grid-cell {
            background: white;
            border-radius: 18px;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
            padding: 20px;
            cursor: pointer;
            transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            position: relative;
            overflow: hidden;
            aspect-ratio: 3/4; /* 保持一致的宽高比 */
        }
        
        .card::before, .grid-cell::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 5px;
            background: linear-gradient(90deg, #6e8efb, #a777e3);
            opacity: 0;
            transition: opacity 0.3s ease;
        }
        
        .card:hover, .grid-cell:hover {
            transform: translateY(-8px) scale(1.02);
            box-shadow: 0 15px 30px rgba(0, 0, 0, 0.18);
        }
        
        .card:hover::before, .grid-cell:hover::before {
            opacity: 1;
        }
        
        .card.selected, .grid-cell.selected {
            border: 3px solid #4a2bca;
            transform: scale(1.05);
            box-shadow: 0 15px 30px rgba(74, 43, 202, 0.2);
        }
        
        .card-title, .grid-title {
            font-weight: bold;
            margin-bottom: 15px;
            color: #4a2bca;
            font-size: 1.3rem;
            height: 30px; /* 固定标题高度,确保内容对齐 */
        }
        
        /* 统一内部网格样式 */
        .card-grid, .grid-inner-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            grid-template-rows: repeat(4, 1fr);
            gap: 10px;
            height: calc(100% - 45px); /* 精确计算高度,避免溢出 */
        }
        
        /* 统一姓氏单元格样式 */
        .surname-cell, .grid-surname-cell {
            padding: 8px 5px;
            background: #f0f4ff;
            border-radius: 10px;
            font-size: 0.95rem;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        
        .large-grid-container {
            background: #f8faff;
            border-radius: 18px;
            padding: 25px;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
            margin: 20px auto;
        }
        
        .btn {
            background: linear-gradient(135deg, #6e8efb, #a777e3);
            color: white;
            border: none;
            border-radius: 50px;
            padding: 16px 40px;
            font-size: 1.2rem;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-top: 25px;
        }
        
        .btn:hover {
            transform: translateY(-3px);
            box-shadow: 0 8px 25px rgba(106, 142, 251, 0.6);
        }
        
        .btn:disabled {
            background: #cccccc;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }
        
        .result {
            margin-top: 40px;
            padding: 30px;
            background: linear-gradient(135deg, #e6f7ff, #f0f4ff);
            border-radius: 20px;
            animation: fadeIn 0.6s ease;
        }
        
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        .result h2 {
            color: #4a2bca;
            margin-bottom: 20px;
            font-size: 2rem;
        }
        
        .result-surname {
            font-size: 4.5rem;
            font-weight: bold;
            color: #ff6b6b;
            margin: 20px 0;
            animation: pulse 1.5s infinite;
        }
        
        @keyframes pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.05); }
            100% { transform: scale(1); }
        }
        
        .play-again {
            background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
            margin-top: 20px;
        }
        
        .step {
            display: none;
        }
        
        .step.active {
            display: block;
            animation: fadeIn 0.6s ease;
        }
        
        .progress-bar {
            display: flex;
            justify-content: space-between;
            max-width: 400px;
            margin: 0 auto 40px;
            position: relative;
        }
        
        .progress-step {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background: #ddd;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            color: #777;
            z-index: 2;
        }
        
        .progress-step.active {
            background: #4a2bca;
            color: white;
        }
        
        .progress-step.completed {
            background: #8a63d2;
            color: white;
        }
        
        .progress-bar::before {
            content: '';
            position: absolute;
            top: 50%;
            left: 20px;
            right: 20px;
            height: 6px;
            background: #ddd;
            transform: translateY(-50%);
            z-index: 1;
        }

        /* 调试信息样式 */
        .debug-info {
            margin-top: 20px;
            padding: 10px;
            background: #f0f0f0;
            border-radius: 8px;
            font-size: 0.8rem;
            color: #666;
            display: none;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>猜姓氏</h1>
        
        <div class="progress-bar">
            <div class="progress-step active">1</div>
            <div class="progress-step">2</div>
            <div class="progress-step">结果</div>
        </div>
        
        <div class="step active" id="step1">
            <div class="instructions">
                <p>请从下面<span class="highlight">9张卡片</span>中选择<span class="highlight">包含您真实姓氏</span>的一张卡片。</p>
            </div>
            
            <div class="card-container" id="cardContainer">
                <!-- 卡片将由JavaScript生成 -->
            </div>
            
            <button class="btn" id="nextToStep2" disabled>下一步</button>
        </div>
        
        <div class="step" id="step2">
            <div class="instructions">
                <p>请从下面<span class="highlight">16个区域</span>中选择<span class="highlight">包含您真实姓氏</span>的区域。</p>
            </div>
            
            <div class="large-grid-container">
                <div class="large-grid" id="largeGrid">
                    <!-- 大网格将由JavaScript生成 -->
                </div>
            </div>
            
            <button class="btn" id="showResult" disabled>揭晓答案</button>
        </div>
        
        <div class="step" id="step3">
            <div class="result">
                <h2><a href="https://lklog.cn" target="_blank">龙鲲博客</a>猜出您的姓氏是:</h2>
                <div class="result-surname" id="resultSurname">?</div>
                <button class="btn play-again" id="playAgain">再玩一次</button>
            </div>
        </div>

        <!-- 调试信息区域 -->
        <div class="debug-info" id="debugInfo"></div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 重新设计的姓氏数据结构 - 确保每个(card, grid)组合唯一
            const surnameData = {
                // 卡片0 (第一张卡片) 上的姓氏
                "李": {card: 0, grid: 0},
                "王": {card: 0, grid: 1},
                "张": {card: 0, grid: 2},
                "刘": {card: 0, grid: 3},
                "陈": {card: 0, grid: 4},
                "杨": {card: 0, grid: 5},
                "赵": {card: 0, grid: 6},
                "黄": {card: 0, grid: 7},
                "周": {card: 0, grid: 8},
                "吴": {card: 0, grid: 9},
                "徐": {card: 0, grid: 10},
                "孙": {card: 0, grid: 11},
                "胡": {card: 0, grid: 12},
                "朱": {card: 0, grid: 13},
                "高": {card: 0, grid: 14},
                "林": {card: 0, grid: 15},
                
                // 卡片1 (第二张卡片) 上的姓氏
                "何": {card: 1, grid: 0},
                "郭": {card: 1, grid: 1},
                "马": {card: 1, grid: 2},
                "罗": {card: 1, grid: 3},
                "梁": {card: 1, grid: 4},
                "宋": {card: 1, grid: 5},
                "郑": {card: 1, grid: 6},
                "谢": {card: 1, grid: 7},
                "韩": {card: 1, grid: 8},
                "唐": {card: 1, grid: 9},
                "冯": {card: 1, grid: 10},
                "于": {card: 1, grid: 11},
                "董": {card: 1, grid: 12},
                "萧": {card: 1, grid: 13},
                "程": {card: 1, grid: 14},
                "曹": {card: 1, grid: 15},
                
                // 卡片2 (第三张卡片) 上的姓氏
                "袁": {card: 2, grid: 0},
                "邓": {card: 2, grid: 1},
                "许": {card: 2, grid: 2},
                "傅": {card: 2, grid: 3},
                "沈": {card: 2, grid: 4},
                "曾": {card: 2, grid: 5},
                "彭": {card: 2, grid: 6},
                "吕": {card: 2, grid: 7},
                "苏": {card: 2, grid: 8},
                "卢": {card: 2, grid: 9},
                "蒋": {card: 2, grid: 10},
                "蔡": {card: 2, grid: 11},
                "贾": {card: 2, grid: 12},
                "丁": {card: 2, grid: 13},
                "魏": {card: 2, grid: 14},
                "薛": {card: 2, grid: 15},
                
                // 卡片3 (第四张卡片) 上的姓氏
                "叶": {card: 3, grid: 0},
                "阎": {card: 3, grid: 1},
                "余": {card: 3, grid: 2},
                "潘": {card: 3, grid: 3},
                "杜": {card: 3, grid: 4},
                "戴": {card: 3, grid: 5},
                "夏": {card: 3, grid: 6},
                "锺": {card: 3, grid: 7},
                "汪": {card: 3, grid: 8},
                "田": {card: 3, grid: 9},
                "任": {card: 3, grid: 10},
                "姜": {card: 3, grid: 11},
                "范": {card: 3, grid: 12},
                "方": {card: 3, grid: 13},
                "石": {card: 3, grid: 14},
                "姚": {card: 3, grid: 15},
                
                // 卡片4 (第五张卡片) 上的姓氏
                "谭": {card: 4, grid: 0},
                "廖": {card: 4, grid: 1},
                "邹": {card: 4, grid: 2},
                "熊": {card: 4, grid: 3},
                "金": {card: 4, grid: 4},
                "陆": {card: 4, grid: 5},
                "郝": {card: 4, grid: 6},
                "孔": {card: 4, grid: 7},
                "白": {card: 4, grid: 8},
                "崔": {card: 4, grid: 9},
                "康": {card: 4, grid: 10},
                "毛": {card: 4, grid: 11},
                "邱": {card: 4, grid: 12},
                "秦": {card: 4, grid: 13},
                "江": {card: 4, grid: 14},
                "史": {card: 4, grid: 15},
                
                // 卡片5 (第六张卡片) 上的姓氏
                "顾": {card: 5, grid: 0},
                "侯": {card: 5, grid: 1},
                "邵": {card: 5, grid: 2},
                "孟": {card: 5, grid: 3},
                "龙": {card: 5, grid: 4},
                "万": {card: 5, grid: 5},
                "段": {card: 5, grid: 6},
                "雷": {card: 5, grid: 7},
                "钱": {card: 5, grid: 8},
                "汤": {card: 5, grid: 9},
                "尹": {card: 5, grid: 10},
                "黎": {card: 5, grid: 11},
                "易": {card: 5, grid: 12},
                "常": {card: 5, grid: 13},
                "武": {card: 5, grid: 14},
                "乔": {card: 5, grid: 15},
                
                // 卡片6 (第七张卡片) 上的姓氏
                "贺": {card: 6, grid: 0},
                "赖": {card: 6, grid: 1},
                "龚": {card: 6, grid: 2},
                "文": {card: 6, grid: 3},
                "庞": {card: 6, grid: 4},
                "樊": {card: 6, grid: 5},
                "兰": {card: 6, grid: 6},
                "殷": {card: 6, grid: 7},
                "施": {card: 6, grid: 8},
                "陶": {card: 6, grid: 9},
                "洪": {card: 6, grid: 10},
                "翟": {card: 6, grid: 11},
                "安": {card: 6, grid: 12},
                "颜": {card: 6, grid: 13},
                "倪": {card: 6, grid: 14},
                "严": {card: 6, grid: 15},
                
                // 卡片7 (第八张卡片) 上的姓氏
                "牛": {card: 7, grid: 0},
                "温": {card: 7, grid: 1},
                "芦": {card: 7, grid: 2},
                "季": {card: 7, grid: 3},
                "俞": {card: 7, grid: 4},
                "章": {card: 7, grid: 5},
                "鲁": {card: 7, grid: 6},
                "葛": {card: 7, grid: 7},
                "伍": {card: 7, grid: 8},
                "韦": {card: 7, grid: 9},
                "申": {card: 7, grid: 10},
                "尤": {card: 7, grid: 11},
                "毕": {card: 7, grid: 12},
                "聂": {card: 7, grid: 13},
                "丛": {card: 7, grid: 14},
                "焦": {card: 7, grid: 15},
                
                // 卡片8 (第九张卡片) 上的姓氏
                "向": {card: 8, grid: 0},
                "柳": {card: 8, grid: 1},
                "邢": {card: 8, grid: 2},
                "路": {card: 8, grid: 3},
                "岳": {card: 8, grid: 4},
                "齐": {card: 8, grid: 5},
                "梅": {card: 8, grid: 6},
                "莫": {card: 8, grid: 7},
                "庄": {card: 8, grid: 8},
                "辛": {card: 8, grid: 9},
                "管": {card: 8, grid: 10},
                "祝": {card: 8, grid: 11},
                "左": {card: 8, grid: 12},
                "涂": {card: 8, grid: 13},
                "谷": {card: 8, grid: 14},
                "时": {card: 8, grid: 15}
            };
            
            // 游戏状态
            const gameState = {
                selectedCard: null,    // 选中的卡片索引
                selectedGrid: null,    // 选中的区域索引
                allSurnames: Object.keys(surnameData)  // 所有姓氏列表
            };
            
            // 验证数据完整性和唯一性
            function validateSurnameData() {
                const cardCounts = Array(9).fill(0);
                const gridCounts = Array(16).fill(0);
                const combinations = new Set();
                const errors = [];
                
                // 检查每个卡片和区域是否有正确数量的姓氏
                for (const surname in surnameData) {
                    const {card, grid} = surnameData[surname];
                    cardCounts[card]++;
                    gridCounts[grid]++;
                    
                    // 检查组合唯一性
                    const key = `${card},${grid}`;
                    if (combinations.has(key)) {
                        errors.push(`组合 (卡片${card + 1}, 区域${grid + 1}) 不唯一,被多个姓氏使用`);
                    }
                    combinations.add(key);
                }
                
                // 检查卡片姓氏数量 (应为16个)
                cardCounts.forEach((count, index) => {
                    if (count !== 16) {
                        errors.push(`卡片 ${index + 1} 姓氏数量错误: 应有16个,实际有${count}个`);
                    }
                });
                
                // 检查区域姓氏数量 (应为9个)
                gridCounts.forEach((count, index) => {
                    if (count !== 9) {
                        errors.push(`区域 ${index + 1} 姓氏数量错误: 应有9个,实际有${count}个`);
                    }
                });
                
                // 显示调试信息
                const debugInfo = document.getElementById('debugInfo');
                if (errors.length === 0) {
                    debugInfo.textContent = "数据验证通过: 所有卡片各有16个姓氏,所有区域各有9个姓氏,所有组合唯一";
                } else {
                    debugInfo.textContent = "数据验证错误:\n" + errors.join("\n");
                }
                
                return errors.length === 0;
            }
            
            // 生成卡片(第一步)
            function generateCards() {
                const cardContainer = document.getElementById('cardContainer');
                cardContainer.innerHTML = '';
                
                // 为每张卡片(0-8)生成内容
                for (let cardIndex = 0; cardIndex < 9; cardIndex++) {
                    const card = document.createElement('div');
                    card.className = 'card';
                    card.dataset.index = cardIndex;
                    
                    // 卡片标题
                    const title = document.createElement('div');
                    title.className = 'card-title';
                    title.textContent = `卡片 ${cardIndex + 1}`;
                    card.appendChild(title);
                    
                    // 姓氏网格
                    const grid = document.createElement('div');
                    grid.className = 'card-grid';
                    
                    // 收集该卡片上的所有姓氏
                    const cardSurnames = [];
                    for (const surname in surnameData) {
                        if (surnameData[surname].card === cardIndex) {
                            cardSurnames.push(surname);
                        }
                    }
                    
                    // 填充4x4网格 (确保16个)
                    for (let i = 0; i < 16; i++) {
                        const cell = document.createElement('div');
                        cell.className = 'surname-cell';
                        cell.textContent = cardSurnames[i];
                        grid.appendChild(cell);
                    }
                    
                    card.appendChild(grid);
                    
                    // 添加点击事件
                    card.addEventListener('click', () => {
                        toggleCardSelection(cardIndex);
                    });
                    
                    cardContainer.appendChild(card);
                }
            }
            
            // 切换卡片选择状态
            function toggleCardSelection(cardIndex) {
                if (gameState.selectedCard === cardIndex) {
                    gameState.selectedCard = null;
                    document.querySelectorAll('.card').forEach(card => {
                        card.classList.remove('selected');
                    });
                } else {
                    gameState.selectedCard = cardIndex;
                    document.querySelectorAll('.card').forEach((card, idx) => {
                        card.classList.toggle('selected', idx === cardIndex);
                    });
                }
                
                document.getElementById('nextToStep2').disabled = gameState.selectedCard === null;
            }
            
            // 生成区域(第二步)
            function generateLargeGrid() {
                const largeGrid = document.getElementById('largeGrid');
                largeGrid.innerHTML = '';
                
                // 修改为4x4网格布局
                largeGrid.style.gridTemplateColumns = 'repeat(4, 1fr)';
                
                // 为每个区域(0-15)生成内容
                for (let gridIndex = 0; gridIndex < 16; gridIndex++) {
                    const cell = document.createElement('div');
                    cell.className = 'grid-cell';
                    cell.dataset.index = gridIndex;
                    
                    // 区域标题
                    const title = document.createElement('div');
                    title.className = 'grid-title';
                    title.textContent = `区域 ${gridIndex + 1}`;
                    cell.appendChild(title);
                    
                    // 姓氏网格 - 使用3x3网格,因为每个区域有9个姓氏
                    const innerGrid = document.createElement('div');
                    innerGrid.className = 'card-grid';
                    innerGrid.style.gridTemplateColumns = 'repeat(3, 1fr)';
                    innerGrid.style.gridTemplateRows = 'repeat(3, 1fr)';
                    
                    // 收集该区域上的所有姓氏
                    const gridSurnames = [];
                    for (const surname in surnameData) {
                        if (surnameData[surname].grid === gridIndex) {
                            gridSurnames.push(surname);
                        }
                    }
                    
                    // 填充3x3网格 (确保9个)
                    for (let i = 0; i < 9; i++) {
                        const surnameCell = document.createElement('div');
                        surnameCell.className = 'surname-cell';
                        surnameCell.textContent = gridSurnames[i];
                        innerGrid.appendChild(surnameCell);
                    }
                    
                    cell.appendChild(innerGrid);
                    
                    // 添加点击事件
                    cell.addEventListener('click', () => {
                        toggleGridSelection(gridIndex);
                    });
                    
                    largeGrid.appendChild(cell);
                }
            }
            
            // 切换区域选择状态
            function toggleGridSelection(gridIndex) {
                if (gameState.selectedGrid === gridIndex) {
                    gameState.selectedGrid = null;
                    document.querySelectorAll('.grid-cell').forEach(cell => {
                        cell.classList.remove('selected');
                    });
                } else {
                    gameState.selectedGrid = gridIndex;
                    document.querySelectorAll('.grid-cell').forEach((cell, idx) => {
                        cell.classList.toggle('selected', idx === gridIndex);
                    });
                }
                
                document.getElementById('showResult').disabled = gameState.selectedGrid === null;
            }
            
            // 根据选择的卡片和区域猜测姓氏
            function guessSurname() {
                for (const surname in surnameData) {
                    const data = surnameData[surname];
                    if (data.card === gameState.selectedCard && data.grid === gameState.selectedGrid) {
                        return surname;
                    }
                }
                return "未知";
            }
            
            // 初始化游戏事件监听
            function initEventListeners() {
                // 下一步按钮
                document.getElementById('nextToStep2').addEventListener('click', () => {
                    document.getElementById('step1').classList.remove('active');
                    document.getElementById('step2').classList.add('active');
                    
                    // 更新进度条
                    document.querySelectorAll('.progress-step')[0].classList.add('completed');
                    document.querySelectorAll('.progress-step')[1].classList.add('active');
                    
                    // 生成区域网格
                    generateLargeGrid();
                });
                
                // 揭晓答案按钮
                document.getElementById('showResult').addEventListener('click', () => {
                    document.getElementById('step2').classList.remove('active');
                    document.getElementById('step3').classList.add('active');
                    
                    // 更新进度条
                    document.querySelectorAll('.progress-step')[1].classList.add('completed');
                    document.querySelectorAll('.progress-step')[2].classList.add('active');
                    
                    // 显示结果
                    const resultElement = document.getElementById('resultSurname');
                    resultElement.textContent = '思考中...';
                    setTimeout(() => {
                        resultElement.textContent = guessSurname();
                    }, 1000);
                });
                
                // 再玩一次按钮
                document.getElementById('playAgain').addEventListener('click', () => {
                    // 重置游戏状态
                    gameState.selectedCard = null;
                    gameState.selectedGrid = null;
                    
                    // 重置界面
                    document.getElementById('step3').classList.remove('active');
                    document.getElementById('step1').classList.add('active');
                    
                    // 重置进度条
                    document.querySelectorAll('.progress-step').forEach(step => {
                        step.classList.remove('active', 'completed');
                    });
                    document.querySelectorAll('.progress-step')[0].classList.add('active');
                    
                    // 重新生成卡片
                    generateCards();
                    
                    // 禁用按钮
                    document.getElementById('nextToStep2').disabled = true;
                    document.getElementById('showResult').disabled = true;
                });
            }
            
            // 初始化游戏
            function initGame() {
                // 验证数据
                validateSurnameData();
                
                // 生成卡片
                generateCards();
                
                // 初始化事件监听
                initEventListeners();
            }
            
            // 启动游戏
            initGame();
        });
    </script>
</body>
</html>

 

不说话知道你姓啥在线版(附源码)最先出现在龙鲲博客

阿里小号一键导出短信并统计绑定的平台

2025-08-17 09:12:00

本文于 2025年8月17日 9:17 更新,注意查看最新内容

阿里小号前段时间发布公告,说要全面下线阿里小号业务(目前暂停),于是着手准备换绑事宜,由于使用快接近十年,注册的平台数不胜数,手动统计起来极其麻烦,于是查找了相关方案,这里记录相关核心信息,以备日后查阅。

阿里小号的短信主要有两种方式接收,一种是直接转发至本机,另一种是在阿里小号APP内查看,本文两种接收方式的统计均会有所介绍,请放心浏览。

阿里小号APP内短信的导出:

原理:利用 adb shell uiautomator dump 循环滚动读取短信界面数据,模拟人工导出短信内容。

1、导出

# adb连接手机
adb devices
# 打开阿里小号 App 的短信界面,并保持在首屏
python export_sms.py
# 脚本会自动循环滚动并抓取短信内容,导出到 conversation_list.csv
import os
import re
import subprocess
import time
import xml.etree.ElementTree as ET

def run_adb_command(command):
    """执行ADB命令并返回输出"""
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    if result.returncode != 0:
        return None
    return result.stdout.strip()

def pull_ui_dump(file_path):
    """拉取UI Dump文件,确保文件生成成功"""
    device_path = f"/sdcard/{os.path.basename(file_path)}"
    
    # 生成UI转储并等待文件创建
    run_adb_command(f"adb shell uiautomator dump {device_path}")
    time.sleep(1.2)  # 延长等待时间,确保文件生成
    run_adb_command(f"adb pull {device_path} {file_path}")
    run_adb_command(f"adb shell rm {device_path}")
    
    return os.path.exists(file_path)

def extract_list_items(xml_file):
    """提取列表项,确保元素识别稳定"""
    items = []
    try:
        tree = ET.parse(xml_file)
        root = tree.getroot()
        
        # 元素标识常量(与UI Dump匹配)
        LIST_CONTAINER_ID = "com.alicom.smartdail:id/xiaohao_conversation_list"
        NAME_ID = "com.alicom.smartdail:id/xiaohao_item_conversation_name"
        TIME_ID = "com.alicom.smartdail:id/xiaohao_item_conversation_time"
        CONTENT_ID = "com.alicom.smartdail:id/xiaohao_item_conversation_body"
        
        # 查找列表容器
        list_container = root.find(f".//node[@resource-id='{LIST_CONTAINER_ID}']")
        if list_container is None:
            return items
            
        # 遍历所有可点击的列表项
        for node in list_container.iter('node'):
            if node.get('class') == 'android.widget.LinearLayout' and node.get('clickable') == 'true':
                # 查找关键子元素
                name_node = node.find(f".//node[@resource-id='{NAME_ID}']")
                time_node = node.find(f".//node[@resource-id='{TIME_ID}']")
                content_node = node.find(f".//node[@resource-id='{CONTENT_ID}']")
                
                # 确保元素存在且有内容
                if (name_node is not None and time_node is not None and content_node is not None and
                    name_node.get('text') and time_node.get('text') and content_node.get('text')):
                    items.append({
                        'name': name_node.get('text'),
                        'time': time_node.get('text'),
                        'content': content_node.get('text')
                    })
        
    except Exception as e:
        print(f"解析错误: {e}")
    return items

def get_screen_size():
    """获取屏幕尺寸(缓存结果)"""
    if not hasattr(get_screen_size, 'cached_size'):
        output = run_adb_command("adb shell wm size")
        if output:
            size_match = re.search(r"(\d+)x(\d+)", output)
            if size_match:
                get_screen_size.cached_size = (int(size_match.group(1)), int(size_match.group(2)))
                return get_screen_size.cached_size
        get_screen_size.cached_size = (1080, 2210)  # 默认值
    return get_screen_size.cached_size

def scroll_down():
    """优化滚动逻辑,确保滚动距离足够"""
    width, height = get_screen_size()
    # 增加滚动距离(从屏幕2/3处滑到1/4处)
    start_x, start_y = width//2, height*2//3
    end_x, end_y = width//2, height//4
    run_adb_command(f"adb shell input swipe {start_x} {start_y} {end_x} {end_y} 250")  # 延长滑动时间

def main():
    os.makedirs("temp_dumps", exist_ok=True)
    all_items = []
    prev_items = []  # 存储上一页完整内容,用于更准确的底部判断
    page = 1
    consecutive_same = 0  # 连续相同页数计数(避免误判)
    max_consecutive = 2   # 连续2页相同才判断为底部
    max_pages = 1000       # 增加最大页数限制
    
    print("开始收集列表数据(完整模式)...")
    
    while consecutive_same < max_consecutive and page <= max_pages:
        dump_file = f"temp_dumps/page_{page}.xml"
        
        if not pull_ui_dump(dump_file):
            print(f"页面 {page} 获取失败,重试...")
            page += 1
            continue
        
        current_items = extract_list_items(dump_file)
        if not current_items:
            print(f"页面 {page} 未找到数据,继续...")
            page += 1
            continue
        
        # 计算新增数据量
        seen_items = set((i['name'], i['time'], i['content']) for i in all_items)
        new_count = 0
        for item in current_items:
            item_key = (item['name'], item['time'], item['content'])
            if item_key not in seen_items:
                seen_items.add(item_key)
                all_items.append(item)
                new_count += 1
        
        # 判断是否与上一页内容相同(优化底部判断)
        if prev_items and len(current_items) == len(prev_items):
            # 比较所有项而非仅最后一项,避免误判
            if all(current_items[i] == prev_items[i] for i in range(len(current_items))):
                consecutive_same += 1
            else:
                consecutive_same = 0
        else:
            consecutive_same = 0
        
        prev_items = current_items.copy()
        print(f"页面 {page} 新增 {new_count} 项,总计 {len(all_items)} 项")
        
        # 继续滚动
        if consecutive_same < max_consecutive:
            scroll_down()
            page += 1
            time.sleep(1.5)  # 延长滚动后等待时间,确保内容加载
        else:
            print(f"连续 {max_consecutive} 页内容相同,判断为已到达底部")
    
    # 导出结果
    if all_items:
        csv_file = "conversation_list.csv"
        with open(csv_file, "w", encoding="utf-8") as f:
            f.write("号码,时间,内容\n")
            for item in all_items:
                name = item['name'].replace('"', '""')
                time_val = item['time'].replace('"', '""')
                content = item['content'].replace('"', '""')
                f.write(f'"{name}","{time_val}","{content}"\n')
        
        print(f"\n完成! 共收集 {len(all_items)} 条记录")
        print(f"结果已导出到: {csv_file}")
    else:
        print("未找到任何列表项")
    
    # 清理临时文件
    try:
        for file in os.listdir("temp_dumps"):
            os.remove(os.path.join("temp_dumps", file))
        os.rmdir("temp_dumps")
    except:
        pass

if __name__ == "__main__":
    main()

执行完上述代码之后,会在执行目录生成一个conversation_list.csv。

2、统计

# 修改脚本中的 input_file 指向你的短信导出文件
python stats_platforms.py
def process_file(input_path, output_path):
    tag_counts = {}          # 存储【xxx】标记及其出现次数
    no_tag_lines = []        # 存储不包含标记的行内容
    required_string = ""     # 其他地方导出时可以填写小号号码以区分

    with open(input_path, 'r', encoding='utf-8', errors='ignore') as file:
        for line in file:
            # 检查是否包含
            if required_string and required_string not in line:
                continue
            
            # 查找第一个【xxx】标记
            start_idx = line.find("【")
            end_idx = line.find("】", start_idx + 1) if start_idx != -1 else -1
            
            if start_idx != -1 and end_idx != -1:
                # 提取第一个完整标记
                tag = line[start_idx:end_idx + 1]
                tag_counts[tag] = tag_counts.get(tag, 0) + 1
            else:
                # 记录不包含标记的行
                no_tag_lines.append(line.strip())
    
    # 按出现次数降序排序
    sorted_tags = sorted(tag_counts.items(), key=lambda x: x[1], reverse=True)
    
    # 将结果写入输出文件
    with open(output_path, 'w', encoding='utf-8') as out_file:
        # 写入标记统计结果
        out_file.write("标记统计结果(按出现次数降序):\n")
        for tag, count in sorted_tags:
            out_file.write(f"{tag}: {count}次\n")
        
        # 写入无标记行
        out_file.write("\n不包含标记的行:\n")
        for i, line in enumerate(no_tag_lines, 1):
            out_file.write(f"{i}. {line}\n")
    
    return sorted_tags, no_tag_lines

# 使用示例
if __name__ == "__main__":
    input_file = "conversation_list.csv"     # 输入文件路径
    output_file = "result.txt"    # 输出文件路径
    
    tags, lines_without_tags = process_file(input_file, output_file)
    
    print(f"处理完成!结果已保存到 {output_file}")
    print(f"共找到 {len(tags)} 种标记,{len(lines_without_tags)} 行无标记内容")

最终会在执行目录生成一个result.txt文件。

以上脚本均出自:https://github.com/AyFun/alixiaohao-tool,在此鸣谢。
其中stats_platforms.py文件原封不动,而export_sms.py进行了特殊处理,不一定适配所有手机型号。
如果处理后的脚本和原脚本均无法运行,建议通过pull_ui_dump函数调用 ADB 命令,生成手机当前界面的 XML 结构文件(uiautomator dump),再将 XML 文件从手机拉取到电脑本地。

# 生成当前界面的XML
adb shell uiautomator dump /sdcard/current_ui.xml
# 拉取到电脑
adb pull /sdcard/current_ui.xml ./

直接转发至本机的短信:

原理:使用手机自带的云同步功能,然后一次性加载完所有短信内容,然后复制粘贴至本地。

数据格式大概如下:

收藏
2019年1月26日 16:38
【电单车】365天骑行卡限时免费送!(来自1085505913543451952679)

收藏
2019年10月29日 03:96
【科技】您的验证码是。(来自1065502406802378354349579)

收藏
2017年6月87日 28:55
【出行】尊敬的乘客(来自10655919825033543456329391)

此时我们仿照原有逻辑重写相应的代码:

最后我们手动将本地和云端的统计数据进行合并,执行下列代码进行去重:

最后进行相应申明,本文的编写主要是为了阿里小号平台下线服务,但不给用户换绑提供任何帮助,而造成个人虚拟财产损失的自救行为,所有统计处理均在本地进行,不会影响任何的正常运行。

阿里小号一键导出短信并统计绑定的平台最先出现在龙鲲博客

一键生成网页二维码脚本 V1.5(2025年8月21日更新)

2025-08-16 22:00:21

本文于 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/[email protected]/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/[email protected]/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();
    }
})();

 

一键生成网页二维码脚本 V1.5(2025年8月21日更新)最先出现在龙鲲博客

如何邮寄一封挂号信

2025-08-09 18:44:44

本文于 2025年8月9日 8:48 更新,注意查看最新内容

前言

前段时间去邮局寄东西的时候,因为一些原因让挂号信再次走入我的视线,于是便详细了解了一下,也寄出了我真正意义上的第一封挂号信,本文记录此间经历,以备日后需要再行查阅。

什么是挂号信

在寄信之前,我们先了解一下挂号信的基本定义。

挂号邮寄是邮政服务的一种形式,指需到邮局窗口办理登记手续、无法直接投递至邮筒的邮件类型,具有安全性和可追责性等特点。其特点在于邮局提供收据凭证,并承担丢失赔偿责任,适用于重要文件、证券等物品的寄送。

国内挂号邮件需支付基础邮费及挂号费,按重量和目的地计费(如跨省21克收费5.4元)。投递时要求收件人凭身份证签收,寄件人可凭收据申请查询,邮局承诺一个月内反馈结果。相较于平信,挂号件在邮局内部处理中需逐件登记,且全程可追溯。

该服务起源于传统邮政体系对重要信函的保障需求,后因普通快件和特快专递的出现产生演变。早期挂号信作为唯一可追责信件类型,后逐渐被普通快件替代;随着普通快件服务取消,目前同类需求主要通过特快专递实现,但邮费显著提高。邮政法明确规定了挂号邮件的责任划分与赔偿标准,现行服务仍保留窗口交寄、凭证查询等核心流程。

——《百度百科 - 挂号邮寄》

简单来说,挂号信就是邮寄服务的一种形式,但是性质区别于普通快递。

挂号信的优劣

当下时代,挂号信已逐步退出主流邮寄舞台,现在选择无非是图挂号信性质上与EMS特快几乎一样,但是在寄一些小件文件时,价格比EMS特快便宜,以及在个人使用时有一定仪式感。劣势就是速度比EMS特快要慢上许多,以及若文件较重,价格比EMS特快贵。

寄信流程

1、先准备一个信封。

据说早期邮寄挂号信的信封是有要求的,背面需要印有某某监制,但该规定现在已经被修改(《国家邮政局关于优化邮政业用品用具生产监管方式的公告》)。

我查看了几年前买的信封,后面确有相应的信息。

2、准备相应的邮票。

邮票官方的购买渠道为中国邮政官网、公众号等,票面价额对应相应现金。

第三方的购买渠道为电商平台,价格比票面价格略低,也就是圈内俗称的“打折票”。若选择第三方渠道购买,请一定擦亮眼睛,有可能买到假票。

2.1、邮票真伪的辨别

比较简单的一种方式,就是在一定年份之后生产的邮票,在紫光灯的照射下会有防伪编码显现。

3、张贴邮票。

20克以内,本市信件0.8元,跨市信件1.2元,挂号信件在其基础上加3元,价格全国通用。

我们要寄的是挂号信,张贴4.2元邮票即可(可不张贴,由邮局工作人员张贴,实测张贴位置似乎有要求,但是没太弄明白所谓要求的具体标准)。

4、前往邮局柜台邮寄即可。

寄信格式

图片来源:知乎@大健

在信封上精准打印对应内容

大概原理就是在电脑上设置对应的尺寸,然后利用OCR识别信封,将信封填充至背景,再到对应位置编写对应内容打印即可。

若打印电脑和编辑电脑非同一台,注意使用的软件是否同一,若不同一,可导出PDF打印。

常用工具

邮政编码查询:https://dey.11185.cn/web/#/idtoolkitaddress

邮件资费查询:https://dey.11185.cn/web/#/jdpostage

邮件查询:https://dey.11185.cn/web/#/waybillno

参考

《小科普:在国内,正确的写信、寄信格式》

《寄递公文莫踩“红线”!》

如何邮寄一封挂号信最先出现在龙鲲博客