前端实战:electron+vue3+ts开发桌面端便签应用

2021-09-03 14:35:28 浏览数 (1)

前端时间我的一个朋友为了快速熟悉 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

接下来我们来看看具体的演示效果:

具体实现过程, 内容很长, 建议先点赞收藏, 再一步步学习, 接下来会就该项目的每一个重点细节做详细的分析.

开发思路

  1. 页面:
    • 列表页index.vue 头部、搜索、内容部分,只能有一个列表页存在
    • 设置页setting.vue 设置内容和软件信息,和列表页一样只能有一个存在
    • 编辑页 editor.vue icons功能和背景颜色功能,可以多个编辑页同时存在
  2. 动效:
    • 打开动效,有一个放大、透明度的过渡,放不了动图这里暂时不演示了。
    • 标题过渡效果
    • 切换indexsetting时头部不变,内容过渡
  3. 数据储存:数据的创建和更新都在编辑页editor.vue进行,这个过程中在储存进nedb之后才通信列表页index.vue更新内容,考虑到性能问题,这里使用了防抖防止连续性的更新而导致卡顿(不过貌似没有这个必要。。也算是一个小功能吧,然后可以设置这个更新速度)
  4. 错误采集:采集在使用中的错误并弹窗提示
  5. 编辑显示:document暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。

在开发的时候还遇到过好多坑,这些都是在electron环境中才有,比如

  1. @input触发2次,加上v-model触发3次。包括创建一个新的electron框架也是这样,别人电脑上不会出现这个问题,猜测是electron缓存问题
  2. vue3碰到空属性报错时无限报错,在普通浏览器(edge和chrome)是正常一次
  3. 组件无法正常渲染不报错,只在控制台报异常
  4. 打包后由于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 

安装完之后完善一些配置,比如别名eslintprettier等等基础配置,还有一些颜色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]

代码语言:javascript复制
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配置,根据自己的喜好进行配置,单行多少个字符、单引号、分号、逗号结尾等等

代码语言:javascript复制
module.exports = {
  printWidth: 120,
  singleQuote: true,
  semi: true,
  trailingComma: 'none'
}; 

tsconfig.json

如果这里没有配置识别@/路径的话,在项目中使用会报错

代码语言:javascript复制
"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

因为需要做一些打开和关闭的动效,因此我们需要配置electronframe无边框透明transparent的属性

代码语言:javascript复制
/* 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.cssscommon.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属性,显示在软件上方头部

代码语言:javascript复制
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中可以直接

代码语言:javascript复制
// bad
<transition name="fade">
  <keep-alive>
    <router-view />
  </keep-alive>
</transition> 

上面的这种写法在vue3中会在控制台报异常,记不住写法的可以看看控制台

0 人点赞