word-editor
v1.0.1
Published
基于原生 JavaScript 开发的 A4 网页富文本与排版编辑器
Maintainers
Readme
Word Editor
基于原生 JavaScript 开发的 A4 网页排版富文本编辑器与电子合同签署组件。无外部运行时依赖,支持与主流前端框架集成。
核心特性
- 原生无外部依赖:基于标准原生 JavaScript 开发,不依赖任何第三方富文本内核或 UI 框架。
- A4 规格分页排版:遵循国际标准 A4 规格(210mm × 297mm),支持毫米级页边距配置、页眉页脚、动态页码、内容跨页自动分割与重流。
- 内核与视图解耦:排版内核与界面独立,内置 Ribbon 工具栏、页面属性栏与右键快捷菜单;亦可按需隐藏默认界面,结合 Element Plus、Ant Design 等组件库自定义操作面板。
- 跨端合同签署(ContractSigner):支持桌面端与移动端签署交互,提供待签位导览、手写签名画板、企业印章配置与签署存证数据导出。
- 领域链式 API:提供
editor.doc、editor.format、editor.table、editor.signer等模块化调用接口。 - 排版表格与无边框模式:支持表格行列增删与边框隐藏功能,编辑态显示辅助对齐虚线,导出与打印时边框完全隐藏,便于实现多方信息并排排版。
- HTML 导出与安全防护:支持导出带内联样式的独立 HTML 文档,内置 URL 协议白名单,拦截恶意伪协议。
- 完整的 TypeScript 支持:提供
index.d.ts与signer.d.ts类型声明文件,支持开发环境的代码补全与类型检查。
安装
npm install word-editor或使用 yarn / pnpm:
yarn add word-editor
# 或
pnpm add word-editor快速上手
1. 原生 HTML / JavaScript
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="node_modules/word-editor/dist/word-editor.css">
<style>
#editor-container { width: 100%; height: 100vh; }
</style>
</head>
<body>
<div id="toolbar-container"></div>
<div id="editor-container"></div>
<script type="module">
import { WordEditor } from 'word-editor';
const editor = new WordEditor('#editor-container', {
toolbarContainer: '#toolbar-container',
margins: { top: 25.4, bottom: 25.4, left: 25.4, right: 25.4 }, // 单位: 毫米 (mm)
headerText: '电子服务采购合同',
footerText: '第 [page] 页 / 共 [total] 页'
});
editor.on('change', (docData) => {
console.log('文档数据更新:', docData);
});
</script>
</body>
</html>2. 在 Vue 3 中使用
<template>
<div class="editor-shell">
<div ref="toolbarRef"></div>
<div ref="editorRef" class="editor-mount"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { WordEditor } from 'word-editor';
import 'word-editor/css';
const editorRef = ref(null);
const toolbarRef = ref(null);
let editor = null;
onMounted(() => {
editor = new WordEditor(editorRef.value, {
toolbarContainer: toolbarRef.value,
margins: { top: 25.4, bottom: 25.4, left: 25.4, right: 25.4 },
headerText: '技术开发合作框架协议',
footerText: '第 [page] 页 / 共 [total] 页'
});
editor.on('change', (docJson) => {
// 监听文档内容变更
});
});
onBeforeUnmount(() => {
if (editor) {
editor.destroy();
editor = null;
}
});
</script>
<style scoped>
.editor-shell {
display: flex;
flex-direction: column;
height: 100vh;
}
.editor-mount {
flex: 1;
overflow: hidden;
}
</style>3. 在 React 中使用
import React, { useEffect, useRef } from 'react';
import { WordEditor } from 'word-editor';
import 'word-editor/css';
export default function DocumentEditor() {
const editorMountRef = useRef(null);
const toolbarMountRef = useRef(null);
const editorInstanceRef = useRef(null);
useEffect(() => {
if (editorMountRef.current && !editorInstanceRef.current) {
editorInstanceRef.current = new WordEditor(editorMountRef.current, {
toolbarContainer: toolbarMountRef.current,
margins: { top: 25.4, bottom: 25.4, left: 25.4, right: 25.4 }
});
}
return () => {
if (editorInstanceRef.current) {
editorInstanceRef.current.destroy();
editorInstanceRef.current = null;
}
};
}, []);
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<div ref={toolbarMountRef} />
<div ref={editorMountRef} style={{ flex: 1, overflow: 'hidden' }} />
</div>
);
}4. 移动端合同签署(ContractSigner)
import { ContractSigner } from 'word-editor/signer';
import 'word-editor/signer/css';
const signer = new ContractSigner('#signer-container', {
contractData: contractJson, // 编辑器导出的文档 JSON 数据
currentRole: 'party_b', // 当前签署方角色 ('party_a' | 'party_b' | 'party_c' | 'custom')
title: '技术服务在线合同签署',
autoJumpToNext: true, // 签署完当前项后自动滚动至下一个待签项
onSignComplete: (evidence) => {
console.log('签署存证数据:', evidence);
// 包含时间戳、角色、签名/印章 Base64 数据、变量值与设备环境指纹
}
});模块分包与按需引入
根据不同业务场景,提供三种入口以适配体积与界面需求:
| 分包名称 | 模块入口 | 适用场景 | 样式导入语句 |
| :--- | :--- | :--- | :--- |
| 完整版 | 'word-editor' | 桌面端富文本排版,包含内置 Ribbon 工具栏与页面属性栏 | import 'word-editor/css'; |
| 核心版 | 'word-editor/core' | 自定义 UI 界面或轻量排版场景,不包含默认工具栏与侧边栏 | import 'word-editor/core/css'; |
| 签署版 | 'word-editor/signer' | 移动端或桌面端在线合同查看、签署与存证归档 | import 'word-editor/signer/css'; |
链式 API 调用范例
编辑器实例挂载了细分领域的 API 模块,支持链式与模块化调用:
// 1. 文档数据与导出 (DocAPI)
const docJson = editor.doc.getJson();
const cleanHtml = editor.doc.getHtml(); // 获取包含内联样式的正文 HTML 片段
const standaloneHtml = editor.doc.getFullHtml();// 获取包含 DOCTYPE 的完整单页 HTML
const plainText = editor.doc.getText(); // 获取正文纯文本
const totalWords = editor.doc.getWordCount(); // 获取当前字数
editor.doc.setHtml('<p>插入富文本内容</p>');
// 2. 富文本排版 (FormatAPI)
editor.format
.bold()
.color('#185abd')
.fontSize('18px')
.lineHeight('1.75')
.align('center');
// 3. 表格操作 (TableAPI)
editor.table.insert(3, 4); // 插入 3 行 4 列标准表格
editor.table.insertRow('after'); // 在当前行后插入新行
editor.table.deleteRow(); // 删除当前行
editor.table.deleteTable(); // 删除当前表格
editor.table.toggleBorder(); // 切换表格边框显示/隐藏
editor.table.setBorderless(null, true); // 设置表格为无边框排版模式
// 4. 印章与签名项管理 (SignerAPI)
editor.signer.addSign({ role: 'party_a', label: '甲方经办人签名' });
editor.signer.addSeal({ role: 'party_b', label: '乙方企业公章' });
editor.signer.remove('layer_id');
// 5. 历史记录 (HistoryAPI)
if (editor.historyApi.canUndo()) {
editor.historyApi.undo();
}自定义界面与第三方组件库集成
编辑器采用核心引擎与视图分离结构,默认界面均可配置或替换:
1. 禁用内置面板
const editor = new WordEditor('#editor-mount', {
toolbarContainer: '#custom-toolbar', // 使用自定义容器挂载默认工具栏
sidebarContainer: null, // 传入 null 禁用默认右侧属性栏
contractPropsContainer: null, // 传入 null 禁用默认印章抽屉
enableContextMenu: true // 启用右键菜单
});2. 结合 Element Plus / Ant Design 实现自定义面板
<template>
<div class="workspace">
<div ref="editorMount"></div>
<!-- 自定义 Element Plus 印签属性抽屉 -->
<el-drawer v-model="drawerVisible" title="配置签署项" :size="320">
<el-form v-if="activeLayer" label-position="top">
<el-form-item label="标签名称">
<el-input v-model="activeLayer.label" @input="syncLayer" />
</el-form-item>
<el-form-item label="签署角色">
<el-select v-model="activeLayer.role" @change="syncLayer">
<el-option label="甲方" value="party_a" />
<el-option label="乙方" value="party_b" />
</el-select>
</el-form-item>
<el-button type="danger" @click="deleteLayer">删除项</el-button>
</el-form>
</el-drawer>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { WordEditor } from 'word-editor';
import 'word-editor/css';
const editorMount = ref(null);
const drawerVisible = ref(false);
const activeLayer = ref(null);
let editor = null;
onMounted(() => {
editor = new WordEditor(editorMount.value, {
contractPropsContainer: null // 禁用默认面板
});
// 监听印签图层选中事件
editor.on('signseal:selected', (layer) => {
activeLayer.value = { ...layer };
drawerVisible.value = true;
});
// 监听取消选择事件
editor.on('signseal:deselected', () => {
drawerVisible.value = false;
activeLayer.value = null;
});
});
function syncLayer() {
if (activeLayer.value?.id) {
editor.updateSignSealLayer(activeLayer.value.id, activeLayer.value);
}
}
function deleteLayer() {
if (activeLayer.value?.id) {
editor.removeSignSealLayer(activeLayer.value.id);
drawerVisible.value = false;
}
}
</script>API 参考
WordEditor
初始化参数 (WordEditorOptions)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| container | string \| HTMLElement | 必填 | 编辑器画布挂载容器 DOM 或 CSS 选择器 |
| pageSize | string | 'A4' | 纸张规格(当前支持标准 A4) |
| margins | Margins | 25.4mm 各边 | 初始页边距配置 { top, bottom, left, right } (单位: mm) |
| headerText | string | "" | 默认页眉文本内容 |
| footerText | string | "" | 默认页脚文本,支持 [page] 和 [total] 动态占位符 |
| showHeader | boolean | true | 是否渲染页眉 |
| showFooter | boolean | true | 是否渲染页脚 |
| toolbarContainer | string \| HTMLElement | null | 顶部 Ribbon 工具栏挂载节点,传入 null 时不渲染 |
| sidebarContainer | string \| HTMLElement | null | 右侧页面属性栏挂载节点,传入 null 时不渲染 |
| contractPropsContainer | string \| HTMLElement | null | 印签属性抽屉挂载节点,传入 null 时不渲染 |
| enableContextMenu | boolean | true | 是否开启右键上下文菜单 |
| variables | Object | {} | 预设合同动态文本变量键值字典 (例如: { party_a: "某某公司" }) |
常用实例方法
| 方法 | 返回值 | 说明 |
| :--- | :--- | :--- |
| getData() / exportJson() | Object | 导出完整文档结构化 JSON(正文 AST、排版边距、印签图层、变量字典) |
| setData(json) / loadJson(json) | void | 加载并恢复结构化 JSON 数据 |
| exportHtml(options?) | string | 导出带有内联样式的 HTML 片段或完整单页文档 |
| getHtml() | string | 获取正文富文本 HTML 片段 |
| loadHtml(html) | void | 导入并解析外部 HTML 富文本片段 |
| openPreview(options?) | void | 打开全屏 A4 打印预览视口 |
| closePreview() | void | 关闭打印预览弹窗 |
| undo() / redo() | void | 撤销 / 重做操作 |
| saveState(options?) | boolean | 将当前文档快照压入撤销历史栈 |
| getPageElement() | HTMLElement | 获取页面根 DOM 节点 |
| getContentElement() | HTMLElement | 获取正文内容编辑区 DOM 节点 (.page-content) |
| getBlocks() | HTMLElement[] | 获取所有一级块级段落 DOM 元素数组 |
| getWordCount() | number | 获取当前文档纯正文字符总数 |
| insertVariable(key, defaultVal?) | HTMLElement | 在当前光标处插入动态文本变量标记 (例如 {{party_name}}) |
| addSignSealLayer(options) | Object | 添加印签图层(别名 insertSignSealLayer) |
| updateSignSealLayer(id, data) | void | 更新指定印签图层属性 |
| removeSignSealLayer(id) | void | 删除指定印签图层 |
| on(event, handler) | this | 订阅内部事件 (change, wordCount, signseal:selected 等) |
| off(event, handler) | this | 取消订阅内部事件 |
| destroy() | void | 销毁实例,解绑全部 DOM 事件与定时器,释放内存 |
ContractSigner
初始化参数 (ContractSignerOptions)
| 参数 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| container | string \| HTMLElement | 必填 | 签署器挂载容器 DOM 或 CSS 选择器 |
| contractData | Object | {} | 由编辑器导出的完整合同 JSON 数据 |
| currentRole | string | 'party_b' | 当前签署主体身份代码 ('party_a', 'party_b', 'party_c', 'custom') |
| title | string | '电子合同在线签署' | 顶部标题栏展示文案 |
| autoJumpToNext | boolean | true | 单项签署完成后是否自动平滑滚动并聚焦至下一个未签项 |
| onSignComplete | Function | null | 全部签署完毕并提交时的存证回调函数 (evidence) => {} |
| onLayerSigned | Function | null | 单个待签位签署完成时的回调函数 (layerItem) => {} |
常用实例方法
| 方法 | 返回值 | 说明 |
| :--- | :--- | :--- |
| load(contractData) | void | 重新载入合同数据并重新排版渲染 |
| setRole(newRole) | void | 切换当前签署角色并刷新待签项高亮状态 |
| jumpToNextSign() | boolean | 滚动至下一个待签位并触发提示效果 |
| getProgress() | SigningProgress | 获取签署进度 { totalMyCount, signedCount, remainingCount, isAllSigned } |
| getEvidenceData() | SignEvidenceData | 获取当前合同签署存证数据 (包含时间戳、签名数据、变量与设备指纹) |
| exportSignedData() | Object | 导出签署完成后的合同全量结构化数据 |
| setVariable(key, value) | void | 设置变量值并同步更新至所有页面 |
| destroy() | void | 销毁签署器,释放全部事件与观察器 |
辅助函数与排版常量
项目公开导出了底层排版算法函数与度量常量:
import {
docToJson, // 文档数据序列化函数
jsonToDom, // 将结构化 JSON 转为块级 DOM 元素数组
paginateContent, // A4 核心分页重流计算函数
createPageElement, // 创建单个 A4 纸张页面 DOM 节点
estimatePages, // 估算标准 A4 规格下的总物理页数
openPreviewModal, // 打开全屏打印预览模态框
closePreviewModal, // 关闭打印预览模态框
MM_TO_PX, // 毫米转像素基准系数 (3.78 @ 96 DPI)
PX_TO_MM, // 像素转毫米基准系数 (0.264583 @ 96 DPI)
PAGE_WIDTH_PX, // 标准 A4 像素宽度 (794px)
PAGE_HEIGHT_PX, // 标准 A4 像素高度 (1123px)
DEFAULT_MARGINS, // 默认页边距配置
DEFAULT_SETTINGS // 默认编辑器排版参数
} from 'word-editor';数据存证与安全
- 自包含存证数据:手写签名笔迹与印章图像统一编码为 Base64 DataURL 保存在图层数据中,避免对外部文件存储的强依赖。
- 协议过滤:内置 URL 白名单机制,拦截
javascript:、vbscript:、data:text/html等脚本伪协议。 - 环境指纹与存证:签署完成时记录客户端 User-Agent、屏幕分辨率、签署时间戳及文档内容指纹,便于后续配合服务端签名进行验证。
许可证
本项目采用 MIT 许可证。
