├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── auto-imports.d.ts ├── components.d.ts ├── index.html ├── jsconfig.json ├── main.js ├── package.json ├── pnpm-lock.yaml ├── preload └── index.js ├── public └── icons │ └── icon.png ├── scripts ├── afterPack.js └── rename.js ├── src ├── App.vue ├── assets │ ├── defaultBGI.jpg │ ├── main.css │ └── voices │ │ ├── pomodoro │ │ ├── default │ │ │ ├── focus.wav │ │ │ ├── longBreak.wav │ │ │ └── shortBreak.wav │ │ ├── dingzhen │ │ │ ├── focus.wav │ │ │ ├── longBreak.wav │ │ │ └── shortBreak.wav │ │ ├── jiaran │ │ │ ├── focus.wav │ │ │ ├── longBreak.wav │ │ │ └── shortBreak.wav │ │ └── kobe │ │ │ ├── focus.wav │ │ │ ├── longBreak.wav │ │ │ └── shortBreak.wav │ │ ├── timer │ │ ├── default │ │ │ ├── fullTime.wav │ │ │ └── halfTime.wav │ │ ├── dingzhen │ │ │ ├── fullTime.wav │ │ │ └── halfTime.wav │ │ ├── jiaran │ │ │ ├── fullTime.wav │ │ │ └── halfTime.wav │ │ └── kobe │ │ │ ├── fullTime.wav │ │ │ └── halfTime.wav │ │ └── todos │ │ ├── default │ │ └── remind.wav │ │ ├── dingzhen │ │ └── remind.wav │ │ ├── jiaran │ │ └── remind.wav │ │ └── kobe │ │ └── remind.wav ├── components │ └── ToDoList.vue ├── main.js ├── router │ └── index.js ├── stores │ ├── CustomSettings.js │ ├── CustomToDoStore.js │ ├── HasVisitedBefore.js │ └── ToDo.js └── views │ ├── desktop │ ├── CustomToDo.vue │ ├── Pomodoro.vue │ └── Timer.vue │ ├── fullscreen │ ├── Pomodoro.vue │ └── Timer.vue │ ├── layout │ ├── LayOutContainer.vue │ └── LayOutSettings.vue │ ├── pc │ ├── About.vue │ ├── AddCustomToDo.vue │ ├── CustomToDo.vue │ ├── PlainToDo.vue │ ├── Pomodoro.vue │ └── Timer.vue │ └── settings │ ├── AppearanceSettings.vue │ ├── ClockSettings.vue │ ├── GlobalSettings.vue │ └── ToDoSettings.vue ├── vite.config.js ├── 软件使用说明书.md └── 项目开发笔记.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | .vscode 18 | dist_electron 19 | 20 | 21 | /cypress/videos/ 22 | /cypress/screenshots/ 23 | 24 | # Editor directories and files 25 | .vscode/* 26 | !.vscode/extensions.json 27 | .idea 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | 34 | *.tsbuildinfo 35 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ 3 | ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Albert Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iTime 2 | 3 | ## 介绍 📘 4 | 5 | ​ iTime🕰️ 是一款基于**vue3**、**arco design**、**electron**开发的桌面端效率应用,slogan 是`让每一秒都刚刚好`,意为提高您的效率,帮助您充分利用自己的每一秒。项目功能模块有**待办模块**、**倒计时模块**、**番茄钟模块**、**自定义设置模块**。 6 | ## 项目预览 👀 7 | 视频版本(B站):[基于electron开发的桌面端效率软件—第一期【功能介绍】](https://www.bilibili.com/video/BV1Yt421W79i) 8 | 9 | 文字版本:移步[iTime软件使用介绍](https://github.com/AZCodingAccount/iTime/blob/master/软件使用说明书.md),内有详细功能介绍与Gif图 10 | 11 | ## 代码仓库 🌟 12 | 13 | - Gitee:https://gitee.com/AZCodingAccount/iTime 14 | - GitHub:https://github.com/AZCodingAccount/iTime 15 | 16 | ## 快速开始 🚀 17 | 18 | - **拉取项目** (您需要先安装 Git) 19 | 20 | ```bash 21 | # Gitee 22 | git pull https://gitee.com/AZCodingAccount/iTime.git 23 | # GitHub 24 | git pull https://github.com/AZCodingAccount/iTime.git 25 | ``` 26 | 27 | - 运行项目 28 | 29 | 30 | 31 | ```bash 32 | cd 拉取项目目录 33 | pnpm i # 安装依赖 34 | pnpm dev # 运行vue程序 35 | pnpm start # 运行electron桌面程序 36 | ``` 37 | 38 | ℹ️ 在开发环境下,您需要设置相应的图片和语音路径,默认路径为生产环境下的 39 | 40 | ## 项目技术应用 🛠️ 41 | 42 | 1. `Vue3`+`Electron`为主要开发技术 43 | 2. 采用`aro design`组件库并进行一定程度定制 44 | 3. 数据持久化采用`pinia` 45 | 4. 引入`quill`富文本编辑器 46 | 5. 第三方包 47 | 1. `uuid`生成 TODO 随机 id 48 | 2. `dayjs`格式化时间 49 | 3. `electron-is-dev`判断开发或生产环境 50 | 4. `electron-win-state`持久化窗口状态 51 | 5. `pinia-plugin-persistedstate`持久化 pinia 52 | 6. [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components)+ [`unplugin-auto-import`](https://github.com/antfu/unplugin-auto-import) 按需引入组件 53 | 6. 打包使用`electron-builder` 54 | 55 | ## 未来 🔮 56 | 57 | - 🪄 自定义外观 58 | - 📸 集成截图和 OCR 功能 59 | - 🤖 集成 ChatGPT 60 | 61 | ## 贡献 🤝 62 | 63 | 项目欢迎任何形式的贡献 64 | 65 | - 提出[issue](https://github.com/AZCodingAccount/iTime/issues)报告 bug 或要求新功能 66 | - 提交[PR](https://github.com/AZCodingAccount/iTime/pulls)帮助完善应用 67 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | AButton: typeof import('@arco-design/web-vue')['Button'] 11 | ACard: typeof import('@arco-design/web-vue')['Card'] 12 | ACollapse: typeof import('@arco-design/web-vue')['Collapse'] 13 | ACollapseItem: typeof import('@arco-design/web-vue')['CollapseItem'] 14 | ADatePicker: typeof import('@arco-design/web-vue')['DatePicker'] 15 | ADivider: typeof import('@arco-design/web-vue')['Divider'] 16 | AForm: typeof import('@arco-design/web-vue')['Form'] 17 | AFormItem: typeof import('@arco-design/web-vue')['FormItem'] 18 | AInput: typeof import('@arco-design/web-vue')['Input'] 19 | AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] 20 | AInputTag: typeof import('@arco-design/web-vue')['InputTag'] 21 | ALayout: typeof import('@arco-design/web-vue')['Layout'] 22 | ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent'] 23 | ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader'] 24 | ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'] 25 | AList: typeof import('@arco-design/web-vue')['List'] 26 | AListItem: typeof import('@arco-design/web-vue')['ListItem'] 27 | AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta'] 28 | AMenu: typeof import('@arco-design/web-vue')['Menu'] 29 | AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'] 30 | AModal: typeof import('@arco-design/web-vue')['Modal'] 31 | AOption: typeof import('@arco-design/web-vue')['Option'] 32 | AProgress: typeof import('@arco-design/web-vue')['Progress'] 33 | ASelect: typeof import('@arco-design/web-vue')['Select'] 34 | ASlider: typeof import('@arco-design/web-vue')['Slider'] 35 | ASwitch: typeof import('@arco-design/web-vue')['Switch'] 36 | ATag: typeof import('@arco-design/web-vue')['Tag'] 37 | ATextarea: typeof import('@arco-design/web-vue')['Textarea'] 38 | ATimePicker: typeof import('@arco-design/web-vue')['TimePicker'] 39 | AUpload: typeof import('@arco-design/web-vue')['Upload'] 40 | RouterLink: typeof import('vue-router')['RouterLink'] 41 | RouterView: typeof import('vue-router')['RouterView'] 42 | ToDoList: typeof import('./src/components/ToDoList.vue')['default'] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | iTime 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | }, 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // 导入模块,不能使用ES6的语法 2 | const { 3 | app, 4 | BrowserWindow, 5 | Menu, 6 | ipcMain, 7 | shell, 8 | globalShortcut, 9 | Notification, 10 | dialog, 11 | powerSaveBlocker, 12 | } = require("electron"); 13 | const path = require("path"); 14 | const WinState = require("electron-win-state").default; 15 | const fs = require("fs"); 16 | // 检查是否已经有一个实例运行 17 | const isSingleInstance = app.requestSingleInstanceLock(); 18 | 19 | if (!isSingleInstance) { 20 | // 如果已经有一个实例在运行,关闭当前实例并退出 21 | app.quit(); 22 | } 23 | let isDev = false; 24 | async function checkIsDev() { 25 | const module = await import("electron-is-dev"); 26 | isDev = module.default; 27 | // 全局捕获一下 28 | try { 29 | console.log(isDev); 30 | work(); 31 | } catch (error) { 32 | dialog.showMessageBox({ 33 | type: "error", // 对话框类型 34 | title: "未知错误", // 标题栏文本 35 | message: "未知错误,请联系开发者", // 主消息文本 36 | // detail: error.message, // 详细错误信息 37 | buttons: ["确定"], // 对话框按钮 38 | }); 39 | } 40 | } 41 | checkIsDev(); 42 | 43 | const work = () => { 44 | if (process.platform === "win32") { 45 | app.setAppUserModelId(app.name); 46 | } 47 | // 创建一个窗口 48 | let win = null; 49 | let globalSettings = []; // 用户全局配置 position:0|voice:1 50 | 51 | // 创建主窗口 52 | const createWindow = () => { 53 | // 防止打开两个主窗口程序 54 | if (win != null) { 55 | return; 56 | } 57 | const winState = new WinState({}); 58 | win = new BrowserWindow({ 59 | ...WinState.winOptions, 60 | icon: "public/icons/icon.png", // 指定图标路径 61 | webPreferences: { 62 | devTools: false, 63 | webSecurity: false, // 禁用 Web 安全策略 64 | nodeIntegration: true, // 启用集成 65 | backgroundThrottling: false, // 取消节流 66 | // 不安全,不建议使用 67 | // nodeIntegration: true, // 启用Node.js集成 68 | // contextIsolation: false, // 取消上下文隔离 69 | sandbox: false, // 取消沙箱模式 70 | preload: path.resolve(__dirname, "./preload"), // 在预加载脚本中执行 Electron API 71 | }, 72 | }); 73 | win.setSize(1100, 700); // 显式设置窗口大小,因为之前的大小被缓存了 74 | win.center(); // 使窗口居中 75 | win.setMenu(null); // 去掉窗口 76 | if (isDev) { 77 | win.loadURL("http://localhost:5173"); 78 | } else { 79 | win.loadFile(path.join("dist", "index.html")); 80 | } 81 | // win.webContents.openDevTools(); // 打开开发者工具 82 | winState.manage(win); // 配置持久化 83 | win.on("ready-to-show", () => { 84 | win.show(); 85 | }); 86 | // 配置所有外部url都使用浏览器打开 87 | win.webContents.setWindowOpenHandler(({ url }) => { 88 | shell.openExternal(url); // 使用外部浏览器打开 89 | return { action: "deny" }; // 阻止 Electron 打开新窗口 90 | }); 91 | }; 92 | 93 | // 创建待办小挂件窗口 94 | const addToDoToDesktop = (title, content) => { 95 | let todoWindow = new BrowserWindow({ 96 | width: 400, 97 | height: 300, 98 | transparent: true, // 透明 99 | frame: false, // 无边框窗口 100 | skipTaskbar: true, // 不在任务栏中显示 101 | webPreferences: { 102 | sandbox: false, // 取消沙箱模式 103 | backgroundThrottling: false, // 取消节流 104 | preload: path.resolve(__dirname, "./preload"), // 配置预加载脚本 105 | }, 106 | }); 107 | 108 | // 加载本地文件(测试过程中通过url访问) 109 | if (isDev) { 110 | todoWindow.loadURL("http://localhost:5173/#/desktop/customtodo"); 111 | } else { 112 | // 导航到特定路由 113 | todoWindow.loadURL( 114 | `file://${path.join( 115 | __dirname, 116 | "dist", 117 | "index.html" 118 | )}#/desktop/customtodo` 119 | ); 120 | } 121 | 122 | // 等待加载完成以后并且延迟500ms发送消息 123 | // 自行测试时间,之前我的100ms就可以,但是电脑性能慢一点需要的时间更多,我索性就直接500ms了 124 | todoWindow.webContents.on("did-finish-load", () => { 125 | setTimeout(() => { 126 | todoWindow.webContents.send("load-html-content", title, content); 127 | console.log("Message sent after delay!"); 128 | }, 500); // 延迟时间,以毫秒为单位 129 | }); 130 | todoWindow.setAlwaysOnTop(globalSettings[0].todoP); // 动态配置是否置顶 131 | 132 | // todoWindow.webContents.openDevTools(); // 打开开发者工具 133 | 134 | todoWindow.on("closed", () => { 135 | todoWindow = null; 136 | }); 137 | // 优雅的打开窗口 138 | todoWindow.once("ready-to-show", () => { 139 | todoWindow.show(); 140 | }); 141 | // hook这个右键消息,禁用窗口 142 | todoWindow.hookWindowMessage(278, function (e) { 143 | todoWindow.setEnabled(false); //窗口禁用 144 | setTimeout(() => { 145 | todoWindow.setEnabled(true); //窗口启用 146 | }, 1); 147 | return true; 148 | }); 149 | }; 150 | // 创建全屏计时器窗口 151 | const createTimerWindow = () => { 152 | let timerWindow = new BrowserWindow({ 153 | frame: false, 154 | fullscreen: true, 155 | resizable: false, // 不允许重新设置尺寸 156 | skipTaskbar: true, // 不在任务栏中显示 157 | webPreferences: { 158 | sandbox: false, // 取消沙箱模式 159 | preload: path.resolve(__dirname, "./preload"), // 预加载脚本 160 | backgroundThrottling: false, // 取消节流 161 | }, 162 | }); 163 | if (isDev) { 164 | timerWindow.loadURL("http://localhost:5173/#/fullscreen/timer"); 165 | } else { 166 | // timerWindow 167 | // .loadFile(path.join(__dirname, "dist", "index.html")) 168 | // .then(() => { 169 | // timerWindow.loadURL( 170 | // `file://${path.join( 171 | // __dirname, 172 | // "dist", 173 | // "index.html" 174 | // )}#/fullscreen/timer` 175 | // ); 176 | // }); 177 | timerWindow.loadURL( 178 | `file://${path.join(__dirname, "dist", "index.html")}#/fullscreen/timer` 179 | ); 180 | } 181 | timerWindow.on("closed", () => { 182 | timerWindow = null; 183 | }); 184 | // 优雅的打开窗口 185 | timerWindow.on("ready-to-show", () => { 186 | timerWindow.show(); 187 | }); 188 | }; 189 | // 创建定时器小挂件窗口 190 | const createTimerWidgetWindow = () => { 191 | let timerWidgetWindow = new BrowserWindow({ 192 | width: 354, 193 | height: 84, 194 | transparent: true, // 透明 195 | frame: false, // 无边框窗口 196 | skipTaskbar: true, // 不在任务栏中显示 197 | resizable: false, // 不允许重新设置尺寸 198 | webPreferences: { 199 | sandbox: false, // 取消沙箱模式 200 | preload: path.resolve(__dirname, "./preload"), // 配置预加载脚本 201 | backgroundThrottling: false, // 取消节流 202 | }, 203 | }); 204 | if (isDev) { 205 | timerWidgetWindow.loadURL("http://localhost:5173/#/desktop/timer"); 206 | } else { 207 | // timerWidgetWindow 208 | // .loadFile(path.join(__dirname, "dist", "index.html")) 209 | // .then(() => { 210 | // timerWidgetWindow.loadURL( 211 | // `file://${path.join( 212 | // __dirname, 213 | // "dist", 214 | // "index.html" 215 | // )}#/desktop/timer` 216 | // ); 217 | // }); 218 | timerWidgetWindow.loadURL( 219 | `file://${path.join(__dirname, "dist", "index.html")}#/desktop/timer` 220 | ); 221 | } 222 | timerWidgetWindow.on("closed", () => { 223 | timerWidgetWindow = null; 224 | }); 225 | // 优雅的打开窗口 226 | timerWidgetWindow.once("ready-to-show", () => { 227 | timerWidgetWindow.show(); 228 | }); 229 | timerWidgetWindow.setAlwaysOnTop(globalSettings[0].timerP); // 动态设置窗口位置 230 | // hook这个右键消息,取消右键 231 | timerWidgetWindow.hookWindowMessage(278, function (e) { 232 | timerWidgetWindow.setEnabled(false); //窗口禁用 233 | setTimeout(() => { 234 | timerWidgetWindow.setEnabled(true); //窗口启用 235 | }, 1); 236 | return true; 237 | }); 238 | }; 239 | // 创建全屏番茄钟窗口 240 | const createPomodoroWindow = () => { 241 | let pomodoroWindow = new BrowserWindow({ 242 | frame: false, 243 | fullscreen: true, 244 | resizable: false, 245 | skipTaskbar: true, // 不在任务栏中显示 246 | webPreferences: { 247 | // webSecurity: false, // 允许加载本地文件 248 | // allowFileAccess: true, // 允许访问文件 249 | sandbox: false, // 取消沙箱模式 250 | backgroundThrottling: false, // 取消节流 251 | preload: path.resolve(__dirname, "./preload"), 252 | }, 253 | }); 254 | if (isDev) { 255 | pomodoroWindow.loadURL("http://localhost:5173/#/fullscreen/pomodoro"); 256 | } else { 257 | // pomodoroWindow 258 | // .loadFile(path.join(__dirname, "dist", "index.html")) 259 | // .then(() => { 260 | // pomodoroWindow.loadURL( 261 | // `file://${path.join( 262 | // __dirname, 263 | // "dist", 264 | // "index.html" 265 | // )}#/fullscreen/pomodoro` 266 | // ); 267 | // }); 268 | pomodoroWindow.loadURL( 269 | `file://${path.join( 270 | __dirname, 271 | "dist", 272 | "index.html" 273 | )}#/fullscreen/pomodoro` 274 | ); 275 | } // 清除窗口状态 276 | pomodoroWindow.on("closed", () => { 277 | pomodoroWindow = null; 278 | }); 279 | // 优雅的打开窗口 280 | pomodoroWindow.on("ready-to-show", () => { 281 | pomodoroWindow.show(); 282 | }); 283 | }; 284 | // 创建番茄钟小挂件窗口 285 | const createPomodoroWidgetWindow = () => { 286 | let pomodoroWidgetWindow = new BrowserWindow({ 287 | width: 374, 288 | height: 104, 289 | transparent: true, // 透明 290 | frame: false, // 无边框窗口 291 | resizable: false, 292 | skipTaskbar: true, // 不在任务栏中显示 293 | webPreferences: { 294 | backgroundThrottling: false, // 取消节流 295 | sandbox: false, // 取消沙箱模式 296 | preload: path.resolve(__dirname, "./preload"), // 配置预加载脚本 297 | }, 298 | }); 299 | if (isDev) { 300 | pomodoroWidgetWindow.loadURL("http://localhost:5173/#/desktop/pomodoro"); 301 | } else { 302 | // pomodoroWidgetWindow 303 | // .loadFile(path.join(__dirname, "dist", "index.html")) 304 | // .then(() => { 305 | // pomodoroWidgetWindow.loadURL( 306 | // `file://${path.join( 307 | // __dirname, 308 | // "dist", 309 | // "index.html" 310 | // )}#/desktop/pomodoro` 311 | // ); 312 | // }); 313 | pomodoroWidgetWindow.loadURL( 314 | `file://${path.join(__dirname, "dist", "index.html")}#/desktop/pomodoro` 315 | ); 316 | } // 清除窗口状态 317 | pomodoroWidgetWindow.on("closed", () => { 318 | pomodoroWidgetWindow = null; 319 | }); 320 | pomodoroWidgetWindow.on("closed", () => { 321 | pomodoroWidgetWindow = null; 322 | }); 323 | pomodoroWidgetWindow.setAlwaysOnTop(globalSettings[0].pomodoroP); // 动态设置窗口位置 324 | 325 | // pomodoroWidgetWindow.webContents.openDevTools(); 326 | pomodoroWidgetWindow.once("ready-to-show", () => { 327 | pomodoroWidgetWindow.show(); 328 | }); 329 | // hook这个右键消息,禁用右键菜单 330 | pomodoroWidgetWindow.hookWindowMessage(278, function (e) { 331 | pomodoroWidgetWindow.setEnabled(false); //窗口禁用 332 | setTimeout(() => { 333 | pomodoroWidgetWindow.setEnabled(true); //窗口启用 334 | }, 1); 335 | return true; 336 | }); 337 | }; 338 | app.whenReady().then(() => { 339 | createWindow(); 340 | 341 | // 阻止应用进入低功耗模式 342 | const id = powerSaveBlocker.start("prevent-app-suspension"); 343 | 344 | app.on("activate", () => { 345 | if (BrowserWindow.getAllWindows().length === 0) { 346 | createWindow(); 347 | } 348 | }); 349 | }); 350 | // 适配mac 351 | app.on("window-all-closed", () => { 352 | if (process.platform !== "darwin") { 353 | app.quit(); 354 | } 355 | }); 356 | // 获取当前操作系统 357 | ipcMain.handle("get-current-os", async (event) => { 358 | return process.platform; 359 | }); 360 | // 删除待办 361 | const removeToDo = (id) => { 362 | win.webContents.send("remove-todo", id); 363 | }; 364 | // 编辑待办 365 | const editToDo = (id) => { 366 | win.webContents.send("edit-todo", id); 367 | }; 368 | let contextMenu = null; 369 | // 创建右键菜单 370 | ipcMain.on("show-context-menu", (event, type, id, title, content) => { 371 | const menuTemplate = []; 372 | // 根据类型添加不同的菜单项 373 | if (type === "customToDo") { 374 | menuTemplate.push({ 375 | label: "添加待办挂件", 376 | click: () => { 377 | addToDoToDesktop(title, content); 378 | }, 379 | }); 380 | menuTemplate.push({ 381 | label: "编辑待办", 382 | click: () => { 383 | // 直接把数据传给添加新待办那个组件过去就行了,顺便编辑成功把待办删除了 384 | editToDo(id); 385 | }, 386 | }); 387 | menuTemplate.push({ 388 | label: "删除待办", 389 | click: () => { 390 | // 发送消息告诉本地存储里面的东西可以删除了 391 | removeToDo(id); 392 | }, 393 | }); 394 | } 395 | 396 | // 创建菜单 397 | contextMenu = Menu.buildFromTemplate(menuTemplate); 398 | 399 | contextMenu.popup(BrowserWindow.fromWebContents(event.sender)); // 弹出菜单 400 | }); 401 | 402 | // 打开倒计时窗口 @params type |f:全屏|a: add widget 403 | ipcMain.on("open-timer-window", (event, type) => { 404 | if (type === "f") { 405 | createTimerWindow(); 406 | } else if (type === "a") { 407 | // 添加小挂件 408 | createTimerWidgetWindow(); 409 | } 410 | }); 411 | // 打开番茄钟窗口 @params type |f:全屏|a: add widget 412 | ipcMain.on("open-pomodoro-window", (event, type) => { 413 | if (type === "f") { 414 | createPomodoroWindow(); 415 | } else if (type === "a") { 416 | // 添加小挂件 417 | createPomodoroWidgetWindow(); 418 | } 419 | }); 420 | // 移除窗口 421 | ipcMain.on("remove-window", (event) => { 422 | const window = BrowserWindow.fromWebContents(event.sender); 423 | if (window) { 424 | window.close(); 425 | } 426 | }); 427 | 428 | // 保存文件 429 | ipcMain.handle("save-file", (event, type, originFilePath) => { 430 | const pathDir = "backgrounds"; 431 | 432 | if (type && originFilePath) { 433 | // 获取原始文件的扩展名 434 | const fileExtension = path.extname(originFilePath); 435 | const fileName = type + fileExtension; // 构造新文件名 436 | const newFilePath = path.join(process.resourcesPath, pathDir, fileName); 437 | console.log(newFilePath); 438 | // 确保backgrounds目录存在 439 | if (!fs.existsSync(path.join(process.resourcesPath, pathDir))) { 440 | fs.mkdirSync(path.join(process.resourcesPath, pathDir), { 441 | recursive: true, 442 | }); 443 | } 444 | 445 | return new Promise((resolve, reject) => { 446 | // 读取原始文件内容 447 | fs.readFile(originFilePath, (readErr, data) => { 448 | if (readErr) { 449 | reject(readErr); 450 | } else { 451 | // 将内容写入新文件 452 | fs.writeFile(newFilePath, data, (writeErr) => { 453 | if (writeErr) { 454 | reject(writeErr); 455 | } else { 456 | // 返回新文件的路径 457 | let pathName = "/" + pathDir + "/" + fileName; 458 | resolve(pathName); 459 | } 460 | }); 461 | } 462 | }); 463 | }); 464 | } 465 | return null; 466 | }); 467 | // 同步全局配置 468 | ipcMain.on("sync-else-setting", (event, settings) => { 469 | // 更新全局的值即可 470 | globalSettings = settings; 471 | }); 472 | 473 | // 全局快捷键设置 474 | ipcMain.handle("shortcut-setting", async (event, keys) => { 475 | globalShortcutSettings = keys; 476 | let isOccupied = false; // 标记下方有没有被占用的快捷键 477 | if (keys) { 478 | // 挨个检查有没有注册过的快捷键 479 | if ( 480 | !globalShortcut.register(keys.fPomodoro, () => { 481 | // 全屏番茄钟 482 | createPomodoroWindow(); 483 | }) 484 | ) { 485 | isOccupied = true; 486 | } 487 | if ( 488 | !globalShortcut.register(keys.wPomodoro, () => { 489 | // 番茄钟挂件 490 | createPomodoroWidgetWindow(); 491 | }) 492 | ) { 493 | isOccupied = true; 494 | } 495 | if ( 496 | !globalShortcut.register(keys.fTimer, () => { 497 | // 全屏计时器 498 | createTimerWindow(); 499 | }) 500 | ) { 501 | isOccupied = true; 502 | } 503 | if ( 504 | !globalShortcut.register(keys.wTimer, () => { 505 | // 计时器挂件 506 | createTimerWidgetWindow(); 507 | }) 508 | ) { 509 | isOccupied = true; 510 | } 511 | } 512 | return isOccupied; 513 | }); 514 | 515 | // 禁用所有快捷键 516 | ipcMain.on("disable-all-shortcut", () => { 517 | globalShortcut.unregisterAll(); 518 | }); 519 | 520 | // 调用原生api给用户通知 521 | // @params type |pomodoro-shortBreak|pomodoro-longBreak|pomodoro-work|timer-half|timer-full|todo 522 | ipcMain.on("notification-user", (event, type) => { 523 | // 也可以传进来title和body,考虑扩展性 524 | if (Notification.isSupported()) { 525 | let notification = null; 526 | switch (type) { 527 | case "pomodoro-shortBreak": 528 | notification = new Notification({ 529 | title: "短休息时间到", 530 | body: "时间到了,短短的休息一会儿吧", 531 | silent: true, // 静音通知 532 | }); 533 | break; 534 | case "pomodoro-longBreak": 535 | notification = new Notification({ 536 | title: "长休息时间到", 537 | body: "你太棒了,接下来是长休息时间", 538 | silent: true, // 静音通知 539 | }); 540 | break; 541 | case "pomodoro-work": 542 | notification = new Notification({ 543 | title: "专注时间到", 544 | body: "好了,开始专注吧", 545 | silent: true, // 静音通知 546 | }); 547 | break; 548 | case "timer-half": 549 | notification = new Notification({ 550 | title: "倒计时过半", 551 | body: "时间已经过去一半了", 552 | silent: true, // 静音通知 553 | }); 554 | break; 555 | case "timer-full": 556 | notification = new Notification({ 557 | title: "倒计时结束", 558 | body: "倒计时结束了", 559 | silent: true, // 静音通知 560 | }); 561 | break; 562 | case "todo": 563 | notification = new Notification({ 564 | title: "待办提醒", 565 | body: "您现在有计划的安排,提醒您一下哈", 566 | silent: true, // 静音通知 567 | }); 568 | break; 569 | } 570 | 571 | notification.show(); 572 | } 573 | }); 574 | }; 575 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iTime", 3 | "version": "1.1.0", 4 | "description": "基于electron+vue3+arco design开发的桌面效率工具", 5 | "author": "AlbertZhang", 6 | "private": true, 7 | "main": "main.js", 8 | "homepage": "./", 9 | "scripts": { 10 | "dev": "vite", 11 | "build": "vite build", 12 | "preview": "vite preview", 13 | "start": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue", 14 | "electron:build": "electron-builder" 15 | }, 16 | "build": { 17 | "appId": "com.albertzhang.itime", 18 | "productName": "iTime", 19 | "asar": true, 20 | "directories": { 21 | "output": "dist_electron" 22 | }, 23 | "nsis": { 24 | "oneClick": false, 25 | "allowToChangeInstallationDirectory": true, 26 | "deleteAppDataOnUninstall": true 27 | }, 28 | "extraFiles": [ 29 | { 30 | "from": "src/assets", 31 | "to": "resources/assets" 32 | }, 33 | { 34 | "from": "软件使用说明书.md", 35 | "to": "软件使用说明书.md" 36 | } 37 | ], 38 | "afterPack": "scripts/afterPack.js", 39 | "artifactBuildCompleted": "scripts/rename.js", 40 | "files": [ 41 | "dist/**/*", 42 | "main.js", 43 | "preload/**/*", 44 | "package.json", 45 | "scripts/**/*" 46 | ], 47 | "win": { 48 | "target": [ 49 | { 50 | "target": "nsis" 51 | }, 52 | { 53 | "target": "portable" 54 | }, 55 | { 56 | "target": "msi" 57 | } 58 | ], 59 | "icon": "dist/icons/icon.png" 60 | }, 61 | "mac": { 62 | "target": [ 63 | "dmg", 64 | "zip" 65 | ], 66 | "hardenedRuntime": true 67 | } 68 | }, 69 | "dependencies": { 70 | "@arco-design/web-vue": "^2.54.3", 71 | "@vueup/vue-quill": "^1.2.0", 72 | "dayjs": "^1.11.10", 73 | "electron-is-dev": "^3.0.1", 74 | "electron-win-state": "^1.1.22", 75 | "pinia": "^2.1.7", 76 | "pinia-plugin-persistedstate": "^3.2.1", 77 | "unplugin-vue-components": "^0.26.0", 78 | "uuid": "^9.0.1", 79 | "vue": "^3.3.11", 80 | "vue-router": "^4.2.5" 81 | }, 82 | "devDependencies": { 83 | "@vitejs/plugin-vue": "^4.5.2", 84 | "electron": "^28.1.4", 85 | "electron-builder": "^24.9.1", 86 | "unplugin-auto-import": "^0.17.3", 87 | "vite": "^5.0.10" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /preload/index.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require("electron"); 2 | 3 | contextBridge.exposeInMainWorld("electron", { 4 | // 渲染进程向主进程发消息 5 | // 展示右键菜单 6 | showContextMenu: (type, id, title, content) => 7 | ipcRenderer.send("show-context-menu", type, id, title, content), 8 | // 移除窗口,都可以使用这个方法 9 | removeWindow: () => { 10 | ipcRenderer.send("remove-window"); 11 | }, 12 | // 打开一个全屏的倒计时窗口 13 | // @params type 'f' 全屏|'a' 打开挂件 14 | openTimerWindow: (type) => { 15 | ipcRenderer.send("open-timer-window", type); 16 | }, 17 | // 打开一个番茄钟窗口 18 | // @params type 'f' 全屏|'a' 打开挂件 19 | openPomodoroWindow: (type) => { 20 | ipcRenderer.send("open-pomodoro-window", type); 21 | }, 22 | // 退出窗口 23 | // 同步系统设置 24 | syncElseSetting: (customSettings) => { 25 | ipcRenderer.send("sync-else-setting", customSettings); 26 | }, 27 | // 禁用所有快捷键 28 | disableAllShortcut: () => { 29 | ipcRenderer.send("disable-all-shortcut"); 30 | }, 31 | notificationUser: (type) => { 32 | ipcRenderer.send("notification-user", type); 33 | }, 34 | 35 | // 主进程向渲染进程发消息 36 | // 参数是一个回调函数,用的话直接使用回调函数即可。 37 | // 接收参数 38 | loadHtmlContent: (callback) => { 39 | ipcRenderer.on("load-html-content", (event, title, content) => { 40 | callback(title, content); 41 | }); 42 | }, 43 | // 删除待办 44 | removeToDo: (callback) => { 45 | ipcRenderer.on("remove-todo", (event, id) => { 46 | console.log("删除待办", id); 47 | callback(id); 48 | }); 49 | }, 50 | // 编辑待办 51 | editToDo: (callback) => { 52 | ipcRenderer.on("edit-todo", (event, id) => { 53 | callback(id); 54 | }); 55 | }, 56 | 57 | // 双向通信 58 | // 保存文件 59 | saveFile: (type, originFilePath) => 60 | ipcRenderer.invoke("save-file", type, originFilePath), 61 | // 快捷键设置 62 | shortcutSetting: (customSettingsForIpc) => 63 | ipcRenderer.invoke("shortcut-setting", customSettingsForIpc), 64 | // 获取当前系统 65 | getCurrentOS: () => ipcRenderer.invoke("get-current-os"), // 直接在预加载进程也可以访问到 66 | // 获取app路径 67 | getAppPath: () => process.resourcesPath, 68 | }); 69 | -------------------------------------------------------------------------------- /public/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/public/icons/icon.png -------------------------------------------------------------------------------- /scripts/afterPack.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs-extra"); 3 | 4 | module.exports = async (context) => { 5 | const unpackedDir = path.join(context.appOutDir, "locales"); 6 | 7 | // 删除除 zh-CN.pak 之外的所有文件 8 | const files = await fs.readdir(unpackedDir); 9 | for (const file of files) { 10 | if (!file.endsWith("zh-CN.pak")) { 11 | await fs.remove(path.join(unpackedDir, file)); 12 | } 13 | } 14 | 15 | // 删除特定的文件 16 | const filesToDelete = ["LICENSE.electron.txt", "LICENSES.chromium.html"]; 17 | 18 | for (const fileName of filesToDelete) { 19 | const filePath = path.join(context.appOutDir, fileName); 20 | if (await fs.pathExists(filePath)) { 21 | await fs.remove(filePath); 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /scripts/rename.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | // 主打就是一点体力活都不干 4 | module.exports = async function (params) { 5 | // 读取版本号 6 | const packageJson = require("../package.json"); 7 | const version = packageJson.version; 8 | let artifact = params.file; 9 | let originFile = artifact; 10 | 11 | const ext = path.extname(artifact); 12 | // 处理版本号前缀,注意空格 13 | artifact = artifact.replace(` ${version}`, `-${version}`); 14 | let newName; 15 | 16 | if (ext === ".exe" && !artifact.includes("Setup")) { 17 | // 不用安装的程序 18 | newName = artifact.replace(/\.exe$/, "-windows-no-installer.exe"); 19 | } else if (ext === ".exe") { 20 | // 常规安装包 21 | newName = artifact.replace( 22 | ` Setup-${version}.exe`, 23 | `-${version}-windows-installer.exe` 24 | ); 25 | } else { 26 | newName = artifact; 27 | } 28 | 29 | // 重命名 30 | if (newName) { 31 | fs.renameSync(originFile, newName); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 153 | 154 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/assets/defaultBGI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/defaultBGI.jpg -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | /* 全局样式 */ 2 | html, 3 | body { 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | border: 0; 9 | /* 或者灰色,#f5f5f5 */ 10 | /* background-color: #f5f5f5; */ 11 | /* background-color: rgba(0, 0, 0, 0); */ 12 | } 13 | * { 14 | margin: 0; 15 | user-select: none; 16 | } 17 | a { 18 | text-decoration: none; 19 | color: inherit; 20 | } 21 | 22 | #app { 23 | width: 100%; 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | border: 0; 28 | } 29 | html { 30 | /* 禁用滚动条,因为用的无框架窗口,默认就会有一个滚动条,所以去掉 */ 31 | overflow-y: hidden; 32 | } 33 | 34 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ 35 | ::-webkit-scrollbar { 36 | width: 2px; /*滚动条宽度*/ 37 | /*height: 2px; !*滚动条高度*!*/ 38 | } 39 | 40 | /*定义滚动条轨道 内阴影+圆角*/ 41 | ::-webkit-scrollbar-track { 42 | /*-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);*/ 43 | /*border-radius: 10px; !*滚动条的背景区域的圆角*!*/ 44 | /*background-color: red;!*滚动条的背景颜色*!*/ 45 | } 46 | 47 | /*定义滑块 内阴影+圆角*/ 48 | ::-webkit-scrollbar-thumb { 49 | border-radius: 99px; /*滚动条的圆角*/ 50 | /*-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);*/ 51 | /*background-color: green; !*滚动条的背景颜色*!*/ 52 | } 53 | 54 | /* 在这里自定义一些html全局样式,其实需要定义的也不算多,就一个列表和标题。需要自己把握间距了~~~ */ 55 | /* 原本40px */ 56 | ul { 57 | padding-inline-start: 17px; 58 | } 59 | ol { 60 | padding-inline-start: 25px; 61 | } 62 | ul li{ 63 | padding-left: 4px; 64 | } 65 | 66 | ol li{ 67 | padding-left: 2px; 68 | } 69 | /* 原本没有间距 */ 70 | li { 71 | margin: 0.8em 0; 72 | } 73 | /* marker用于定制元素的样式(比如大小颜色),before用于改变样式,比如改变标点符号,必须加!important,不然会被覆盖 */ 74 | /* 无序列表 */ 75 | .ql-container ul > li::before { 76 | content: var(--ulIcon, "●") !important; 77 | } 78 | 79 | .card ul > li::marker { 80 | content: var(--ulIcon, "●") !important; 81 | } 82 | 83 | /* h1 原本0.67em */ 84 | h1 { 85 | margin: 0.2em 0; 86 | } 87 | /* p */ 88 | p { 89 | margin: 0.5em; 90 | } 91 | -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/default/focus.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/default/focus.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/default/longBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/default/longBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/default/shortBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/default/shortBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/dingzhen/focus.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/dingzhen/focus.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/dingzhen/longBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/dingzhen/longBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/dingzhen/shortBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/dingzhen/shortBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/jiaran/focus.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/jiaran/focus.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/jiaran/longBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/jiaran/longBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/jiaran/shortBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/jiaran/shortBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/kobe/focus.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/kobe/focus.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/kobe/longBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/kobe/longBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/pomodoro/kobe/shortBreak.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/pomodoro/kobe/shortBreak.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/default/fullTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/default/fullTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/default/halfTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/default/halfTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/dingzhen/fullTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/dingzhen/fullTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/dingzhen/halfTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/dingzhen/halfTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/jiaran/fullTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/jiaran/fullTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/jiaran/halfTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/jiaran/halfTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/kobe/fullTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/kobe/fullTime.wav -------------------------------------------------------------------------------- /src/assets/voices/timer/kobe/halfTime.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/timer/kobe/halfTime.wav -------------------------------------------------------------------------------- /src/assets/voices/todos/default/remind.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/todos/default/remind.wav -------------------------------------------------------------------------------- /src/assets/voices/todos/dingzhen/remind.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/todos/dingzhen/remind.wav -------------------------------------------------------------------------------- /src/assets/voices/todos/jiaran/remind.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/todos/jiaran/remind.wav -------------------------------------------------------------------------------- /src/assets/voices/todos/kobe/remind.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AZCodingAccount/iTime/6e4d2b39cb80cd2ccd984537195228ed3c81060a/src/assets/voices/todos/kobe/remind.wav -------------------------------------------------------------------------------- /src/components/ToDoList.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 157 | 158 | 236 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import "./assets/main.css"; 2 | import { createApp } from "vue"; 3 | import App from "./App.vue"; 4 | // 引入vue-router和pinia 5 | import router from "./router"; 6 | import { createPinia } from "pinia"; 7 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; // 持久化插件 8 | // 引入图标 9 | import ArcoVueIcon from "@arco-design/web-vue/es/icon"; 10 | import "@arco-design/web-vue/dist/arco.css"; 11 | 12 | const app = createApp(App); 13 | app.use(ArcoVueIcon); 14 | 15 | // 引入并挂载 16 | app.use(createPinia().use(piniaPluginPersistedstate)).use(router).mount("#app"); 17 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | 3 | const router = createRouter({ 4 | history: createWebHashHistory(import.meta.env.BASE_URL), // 主要是hash模式 5 | routes: [ 6 | { 7 | // 软件主界面 8 | path: "/", 9 | component: () => import("@/views/layout/LayOutContainer.vue"), // 使用箭头函数可以懒加载 10 | redirect: "/plain/todo", 11 | children: [ 12 | { 13 | path: "/plain/todo", 14 | component: () => import("@/views/pc/PlainToDo.vue"), 15 | }, 16 | { 17 | path: "/add/customtodo", 18 | component: () => import("@/views/pc/AddCustomToDo.vue"), 19 | }, 20 | { 21 | path: "/custom/todo", 22 | component: () => import("@/views/pc/CustomToDo.vue"), 23 | }, 24 | { 25 | path: "/settings", 26 | redirect: "/settings/global", 27 | component: () => import("@/views/layout/LayOutSettings.vue"), 28 | children: [ 29 | { 30 | path: "/settings/global", 31 | component: () => import("@/views/settings/GlobalSettings.vue"), 32 | }, 33 | { 34 | path: "/settings/todo", 35 | component: () => import("@/views/settings/ToDoSettings.vue"), 36 | }, 37 | { 38 | path: "/settings/clock", 39 | component: () => import("@/views/settings/ClockSettings.vue"), 40 | }, 41 | { 42 | path: "/settings/appearance", 43 | component: () => 44 | import("@/views/settings/AppearanceSettings.vue"), 45 | }, 46 | ], 47 | }, 48 | { path: "/about", component: () => import("@/views/pc/About.vue") }, 49 | { 50 | path: "/timer", 51 | component: () => import("@/views/pc/Timer.vue"), 52 | }, 53 | { 54 | path: "/pomodoro", 55 | component: () => import("@/views/pc/Pomodoro.vue"), 56 | }, 57 | ], 58 | }, 59 | // 桌面用的三个路径 60 | { 61 | path: "/desktop/customtodo", 62 | component: () => import("@/views/desktop/CustomToDo.vue"), 63 | }, 64 | { 65 | path: "/desktop/timer", 66 | component: () => import("@/views/desktop/Timer.vue"), 67 | }, 68 | { 69 | path: "/desktop/pomodoro", 70 | component: () => import("@/views/desktop/Pomodoro.vue"), 71 | }, 72 | // 全屏跳转的路径 73 | { 74 | path: "/fullscreen/timer", 75 | component: () => import("@/views/fullscreen/Timer.vue"), 76 | }, 77 | { 78 | path: "/fullscreen/pomodoro", 79 | component: () => import("@/views/fullscreen/Pomodoro.vue"), 80 | }, 81 | ], 82 | }); 83 | 84 | export default router; 85 | -------------------------------------------------------------------------------- /src/stores/CustomSettings.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref, watchEffect } from "vue"; 3 | // 定义待办列表 4 | export const useCustomSettingsStore = defineStore( 5 | "customSettings", 6 | () => { 7 | const customSettings = ref({}); // 自定义设置,必须暴露出去,不然持久化不生效 8 | // 有自定义快捷键、自定义番茄钟设置,自定义字体设置 9 | // 默认番茄钟设置 10 | customSettings.value["pomodoroSettings"] = { 11 | duration: 25, 12 | shortBreakDuration: 5, 13 | longBreakDuration: 15, 14 | longBreakInterval: 4, 15 | }; 16 | // // 测试设置 17 | // customSettings.value["pomodoroSettings"] = { 18 | // duration: 0.1, 19 | // shortBreakDuration: 0.1, 20 | // longBreakDuration: 0.1, 21 | // longBreakInterval: 2, 22 | // }; 23 | 24 | const currentPath = window.electron.getAppPath(); 25 | 26 | // customSettings.value["f-pomodoro-bgi"] = 27 | // "file:///C:/Users/Albert%20han/Desktop/easyToDo/dist_electron/win-unpacked/resources/assets/defaultBGI.jpg"; 28 | // 对windows做处理 29 | // "C:\\Users\\Albert han\\Desktop\\easyToDo\\dist_electron\\win-unpacked\\resources/assets/defaultBGI.jpg" 30 | // 首先加上file前缀 file:/// 如果包含\\,替换成/ 如果包含空格、替换成%20 31 | const originValue = `${currentPath}/assets/defaultBGI.jpg`; 32 | const formattedValue = originValue 33 | .replace(/\\/g, "/") 34 | .replace(/\s/g, "%20"); 35 | const targetValue = `file:///${formattedValue}`; 36 | // 默认番茄钟背景图设置 37 | customSettings.value["f-pomodoro-bgi"] = targetValue; 38 | customSettings.value["w-pomodoro-bgi"] = "none"; 39 | 40 | // 待办图标样式 41 | customSettings.value["todo-icons"] = { 42 | olIcon: "1.", 43 | ulIcon: "●", 44 | }; 45 | 46 | // 默认快捷键设置 47 | customSettings.value["shortcutKeys"] = { 48 | fPomodoro: "Control+Alt+0", 49 | wPomodoro: "Control+Alt+9", 50 | fTimer: "Control+Alt+8", 51 | wTimer: "Control+Alt+7", 52 | }; 53 | let defaultShortcutKeys = ref({ 54 | fPomodoro: "Control+Alt+0", 55 | wPomodoro: "Control+Alt+9", 56 | fTimer: "Control+Alt+8", 57 | wTimer: "Control+Alt+7", 58 | }); 59 | 60 | // 默认位置设置,是否在顶层 61 | customSettings.value["position"] = { 62 | pomodoroP: true, 63 | timerP: true, 64 | todoP: false, 65 | }; 66 | 67 | // 提示语音设置 68 | customSettings.value["voice"] = { 69 | pomodoroV: "default", 70 | timerV: "default", 71 | todoV: "default", 72 | isClosedV: false, 73 | }; 74 | // 防止用户快捷键输入空值 75 | watchEffect(() => { 76 | if (customSettings.value["shortcutKeys"]["fPomodoro"] === "") { 77 | customSettings.value["shortcutKeys"]["fPomodoro"] = "Control+Alt+0"; 78 | } 79 | if (customSettings.value["shortcutKeys"]["wPomodoro"] === "") { 80 | customSettings.value["shortcutKeys"]["wPomodoro"] = "Control+Alt+1"; 81 | } 82 | if (customSettings.value["shortcutKeys"]["fTimer"] === "") { 83 | customSettings.value["shortcutKeys"]["fTimer"] = "Control+Alt+2"; 84 | } 85 | if (customSettings.value["shortcutKeys"]["wTimer"] === "") { 86 | customSettings.value["shortcutKeys"]["wTimer"] = "Control+Alt+3"; 87 | } 88 | }); 89 | // 重置番茄钟背景图 90 | const resetPomodoroBGI = () => { 91 | const originValue = `${currentPath}/assets/defaultBGI.jpg`; 92 | const formattedValue = originValue 93 | .replace(/\\/g, "/") 94 | .replace(/\s/g, "%20"); 95 | const targetValue = `file:///${formattedValue}`; 96 | customSettings.value["f-pomodoro-bgi"] = targetValue; 97 | customSettings.value["w-pomodoro-bgi"] = "none"; 98 | }; 99 | // 重置待办图标 100 | const resetForm = () => { 101 | customSettings.value["todo-icons"] = { 102 | olIcon: "1.", 103 | ulIcon: "●", 104 | }; 105 | }; 106 | // 重置快捷键设置 107 | const resetShortcutKeys = () => { 108 | console.log({ ...defaultShortcutKeys.value }); 109 | customSettings.value["shortcutKeys"] = { ...defaultShortcutKeys.value }; 110 | }; 111 | // 重置位置设置 112 | const resetPositionSettings = () => { 113 | customSettings.value["position"] = { 114 | pomodoroP: true, 115 | timerP: true, 116 | todoP: false, 117 | }; 118 | }; 119 | // 重置语音提示设置 120 | const resetVoiceSettings = () => { 121 | customSettings.value["voice"] = { 122 | pomodoroV: "default", 123 | timerV: "default", 124 | todoV: "default", 125 | isClosedV: false, 126 | }; 127 | }; 128 | return { 129 | customSettings, 130 | defaultShortcutKeys, 131 | resetPomodoroBGI, 132 | resetForm, 133 | resetShortcutKeys, 134 | resetPositionSettings, 135 | resetVoiceSettings, 136 | }; 137 | }, 138 | { persist: true } // 持久化 139 | ); 140 | -------------------------------------------------------------------------------- /src/stores/CustomToDoStore.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref } from "vue"; 3 | // 定义待办列表 4 | export const useCustomToDoStore = defineStore( 5 | "customTodoList", 6 | () => { 7 | const isFirst = ref(true); // 用来辅助实现单例模式 8 | const customTodoList = ref([]); // 待办列表,必须暴露出去,不然持久化不生效 9 | 10 | // 定义代办标题和内容(这个是自定义) 11 | // let title = ref(""); // 标题 12 | // let content = ref(""); // 内容 13 | // let createTime = ref(null); // 创建时间 14 | // 添加待办 15 | const addToDo = (id, title, content, createTime) => { 16 | customTodoList.value.unshift({ id, title, content, createTime }); 17 | }; 18 | // 获取待办列表 19 | const getToDoList = () => { 20 | return customTodoList.value; 21 | }; 22 | // 删除待办 23 | const removeToDo = (id) => { 24 | console.log("删除待办", id, customTodoList.value); 25 | customTodoList.value = customTodoList.value.filter( 26 | (item) => item.id !== id 27 | ); 28 | console.log("删除后的列表", customTodoList.value); 29 | }; 30 | 31 | return { isFirst, customTodoList, addToDo, getToDoList, removeToDo }; 32 | }, 33 | { 34 | persist: { 35 | paths: ["customTodoList"], 36 | }, 37 | } // 持久化 38 | ); 39 | -------------------------------------------------------------------------------- /src/stores/HasVisitedBefore.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref } from "vue"; 3 | // 记录用户访问过哪些页面 4 | export const useHasVisitedBeforeStore = defineStore( 5 | "hasVisitedBefore", 6 | () => { 7 | const appToDo = ref(true); 8 | const appPomodoro = ref(true); 9 | const appTimer = ref(true); 10 | const fullScreenPomodoro = ref(true); 11 | const fullScreenTimer = ref(true); 12 | const widgetPomodoro = ref(true); 13 | const widgetTimer = ref(true); 14 | return { 15 | appToDo, 16 | appPomodoro, 17 | appTimer, 18 | fullScreenPomodoro, 19 | fullScreenTimer, 20 | widgetPomodoro, 21 | widgetTimer, 22 | }; 23 | }, 24 | { persist: true } // 持久化,必须持久化,不然那4个创建新窗口的页面没办法共享pinia值,除非你存储到sqlLite这种数据库里面 25 | ); 26 | -------------------------------------------------------------------------------- /src/stores/ToDo.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref } from "vue"; 3 | // 定义待办列表 4 | export const useToDoStore = defineStore( 5 | "todoList", 6 | () => { 7 | const todoList = ref([]); // 待办列表,必须暴露出去,不然持久化不生效 8 | 9 | // 添加待办 10 | const addToDo = (id, content, tags, remindTime) => { 11 | let isFinish = false; // 待办 12 | todoList.value.unshift({ id, content, tags, isFinish, remindTime }); 13 | }; 14 | // 获取待办列表 15 | const getToDoList = () => { 16 | return todoList.value; 17 | }; 18 | // 删除待办 19 | const deleteToDo = (id) => { 20 | todoList.value = todoList.value.filter((todo) => { 21 | if (todo.id === id) { 22 | return false; 23 | } 24 | return true; 25 | }); 26 | }; 27 | 28 | return { todoList, addToDo, getToDoList, deleteToDo }; 29 | }, 30 | { persist: true } // 持久化 31 | ); 32 | -------------------------------------------------------------------------------- /src/views/desktop/CustomToDo.vue: -------------------------------------------------------------------------------- 1 | 23 | 87 | 138 | -------------------------------------------------------------------------------- /src/views/desktop/Pomodoro.vue: -------------------------------------------------------------------------------- 1 | 164 | 256 | 257 | 323 | -------------------------------------------------------------------------------- /src/views/desktop/Timer.vue: -------------------------------------------------------------------------------- 1 | 151 | 219 | 220 | 256 | -------------------------------------------------------------------------------- /src/views/fullscreen/Pomodoro.vue: -------------------------------------------------------------------------------- 1 | 164 | 257 | 258 | 333 | -------------------------------------------------------------------------------- /src/views/fullscreen/Timer.vue: -------------------------------------------------------------------------------- 1 | 149 | 220 | 221 | 266 | -------------------------------------------------------------------------------- /src/views/layout/LayOutContainer.vue: -------------------------------------------------------------------------------- 1 | 14 | 229 | 262 | -------------------------------------------------------------------------------- /src/views/layout/LayOutSettings.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 36 | -------------------------------------------------------------------------------- /src/views/pc/About.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 237 | 238 | 254 | -------------------------------------------------------------------------------- /src/views/pc/AddCustomToDo.vue: -------------------------------------------------------------------------------- 1 | 170 | 191 | 202 | -------------------------------------------------------------------------------- /src/views/pc/CustomToDo.vue: -------------------------------------------------------------------------------- 1 | 55 | 83 | 116 | -------------------------------------------------------------------------------- /src/views/pc/PlainToDo.vue: -------------------------------------------------------------------------------- 1 | 80 | 131 | 141 | -------------------------------------------------------------------------------- /src/views/pc/Pomodoro.vue: -------------------------------------------------------------------------------- 1 | 190 | 284 | 285 | 360 | -------------------------------------------------------------------------------- /src/views/pc/Timer.vue: -------------------------------------------------------------------------------- 1 | 158 | 229 | 230 | 273 | -------------------------------------------------------------------------------- /src/views/settings/AppearanceSettings.vue: -------------------------------------------------------------------------------- 1 | 3 | 26 | 36 | -------------------------------------------------------------------------------- /src/views/settings/ClockSettings.vue: -------------------------------------------------------------------------------- 1 | 131 | 314 | 328 | -------------------------------------------------------------------------------- /src/views/settings/GlobalSettings.vue: -------------------------------------------------------------------------------- 1 | 160 | 269 | 270 | -------------------------------------------------------------------------------- /src/views/settings/ToDoSettings.vue: -------------------------------------------------------------------------------- 1 | 33 | 87 | 93 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | import AutoImport from "unplugin-auto-import/vite"; 6 | import Components from "unplugin-vue-components/vite"; 7 | import { ArcoResolver } from "unplugin-vue-components/resolvers"; 8 | 9 | // import path from "path"; // 新增 10 | 11 | // https://vitejs.dev/config/ 12 | // 配置自动按需引入 13 | export default defineConfig({ 14 | base: "./", // 设置为相对路径 15 | plugins: [ 16 | vue(), 17 | AutoImport({ 18 | resolvers: [ArcoResolver()], 19 | }), 20 | Components({ 21 | resolvers: [ 22 | ArcoResolver({ 23 | sideEffect: true, 24 | }), 25 | ], 26 | }), 27 | ], 28 | resolve: { 29 | alias: { 30 | "@": fileURLToPath(new URL("./src", import.meta.url)), 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /软件使用说明书.md: -------------------------------------------------------------------------------- 1 | # iTime软件使用说明书📄 2 | 3 | ## 软件介绍📘 4 | 5 | ​ iTime🕰️ 是一款基于vue3+arco design+electron开发的桌面端效率应用,slogan是`让每一秒都刚刚好`🌟,意为提高您的效率,帮助您充分利用自己的每一秒。 6 | 7 | ## 功能介绍🔧 8 | 9 | ​ 目前iTime有四个模块,分别是待办📝、倒计时⏳、番茄钟🍅、自定义设置⚙️,与这四个功能相辅相成的有**屏幕挂件**🖥️、**提示语音**🔊。下面将详细介绍这四个功能,**每个小功能之前都会有一个演示的gif动图** 10 | 11 | ### 待办功能📝 12 | 13 | 待办功能分为标准待办和自定义待办部分: 14 | 15 | 标准待办动图: 16 | 17 | 标准待办演示 18 | 19 | - 标准待办中,您每新增一个代办需要填写待办的**内容**✍️、**标签(可选)**🏷️、**提醒时间**⏰这三个部分,**时间**和**内容**是必需的,以便程序知道何时提醒您。 20 | 21 | 💡为了ui展示的合理性,建议您每个待办不要超过**5个**标签 22 | 23 | 自定义待办动图: 24 | 25 | 自定义待办演示 26 | 27 | - 自定义待办中,您可以把待办当做一个便签📌,在富文本编辑器编辑好以后保存,去预览界面,右键发送待办挂件即可成为屏幕挂件。 28 | 29 | ### 倒计时功能⏳ 30 | 31 | 倒计时器演示 32 | 33 | 倒计时器有三种使用方式: 34 | 35 | - 在软件主界面启动倒计时器 36 | - 左键双击全屏倒计时器 37 | - 右键双击发送倒计时器成为挂件 38 | 39 | ❗倒计时器的范围是**0.1-999**分钟。 40 | 41 | ❗您可以在获取焦点后按**R**键重置倒计时器 42 | 43 | ### 番茄钟功能🍅 44 | 45 | 番茄钟演示 46 | 47 | ℹ️:上述gif番茄钟时间参数是为了方便开发时设置的参数、实际并不暴露给定范围外的接口 48 | 49 | 与倒计时器类似,也支持上述三种方式使用番茄钟。番茄钟了解链接:[番茄钟如何提高效率](https://www.zhihu.com/question/330121574) 50 | 51 | - 在软件主界面启动倒计时器 52 | - 左键双击全屏倒计时器 53 | - 右键双击发送倒计时器成为挂件 54 | 55 | ℹ️ 番茄钟可以自定义`长休息`🌴、`短休息`☕、`专注时间`⏲️、`轮数`🔄4个选项,稍后会介绍。 56 | 57 | ### 自定义设置⚙️ 58 | 59 | 我们暴露出了一些接口供用户自定义,包含**全局快捷键设置**⌨️、**挂件位置设置**📍、**提示语音设置**🔊、**待办列表项设置**🗃️、**番茄钟参数设置**🕒、**番茄钟与计时器背景图设置**🖼️,下面详细介绍: 60 | 61 | 全局设置演示 62 | 63 | - 全局快捷键设置⌨️。自定义快捷键全屏显示番茄钟、召唤番茄钟挂件、全屏显示计时器、召唤计时器挂件4个功能。 64 | - 挂件是否在顶层设置🔝。如果选择顶层,那么挂件将一直悬浮在屏幕上,三个挂件可以设置是否在顶层。 65 | - 提示语音设置🔊。在番茄钟、计时器、待办达到一定条件后软件会播放提示语音,您可以自定义提示语音的AI角色和是否开启提示语音。 66 | 67 | 待办设置演示 68 | 69 | - 待办列表项设置🗃️。您可以自定义有序列表和无序列表前面显示的图标。 70 | 71 | 时钟设置演示 72 | 73 | 番茄钟参数设置🕒。如前所述、您可以在一定范围内自定义番茄钟的4个参数。 74 | 75 | - 番茄钟和计时器背景图设置🖼️。默认情况下桌面挂件的样式是半透明的灰色。您可以自定义全屏的背景图和挂件的背景图。 76 | 77 | ## 快捷键介绍⌨️ 78 | 79 | ### 待办📝 80 | 81 | 标准待办中**Ctrl/Command+Alt+Enter**调出添加待办窗口。 82 | 83 | 自定义代办中富文本编辑器选项: 84 | 85 | - **\"-"+空格**➡️ 无序列表 86 | - **1+“.“**➡️ 有序列表 87 | - **Ctrl/Command+B**➡️ 将选中文本加粗 88 | - **Ctrl/Command+I**➡️ 将选中文本斜体 89 | - **Ctrl/Command+U**➡️ 将选中文本加下划线 90 | 91 | 自定义代办中操作选项: 92 | 93 | - **Ctrl/Command+Alt+Enter**调出确认窗口 94 | - 标题窗口+**Enter**即可添加 95 | 96 | ### 定时器&番茄钟⏲️ 97 | 98 | **全局快捷键⌨️** 99 | 100 | - **Ctrl/Command+Alt+0**全屏番茄钟 101 | - **Ctrl/Command+Alt+9**召唤番茄钟挂件 102 | - **Ctrl/Command+Alt+8**全屏计时器 103 | - **Ctrl/Command+Alt+7**召唤计时器挂件 104 | 105 | **组件快捷键⚡** 106 | 107 | - **F**全屏 108 | - 全屏模式按**F**退出 109 | - **A**添加挂件 110 | - 挂件模式下按**E**删除挂件 111 | 112 | **鼠标设置🖱️** 113 | 114 | - **左键双击背景**全屏 115 | - **右键双击背景**添加挂件 116 | - 全屏模式下**左键双击背景**退出 117 | - 挂件模式下**左键双击时间**删除挂件 118 | 119 | ## 语音内容🔊 120 | 121 | 我们采用内置音频的方式来提醒,软件不需连接互联网 122 | 123 | | 事件 | 语音内容 | 124 | | ------------ | -------------------------------- | 125 | | 番茄钟短休息 | 时间到了,短短的休息一会儿吧 | 126 | | 番茄钟长休息 | 你太棒了,接下来是长休息时间 | 127 | | 番茄钟专注 | 好了,开始专注吧 | 128 | | 倒计时过半 | 时间已经过去一半了 | 129 | | 倒计时结束 | 倒计时结束了 | 130 | | 待办提醒 | 您现在有计划的安排,提醒您一下哈 | 131 | 132 | 这些提示的英文版本 133 | 134 | | 事件 | 语音内容 | 135 | | ------------ | ------------------------------------------ | 136 | | 番茄钟短休息 | Take a short break, time's up. | 137 | | 番茄钟长休息 | You're doing great, time for a long break. | 138 | | 番茄钟专注 | Alright, time to focus. | 139 | | 倒计时过半 | Half the time has passed. | 140 | | 倒计时结束 | The countdown is over. | 141 | | 待办提醒 | You have scheduled tasks, just a reminder. | 142 | 143 | --------------------------------------------------------------------------------