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

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

同事丢来的需求文档、群里转的会议记录、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. 点击扩展图标即可使用

最后想说的

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

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

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

相关推荐
Zyx20075 小时前
CSS 超级武器:Stylus 与 Flexbox 强强联手,打造极致响应式动画界面(上篇)
前端·css
烛阴6 小时前
超越面向对象:用函数式思维重塑你的Lua代码
前端·lua
微知语6 小时前
Cell 与 RefCell:Rust 内部可变性的双生子解析
java·前端·rust
雨过天晴而后无语6 小时前
Windchill10+html使用Lightbox轻量化wizard的配置
java·前端·html
Yeats_Liao6 小时前
Go Web 编程快速入门 12 - 微服务架构:服务发现、负载均衡与分布式系统
前端·后端·架构·golang
旺仔小拳头..6 小时前
HTML——表单与表格
前端·html
xu_duo_i6 小时前
vue2+elementUI后端返回二进制流,前端下载实现
前端·javascript·elementui
慧一居士6 小时前
在Vue项目中平滑地引入HTML文件
前端·vue.js
我的20096 小时前
HTML常用特殊字符
前端·html