不要让自己的上限成为你的底线
本来以为有万字的。。没想到才堪堪近6000字。为了水文的嫌疑,只挑了重点的地方讲,比如component
内的组件就挑了右键弹窗去说明
,建议在看本文的时候边查看项目,有不懂的可以在下方评论,谢谢。
github
github: https://github.com/heiyehk/electron-vue3-inote
包下载
release: https://github.com/heiyehk/electron-vue3-inote/releases
接上篇配置篇 【electron+vue3+ts实战便笺exe】一、搭建框架配置,这里更新了一下vue3的版本3.0.4
,本篇文章只讲开发内容,主要还是vue3
方面,长文警告。ps:smartblue这个主题好好看。。。
router
增加meta
中的title
属性,显示在软件上方头部
1 | import { createRouter, createWebHashHistory } from 'vue-router'; |
utils
1 | /* eslint-disable @typescript-eslint/ban-types */ |
main.vue
main.vue
文件主要是作为一个整体框架,考虑到页面切换时候的动效,分为头部和主体部分,头部作为一个单独的组件处理,内容区域使用router-view
渲染。
html部分,这里和vue2.x有点区别的是,在vue2.x中可以直接
1 | // bad |
上面的这种写法在vue3中会在控制台报异常,记不住写法的可以看看控制台🤣🤣
1 | <router-view v-slot="{ Component }"> |
然后就是ts部分了,使用vue3的写法去写,script
标签注意需要写上lang="ts"
代表是ts语法。router
的写法也不一样,虽然在vue3中还能写vue2的格式,但是不推荐使用。这里是获取route
的name
属性,来进行一个页面过渡的效果。
1 | <script lang="ts"> |
less部分
1 |
|
以上就是main.vue
的内容,在页面刷新或者进入的时候根据useRouter().name
的切换进行放大的过渡效果
,后面的内容会更简洁一点。
header.vue
onBeforeRouteUpdate
头部组件还有一个标题过渡的效果,根据路由导航获取当前路由的mate.title
变化进行过渡效果。vue3中路由守卫需要从vue-route
导入使用。
1 | import { onBeforeRouteUpdate, useRoute } from 'vue-router'; |
computed
这里是计算不同的路由下标题内边距的不同,首页是有个设置入口的按钮,而设置页面是只有两个按钮,computed
会返回一个你需要的新的值
1 | // 获取首页的内边距 |
emit子传父和props父传子
vue3没有了this
,那么要使用emit
怎么办呢?在入口setup
中有2个参数
1 | setup(props, content) {} |
props
是父组件传给子组件的内容,props
常用的emit
和props
都在content
中。
🧨这里需要注意的是,使用
props
和emit
需要先定义,才能去使用,并且会在vscode
中直接调用时辅助弹窗显示
props示例
emit示例
1 | export default defineComponent({ |
electron打开窗口
1 | import { browserWindowOption } from '@/config'; |
electron图钉固定屏幕前面
先获取当前屏幕实例
🧨这里需要注意的是,需要从
remote
获取当前窗口信息
判断当前窗口是否在最前面isAlwaysOnTop()
,然后通过setAlwaysOnTop()
属性设置当前窗口最前面。
1 | import { remote } from 'electron'; |
electron关闭窗口
这里是在utils
封装了通过对dom
的样式名操作,达到一个退出的过渡效果,然后再关闭。
1 | // 过渡关闭窗口 |
noteDb数据库
安装nedb数据库,文档: https://www.w3cschool.cn/nedbintro/nedbintro-t9z327mh.html
1 | yarn add nedb @types/nedb |
数据储存在nedb
中,定义字段,并在根目录的shims-vue.d.ts
加入类型
1 | /** |
对nedb的封装
自我感觉这里写的有点烂。。。勿喷,持续学习中
这里的QueryDB
是shims-vue.d.ts
定义好的类型
这里的意思是QueryDB<T>
是一个对象,然后这个对象传入一个泛型T
,这里keyof T
获取这个对象的key
(属性)值,?:
代表这个key
可以是undefined
,表示可以不存在。T[K]
表示从这个对象中获取这个K
的值。
1 | type QueryDB<T> = { |
1 | import Datastore from 'nedb'; |
使用ref
和reactive
代替vuex,并用watch
监听
创建exeConfig.state.ts
用ref
和reactive
引入的方式就可以达到vuex
的state
效果,这样就可以完全舍弃掉vuex
。比如软件配置,创建exeConfig.state.ts
在store
中,这样在外部.vue
文件中进行更改也能去更新视图。
1 | import { reactive, watch } from 'vue'; |
vuex番外
vuex的使用是直接在项目中引入useStore
,但是是没有state
类型提示的,所以需要手动去推导state
的内容。这里的S
代表state
的类型,然后传入vuex
中export declare class Store<S> { readonly state: S; }
想要查看某个值的类型的时候在vscode中
ctrl+鼠标左键
点进去就能看到,或者鼠标悬浮该值
1 | declare module 'vuex' { |
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
时判断是否存在,否则就删除重新显示
1 | import { createApp, h, App, VNode, RendererElement, RendererNode } from 'vue'; |
右键弹窗插件配合electron打开、删除便笺笔记
在使用的时候直接引入即可,如在index.vue
中使用创建右键的方式,这里需要额外的说明一下,打开窗口需要进行一个窗口通信判断,ipcMain
需要从remote
中获取
- 每个便笺笔记都有一个
uid
,也就是utils
中生成的 - 每个在打开笔记的时候也就是编辑页,需要判断
该uid的窗口
是否已经打开 - 窗口之间用
ipcRenderer
和ipcMain
去通信 - 判断通信失败的方法,用一个定时器来延时判断是否
通信成功
,因为没有判断通信失败的方法 countFlag = true
就说明打开窗口,countFlag = false
说明没有打开窗口
ipcRenderer
和ipcMain
通信
🧨on
是一直处于通信状态,once
是通信一次之后就关闭了
1 | // countFlag是一个状态来标记收到东西没 |
右键弹窗的使用
🧨这里的打开笔记功能会把选中的笔记uid
当作一个query
参数跳转到编辑页
1 | import CreateRightClick from '@/components/rightClick'; |
editor.vue重点
这个editor.vue是view/文件夹下
的,以下对本页面统称编辑页,更好区分editor组件
和页面
开发思路
- 打开
新增
编辑页窗口时就生成uid
并向数据库nedb
添加数据,并向列表页通信ipcRenderer.send('createNewNote', res)
- 需要使用富文本,能实时处理格式
document.execCommand
- 页面加载完时进行聚焦
createRange
和getSelection
- 对列表页实时更新,编辑的时候防抖函数
debounce
可以控制输入更新,这个时间在设置是可控
的 图钉固定
在header.vue
已经说明选项功能
能选择颜色,打开列表之后需要判断是否已经打开列表窗口- 在
点击关闭
的时候需要删除
数据库本条数据,如果没有输入内容就删除数据库uid
内容并向列表页通信removeEmptyNoteItem
- 在列表页时关闭本窗口的一个通信
deleteActiveItem_{uid}
- 列表页
打开笔记
时,携带uid
,在编辑页根据是否携带uid
查询该条数据库内容
富文本编辑做成了一个单独的组件,使编辑页
的代码不会太臃肿
document.execCommand文档
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand
首先在编辑页对路由进行判断是否存在,如果不存在就创建,否则就查询并把查询到的笔记传给editor组件
1 | <Editor :content="editContent" :className="currentBgClassName" @on-input="changeEditContent" /> |
1 | const routeUid = useRoute().query.uid as string; |
富文本聚焦和ref获取dom节点
原理是通过getSelection
选择光标和createRange
文本范围两个方法,选中富文本节点
。
获取
1 | import { defineComponent, onMounted, ref, Ref, watch } from 'vue'; |
editor组件的父传子以及watch监听
🧨这里需要注意的是因为在父组件传给子组件,然后子组件进行更新一次会导致富文本无法撤回,相当于重新给富文本组件赋值渲染了一次,因此这里就只用一次props.content
1 | export default defineComponent({ |
editor组件的防抖子传父
exeConfig.syncDelay
是设置里面的一个时间,可以动态根据这个时间来调节储存进数据库和列表的更新,获取富文本组件的html
然后储存到数据库并传到列表页更新
1 | const changeEditorContent = debounce((e: InputEvent) => { |
富文本组件的粘贴纯文本
vue自带的粘贴事件,@paste
获取到剪切板的内容,然后获取文本格式的内容e.clipboardData?.getData('text/plain')
并插入富文本
1 | const paste = (e: ClipboardEvent) => { |
(🎉🎉🎉额外的)getCurrentInstance
选择dom方式
官方和网上的例子是这样:
1 | <div ref="editor"></div> |
1 | setup(props, { emit }) { |
直接获取dom节点
,但其实不管这个editor
是什么,只要从setup
中return
,就会直接标记instance
变量名,强行把内容替换成dom节点
,甚至不用定义可以看看下面例子
1 | <div ref="test"></div> |
1 | import { defineComponent, getCurrentInstance, onMounted } from 'vue'; |
但是为了规范还是使用下面这样
1 | <div ref="dom"></div> |
1 | const dom = ref(null); |
此处推广一下一个98年大佬的vue3源码解析github: https://github.com/Kingbultsea/vue3-analysis
setting.vue
这里的话需要用到exeConfig.state.ts
的配置信息,包括封装的input
、switch
、tick
组件
在这里说明一下,自动缩小
、靠边隐藏
和同步设置
暂时还没有开发的
自动缩小
: 编辑页失去焦点时自动最小化,获得焦点重新打开靠边隐藏
: 把软件拖动到屏幕边缘时,自动隐藏到边上,类似QQ那样的功能同步设置
: 打算使用nestjs
做同步服务,后面可能
会出一篇有关的文章,但是功能一定会做的
directives自定义指令
根据是否开启提示的设置写的一个方便控制的功能,这个功能是首先获取初始化的节点高度,放置在dom
的自定义数据上面data-xx
,然后下次显示的时候再重新获取赋值css显示,当然这里也是用了一个过渡效果
使用方法
1 | <div v-tip="switch"></div> |
1 | export default defineComponent({ |
原生点击复制
原理是先隐藏一个input
标签,然后点击的之后选择它的内容,在使用document.execCommand('copy')
复制就可以
1 | <a @click="copyEmail">复制</a> |
1 | const mailInput: Ref<HTMLInputElement | null> = ref(null); |
electron打开文件夹和打开默认浏览器链接
打开文件夹使用shell
这个方法
1 | import { remote } from 'electron'; |
打开默认浏览器链接
1 | import { remote } from 'electron'; |
错误收集
收集一些使用中的错误,并使用message
插件进行弹窗提示,软件宽高和屏幕宽高只是辅助信息。碰到这些错误之后,在软件安装位置输出一个inoteError.log
的错误日志文件,然后在设置中判断文件是否存在,存在就打开目录选中。
- 版本号
- 时间
- 错误
- electron版本
- Windows信息
- 软件宽高信息
- 屏幕宽高
比如这个框中的才是主要的信息
vue3 errorHandler
main.ts
我们需要进行一下改造,并使用errorHandler
进行全局的错误监控
1 | import { createApp } from 'vue'; |
errorLog.ts封装对Error类型输出为日志文件
获取软件安装位置
remote.app.getPath('exe')
获取软件安装路径,包含软件名.exe
1 | export const errorLogPath = path.join(remote.app.getPath('exe'), '../inoteError.log'); |
输出日志文件
flag: a
代表末尾追加,确保每一行一个错误加上换行符'\n'
1 | fs.writeFileSync(errorLogPath, JSON.stringify(errorLog) + '\n', { flag: 'a' }); |
errorLog.ts
的封装,对Error
类型的封装
1 | import { ComponentPublicInstance } from 'vue'; |
使用此方法后封装的结果是这样的,message
插件具体看component
这个是之前的错误日志文件
获取electron版本等信息
1 | const appInfo = process.versions; |
打包
这个倒是没什么好讲的了,主要还是在vue.config.js
文件中进行配置一下,然后使用命令yarn electron:build
即可,当然了,还有一个打包前清空的旧的打包文件夹的脚本
deleteBuild.js
打包清空dist_electron
旧的打包内容,因为eslint
的原因,这里就用eslint-disable
关掉了几个
原理就是先获取vue.config.js
中的打包配置,如果重新配置了路径directories.output
就动态去清空
1 | const rm = require('rimraf'); |
结尾
1 | 人学始知道不学非自然 |
如果有不好的地方勿喷,及时评论及时改正,谢谢!内容有点多。