自从一月份离职到现在已经过去了三个多月,接了一个私活儿导致这段时间也算充实。
由于也是第一次正式使用
electron
框架开发桌面应用,TinyMCE
更是第一次接触,再加上项目对接流程的问题,所以项目进展磕磕绊绊。目前项目已近尾声,整理几篇复盘以供日后自我查阅。
项目介绍及工作内容
项目是一个桌面级文本编辑器应用,需要基于 electron
、TinyMCE
开发。产品预期目标为,为用户提供书籍编辑功能,最终生成电子版文件上传入库并开放到书籍浏览器浏览。
主要涉及页面如下:
其中,我负责开发的板块不限于以下:
- 插入关键词;
- 导入关键词;
- 插入脚注;
- 插入Word;
- 导入h5包;
- 引入就编辑器上传入库;
- 打包日志;
- 打包导出;
- 编辑器页基础功能开发;
- 教材信息及习题列表页开发;
- 其他;
项目涉及轮子
项目的底层轮子采用 Vue3
+ Electron
+ TinyMCE
+ Element Plus
组成。
Vue 3.0
易学易用,性能出色,适用场景丰富的 Web 前端框架。
Electron
Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台的桌面应用程序。它基于 Node.js 和 Chromium,被 Atom 编辑器和许多其他应用程序使用。
Element Plus
Element Plus 基于 Vue 3,面向设计师和开发者的组件库。
TinyMCE
TinyMCE 是一个轻量级的,基于浏览器的,所见即所得编辑器,支持目前流行的各种浏览器,由 JavaScript 写成。
传送门:https://www.tiny.cloud/、《[TinyMCE中文文档中文手册](http://tinymce.ax-z.cn/)》
Vue 关联 Electron
为了开发便捷,我们要将 Electron 和 vue 结合,以便后期在进程下同时对双端进行监视并调试。
要实现这点,首先需要确保 Vue 可以正常运行之后安装 Electron
npm install electron --save-dev // 我安装的版本为 "electron": "^28.1.4"
在项目根目录增加 electron
文件夹,并在其内创建 index.html
和 main.js
,内容分别为:
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>
// main.js
const { app, BrowserWindow } = require('electron')
const path = require("path")
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
})
// 使用 loadFile 加载 electron/index.html 文件
mainWindow.loadFile(path.join(__dirname, "./index.html"));
// 通过此段关联刚刚创建的 index.html,而 index.html 最终会展示 vue 运行的结果
}
// 在应用准备就绪时调用函数
app.whenReady().then(() => {
createWindow()
})
添加完毕后,修改根目录 package.json
文件,追加 Electron 启动命令
{
"main": "electron/main.js", // 关联刚刚创建的 main.js 文件
"scripts": {
"electron:dev": "electron ."
}
}
此时需要执行两个进程启动项目:
- 执行
npm run serve
运行 Vue; - 执行
npm run electron:dev
运行 Electron;mainWindow.loadFile(path.join(__dirname, "./index.html"));
可将项目本地化,也就是 electron 指向本地文件;- 非本地化则将上述代码修改为
mainWindow.loadURL("http://localhost:3004/");
,比如调用 Vue 运行地址,则将代码中地址改为http://localhost:8080/
;
目前虽然项目可以正常启动,修改 Vue 时页面可以联通响应,但 main.js 主进程修改则无法做到联动相应
要做到 Electron 热启动,需要安装以下依赖
npm i -D nodemon // 我安装的版本为 "nodemon": "^3.1.0"
并将 package.json
内容 electron:dev
字段改为
"electron:dev": "nodemon --watch main.js --exec electron ."
继续修改 electron:dev
字段,已避免控制台遇到中文会乱码的问题
"electron:dev": "chcp 65001 && nodemon --watch main.js --exec electron ."
参考:《electron 控制台打印乱码问题)》
配置 Electron 打包
依旧要先解决 Vue 打包空白的问题,vue.config.js
增加字段
module.exports = defineConfig({
publicPath: './'
})
安装打包依赖
npm i -D electron-builder // 我安装的版本为 "electron-builder": "^24.9.1"
在 package.json
中增加打包配置参数,以下是我的配置
"scripts": {
"electron:build": "npm run build && electron-builder", // 增加打包命令,同时打包 Vue 和 Electron
},
"build": {
"productName": "数字教材编辑器v1.0.1-beta", // 打包后应用的名称
"appId": "com.example.my-electron-app",
"directories": {
"output": "dist-electron" // 打包的输出路径
},
"win": {
"icon": "./logo-256x256.ico", // 图标必须是 256x256 的尺寸
"target": [
{
"target": "nsis", // 表示使用 NSIS 进行打包。除了 NSIS,还可以使用其他打包工具,比如 portable、squirrel 等,此处 nsis 对应外层 nsis 配置
"arch": [
"x64",
"ia32"
]
}
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./logo-256x256.ico",
"uninstallerIcon": "./logo-256x256.ico"
}
// 在 Electron 的配置中,nsis 字段指定了使用 NSIS (Nullsoft Scriptable Install System) 进行打包时的相关设置。NSIS 是一个用于创建 Windows 安装程序的脚本驱动的系统,它允许开发者定义安装程序的外观、行为以及安装过程中需要执行的任务。
// 在 Electron 中,nsis 字段的常见用法包括指定安装程序的图标、安装程序的名称、安装路径等。通过配置 nsis 字段,开发者可以定制生成的安装程序,以满足特定的需求和品牌标识。
// {
// "build": {
// "nsis": {
// "oneClick": false, // 是否一键安装
// "allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
// "allowToChangeInstallationDirectory": true, // 允许修改安装目录
// "installerIcon": "./build/icons/aaa.ico", // 安装图标
// "uninstallerIcon": "./build/icons/bbb.ico", // 卸载图标
// "installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
// "createDesktopShortcut": true, // 创建桌面图标
// "createStartMenuShortcut": true,// 创建开始菜单图标
// "shortcutName": "xxxx", // 图标名称
// "include": "build/script/installer.nsh", // 包含的自定义 nsis 脚本 这个对于构建需求严格得安装过程相当有用
// "script" : "build/script/installer.nsh" // NSIS 脚本的路径,用于自定义安装程序。 默认为build / installer.nsi
// }
// }
// }
}
引入 Element Plus
由于前期是依靠原型开发,所以选择使用 Element Plus 框架,根据框架前期可以快递搭建页面,但是随着三月初设计的进驻却带来了不少修改成本。
安装依赖
将 Element Plus 整合到 Vue3 并不繁琐,步骤如下:
npm i element-plus // 我安装的版本为 "element-plus": "^2.5.1"
注入依赖
安装完依赖后,直接在 Vue 的 main.js 文件中 注入依赖
import ElementPlus from 'element-plus' // 引入模块
import 'element-plus/dist/index.css' // 引入css
这里要注意,Element Plus 组件 默认使用英语,可以继续在 main.js 中引入国际化将其修改为中文
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus, {
locale: zhCn,
})
为了仅可能的减小未来产出的文件大小,项目采取了按需引入的方式。按需引入需要修改 vue.config.js
即可
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
export default defineConfig({
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
})
]
}
})
初期设计没有进驻,所需的图片全部依赖 Element Plus 图标库
npm install @element-plus/icons-vue // 我安装的版本为 "@element-plus/icons-vue": "^2.3.1"
并在 main.js 中添加
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
参考:《Vue3使用Element-plus》《快速开始 | Element Plus》《国际化 | Element Plus》《Icon 图标 | Element Plus》
引入 TinyMCE
安装依赖
本项目一共使用了两种引入方式,第一种需要安装以下依赖
npm install tinymce -S // 我安装的版本为 "tinymce": "^5.10.2"
npm install @tinymce/tinymce-vue -S // 我安装的版本为 "@tinymce/tinymce-vue": "^5.1.1"
并根据所处的语言环境,在下对应的语言包 https://www.tiny.cloud/get-tiny/language-packages/
之后在项目 public 文件夹下新建 TinyMCE 文件夹,并将语言包解压至其中,同时复制 node_modules/tinymce/skins 文件夹同样复制到该目录
但由于项目需要对 TinyMCE 源文件进行部分修改,为了多人协作便利故无法直接使用 node_modules/tinymce,所以采取了第二种引入方式
下载社区版:http://download.tiny.cloud/tinymce/community/tinymce_5.10.0.zip 将其解压后放至 src/components 目录中
注入依赖
这里大部分使用的 ./
因为引入的为 src/components 中的 TinyMCE,以下是我的编辑器配置仅供参考
// src/components/TEditor.vue
<template>
<editor v-model="myValue" :init="init" :disabled="disabled" id="tinymceId"></editor>
</template>
<script setup>
import tinymce from "tinymce/tinymce";
import Editor from "@tinymce/tinymce-vue";
import "./tinymce/themes/silver"; // 引入默认主题
const init = reactive({ // 编辑器配置
selector: "#tinymceId", // 编辑 id
language_url: "./tinymce/langs/zh_CN.js", // 语言包的路径,具体路径看自己的项目,文档后面附上中文 js 文件
language: "zh_CN", // 语言
height: document.documentElement.clientHeight, // 编辑器高度(引入 autoresize 插件时,此属性失效)
plugins: [], // 插件(这里的插件用于实现官方插件功能)
toolbar: ["fontsizeselect | fontselect formatselect | forecolor backcolor | bold italic underline strikethrough subscripts | lineheight indent2em alignment numlist bullist", "undo redo removeformat | cut copy paste], // 工具栏(自定义插件、官方插件、默认插件的图标展示),如果要实现多行显示以逗号区分两段字符串
// content_css:, // 补充编辑器内容样式,这里是 .css 文件路径
content_style: `p {word-wrap: break-word; word-break: break-all;}` // 补充编辑器内容样式
branding: false, // 禁止显示 “Powered by TinyMCE”
menubar: false, // 禁用编辑器菜单
statusbar: false, // 禁止显示底部状态栏
extended_valid_elements: ["iframe[*]"], // 默认编辑器会阻止某些 html 标签的展示,此属性用于释放被限制的标签
convert_urls: false, // 该选项允许你控制 TinyMCE 是否存储 URL 的原始值,默认情况 URL 会被自动转化,没有方法获取真正的 URL,如果你设置该选项为 false,那么它会试着保持 URL 的完整性,默认为 true,意味着 URL 会依据 relative_urls 的状态而被强制为绝对或相对的
relative_urls: true, // 如果该选项设为 true,所有通过 MCFileManager 返回的 URL 都会与 document_base_url 相关,如果设为false,所有 URL 会被转化成绝对 URL,默认为 true
document_base_url: ’file:///‘ + store.state.projectPath, // 该选项为文档中所有相对的 URL 指定 URL 基础,默认值为当前文档的目录。如果提供了值,一定要是目录(而不是文档),必须以 “/” 结尾。该选项还与 relative_urls,remove_script_host 和 convert_urls 选项结合使用以确定 TinyMCE 返回相对还是绝对的 URL
font_formats: "微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;" // 设置字体选择下拉框拥有的字体
style_formats: [ // 设置标题下拉框拥有的标题
{ title: "Heading 1", format: "h1" },
{ title: "Heading 2", format: "h2" },
{ title: "Heading 3", format: "h3" },
{ title: "Heading 4", format: "h4" },
{ title: "Heading 5", format: "h5" },
{ title: "Heading 6", format: "h6" },
],
lineheight_formats: "0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5", // 设置行高下拉框拥有的行高
setup: (editor) => {
}
})
</script>
这样页面中就会看到一个基础的编辑器了,想要实现更多功能只需注入功能更多插件即可
官方插件的使用可以翻阅《TinyMCE中文文档中文手册》获得,该文档仅提供版本 5 的使用方案,以下是我项目的部分插件
import "./tinymce/plugins/lists"; //列表插件
import './tinymce/plugins/indent2em' // 首行缩进
import "./tinymce/plugins/link"; // 超链接插件
import './tinymce/plugins/table' // 插入表格插件
import "./tinymce/plugins/hr"; // 水平分割线
import './tinymce/plugins/charmap' // 特殊字符
import './tinymce/plugins/pagebreak' // 插入分页符
import "./tinymce/plugins/charmap"; // 特殊字符
import "./tinymce/plugins/pagebreak"; // 插入分页符
const init = reactive({
...
plugins: ["indent2em link table charmap hr pagebreak"],
toolbar: ["indent2em link table charmap hr pagebreak"]
...
})
参考:https://www.tiny.cloud/ 《vue3项目中使用tinymce的方法》《TinyMCE中文文档中文手册》
以上就是项目的基础轮子逻辑使用,如果有遗漏我会后期复盘中陆续补充...