- 功能讲解
- 介绍项目结构和踩坑点
- electron+vue大项目完整打包
大公司做的TODO支持多端同步但功能实在太多,且收费。
开源项目存在我想要的功能可能没有实现,且没人维护的问题
于是决定自己开发一个简单的工具软件,使用electron绝对不是最优解,因为往应用里面塞个浏览器内核和node实在是太大了,但是我只会electron啊,NW.js、Tauri生态还是没electron好,flutter还在学习中......,后续会考虑使用flutter重构,进行三端同步一下,现在开发个桌面端练练手。
不过B站、qq,discord、clash、utools、vscode、mongodb compass、typora、draw.io、picGo这些也都用electron开发的,应该也还好。
博客参考:Electron-vue开发实战之To-do-List - 简书 (jianshu.com)
Electron-vue开发实战0——Electron-vue入门 | MARKSZのBlog (molunerfinn.com)(开发PicGo的大佬)
github地址:https://github.com/xiajingren/xhznl-todo-list
github地址:https://github.com/Aziteee/my-notes
simpleNotes:https://www.simplestickynotes.com/
国外一个大佬使用flutter的开发视频截图
设计初衷是简单。直接在桌面上展示一个可折叠的便签,支持快捷键调出,快捷键添加TODO,每个代办可以设置时间,修改状态,还要有持久化功能,可以看自己以前做了什么。最好有一个时间提醒的功能。再加上一个倒计时的效果,番茄钟
- 支持快捷键调出应用、添加代办
- 支持按照类别分组,但只能显示当天的
- 支持当天代办悬浮显示,设置代办已完成,修改代办信息
- 支持自定义设置快捷键(添加代办)
- 设置日期提醒,指定时间,自动播放语音
- 时间模块,计时器、番茄钟
- 自定义主题
初始化vue项目:使用vite,跟着 vue官网,不加美化和代码格式校验插件
初始化electron项目:安装electron和nodemon和electron-win-state,注意注释package.json的type:module
引入naive-ui组件库(还是不符合我需求)移除的命令:npm uninstall naive-ui
,使用Arco Design组件库了,配置组件按需引入。
搭建架子
使用上下布局,下面左右布局。
采用router-link标签和路由实现界面的切换
!注意app.vue里面也要是router-link。这个出口别忘记了,不然你会得到两个重复的界面
!样式问题,注意/deep/ >>>和!important的使用,我们使用第三方组件库常常使用这个来更改样式
开发第一天:
-
决定砍掉分类这个需求,更改成标签给用户一个自定义的空间。因为是分类的话要造的轮子太多了,又有个分类的CRUD。
-
pinia持久化插件使用 pinia-plugin-persistedstate
踩坑日记
1:arco design的message组件第三种方法按需引入显示的踩坑。
需要在main.js中导入对应css
2:持久化不生效的坑。
持久化时候暴露要把原来的对象暴露出来,仅仅暴露对数据操作的方法是不可以的,持久化不会生效。
3: :属性值v-bind绑定。
使用v-bind绑定可以接收到本来应该接收的类型,不然统一就是字符串,使用组件库时候注意,比如要传true和false
开发第二天
使用uuid库生成随机id,使用dayjs格式化时间
踩坑日记
1:给pinia默认值但是他无法加载的问题:。
注意pinia的使用方式,在组件里面不需要.value
2:vue插槽不能被包裹在div里面
3:那些富文本编辑器清除的时候注意一下
基本不是你直接设置值就可以的,需要调用富文本编辑器暴露出来的方法
额外知识
使用electron通信时候,不要直接使用ipcRender,使用preload,不然可能会被代码注入
ipc通信的三种方式,主进程到渲染进程、渲染进程到主进程、双向通信,API都是不同的
开发第三天:
踩坑日记
1:winstate持久化问题
看的22年秋B站的课,api改了。现在是 winState.manage(win); // 配置持久化
2:监听的时机问题
创建新窗口想要给新窗口传递信息时,主进程给渲染进程发送消息,渲染进程还没有启动监听模式,主进程延迟1ms发就可以了
3:自定义html问题。
我发现设置无边框窗口上方会出现一个小的条,这是因为元素有默认的margin和padding,清除了就好了
4:electron窗口透明不生效的原因
是因为开启了开发者工具,把这个关了就可以了
5:注意预加载脚本的引入
就算主窗口引入了子窗口还是得引入,每个窗口如果需要都得设置预加载脚本的路径
如何实现一个窗口一直在桌面上?
手动滑的话不能屏蔽win+d这种快捷键。那么监听这个快捷键可不可行呢?也是不可行的。因为快捷键不固定啊。找了几个小时,发现没人实现过这个功能,真的奇怪啊,网上那么多培训机构没有遇到过这种问题?看一下这个博客,但是人家直接知难而退electron+vue从0到1实现一个桌面端日期时间倒计时软件实践(持续更新)-腾讯云开发者社区-腾讯云 (tencent.com)
我可以使用一些包通过node调用c++代码,c++可以操作系统桌面,这个后续扩展吧,目前能给出的解决方案只有监听win+d,然后每次win+d的时候都激活一下这个窗口。windows应该有这种api吧,不然那些桌面宠物什么的怎么实现的
npm代理配置命令
npm config set registry https://registry.npm.taobao.org/
npm config get registry
nrm ls
nrm use taobao
npm config set https-proxy http://proxy.example.com:port
npm config set proxy http://proxy.example.com:port
npm config delete proxy
npm config delete https-proxy
npm config get proxy
npm config get https-proxy
双击标题怎么实现内容隐藏的?很简单,就是监听双击事件,固定头部高度,双击改高度,返璞归真
6:arco design导航栏问题
跟饿了么稍有不同,这个导航栏需要自己手动设置当前激活的是哪个,思路就是用计算属性监听route,route一改就跟着改当前激活的路由。
7:electron在相同的上下文创建右键菜单
右键菜单涉及到页面跳转的时候,注意使用单例模式,不要重复注册,思路是在仓库定义一个不持久化的标记变量,每次进来绑定前就看一下是不是第一次注册。
8:store值的问题
反映store的值最好使用计算属性而不是ref。有关设计的思考,pinia的值本来就不应该是双向绑定的,应该使用暴露出来的方法对pinia的值进行修改,使用计算属性可以实现单向的伪响应式
开发第四天
1:沙箱模式的设置
关闭sandbox模式,不然渲染进程无法通过window访问预加载进程暴露出来的功能。
2:arco design动态组件的自定义样式
Arco design 没办法自定义顶部菜单,样式选不中,不知道为啥。我发现动态的都是选不中的,比如下拉框这些需要动态渲染的
3:存储用户文件到本地的思考。
其实浏览器可以访问用户的图片在哪个目录,然后直接传递那个路径到electron中,由electron读取文件并写到我们指定的那个目录,注意,文件不能直接传,因为electron传递数据是会序列化的,二进制数据没办法被序列化。
开发第五天
1:解决不在任务栏显示窗口和阻止右键默认菜单的问题。
skipTaskbar: true, // 不在任务栏中显示
pomodoroWidgetWindow.hookWindowMessage(278, function (e) {
pomodoroWidgetWindow.setEnabled(false); //窗口禁用
setTimeout(() => {
pomodoroWidgetWindow.setEnabled(true); //窗口启用
}, 100);
return true;
});
2:自定义css样式全局不生效问题:。
注意某些样式值的特殊要求,比如伪元素的content必须"",在css里面就可以引用全局变量了
3:在js中给指定元素添加指定的样式
// 使用 watchEffect 来响应式地更新 CSS 变量
watchEffect(() => {
// 通过js操作元素样式
// 不使用默认的有序列表样式
if (todoIcons.value.olIcon !== "1." && todoIcons.value.olIcon.trim() !== "") {
// 设置全局变量
const style = document.createElement("style");
style.id = "custom-ol-style"; // 为 <style> 元素设置一个唯一的 id
style.innerHTML = `.ql-container ol > li::before,.card ol>li::marker { content: "${todoIcons.value.olIcon}" !important; }`;
document.head.appendChild(style);
} else {
// 使用默认的有序列表样式
const styleElement = document.getElementById("custom-ol-style"); // 通过 id 选中 <style> 元素
if (styleElement) {
styleElement.parentNode.removeChild(styleElement); // 从其父元素中移除
}
}
// 设置自定义样式变量
document.documentElement.style.setProperty(
"--ulIcon",
`"${todoIcons.value.ulIcon}"`
);
});
4:跳转浏览器逻辑的开发
下面这两个不会触发,必须使用官方提供的新的api(第三个方法),我也没设置什么安全策略啊,按道理之前那个不行了网上应该有解决方案的,可是网上还是推荐的前两个,还是gpt让我用新api的
win.webContents.on("new-window", (event, url) => {
console.log(111);
// 检查 URL 是否以 "https://" 或 "http://" 开头
if (url.startsWith("https://") || url.startsWith("http://")) {
event.preventDefault();
shell.openExternal(url);
}
});
win.webContents.on("will-navigate", (event, url) => {
console.log(222);
// 同样的逻辑,允许 http 和 https
if (url.startsWith("https://") || url.startsWith("http://")) {
event.preventDefault();
shell.openExternal(url);
}
});
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
// 检查 URL 协议
if (url.startsWith('https://') || url.startsWith('http://')) {
shell.openExternal(url); // 使用外部浏览器打开
return { action: 'deny' }; // 阻止 Electron 打开新窗口
}
});
开发第六天
1:watch和watchEffect的区别,watch使用的注意事项。
上面两个都可以,要注意的watch监听对象的时候,一般都得deep:true,不然监听不到对象的属性值。要看你这个引用是不是改变了。不改变的话不用deep。但是从store里面拿的话引用肯定是变了的。注意如果监听普通属性需要 使用箭头函数监听
watchEffect(() => {
console.log(111);
const customSettingsForIpc = JSON.parse(JSON.stringify(customSettings.value));
window.electron.syncSetting(customSettingsForIpc);
});
watch(
customSettings,
(oldV, newV) => {
console.log(newV);
const customSettingsForIpc = JSON.parse(JSON.stringify(newV));
window.electron.syncSetting(customSettingsForIpc);
},{deep:true}
);
2:全局快捷键注册:
Ctrl+Alt注册不能成功,需要加上另外的一个键。注意和mac的兼容,mac里面ctrl是command。如果允许用户输入两个相同的快捷键如ctrl+ctrl需要特殊处理下面的逻辑。:这里只是实现了组合键
// 实时监听键盘的组合键并在输入框回显
const beginKeyBoardListener = () => {
const pressedKeys = new Array(); // 存储按下的键
const keyDownHandler = async (event) => {
form.value[currentInput.value] = "";
let bigKey = event.key;
// 转换成大写更好看,也可以跟API对接
if (event.key.length == 1) {
bigKey = event.key.toUpperCase();
}
pressedKeys.push(bigKey); // 添加按下的键
const combination = Array.from(pressedKeys).join("+"); // 构建组合键字符串
// 检查当前是否有输入框被激活
if (currentInput.value && form.value.hasOwnProperty(currentInput.value)) {
// 将组合键回显到激活的输入框
form.value[currentInput.value] += `${combination}`;
event.preventDefault(); // 阻止默认行为
}
};
// 捕捉键盘弹起事件
const keyUpHandler = (event) => {
console.log("up", event.key);
pressedKeys.pop(); // 释放键时移除
};
// 添加键盘事件监听器
document.addEventListener("keydown", keyDownHandler);
document.addEventListener("keyup", keyUpHandler);
// 不需要时移除监听器
removeListener.value = () => {
document.removeEventListener("keydown", keyDownHandler);
document.removeEventListener("keyup", keyUpHandler);
};
};
3:关于响应式的思考。
有的时候不需要响应式,比如注册快捷键时候。一方面可能会报错,如果是响应式快捷键对象,watch监听到立即就注册,而用户快捷键可能还没有输入完成。另一方面是消耗性能。处理方法是解构,不要让对象响应式。
4:electron传递vue响应式对象问题
一定是要用json序列化一下,直接.value不行的。需要这样: JSON.parse(JSON.stringify(form.value))
5:箭头函数问题
不加大括号,一行他可以自动返回,加了的话就不能自动返回了!!!!。因为不加的话会默认给你加上return
shortcutSetting: (customSettingsForIpc) =>
ipcRenderer.invoke("shortcut-setting", customSettingsForIpc),
shortcutSetting: (customSettingsForIpc) =>
{ ipcRenderer.invoke("shortcut-setting", customSettingsForIpc)}
提示功能
还是跟我之前那个五子棋游戏一样
事件 | 语音 |
---|---|
番茄钟短休息 | 时间到了,短短的休息一会儿吧 |
番茄钟长休息 | 你太棒了,接下来是长休息时间 |
番茄钟专注 | 好了,开始专注吧 |
倒计时过半 | 时间已经过去一半了 |
倒计时结束 | 倒计时结束了 |
待办提醒 | 您现在有计划的安排,提醒您一下哈 |
6:对于electron打开一个新窗口pinia里面的值不自动同步问题:。
vue是开发SPA的,打开新窗口会重新加载APP.vue,之前的pinia应该就不默认同步过来(重新初始化pinia状态),需要监听这个store事件手动同步,只能同步持久化的值,之前的缓存状态是同步不过来的,除非打开窗口时候传递过来
window.addEventListener("storage", (event) => {
if (event.key === "customSettings") {
// 重新从localStorage加载状态
const updatedState = JSON.parse(event.newValue)?.customSettings;
if (updatedState) {
// 手动更新pinia的状态,因为打开新窗口默认又是一个应用,不会响应式更新了
customSettingsStore.customSettings = updatedState;
}
}
});
开发第7天
1:关于点击事件的设置。
可以从event事件拿到元素的属性,进而动态判断是不是应该触发。另外还有可以模拟三击右键双击事件,但是需要自己实现逻辑
模拟右键双击
let lastRightClickTime = "";
// 右键双击
const handleContextMenu = (event) => {
if (event.button === 2) {
// 检查右键单击
const now = new Date().getTime();
if (!lastRightClickTime || now - lastRightClickTime > 300) {
// 大于300毫秒,认为是右键单击
lastRightClickTime = now;
} else {
// 小于300毫秒,认为是右键双击
window.electron.openPomodoroWindow("a");
}
}
};
动态实现鼠标事件
// 双击定时器部分不应该有响应
if (event.target.closest(".pomodoro-timer")) {
return;
}
electron配置:
// 不安全,不建议使用
// nodeIntegration: true, // 启用Node.js集成
// contextIsolation: false, // 取消上下文隔离
// 设置偏好
// frame: false, // 无边框窗口
// titleBarStyle: "hidden",
// titleBarOverlay: true,
// fullscreen: true,
2:svg图标颜色更改
更改path的fill,有的时候需要动态设置属性值,如导航栏高亮,可以下面这样设置
模版
:class="{ highlight: currentRouteKey === '/plain/todo' }"
css
/* 自定义图标高亮*/
.highlight svg path {
fill: rgb(57, 134, 253);
}
开发第8天
完善提示功能
添加项目使用说明书
添加README
开发第9天
修复小bug,录制讲解视频
1:electctron窗口使用单例模式,避免用户打开多个主窗口
// 检查是否已经有一个实例运行
const isSingleInstance = app.requestSingleInstanceLock();
if (!isSingleInstance) {
// 如果已经有一个实例在运行,关闭当前实例并退出
app.quit();
}
2:优化体积
package.json中添加一个钩子
"afterPack": "scripts/afterPack.js",
afterPack脚本
const path = require("path");
const fs = require("fs-extra");
module.exports = async (context) => {
const unpackedDir = path.join(context.appOutDir, "locales");
// 删除除 zh-CN.pak 之外的所有文件
const files = await fs.readdir(unpackedDir);
for (const file of files) {
if (!file.endsWith("zh-CN.pak")) {
await fs.remove(path.join(unpackedDir, file));
}
}
// 删除特定的文件
const filesToDelete = ["LICENSE.electron.txt", "LICENSES.chromium.html"];
for (const fileName of filesToDelete) {
const filePath = path.join(context.appOutDir, fileName);
if (await fs.pathExists(filePath)) {
await fs.remove(filePath);
}
}
};
3:通知栏中去掉electron.app的前缀
if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
4:打包应用后重命名打包的应用脚本
package.json中添加一个钩子
"artifactBuildCompleted": "scripts/rename.js",
rename脚本
const fs = require("fs");
const path = require("path");
// 主打就是一点体力活都不干
module.exports = async function (params) {
// 读取版本号
const packageJson = require("../package.json");
const version = packageJson.version;
let artifact = params.file;
let originFile = artifact;
const ext = path.extname(artifact);
// 处理版本号前缀,注意空格
artifact = artifact.replace(` ${version}`, `-${version}`);
let newName;
if (ext === ".exe" && !artifact.includes("Setup")) {
// 不用安装的程序
newName = artifact.replace(/\.exe$/, "-windows-no-installer.exe");
} else if (ext === ".exe") {
// 常规安装包
newName = artifact.replace(
` Setup-${version}.exe`,
`-${version}-windows-installer.exe`
);
} else {
newName = artifact;
}
// 重命名
if (newName) {
fs.renameSync(originFile, newName);
}
};
开发第10天
整理笔记
发布教程
录制视频
发现一个坑,富文本编辑器空格不生效和对不齐问题
空格不生效是因为默认解析成一个空格,添加这个css解决空格和换行问题。(如果需要跟后端联动可能还需要replace一下)
white-space: pre-wrap
对不齐是因为字体和缩进不一样,开发者工具检查一下把富文本编辑器的字体和缩进拷贝过来就可以了
font-family: Helvetica, Arial, sans-serif;
tab-size: 4;
频繁下载安装可能会出现这个bug,就是pinia状态公用了,这时候去C:\Users\Albert han\AppData\Roaming
目录下清缓存,只可能在开发机器上有这个问题,删除的话默认也会删除这个目录
第2个bug,窗口到后台计时器不会正常工作。参考文章:
stackoverflow的解决方案:https://cloud.tencent.com/developer/ask/sof/108364807
issue:electron/electron#20974
官方文档:https://www.electronjs.org/zh/docs/latest/api/browser-window
在每个窗口webPreference配置这个选项,表示进入后台后拒绝节流,不让electron关闭定时器、播放音频、执行后台计算等等
backgroundThrottling: false,
当你按Win + D
时,所有窗口都会最小化,包括你的Electron应用。当你再次按Win + D
时,操作系统会尝试恢复之前的窗口状态,这个过程中可能会出现焦点管理的问题,导致Electron窗口的行为异常。
electron应用通信
graph LR
渲染进程 <--"window.electron."-->预加载进程<--"ipcMain&ipcRender"-->主进程
定时提醒的流程图
flowchart TD
A[开始]-->ADD[清空定时器]-->B[遍历待办数组]
B-->C{时间过了吗}
C--Yes-->D{待办完成了吗}
C--No-->F
D--Yes-->E[给每个待办注册监听器]
D--No-->F
E-->F[结束]
上传图片业务的时序图
sequenceDiagram
participant pinia
participant 渲染进程
participant 主进程
渲染进程->>渲染进程:1. 获取上传图片的本地路径
渲染进程->>主进程: 2. 将图片类型和路径传递给主进程
主进程->>主进程: 3. 将图片写入指定目录/resources/backgrounds/
主进程->>渲染进程: 4. 返回图片路径
渲染进程->>pinia: 5. 路径存储到pinia
本教程的应用结构:AZCodingAccount/iTime: 基于electron、vue3、Arco Design的桌面端效率软件 (github.com)
恕我直言,网上的electron打包教程10个里面有9坨。踩了5个小时的坑才整完,看了几十个文章,基本没一个可以把一个大项目完整打下来的,看到几篇都是官方示例往上一搬就是vue+electron打包教程了,自己看都不看就硬搬。离谱至极
不使用electron-forge,使用electron-builder 官网:electron-builder
1:安装electron-is-dev 用来判断是开发环境还是生产环境(其他方式也可)
pnpm i electron-is-dev # 安装
const isDev = require("electron-is-dev"); #使用
2:改装项目,如果有使用加载路由的,改成加载文件url,vite会自动导航,修改vue-router模式为hash
如果需要创建新窗口,修改代码如:
if (isDev) {
pomodoroWindow.loadURL("http://localhost:5173/fullscreen/pomodoro");
} else {
pomodoroWindow
.loadFile(path.join(__dirname, "dist", "index.html"))
.then(() => {
pomodoroWindow.loadURL(
`file://${path.join(
__dirname,
"dist",
"index.html"
)}#/fullscreen/pomodoro`
);
});
}
3:新建.npmrc文件(需要从github拉取,添加一个镜像更快)
node-linker=hoisted # pnpm兼容配置,参考官方issue https://github.com/electron-userland/electron-builder/issues/6289#issuecomment-1042620422
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
4:安装electron-builder
pnpm i electron-builder -D
5:打包vue项目:
pnnpm build
6:修改package.json(开发和生产环境依赖已省略 )
{
"name": "iTime",
"version": "1.0.0",
"description": "基于electron+vue3+arco design开发的桌面效率工具",
"author": "AlbertZhang<[email protected]>",
"private": true,
"main": "main.js",
"homepage": "./",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"start": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue",
"electron:build": "electron-builder" # 添加这个脚本
}, # 添加下面这个配置项
"build": {
"appId": "com.albertzhang.itime",
"productName": "iTime",
"directories": {
"output": "dist_electron" # 指定打包后的输出目录
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": true # 卸载时清除用户数据
}, # 取消一键安装,允许用户自定义
"extraFiles": [{
"from": "src/assets",
"to": "resources/assets"
},
{
"from": "软件使用说明书.md",
"to": "软件使用说明书.md"
}], # 原封不动拷贝
"files": [
"dist/**/*",
"main.js",
"preload/**/*",
"package.json"
], # 定义应该被打包的文件路径
"win": {
"target": "nsis",
"icon": "dist/icons/icon.png"# 图标路径
},
"mac": {
"target": "dmg"
}
}
}
7:配置vite.config.js文件(根属性)。一定要设置,不然会白屏,因为路径找不到
base: "./", // 设置为相对路径
如果有引用到本地文件的,静态文件可以放到public或者assets下均可正常引用(vite会处理)。如果需要设置动态加载的静态文件按照下面步骤:
-
获取app路径: getAppPath: () => process.resourcesPath:preload.js
-
渲染进程(组件)中引入并使用:window.键名.getAppPath(),手动跟之前原封不动拷贝的
extraFiles
文件路径做拼接,注意getAppPath获取的是win-unpacked\resources
目录,自己拷贝的目录文件在哪,手动拼接即可 -
如果想设置动态背景图url,绝对路径遵循下面的格式,其他格式不可生效(自己对空格,前缀\这些进行一下处理)
标准格式: file:///C:/Users/Albert%20han/Desktop/easyToDo/dist_electron/win-unpacked/resources/assets/timeBGI.jpg 拼接格式(appPath+extraFiles) C:\\Users\\Albert han\\Desktop\\easyToDo\\dist_electron\\win-unpacked\\resources/assets/timeBGI.jpg
ℹ️:对于播放音频这种,简单拼接即可,Windows可以兼容\和/同时存在的路径
ℹ️:我们使用__dirname获得的路径不强关联与任何本机相关的路径,引用静态资源或创建目录时使用app.getPath或process.resourcesPath获取路径
8:运行打包命令
pnpm electron:build -- --win # 注意自己包管理工具的语法
番外篇 优化大小。
我的应用打包完成是320MB,这个体积是非常不友好的。打包完成以后发现有些文件是完全没有必要,如下图
- 其中locales是多语言的配置,36MB,我的应用不需要多语言,因此把除中文的全部排除
- 资源目录下是我们的asar归档文件,我的是40多M,有优化的空间但是不多,如优化代码,优化依赖项等
可以看asar文件中包含的是什么
npm i -g asar # 下载插件
asar extract app.asar ./app #解压asar文件
- 两个许可证文件LICENSES.chromium和LICENSE.electron,我这里选择直接不要,优化了9MB
一共优化了45M,这个自己开发的话还是可以的,至少比网上的一些人才让把node_modules文件夹排除了有用点
需要自己在afterPack钩子后执行一个脚本,这个脚本返回一个异步函数负责删除那些不用的文件
在package.json里面添加这个配置项
"afterPack": "scripts/afterPack.js",
files也要包含这个路径
"files": [
"dist/**/*",
"main.js",
"preload/**/*",
"package.json",
"scripts/**/*"
],
脚本文件
const path = require("path");
const fs = require("fs-extra"); // fs 的一个扩展
module.exports = async (context) => {
const unpackedDir = path.join(context.appOutDir, "locales");
// 删除除 zh-CN.pak 之外的所有文件
const files = await fs.readdir(unpackedDir);
for (const file of files) {
if (!file.endsWith("zh-CN.pak")) {
await fs.remove(path.join(unpackedDir, file));
}
}
// 删除特定的文件
const filesToDelete = ["LICENSE.electron.txt", "LICENSES.chromium.html"];
for (const fileName of filesToDelete) {
const filePath = path.join(context.appOutDir, fileName);
if (await fs.pathExists(filePath)) {
await fs.remove(filePath);
}
}
};
electron-builder钩子:artifactBuildCompleted
、afterPack
、afterSign
这三个钩子,可以自动化构建流程
经过一系列操作,优化到了280MB。
1:桌面挂件UI的设置?
- 当我们倒计时的时候,我认为重置按钮使用频率不会很大,这样没必要影响页面的美观性。因此我把这个按钮改成了快捷方式R,reset
- 对于番茄钟,这个使用频率会高一点,所以我把他设置成了桌面的一个按钮。
- 值得指出的是,这两个挂件都没有设置退出的按钮,支持双击时间和e键退出。
2:为什么待办使用标签而不是分类?
一方面如果是分类要对分类进行CRUD,增加了项目复杂度,且用户体验也没有提升多少。使用标签的话相对自由一点,虽然也可以对标签CRUD,但是我没实现也不会显得很突兀。
3:为什么设置通知时候系统通知是静默通知?
我是很受不了windows的默认声音的,并且在给用户通知的时候已经实现了声音的播放,没必要再来一次,弹一个系统弹框就可以了。话说这个通知可能也有同学想要自定义显示隐藏,我这个忘记了😂,用的人多了后续版本迭代吧
4:为什么允许用户设置背景图?
-
对番茄钟来说,实现一个全屏的对很多同学来说可能是刚需,比如电脑用来提醒,自己可以做作业什么的,如果不提供设置背景图的接口用户体验会有点差。
-
而桌面挂件这个只是为了UI看起来不是那么突兀,这个自定义设置感觉很多同学应该是用不到,一般默认的半透明就可以了
5:桌面挂件的尺寸设置
-
为了UI的合理性,我为番茄钟和计时器都设置了一个固定的宽高,用户不能自己拖动更改。
-
对待办挂件来说,默认本来是有个滚动条的,如果你的内容多于高度,就会显示滚动条,为了美观我去掉了这个滚动条,允许用户拖动控制大小,因为我认为对于桌面代办,应该很少有同学会设置几十个。如果超过了高度剩下的内容是展示不出来的。
6:自定义设置
用户自定义设置的部分,我暴露出了一些接口
- 界面外观设置(新版本推出)
- 字体大小、样式、颜色的设置
- 主题的设置
- 待办部分
- 允许用户自定义待办背景图(感觉没必要,后续TODO吧)
- 允许用户自定义列表图标显示的内容
- 番茄钟
- 允许用户自定义倒计时和番茄钟背景图
- 允许用户自定义番茄钟4个参数
- 全局设置
- 允许用户自定义全局快捷键(一键打开番茄钟、倒计时,一键全屏显示番茄钟)
- 允许用户设置桌面挂件的高度,倒计时和番茄钟是不是一直在顶层。
- 允许用户自定义提示语音,待办提示语音,倒计时番茄钟提示语音
开发规范
- feat:新功能(feature)的添加。
- fix:修复 bug。
- docs:文档(documentation)的变更
- style:不影响代码含义的变更(空格、格式化、缺少分号等)。
- refactor:既不修复 bug 也不添加功能的代码变更。
- perf:提高性能的代码变更。
- test:添加缺失的测试或更正现有的测试。
- chore:对构建过程或辅助工具和库(如文档生成)的更改。
- build:影响构建系统或外部依赖关系的更改(例如:gulp、webpack、npm)。
- ci:对 CI 配置文件和脚本的更改(例如:GitLab CI、Circle、Travis、Azure Pipelines)。
- revert:撤销之前的 commit
带图标的提交:
- feat:新功能(feature)的添加。
- ✨
:sparkles:
- ✨
- fix:修复 bug。
- 🐛
:bug:
- 🐛
- docs:文档(documentation)的变更。
- 📝
:memo:
- 📝
- style:不影响代码含义的变更(空格、格式化、缺少分号等)。
- 💄
:lipstick:
(主要用于样式和外观的更新,但在没有更合适的图标时也可用于代码样式更改)
- 💄
- refactor:既不修复 bug 也不添加功能的代码变更。
- ♻️
:recycle:
- ♻️
- perf:提高性能的代码变更。
- 🚀
:rocket:
- 🚀
- test:添加缺失的测试或更正现有的测试。
- ✅
:white_check_mark:
- ✅
- chore:对构建过程或辅助工具和库(如文档生成)的更改。
- 🔧
:wrench:
(表示工具或配置文件的更改) - 📦
:package:
(用于更新编译文件或包)
- 🔧
- build:影响构建系统或外部依赖关系的更改(例如:gulp、webpack、npm)。
- 🛠
:hammer_and_wrench:
(表示构建工具或外部依赖的更改)
- 🛠
- ci:对 CI 配置文件和脚本的更改(例如:GitLab CI、Circle、Travis、Azure Pipelines)。
- 🚧
:construction:
(表示 CI 构建系统的工作)
- 🚧
- revert:撤销之前的 commit。
- ⏪
:rewind:
- ⏪
- ui :美化样式
git常用命令
查看提交日志
git log
git log -p # 查看提交的差异
git log -n 5 # 指定查看5条
git log --pretty=format:"%h - %an, %ar : %s" # 美化输出格式 %h 是缩短的哈希值,%an 是作者名字,%ar 是相对日期(例如,“2 days ago”),%s 是提交信息
git log --author="作者名字" # 查看指定作者的提交日志
git log --graph # 图形化方式显示
# q退出
回退提交
git reset --soft HEAD~3 # 软提交,保留暂存区和目录
git reset HEAD~3 # 回退3个提交,指向倒数第4个。删除之前的提交记录。混合提交,保留目录但不保存暂存区
git reset --hard HEAD~3 #硬提交,不保存暂存区和目录
git revert hash值 # 回退之前提交做的修改,不影响之后的,且保留记录
git revert --continue # 如果遇到冲突,解决冲突后继续回退
删除已经提交过的文件
git rm -r --cached .vscode
移除暂存区
git restore --staged "文件名"
远程仓库配置
git remote -v
git remote set-url origin 新的仓库地址
git remote add <远程仓库名> <远程仓库URL>
gif动图有的可以复用帧,所以才能做到像素较低