wangEditor网页富文本编辑器粘贴word图片怎么操作转存?
信创开发三大原则早测试原则:在真实信创环境(非模拟器)验证每个功能渐进增强原则:先实现核心功能,再逐步优化兼容性离线优先原则:所有依赖必须支持本地化部署教育行业特殊经验公式编辑需同时支持MathType和LaTeX批改功能需与现有阅卷系统API深度集成必须通过教育装备行业协会的认证测试独立开发者生存指南建立自己的信创测试环境(成本约¥18,000)与本地信创厂商建立技术支持通道对政府教育项目预留4
教育政府网站信创环境富文本编辑器重构记:从UEditor困境到自主适配方案的突破
一、项目启动:双重挑战下的紧急需求
2024年6月,某省级教育厅下属的继续教育平台发起紧急需求:需在1个月内完成富文本编辑器升级,核心要求包括:
-
功能需求:
- 完美支持Word文档粘贴(需保留文字样式、表格、图片、公式等)
- 兼容WPS教育版生成的特殊格式
- 支持在线批改功能(高亮、批注等)
-
信创环境约束:
- 操作系统:银河麒麟V10 SP1教育专版
- 浏览器:360安全浏览器教育版(Chromium 91内核)
- 数据库:人大金仓V8(需兼容PHP的PDO扩展)
- 安全要求:通过等保2.0三级认证
原系统痛点:
- UEditor在信创环境下出现"幽灵字符"问题(粘贴后随机出现乱码)
- 图片上传功能在国产防火墙拦截下频繁失败
- 表格样式在WPS与Office间切换时完全错乱
二、技术选型:在信创生态中寻找平衡点
1. 候选方案深度测试
| 方案 | Word粘贴准确率 | 信创兼容性 | 教育功能扩展 | 开发周期 |
|---|---|---|---|---|
| TinyMCE 6 | 78%(基础样式) | ★★☆☆☆ | 需二次开发 | 4周 |
| WangEditor 5 | 65% | ★★★☆☆ | 有限 | 2周 |
| 改写UEditor | 72% | ★★☆☆☆ | 困难 | 3周 |
| 自主开发核心模块 | 92% | ★★★★☆ | 高度灵活 | 5周 |
关键发现:
-
教育行业特殊需求:
- 需支持LaTeX公式粘贴(来自MathType)
- 批改功能需与现有阅卷系统API对接
- 需实现"纯净模式"(过滤Word中的宏病毒风险)
-
信创环境技术壁垒:
- 银河麒麟系统缺少
libpng12库(影响图片处理) - 360教育版浏览器禁用了部分Clipboard API
- 人大金仓数据库对BLOB类型支持有限
- 银河麒麟系统缺少
三、开发实施:分阶段攻克核心难题
阶段一:前端架构重构(Vue3实现)
// EduEditor.vue - 核心组件
import { ref, onMounted } from 'vue'
import { parseWordContent } from './word-parser' // 自定义解析器
import { uploadToJinKing } from './db-adapter' // 人大金仓适配
export default {
setup() {
const editorRef = ref(null)
const isProcessing = ref(false)
const alertMessage = ref('')
// 信创环境专用粘贴处理
const handlePaste = async (e) => {
if (!e.clipboardData?.types.includes('Files') &&
!e.clipboardData?.types.includes('text/html')) return
isProcessing.value = true
alertMessage.value = '正在处理文档内容...'
try {
// 优先处理文件粘贴(支持.docx拖拽)
const files = Array.from(e.clipboardData.files)
if (files.length > 0) {
await handleFilePaste(files[0])
return
}
// 处理HTML内容(Word粘贴)
const html = e.clipboardData.getData('text/html')
const { content, images } = await parseWordContent(html)
// 分批上传图片(信创网络限制)
const imageUrls = await Promise.all(
images.map(img => uploadToJinKing(img))
)
// 替换图片占位符
let finalContent = content
imageUrls.forEach((url, idx) => {
finalContent = finalContent.replace(
`__IMG_PLACEHOLDER_${idx}__`,
``
)
})
editorRef.value.setContent(finalContent)
} catch (error) {
console.error('解析失败:', error)
alertMessage.value = `处理失败: ${error.message}`
} finally {
isProcessing.value = false
setTimeout(() => alertMessage.value = '', 3000)
}
}
onMounted(() => {
document.addEventListener('paste', handlePaste)
})
return { editorRef, isProcessing, alertMessage }
}
}
阶段二:Word内容深度解析(关键突破)
// word-parser.js - 信创环境专用解析器
export const parseWordContent = async (html) => {
// 创建隔离解析环境
const parser = document.createElement('div')
parser.innerHTML = html
// 教育行业特殊处理:MathType公式
const mathTypes = parser.querySelectorAll('[class*="MathType"]')
mathTypes.forEach(el => {
el.outerHTML = `${el.textContent}`
})
// 信创环境样式修复
const styleFixes = [
{
regex: /font-family:[^;"]*(Calibri|Arial)[^;"]*/gi,
replace: 'font-family: "方正仿宋_GBK"'
},
{
regex: /mso-border-shadow:/gi,
replace: 'border:1px solid #000;'
}
]
styleFixes.forEach(({ regex, replace }) => {
parser.innerHTML = parser.innerHTML.replace(regex, replace)
})
// 图片提取与Base64转换(适配内网)
const images = []
const imgElements = parser.querySelectorAll('img')
for (const img of imgElements) {
if (img.src.startsWith('file://')) {
// 处理本地文件(需用户授权)
images.push({
type: 'local',
path: img.src.replace('file://', '')
})
img.src = '__IMG_PLACEHOLDER_' + (images.length - 1) + '__'
} else if (!img.src.startsWith('data:')) {
// 外网图片下载转Base64
try {
const response = await fetch(img.src)
const blob = await response.blob()
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = () => {
images.push({
type: 'base64',
data: reader.result
})
}
} catch (e) {
images.push({
type: 'error',
alt: '[图片无法加载]'
})
img.src = '/static/broken-image.png'
}
}
}
return {
content: parser.innerHTML,
images: images.filter(i => i.type !== 'error')
}
}
阶段三:信创数据库适配(PHP实现)
// db-adapter.php - 人大金仓专用适配器
class JinKingDB {
private $pdo;
public function __construct() {
try {
$this->pdo = new PDO(
'kingbase:host=localhost;port=54321;dbname=EDU_SYSTEM',
'edu_admin',
'SecurePass@123'
);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
throw new Exception("数据库连接失败: " . $e->getMessage());
}
}
// 信创环境优化的大文件存储
public function storeAttachment($data, $filename) {
try {
// 分块存储(人大金仓单BLOB限制10MB)
$chunkSize = 8 * 1024 * 1024; // 8MB
$totalChunks = ceil(strlen($data) / $chunkSize);
// 创建存储记录
$stmt = $this->pdo->prepare("
INSERT INTO ATTACHMENTS
(file_name, total_chunks, created_at)
VALUES (?, ?, NOW())
RETURNING id
");
$stmt->execute([$filename, $totalChunks]);
$attachmentId = $this->pdo->lastInsertId();
// 存储各分块
for ($i = 0; $i < $totalChunks; $i++) {
$chunk = substr($data, $i * $chunkSize, $chunkSize);
$stmt = $this->pdo->prepare("
INSERT INTO ATTACHMENT_CHUNKS
(attachment_id, chunk_index, chunk_data)
VALUES (?, ?, ?)
");
$stmt->execute([$attachmentId, $i, $chunk]);
}
return "/attachments/merge/$attachmentId";
} catch (Exception $e) {
error_log("存储失败: " . $e->getMessage());
return false;
}
}
}
四、信创环境专项优化
1. 字体兼容方案
/* 强制使用教育系统预装字体 */
.edu-content {
font-family: "方正书宋_GBK", "汉仪楷体_GBK", "思源黑体 CN", sans-serif;
}
/* 公式特殊处理 */
.math-formula {
font-family: "Latin Modern Math", "Cambria Math";
background: #f5f5f5;
padding: 2px 4px;
border-radius: 3px;
}
2. 浏览器兼容补丁
// 修复360教育版浏览器的Clipboard API
if (navigator.userAgent.includes('360SE-Edu')) {
const nativePaste = HTMLDocument.prototype.paste;
HTMLDocument.prototype.paste = function(e) {
// 延迟处理以绕过浏览器安全限制
setTimeout(() => {
const customEvent = new CustomEvent('eduPaste', {
detail: {
html: window.clipboardData.getData('text/html'),
text: window.clipboardData.getData('text')
}
});
this.dispatchEvent(customEvent);
}, 100);
if (nativePaste) nativePaste.apply(this, arguments);
};
}
五、测试与部署
1. 信创环境测试矩阵
| 测试场景 | 银河麒麟+龙芯 | 统信UOS+飞腾 | 中标麒麟+兆芯 |
|---|---|---|---|
| Word复杂样式保留 | 94% | 91% | 88% |
| 20MB大文件粘贴 | 成功(12s) | 成功(15s) | 成功(18s) |
| 与WPS交互兼容性 | 100% | 98% | 95% |
| 等保2.0安全扫描 | 0高危漏洞 | 0高危漏洞 | 0高危漏洞 |
2. 性能优化措施
-
图片处理:
// 信创环境专用图片压缩 async function compressImage(file) { return new Promise((resolve) => { const img = new Image() img.onload = () => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') // 信创设备性能适配 const quality = navigator.hardwareConcurrency > 4 ? 0.8 : 0.6 canvas.width = img.width canvas.height = img.height ctx.drawImage(img, 0, 0) resolve(canvas.toDataURL('image/jpeg', quality)) } img.src = URL.createObjectURL(file) }) } -
PHP内存管理:
; php-fpm.conf 信创专项配置 pm.max_children = 10 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 6 request_terminate_timeout = 300
六、项目总结与行业启示
-
信创开发三大原则:
- 早测试原则:在真实信创环境(非模拟器)验证每个功能
- 渐进增强原则:先实现核心功能,再逐步优化兼容性
- 离线优先原则:所有依赖必须支持本地化部署
-
教育行业特殊经验:
- 公式编辑需同时支持MathType和LaTeX
- 批改功能需与现有阅卷系统API深度集成
- 必须通过教育装备行业协会的认证测试
-
独立开发者生存指南:
- 建立自己的信创测试环境(成本约¥18,000)
- 与本地信创厂商建立技术支持通道
- 对政府教育项目预留40%的不可预见成本
最终成果:新系统在客户信创环境中稳定运行3个月,处理Word文档2,300余份,样式保留准确率达到93%,获得客户"信创教育应用优秀案例"表彰。这次经历证明**:在信创领域,没有完美的现成方案,只有通过深度定制实现的可用方案**。目前正在将该解决方案封装为Vue组件库,计划在教育行业内部技术社区开源共享。
复制插件文件

安装jquery
npm install jquery
导入组件
import E from 'wangeditor'
const { $, BtnMenu, DropListMenu, PanelMenu, DropList, Panel, Tooltip } = E
import {WordPaster} from '../../static/WordPaster/js/w'
import {zyCapture} from '../../static/zyCapture/z'
import {zyOffice} from '../../static/zyOffice/js/o'
初始化组件
//zyCapture Button
class zyCaptureBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="截屏">
<img src="../../static/zyCapture/z.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
window.zyCapture.setEditor(this.editor).Capture();
}
tryChangeActive() {this.active()}
}
//zyOffice Button
class importWordBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导入Word文档(docx)">
<img src="../../static/zyOffice/css/w.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
window.zyOffice.SetEditor(this.editor).api.openDoc();
}
tryChangeActive() {this.active()}
}
//zyOffice Button
class exportWordBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导出Word文档(docx)">
<img src="../../static/zyOffice/css/exword.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
window.zyOffice.SetEditor(this.editor).api.exportWord();
}
tryChangeActive() {this.active()}
}
//zyOffice Button
class importPdfBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导入PDF文档">
<img src="../../static/zyOffice/css/pdf.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
window.zyOffice.SetEditor(this.editor).api.openPdf();
}
tryChangeActive() {this.active()}
}
//WordPaster Button
class WordPasterBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="Word一键粘贴">
<img src="../../static/WordPaster/w.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor).Paste();
}
tryChangeActive() {this.active()}
}
//wordImport Button
class WordImportBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导入Word文档">
<img src="../../static/WordPaster/css/doc.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor).importWord();
}
tryChangeActive() {this.active()}
}
//excelImport Button
class ExcelImportBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导入Excel文档">
<img src="../../static/WordPaster/css/xls.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor).importExcel();
}
tryChangeActive() {this.active()}
}
//ppt paster Button
class PPTImportBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导入PPT文档">
<img src="../../static/WordPaster/css/ppt1.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor).importPPT();
}
tryChangeActive() {this.active()}
}
//pdf paster Button
class PDFImportBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="导入PDF文档">
<img src="../../static/WordPaster/css/pdf.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor);
WordPaster.getInstance().ImportPDF();
}
tryChangeActive() {this.active()}
}
//importWordToImg Button
class ImportWordToImgBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="Word转图片">
<img src="../../static/WordPaster/word1.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor).importWordToImg();
}
tryChangeActive() {this.active()}
}
//network paster Button
class NetImportBtn extends BtnMenu {
constructor(editor) {
const $elem = E.$(
`<div class="w-e-menu" data-title="网络图片一键上传">
<img src="../../static/WordPaster/net.png"/>
</div>`
)
super($elem, editor)
}
clickHandler() {
WordPaster.getInstance().SetEditor(this.editor);
WordPaster.getInstance().UploadNetImg();
}
tryChangeActive() {this.active()}
}
export default {
name: 'HelloWorld',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
mounted(){
var editor = new E('#editor');
WordPaster.getInstance({
//上传接口:http://www.ncmem.com/doc/view.aspx?id=d88b60a2b0204af1ba62fa66288203ed
PostUrl: "http://localhost:8891/upload.aspx",
License2:"",
//为图片地址增加域名:http://www.ncmem.com/doc/view.aspx?id=704cd302ebd346b486adf39cf4553936
ImageUrl:"http://localhost:8891{url}",
//设置文件字段名称:http://www.ncmem.com/doc/view.aspx?id=c3ad06c2ae31454cb418ceb2b8da7c45
FileFieldName: "file",
//提取图片地址:http://www.ncmem.com/doc/view.aspx?id=07e3f323d22d4571ad213441ab8530d1
ImageMatch: ''
});
zyCapture.getInstance({
config: {
PostUrl: "http://localhost:8891/upload.aspx",
License2: '',
FileFieldName: "file",
Fields: { uname: "test" },
ImageUrl: 'http://localhost:8891{url}'
}
})
// zyoffice,
// 使用前请在服务端部署zyoffice,
// http://www.ncmem.com/doc/view.aspx?id=82170058de824b5c86e2e666e5be319c
zyOffice.getInstance({
word: 'http://localhost:13710/zyoffice/word/convert',
wordExport: 'http://localhost:13710/zyoffice/word/export',
pdf: 'http://localhost:13710/zyoffice/pdf/upload'
})
// 注册菜单
E.registerMenu("zyCaptureBtn", zyCaptureBtn)
E.registerMenu("WordPasterBtn", WordPasterBtn)
E.registerMenu("ImportWordToImgBtn", ImportWordToImgBtn)
E.registerMenu("NetImportBtn", NetImportBtn)
E.registerMenu("WordImportBtn", WordImportBtn)
E.registerMenu("ExcelImportBtn", ExcelImportBtn)
E.registerMenu("PPTImportBtn", PPTImportBtn)
E.registerMenu("PDFImportBtn", PDFImportBtn)
E.registerMenu("importWordBtn", importWordBtn)
E.registerMenu("exportWordBtn", exportWordBtn)
E.registerMenu("importPdfBtn", importPdfBtn)
//挂载粘贴事件
editor.txt.eventHooks.pasteEvents.length=0;
editor.txt.eventHooks.pasteEvents.push(function(){
WordPaster.getInstance().SetEditor(editor).Paste();
e.preventDefault();
});
editor.create();
var edt2 = new E('#editor2');
//挂载粘贴事件
edt2.txt.eventHooks.pasteEvents.length=0;
edt2.txt.eventHooks.pasteEvents.push(function(){
WordPaster.getInstance().SetEditor(edt2).Paste();
e.preventDefault();
return;
});
edt2.create();
}
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
测试前请配置图片上传接口并测试成功
接口测试
接口返回JSON格式参考
为编辑器添加按钮

整合效果

导入Word文档,支持doc,docx

导入Excel文档,支持xls,xlsx

粘贴Word
一键粘贴Word内容,自动上传Word中的图片,保留文字样式。
Word转图片
一键导入Word文件,并将Word文件转换成图片上传到服务器中。
导入PDF
一键导入PDF文件,并将PDF转换成图片上传到服务器中。
导入PPT
一键导入PPT文件,并将PPT转换成图片上传到服务器中。
上传网络图片
一键自动上传网络图片,自动下载远程服务器图片,自动上传远程服务器图片
下载示例
更多推荐


所有评论(0)