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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端实战:electron+vue3+ts开发桌面端便签应用相关的知识,希望对你有一定的参考价值。


前端时间我的一个朋友为了快速熟悉 Vue3 开发, 特意使用 electron+vue3+ts 开发了一个桌面端应用, 并在 ​​github​​​ 上开源了, 接下来我就带大家一起了解一下这个项目, 在文章末尾我会放 ​​github​​​的地址, 大家如果想学习vue3 + ts + electron 开发, 可以本地 ​​clone​​ 学习参考一下.

 

 


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

image.png

技术栈

以上是我们看到的便签软件使用界面, 整体技术选型如下:

脚手架 vue-cli

前端框架和语言规范 vue + typescript

桌面端开发框架 electron

electron支持插件 vue-cli-plugin-electron-builder

数据库 NeDB | 一款NoSQL嵌入式数据库

代码格式规范 eslint

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

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


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

开发思路

页面:

列表页​​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(虽然说是手动。。其实就两个步骤)

目录结构

 
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环境

没有脚手架的可以先安装脚手架

npm install -g @vue/cli

创建vue3项目

 
vue create electron-vue-notes

  

 
# 后续

 

? Please pick a preset: (Use arrow keys)

2] babel, eslint)

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 

创建完之后的目录是这样的

 
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的依赖

 
# 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

npm i eslint -g

进入项目之后初始化eslint

 
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 youve 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 = 

true,

env:

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:

2020

,

rules:

1, single],

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 = 

120,

true,

true,

none

;
tsconfig.json

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

 

"paths": 

"@/*": [


]


package.json

"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([



app,

privileges:

true,

true





]);


function createWindow()

new BrowserWindow(

false, // 无边框

false,

true, // 透明

950,

600,

webPreferences:

true,

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

app);

http://localhost:8080);




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)

message, data =>

if (data === graceful-exit)

app.quit();



);

else

SIGTERM, () =>

app.quit();

);



启动

yarn electron:serve

到这里配置就算是成功搭建好这个窗口了,但是还有一些其他细节需要进行配置,比如​​electron打包​​​配置,​​模块化​​的配置等等

常规配置

这里配置一些常用的开发内容和一些轮子代码, 大家可以参考 ​​reset.csss​​​ 和 ​​common.css​​ 这两个文件.

config

这个对应项目中的config文件夹

 
config

 
├── browser.options.ts # 窗口的配置

 
├── classNames.options.ts # 样式名的配置,背景样式都通过这个文件渲染

 
├── editorIcons.options.ts # 编辑页面的一些editor图标

 
├── index.ts # 导出

 
└── shortcuts.keys.ts # 禁用的一些快捷键,electron是基于chromium浏览器,所以也存在一些浏览器快捷键比如F5 

browser.options

这个文件的主要作用就是配置主窗口和编辑窗口区分开发正式的配置,宽高等等,以及要显示的主页面

 

/**

* 软件数据和配置

* C:\\Users\\用户名\\AppData\\Roaming

* 共享

* C:\\ProgramData\\Intel\\ShaderCache\\i-notesxx

* 快捷方式

* C:\\Users\\用户名\\AppData\\Roaming\\Microsoft\\Windows\\Recent

* 电脑自动创建缓存

* C:\\Windows\\Prefetch\\I-NOTES.EXExx
*/


/** */

const globalEnv = process.env.NODE_ENV;


const devWid = globalEnv === development ? 950 : 0;

const devHei = globalEnv === development ? 600 : 0;


// 底部icon: 40*40
const editorWindowOptions =

290,

350,

250

;


/**

* BrowserWindow的配置项

* @param type 单独给编辑窗口的配置

*/

const browserWindowOption = (type?: editor): Electron.BrowserWindowConstructorOptions =>

const commonOptions =

48,

false,

true,

true,

webPreferences:

true,

true



;

if (!type)

return

350,

600,

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> = [



/,

main,

component: main,

children: [



/,

index,

import(../views/index.vue),

meta:

I便笺



,



/editor,

editor,

import(../views/editor.vue),

meta:





,



/setting,

setting,

import(../views/setting.vue),

meta:

设置





]



];


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中会在控制台报异常,记不住写法的可以看看控制台????????

 

<router-view v-slot=" Component ">

"main-fade">

"transition" :key="routeName">

<keep-alive>

"Component" />

</keep-alive>

</div>

</transition>

</router-view>

然后就是ts部分了,使用vue3的写法去写,​​script​​​标签注意需要写上​​lang="ts"​​​代表是ts语法。​​router​​​的写法也不一样,虽然在vue3中还能写vue2的格式,但是不推荐使用。这里是获取​​route​​​的​​name​​属性,来进行一个页面过渡的效果。

 

<script lang="ts">

import defineComponent, ref, onBeforeUpdate from vue;

import useRoute from vue-router;

import Header from @/components/header.vue;


export default defineComponent(

components:

Header

,

setup()

const routeName = ref(useRoute().name);


onBeforeUpdate(() =>

routeName.value = useRoute().name;

);


return

routeName

;



);

</script>
less部分

<style lang="less" scoped>

.main-fade-enter,

.main-fade-leave-to

display: none;

opacity: 0;

animation: main-fade 0.4s reverse;



.main-fade-enter-active,

.main-fade-leave-active

opacity: 0;

animation: main-fade 0.4s;



@keyframes main-fade

from

opacity: 0;

transform: scale(0.96);



to

opacity: 1;

transform: scale(1);





</style>

以上就是​​main.vue​​​的内容,在页面刷新或者进入的时候根据​​useRouter().name​​​的切换进行​​放大的过渡效果​​,后面的内容会更简洁一点。

header.vue

onBeforeRouteUpdate

头部组件还有一个标题过渡的效果,根据路由导航获取当前路由的​​mate.title​​​变化进行过渡效果。vue3中路由守卫需要从​​vue-route​​导入使用。

 

import  onBeforeRouteUpdate, useRoute  from vue-router;

...

onBeforeRouteUpdate((to, from, next) =>

title.value = to.meta.title;

currentRouteName.value = to.name;

next();

);
computed

这里是计算不同的路由下标题内边距的不同,首页是有个设置入口的按钮,而设置页面是只有两个按钮,​​computed​​​会返回一个你需要的​​新的值​

// 获取首页的内边距 
const computedPaddingLeft = computed(() =>

return currentRouteName.value === index ? padding-left: 40px; : ;

);

emit子传父和props父传子

vue3没有了​​this​​​,那么要使用​​emit​​​怎么办呢?在入口​​setup​​​中有​​2个参数​

setup(props, content) 

​props​​​是父组件传给子组件的内容,​​props​​​常用的​​emit​​​和​​props​​​都在​​content​​中。

????这里需要注意的是,使用​​props​​​和​​emit​​​需要先定义,才能去使用,并且会在​​vscode​​中直接调用时辅助弹窗显示

props示例

emit示例

 

export default defineComponent(

props:

test: String

,

option-click, on-close],

// 如果只用emit的话可以使用es6解构

// 如:setup(props, emit )

setup(props, content)

option-click));



)
electron打开窗口

import browserWindowOption from @/config;

import createBrowserWindow, transitCloseWindow from @/utils;

...

const editorWinOptions = browserWindowOption(editor);

// 打开新窗口

const openNewWindow = () =>

/editor);

;

electron图钉固定屏幕前面

先获取当前屏幕实例

????这里需要注意的是,需要从​​remote​​​获取当前​​窗口信息​

判断当前窗口是否在最前面​​isAlwaysOnTop()​​​,然后通过​​setAlwaysOnTop()​​属性设置当前窗口最前面。

 

import  remote  from electron;

...

// 获取窗口固定状态

let isAlwaysOnTop = ref(false);

const currentWindow = remote.getCurrentWindow();

isAlwaysOnTop.value = currentWindow.isAlwaysOnTop();


// 固定前面

const drawingPin = () =>

if (isAlwaysOnTop.value)

false);

false;

else

true);

true;



;
electron关闭窗口
这里是在​​utils​​​封装了通过对​​dom​​的样式名操作,达到一个退出的过渡效果,然后再关闭。

// 过渡关闭窗口

export const transitCloseWindow = (): void =>

#app)?.classList.remove(app-show);

#app)?.classList.add(app-hide);

close();

;

noteDb数据库

安装nedb数据库,文档: www.w3cschool.cn/nedbintro/n…[5]

yarn add nedb @types/nedb

数据储存在​​nedb​​​中,定义字段,并在根目录的​​shims-vue.d.ts​​加入类型

/** 
* 储存数据库的

*/

interface DBNotes

string; // 样式名

string; // 内容

// 创建时间,这个时间是nedb自动生成的

string; // uid,utils中的方法生成

// update,自动创建的

string; // 自动创建的

对nedb的封装

自我感觉这里写的有点烂。。。勿喷,持续学习中

这里的​​QueryDB​​​是​​shims-vue.d.ts​​定义好的类型

这里的意思是​​QueryDB<T>​​​是一个对象,然后这个对象传入一个​​泛型T​​​,这里​​keyof T​​​获取这个对象的​​key​​​(属性)值,​​?:​​​代表这个​​key​​​可以是​​undefined​​​,表示可以不存在。​​T[K]​​​表示从这个对象中获取这个​​K​​的值。

 

type QueryDB<T> = 

[K in keyof T]?: T[K];

;

import Datastore from nedb;

import path from path;

import remote from electron;


/**

* @see https://www.npmjs.com/package/nedb

*/

class INoteDB<G = any>

/**

* 默认储存位置

* C:\\Users\\Windows User Name\\AppData\\Roaming\\i-notes

*/

// dbPath = path.join(remote.app.getPath(userData), db/inote.db);

// dbPath = ./db/inote.db;

dbPath = this.path;


_db: Datastore<Datastore.DataStoreOptions> = this.backDatastore;


get path()

if (process.env.NODE_ENV === development)

return path.join(__dirname, db/inote.db);



return path.join(remote.app.getPath(userData), db/inote.db);




get backDatastore()

return new Datastore(

/**

* autoload

* default: false

* 当数据存储被创建时,数据将自动从文件中加载到内存,不必去调用loadDatabase

* 注意所有命令操作只有在数据加载完成后才会被执行

*/

true,

filename: this.dbPath,

true

);




refreshDB()

this._db = this.backDatastore;




insert<T extends G>(doc: T)

return new Promise((resolve: (value: T) => void) =>

this._db.insert(doc, (error: Error | null, document: T) =>

if (!error) resolve(document);

);

);




/**

* db.find(query)

* @param Query<T> query:object类型,查询条件,可以使用空对象。

* 支持使用比较运算符($lt, $lte, $gt, $gte, $in, $nin, $ne)

* 逻辑运算符($or, $and, $not, $where)

* 正则表达式进行查询。

*/

find(query: QueryDB<DBNotes>)

return new Promise((resolve: (value: DBNotes[]) => void) =>

this._db.find(query, (error: Error | null, document: DBNotes[]) =>

if (!error) resolve(document as DBNotes[]);

);

);




/**

* db.findOne(query)

* @param query

*/

findOne(query: QueryDB<DBNotes>)

return new Promise((resolve: (value: DBNotes) => void) =>

this._db.findOne(query, (error: Error | null, document) =>

if (!error) resolve(document as DBNotes);

);

);




/**

* db.remove(query, options)

* @param Record<keyof DBNotes, any> query

* @param Nedb.RemoveOptions options

* @return BackPromise<number>

*/

remove(query: QueryDB<DBNotes>, options?: Nedb.RemoveOptions)

return new Promise((resolve: (value: number) => void) =>

if (options)

this._db.remove(query, options, (error: Error | null, n: number) =>

if (!error) resolve(n);

);

else

this._db.remove(query, (error: Error | null, n: number) =>

if (!error) resolve(n);

);



);




update<T extends G>(query: T, updateQuery: T, options: Nedb.UpdateOptions = )

return new Promise((resolve: (value: T) => void) =>

this._db.update(

query,

updateQuery,

options,

(error: Error | null, numberOfUpdated: number, affectedDocuments: T) =>

if (!error) resolve(affectedDocuments);



);

);



  

 
export default new INoteDB(); 

使用​​ref​​​和​​reactive​​​代替vuex,并用​​watch​​监听

创建​​exeConfig.state.ts​

用​​ref​​​和​​reactive​​​引入的方式就可以达到​​vuex​​​的​​state​​​效果,这样就可以完全舍弃掉​​vuex​​​。比如软件配置,创建​​exeConfig.state.ts​​​在​​store​​​中,这样在外部​​.vue​​文件中进行更改也能去更新视图。

import  reactive, watch  from vue;  

const exeConfigLocal = localStorage.getItem(exeConfig);


export let exeConfig = reactive(

1000,

...

switchStatus:

/**

* 开启提示

*/

true



);


if (exeConfigLocal)

exeConfig = reactive(JSON.parse(exeConfigLocal));

else

exeConfig, JSON.stringify(exeConfig));




watch(exeConfig, e =>

exeConfig, JSON.stringify(e));

);

vuex番外

vuex的使用是直接在项目中引入​​useStore​​​,但是是没有​​state​​​类型提示的,所以需要手动去推导​​state​​​的内容。这里的​​S​​​代表​​state​​​的类型,然后传入​​vuex​​​中​​export declare class Store<S> readonly state: S; ​

想要查看某个值的类型的时候在vscode中​​ctrl+鼠标左键​​点进去就能看到,或者鼠标悬浮该值

 

declare module vuex 

type StoreStateType = typeof store.state;

export function useStore<S = StoreStateType>(): Store<S>;

index.vue

这里在防止没有数据的时候页面空白闪烁,使用一个图片和列表区域去控制显示,拿到数据之后就显示列表,否则就只显示图片。

在这个页面对​​editor.vue​​​进行了​​createNewNote​​​创建便笺笔记、​​updateNoteItem_className​​​更新类型更改颜色、​​updateNoteItem_content​​​更新内容、​​removeEmptyNoteItem​​​删除、​​whetherToOpen​​​是否打开(在editor中需要打开列表的操作)​​通信操作​

以及对软件失去焦点进行监听​​getCurrentWindow().on(blur)​​,如果失去焦点,那么在右键弹窗打开的情况下进行去除。

​deleteActiveItem_uid​​​删除便笺笔记内容,这里在​​component​​​封装了一个弹窗组件​​messageBox​​​,然后在弹窗的时候提示​​是否删除​​​和​​不在询问​​的功能操作。

????如果​​勾选不在询问​​​,那么在​​store=>exeConfig.state​​中做相应的更改

这里在设置中会进行详细的介绍

开发一个vue3右键弹窗插件

vue3也发布了有段时间了,虽然还没有完全稳定,但后面的时间出现的插件开发方式说不定也会多起来。插件开发思路

定义好插件类型,比如需要哪些属性​​MenuOptions​

判断是否需要在触发之后立即关闭还是继续显示

在插入​​body​​时判断是否存在,否则就删除重新显示

 

import  createApp, h, App, VNode, RendererElement, RendererNode  from vue;

import ./index.css;

type ClassName = string | string[];


interface MenuOptions

/**

* 文本

*/

string;


/**

* 是否在使用后就关闭

*/

once?: boolean;


/**

* 单独的样式名

*/

className?: ClassName;


/**

* 图标样式名

*/

iconName?: ClassName;


/**

* 函数

*/

handler(): void;




type RenderVNode = VNode<

RendererNode,

RendererElement,



string]: any;



>;


class CreateRightClick

rightClickEl?: App<Element>;

rightClickElBox?: HTMLDivElement | null;


constructor()

this.removeRightClickHandler();




/**

* 渲染dom

* @param menu

*/

render(menu: MenuOptions[]): RenderVNode

return h(

ul,



right-click-menu-list]

,

[

map(item =>

return h(

li,



class: item.className,

// vue3.x中简化了render,直接onclick即可,onClick也可以

onclick: () =>

// 如果只是一次,那么点击之后直接关闭

if (item.once) this.remove();

return item.handler();



,

[

// icon

i,

class: item.iconName

),

// text

h(

span,



right-click-menu-text

,

item.text

)

]

);

)

]

);




/**

* 给右键的样式

* @param event 鼠标事件

*/

len: number): void

if (!this.rightClickElBox) return;

`$len * 36px`;

const clientX, clientY = event;

const innerWidth, innerHeight = window;

const clientWidth, clientHeight = this.rightClickElBox;

`height: $len * 36px;opacity: 1;transition: all 0.2s;`;

if (clientX + clientWidth < innerWidth)

`left: $clientX + 2px;`;

else

`left: $clientX - clientWidthpx;`;



if (clientY + clientHeight < innerHeight)

`top: $clientY + 2px;`;
<

以上是关于前端实战:electron+vue3+ts开发桌面端便签应用的主要内容,如果未能解决你的问题,请参考以下文章

Electron-Vite2-MacUI桌面管理框架|electron13+vue3.x仿mac桌面UI

vue3 electron 记录

vite+vue3+ts实战项目,教你实现一个网页版的typora!(前端篇)

前端开发人员的桌面应用神器 Electron

百度云mksz466Vue3.0+TS打造企业级组件库前端中高级开发者必修课

electron和vue的关系