编辑器项目复盘-初始化

自从一月份离职到现在已经过去了三个多月,接了一个私活儿导致这段时间也算充实。

由于也是第一次正式使用 electron 框架开发桌面应用,TinyMCE 更是第一次接触,再加上项目对接流程的问题,所以项目进展磕磕绊绊。目前项目已近尾声,整理几篇复盘以供日后自我查阅。

项目介绍及工作内容

项目是一个桌面级文本编辑器应用,需要基于 electronTinyMCE 开发。产品预期目标为,为用户提供书籍编辑功能,最终生成电子版文件上传入库并开放到书籍浏览器浏览。

主要涉及页面如下:

其中,我负责开发的板块不限于以下:

  • 插入关键词;
  • 导入关键词;
  • 插入脚注;
  • 插入Word;
  • 导入h5包;
  • 引入就编辑器上传入库;
  • 打包日志;
  • 打包导出;
  • 编辑器页基础功能开发;
  • 教材信息及习题列表页开发;
  • 其他;

项目涉及轮子

项目的底层轮子采用 Vue3 + Electron + TinyMCE + Element Plus 组成。

Vue 3.0

易学易用,性能出色,适用场景丰富的 Web 前端框架。

传送门:https://cn.vuejs.org/

Electron

Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台的桌面应用程序。它基于 Node.js 和 Chromium,被 Atom 编辑器和许多其他应用程序使用。

传送门:https://www.electronjs.org/zh/

Element Plus

Element Plus 基于 Vue 3,面向设计师和开发者的组件库。

传送门:https://element-plus.org/zh-CN/

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.htmlmain.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/

参考:《Electron + Vue3 开发桌面应用

目前虽然项目可以正常启动,修改 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学习-02: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
  //     }
  //   }
  // }
}

参考:《Electron + Vue3 开发桌面应用》《electron-builder通用配置(翻译)


引入 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中文文档中文手册


以上就是项目的基础轮子逻辑使用,如果有遗漏我会后期复盘中陆续补充...

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注