前言
该项目为毕业设计所做项目
技术栈学习相关
Electron
安装
开幕雷击,官网的hello world就遇到了问题。在镜像、网络一切正常的情况下,不使用cnpm,npm和pnpm直接安装electron都会出现问题。搜索到的一些文章只提供了解决方法,并没有解释原因,这里根据我自己观察到的报错理解记录一下。
Electron 的安装过程分为两个阶段:
-
第一阶段:通过 pnpm 安装 Electron 的 npm 包(很小,只有几百 KB)
- 这个阶段很快,不会有问题
- 包含了一些基础的 JS 文件和安装脚本
-
第二阶段:postinstall 脚本执行
- 在 node_modules/electron 安装完成后会自动触发
- 这个脚本会下载对应平台的 Electron 预编译二进制文件
- 这才是真正卡住的地方
# 这一步很快
pnpm i electron
# 然后自动执行这一步时才会卡住
# 此时在下载类似 electron-v25.x.x-win32-x64.zip 这样的文件
node node_modules/electron/install.js
因此会有以下几种解决方法。
使用国内镜像;提前下载好对应版本的预编译包放到缓存目录、设置.npmrc或全局设置 pnpm config set ELECTRON_MIRROR https://npmmirror.com/mirrors/electron/ -g
API相关
弃用api更换
registerFileProtocol
在做搜索app时,需要展示搜索出的app的图标,不做任何处理的话会报错:
Not allowed to load local resource: file:///C:…
需要分别在主进程使用protocol.registerFileProtocol注册协议和在渲染进程中添加CSP允许加载本地图片
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: app-icon:;"
/>
Electron 废弃 API 改造的疑难杂症——protocol.register*Protocol根据 Electr - 掘金
vuejs3 - 现在 “registerFileProtocol” 在 Electron 中被弃用了怎么办?- 堆栈溢出
主要功能实现
插件机制
webview
iframe
browserView
插件市场
npm
项目技术及架构
技术栈
electron + vite + vue3 + tailwindCSS
采用了 electron-vite
这个开源脚手架,其中主要实现热更新的关键代码在于
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
} else {
mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
}
开发模式下,渲染进程实际上是通过 Vite 开发服务器提供的,所以不同于web页面,electron相关的内容出现的改动都需要重新构建,比如主进程代码、preload脚本,以及electron自身的配置项(BrowserWindow 的配置)。能起热更新作用的也就样式部分和静态资源,聊胜于无吧只能说。
Tailwind目前如果插件想使用还是得引cdn,有空再看看能不能注入到browserview里。
系统级功能
搜索
主要目的是获取到已安装应用的信息。
win
尝试了从注册表获取(get-installed-apps)的方法,不过效果不佳。参考了utools的实现方式,核心代码与思路如下:
1.使用 PowerShell 命令获取已安装应用信息
这里查询了 Windows 注册表中三个关键位置,分别是系统级安装的应用、当前用户安装的应用、64位系统上的32位应用。
// 定义要获取的应用属性
const filterValues =
"Select-Object DisplayName,DisplayIcon,UninstallString,DisplayVersion,InstallDate,Publisher,InstallLocation";
// 从三个注册表位置获取应用信息
const localMachine = `Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | ${filterValues}`;
const currentUser = `Get-ItemProperty HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | ${filterValues}`;
const wow6432Node = `Get-ItemProperty HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | ${filterValues}`;
// 64位系统额外检查 Wow6432Node
const x64 = process.arch === "x64" ? `;${wow6432Node}` : "";
然后执行命令。
function executePowerShell(cmd, callback) {
// 使用 spawn 而不是 exec,更适合处理大量输出
const ps = child.spawn('powershell', [
'-NoProfile', // 不加载配置文件,加快启动
'-Command',
cmd
])
const chunks: string[] = []
const errorChunks: string[] = []
// 使用 iconv-lite 处理中文编码
ps.stdout.on('data', (chunk) => chunks.push(iconv.decode(chunk, 'cp936')))
ps.stderr.on('data', (errorChunk) => errorChunks.push(iconv.decode(errorChunk, 'cp936')))
ps.on('close', () => {
const stdout = chunks.join('')
const stderr = errorChunks.join('')
callback(stdout, stderr)
})
}
后续就是各种js字符串方法的组合调用,填充app的属性,以便搜索筛选。不多赘述直接贴代码
function getAppList(callback: (apps: AppInfo[]) => void) {
executePowerShell(
`${localMachine};${currentUser}${x64}`,
(stdout, stderr) => {
if (stderr) {
console.error("PowerShell error:", stderr);
callback([]);
return;
}
try {
// 处理 PowerShell 输出格式
const apps = stdout
.trim()
.replace(/\r\n[ ]{10,}/g, "") // 移除多余空格
.split("\r\n\r\n") // 按应用分割
.filter(app => app.trim()); // 过滤空项
const appList = apps
.map(app => {
// 将每个应用的属性解析为字典
const dict: { [key: string]: string } = {};
app.split("\r\n").forEach(line => {
if (line) {
const [key, ...valueParts] = line.split(/\s+:\s*/);
const value = valueParts.join(":").trim();
if (key && value) dict[key] = value;
}
});
if (!dict.DisplayName) return null;
// 生成搜索关键词
const shortName = dict.DisplayName.split(" ")
.map(word => word[0])
.join("");
// 生成拼音关键词
let pinyinResult = "";
let pinyinShortName = "";
if (dict.DisplayName) {
pinyinResult = dict.DisplayName.split(" ")
.map(word =>
pinyin(word, { toneType: "none" }).replace(/\s+/g, "")
)
.join(" ");
pinyinShortName = pinyin(dict.DisplayName, { toneType: "none" })
.split(" ")
.map(word => word.charAt(0))
.join("")
.replace(/[^a-zA-Z]/g, "");
}
// 返回格式化的应用信息
return {
name: dict.DisplayName,
desc: dict.DisplayIcon || dict.InstallLocation || "",
type: "app",
icon: `app-icon://${encodeURIComponent(
sanitizeFileName(dict.DisplayName)
)}`,
keyWords: [
dict.DisplayName,
pinyinResult,
shortName,
pinyinShortName,
],
action: dict.DisplayIcon,
};
})
.filter(app => app !== null);
// 提取应用图标
appList.forEach(getIcons);
callback(appList);
} catch (error) {
console.error("处理应用列表出错:", error);
callback([]);
}
}
);
}
mac
还没做
快速调起
截图
命令行报错解决
输出中文 chcp 65001 + utf-8格式
权限报错 Error: listen EACCES: permission denied ::1:5173 net stop winnat