程序员的外包奇遇记:Word一键粘贴大作战

大家好,我就是那个在安徽码PHP的"秃"出程序员!最近接了个CMS企业官网的活儿,客户爸爸突然甩来一个需求…

需求来了!

“小张啊,我们这个新闻发布系统啊,能不能像Word那样直接粘贴?那些老同志不会用啊!”

我:“呃…老板,您这需求…”

“还有啊,Word、Excel、PPT、PDF都要能导入!公式啊图片啊都不能丢!Latex公式要转成什么ML…”

我:“MathML…”

“对!就是这个ML!还要支持公众号内容导入!预算680!”

我:“…”(内心OS:680?您这是要我的命啊!)

技术选型历险记

我翻遍了GitHub,试了各种开源方案:

  • 方案A:能导入Word,但公式变成了一堆乱码
  • 方案B:支持PDF,但图片全丢了
  • 方案C:号称全支持,结果安装包比我的项目还大

最后我决定——自己动手,丰衣足食!

前端Vue3+UEditor魔改方案

// 在UEditor中增加导入按钮
UE.registerUI('importword', function(editor) {
  const btn = new UE.ui.Button({
    name: 'importword',
    title: '导入Office文档',
    onclick: function() {
      // 创建隐藏的file input
      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]
        if (!file) return
        
        // 显示加载中
        editor.setDisabled()
        editor.fireEvent('showmessage', {
          id: 'uploading',
          content: '正在解析文档,请稍候...'
        })
        
        try {
          const formData = new FormData()
          formData.append('file', file)
          
          // 调用后端API解析文档
          const res = await axios.post('/api/parse-office', formData, {
            headers: { 'Content-Type': 'multipart/form-data' }
          })
          
          // 插入解析后的HTML
          editor.execCommand('insertHtml', res.data.html)
        } catch (err) {
        } finally {
        }
      }
      
      fileInput.click()
    }
  })
  
  return btn
})

后端PHP处理逻辑

// 文件接收接口
public function parseOffice(Request $request) {
    $file = $request->file('file');
    
    if (!$file->isValid()) {
        return response()->json(['error' => '文件上传失败'], 400);
    }
    
    $extension = strtolower($file->getClientOriginalExtension());
    $allowed = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'];
    
    if (!in_array($extension, $allowed)) {
        return response()->json(['error' => '不支持的文件类型'], 400);
    }
    
    try {
        $tempPath = $file->store('temp');
        $fullPath = storage_path('app/'.$tempPath);
        
        // 根据文件类型调用不同的解析器
        switch ($extension) {
            case 'doc':
            case 'docx':
                $html = $this->parseWord($fullPath);
                break;
            case 'xls':
            case 'xlsx':
                $html = $this->parseExcel($fullPath);
                break;
            case 'ppt':
            case 'pptx':
                $html = $this->parsePPT($fullPath);
                break;
            case 'pdf':
                $html = $this->parsePDF($fullPath);
                break;
            default:
                throw new \Exception('未知文件类型');
        }
        
        // 处理图片上传到OSS
        $html = $this->processImages($html);
        
        return response()->json(['html' => $html]);
    } catch (\Exception $e) {
    } finally {
    }
}

数学公式转换方案

// Latex转MathML
private function latexToMathML($latex) {
    // 使用第三方API或本地库转换
    $apiUrl = "https://latex2mathml.example.com/convert";
    
    $response = Http::post($apiUrl, [
        'latex' => $latex,
        'type' => 'mathml'
    ]);
    
    if ($response->successful()) {
        return $response->json()['mathml'];
    }
    
    // 备用方案:使用本地PHP库
    return $this->fallbackLatexToMathML($latex);
}

图片处理方案

// 处理HTML中的图片并上传到OSS
private function processImages($html) {
    $dom = new \DOMDocument();
    @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    
    $images = $dom->getElementsByTagName('img');
    
    foreach ($images as $img) {
        $src = $img->getAttribute('src');
        
        // 如果是base64图片
        if (strpos($src, 'data:image') === 0) {
            $uploadedUrl = $this->uploadBase64Image($src);
            $img->setAttribute('src', $uploadedUrl);
        }
        // 如果是本地临时图片
        elseif (strpos($src, '/temp/') !== false) {
            $uploadedUrl = $this->uploadLocalImage($src);
            $img->setAttribute('src', $uploadedUrl);
        }
    }
    
    // 保存处理后的HTML
    return $dom->saveHTML();
}

部署与集成指南

  1. 前端集成

    • 将UEditor插件打包成npm包
    • npm install ueditor-office-import
    • 在main.js中导入并初始化插件
  2. 后端准备

    • 安装PHP依赖:composer require phpoffice/phpword phpoffice/phpspreadsheet
    • 配置阿里云OSS凭证
    • 部署API接口
  3. 环境要求

    • PHP >= 7.4
    • 开启exec函数权限(用于调用外部转换工具)
    • 至少100MB临时存储空间

预算控制秘籍

如何在680元内搞定这个需求?我的秘诀是:

  1. 能用开源绝不自研

    • PHPWord/PHPSpreadsheet处理Office文档
    • UEditor作为基础编辑器
    • 数学公式转换使用开源API
  2. 云服务薅羊毛

    • 阿里云OSS新用户免费额度
    • 使用按量付费的函数计算处理大文件
  3. 代码复用

    • 把通用功能打包成插件,下次还能用
    • 公式转换等复杂逻辑封装成微服务

加入我们的QQ群!

遇到问题?想交流经验?来加群223813913!群里福利多多:

  • 🧧 新人红包1-99元(能不能抢到99看手气)
  • 💰 推荐客户拿20%提成(月入4万不是梦)
  • 🆓 定期分享外包项目(有钱大家一起赚)
  • 💻 技术交流无门槛(从PHP到AI都能聊)

记住我们的口号: 代码写得好,对象少不了;需求接得多,早日开豪车!


温馨提示:本文代码仅供参考,实际开发中请做好异常处理和安全性检查。公式转换部分可能需要购买商业API服务。预算680元请自行斟酌,本程序员不承担因加班过度导致的发际线后移责任。

复制插件目录

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

上传网络图片

自动上传网络图片

下载示例

点击下载完整示例

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐