@bireturn/word-editor
v1.0.14
Published
类 Word 的 Web 富文本编辑器,基于canvas-editor的数据结构渲染
Downloads
2,158
Readme
@bireturn/word-editor
类 Word 的 Web 富文本编辑器,目前只实现了只读渲染功能,基于 Vue 3 + Vite + TypeScript。参考 canvas-editor 的数据结构与命令系统设计,但使用 DOM 渲染代替 Canvas,并通过虚拟滚动支撑大文档。
安装
npm install @bireturn/word-editor需宿主项目提供
vue ^3.4(已声明为peerDependency,避免重复打包与多实例)。
使用
数据由使用方通过 props 传入,组件本身不发请求:
<script setup>
import { EditorContainer } from '@bireturn/word-editor'
import '@bireturn/word-editor/style.css' // 必须引入样式
import { ref, onMounted } from 'vue'
const docData = ref([]) // IElement[],来源由使用方决定
const loading = ref(false)
onMounted(async () => {
loading.value = true
docData.value = await fetchDoc() // 使用方自己的数据源
loading.value = false
})
</script>
<template>
<EditorContainer :data="docData" :loading="loading" />
</template>也可通过 app.use() 全局注册:
import WordEditor from '@bireturn/word-editor'
import '@bireturn/word-editor/style.css'
app.use(WordEditor) // 注册全局组件 <EditorContainer />Props
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| data | IElement[] | [] | 文档结构数据,渲染所需的行内元素列表 |
| loading | boolean | false | 外部数据加载态,为 true 时显示加载占位 |
特性
- 数据驱动渲染:文档数据模型是唯一数据源,DOM 仅作视图
- 命令系统(Command Pattern):所有写操作经
editor.exec()分发,统一支持撤销/重做 - 虚拟滚动:以段落为最小单元,配合自研
VirtualTable支撑大文档 - A4 分页排版:按页面内容高度自动分页,支持页眉/页脚、连续图片与表格跨页拆分
- 富内容:文本、图片、超链接、表格、控件(文本/输入/单选)、上下标、复选框等
- 插件机制:通过
editor.use()注入自定义命令与能力
技术栈
| 类别 | 选型 | | --- | --- | | 框架 | Vue 3(Composition API) | | 构建 | Vite 5 + TypeScript 5 | | 状态 | Pinia | | 图标 | lucide-vue-next | | 工具 | @vueuse/core、lodash-es | | 虚拟滚动 | vue-virtual-scroller + 自研 VirtualTable | | Mock | vite-plugin-mock-dev-server |
快速开始
npm install # 安装依赖
npm run dev # 启动开发服务器(默认 http://localhost:5173)开发态接口由 mock/ 提供,可完全离线运行,无需后端。
常用命令
npm run dev # 启动开发服务器
npm run build # 构建可部署的应用产物
npm run build:lib # 构建组件库产物(lib/,含 ESM/UMD + 类型声明)
npm run preview # 本地预览构建产物
npm run lint # ESLint 检查并自动修复
npm run type-check # 仅做 TypeScript 类型检查构建与本地联调
本项目可同时作为独立应用与组件库使用。vite.config.ts 按 mode 区分两种产物:默认模式构建应用,--mode lib(即 npm run build:lib)构建组件库。
构建产物
npm run build:lib产物输出到 lib/:
| 文件 | 说明 |
| --- | --- |
| word-editor.es.js | ESM 格式,供打包工具按需引入 |
| word-editor.umd.js | UMD 格式,供浏览器/旧环境直接引用 |
| word-editor.css | 组件样式,需由使用方手动引入 |
| index.d.ts | TypeScript 类型声明 |
vue 被声明为 peerDependencies 并在构建时外置,由使用方提供,避免重复打包与多实例问题。
本地联调(不发布到 npm)
构建后,在消费方项目通过本地路径安装,推荐 file: 方式:
// 消费方 package.json
{
"dependencies": {
"@bireturn/word-editor": "file:../word-editor" // 改成实际路径
}
}npm install改动本库后,重跑 npm run build:lib 并重启消费方 dev server 即可生效。也可用 npm pack 生成 .tgz 后安装(更贴近真实发布),或 npm link(改库实时联动)。
⚠️ 若用
file:/npm link出现 Vue 响应式失效或 inject 报错,通常是加载了两份 Vue。在消费方vite.config.ts加resolve.dedupe: ['vue']与optimizeDeps.include: ['@bireturn/word-editor']去重即可。
发布到 npm
npm run build:lib # 先构建产物到 lib/
npm publish --access public # scoped 包首次发布需加 --access public仅
lib/会随包发布(由package.json的files字段约定)。
Mock 数据
开发态接口基于 vite-plugin-mock-dev-server 实现,配置见 mock/api.mock.ts,数据文件存放于 mock/json/,可完全离线运行、无需后端。
接口约定
前端通过 src/api/file.ts 的 getStructInfo() 请求 /api/info,期望返回文档结构数组 IElement[](即 DocumentModel 渲染所需的行内元素列表)。
mock/api.mock.ts 中有两类挂载方式:
- 约定式(
apiList):/api/xxx自动读取mock/json/xxx.json,默认延迟 300ms - 自定义式(
customApis):显式指定 url 与数据文件,可单独设置延迟。当前/api/info即在此挂载
切换测试数据
修改 customApis 中 /api/info 读取的 json 文件即可切换渲染数据,例如:
const customApis = [
{
url: '/api/info',
delay: 1000,
body: () =>
JSON.parse(
fs.readFileSync(path.resolve(root, `./mock/json/info.json`), 'utf8')
)
}
]数据文件清单
所有文件均为顶层 IElement[] 数组,元素以 type 区分(text / table / image / hyperlink / control / list 等)。
| 文件 | 体积 | 元素数 | 主要内容 | 用途 |
| --- | --- | --- | --- | --- |
| info.json | ~1.6 MB | 81 | 文本、9 个表格、控件、超链接 | 常规文档,功能演示基线 |
| pagetable.json | ~388 KB | 1 | 单个跨页大表格(77 行) | 表格分页与行渲染调试 |
| page1000.json | ~32 MB | 84 | 84 个表格 | 大量表格的分页/性能压测 |
| page69.json | ~85 MB | 2323 | 文本、198 表格、列表、控件 | 超大文档虚拟滚动压测 |
| page69i.json | ~85 MB | 2344 | 在 page69 基础上增加 18 张图片 | 含图片的超大文档压测 |
说明:
page69*/page1000体积较大,仅用于性能与边界场景验证,日常开发建议使用info.json或pagetable.json。
元素数据结构
各 type 的字段含义见 docs/0.JSON数据类型说明.md 与 docs/1.页面样式属性说明.md。
核心设计原则
- 数据模型是唯一数据源,DOM 只是视图,不存储状态
- contenteditable 仅用于输入捕获,不用于渲染(透明 overlay 模式)
- 所有写操作必须通过
editor.exec(),不能直接修改docModel - 大文档通过虚拟滚动实现,段落是虚拟列表的最小单元
目录结构
src/
├── core/ # 纯 TypeScript,与 Vue 无关
│ ├── editor/Editor.ts # 编辑器主类,所有子系统的聚合入口
│ ├── model/DocumentModel.ts # 文档数据模型,所有原子修改方法在此
│ ├── command/ # 命令系统
│ │ ├── CommandManager.ts # exec() 分发,注册/执行命令
│ │ └── commands/ # InsertText / Delete / Format / InsertParagraph 等
│ ├── selection/SelectionManager.ts # 模型坐标系的光标/选区
│ ├── history/HistoryManager.ts # undo/redo 栈
│ └── input/InputHandler.ts # DOM 事件 → exec() 转换层
├── composables/
│ ├── useEditor.ts # provide/inject Editor 实例
│ └── useDocumentData.ts # 从 docModel 提取响应式数据
├── components/
│ ├── Editor/ # 编辑器根组件(含分页、表格、页眉页脚渲染)
│ └── VirtualTable/ # 自研虚拟表格组件
├── api/ # 接口请求封装
└── types/ # 全局类型(document/selection/command/plugin)
mock/ # vite mock,离线可用的接口数据
docs/ # 功能说明与问题分析文档架构
数据流
用户输入(键盘/鼠标)
→ InputHandler(DOM 事件拦截)
→ editor.exec(commandName, payload)
→ CommandManager(查找命令工厂)
→ ICommand.execute()(修改 DocumentModel)
→ Vue 响应式自动触发重渲染
→ 组件根据最新 docModel 渲染 DOM数据模型
DocumentModel
└── sections[] # 节(分页/分栏单位)
└── paragraphs[] # 段落(块级,虚拟滚动的最小单元)
└── children: IElement[] # 行内元素(文本/图片/超链接/表格等)坐标系
所有位置用 ModelPoint 表示:
{ sectionIndex, paragraphIndex, elementIndex, offset }DOM 元素通过 data-si / data-pi / data-ei 属性反向映射回模型坐标(见 SelectionManager.domToModelPoint)。
扩展
注册命令
在 CommandManager._registerBuiltinCommands() 中注册,或通过插件注入:
editor.commands.register('myCommand', (payload) => new MyCommand(...))命令须实现 ICommand(execute + undo),可选实现 canMergeWith / merge 以支持连续输入合并。
插件
editor.use({
name: 'my-plugin',
install(editor) {
editor.commands.register('customCommand', ...)
}
})新增元素类型
需同时更新:
src/types/document.ts(类型定义)src/components/Editor/index.vue(渲染逻辑)
关键约束
- 禁止在渲染组件中直接修改
docModel - 禁止在
InputHandler中直接修改docModel(必须通过exec) DocumentModel的原子方法(insertText/deleteAt等)只能由 Command 调用
文档
更多功能说明与问题分析见 docs/ 目录。
