wangEditor医院CMS站群中如何实现芯片制造文档导入转存?

《一个独立开发者的"Word粘贴大作战":从抓狂到真香的技术冒险》
——上海野生程序员的血泪实录

第一章:当甲方爸爸说"要能粘贴Word图片"时,我的表情是这样的

事情是这样的:我独自开发了一个网站(前端Vue2 + wangEditor,后端PHP + MySQL,服务器在阿里云),结果甲方突然提出灵魂三连:

  1. “用户要能直接从Word粘贴内容,图片自动上传到服务器!”
  2. “还要支持导入Word/Excel/PPT/PDF,图片不能丢,样式不能变!”
  3. “对了,后端得用对象存储(阿里云/腾讯云/华为云…都行)!”

我当时的内心OS:“您直接说想要个Office 365得了?”
但作为独立开发者,我只能默默打开GitHub,开始我的"技术考古"之旅…


第二章:前端篇——wangEditor的"粘贴魔法"

1. 拦截粘贴事件:和Word的"脏HTML"斗智斗勇

wangEditor默认粘贴Word内容会带一堆等Office专属标签,直接显示会乱码。我的解决方案:

// 在wangEditor配置中拦截paste事件
editor.config.pasteFilterStyle = false; // 先允许样式
editor.config.pasteIgnoreImg = false; // 不忽略图片
editor.config.customPaste = (editor, html) => {
  // 1. 清理Office冗余标签
  let cleanHtml = html.replace(/<\/o:p>/g, '')
                    .replace(//g, '');
  
  // 2. 提取图片并上传(后面细说)
  return cleanHtml;
};
2. 图片自动上传:从Base64到Blob的逆袭

Word粘贴的图片默认是data:image/png;base64格式,直接存数据库会炸(Base64比二进制大33%!)。我的操作:

// 提取Base64图片并上传
const extractImages = (html) => {
  const div = document.createElement('div');
  div.innerHTML = html;
  const imgs = div.querySelectorAll('img[src^="data:image"]');
  
  imgs.forEach(img => {
    const base64 = img.src.split(',')[1];
    const blob = base64ToBlob(base64); // 自定义转换函数
    
    // 调用后端API上传
    fetch('/api/upload', { method: 'POST', body: blob })
      .then(res => res.json())
      .then(data => {
        img.src = data.url; // 替换为服务器URL
      });
  });
  
  return div.innerHTML;
};

效果:用户粘贴Word后,图片自动上传到服务器,编辑器里显示的是可访问的URL,样式(字体、颜色)完美保留!


第三章:后端篇——PHP和对象存储的"相爱相杀"

1. 接收图片:从Blob到文件存储

PHP处理上传的Blob需要点技巧:

// api/upload.php
$input = file_get_contents('php://input');
$filename = uniqid() . '.png';
file_put_contents('/tmp/' . $filename, $input);

// 调用对象存储SDK(以阿里云OSS为例)
require_once 'aliyun-oss-sdk.php';
$ossClient = new OSS\OssClient('key', 'secret', 'endpoint');
$ossClient->putObject('your-bucket', 'uploads/' . $filename, fopen('/tmp/' . $filename, 'r'));

echo json_encode(['url' => 'https://your-bucket.oss-cn-shanghai.aliyuncs.com/uploads/' . $filename]);
2. 文档导入:Word/Excel/PPT/PDF全制霸

这里我用了Unoconv(LibreOffice的命令行工具)转换文档为HTML:

// 导入Word/Excel/PPT
function convertToHtml($filePath) {
  $outputPath = '/tmp/' . basename($filePath, '.docx') . '.html';
  exec("unoconv -f html -o /tmp $filePath");
  return file_get_contents($outputPath);
}

// 导入PDF(用pdf2htmlEX)
function convertPdfToHtml($filePath) {
  $outputPath = '/tmp/' . basename($filePath, '.pdf') . '.html';
  exec("pdf2htmlEX --zoom 1.3 $filePath $outputPath");
  return file_get_contents($outputPath);
}

坑点

  • Unoconv需要安装LibreOffice,服务器上配置略麻烦
  • PDF转换后的HTML可能很乱,需要用CSS重置样式

第四章:同行交流——QQ群里的"技术互救"

在开发过程中,我加了几个技术群(比如QQ群:223813913),发现大家的问题出奇地一致:

  • A君:“Word粘贴的图片怎么去掉EXIF信息?”
  • B妹:“Excel导入后表格线全没了怎么办?”
  • C大佬:“推荐用Mammoth.js处理Word,比Unoconv轻量!”

我的感悟:独立开发不孤单,群里问一句能省半天调试时间!


最终成果:甲方爸爸满意,我也能睡个好觉了

现在网站支持:
Word粘贴:内容+图片自动上传,样式保留
多文档导入:Word/Excel/PPT/PDF一键转换
对象存储:图片存阿里云OSS,速度飞起

技术栈总结

  • 前端:Vue2 + wangEditor(粘贴拦截+图片上传)
  • 后端:PHP(文件处理+对象存储SDK)
  • 工具:Unoconv(文档转换)+ pdf2htmlEX(PDF处理)

最后广告
如果你也在搞富文本编辑器+Word粘贴,欢迎加入QQ群:223813913,一起吐槽技术难题,分享解决方案!

(完)
——上海野生程序员·老张

复制插件文件

WordPaster插件文件夹
安装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格式参考

为编辑器添加按钮

  components: { Editor, Toolbar },
  data () {
    return {
      editor: null,
      html: 'dd',
      toolbarConfig: {
        insertKeys: {
          index: 0,
          keys: ['zycapture', 'wordpaster', 'pptimport', 'pdfimport', 'netimg', 'importword', 'exportword', 'importpdf']
        }
      },
      editorConfig: {
        placeholder: ''
      },
      mode: 'default' // or 'simple'
    }
  },

整合效果

wangEditor4整合效果

导入Word文档,支持doc,docx

粘贴Word和图片

导入Excel文档,支持xls,xlsx

粘贴Word和图片

粘贴Word

一键粘贴Word内容,自动上传Word中的图片,保留文字样式。
粘贴Word和图片

Word转图片

一键导入Word文件,并将Word文件转换成图片上传到服务器中。
导入Word转图片

导入PDF

一键导入PDF文件,并将PDF转换成图片上传到服务器中。
导入PDF转图片

导入PPT

一键导入PPT文件,并将PPT转换成图片上传到服务器中。
导入PPT转图片

上传网络图片

一键自动上传网络图片,自动下载远程服务器图片,自动上传远程服务器图片
自动上传网络图片

下载示例

点击下载完整示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值