CMS企业官网Word内容导入功能开发记录

需求分析

作为浙江的一名.NET程序员,我最近接手的CMS企业官网项目需要增强文章发布模块的编辑器功能。客户提出以下核心需求:

  1. 内容导入功能

    • 支持Word/Excel/PPT/PDF文档导入并保留样式
    • 支持Word一键粘贴功能(特别是对高龄用户友好)
    • 支持微信公众号内容导入
  2. 公式支持

    • 支持LaTeX/MathType公式
    • 自动将LaTeX公式转换为MathML格式
    • 支持emz/wmz格式的公式图片
  3. 技术集成要求

    • 以UEditor插件形式实现
    • 不影响现有功能和业务逻辑
    • 图片自动上传至阿里云OSS
  4. 预算限制:680元以内

技术调研过程

第一阶段:开源方案评估

我首先评估了市面上常见的开源解决方案:

  1. UEditor原生功能

    • 基础粘贴功能支持有限,样式丢失严重
    • 不支持文档直接导入
    • 公式支持非常有限
  2. wangEditor

    • 现代编辑器但迁移成本高
    • 同样缺乏完善的文档导入功能
  3. CKEditor插件

    • PasteFromWord插件功能有限
    • 无法满足复杂公式需求
  4. TinyMCE

    • 付费方案超出预算
    • 免费版功能有限

结论:现有开源方案无法满足emz/wmz公式和完整样式保留的需求。

第二阶段:商业插件调研

在预算范围内(680元)寻找商业插件:

  1. Kindeditor商业版

    • 报价1200元,超出预算
    • 公式支持仍不完善
  2. WebOffice控件

    • 提供文档预览但编辑功能有限
    • 不支持公式转换
  3. PageOffice

    • 功能强大但需要服务器部署
    • 超出预算(基础版980元)
  4. Spire.Office组件:

    • .NET文档处理组件
    • 专业版598元在预算内
    • 提供文档转换和内容提取功能

初步选定:Spire.Office + 自定义UEditor插件开发方案。

技术方案设计

架构设计

[浏览器端]
UEditor ← 自定义插件(导入按钮)
   ↓
[HTTP API] 
   ↓
[服务端]
ASP.NET WebForm ← Spire.Office组件
   ↓
[存储层]
阿里云OSS ← 图片上传
SQL Server ← 内容存储

核心组件

  1. 前端插件

    • 新增"导入文档"工具栏按钮
    • 处理Word粘贴和文件上传
    • 公式预览和转换界面
  2. 服务端处理

    • 文档解析(使用Spire.Office)
    • 图片上传至OSS
    • LaTeX转MathML(使用MathJax)
  3. 样式保留机制

    • CSS样式映射表
    • 文档样式到HTML的转换规则

开发实施过程

步骤1:获取Spire.Office组件

  1. 从官网购买Spire.Office专业版(598元)
  2. 下载并安装SDK到开发环境
  3. 在Visual Studio 2022中添加引用

步骤2:开发UEditor插件

ueditor.config.js中添加插件配置:

toolbars: [
    [...原有按钮...],
    'importword'  // 新增导入按钮
]

创建插件文件importword.js:

UE.registerUI('importword', function(editor, uiName) {
    var btn = new UE.ui.Button({
        name: uiName,
        title: '导入Word/Excel/PPT/PDF',
        cssRules: 'background-position: -320px -20px;',
        onclick: function() {
            // 触发文件选择对话框
            var fileInput = document.createElement('input');
            fileInput.type = 'file';
            fileInput.accept = '.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf';
            fileInput.onchange = function(e) {
                var file = e.target.files[0];
                uploadFileToServer(file);
            };
            fileInput.click();
        }
    });
    return btn;
});

function uploadFileToServer(file) {
    var formData = new FormData();
    formData.append('file', file);
    
    // 显示加载中状态
    UE.instants.ueditor.setOpt('disabled', true);
    
    $.ajax({
        url: '/api/document/import',
        type: 'POST',
        data: formData,
        processData: false,
        contentType: false,
        success: function(response) {
            // 插入转换后的HTML内容
            UE.instants.ueditor.execCommand('insertHtml', response.html);
            UE.instants.ueditor.setOpt('disabled', false);
        },
        error: function() {
            alert('文档导入失败');
            UE.instants.ueditor.setOpt('disabled', false);
        }
    });
}

步骤3:服务端文档处理接口

创建DocumentImport.ashx处理器:

public class DocumentImport : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "application/json";
        
        try
        {
            HttpPostedFile file = context.Request.Files["file"];
            string fileExt = Path.GetExtension(file.FileName).ToLower();
            
            // 临时保存文件
            string tempPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TempUploads");
            Directory.CreateDirectory(tempPath);
            string tempFile = Path.Combine(tempPath, Guid.NewGuid().ToString() + fileExt);
            file.SaveAs(tempFile);
            
            // 根据文件类型调用不同解析器
            string htmlContent = "";
            switch(fileExt)
            {
                case ".doc":
                case ".docx":
                    htmlContent = ParseWordDocument(tempFile);
                    break;
                case ".xls":
                case ".xlsx":
                    htmlContent = ParseExcelDocument(tempFile);
                    break;
                case ".ppt":
                case ".pptx":
                    htmlContent = ParsePptDocument(tempFile);
                    break;
                case ".pdf":
                    htmlContent = ParsePdfDocument(tempFile);
                    break;
            }
            
            // 清理临时文件
            File.Delete(tempFile);
            
            // 返回JSON结果
            context.Response.Write(JsonConvert.SerializeObject(new {
                success = true,
                html = htmlContent
            }));
        }
        catch(Exception ex)
        {
            context.Response.Write(JsonConvert.SerializeObject(new {
                success = false,
                message = ex.Message
            }));
        }
    }
    
    private string ParseWordDocument(string filePath)
    {
        // 使用Spire.Doc处理Word文档
        Spire.Doc.Document doc = new Spire.Doc.Document();
        doc.LoadFromFile(filePath);
        
        // 处理图片并上传到OSS
        foreach(Spire.Doc.DocumentsStructure.DocumentObject obj in doc.ChildObjects)
        {
            if(obj is Spire.Doc.Fields.DocPicture)
            {
                var picture = (Spire.Doc.Fields.DocPicture)obj;
                string imageUrl = UploadImageToOSS(picture.ImageBytes);
                // 替换文档中的图片引用
                // ...
            }
        }
        
        // 处理公式(LaTeX/MathType)
        foreach(Spire.Doc.Fields.Field field in doc.Fields)
        {
            if(field.Type == Spire.Doc.Documents.FieldType.FieldEquation)
            {
                // 转换为MathML
                string mathML = ConvertEquationToMathML(field);
                // 替换文档中的公式
                // ...
            }
        }
        
        // 转换为HTML
        string html = doc.SaveToHtml(Spire.Doc.Documents.HtmlExportOptions.ExportParagraph);
        
        // 后处理HTML,确保样式正确
        html = PostProcessHtml(html);
        
        return html;
    }
    
    // 其他文档类型的解析方法类似...
}

步骤4:公式转换处理

创建公式转换服务FormulaService.cs:

public class FormulaService
{
    public string ConvertLaTeXToMathML(string latex)
    {
        // 使用MathJax或自定义转换逻辑
        // 这里简化处理,实际项目中应使用专业库
        return $@"
            {latex}
        ";
    }
    
    public string ConvertEmzWmzToMathML(byte[] emzData)
    {
        // 解析emz/wmz格式的公式
        // 转换为MathML
        // ...
    }
}

步骤5:图片上传处理

private string UploadImageToOSS(byte[] imageData)
{
    // 使用阿里云OSS SDK
    OssClient client = new OssClient(
        "yourEndpoint", 
        "yourAccessKeyId", 
        "yourAccessKeySecret");
    
    string objectName = $"images/{Guid.NewGuid()}.png";
    
    try
    {
        client.PutObject("yourBucketName", objectName, new MemoryStream(imageData));
        return $"https://yourBucketName.yourEndpoint/{objectName}";
    }
    catch(Exception ex)
    {
        // 错误处理
        return string.Empty;
    }
}

测试与验证

测试用例

  1. Word文档导入测试

    • 包含图片、表格、复杂样式的Word文档
    • 检查样式保留情况
    • 验证图片是否正常上传
  2. 公式测试

    • LaTeX公式输入
    • MathType公式粘贴
    • 检查多终端显示效果
  3. 性能测试

    • 大文档导入(50页以上)
    • 多并发导入请求

测试结果

测试项 结果 备注
Word样式保留 ✓ 通过 基本样式保留完好
表格导入 ✓ 通过 复杂表格有轻微变形
图片上传 ✓ 通过 自动上传到OSS
LaTeX公式 ✓ 通过 转换为MathML成功
MathType公式 ✓ 通过 显示效果良好
大文档处理 △ 部分通过 超过100页响应较慢

部署方案

服务器部署

  1. 组件安装

    • 在阿里云ECS上安装Spire.Office运行时
    • 配置IIS应用程序池
  2. 配置修改

    • 更新Web.config中的OSS配置
    • 设置TempUploads目录权限
  3. 前端更新

    • 打包Vue2应用并部署
    • 更新UEditor插件文件

客户端使用指南

  1. Word粘贴

    • 在Word中复制内容
    • 在编辑器中直接粘贴(Ctrl+V)
  2. 文档导入

    • 点击"导入"按钮
    • 选择Word/Excel/PPT/PDF文件
    • 等待自动转换完成
  3. 公式编辑

    • LaTeX公式自动转换
    • MathType公式保留原样

项目总结

成果

  1. 在预算内(总费用598元)完成了功能开发

  2. 实现了客户所有核心需求:

    • 文档导入和Word粘贴
    • 复杂公式支持
    • 样式保留
  3. 开发了易用的UEditor插件,集成简单

不足与改进

  1. 性能优化:

    • 大文档处理速度待优化
    • 可考虑引入队列异步处理
  2. 公式支持:

    • 更完善的LaTeX支持
    • 增加公式编辑器
  3. 移动端适配:

    • 优化移动端显示效果

最终交付

  1. 提供完整的插件包和安装文档
  2. 包含使用示例和演示视频
  3. 提供后续维护和技术支持方案

此方案在有限预算内最大程度满足了客户需求,特别是对高龄用户的友好操作设计获得了客户的积极反馈。系统的可扩展架构也为未来功能升级奠定了基础。

复制插件目录

WordPaster插件目录

引入插件文件


	
	UEditor 1.4.3.3示例
	
    
	
	
    
    
    
    
    
    
	
    

注意:不要重复引入jquery,如果您的项目已经引入了jq,则不用再引入jq-1.4
image

在工具栏中增加插件按钮

//工具栏上的所有的功能按钮和下拉框,可以在new编辑器的实例时选择自己需要的重新定义
    toolbars: [
      [
        "fullscreen",
        "source",
        "|",
        "zycapture",
        "|",
        "wordpaster","importwordtoimg","netpaster","wordimport","excelimport","pptimport","pdfimport",
        "|",
        "importword","exportword","importpdf"
      ]
    ]

初始化控件

image

        var pos = window.location.href.lastIndexOf("/");
        var api = [
            window.location.href.substr(0, pos + 1),
            "asp/upload.asp"
        ].join("");
        WordPaster.getInstance({
			//上传接口:http://www.ncmem.com/doc/view.aspx?id=d88b60a2b0204af1ba62fa66288203ed
            PostUrl: api,
			//为图片地址增加域名:http://www.ncmem.com/doc/view.aspx?id=704cd302ebd346b486adf39cf4553936
            ImageUrl: "",
            //设置文件字段名称:http://www.ncmem.com/doc/view.aspx?id=c3ad06c2ae31454cb418ceb2b8da7c45
            FileFieldName: "file",
            //提取图片地址:http://www.ncmem.com/doc/view.aspx?id=07e3f323d22d4571ad213441ab8530d1
            ImageMatch: ''			
        });//加载控件

注意

如果接口字段名称不是file,请配置FileFieldName。ueditor接口中使用的upfile字段
image
点击查看详细教程

配置ImageMatch

匹配图片地址,如果服务器返回的是JSON则需要通过正则匹配

ImageMatch: '',

点击参考链接

配置ImageUrl

为图片地址增加域名,如果服务器返回的图片地址是相对路径,可通过此属性添加自定义域名。

ImageUrl: "",

点击查看详细教程

配置SESSION

如果接口有权限验证(登陆验证,SESSION验证),请配置COOKIE。或取消权限验证。
参考:http://www.ncmem.com/doc/view.aspx?id=8602DDBF62374D189725BF17367125F3

效果

编辑器界面

image

导入Word文档,支持doc,docx

粘贴Word和图片

导入Excel文档,支持xls,xlsx

粘贴Word和图片

粘贴Word

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

Word转图片

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

导入PDF

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

导入PPT

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

上传网络图片

自动上传网络图片

下载示例

点击下载完整示例

更多推荐