简介:针对WPS插件开发场景,这份演示程序基于Node.js环境,展示了在网页中调用并操作本地WPS应用的具体实现,适合具备JavaScript基础的开发者参考。资源共161个文件,其中JavaScript与TypeScript脚本承担核心业务逻辑,HTML页面完成前端展示,JSON文件保存配置参数,SVG资源用于图标呈现,另有sample示例可直接运行体验,MD与DOCX文档则提供使用说明与参考样例。整个压缩包仅1.52MB,轻量易部署,运行环境要求不高,目前已有1114人下载学习。借助该演示,开发者能够掌握WPS插件的基本工程结构、网页端与WPS程序的通信机制,以及常用API的调用方式,并据此扩展出在线打开、编辑、保存WPS文档等实际功能。虽然体积不大,但示例覆盖了插件开发的主要环节,代码组织与调用逻辑清晰,无论刚入门的初学者还是进阶开发者,都能从中获得良好的参考价值。 先别急着打开编辑器写代码,我见过太多人拿到wps-js-demo就往里塞业务逻辑,结果连插件跑不起来的原因都找不到。这个项目表面上是个JS Demo,实际涉及两条完全不同的技术路线:一条是在WPS客户端内部跑网页插件(加载项),另一条是从普通网页唤起并操作本机WPS程序。很多人把这两者混为一谈,才导致后面处处碰壁。这篇文章我会从demo的目录结构、调用链路、核心API、常见坑四个方面,把这两条路线一次讲透,顺便把我实际调试时踩过的坑也一并交代清楚。
1. 先搞清楚wps-js-demo到底在解决什么问题
1.1 网页与WPS之间的最后一公里
办公场景里有个很常见的需求:业务系统里点一个按钮,就能打开本机的WPS,把服务器上的合同、审批单或者模板直接编辑起来。传统的做法是ActiveX控件或者本地装个代理程序,但这两者一个被浏览器安全机制越收越紧,另一个要单独维护部署。wps-js-demo这类项目解决的就是这个问题,它用JavaScript作为统一入口,把"网页触发"和"WPS承载"之间的通信链路打通,让开发者不需要维护复杂的本地服务,也能实现网页调用WPS打开文档、填充内容、触发保存等操作。
1.2 demo的两种形态,别搞混
我在读这个demo源码的时候,发现很多初学者最大的困惑是:这个项目到底跑在哪?答案是有两种形态。
第一种是WPS加载项(Add-in)形态。WPS客户端本身内置了Chromium内核,可以加载一个基于HTML/JavaScript的网页插件,这个插件运行在WPS窗口内部,通过wps.Office等命名空间直接操作当前打开的文档。这种形态下,"网页"指的是插件UI,本质上是WPS客户端的一部分。
第二种是网页唤起本机客户端的形态。你在浏览器里打开一个业务系统页面,点击按钮后通过自定义URL协议(类似wps://open?path=xxx)让系统唤起本机已经安装的WPS进程,并带着参数打开指定文件。
wps-js-demo这个项目有时候会同时包含这两种能力的示例,如果分不清正在看哪部分代码,后面所有逻辑都会乱套。我的建议是:先确认demo里manifest.xml和wpsjs.config.json这两个文件的定位,前者是加载项形态的入口,后者是工程化编译配置,看到它们基本就能判断项目框架属于哪种路线。
2. demo项目结构与核心模块拆解
2.1 标准目录结构长什么样
以一版典型的wps-js-demo为例,目录结构大致如下:
wps-js-demo/ ├── manifest.xml # 加载项注册清单,声明插件名称、图标、入口页面 ├── wpsjs.config.json # wpsjs工程配置,定义调试端口、编译输出路径 ├── package.json # npm脚本与依赖声明 ├── src/ │ ├── index.html # 插件主页面,加载项形态下显示在任务窗格中 │ ├── index.js # 主逻辑入口 │ ├── api/ │ │ └── wps-handler.js # 封装WPS API调用的工具模块 │ └── utils/ │ └── protocol.js # 网页唤起客户端时生成协议URL的工具 ├── demo/ │ └── web-page.html # 纯浏览器页面,演示从网页发起调用 └── README.md这种结构有一个好处:把"在WPS内跑的代码"和"在浏览器里跑的代码"分开目录管理,避免互相污染。src是加载项代码,demo/web-page.html是外部网页调用示例。如果你拿到的demo不是这个结构,也没关系,认准manifest.xml和protocol相关文件就行。
2.2 入口文件和manifest配置是命门
加载项形态的入口是manifest.xml,它负责告诉WPS:我这个插件叫什么、图标在哪、任务窗格里加载哪个网页。一个最简化的manifest核心配置是这样:
<?xml version="1.0" encoding="UTF-8"?> <WpsAddIn> <DisplayName>我的WPS插件</DisplayName> <Version>1.0.0</Version> <Type>TaskPane</Type> <IconPath>images/icon.png</IconPath> <SourceLocation>https://localhost:3000/index.html</SourceLocation> </WpsAddIn>别看字段不多,SourceLocation这个值决定了WPS从哪加载你的插件页面。调试时它指向本地开发服务器的地址,部署后要改成线上HTTPS地址。我当时在这个字段上卡了很久,改了代码不生效,最后发现是WPS缓存了旧的manifest,清掉WPS缓存目录才恢复正常。如果你也遇到"插件列表里有但页面一直是旧的"这种问题,优先排查manifest缓存。
3. 网页调用WPS的完整链路
3.1 加载项形态:网页运行在WPS内部
在加载项形态下,调用链路是:用户打开WPS文档,任务窗格加载index.html,页面里的JavaScript通过WPS注入的wps全局对象操作文档。代码看起来是这个样子:
// src/index.js $(function () { if (window.wps) { // 获取当前WPS应用实例 const app = wps.WpsApplication(); // 读取当前文档的选中内容 const sel = app.ActiveDocument.Selection; const text = sel.Text; document.querySelector('#result').innerText = text; } });这里的关键点在于:window.wps不是你在浏览器里随便定义的对象,而是WPS客户端环境里由宿主注入的桥接对象。所以这个JS文件只能在WPS加载项环境里运行,不能直接在普通浏览器打开运行。很多初学者在Chrome里打开index.html,看到wps is not defined就以为代码错了,其实环境不对。
3.2 网页唤起本机WPS的URL协议原理
再看外部网页唤起本机客户端的形态。这种形态的底层是操作系统级的URL Protocol注册机制。程序安装时会在注册表里注册一个自定义协议(例如wps),浏览器遇到wps://开头的链接时,会把这个请求转交给注册表对应程序处理。
protocol.js里做的事情,本质上是拼接并触发这种URL:
// demo/web-page.html 中的核心逻辑 function openWithWps(filePath) { // 构造协议URL,encodeURIComponent处理中文和空格 const url = 'wps://open?path=' + encodeURIComponent(filePath); // 通过隐藏iframe触发协议,不打断当前页面 const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = url; document.body.appendChild(iframe); setTimeout(() => { document.body.removeChild(iframe); }, 2000); }用隐藏iframe而不是直接window.location.href,是为了防止页面在调用WPS时发生跳转,影响用户当前操作。这个细节非常实用,我建议直接照抄。
3.3 一次完整调用路径
整个流程可以归纳为五步:业务页面点击按钮,JavaScript拼接协议URL,浏览器把协议请求交给操作系统,操作系统根据注册表找到WPS主程序,WPS收到参数后解析path并打开对应文档。期间如果WPS还没启动,操作系统会先拉起进程再传参;如果已经启动了,需要WPS自身处理进程间通信,把参数交给已有实例。
这也能解释一个常见现象:为什么WPS已经在运行的时候,网页唤起打开文件比冷启动时慢。因为冷启动只是新开进程,而进程已在运行时,需要走一套IPC机制,容易因为单实例锁、文件占用等问题失败。遇到"第二次唤起没反应"的,大概率是WPS进程没释放锁。
4. 核心JS操作与参数详解
4.1 加载项API调用规范
WPS加载项形态提供的API,整体上模仿了Office JavaScript API的语义,但又针对WPS做了裁剪。日常写demo最常用到的是下面这组:
| 对象/方法 | 作用 | 备注 |
|---|---|---|
wps.WpsApplication() | 获取WPS应用实例 | 所有操作的根入口 |
Application.ActiveDocument | 当前活动文档 | 文字/表格/演示通用 |
Document.Save() | 保存文档 | 可以接as参数另存 |
Selection.Text | 读写选中区域文本 | 表格场景下是单元格内容 |
Application.CreateNewDocument() | 新建空白文档 | 常用在初始化测试中 |
Document.Sections.Item(index) | 按索引访问节 | 处理页眉页脚时会用 |
需要特别提醒的是:WPS的JS API在不同组件(文字、表格、演示)上的支持程度不一样。比如Selection.Text在文字组件里很稳定,但在表格组件里要改成Selection.Cells.Item(1,1).Text这种写法,否则会报空引用。demo里如果只覆盖了文字场景,你搬到表格里用之前,最好先去官方API列表查一下当前版本的组件支持矩阵。
4.2 一段能跑的完整示例
我从wps-js-demo里简化出一段可以直接跑的代码,功能是:在插件页面点击按钮,把当前文档选中内容替换为指定文本,然后另存一份副本:
// src/index.js const wpsApp = wps.WpsApplication(); const doc = wpsApp.ActiveDocument; function replaceAndSave() { try { const sel = doc.Selection; if (!sel.Text) { alert('请先选中一段文字'); return; } sel.Text = '【已替换】' + sel.Text; // 另存为docx doc.SaveAs('C:\\temp\\output.docx', 12); // 12是wdFormatXMLDocument枚举值,对应docx格式 console.log('保存成功'); } catch (err) { console.error('调用WPS API出错了:', err); alert('操作失败,错误信息:' + err.message); } } document.querySelector('#btn-replace').addEventListener('click', replaceAndSave);SaveAs的第二个参数是格式枚举值,这一点很多人忽略。12是docx,其他常用值包括0对应doc、17对应pdf。如果传错枚举,轻则保存出来的格式不对,重则直接抛异常。建议每次都显式传格式参数,不要依赖默认值。
4.3 网页端调用的参数传递细节
外部网页唤起形态下,参数传递就是一个协议URL拼接的过程,但实际项目里参数往往不止path一个,还可能有action(打开还是新建)、password(加密文档密码)、readonly(只读打开)等。demo里utils/protocol.js通常会把参数对象序列化成query string:
// utils/protocol.js export function buildWpsUrl(params) { const base = 'wps://action'; const query = Object.keys(params) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) .join('&'); return `${base}?${query}`; }这里有一个特别值得注意的点:协议URL的query string不一定要区分大小写,但参数值一定要做URL编码。中文、空格、&符号这几种字符,如果不编码,一进入WPS解析阶段就会出问题。我遇到过最典型的情况是文件路径里带&,结果WPS把路径截断,只打开了前半段目录,排查半天才意识到是编码问题。
5. 常见问题与排查经验
5.1 网页唤起没反应,WPS完全不动
优先检查三件事。第一,协议是否被安全软件拦截,很多企业安全策略会禁止浏览器自定义协议唤起本地程序。第二,是否处于框架限制环境,比如页面嵌在第三方App的WebView里,WebView往往不支持协议跳转。第三,WPS是否安装为MSI版本且注册表协议被清理。
如果想快速验证是不是注册表问题,可以在命令行里直接输入:
start wps://open?path=C:\test.docx如果命令行能唤起,说明WPS和注册表正常,问题出在浏览器或网页环境;如果命令行也唤不起,那就是WPS的协议注册出了故障,重装WPS或者导入注册表修复文件能解决。
5.2 加载项调试时页面白屏
遇到白屏,八成是SourceLocation指向的本地服务器没启动,或者WPS无法访问localhost地址。这时候先确认命令终端里wpsjs debug是否真的跑起来了,然后直接在Chrome里打开配置中的地址,看页面能否正常渲染。如果Chrome渲染正常而WPS里白屏,再考虑是不是WPS的调试端口被占用,换个端口重试。
另外提一句,WPS加载项的调试比较特殊,它不会像浏览器插件那样直接显示开发者工具。你需要在wpsjs.config.json里开启debug模式,然后在WPS插件菜单里找到"开发者工具"入口,才能打开控制台。很多新人对这一点没概念,以为JavaScript报错就只能干瞪眼。
5.3 Document对象拿不到,API一直报错
这个问题通常和事件时机有关。WPS加载项页面加载时,WPS可能还没有把ActiveDocument准备妥当,此时调用wps.WpsApplication().ActiveDocument,拿到的对象是null。老实的做法是监听文档就绪事件:
wps.WpsApplication().ActivePresentation; // 或者用setTimeout轮询等待 function waitForDocument(callback) { const timer = setInterval(() => { const doc = wps.WpsApplication().ActiveDocument; if (doc) { clearInterval(timer); callback(doc); } }, 200); }注意不同组件文档就绪事件的名称不一样,文字组件是DocumentOpen、表格组件是WorkbookOpen。如果你拿到的demo只写了文字示例,应对表格场景时一定要自己查事件名,否则就会陷入轮询死循环。
5.4 安全校验与白名单限制
不管哪条路线,最终都会遇到安全策略这道坎。网页唤起本机WPS,不仅要把协议注册好,还要确保站点的域名在这台机器上被浏览器允许发起协议跳转。企业内网环境里,如果IE浏览器作为默认浏览器,安全区设置里面"允许ActiveX"和"允许打开本地程序"两个选项经常是关闭状态,这是很多内网系统调用WPS失败的隐性原因。
加载项形态也有类似限制,WPS在加载外部网页插件时,会对证书和地址做校验。开发阶段用http://localhost没问题,一旦部署到生产环境,必须换成HTTPS地址,否则插件会被拒绝加载。这一点我建议在项目初期就规划好,别等到上线前才匆匆忙忙换证书,到时候会有一堆混合内容、跨域问题等着你。
6. 一些实际的建议
如果你准备在自己的项目里复刻wps-js-demo,我建议不要一上来就追求把所有功能都塞进插件页面。先做最小闭环:把"网页按钮点击,唤起WPS打开固定路径文档"跑通,再考虑"将业务数据传入文档模板"。因为URL协议方式传复杂数据结构很受限,一旦需要传JSON或大批量数据,就说明你该用本地代理服务或加载项方式了,而那条路线的改造量远超预期。
另外,WPS的JS API文档更新比较频繁,不同版本的WPS客户端对API的支持程度也不完全一致。我吃过亏的是,代码里调用了某个新版才有的API,在客户的老版本WPS上直接白屏。稳妥的做法是在代码里做能力检测,像浏览器特性检测那样,判断某个API是否存在后再调用,这样老版本至少能降级提示,而不是静默崩溃。
最后再分享一个调试技巧:WPS加载项页面里的console.log,在开发者工具控制台里是能看到的,但它打印的对象结构跟浏览器里略有差异,很多WPS对象打印出来显示的是Object,没有可展开的属性。这个时候不要慌,用JSON.stringify先序列化常见字段,或者用Object.keys()查看对象上挂了哪些属性,会比直接点开对象快得多。实践几次之后你会发现,WPS JS插件开发和网页开发在思维上没有本质区别,只要把环境差异和API边界摸清楚,剩下的都是常规操作。
本文还有配套的精品资源,点击获取