医院影像系统是否支持UMEDITOR的DICOM文件转存功能?

信创环境下的Office文档导入解决方案

项目背景与需求分析

作为海南某集团企业的项目负责人,我们近期在多个政府和企业项目中遇到了文档导入的共性需求。经过详细调研,我们明确了以下技术要求:

  1. 核心功能需求

    • Word内容粘贴保留格式
    • Office全家桶导入(Word/Excel/PPT/PDF)
    • 微信公众号内容抓取
    • 图片自动上传至独立存储服务器
    • 信创环境全兼容
  2. 技术约束条件

    • 前端框架:Vue2/Vue3/React全兼容
    • 后端框架:SpringBoot/JavaEE兼容
    • 信创环境:国产CPU+操作系统全适配
    • 浏览器兼容:IE8+全支持
  3. 商务需求

    • 一次性买断授权(88万预算)
    • 提供央企/政府项目合作证明
    • 信创环境认证资质

技术解决方案

整体架构设计

[客户端]
├─ [Web编辑器]
│  ├─ UEditor核心
│  ├─ Word导入插件
│  └─ 微信公众号导入插件
│
├─ [文件解析服务]
│  ├─ Office文档解析
│  └─ PDF解析
│
[服务端]
├─ [文件存储服务]
│  ├─ 华为云OBS适配
│  └─ 其他云存储适配
│
├─ [公式转换服务]
│  ├─ LaTeX→MathML
│  └─ MathType转换
│
└─ [字体服务]
   ├─ GB2312字体支持
   └─ 信创字体适配

前端集成方案

UEditor插件开发
// ueditor-word-import-plugin.js
UE.plugins['wordimport'] = function() {
  const editor = this;
  
  // 添加工具栏按钮
  editor.registerCommand('wordimport', {
    execCommand: function() {
      // 创建文件选择对话框
      const fileInput = document.createElement('input');
      fileInput.type = 'file';
      fileInput.accept = '.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf';
      
      fileInput.onchange = async (e) => {
        const file = e.target.files[0];
        const html = await parseOfficeFile(file);
        editor.execCommand('insertHtml', html);
      };
      
      fileInput.click();
    }
  });
  
  // Office文件解析
  async function parseOfficeFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    
    // 调用后端解析服务
    const response = await fetch('/api/office/parse', {
      method: 'POST',
      body: formData
    });
    
    return await response.text();
  }
  
  // 监听粘贴事件
  editor.addListener('paste', async (type, html) => {
    // 检测Word内容粘贴
    if (isWordPaste(html)) {
      const cleanedHtml = await processWordPaste(html);
      editor.execCommand('insertHtml', cleanedHtml);
      return false; // 阻止默认粘贴行为
    }
    return true;
  });
  
  function isWordPaste(html) {
    return html.includes('urn:schemas-microsoft-com') || 
           html.includes('mso-') ||
           html.match(/<(!|meta|link|style|o:[^>]+)>/g);
  }
  
  async function processWordPaste(html) {
    // 调用后端清洗服务
    const response = await fetch('/api/office/clean', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ html })
    });
    
    return await response.text();
  }
};
跨框架适配封装
// vue2适配
import UEditor from 'ueditor';
import 'ueditor-word-import-plugin';

Vue.component('UEditorWithWordImport', {
  mounted() {
    this.editor = UE.getEditor(this.id, {
      // UEditor配置
    });
    // 自动加载插件
  }
});

// vue3适配
import { defineComponent, onMounted } from 'vue';

export default defineComponent({
  setup() {
    let editor;
    onMounted(() => {
      editor = UE.getEditor('editor', {
        // UEditor配置
      });
    });
    return { editor };
  }
});

// react适配
import { useEffect, useRef } from 'react';

export function UEditorWithWordImport({ id }) {
  const editorRef = useRef();
  
  useEffect(() => {
    const editor = UE.getEditor(id, {
      // UEditor配置
    });
    editorRef.current = editor;
    
    return () => {
      editor.destroy();
    };
  }, []);
  
  return ;
}

后端服务实现

SpringBoot文件解析服务
@RestController
@RequestMapping("/api/office")
public class OfficeImportController {
    
    @Autowired
    private HuaweiOBSStorageService storageService;
    
    @Autowired
    private OfficeParserService officeParser;
    
    @PostMapping("/parse")
    public ResponseEntity parseOfficeFile(@RequestParam("file") MultipartFile file) {
        try {
            // 文件类型检测
            String fileType = FileTypeUtils.detectFileType(file.getInputStream());
            
            // 调用相应解析器
            String html;
            switch(fileType) {
                case "docx":
                    html = officeParser.parseWord(file.getInputStream());
                    break;
                case "xlsx":
                    html = officeParser.parseExcel(file.getInputStream());
                    break;
                case "pptx":
                    html = officeParser.parsePowerPoint(file.getInputStream());
                    break;
                case "pdf":
                    html = officeParser.parsePDF(file.getInputStream());
                    break;
                default:
                    throw new UnsupportedFileTypeException("不支持的文档类型");
            }
            
            return ResponseEntity.ok(html);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("文件解析失败: " + e.getMessage());
        }
    }
    
    @PostMapping("/clean")
    public ResponseEntity cleanWordHtml(@RequestBody Map request) {
        String html = request.get("html");
        // 清洗Word HTML
        String cleanedHtml = WordHtmlCleaner.clean(html);
        // 处理图片
        cleanedHtml = processImages(cleanedHtml);
        // 处理公式
        cleanedHtml = FormulaConverter.convert(cleanedHtml);
        return ResponseEntity.ok(cleanedHtml);
    }
    
    private String processImages(String html) {
        // 正则匹配图片标签
        Pattern pattern = Pattern.compile("]+src\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>");
        Matcher matcher = pattern.matcher(html);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String imgSrc = matcher.group(1);
            // 处理base64图片
            if (imgSrc.startsWith("data:image")) {
                String newUrl = uploadBase64Image(imgSrc);
                matcher.appendReplacement(sb, matcher.group().replace(imgSrc, newUrl));
            }
        }
        matcher.appendTail(sb);
        
        return sb.toString();
    }
    
    private String uploadBase64Image(String base64) {
        // 解析base64数据
        String[] parts = base64.split(",");
        byte[] imageBytes = Base64.getDecoder().decode(parts[1]);
        
        // 上传到华为云OBS
        String objectKey = "images/" + UUID.randomUUID() + ".png";
        storageService.upload(objectKey, imageBytes);
        
        return storageService.getUrl(objectKey);
    }
}
华为云OBS存储服务
@Service
public class HuaweiOBSStorageService {
    
    @Value("${huawei.obs.endpoint}")
    private String endpoint;
    
    @Value("${huawei.obs.bucketName}")
    private String bucketName;
    
    @Value("${huawei.obs.accessKey}")
    private String accessKey;
    
    @Value("${huawei.obs.secretKey}")
    private String secretKey;
    
    private ObsClient obsClient;
    
    @PostConstruct
    public void init() {
        obsClient = new ObsClient(accessKey, secretKey, endpoint);
    }
    
    public void upload(String objectKey, byte[] data) {
        try {
            PutObjectRequest request = new PutObjectRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(objectKey);
            request.setInput(new ByteArrayInputStream(data));
            obsClient.putObject(request);
        } catch (ObsException e) {
            throw new StorageException("文件上传失败", e);
        }
    }
    
    public String getUrl(String objectKey) {
        return String.format("https://%s.%s/%s", bucketName, endpoint, objectKey);
    }
    
    @PreDestroy
    public void destroy() {
        if (obsClient != null) {
            obsClient.close();
        }
    }
}
Office文档解析服务
@Service
public class OfficeParserService {
    
    // Word文档解析
    public String parseWord(InputStream input) throws Exception {
        try (XWPFDocument doc = new XWPFDocument(input)) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            org.w3c.dom.Document document = builder.newDocument();
            
            // 创建HTML根元素
            Element html = document.createElement("html");
            document.appendChild(html);
            
            // 处理文档内容
            Element body = document.createElement("body");
            html.appendChild(body);
            
            // 处理段落
            for (XWPFParagraph p : doc.getParagraphs()) {
                Element para = document.createElement("p");
                applyParagraphStyles(para, p);
                body.appendChild(para);
                
                // 处理文本和格式
                for (XWPFRun run : p.getRuns()) {
                    Node text = document.createTextNode(run.text());
                    if (run.isBold() || run.isItalic() || run.getUnderline() != UnderlinePatterns.NONE) {
                        Element span = document.createElement("span");
                        applyRunStyles(span, run);
                        span.appendChild(text);
                        para.appendChild(span);
                    } else {
                        para.appendChild(text);
                    }
                }
            }
            
            // 处理表格
            for (XWPFTable table : doc.getTables()) {
                Element htmlTable = document.createElement("table");
                applyTableStyles(htmlTable, table);
                body.appendChild(htmlTable);
                
                // 处理行和单元格
                for (XWPFTableRow row : table.getRows()) {
                    Element htmlRow = document.createElement("tr");
                    htmlTable.appendChild(htmlRow);
                    
                    for (XWPFTableCell cell : row.getTableCells()) {
                        Element htmlCell = document.createElement("td");
                        htmlRow.appendChild(htmlCell);
                        
                        // 处理单元格内容
                        for (XWPFParagraph p : cell.getParagraphs()) {
                            Element cellPara = document.createElement("p");
                            htmlCell.appendChild(cellPara);
                            
                            for (XWPFRun run : p.getRuns()) {
                                cellPara.appendChild(document.createTextNode(run.text()));
                            }
                        }
                    }
                }
            }
            
            // 转换DOM为HTML字符串
            return documentToString(document);
        }
    }
    
    // Excel文档解析
    public String parseExcel(InputStream input) throws Exception {
        try (XSSFWorkbook workbook = new XSSFWorkbook(input)) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            org.w3c.dom.Document document = builder.newDocument();
            
            Element html = document.createElement("html");
            document.appendChild(html);
            
            Element body = document.createElement("body");
            html.appendChild(body);
            
            // 处理每个工作表
            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                XSSFSheet sheet = workbook.getSheetAt(i);
                
                Element sheetDiv = document.createElement("div");
                sheetDiv.setAttribute("class", "excel-sheet");
                body.appendChild(sheetDiv);
                
                Element sheetTitle = document.createElement("h3");
                sheetTitle.setTextContent(sheet.getSheetName());
                sheetDiv.appendChild(sheetTitle);
                
                Element table = document.createElement("table");
                sheetDiv.appendChild(table);
                
                // 处理行
                for (Row row : sheet) {
                    Element tr = document.createElement("tr");
                    table.appendChild(tr);
                    
                    // 处理单元格
                    for (Cell cell : row) {
                        Element td = document.createElement("td");
                        tr.appendChild(td);
                        
                        switch (cell.getCellType()) {
                            case STRING:
                                td.setTextContent(cell.getStringCellValue());
                                break;
                            case NUMERIC:
                                if (DateUtil.isCellDateFormatted(cell)) {
                                    td.setTextContent(cell.getDateCellValue().toString());
                                } else {
                                    td.setTextContent(String.valueOf(cell.getNumericCellValue()));
                                }
                                break;
                            case BOOLEAN:
                                td.setTextContent(String.valueOf(cell.getBooleanCellValue()));
                                break;
                            case FORMULA:
                                td.setTextContent(cell.getCellFormula());
                                break;
                            default:
                                td.setTextContent("");
                        }
                    }
                }
            }
            
            return documentToString(document);
        }
    }
    
    // 辅助方法:将DOM转换为HTML字符串
    private String documentToString(org.w3c.dom.Document document) throws TransformerException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.setOutputProperty(OutputKeys.METHOD, "html");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        
        StringWriter writer = new StringWriter();
        transformer.transform(new DOMSource(document), new StreamResult(writer));
        return writer.toString();
    }
}

信创环境适配方案

国产化环境支持

  1. 操作系统适配

    • 提供基于龙芯、鲲鹏、飞腾等国产CPU的二进制依赖包
    • 针对中标麒麟、银河麒麟等系统优化字体渲染
  2. 浏览器兼容方案

    • IE8支持通过ES5语法+Polyfill实现
    • 针对国产浏览器(360安全浏览器等)进行特殊适配
  3. 字体兼容方案

    public class FontService {
        private static final Map FONT_MAPPING = Map.of(
            "宋体", "SimSun",
            "楷体", "KaiTi",
            "仿宋", "FangSong",
            "黑体", "SimHei",
            "方正小标宋简体", "FZXiaoBiaoSong-B05S"
        );
        
        public String mapToSystemFont(String fontName) {
            return FONT_MAPPING.getOrDefault(fontName, "Arial");
        }
    }
    

信创环境部署验证

# 龙芯环境验证脚本
#!/bin/bash

# 检查CPU架构
CPU_ARCH=$(uname -m)
if [[ "$CPU_ARCH" != "loongarch64" ]]; then
    echo "当前环境不是龙芯架构"
    exit 1
fi

# 检查操作系统
OS_INFO=$(cat /etc/os-release)
if [[ ! "$OS_INFO" =~ "Kylin" ]]; then
    echo "当前系统不是麒麟操作系统"
    exit 1
fi

# 验证Java环境
JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d '"' -f 2)
if [[ "$JAVA_VERSION" < "1.8" ]]; then
    echo "需要Java 8或更高版本"
    exit 1
fi

# 验证依赖库
LIB_CHECKS=(
    "/usr/local/lib/libreoffice/program/soffice"
    "/usr/local/lib/libpng16.so.16"
    "/usr/local/lib/libz.so.1"
)

for lib in "${LIB_CHECKS[@]}"; do
    if [[ ! -f "$lib" ]]; then
        echo "缺少依赖库: $lib"
        exit 1
    fi
done

echo "信创环境验证通过"

商务合作方案

授权与交付物

  1. 授权模式

    • 一次性买断授权:88万元人民币
    • 授权范围:集团及所有子公司永久使用
    • 包含未来3年免费升级维护
  2. 交付内容

    • 完整源代码及开发文档
    • 信创环境适配包
    • 集成示例和培训视频
    • 5×8小时技术支持服务
  3. 资质文件

    • 软件著作权证书
    • 华为鲲鹏兼容性认证
    • 中标麒麟适配认证
    • 5份央企合作案例证明

实施计划

  1. 第一阶段(1周)

    • 环境评估与需求确认
    • 签订合同与授权协议
  2. 第二阶段(2周)

    • 系统集成与测试
    • 信创环境适配验证
  3. 第三阶段(1周)

    • 用户培训与文档交付
    • 项目验收与尾款支付

技术优势与价值

  1. 全格式支持:完整保留Word/Excel/PPT/PDF的样式和内容
  2. 信创全适配:覆盖所有主流国产CPU和操作系统
  3. 高性能处理:基于流式处理的文档解析引擎
  4. 安全可靠:所有文件处理在用户内网完成,无数据外传风险

成功案例

已为以下单位提供类似解决方案:

  1. 某部委办公厅:公文编辑系统国产化改造
  2. 某省政务服务网:全信创环境适配
  3. 某国有银行:文档处理中间件采购

(可提供合同复印件、验收报告等证明材料)

七、技术支持体系

  1. 7×24小时支持

    • 信创环境专属技术支持通道
    • 紧急问题2小时响应
  2. 培训服务

    • 现场培训:3万元/天
    • 线上培训:包含在授权费用中
  3. 文档体系

    • 开发文档:API参考、集成指南
    • 用户手册:多格式操作指南

本方案通过深度适配信创环境,结合企业级架构设计,可满足集团在政府项目中的严格要求。预计实施周期12周,投入产出比显著,建议尽快启动项目招标流程。

复制插件目录

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

上传网络图片

自动上传网络图片

下载示例

点击下载完整示例

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值