女朋友被链接折磨疯了,我写了个工具一键解救

有一天女朋友跟我抱怨:工作里被各种链接折腾得头大,飞书和浏览器之间来回切窗口,一会忘了看哪个,心情都被搅乱了。我回头一想------我也一样,办公室每个人都被链接淹没。

同事丢来的需求文档、群里转的会议记录、GitLab 的 MR 链接、还有那些永远刷不完的通知------每点一个链接就得在聊天工具和浏览器之间跳转,回来后一秒钟就忘了"本来要点哪个、看哪个"。更别提那些收集了一堆好文章想集中看,或者别人发来一串链接让你"挑哪个好"的时候,光是打开就要折腾半天。

这不是注意力不集中,是工具没有帮你省掉这些无意义的切换。

于是我做了一个极简 Chrome 插件: Open‑All 。它只做一件事------把你所有网址一次性在新窗口打开。你复制粘贴一次,它把链接都整齐地摆在新标签页里,你只要从左到右按顺序看就行。简单、直接,让你把注意力放在真正重要的事情上

先看效果:一键打开多个链接

这些痛点你肯定也遇到过

每天都在经历的折磨

  • 来回切应用 :复制、粘贴、点开、切回来,这套动作做一遍就够烦的了
  • 容易忘事 :打开到第几个了?这个看过没?脑子根本记不住
  • 启动成本高 :一想到要一个个点开,就懒得开始了
  • 没法对比 :想要横向比较几个方案,但打开都费劲

具体什么时候最痛苦

  1. 收集的文章想一口气看完 :平时存了一堆好文章,周末想集中看,结果光打开就累了
  2. 别人让你帮忙选 :同事发来几个方案链接问你觉得哪个好,你得全部打开才能比较
  3. 代码 Review :GitLab 上好几个 MR 要看,还有相关的 Issue 和 CI 结果
  4. 开会前准备 :会议文档、背景资料、相关链接,都得提前打开看看

我的解决方案

设计思路很简单

  • 就解决一个问题 :批量打开链接,不搞那些花里胡哨的功能
  • 零学习成本 :会复制粘贴就会用
  • 让你专注 :少折腾,多干活

能干什么

  • 把一堆链接一次性在新窗口打开
  • 自动保存你输入的内容,不怕误关
  • 界面超简单,点两下就搞定

技术实现

项目结构

bash 复制代码
shiba-cursor
├── manifest.json      # 扩展的"身份证"
├── popup.html         # 弹窗样式
└── popup.js          # 弹窗交互

文件说明:

  • manifest.json:扩展身份信息
  • popup.html:弹窗样式
  • popup.js:弹窗交互

🚀 浏览项目的完整代码可以点击这里 github.com/Teernage/op...,如果对你有帮助欢迎Star。

动手实现

第一步:创建项目文件

创建文件夹 open-all

创建manifest.json文件

json 复制代码
{
  "manifest_version": 3,
  "name": "批量打开URL",
  "version": "1.0",
  "description": "输入多个URL,一键在新窗口中打开",
  "permissions": [
    "tabs",
    "storage"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_title": "批量打开URL"
  }
}

创建popup.html文件

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        width: 320px;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          sans-serif;
        color: #333;
      }

      .container {
        background: rgba(255, 255, 255, 0.95);
        padding: 20px;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        backdrop-filter: blur(10px);
        border: 1px solid rgba(255, 255, 255, 0.2);
      }

      .title {
        font-size: 18px;
        font-weight: 600;
        text-align: center;
        margin-bottom: 16px;
        color: #1d1d1f;
        letter-spacing: -0.5px;
      }

      #urlInput {
        width: 100%;
        height: 140px;
        padding: 12px;
        border: 2px solid #e5e5e7;
        border-radius: 12px;
        font-size: 14px;
        font-family: 'SF Mono', Monaco, monospace;
        resize: none;
        background: #fafafa;
        transition: all 0.2s ease;
        line-height: 1.4;
      }

      #urlInput:focus {
        outline: none;
        border-color: #007aff;
        background: #fff;
        box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.1);
      }

      #urlInput::placeholder {
        color: #8e8e93;
        font-size: 13px;
      }

      .button-group {
        display: flex;
        gap: 8px;
        margin-top: 16px;
      }

      button {
        flex: 1;
        padding: 12px 16px;
        border: none;
        border-radius: 10px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.2s ease;
        font-family: inherit;
      }

      #openBtn {
        background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
        color: white;
        box-shadow: 0 2px 8px rgba(0, 122, 255, 0.3);
      }

      #openBtn:hover {
        transform: translateY(-1px);
        box-shadow: 0 4px 12px rgba(0, 122, 255, 0.4);
      }

      #openBtn:active {
        transform: translateY(0);
      }

      #clearBtn {
        background: #f2f2f7;
        color: #8e8e93;
        border: 1px solid #e5e5e7;
      }

      #clearBtn:hover {
        background: #e5e5ea;
        color: #636366;
      }

      #status {
        margin-top: 12px;
        padding: 8px 12px;
        border-radius: 8px;
        font-size: 12px;
        text-align: center;
        display: none;
        background: rgba(52, 199, 89, 0.1);
        color: #30d158;
        border: 1px solid rgba(52, 199, 89, 0.2);
      }

      .tip {
        font-size: 11px;
        color: #8e8e93;
        text-align: center;
        margin-top: 8px;
        line-height: 1.3;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="title">批量打开 URL</div>

      <textarea
        id="urlInput"
        placeholder="输入 URL,每行一个:

https://www.apple.com
https://www.github.com
https://www.google.com"
      ></textarea>

      <div class="button-group">
        <button id="clearBtn">清空</button>
        <button id="openBtn">打开</button>
      </div>

      <div class="tip">输入会自动保存,打开后自动清空</div>

      <div id="status"></div>
    </div>

    <script src="popup.js"></script>
  </body>
</html>

创建popup.js文件

js 复制代码
document.addEventListener('DOMContentLoaded', function() {
  const urlInput = document.getElementById('urlInput');
  const openBtn = document.getElementById('openBtn');
  const clearBtn = document.getElementById('clearBtn');
  const status = document.getElementById('status');

  // 恢复上次保存的输入
  chrome.storage.local.get(['savedUrls'], function(result) {
    if (result.savedUrls) {
      urlInput.value = result.savedUrls;
    }
  });

  // 自动保存输入内容
  urlInput.addEventListener('input', function() {
    chrome.storage.local.set({savedUrls: urlInput.value});
  });

  // 清空按钮
  clearBtn.addEventListener('click', function() {
    urlInput.value = '';
    chrome.storage.local.remove(['savedUrls']);
    showStatus('已清空');
  });

  // 打开URL按钮
  openBtn.addEventListener('click', function() {
    const urls = getUrls(urlInput.value);
    
    if (urls.length === 0) {
      showStatus('请输入有效的URL');
      return;
    }

    // 创建新窗口并打开所有URL
    chrome.windows.create({url: urls[0]}, function(window) {
      for (let i = 1; i < urls.length; i++) {
        chrome.tabs.create({
          windowId: window.id,
          url: urls[i],
          active: false
        });
      }
      
      // 成功打开后清空输入并移除存储
      urlInput.value = '';
      chrome.storage.local.remove(['savedUrls']);
      showStatus(`已打开 ${urls.length} 个URL`);
    });
  });

  // 解析URL
  function getUrls(input) {
    return input.split('\n')
      .map(line => line.trim())
      .filter(line => line && (line.startsWith('http://') || line.startsWith('https://')));
  }

  // 显示状态
  function showStatus(message) {
    status.textContent = message;
    status.style.display = 'block';
    setTimeout(() => {
      status.style.display = 'none';
    }, 2000);
  }
});

第二步:安装扩展

  1. 打开Chrome浏览器
  2. 地址栏输入:chrome://extensions/
  3. 打开右上角"开发者模式"
  4. 点击"加载已解压的扩展程序"
  5. 选择刚才的文件夹,然后确定
  6. 固定扩展
  7. 点击扩展图标即可使用

最后想说的

这个插件功能很简单,但解决的是我们每天都会遇到的真实问题。它不会让你的工作效率翻倍,但能让你少一些无聊的重复操作,多一些专注的时间。

我和女朋友现在用着都挺爽的,希望也能帮到你。如果你也有类似的困扰,试试看吧,有什么想法也欢迎在评论区聊聊。

你最希望下个版本加什么功能?评论区告诉我!

相关推荐
计算机魔术师2 分钟前
Dario Amodei 发文呼吁 AI 行业放慢前沿速度并公布三步计划
前端
EatFan6 分钟前
前后端分离项目中 Token 到底应该怎么设计?Access Token + Refresh Token 实战
java·前端·后端
dsyyyyy110113 分钟前
Typescript装饰器
前端·javascript·typescript
一拳不是超人1 小时前
一个没测暗色模式的 Bug,吃掉了我一半 谷歌扩展用户
前端·javascript·程序员
liangshanbo12152 小时前
React useState 函数式更新面试题整理
前端·react.js·前端框架
涛涛ing2 小时前
为什么你的页面在 Safari 上总出问题?Interop 2027 正在解决这个 20 年老毛病
前端
不一样的少年_2 小时前
WebP 压缩到底在干嘛?小白也能看懂的原理拆解
前端·后端·图片资源
问心无愧05132 小时前
ctf show web 177
前端·笔记
不一样的少年_2 小时前
PNG/JPG 如何变成 WebP?真相不是改后缀!
前端·后端·图片资源
杉氧2 小时前
RN 性能调优指南:重渲染(Re-renders)控制与长列表(FlatList)优化
android·前端·react native