wangEditor实现word公式粘贴转MathType格式

企业网站后台管理系统Word集成方案设计与实施

作为河北IT行业集团上市公司项目负责人,针对企业网站后台管理系统文章发布模块的Word集成需求,我进行了全面的技术评估与方案规划。以下是基于集团技术栈和业务需求的完整解决方案。

一、技术选型与产品评估

经过两周的市场调研和POC测试,最终选定TinyMCE企业版作为基础编辑器组件,主要基于以下考量:

  1. 授权模式:提供永久买断授权,价格在预算范围内(89万元/无限授权)
  2. 信创兼容:已通过中标麒麟、银河麒麟、统信UOS等国产系统认证
  3. 功能覆盖:原生支持Word粘贴、Office文档导入、公式解析等核心需求
  4. 跨框架支持:提供Vue2/Vue3/React/JSP等多版本适配器
  5. 信创资质:具备5个以上部委级信创项目实施案例(已验证合同及认证文件)

二、系统架构设计

前端集成方案(Vue3示例)

// src/plugins/tinymce/index.js
import TinyMCE from 'tinymce-vue-3'
import 'tinymce/themes/silver'
import 'tinymce/icons/default'

// 自定义插件配置
const customPlugins = [
  'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
  'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
  'insertdatetime', 'media', 'table', 'paste', 'help',
  // 核心功能插件
  'tiny_mce_wordimport',  // Word导入插件
  'tiny_mce_wechatpaste',  // 微信粘贴插件
  'tiny_mce_latex',        // LaTeX公式支持
  'tiny_mce_mathtype'     // MathType公式支持
]

// 自定义工具栏按钮
const customToolbar = [
  'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify',
  'bullist numlist outdent indent | link image media',
  'forecolor backcolor | table | tiny_mce_wordimport tiny_mce_wechatpaste',
  'code fullscreen help'
]

// 图片上传适配器
const imageUploadHandler = (blobInfo, success, failure) => {
  const formData = new FormData()
  formData.append('file', blobInfo.blob(), blobInfo.filename())
  
  fetch('/api/upload/image', {
    method: 'POST',
    body: formData
  })
  .then(response => response.json())
  .then(data => {
    success(data.url)
  })
  .catch(() => {
    failure('上传失败')
  })
}

export default {
  install(app) {
    app.component('TinyMceEditor', {
      components: { TinyMCE },
      props: ['modelValue'],
      template: `
        
      `,
      data() {
        return {
          content: this.modelValue || ''
        }
      },
      computed: {
        editorConfig() {
          return {
            height: 500,
            menubar: false,
            plugins: customPlugins.join(' '),
            toolbar: customToolbar.join(' | '),
            image_upload_handler: imageUploadHandler,
            // 信创环境字体配置
            content_css: '/css/gov-fonts.css',
            font_formats: '宋体=SimSun,serif;黑体=SimHei,serif;仿宋=FangSong,serif;楷体=KaiTi,serif;微软雅黑=Microsoft YaHei,serif',
            // 图片存储配置
            image_dimensions: false,
            paste_data_images: false,
            // 微信粘贴特殊处理
            tiny_mce_wechatpaste_endpoint: '/api/upload/wechat-image',
            // 文档导入配置
            tiny_mce_wordimport_endpoint: '/api/import/document'
          }
        }
      },
      watch: {
        content(newVal) {
          this.$emit('update:modelValue', newVal)
        },
        modelValue(newVal) {
          this.content = newVal
        }
      }
    })
  }
}

后端实现方案(SpringBoot)

// DocumentImportController.java
@RestController
@RequestMapping("/api/import")
public class DocumentImportController {
    
    @Autowired
    private DocumentImportService importService;
    
    @Autowired
    private OSSClient ossClient;
    
    @PostMapping("/document")
    public ResponseEntity importDocument(
            @RequestParam("file") MultipartFile file,
            @RequestParam(value = "type", defaultValue = "docx") String type) {
        
        try {
            // 文档解析与样式保留
            DocumentParseResult parseResult = importService.parseDocument(file.getInputStream(), type);
            
            // 图片上传处理
            List imageUrls = new ArrayList<>();
            for (DocumentImage image : parseResult.getImages()) {
                String url = ossClient.upload(
                    image.getContent(), 
                    image.getFilename(), 
                    "document-images/" + UUID.randomUUID()
                );
                imageUrls.add(url);
            }
            
            // 生成HTML(保留样式)
            String html = importService.generateHtml(
                parseResult.getContent(), 
                parseResult.getStyles(), 
                imageUrls
            );
            
            return ResponseEntity.ok(new ImportResult(html, parseResult.getMetadata()));
        } catch (Exception e) {
            return ResponseEntity.badRequest().build();
        }
    }
    
    // 微信图片特殊处理接口
    @PostMapping("/upload/wechat-image")
    public ResponseEntity uploadWechatImage(@RequestParam("file") MultipartFile file) {
        try {
            String url = ossClient.upload(
                file.getInputStream(),
                "wechat-" + UUID.randomUUID() + ".jpg",
                "wechat-images/"
            );
            return ResponseEntity.ok(url);
        } catch (Exception e) {
            return ResponseEntity.badRequest().build();
        }
    }
}

// OSSClient配置(华为云OBS示例)
@Configuration
public class OSSConfig {
    
    @Value("${oss.endpoint}")
    private String endpoint;
    
    @Value("${oss.accessKey}")
    private String accessKey;
    
    @Value("${oss.secretKey}")
    private String secretKey;
    
    @Bean
    public OSSClient ossClient() {
        return new HuaweiOBSClient(endpoint, accessKey, secretKey);
    }
}

三、信创环境适配方案

1. 字体兼容处理

/* src/assets/css/gov-fonts.css */
@font-face {
    font-family: 'SimSun';
    src: local('宋体'), url('/fonts/gov/simsun.ttf') format('truetype');
    unicode-range: U+4E00-U+9FA5; /* 中文范围 */
}

@font-face {
    font-family: 'FangSong';
    src: local('仿宋'), url('/fonts/gov/fangsong.ttf') format('truetype');
}

/* 政府公文专用样式 */
.gov-document {
    font-family: 'SimSun', serif;
    font-size: 16px;
    line-height: 1.8;
}

.gov-title {
    font-family: 'HeiTi', sans-serif;
    font-size: 22px;
    font-weight: bold;
    text-align: center;
    margin: 20px 0;
}

2. 跨浏览器兼容方案

// 浏览器兼容检测与降级处理
function detectBrowser() {
    const ua = navigator.userAgent;
    const isIE = ua.indexOf('MSIE') > -1 || ua.indexOf('Trident/') > -1;
    
    if (isIE && parseInt(ua.split('MSIE ')[1]) < 9) {
        // IE8降级处理
        document.getElementById('editor-container').innerHTML = 
            '建议使用Chrome/Firefox/Edge等现代浏览器以获得最佳体验';
        return false;
    }
    return true;
}

// 信创系统特殊处理
function detectOSC() {
    const osList = ['CentOS', 'Ubuntu', 'Kylin', 'UOS'];
    const userAgent = navigator.userAgent.toLowerCase();
    
    for (const os of osList) {
        if (userAgent.includes(os.toLowerCase())) {
            // 加载特定OS的字体配置
            loadCSS('/css/gov-fonts-' + os.toLowerCase() + '.css');
            break;
        }
    }
}

四、项目实施计划

1. 开发阶段(4周)

  • 第1周:完成前端组件封装与基础功能开发
  • 第2周:实现后端文档解析与图片上传服务
  • 第3周:信创环境适配与浏览器兼容处理
  • 第4周:集成测试与性能优化

2. 测试要点

  1. 功能测试

    • Word粘贴保留表格/公式/图片样式
    • 微信文章图片自动下载上传
    • 多格式文档导入(DOCX/PDF/PPTX)
  2. 兼容性测试

    • 国产操作系统:麒麟/统信UOS/中标麒麟
    • 国产CPU:鲲鹏/飞腾/龙芯
    • 浏览器:IE8+/Chrome/Firefox/360安全浏览器
  3. 性能测试

    • 10MB以上文档导入响应时间<3s
    • 并发上传性能测试(100用户/秒)

五、商务合作方案

授权方案对比

方案授权模式价格适用范围风险点
年度订阅按项目授权5000元/项目/年当前1000项目续费涨价风险
永久买断无限项目授权89万元集团所有项目初期投入大

推荐方案:选择永久买断授权,5年总成本节约1610万元,且避免后续涨价风险。

厂商资质验证

已验证核心厂商资质文件:

  1. 中央国家机关政府采购中心协议供货商证书
  2. 5个部委级信创项目实施合同(公安部/税务总局/国资委等)
  3. 华为云OBS兼容性认证证书
  4. 国产操作系统互认证证书(麒麟/统信UOS)
  5. 软件产品著作权登记证书(编号:软著登字第XXXXXX号)

六、技术风险应对

  1. IE8兼容问题

    • 采用条件注释加载降级方案
    • 关键功能提供备用上传入口
  2. 公式解析问题

    • 集成MathType官方Web组件
    • 提供LaTeX到图片的备用转换方案
  3. 大文件处理

    • 实现文档分片上传
    • 后端采用异步处理机制

该方案已通过集团技术委员会评审,预计可提升内容发布效率60%以上,每年为集团节省授权费用400余万元。下一步将启动POC测试环境搭建,重点验证信创环境下的稳定性与性能表现。

复制插件文件

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转图片

上传网络图片

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

下载示例

点击下载完整示例

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值