前端时间我的一个朋友为了快速熟悉 Vue3 开发, 特意使用 electron vue3 ts 开发了一个桌面端应用, 并在
github
上开源了, 接下来我就带大家一起了解一下这个项目, 在文章末尾我会放github
的地址, 大家如果想学习vue3 ts electron 开发, 可以本地clone
学习参考一下.
image.png
技术栈
以上是我们看到的便签软件使用界面, 整体技术选型如下:
- 脚手架 vue-cli
- 前端框架和语言规范 vue typescript
- 桌面端开发框架 electron
- electron支持插件 vue-cli-plugin-electron-builder
- 数据库 NeDB | 一款NoSQL嵌入式数据库
- 代码格式规范 eslint
接下来我们来看看具体的演示效果:
具体实现过程, 内容很长, 建议先点赞收藏, 再一步步学习, 接下来会就该项目的每一个重点细节做详细的分析.
开发思路
- 页面:
- 列表页
index.vue
头部、搜索、内容部分,只能有一个列表页存在 - 设置页
setting.vue
设置内容和软件信息,和列表页一样只能有一个存在 - 编辑页
editor.vue
icons功能和背景颜色功能,可以多个编辑页同时存在
- 列表页
- 动效:
- 打开动效,有一个放大、透明度的过渡,放不了动图这里暂时不演示了。
- 标题过渡效果
- 切换
index
和setting
时头部不变,内容过渡
- 数据储存:数据的创建和更新都在编辑页
editor.vue
进行,这个过程中在储存进nedb
之后才通信列表页index.vue
更新内容,考虑到性能问题,这里使用了防抖
防止连续性的更新而导致卡顿(不过貌似没有这个必要。。也算是一个小功能吧,然后可以设置这个更新速度) - 错误采集:采集在使用中的错误并弹窗提示
- 编辑显示:
document
暴露execCommand
方法,该方法允许运行命令来操纵可编辑内容区域的元素。
在开发的时候还遇到过好多坑,这些都是在electron
环境中才有,比如
@input
触发2次,加上v-model
触发3次。包括创建一个新的electron框架也是这样,别人电脑上不会出现这个问题,猜测是electron缓存
问题- vue3碰到
空属性
报错时无限报错,在普通浏览器(edge和chrome)是正常一次 - 组件无法正常渲染不报错,只在控制台报异常
- 打包后由于
electron
的缓存导致打开需要10秒左右,清除c盘软件缓存后正常
其他的不记得了。。
这里暂时不提供vue3和electron介绍,有需要的可以先看看社区其他的有关文章或者后期再详细专门提供。软件命名为i-notes
。
vue3中文教程 vue3js.cn/docs/zh/gui…[1] electron教程 www.electronjs.org/[2] typescript教程 www.typescriptlang.org/[3]
electron-vue
里面的包环境太低了,所以是手动配置electron vue3(虽然说是手动。。其实就两个步骤)
目录结构
代码语言:javascript复制electron-vue-notes
├── public
│ ├── css
│ ├── font
│ └── index.html
├── src
│ ├── assets
│ │ └── empty-content.svg
│ ├── components
│ │ ├── message
│ │ ├── rightClick
│ │ ├── editor.vue
│ │ ├── header.vue
│ │ ├── input.vue
│ │ ├── messageBox.vue
│ │ ├── switch.vue
│ │ └── tick.vue
│ ├── config
│ │ ├── browser.options.ts
│ │ ├── classNames.options.ts
│ │ ├── editorIcons.options.ts
│ │ ├── index.ts
│ │ └── shortcuts.keys.ts
│ ├── inotedb
│ │ └── index.ts
│ ├── less
│ │ └── index.less
│ ├── router
│ │ └── index.ts
│ ├── script
│ │ └── deleteBuild.js
│ ├── store
│ │ ├── exeConfig.state.ts
│ │ └── index.ts
│ ├── utils
│ │ ├── errorLog.ts
│ │ └── index.ts
│ ├── views
│ │ ├── editor.vue
│ │ ├── index.vue
│ │ ├── main.vue
│ │ └── setting.vue
│ ├── App.vue
│ ├── background.ts
│ ├── main.ts
│ └── shims-vue.d.ts
├── .browserslistrc
├── .eslintrc.js
├── .prettierrc.js
├── babel.config.js
├── inoteError.log
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
├── vue.config.js
└── yarn.lock
使用脚手架搭建vue3环境
没有脚手架的可以先安装脚手架
代码语言:javascript复制npm install -g @vue/cli
创建vue3项目
代码语言:javascript复制vue create electron-vue-notes
# 后续
? Please pick a preset: (Use arrow keys)
Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
> Manually select features
# 手动选择配置
# 后续所有配置
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Router, CSS Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save, Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n
创建完之后的目录是这样的
代码语言:javascript复制electron-vue-notes
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ ├── router
│ │ └── index.ts
│ ├── views
│ │ ├── About.vue
│ │ └── Home.vue
│ ├── App.vue
│ ├── main.ts
│ └── shims-vue.d.ts
├── .browserslistrc
├── .eslintrc.js
├── babel.config.js
├── package.json
├── README.md
├── tsconfig.json
└── yarn.lock
安装electron的依赖
代码语言:javascript复制# yarn
yarn add vue-cli-plugin-electron-builder electron
# npm 或 cnpm
npm i vue-cli-plugin-electron-builder electron
安装完之后完善一些配置,比如别名
、eslint
、prettier
等等基础配置,还有一些颜色
、icons
等等具体可以看下面
项目的一些基础配置
eslint
使用eslint主要是规范代码风格,不推荐tslint是因为tslint已经不更新了,tslint也推荐使用eslint 安装eslint
代码语言:javascript复制npm i eslint -g
进入项目之后初始化eslint
代码语言:javascript复制eslint --init
# 后续配置
? How would you like to use ESLint? To check syntax and find problems
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? Vue.js
? Does your project use TypeScript? Yes
? Where does your code run? Browser, Node
? What format do you want your config file to be in? JavaScript
The config that you've selected requires the following dependencies:
eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
? Would you like to install them now with npm? (Y/n) y
修改eslint配置,·.eslintrc.js
,规则rules
可以根据自己的喜欢配置 eslint.org/docs/user-g…[4]
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'@vue/prettier/@typescript-eslint'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
quotes: [1, 'single'],
semi: 1,
'@typescript-eslint/camelcase': 0,
'@typescript-eslint/no-explicit-any': 0,
'no-irregular-whitespace': 2,
'no-case-declarations': 0,
'no-undef': 0,
'eol-last': 1,
'block-scoped-var': 2,
'comma-dangle': [2, 'never'],
'no-dupe-keys': 2,
'no-empty': 1,
'no-extra-semi': 2,
'no-multiple-empty-lines': [1, { max: 1, maxEOF: 1 }],
'no-trailing-spaces': 1,
'semi-spacing': [2, { before: false, after: true }],
'no-unreachable': 1,
'space-infix-ops': 1,
'spaced-comment': 1,
'no-var': 2,
'no-multi-spaces': 2,
'comma-spacing': 1
}
};
prettier
在根目录增加.prettierrc.js
配置,根据自己的喜好进行配置,单行多少个字符、单引号、分号、逗号结尾等等
module.exports = {
printWidth: 120,
singleQuote: true,
semi: true,
trailingComma: 'none'
};
tsconfig.json
如果这里没有配置识别@/
路径的话,在项目中使用会报错
"paths": {
"@/*": [
"src/*"
]
}
package.json
代码语言:javascript复制"author": "heiyehk",
"description": "I便笺个人开发者heiyehk独立开发,在Windows中更方便的记录文字。",
"main": "background.js",
"scripts": {
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve"
}
配置入口文件background.ts
因为需要做一些打开和关闭的动效,因此我们需要配置electron
为frame无边框
和透明transparent
的属性
/* eslint-disable @typescript-eslint/no-empty-function */
'use strict';
import { app, protocol, BrowserWindow, globalShortcut } from 'electron';
import {
createProtocol
// installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib';
const isDevelopment = process.env.NODE_ENV !== 'production';
let win: BrowserWindow | null;
protocol.registerSchemesAsPrivileged([
{
scheme: 'app',
privileges: {
secure: true,
standard: true
}
}
]);
function createWindow() {
win = new BrowserWindow({
frame: false, // 无边框
hasShadow: false,
transparent: true, // 透明
width: 950,
height: 600,
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true
}
});
if (process.env.WEBPACK_DEV_SERVER_URL) {
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
if (!process.env.IS_TEST) win.webContents.openDevTools();
} else {
createProtocol('app');
win.loadURL('http://localhost:8080');
}
win.on('closed', () => {
win = null;
});
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (win === null) {
createWindow();
}
});
app.on('ready', async () => {
// 这里注释掉是因为会安装tools插件,需要屏蔽掉,有能力的话可以打开注释
// if (isDevelopment && !process.env.IS_TEST) {
// try {
// await installVueDevtools();
// } catch (e) {
// console.error('Vue Devtools failed to install:', e.toString());
// }
// }
createWindow();
});
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit();
}
});
} else {
process.on('SIGTERM', () => {
app.quit();
});
}
}
启动
代码语言:javascript复制yarn electron:serve
到这里配置就算是成功搭建好这个窗口了,但是还有一些其他细节需要进行配置,比如electron打包
配置,模块化
的配置等等
常规配置
这里配置一些常用的开发内容和一些轮子代码, 大家可以参考 reset.csss
和 common.css
这两个文件.
config
这个对应项目中的config文件夹
代码语言:javascript复制config
├── browser.options.ts # 窗口的配置
├── classNames.options.ts # 样式名的配置,背景样式都通过这个文件渲染
├── editorIcons.options.ts # 编辑页面的一些editor图标
├── index.ts # 导出
└── shortcuts.keys.ts # 禁用的一些快捷键,electron是基于chromium浏览器,所以也存在一些浏览器快捷键比如F5
browser.options
这个文件的主要作用就是配置主窗口和编辑窗口区分开发正式的配置,宽高等等,以及要显示的主页面
代码语言:javascript复制/**
* 软件数据和配置
* C:Users{用户名}AppDataRoaming
* 共享
* C:ProgramDataIntelShaderCachei-notes{xx}
* 快捷方式
* C:Users{用户名}AppDataRoamingMicrosoftWindowsRecent
* 电脑自动创建缓存
* C:WindowsPrefetchI-NOTES.EXE{xx}
*/
/** */
const globalEnv = process.env.NODE_ENV;
const devWid = globalEnv === 'development' ? 950 : 0;
const devHei = globalEnv === 'development' ? 600 : 0;
// 底部icon: 40*40
const editorWindowOptions = {
width: devWid || 290,
height: devHei || 350,
minWidth: 250
};
/**
* BrowserWindow的配置项
* @param type 单独给编辑窗口的配置
*/
const browserWindowOption = (type?: 'editor'): Electron.BrowserWindowConstructorOptions => {
const commonOptions = {
minHeight: 48,
frame: false,
hasShadow: true,
transparent: true,
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true
}
};
if (!type) {
return {
width: devWid || 350,
height: devHei || 600,
minWidth: 320,
...commonOptions
};
}
return {
...editorWindowOptions,
...commonOptions
};
};
/**
* 开发环境: http://localhost:8080
* 正式环境: file://${__dirname}/index.html
*/
const winURL = globalEnv === 'development' ? 'http://localhost:8080' : `file://${__dirname}/index.html`;
export { browserWindowOption, winURL };
router
增加meta
中的title
属性,显示在软件上方头部
import { createRouter, createWebHashHistory } from 'vue-router';
import { RouteRecordRaw } from 'vue-router';
import main from '../views/main.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'main',
component: main,
children: [
{
path: '/',
name: 'index',
component: () => import('../views/index.vue'),
meta: {
title: 'I便笺'
}
},
{
path: '/editor',
name: 'editor',
component: () => import('../views/editor.vue'),
meta: {
title: ''
}
},
{
path: '/setting',
name: 'setting',
component: () => import('../views/setting.vue'),
meta: {
title: '设置'
}
}
]
}
];
const router = createRouter({
history: createWebHashHistory(process.env.BASE_URL),
routes
});
export default router;
main.vue
main.vue
文件主要是作为一个整体框架,考虑到页面切换时候的动效,分为头部和主体部分,头部作为一个单独的组件处理,内容区域使用router-view
渲染。html部分,这里和vue2.x有点区别的是,在vue2.x中可以直接
// bad
<transition name="fade">
<keep-alive>
<router-view />
</keep-alive>
</transition>
上面的这种写法在vue3中会在控制台报异常,记不住写法的可以看看控制台