教育行业整站程序如何通过TinyMCE插件实现Word公式粘贴历史记录?

企业网站后台Word/公众号内容导入功能集成项目记录

1. 项目需求分析

作为广西某集团公司的项目负责人,我们近期需要为企业网站后台管理系统的文章发布模块增加以下功能:

核心需求:

  1. Word粘贴功能:支持从Word复制内容粘贴到编辑器,自动上传图片到存储服务器
  2. Word文档导入功能:支持批量导入Word/Excel/PPT/PDF文档
  3. 微信公众号内容粘贴:自动下载公众号文章图片并上传
  4. 保留原始样式:表格、形状、公式、字体(GB2312)、字号、颜色等

技术栈要求:

  • 前端:Vue2 CLI + TinyMCE
  • 后端:SpringBoot
  • 数据库:MySQL
  • 服务器:华为云ECS + OBS
  • 开发工具:IntelliJ IDEA

特殊要求:

  • 信创国产化兼容:多种OS+CPU组合
  • 浏览器兼容:包含IE8在内的主流浏览器
  • 预算:58万以内买断授权
  • 避免后期涨价风险

2. 市场调研与产品评估

2.1 候选方案调研

经过市场调研,我筛选出以下几个可能的解决方案:

  1. TinyMCE官方PowerPaste插件

    • 优点:原生支持TinyMCE,集成简单
    • 缺点:不支持公众号内容导入,部分信创环境兼容性存疑
  2. KindEditor商业版

    • 优点:国产产品,信创兼容性好
    • 缺点:不支持公式和复杂形状,更新维护频率低
  3. WangEditor企业版

    • 优点:轻量级,国产化支持好
    • 缺点:功能相对简单,不支持Word/Excel/PPT/PDF/微信公众号导入
  4. UEditor百度编辑器

    • 优点:功能全面,国产化支持
    • 缺点:维护停滞,IE8兼容性问题
  5. 自定义开发方案

    • 优点:完全定制化
    • 缺点:开发周期长,成本高

2.2 产品功能对比表

功能特性TinyMCE PowerPasteKindEditor商业版WangEditor企业版UEditor自定义开发WordPaster源码版
Word粘贴✔️✔️✔️✔️✔️✔️
文件导入✔️(仅Word)✔️✔️✔️Word,Excel,PPT,PDF
公众号粘贴✔️✔️✔️
公式支持✔️✔️Latex,MathType
信创兼容部分✔️✔️✔️✔️✔️
IE8兼容✔️✔️✔️✔️✔️
买断授权✔️✔️✔️不适用✔️
预算内✔️✔️✔️✔️✔️

2.3 最终选择:基于WordPaster的源码版方案

决策理由:

  1. 原生支持TinyMCE,集成风险最小
  2. 核心Word处理功能成熟稳定
  3. 可扩展性强,便于增加公众号功能
  4. 技术团队对TinyMCE熟悉,维护成本低
  5. 通过二次开发可满足信创要求

商务谈判结果:

  • 采购WordPaster源码授权+PowerPaste插件
  • 委托原厂开发公众号功能模块
  • 总费用55万元(授权买断+定制开发)
  • 包含3年技术支持和版本更新

3. 技术实现方案

3.1 系统架构设计

[前端]
Vue2 CLI → TinyMCE编辑器 → 自定义插件(Word/公众号处理)
↓
[后端API]
SpringBoot → 文件处理服务 → 华为云OBS存储
↑
[数据库]
MySQL(存储元数据)

3.2 关键技术点实现

3.2.1 前端实现(Vue2+TinyMCE)

main.js 集成代码:

import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/silver'
import 'tinymce/plugins/powerpaste'
import 'tinymce/plugins/wechatpaste' // 自定义公众号插件

Vue.prototype.$tinymce = tinymce

const initTinyMCE = {
  language: 'zh_CN',
  plugins: 'powerpaste wechatpaste image table code',
  toolbar: 'paste wechatpaste | image table',
  powerpaste_word_import: 'clean',
  powerpaste_html_import: 'clean',
  powerpaste_allow_local_images: true,
  paste_data_images: true,
  images_upload_handler: function (blobInfo, success, failure) {
    // 调用后端图片上传接口
    uploadImage(blobInfo.blob()).then(url => {
      success(url)
    }).catch(err => {
      failure('上传失败: ' + err)
    })
  }
}
3.2.2 后端实现(SpringBoot)

图片上传Controller:

@RestController
@RequestMapping("/api/upload")
public class UploadController {
    
    @Autowired
    private HuaweiObsService obsService;
    
    @PostMapping("/image")
    public ResponseEntity uploadImage(@RequestParam("file") MultipartFile file) {
        try {
            // 生成唯一文件名
            String filename = UUID.randomUUID() + getFileExtension(file.getOriginalFilename());
            
            // 上传到华为云OBS
            String url = obsService.uploadFile("images/"+filename, file.getInputStream());
            
            // 返回JSON结果
            return ResponseEntity.ok(Collections.singletonMap("url", url));
        } catch (Exception e) {
            return ResponseEntity.status(500).body("上传失败: " + e.getMessage());
        }
    }
    
    private String getFileExtension(String filename) {
        return filename.substring(filename.lastIndexOf("."));
    }
}
3.2.3 华为云OBS服务封装
@Service
public class HuaweiObsServiceImpl implements HuaweiObsService {
    
    @Value("${huawei.obs.endpoint}")
    private String endpoint;
    
    @Value("${huawei.obs.bucket}")
    private String bucketName;
    
    private ObsClient obsClient;
    
    @PostConstruct
    public void init() {
        // 使用华为云永久AK/SK初始化
        obsClient = new ObsClient(
            "your-access-key",
            "your-secret-key",
            endpoint
        );
    }
    
    @Override
    public String uploadFile(String objectKey, InputStream inputStream) throws Exception {
        PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, inputStream);
        obsClient.putObject(request);
        return String.format("https://%s.%s/%s", bucketName, endpoint, objectKey);
    }
    
    @PreDestroy
    public void destroy() {
        if (obsClient != null) {
            try {
                obsClient.close();
            } catch (Exception e) {
                // 记录日志
            }
        }
    }
}

3.3 信创兼容性处理

针对信创环境的特殊处理:

  1. 字体兼容性方案:

    /* 全局CSS设置GB2312字体回退 */
    body {
      font-family: "SimSun", "STSong", "NSimSun", "FangSong", "KaiTi", sans-serif;
    }
    
  2. 浏览器polyfill处理:

    // 在入口文件添加IE8兼容代码
    if (window.attachEvent && !window.addEventListener) {
      // 加载es5-shim等polyfill
      import('es5-shim').then(() => {
        import('console-polyfill')
        import('eventlistener-polyfill')
      })
    }
    
  3. 国产CPU适配:
    在Docker构建时针对不同CPU架构使用多阶段构建:

    # 多架构Dockerfile示例
    FROM --platform=$TARGETPLATFORM openjdk:8-jdk-alpine
    COPY target/*.jar app.jar
    ENTRYPOINT ["java","-jar","/app.jar"]
    

4. 项目进度与实施

4.1 项目实施里程碑

  1. 第1周:完成产品选型与商务谈判
  2. 第2周:环境准备与架构设计评审
  3. 第3-4周:核心功能开发与集成测试
  4. 第5周:信创环境适配测试
  5. 第6周:用户验收测试与上线准备

4.2 测试策略

  1. 浏览器兼容性测试矩阵:

    • IE8/IE11/Edge/Chrome/Firefox/QQ浏览器
    • 国产浏览器:360安全/急速模式、UC、搜狗
  2. 信创OS测试清单:

    • 中标麒麟NeoKylin V7
    • 银河麒麟Kylin V10
    • 统信UOS 20
    • 深度Deepin 20

5. 项目风险管理

  1. 信创环境兼容风险:

    • 应对:提前与厂商沟通获取测试设备
    • 备选方案:针对不兼容环境提供简化版UI
  2. IE8性能问题:

    • 应对:代码拆分+懒加载
    • 监控:实施后密切监控CPU/内存使用
  3. 图片上传稳定性:

    • 应对:实现断点续传和分片上传
    • 重试机制:3次自动重试+人工干预提醒

6. 项目成果

6.1 功能达成情况

需求项实现状态备注
Word粘贴✔️支持到Office 2019格式
文件导入✔️支持Word/Excel/PPT/PDF
公众号内容粘贴✔️支持常见公众号排版
样式保留✔️表格/公式/字体等
信创兼容✔️测试通过麒麟/UOS等
IE8兼容✔️性能优化后达标

6.2 性能指标

  • 图片上传平均耗时:<1.5s(2MB以内图片)
  • Word文档导入(20页):<3s
  • 内存占用:<50MB(IE8下)

7. 后续优化建议

  1. 长期维护计划:

    • 建立插件自动更新机制
    • 每季度一次信创环境复测
  2. 功能扩展路线图:

    • WPS文档专项优化
    • 党政机关公文格式自动校验
    • 多级审核工作流集成
  3. 技术升级准备:

    • Vue3迁移预案
    • 对象存储多云适配层

本项目在预算内按时完成,各项指标达到或超过预期,特别是在信创兼容性方面表现优异,为后续政府项目投标提供了有力的技术支撑。通过买断授权方式,集团后续项目无需重复采购,累计可节省约200万元授权费用。

复制插件

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: ''
})

在页面中引入组件


功能演示

编辑器

在编辑器中增加功能按钮
TinyMCE编辑器界面

导入Word文档,支持doc,docx

粘贴Word和图片

导入Excel文档,支持xls,xlsx

粘贴Word和图片

粘贴Word

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

Word转图片

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

导入PDF

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

导入PPT

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

上传网络图片

一键自动上传网络图片。
自动上传网络图片

下载示例

点击下载完整示例

TinyMCE中处理从Word粘贴的内容可以通过以下几种方法来实现: 1. **使用内置的粘贴插件**: TinyMCE提供了内置的粘贴插件,可以帮助清理从Word粘贴的HTML内容。确保在初始化TinyMCE时启用了粘贴插件: ```javascript tinymce.init({ selector: 'textarea', // 选择器 plugins: 'paste', // 启用粘贴插件 paste_retain_style_properties: 'all', // 保留所有样式属性 paste_word_valid_elements: '*[*]', // 允许所有Word元素 paste_data_images: true // 允许粘贴图片 }); ``` 2. **自定义粘贴处理函数**: 可以通过自定义粘贴处理函数来进一步清理和转换从Word粘贴的内容: ```javascript tinymce.init({ selector: 'textarea', plugins: 'paste', paste_preprocess: function(plugin, args) { // 自定义处理逻辑 args.content = args.content.replace(/<meta[^>]+>/g, ""); // 移除meta标签 args.content = args.content.replace(/<style[^>]+>/g, ""); // 移除style标签 } }); ``` 3. **使用外部库进行清理**: 可以结合使用如[html-cleaner](https://www.npmjs.com/package/html-cleaner)这样的外部库来进一步清理HTML内容: ```javascript const HtmlCleaner = require('html-cleaner'); tinymce.init({ selector: 'textarea', plugins: 'paste', paste_preprocess: function(plugin, args) { const cleaner = new HtmlCleaner(args.content); cleaner.removeTags(['meta', 'style']); // 移除特定标签 args.content = cleaner.getCleaned(); } }); ``` 通过以上方法,可以有效地处理从Word粘贴TinyMCE中的内容,确保内容的整洁和格式的正确。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值