机械制造中WANGEDITOR网页编辑器粘贴Word图文图片为何不显示?

企业级Word内容导入解决方案评估与实施报告

一、需求分析与现状评估

1.1 需求核心要点

  • 功能需求
    • Word粘贴功能(保留格式+图片自动上传)
    • Word文档导入功能(支持Office/PDF格式)
    • 微信公众号内容抓取
  • 技术要求
    • 兼容现有技术栈(Vue2+wangEditor+SpringBoot)
    • 支持信创全环境
    • 浏览器兼容性(含IE8)
    • 多架构CPU支持
  • 商务需求
    • 买断授权(预算≤58万)
    • 提供央企/政府项目证明

1.2 现有技术栈分析

技术项当前版本兼容性评估
Vue.js2.x需兼容IE8的polyfill
wangEditor4.x需插件扩展机制
SpringBoot2.x文件处理能力充足
华为云OBS-已有SDK集成经验

二、市场方案调研与评估

2.1 候选方案对比

方案A:CKEditor 5 + Word导入插件

优势

  • 成熟的商业插件
  • 良好的格式保留能力
    劣势
  • 替换现有编辑器成本高
  • 信创兼容性不明确
方案B:KindEditor 商业版

优势

  • 国产化支持较好
  • 买断授权模式
    劣势
  • 技术较陈旧
  • IE8支持需额外测试
方案C:自主开发插件(推荐方案)

优势

  • 完全可控
  • 无缝集成现有系统
  • 长期成本最优
    劣势
  • 初期开发周期较长

2.2 推荐方案技术路线

粘贴/导入
客户端
Word内容解析
图片提取
华为OBS上传
URL替换
格式转换
HTML输出
wangEditor内容插入

三、开发实施方案

3.1 前端实现方案(Vue2组件)

// WordImportButton.vue



export default {
  methods: {
    triggerFileInput() {
      this.$refs.fileInput.click();
    },
    async handleFileImport(e) {
      const file = e.target.files[0];
      if (!file) return;
      
      try {
        // 解析Word内容
        const { html, images } = await this.parseWordFile(file);
        
        // 上传图片
        const uploadedImages = await Promise.all(
          images.map(img => this.uploadImage(img))
        );
        
        // 替换图片引用
        const finalHtml = this.replaceImageUrls(html, uploadedImages);
        
        // 插入编辑器
        this.editor.cmd.do('insertHTML', finalHtml);
      } catch (err) {
        console.error('导入失败:', err);
        this.$message.error('文档导入失败');
      }
    },
    parseWordFile(file) {
      // 使用mammoth.js或自定义解析器
      return new Promise((resolve) => {
        // 解析实现...
      });
    },
    uploadImage(imageFile) {
      // 华为OBS上传逻辑
      return this.$http.post('/api/upload', imageFile, {
        headers: {
          'Content-Type': 'application/octet-stream'
        }
      });
    }
  }
}

3.2 后端服务实现(SpringBoot)

文件上传接口
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
    
    @Autowired
    private HuaweiObsService obsService;
    
    @PostMapping
    public ResponseEntity uploadFile(
        @RequestParam("file") MultipartFile file,
        @RequestParam(value = "type", defaultValue = "image") String type) {
        
        try {
            // 生成唯一文件名
            String fileName = UUID.randomUUID() + getFileExtension(file.getOriginalFilename());
            
            // 上传到华为OBS
            String url = obsService.uploadFile(
                "doc-uploads", 
                fileName, 
                file.getInputStream()
            );
            
            return ResponseEntity.ok(new UploadResult(true, url));
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body(new UploadResult(false, "上传失败: " + e.getMessage()));
        }
    }
    
    private String getFileExtension(String filename) {
        return filename.substring(filename.lastIndexOf("."));
    }
    
    @Data
    @AllArgsConstructor
    public static class UploadResult {
        private boolean success;
        private String url;
    }
}
华为OBS服务封装
@Service
public class HuaweiObsServiceImpl implements HuaweiObsService {
    
    @Value("${huawei.obs.endpoint}")
    private String endpoint;
    
    @Value("${huawei.obs.accessKey}")
    private String accessKey;
    
    @Value("${huawei.obs.secretKey}")
    private String secretKey;
    
    @Override
    public String uploadFile(String bucketName, String objectName, InputStream inputStream) {
        ObsClient obsClient = new ObsClient(accessKey, secretKey, endpoint);
        
        try {
            // 创建上传请求
            PutObjectRequest request = new PutObjectRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(objectName);
            request.setInput(inputStream);
            
            // 执行上传
            PutObjectResult result = obsClient.putObject(request);
            
            // 生成访问URL
            return String.format("https://%s.%s/%s", bucketName, endpoint, objectName);
        } finally {
            try {
                obsClient.close();
            } catch (IOException e) {
                log.error("关闭OBS客户端失败", e);
            }
        }
    }
}

3.3 信创环境兼容性保障措施

  1. 测试矩阵

    • 操作系统:银河麒麟v10、统信UOS 20、中标麒麟7
    • 浏览器:IE8-11、Chrome(龙芯版)、Firefox(兆芯版)
    • CPU架构:x86、ARM64、MIPS64、LoongArch
  2. 兼容性解决方案

    • 使用core-js提供ES5+ polyfill
    • 图片处理使用纯Java实现(避免本地库依赖)
    • 文档解析采用流式处理(降低内存消耗)

四、商业合作方案建议

4.1 供应商评估要点

  1. 资质要求

    • 软件著作权证书(需包含信创相关技术)
    • 国家保密局认证
    • 至少5个政府/央企合作案例
  2. 合同关键条款

    • 买断授权(全集团永久使用)
    • 版本免费更新(至少3年)
    • 价格锁定条款(防涨价)

4.2 预算分配建议

项目预算(万元)说明
基础授权费45买断核心功能授权
定制开发8信创专项适配
驻场服务5部署与培训
总计58

五、实施路线图

  1. 第一阶段(2周)

    • 技术验证(Word解析核心算法)
    • 信创环境适配测试
  2. 第二阶段(3周)

    • 前后端功能开发
    • 华为OBS集成
  3. 第三阶段(1周)

    • 全环境兼容性测试
    • 性能优化(大文档处理)
  4. 第四阶段(持续)

    • 按项目需求逐步部署
    • 建立二线技术支持机制

六、技术难点解决方案

6.1 Word格式保留方案

表格
公式
形状
文本
原始文档
Apache POI解析
元素类型
转换为HTML table
转为LaTeX+MathJax
转为SVG
保留样式span

6.2 IE8兼容性方案

// polyfill加载策略
const loadPolyfills = () => {
  if (typeof Promise === 'undefined') {
    document.write('<\/script>');
  }
  if (!Array.prototype.forEach) {
    document.write('<script src="/polyfills/array-foreach.min.js"><\/script>');
  }
};

// 条件加载
const ua = navigator.userAgent;
if (ua.indexOf('MSIE 8.0') > -1 || ua.indexOf('Trident/7.0') > -1) {
  window.onload = loadPolyfills;
}

七、后续升级规划

  1. 短期(6个月内)

    • 增加WPS格式支持
    • 集成国产加密算法
  2. 中期(1年)

    • 对接政府文档安全管理系统
    • 实现OFD版式文件支持
  3. 长期

    • 构建文档智能解析中台
    • 对接政府大数据平台

本方案在技术可行性、成本控制和合规性方面均满足集团要求,建议采用"自主开发+商业插件采购"的混合模式推进实施。

复制插件文件

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'

初始化组件

<template>
  <div class="">
  <div id="editor">
    <p>泽优全平台内容发布解决方案 for vue2 cli wangEditor4</p>
    <p>泽优全平台Word一键粘贴控件(WordPaster)</p>
    <p>泽优全平台截屏解决方案(zyCapture)</p>
    <p>泽优Office文档转换服务(zyOffice)</p>
    </div><br/>
  <p>第二个编辑器</p>
  <div id="editor2">
    <p>泽优全平台内容发布解决方案 for vue2 cli wangEditor4</p>
    <p>泽优全平台Word一键粘贴控件(WordPaster)</p>
    <p>泽优全平台截屏解决方案(zyCapture)</p>
    <p>泽优Office文档转换服务(zyOffice)</p></div>  
  </div>
</template>

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

余额充值