[TOC] > Tue May 11 2021 04:44:50 GMT+0800 (GMT+08:00) WPS JS 插件功能区菜单貌似是基于 Office 2007 UI 标准的,你可以用它的 Ribbon UI 标准库来给自己的插件设计菜单。差别在于: 1. 面向 Office 设计的功能区菜单一般需要 VBA 宏作为菜单元素的事件响应,比如按钮、输入框等等。而 WPS 插件则需要用 JavaScript 的函数来响应。 > 注意:单文件`"注入"式(嵌入)`的 CustomUI 在 WPS 中打开虽然能显示自定义界面,但不会响应——除非你的 WPS 安装了 VBA 环境且切换到 VBA 环境。 > > WPS 支持的 CustomUI 是针对 JS 加载项(JS插件)的(通常它是全局的),而 Micro Office 的 CustomUI 是可以镶嵌到单个文件中(非全局,和文件一起保存,仅对这个文件生效),`WPS 没有提供这个功能支持——将CustomUI注入单个文件中配合JS宏一起使用`。 2. RibbonUI 的配置文件 XML 中各组件的配置大同小异,主要差异是在组件回调上。了解Custom UI 的话很容易就做好。 我们来看看官方文档中的示例: 1. Ribbon xml ```XML <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="OnAddinLoad"> <ribbon startFromScratch="false"> <tabs> <tab id="wpsAddinTab" label="wps加载项示例"> <group id="btnDemoGroup" label="group1"> <button id="btnShowMsg" label="弹出消息框" onAction="OnAction" getEnabled="OnGetEnabled" getImage="GetImage" visible="true" size="large"/> <button id="btnIsEnbable" getLabel="OnGetLabel" onAction="OnAction" enabled="true" getImage="GetImage" visible="true" size="large"/> <button id="btnShowDialog" label="弹对话框网页" onAction="OnAction" getEnabled="OnGetEnabled" getImage="GetImage" getVisible="OnGetVisible" size="large"/> <button id="btnShowTaskPane" label="弹任务窗格网页" onAction="OnAction" getEnabled="OnGetEnabled" getImage="GetImage" getVisible="OnGetVisible" size="large"/> <button id="btnApiEvent" getLabel="OnGetLabel" onAction="OnAction" getEnabled="OnGetEnabled" getImage="GetImage" getVisible="OnGetVisible" size="large"/> <button id="btnWebNotify" label="给业务系统发通知" onAction="OnAction" enabled="true" getImage="GetImage" getVisible="OnGetVisible" size="large"/> </group> </tab> </tabs> </ribbon> </customUI> ``` UI 组件回调响应 JS: ```js //这个函数在整个wps加载项中是第一个执行的 function OnAddinLoad(ribbonUI){ if (typeof (wps.ribbonUI) != "object"){ wps.ribbonUI = ribbonUI } if (typeof (wps.Enum) != "object") { // 如果没有内置枚举值 wps.Enum = WPS_Enum } wps.PluginStorage.setItem("EnableFlag", false) //往PluginStorage中设置一个标记,用于控制两个按钮的置灰 wps.PluginStorage.setItem("ApiEventFlag", false) //往PluginStorage中设置一个标记,用于控制ApiEvent的按钮label return true } var WebNotifycount = 0; function OnAction(control) { const eleId = control.Id switch (eleId) { case "btnShowMsg": { const doc = wps.WpsApplication().ActiveDocument if (!doc) { alert("当前没有打开任何文档") return } alert(doc.Name) } break; case "btnIsEnbable": { let bFlag = wps.PluginStorage.getItem("EnableFlag") wps.PluginStorage.setItem("EnableFlag", !bFlag) //通知wps刷新以下几个按饰的状态 wps.ribbonUI.InvalidateControl("btnIsEnbable") wps.ribbonUI.InvalidateControl("btnShowDialog") wps.ribbonUI.InvalidateControl("btnShowTaskPane") //wps.ribbonUI.Invalidate(); 这行代码打开则是刷新所有的按钮状态 break } case "btnShowDialog": wps.ShowDialog(GetUrlPath() + "/ui/dialog.html", "这是一个对话框网页", 400 * window.devicePixelRatio, 400 * window.devicePixelRatio, false) break case "btnShowTaskPane": { let tsId = wps.PluginStorage.getItem("taskpane_id") if (!tsId) { let tskpane = wps.CreateTaskPane(GetUrlPath() + "/ui/taskpane.html") let id = tskpane.ID wps.PluginStorage.setItem("taskpane_id", id) tskpane.Visible = true } else { let tskpane = wps.GetTaskPane(tsId) tskpane.Visible = !tskpane.Visible } } break case "btnApiEvent": { let bFlag = wps.PluginStorage.getItem("ApiEventFlag") let bRegister = bFlag ? false : true wps.PluginStorage.setItem("ApiEventFlag", bRegister) if (bRegister){ wps.ApiEvent.AddApiEventListener('DocumentNew', OnNewDocumentApiEvent) } else{ wps.ApiEvent.RemoveApiEventListener('DocumentNew', OnNewDocumentApiEvent) } wps.ribbonUI.InvalidateControl("btnApiEvent") } break case "btnWebNotify": { let currentTime = new Date() let timeStr = currentTime.toLocaleDateString() + ", " + currentTime.toLocaleTimeString() wps.OAAssist.WebNotify("这行内容由wps加载项主动送达给业务系统,可以任意自定义, 比如时间值:" + timeStr + ",次数:" + (++WebNotifycount), true) } break default: break } return true } function GetImage(control) { const eleId = control.Id switch (eleId) { case "btnShowMsg": return "images/1.svg" case "btnShowDialog": return "images/2.svg" case "btnShowTaskPane": return "images/3.svg" default: ; } return "images/newFromTemp.svg" } function OnGetEnabled(control) { const eleId = control.Id switch (eleId) { case "btnShowMsg": return true break case "btnShowDialog": { let bFlag = wps.PluginStorage.getItem("EnableFlag") return bFlag break } case "btnShowTaskPane": { let bFlag = wps.PluginStorage.getItem("EnableFlag") return bFlag break } default: break } return true } function OnGetVisible(control){ return true } function OnGetLabel(control){ const eleId = control.Id switch (eleId) { case "btnIsEnbable": { let bFlag = wps.PluginStorage.getItem("EnableFlag") return bFlag ? "按钮Disable" : "按钮Enable" break } case "btnApiEvent": { let bFlag = wps.PluginStorage.getItem("ApiEventFlag") return bFlag ? "清除新建文件事件" : "注册新建文件事件" break } } return "" } function OnNewDocumentApiEvent(doc){ alert("新建文件事件响应,取文件名: " + doc.Name) } ``` > 能在 JS 中找到组件中的一些属性(配置项)吧,函数名对应组件的ID或者其他属性值。 ## 工具集 1. Office Ribbon Editor [GitHub](https://github.com/fernandreu/office-ribbonx-editor) 2. visualribboneditor (据说不支持中文 label)加载项 https://andypope.info 上面两个都是针对 Microsoft Office 的工具,可定制 Office Menu UI(加载项的菜单\功能区自定义界面),因为 WPS 没有类似的工具,如果你不熟悉 Custom UI 标准,可以先在 Micro Office 上设计好,然后拿到 WPS 上使用。 custom UI 文档 https://docs.microsoft.com/en-us/openspecs/office_standards/ms-customui/574eeee8-7a03-406a-b95f-f9e51e53dd9d 貌似都用 Ribbon 这个组件。没太深入了解,大家自行研究。 自定义的菜单、功能区是由 xml 配置的,你还得为为这些自定义的菜单、按钮等写代码以响应其动作以及实现其功能。上面两个工具生成的组件回调函数都是 VBA 的 Sub 过程,你要自己手动改成 JS 的 function。并进一步完善功能逻辑。 如果你很熟悉这一套流程,可以: 1. Custom UI XML 转为 JSON。 2. JSON 中提取组件 ID、方法、事件名称,然后用字符模板生成组件的事件、功能对应的 JS 函数。 > 目的是不必在 XML 中一个一个组件地去找,然后一个一个组件地去写功能实现。 当然了,一个一个组件地去实现,靠谱些。