


作者: VON HarmonyOS 初级开发者|CSDN 新星创作者(Java/Web 领域) 专栏链接:Electron for OpenHarmony 仓库地址:Electron口袋工具
在日常开发中,你是否经常需要快速访问 GitCode(中国版 GitHub)上的项目、仓库或个人主页?是否希望有一个轻量、置顶、一键直达的桌面小工具,无需打开浏览器即可跳转常用页面?
本文将带你用 Electron + 安全架构 打造一个 GitCode 口袋工具。它具备:
contextIsolation: true);这个工具虽小,却融合了 系统集成、用户配置、安全 IPC 与 Web 跳转 四大能力,是实用型 Electron 应用的典范。
功能 | 说明 |
|---|---|
✅ 主界面 | 展示常用链接卡片(主页、仓库、个人) |
✅ 自定义仓库 | 用户可添加/删除自己的常用仓库地址 |
✅ 系统托盘 | 应用最小化到托盘,右键菜单操作 |
✅ 全局快捷键 | Ctrl+Alt+G 唤出/隐藏窗口 |
✅ 安全跳转 | 所有链接通过主进程校验后打开(防 XSS) |
💡 本工具不收集任何用户数据,所有配置仅保存在本地。
gitcode-pocket/
├── main.js # 主进程:窗口 + 托盘 + 快捷键 + 跳转逻辑
├── preload.js # 安全桥接:暴露配置与跳转 API
├── index.html # 渲染界面:链接卡片 + 表单
├── config.json # 本地存储的自定义仓库列表(可选)
└── package.jsonmkdir gitcode-pocket && cd gitcode-pocket
npm init -y
npm install electron --save-devpackage.json{
"name": "gitcode-pocket",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^33.0.0"
}
}config.json(初始空配置){
"customRepos": []
}⚠️ 首次运行时若不存在,程序会自动创建。
main.js —— 主进程核心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'),符合系统规范。preload.js —— 安全桥接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)
});index.html —— 用户界面<!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>🎯 特性:
npm startCtrl+Alt+G 唤出窗口;
✅ 开发者效率提升利器!
虽然 OpenHarmony 目前不支持系统托盘和全局快捷键,但核心功能仍可迁移:
Electron 功能 | OpenHarmony 替代方案 |
|---|---|
托盘 + 快捷键 | 改为常驻小窗或桌面小组件 |
shell.openExternal | 使用 @ohos.app.ability.UIAbilityContext.startAbility 跳转浏览器 |
本地配置存储 | 使用 @ohos.data.preferences |
🌉 迁移步骤:
index.html 放入 resources/rawfile/;Web 组件加载;openUrl 方法,调用鸿蒙跳转 API;preferences 替代 config.json。还是老办法直接源代码迁移即可

真机测试成功

可以完成跳转功能

实践 | 说明 |
|---|---|
❌ 禁用 nodeIntegration | 防止页面执行任意 Node 代码 |
✅ 域名白名单校验 | 仅允许 gitcode.com 跳转 |
✅ 配置隔离存储 | 使用 app.getPath('userData') |
✅ 最小权限原则 | 不请求不必要的系统权限 |
“GitCode 口袋工具”证明了:一个优秀的开发者工具,不在于功能繁多,而在于解决具体痛点。
它用不到 200 行核心代码,实现了高频场景的极致优化。更重要的是,它的架构清晰、安全合规、易于迁移——这正是现代跨端应用应有的样子。
下次当你觉得“每次都要打开浏览器太麻烦”时,不妨问问自己: “能不能做一个口袋工具?”