首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Electron 与 OpenHarmony 的实战入门:GitCode 口袋工具V1.0.0

Electron 与 OpenHarmony 的实战入门:GitCode 口袋工具V1.0.0

作者头像
@VON
发布2025-12-21 13:22:54
发布2025-12-21 13:22:54
1400
举报
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Electron 与 OpenHarmony 的实战入门:GitCode 口袋工具V1.0.0

作者VON HarmonyOS 初级开发者|CSDN 新星创作者(Java/Web 领域) 专栏链接:Electron for OpenHarmony 仓库地址:Electron口袋工具


在日常开发中,你是否经常需要快速访问 GitCode(中国版 GitHub)上的项目、仓库或个人主页?是否希望有一个轻量、置顶、一键直达的桌面小工具,无需打开浏览器即可跳转常用页面?

本文将带你用 Electron + 安全架构 打造一个 GitCode 口袋工具。它具备:

  • 快速跳转 GitCode 主页、仓库、个人空间;
  • 自定义常用仓库快捷入口;
  • 系统托盘常驻 + 全局快捷键唤出;
  • 符合现代安全规范(contextIsolation: true);
  • 并为未来迁移到 OpenHarmony 预留扩展接口。

这个工具虽小,却融合了 系统集成、用户配置、安全 IPC 与 Web 跳转 四大能力,是实用型 Electron 应用的典范。


一、功能设计

功能

说明

✅ 主界面

展示常用链接卡片(主页、仓库、个人)

✅ 自定义仓库

用户可添加/删除自己的常用仓库地址

✅ 系统托盘

应用最小化到托盘,右键菜单操作

✅ 全局快捷键

Ctrl+Alt+G 唤出/隐藏窗口

✅ 安全跳转

所有链接通过主进程校验后打开(防 XSS)

💡 本工具不收集任何用户数据,所有配置仅保存在本地。


二、项目结构

代码语言:javascript
复制
gitcode-pocket/
├── main.js               # 主进程:窗口 + 托盘 + 快捷键 + 跳转逻辑
├── preload.js            # 安全桥接:暴露配置与跳转 API
├── index.html            # 渲染界面:链接卡片 + 表单
├── config.json           # 本地存储的自定义仓库列表(可选)
└── package.json

三、完整代码实现

1. 初始化项目
代码语言:javascript
复制
mkdir gitcode-pocket && cd gitcode-pocket
npm init -y
npm install electron --save-dev
2. package.json
代码语言:javascript
复制
{
  "name": "gitcode-pocket",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^33.0.0"
  }
}
3. config.json(初始空配置)
代码语言:javascript
复制
{
  "customRepos": []
}

⚠️ 首次运行时若不存在,程序会自动创建。


4. main.js —— 主进程核心
代码语言:javascript
复制
const { app, BrowserWindow, Tray, Menu, globalShortcut, shell, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs').promises;

let mainWindow = null;
let tray = null;
const CONFIG_PATH = path.join(app.getPath('userData'), 'config.json');

// 读取配置
async function loadConfig() {
  try {
    const data = await fs.readFile(CONFIG_PATH, 'utf8');
    return JSON.parse(data);
  } catch (err) {
    // 首次运行,创建默认配置
    const defaultConfig = { customRepos: [] };
    await saveConfig(defaultConfig);
    return defaultConfig;
  }
}

// 保存配置
async function saveConfig(config) {
  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
}

// 创建主窗口
function createWindow() {
  mainWindow = new BrowserWindow({
    width: 400,
    height: 500,
    resizable: false,
    alwaysOnTop: true,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });
  mainWindow.loadFile('index.html');

  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  // 隐藏关闭按钮,改为最小化到托盘
  mainWindow.on('close', (e) => {
    if (mainWindow.isVisible()) {
      e.preventDefault();
      mainWindow.hide();
    }
  });
}

// 创建系统托盘
function createTray() {
  tray = new Tray(path.join(__dirname, 'icon.png')); // 可替换为实际图标
  const contextMenu = Menu.buildFromTemplate([
    {
      label: '显示',
      click: () => mainWindow?.show()
    },
    {
      label: '退出',
      click: () => app.quit()
    }
  ]);
  tray.setToolTip('GitCode 口袋工具');
  tray.setContextMenu(contextMenu);
  tray.on('click', () => mainWindow?.show());
}

// 安全打开外部链接
function safeOpenUrl(url) {
  // 仅允许 gitcode.com 域名
  try {
    const u = new URL(url);
    if (u.hostname === 'gitcode.com' || u.hostname === 'www.gitcode.com') {
      shell.openExternal(url);
    } else {
      console.warn('Blocked non-GitCode URL:', url);
    }
  } catch (err) {
    console.error('Invalid URL:', url);
  }
}

// IPC 处理
ipcMain.handle('get-config', loadConfig);
ipcMain.handle('save-config', async (_, config) => {
  await saveConfig(config);
});
ipcMain.on('open-gitcode-url', (_, url) => {
  safeOpenUrl(url);
});

// 应用启动
app.whenReady().then(async () => {
  createWindow();
  createTray();

  // 注册全局快捷键
  const ret = globalShortcut.register('CommandOrControl+Alt+G', () => {
    if (mainWindow) {
      if (mainWindow.isVisible()) {
        mainWindow.hide();
      } else {
        mainWindow.show();
      }
    }
  });

  if (!ret) console.warn('全局快捷键注册失败');
});

app.on('will-quit', () => {
  globalShortcut.unregisterAll();
});

app.on('window-all-closed', () => {
  // 不退出,保持托盘运行
});

🔒 安全要点:

  • 使用 shell.openExternal 而非 window.open
  • 严格校验域名,防止跳转恶意网站;
  • 配置文件存于 app.getPath('userData'),符合系统规范。

5. preload.js —— 安全桥接
代码语言:javascript
复制
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('gitcodeAPI', {
  getConfig: () => ipcRenderer.invoke('get-config'),
  saveConfig: (config) => ipcRenderer.invoke('save-config', config),
  openUrl: (url) => ipcRenderer.send('open-gitcode-url', url)
});

6. index.html —— 用户界面
代码语言:javascript
复制
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>GitCode 口袋</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
      margin: 0;
      padding: 20px;
      background: #f8f9fa;
      color: #333;
    }
    h1 {
      text-align: center;
      color: #0d6efd;
      margin-top: 0;
    }
    .link-card {
      background: white;
      padding: 14px;
      margin: 10px 0;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      cursor: pointer;
      transition: background 0.2s;
    }
    .link-card:hover {
      background: #e9f2ff;
    }
    .link-title {
      font-weight: bold;
      font-size: 16px;
    }
    .link-url {
      font-size: 12px;
      color: #666;
      word-break: break-all;
    }
    .form-group {
      margin-top: 20px;
    }
    input {
      width: 100%;
      padding: 8px;
      margin: 6px 0;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    button {
      background: #0d6efd;
      color: white;
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
      margin-right: 8px;
    }
    button:hover {
      background: #0b5ed7;
    }
    .delete-btn {
      background: #dc3545;
    }
    .delete-btn:hover {
      background: #c82333;
    }
  </style>
</head>
<body>
  <h1>📦 GitCode 口袋</h1>

  <!-- 固定链接 -->
  <div class="link-card" onclick="openUrl('https://gitcode.com')">
    <div class="link-title">🏠 GitCode 首页</div>
    <div class="link-url">https://gitcode.com</div>
  </div>

  <div class="link-card" onclick="openUrl('https://gitcode.com/explore')">
    <div class="link-title">🔍 探索仓库</div>
    <div class="link-url">https://gitcode.com/explore</div>
  </div>

  <div class="link-card" onclick="openUrl('https://gitcode.com/dashboard')">
    <div class="link-title">📊 个人仪表盘</div>
    <div class="link-url">https://gitcode.com/dashboard</div>
  </div>

  <hr style="margin: 20px 0; border: 0; border-top: 1px solid #eee;" />

  <!-- 自定义仓库 -->
  <h3>📌 我的仓库</h3>
  <div id="custom-repos"></div>

  <div class="form-group">
    <input type="text" id="repoUrl" placeholder="输入 GitCode 仓库地址,如 https://gitcode.com/user/repo" />
    <button onclick="addRepo()">添加</button>
  </div>

  <script>
    let customRepos = [];

    async function loadCustomRepos() {
      const config = await window.gitcodeAPI.getConfig();
      customRepos = config.customRepos || [];
      renderCustomRepos();
    }

    function renderCustomRepos() {
      const container = document.getElementById('custom-repos');
      if (customRepos.length === 0) {
        container.innerHTML = '<p style="color:#999;">暂无自定义仓库</p>';
        return;
      }
      container.innerHTML = customRepos.map((url, index) => `
        <div style="display:flex; gap:8px;">
          <div class="link-card" style="flex:1;" onclick="openUrl('${url}')">
            <div class="link-title">📦 仓库 ${index + 1}</div>
            <div class="link-url">${url}</div>
          </div>
          <button class="delete-btn" onclick="removeRepo(${index})">×</button>
        </div>
      `).join('');
    }

    async function addRepo() {
      const input = document.getElementById('repoUrl');
      const url = input.value.trim();
      if (!url) return alert('请输入仓库地址');

      try {
        const u = new URL(url);
        if (u.hostname !== 'gitcode.com' && u.hostname !== 'www.gitcode.com') {
          return alert('仅支持 GitCode 仓库地址!');
        }
      } catch {
        return alert('无效的 URL 格式');
      }

      if (!customRepos.includes(url)) {
        customRepos.push(url);
        await window.gitcodeAPI.saveConfig({ customRepos });
        input.value = '';
        renderCustomRepos();
      }
    }

    async function removeRepo(index) {
      customRepos.splice(index, 1);
      await window.gitcodeAPI.saveConfig({ customRepos });
      renderCustomRepos();
    }

    function openUrl(url) {
      window.gitcodeAPI.openUrl(url);
    }

    // 初始化
    window.addEventListener('DOMContentLoaded', loadCustomRepos);
  </script>
</body>
</html>

🎯 特性:

  • 固定三个常用入口;
  • 支持添加/删除自定义仓库;
  • 输入校验,防止非 GitCode 链接;
  • 简洁卡片式 UI,响应迅速。

四、运行与使用

代码语言:javascript
复制
npm start
  1. 应用启动后常驻托盘;
  2. Ctrl+Alt+G 唤出窗口;
  3. 点击任意卡片跳转 GitCode;
  4. 添加自己的常用仓库,下次直接点击进入。
在这里插入图片描述
在这里插入图片描述

✅ 开发者效率提升利器!


五、向 OpenHarmony 迁移思路

虽然 OpenHarmony 目前不支持系统托盘和全局快捷键,但核心功能仍可迁移:

Electron 功能

OpenHarmony 替代方案

托盘 + 快捷键

改为常驻小窗或桌面小组件

shell.openExternal

使用 @ohos.app.ability.UIAbilityContext.startAbility 跳转浏览器

本地配置存储

使用 @ohos.data.preferences

🌉 迁移步骤:

  1. index.html 放入 resources/rawfile/
  2. 在 ArkTS 中通过 Web 组件加载;
  3. 重写 openUrl 方法,调用鸿蒙跳转 API;
  4. preferences 替代 config.json

0成本迁移

还是老办法直接源代码迁移即可

在这里插入图片描述
在这里插入图片描述

真机测试成功

在这里插入图片描述
在这里插入图片描述

可以完成跳转功能

在这里插入图片描述
在这里插入图片描述

六、安全与最佳实践

实践

说明

❌ 禁用 nodeIntegration

防止页面执行任意 Node 代码

✅ 域名白名单校验

仅允许 gitcode.com 跳转

✅ 配置隔离存储

使用 app.getPath('userData')

✅ 最小权限原则

不请求不必要的系统权限


七、结语:小工具,大效率

“GitCode 口袋工具”证明了:一个优秀的开发者工具,不在于功能繁多,而在于解决具体痛点

它用不到 200 行核心代码,实现了高频场景的极致优化。更重要的是,它的架构清晰、安全合规、易于迁移——这正是现代跨端应用应有的样子。

下次当你觉得“每次都要打开浏览器太麻烦”时,不妨问问自己: “能不能做一个口袋工具?”

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Electron 与 OpenHarmony 的实战入门:GitCode 口袋工具V1.0.0
    • 一、功能设计
    • 二、项目结构
    • 三、完整代码实现
      • 1. 初始化项目
      • 2. package.json
      • 3. config.json(初始空配置)
      • 4. main.js —— 主进程核心
      • 5. preload.js —— 安全桥接
      • 6. index.html —— 用户界面
    • 四、运行与使用
    • 五、向 OpenHarmony 迁移思路
      • 0成本迁移
    • 六、安全与最佳实践
    • 七、结语:小工具,大效率
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档