ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用JavaScript开发WPS插件:从JS加载项到文档自动化实践

用JavaScript开发WPS插件:从JS加载项到文档自动化实践 简介面向Web与Node.js开发者的WPS插件开发示例工程解决网页端调用WPS完成文档打开、编辑、保存的集成需求适合需嵌入WPS能力或做二次开发的技术人员参考。压缩包共161个文件以JS/TS源文件、HTML页面、JSON配置、CSS样式及SVG图标为主另有sample示例、docx测试文档与Git版本记录JS/TS构成插件逻辑HTML/CSS呈现页面JSON/config定义调用参数整体仅1.52MB目录清晰易于定位入口。目前已有1118人学习下载。工程含可运行demo覆盖资源初始化、文档保存、页签与回调等流程通过源码和sample示例可理清调用链路、鉴权参数配置及异常处理思路方便迁移到自身业务系统是WPS插件开发的上手参考。 wps-js-demo 这个项目名已经把定位交代清楚了一套用来演示“用 JS 写 WPS 插件”的工程。它解决的核心诉求是——当你想在网页里打开 WPS、读写文档、触发另存为时不再走 VBA 或 COM 那条老路而是用前端技术栈直接调 WPS 的能力。常见落地场景有企业 OA 里的合同套红、报价单批量生成、模板填值以及把已有网页编辑器能力嫁接到 WPS 文档上。适合已经写过 HTML/CSS/JS、但没接触过 WPS 二次开发的人也适合想评估“该学 VBA 还是直接上车 JS 插件”的团队。它不是一个开箱即用的成品而是把最小可跑通的链路铺给你看让你顺着这条线做自己的插件。2. 为什么是 JS 加载项选型对比与最小 Demo 跑通2.1 WPS 插件开发的几条路JS 加载项、JS 宏、VBA 宏怎么选WPS 的二次开发并不只有一种入口。传统做法里VBA 宏最老也最常见很多自动化脚本都是从一个Sub Macro()开始的后来有了 COM 加载项能用 C/C# 做更重的功能但部署要在注册表里登记版本和位数一不匹配就翻车。wps-js-demo 走的则是 JS 加载项这条路插件本体是一个网页WPS 在特定位置加载这个页面页面里的 JS 通过桥接对象调用文档能力。我一般会先用一个对比把选型想清楚再动手避免做到一半发现能力边界不够用。VBA 宏的优势是离文档最近录制、编辑、调试都在 WPS 里适合个人脚本劣势是界面能力弱想做一个带交互的侧边栏非常痛苦。COM 加载项性能好但开发门槛和分发成本高。JS 加载项恰好补了中间地带界面用 HTML/CSS业务逻辑用 JS文档操作用 WPS 暴露出来的对象模型和 VBA 的调用思路几乎一致但不再需要受宏安全策略的气。这里要注意一个很多人刚接触时会混的点wps-js-demo 里的“JS”和 WPS 自带的“JS 宏”不是一回事。JS 宏是在 WPS 宏编辑器里写脚本运行在文档进程内适合自动化处理当前文档JS 加载项则是一个独立网页应用通过清单文件挂到 WPS 里既能调文档 API也能用你自己的前端组件。实际项目中我的经验是只要能接受“用户先打开 WPS 再打开侧边栏”就优先用加载项如果脚本要绑在按钮上、由别人在文档里触发JS 宏或 VBA 反而更直接。wps-js-demo 这类工程的价值就是把加载项这条路的最小骨架给你立起来。2.2 最小工程长什么样目录、清单文件和启动命令一个新项目不用着急写功能先把目录立起来。wps-js-demo 的最小形态有三个文件一个清单文件告诉 WPS“你是谁、页面在哪”一个 HTML 承载界面一个 JS 负责业务逻辑。我习惯把静态资源都放在 assets 下但初版没必要过度分层。wps-js-demo/ ├── manifest.xml ├── index.html ├── demo.js └── assets/ ├── css/ └── js/manifest.xml 是加载项能不能被 WPS 认出来的关键。它声明了插件的 ID、名称、权限以及最重要的SourceLocation——也就是你的网页地址。开发阶段我会起一个本地静态服务让 WPS 指向http://localhost:3000/index.html改完代码刷新页面就能看到效果不用反复重装插件。?xml version1.0 encodingUTF-8? OfficeApp xmlnshttp://schemas.microsoft.com/office/appforoffice/1.1 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:typeTaskPaneApp Id8f3a4c9e-9b2d-4f7a-9e15-dc9c9d5e5a1b/Id Version1.0.0.0/Version ProviderNameDemo/ProviderName DefaultLocalezh-CN/DefaultLocale DisplayName DefaultValuewps-js-demo / Description DefaultValueWPS JS 插件示例工程 / Hosts Host NameDocument / /Hosts DefaultSettings SourceLocation DefaultValuehttp://localhost:3000/index.html / /DefaultSettings PermissionsReadWriteDocument/Permissions /OfficeApp这份清单里最常改的是SourceLocation本地调试指到 localhost发布时换成内网地址或 HTTPS 地址。Id必须是一个唯一值我一般用在线 UUID 生成器生成一次就不再动。Permissions声明了文档读写权限如果你的插件只读不写改成ReadDocument可以减少权限弹窗但实际项目里大多数场景都是 ReadWriteDocument。加载清单时不同 WPS 版本的入口位置不一样常见做法是在 WPS 的“开发工具”里选“加载项”并指定这个文件也有团队直接把 manifest 放到加载项目录里让 WPS 启动时自动识别但注意目录路径不能有中文否则部分版本会识别失败。2.3 跑通第一条链路网页按钮触发 WPS 新建文档清单文件只是登记关系真正要验证的是“网页里的 JS 能不能驱动 WPS”。第一条链路我建议做成最简单的新建文档页面放一个按钮点击后调用Application.Documents.Add()再往当前文档里写入一段文字。这一步跑通后面所有文档操作都基于同样的桥接方式。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / titlewps-js-demo/title /head body button idcreateDoc新建文档并写入内容/button script srcdemo.js/script /body /html// demo.js // 在 WPS 加载项宿主中window.Application 是全局入口 var app window.Application; document.getElementById(createDoc).onclick function () { if (!app) { alert(当前不在 WPS 加载项环境中无法调用文档 API); return; } // 新建一个空白文档并激活 app.Documents.Add(); var doc app.ActiveDocument; doc.Range().Text wps-js-demo 跑通了; doc.SaveAs(C:\\demo\\output.docx, 0); doc.Close(false); };这里有两个参数值得细看。SaveAs的第二个参数是文件格式枚举0代表 docx 格式如果你要导出为其他格式需要去查对应枚举值而不是猜数字。doc.Close(false)里的false表示不保存关闭因为前面已经主动SaveAs了这里如果再传true反而会再弹一次保存确认框。doc.Range()不带参数表示选区覆盖全文也可以传起止位置比如doc.Range(0, 5)只操作前 5 个字符。这段代码跑通后就说明网页到 WPS 的桥接是通的可以进入真正的功能开发了。3. 打开 WPS 文档的实用 API参数、调用时机与和 VBA 的差异3.1 打开、新建与激活从 Application 到 Documents 的对象关系wps-js-demo 的核心操作基本都是围绕三件套Application、Documents、Document。Application是 WPS 主程序对象Documents是当前打开的文档集合Document是单个文档。第一次写的人容易把Application和Document混在一起总想着app.Open(...)但正确路径是app.Documents.Open(...)因为打开动作天然属于“文档集合”这个层级。var app window.Application; // 打开已有文档路径中的反斜杠在 JS 字符串里要写成双反斜杠 var doc app.Documents.Open(D:\\template\\合同模板.docx, false, true); // 第二个参数 false 表示不询问格式转换第三个参数 true 表示只读打开 // 只读打开适合做预览避免误改原文件Open方法有多个可选参数最常用的前四个是FileName、ConfirmConversions、ReadOnly和AddToRecentFiles。我的建议是预览场景传true只读编辑场景传false同时把ConfirmConversions固定为false否则遇到格式不完全兼容的文件时会弹一个用户看不懂的对话框。如果你要打开的文件路径由网页前端传入注意 JS 字符串里的转义。路径用正斜杠D:/template/合同模板.docx也能被 WPS 接受比双反斜杠省心。新建文档则简单得多app.Documents.Add()返回一个新文档对象但并不会自动变成当前激活文档。如果你立即想往里面写内容最好先拿到app.ActiveDocument因为Add()在部分 WPS 版本里返回的虽然是新文档但焦点可能还在旧文档上。稳妥的做法是var doc app.Documents.Add(); doc.Activate();再操作这个doc。这个细节在 VBA 里不太明显但在网页加载项里很容易踩到因为浏览器的焦点和 WPS 的焦点是两回事。3.2 内容读写与查找替换网页传入的参数如何落进文档打开文档只是第一步真正的价值在于把网页上的数据填到文档里。最朴素的做法是拿到全文然后做字符串替换。wps-js-demo 的演示代码里经常出现doc.Range().Text这个属性返回全文的纯文本往它赋值就是把整篇内容替换掉。注意非文字内容图片、表格、页眉页脚不会出现在Text里直接操作Text只适合纯文本模板。var doc app.ActiveDocument; var body doc.Range().Text; body body.replace(/{{甲方}}/g, 某某科技有限公司); body body.replace(/{{金额}}/g, 19800.00); doc.Range().Text body;这段代码有性能隐患body.replace在 JS 里是一次性全文替换但如果模板有几百个占位符、文档又是几十页反复读写Range().Text会明显卡顿。更稳的做法是先用正则把占位符收集成数组分批次写入而不是每替换一个就整篇赋值一次。我一般会先把替换规则放在一个对象里循环replace最后只写回一次。查找替换还有一个细节容易被网页前端忽略——大小写。VBA 里的Find默认区分大小写而 JS 的String.prototype.replace不区分不了的话容易错。比如用户输入company和文档里的Company直接字符串替换会漏掉。这时可以考虑先用正则加i标志做忽略大小写匹配再保留原词的大小写形态。反过来如果你做的是代码文档又希望严格匹配大小写那就别加i标志。WPS 的 JS 对象模型里也保留了类似Find.Execute的调用方式但参数不如 VBA 直观在网页端我更推荐先把内容读出来、在 JS 层处理再写回调试时能直接看中间变量。3.3 保存、另存为与格式选择别让输出类型只靠数字猜保存操作看似简单却是 wps-js-demo 用户最容易产生“文件打不开”“格式不对”抱怨的地方。直接doc.Save()会存回原路径这对只读打开的文件会报错。doc.SaveAs(path, fileFormat)的第二个参数是枚举数字不同 WPS 版本的枚举值并不总是同一个尤其是导出为 PDF、HTML、CSV 这类跨格式场景。var doc app.ActiveDocument; var ext docx; if (ext pdf) { doc.SaveAs(D:\\output\\报表.pdf, 7); // 7 在多数版本里代表 PDF } else if (ext docx) { doc.SaveAs(D:\\output\\报表.docx, 0); } else if (ext txt) { doc.SaveAs(D:\\output\\报表.txt, 2); }我的血泪经验是不要在自己的代码里硬编码这些数字除非你确认当前 WPS 版本的枚举值。更稳妥的做法是先查宿主环境支持的格式列表或者只保留最常用的 docx 和 PDF其余格式让用户手动另存为。另一个常见的翻车点是传了路径但没传文件名后缀WPS 不会自动补全保存出来的文件没有扩展名。正确做法是在 JS 侧自己拼好完整文件名再传给SaveAs。如果你想让网页端能下载生成的文件不要试图从插件里直接拿二进制而是把文档保存到一个共享目录或服务端指定的路径再交给前端下载。4. 从 Demo 到可用插件JS 宏、事件绑定与任务窗格交互4.1 JS 宏和加载项怎么配合三个入口各管一段很多团队最初接触 wps-js-demo 时会下意识地问我是不是还要学 WPS 的 JS 宏答案是看场景。JS 宏适合做“用户打开文档就能点按钮执行”的自动化脚本加载项适合做“带网页界面、需要远程配置或集成登录”的业务应用。实际项目里两者经常共存加载项负责收集网页端的表单数据再调用一个doc.Execute之类的方法执行已经注册好的宏或者加载项本身就把宏逻辑用 JS 重写不需要再依赖文档内宏。入口一是在加载项页面里直接写业务逻辑所有操作都在demo.js里适合逻辑简单、只有你自己的团队用。入口二是通过Application.Run调用文档内已有的宏适合已有大量 VBA 或 JS 宏资产、不想重复迁移的存量项目。入口三是把宏写成独立 JS 文件打包时随加载项一起分发在页面里用script src引入。我一般会优先选入口三因为它既保留宏的可复用性又让加载项页面还能访问这些函数。// demo.js // 注册一个全局函数让任务窗格里的按钮能调用 window.generateReport function () { var app window.Application; var doc app.ActiveDocument; var data window.collectFormData(); // 另一个 JS 文件里的函数 var tpl doc.Range().Text; Object.keys(data).forEach(function (key) { tpl tpl.replace(new RegExp({{ key }}, g), data[key]); }); doc.Range().Text tpl; doc.Save(); };这种写法最大的好处是让“取数”和“写文档”分离。页面负责把表单数据整理好宏只做模板替换和保存将来表单改版宏不用动反之亦然。如果你要在整个页面生命周期里维护一份自定义状态可以挂在window上但要注意不要和 WPS 注入的对象重名否则可能出现诡异覆盖。4.2 网页与任务窗格的通信postMessage 解决多级联动和异步刷新加载项的界面本质是一个网页它跑在 WPS 的任务窗格里。这个窗格和 WPS 文档区是两个独立窗口但可以通过postMessage通信。第一次做的人容易以为页面 JS 能直接操作 DOM 一样操作文档其实文档那侧的数据必须经过 API 拿回来再渲染到页面 DOM 上。举一个常见的例子网页里做省市区三级联动下拉用户选完省市和区要异步刷新。这个逻辑纯前端就能做不涉及 WPS API所以放在index.html里正常写就行。但如果你选择省之后需要实时读取当前文档里的某个字段来联动就要先通过Application拿数据再渲染下拉。// parent.html 中向任务窗格发送命令 window.parent.postMessage({ type: GET_DOC_TEXT, key: customerName }, *); // 任务窗格内接收 window.addEventListener(message, function (event) { var msg event.data; if (msg.type GET_DOC_TEXT) { var doc window.Application.ActiveDocument; var value doc.Range().Text.match(msg.key)[0]; event.source.postMessage({ type: DOC_TEXT, value: value }, *); } });这里*仅为开发演示方便实际生产中要限定为 WPS 加载项的 origin否则任意网页都能向你发消息。事件监听里每次收到消息最好做一个typeof判断因为任务窗格可能同时收到 WPS 内部的初始化消息不小心处理就会抛错。此外postMessage传递的是文本数据千万别尝试直接传文档对象序列化后到对面已经不是可调用的对象了。4.3 加载项发布前的权限与清理别让插件变成只开不关的黑匣子页面里每新建一个文档对象WPS 进程里就多一份资源占用。wps-js-demo 的演示代码通常不会在意这个但你要做成生产插件必须在每次操作完成后主动释放。doc.Close(false)是关闭但不保存app.Documents.CloseAll()可以一次关掉所有文档但要注意会打断用户手头的工作。更温和的做法是只关闭你打开的那些文档并且把引用变量设置为null。权限方面清单文件里的Permissions只是第一道门槛。如果你还用了网页里的window.open、XMLHttpRequest请求外网接口WPS 宿主对这类请求的拦截策略和普通浏览器不完全一样。我遇到过加载项页面里 AJAX 请求被拦、控制台却看不出原因的情况后来查到是加载项宿主对跨域请求有额外限制。解决方法是把需要请求的域名加进清单文件的AppDomains节点里或者让后端接口支持Access-Control-Allow-Origin。这两个方向都不难但容易在测试环境漏掉。5. 避坑指南wps-js-demo 最常见的 5 个翻车现场5.1 宿主环境与路径编码看似 API 报错的三种情况翻车现场一在普通浏览器里打开index.html调试时发现window.Application是undefined。现象是页面上其他功能都正常一调用文档 API 就抛“对象不支持此操作”。原因很简单window.Application是 WPS 加载项宿主注入的普通浏览器没有。解决开发时先用一个能力探测函数兜底比如typeof window.Application undefined时给出明确提示调试文档 API 必须挂到 WPS 加载项里跑浏览器只负责调试 UI 样式。翻车现场二中文路径打开失败报“文件不存在”但在 WPS 里手动打开同一路径就没问题。原因大多是 JS 文件保存时用了非 UTF-8 编码或路径字符串里的中文在加载项宿主和操作系统之间转码不一致。解决代码文件一律 UTF-8 编码路径统一用正斜杠避免在字符串里拼接反斜杠和中文。如果路径由后端传入先打印一遍确认再传给Open。翻车现场三写了doc.SaveAs后用 WPS 打不开报告说“格式与扩展名不匹配”。原因几乎都是第二个参数的文件格式枚举值传错了尤其不要从网上复制来历不明的数字。解决准备一个本地测试脚本把常见格式枚举列出来逐一验证或者保存前先检查扩展名。不要相信“7 就是 PDF”这种经验版本一换就可能变成别的格式。5.2 文档对象生命周期打开快、关不掉、保存错翻车现场四页面里连续打开多个文档最终 WPS 内存占用飙升甚至整个进程无响应。原因是每次Documents.Open返回的doc变量没有被释放虽然 JS 有垃圾回收但 WPS 对象模型属于 COM 桥接对象回收并不及时。解决用完后显式doc.Close(false)并把变量置null如果确实需要同时打开多份建议控制并发一次只处理一份处理完再开下一份。翻车现场五对只读打开的文档执行Save时报“权限不足”但用户明明有文件写权限。原因是打开时第三个参数true指定了只读模式文档对象内部状态就是只读后续Save自然失败。解决要么打开时传false要么在需要保存前先doc.ReadOnly false。更稳妥的做法是把“预览模式”和“编辑模式”设计成两个按钮让用户明确知道当前是什么状态而不是始终用同一个入口。6. 从 Demo 到生产力用日志和断言把插件质量守住wps-js-demo 跑通之后你真正要面对的是“这个插件能不能稳定用好几年”。我自己的习惯是给所有关键调用加日志和最小断言不要等到用户报错才去猜。在开发环境我会用一个简单的log()函数包一层记录每次打开、保存、替换操作的参数和结果。function log(action, detail) { console.log(new Date().toISOString(), action, detail); } function assertDoc(doc, action) { if (!doc || doc.Name undefined) { throw new Error(文档对象无效操作失败 action); } }日志不一定只写控制台生产环境可以把这个log接一个上报接口把操作序列发到服务端。一旦用户反馈“我的模板被改了”你能从日志看到是哪一次替换、哪个占位符没有匹配上没有日志那就只能靠用户口头描述等于让一个看不见的黑匣子背锅。断言则要在关键节点检查状态打开成功后确认文件名保存成功后确认文件大小大于零替换后确认占位符残留数量。这些断言会让你的插件在早期暴露问题而不是等到文档已经被错误覆盖才后悔。另一条实用技巧是给所有写操作做“双保险”先另存为副本再在副本上做替换成功后再覆盖原文件。我过去的教训是把占位符替换直接写在原文档上结果正则漏了一个边缘情况导致整段模板内容被清空又没有备份只能从 Git 历史里找找回。现在我的习惯是每个文档操作前先备份一份.bak文件既不影响用户体验又给自己留一粒后悔药。同时任务窗格要有一个醒目的“当前只读/可编辑”状态提示避免用户在预览模式里以为保存成功了。如果你正准备照着 wps-js-demo 做自己的插件我的建议是先不要扩散到太多 API把一个“打开模板→填值→另存为→回收对象”的闭环打磨干净把日志和断言同步加上。这样扩展其他功能时每一步都有迹可循。希望帮到你。本文还有配套的精品资源点击获取
返回列表