金融系统站群如何用TinyMCE解决Word公式粘贴乱码问题?

新能源集团CMS文档管理解决方案

作为江苏新能源行业集团上市公司项目负责人,我们当前面临一个重要的系统功能升级需求:在企业网站的后台管理系统文章发布模块中增加Word粘贴、Word文档导入以及微信公众号内容粘贴功能。


一、方案概述(央企级适配版)

本方案基于TinyMCE编辑器+Java JSP后端+信创中间件技术栈,实现以下核心能力:
一键粘贴:Word/公众号内容粘贴(图片自动上传OSS,样式/公式完整保留)
全格式导入:Word/Excel/PPT/PDF导入(支持形状组、Latex/MathType公式、表格等)
信创适配:兼容Windows/macOS/Linux/统信UOS/麒麟OS,支持x86/ARM/龙芯架构
多云存储:无缝集成阿里云OSS、华为云OBS、腾讯云COS等(配置即切换)
安全合规:图片二进制存储(非Base64)、数据加密传输、等保三级认证


二、前端实现(TinyMCE插件集成)

1. 插件目录结构

/tiny-mce/plugins/doc_manager/
├─ plugin.js          # 核心插件逻辑
├─ dialog.html        # 多功能操作面板
├─ style.css          # 样式文件
└─ lib/               # 依赖库(tinymce-iconfont、公式渲染引擎)

2. 核心代码(plugin.js)

// 注册TinyMCE插件(兼容Vue3/React)
tinymce.PluginManager.add('doc_manager', function(editor, url) {
    // 创建万能按钮(适配信创浏览器)
    const btn = editor.ui.registry.addButton('doc_manager', {
        icon: 'document',
        tooltip: '文档导入/粘贴',
        onAction: () => showDocDialog(editor)
    });

    // 显示多功能弹窗(支持IE8+及国产浏览器)
    function showDocDialog(editor) {
        editor.windowManager.open({
            title: '文档管理神器',
            width: 1000,
            height: 700,
            body: {
                type: 'tabpanel',
                tabs: [{
                    title: '粘贴内容',
                    items: [{
                        type: 'textarea',
                        name: 'pasteContent',
                        label: '粘贴Word/公众号内容',
                        multiline: true,
                        maxHeight: 300
                    }, {
                        type: 'button',
                        text: '提取内容',
                        onclick: () => processPaste(editor)
                    }, {
                        type: 'htmlpanel',
                        htmlId: 'pastePreview'
                    }]
                }, {
                    title: '导入文档',
                    items: [{
                        type: 'filepicker',
                        name: 'fileUpload',
                        label: '选择文档(Word/Excel/PPT/PDF)',
                        onchange: (e) => handleFileUpload(e, editor)
                    }, {
                        type: 'htmlpanel',
                        htmlId: 'filePreview'
                    }]
                }, {
                    title: '公众号导入',
                    items: [{
                        type: 'textbox',
                        name: 'wechatUrl',
                        label: '公众号文章链接',
                        maxWidth: 500
                    }, {
                        type: 'button',
                        text: '抓取内容',
                        onclick: () => fetchWechatContent(editor)
                    }, {
                        type: 'htmlpanel',
                        htmlId: 'wechatPreview'
                    }]
                }]
            },
            buttons: [{
                type: 'cancel',
                text: '关闭'
            }]
        });
    }

    // 处理粘贴内容(含图片上传OSS)
    async function processPaste(editor) {
        const content = tinymce.activeEditor.dom.get('pasteContent').value;
        // 调用后端API处理图片和公式
        const res = await fetch('/api/doc/process-paste', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ content })
        });
        const data = await res.json();
        tinymce.activeEditor.dom.get('pastePreview').innerHTML = data.content;
    }

    // 处理文件上传(兼容多OSS)
    async function handleFileUpload(e, editor) {
        const file = e.target.files[0];
        const formData = new FormData();
        formData.append('file', file);
        
        const res = await fetch('/api/doc/upload-file', {
            method: 'POST',
            body: formData
        });
        const data = await res.json();
        tinymce.activeEditor.dom.get('filePreview').innerHTML = data.content;
    }

    // 抓取公众号内容(自动下载图片)
    async function fetchWechatContent(editor) {
        const url = tinymce.activeEditor.dom.get('wechatUrl').value;
        const res = await fetch('/api/doc/fetch-wechat', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ url })
        });
        const data = await res.json();
        tinymce.activeEditor.dom.get('wechatPreview').innerHTML = data.content;
    }
});

3. 操作面板(dialog.html)




    文档管理神器
    
    
    


    
        
    

    
        // 兼容国产浏览器的DOM操作
        function getElementsByClassName(className) {
            return document.querySelectorAll('.' + className);
        }
    



三、后端实现(Java JSP+信创适配)

1. OSS配置工具类(OssAdapter.java)

// 支持多云存储(阿里云/华为云/腾讯云)
public class OssAdapter {
    private static final Map STORAGE_MAP = new HashMap<>();

    static {
        // 阿里云OSS配置(从环境变量获取)
        STORAGE_MAP.put("aliyun", new AliyunOss(
            System.getenv("ALIYUN_ENDPOINT"),
            System.getenv("ALIYUN_ACCESS_KEY"),
            System.getenv("ALIYUN_SECRET"),
            System.getenv("ALIYUN_BUCKET")
        ));
        // 华为云OBS配置
        STORAGE_MAP.put("huawei", new HuaweiObs(
            System.getenv("HUAWEI_ENDPOINT"),
            System.getenv("HUAWEI_ACCESS_KEY"),
            System.getenv("HUAWEI_SECRET"),
            System.getenv("HUAWEI_BUCKET")
        ));
    }

    // 上传文件到指定云存储
    public static String uploadToCloud(String cloudType, String filePath, String fileName) throws Exception {
        CloudStorage storage = STORAGE_MAP.get(cloudType);
        if (storage == null) {
            throw new IllegalArgumentException("不支持的云存储类型: " + cloudType);
        }
        return storage.upload(filePath, fileName);
    }
}

// 阿里云OSS实现(示例)
class AliyunOss implements CloudStorage {
    private final String endpoint;
    private final String accessKey;
    private final String secret;
    private final String bucket;

    public AliyunOss(String endpoint, String accessKey, String secret, String bucket) {
        this.endpoint = endpoint;
        this.accessKey = accessKey;
        this.secret = secret;
        this.bucket = bucket;
    }

    @Override
    public String upload(String filePath, String fileName) throws Exception {
        // 使用阿里云OSS SDK实现上传(省略具体代码)
        return "https://" + bucket + "." + endpoint + "/" + fileName;
    }
}

2. 文档处理核心类(DocProcessor.java)

// 支持Word/Excel/PPT/PDF解析(兼容信创环境)
public class DocProcessor {
    // 处理粘贴的Word内容(含图片、公式)
    public String processPastedWord(String html) throws Exception {
        // 1. 清理Word特有标签(兼容IE8)
        String cleanHtml = cleanWordTags(html);
        
        // 2. 提取并上传图片(二进制存储)
        cleanHtml = uploadImages(cleanHtml);
        
        // 3. 转换Latex公式为MathML(支持MathType)
        cleanHtml = convertLatexToMathML(cleanHtml);
        
        return cleanHtml;
    }

    // 解析Word文档(.docx)
    public String parseWord(File file) throws Exception {
        XWPFDocument doc = new XWPFDocument(new FileInputStream(file));
        StringBuilder html = new StringBuilder("");

        // 处理段落(保留字体/字号/颜色)
        for (XWPFParagraph para : doc.getParagraphs()) {
            html.append("");
            for (XWPFRun run : para.getRuns()) {
                html.append("");
                html.append(run.getText(0));
                html.append("");
            }
            html.append("");
        }

        // 处理表格(保留形状组)
        for (XWPFTable table : doc.getTables()) {
            html.append("");
            for (XWPFTableRow row : table.getRows()) {
                html.append("");
                for (XWPFTableCell cell : row.getTableCells()) {
                    html.append("");
                }
                html.append("");
            }
            html.append("").append(parseCell(cell)).append("");
        }

        html.append("");
        return html.toString();
    }

    // 辅助方法:清理Word标签(兼容旧浏览器)
    private String cleanWordTags(String html) {
        return html.replaceAll(".*?", "")
                   .replaceAll("class=\"Mso[^"]*\"", "")
                   .replaceAll("", "");
    }

    // 辅助方法:上传图片(二进制存储)
    private String uploadImages(String html) throws Exception {
        Pattern pattern = Pattern.compile("]+src=\"data:image/(png|jpg);base64,(.*?)\"[^>]*>");
        Matcher matcher = pattern.matcher(html);
        StringBuffer sb = new StringBuffer();

        while (matcher.find()) {
            String base64 = matcher.group(2);
            byte[] bytes = Base64.getDecoder().decode(base64);
            File tempFile = File.createTempFile("img_", ".png");
            Files.write(tempFile.toPath(), bytes);
            
            // 上传到OSS(默认阿里云,可通过参数切换)
            String ossUrl = OssAdapter.uploadToCloud("aliyun", tempFile.getPath(), "paste_img_" + System.currentTimeMillis() + ".png");
            matcher.appendReplacement(sb, "");
            tempFile.delete();
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    // 辅助方法:Latex转MathML(调用MathJax API)
    private String convertLatexToMathML(String html) {
        return html.replaceAll("\\$(.*?)\\$", match -> {
            String latex = match.group(1);
            try {
                // 调用MathJax API转换(实际需替换为真实接口)
                return "" + latex + "";
            } catch (Exception e) {
                return match.group(0); // 转换失败保留原格式
            }
        });
    }
}

3. API接口控制器(DocApi.jsp)

<%@ page import="com.example.DocProcessor" %>
<%@ page import="com.example.OssAdapter" %>
<%@ page import="org.apache.poi.xwpf.extractor.XWPFWordExtractor" %>
<%@ page import="org.apache.pdfbox.pdmodel.PDDocument" %>
<%@ page import="java.io.*" %>

<%
    response.setContentType("application/json;charset=UTF-8");
    String action = request.getParameter("action");

    if ("processPaste".equals(action)) {
        String content = request.getParameter("content");
        DocProcessor processor = new DocProcessor();
        String result = processor.processPastedWord(content);
        out.print("{\"code\":0,\"msg\":\"处理成功\",\"data\":{\"content\":\"" + result + "\"}}");
    } else if ("uploadFile".equals(action)) {
        Part filePart = request.getPart("file");
        String fileName = getFileName(filePart);
        File tempFile = File.createTempFile("upload_", "." + fileName.split("\\.")[1]);
        filePart.write(tempFile.getPath());
        
        // 默认上传阿里云OSS(可通过参数指定云类型)
        String ossUrl = OssAdapter.uploadToCloud("aliyun", tempFile.getPath(), fileName);
        out.print("{\"code\":0,\"msg\":\"上传成功\",\"data\":{\"content\":\"\"}}");
    } else if ("fetchWechat".equals(action)) {
        String url = request.getParameter("url");
        String html = fetchWechatContent(url);
        out.print("{\"code\":0,\"msg\":\"抓取成功\",\"data\":{\"content\":\"" + html + "\"}}");
    }
%>

<%!
    // 提取文件名工具方法(兼容旧浏览器)
    private String getFileName(Part part) {
        String contentDisp = part.getHeader("content-disposition");
        for (String cd : contentDisp.split(";")) {
            if (cd.trim().startsWith("filename")) {
                return cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
            }
        }
        return "unknown_" + System.currentTimeMillis() + ".docx";
    }

    // 解析PDF(简化版,实际需用PDFBox提取文本和图片)
    private String parsePdf(File file) throws IOException {
        PDDocument doc = PDDocument.load(file);
        StringBuilder html = new StringBuilder("");
        // 实际需遍历页面提取内容,此处简化
        html.append("PDF内容预览(完整解析需集成PDFBox)");
        doc.close();
        return html.toString();
    }

    // 抓取公众号内容(简化版,实际需用Jsoup解析)
    private String fetchWechatContent(String url) throws IOException {
        // 实际需处理反爬和图片下载,此处简化
        return "公众号内容预览(完整抓取需对接微信API)";
    }
%>

四、信创适配与兼容性保障

1. 环境兼容性测试矩阵

操作系统CPU架构浏览器/内核测试结果
Windows 10x86(Intel)Chrome 100+/Edge 100+✔️
统信UOSx86(兆芯)360安全浏览器(信创版)✔️
麒麟OSARM(飞腾)龙芯浏览器✔️
Ubuntu 20.04ARM(鲲鹏)Firefox 90+✔️
CentOS 7x86(海光)IE8(兼容模式)✔️

2. 安全合规措施

  • 图片存储:采用二进制存储(非Base64),避免前端HTML膨胀
  • 数据加密:OSS传输使用HTTPS,敏感字段(如OSS密钥)通过环境变量管理
  • 等保认证:提供《信息系统安全等级保护备案证明》(三级)
  • 信创认证:通过《信息技术应用创新解决方案》认证(编号:XXX-2024)

五、源码交付与实施支持

1. 源码清单

  • 前端:TinyMCE插件代码(含Vue3/React适配)
  • 后端:Java JSP核心类、OSS适配器、文档解析工具
  • 配置:环境变量模板、数据库脚本(MySQL)、部署脚本(Docker/K8s)
  • 文档:《集成手册》《信创适配指南》《API接口文档》

2. 实施支持

  • 培训服务:提供2天现场培训(含插件集成、OSS配置、故障排查)
  • 驻场支持:项目上线后提供1个月驻场运维(解决兼容性问题)
  • 升级保障:每年免费提供2次功能迭代(如新增WPS格式支持)

3. 资质文件(部分示例)

  • 央企合作案例:某能源集团CMS系统采购合同(合同编号:XX-2023-001)
  • 信创认证:《信息技术应用创新解决方案认证证书》(证书编号:XXX-2024)
  • 软著:《新能源集团文档管理系统V1.0》(登记号:2024SR0000000)
  • 法人身份:营业执照(统一社会信用代码:XXX)、法人身份证复印件

六、成本与预算说明

1. 总成本构成

项目费用(万元)说明
源码买断费80包含所有功能模块的完整源码
定制开发费15适配集团现有系统的接口开发
实施服务费3培训+驻场支持
合计98预算内

2. 成本优势

  • 一次性投入:买断源码后,集团所有项目复用(500+项目/年节省500万+)
  • 零授权费:无后续License费用(对比采购商业软件节省90%+成本)
  • 自主可控:源码完全自主,避免供应链风险(符合政府/国企要求)

七、合作流程

  1. 需求确认:签署《需求确认单》(含功能清单、兼容性要求)
  2. 源码交付:提供完整源码包(含部署脚本、配置模板)
  3. 试点测试:在集团1-2个项目中进行试点(1周内完成)
  4. 批量推广:试点通过后,集团内部全面推广(提供技术支持)

结语:本方案深度适配集团信创需求,兼顾功能完备性与成本可控性,助力您在政府/事业单位项目中建立技术壁垒。期待与您携手,共同打造新能源行业数字化标杆!

复制插件

WordPaster插件文件夹

安装jquery

npm install jquery

在组件中引入

  // 引入tinymce-vue
  import Editor from '@tinymce/tinymce-vue'
  import {WordPaster} from '../../static/WordPaster/js/w'
  import {zyOffice} from '../../static/zyOffice/js/o'
  import {zyCapture} from '../../static/zyCapture/z'

添加工具栏

//添加导入excel工具栏按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor).importExcel()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('excelimport', {
        text: '',
        tooltip: '导入Excel文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('excelimport', {
        text: '',
        tooltip: '导入Excel文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('excelimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加word转图片工具栏按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().importWordToImg()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('importwordtoimg', {
        text: '',
        tooltip: 'Word转图片',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('importwordtoimg', {
        text: '',
        tooltip: 'Word转图片',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('importwordtoimg', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加粘贴网络图片工具栏按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().UploadNetImg()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('netpaster', {
        text: '',
        tooltip: '网络图片一键上传',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('netpaster', {
        text: '',
        tooltip: '网络图片一键上传',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('netpaster', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加导入PDF按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().ImportPDF()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('pdfimport', {
        text: '',
        tooltip: '导入pdf文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('pdfimport', {
        text: '',
        tooltip: '导入pdf文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('pdfimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加导入PPT按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor);
      WordPaster.getInstance().importPPT()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('pptimport', {
        text: '',
        tooltip: '导入PowerPoint文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('pptimport', {
        text: '',
        tooltip: '导入PowerPoint文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('pptimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加导入WORD按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    function selectLocalImages(editor) {        
      WordPaster.getInstance().SetEditor(editor).importWord()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('wordimport', {
        text: '',
        tooltip: '导入Word文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('wordimport', {
        text: '',
        tooltip: '导入Word文档',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('wordimport', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

//添加WORD粘贴按钮
(function () {
    'use strict';
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
    var ico = "http://localhost:8080/static/WordPaster/plugin/word.png"
    function selectLocalImages(editor) {
      WordPaster.getInstance().SetEditor(editor).PasteManual()
    }

    var register$1 = function (editor) {
      editor.ui.registry.addButton('wordpaster', {
        text: '',
        tooltip: 'Word一键粘贴',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
      editor.ui.registry.addMenuItem('wordpaster', {
        text: '',
        tooltip: 'Word一键粘贴',
        onAction: function () {
          selectLocalImages(editor)
        }
      });
    };
    var Buttons = { register: register$1 };
    function Plugin () {
      global.add('wordpaster', function (editor) {        
        Buttons.register(editor);
      });
    }
    Plugin();
}());

在线代码:

添加插件

// 插件
      plugins: {
          type: [String, Array],
          // default: 'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars'
          default: 'autoresize code autolink autosave image imagetools paste preview table powertables'
      },

点击查看在线代码

初始化组件

// 初始化
WordPaster.getInstance({
    // 上传接口:http://www.ncmem.com/doc/view.aspx?id=d88b60a2b0204af1ba62fa66288203ed
    PostUrl: 'http://localhost:8891/upload.aspx',
    // 为图片地址增加域名: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: ''
})

在页面中引入组件


功能演示

编辑器

在编辑器中增加功能按钮
WordPaster-TinyMCE5

导入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、付费专栏及课程。

余额充值