📎 配套资产 / Companion assets:本篇所有示例的可下载文件在仓库
配套资产/B27_OfficeJS/——manifest.xml + functions.json + functions.js + commands.js + package.json,每个子目录都带中文 README 与 English README。 English: downloadable files for every example in this article live in配套资产/B27_OfficeJS/— manifest.xml + functions.json + functions.js + commands.js + package.json, each with a bilingual README.
【办公开发】AI 辅助开发 Office JS 加载项(自定义函数)
摘要:用 AI 帮你搭一个 Office JS 加载项,做一个能在 Excel 里直接调用的自定义函数——不写 VBA、不弹宏警告、也不锁定平台。
一、痛点引入
你有没有过这种场景:财务给了一套复杂的奖金计算规则,要在 50 张表、上百个单元格里反复用;或者你写了个 VBA 函数,结果同事用的是 Mac 版 Excel,宏根本打不开。
传统办法要么是复制粘贴公式(改一处全错),要么上 VBA(跨平台差、打开还总弹安全提示,烦人)。
其实 Excel 还藏了一条"官方正道":用 Office JS(Office JavaScript API,一套微软官方提供的、用 JavaScript 给 Office 写扩展的接口)做一个加载项(add-in)=可以理解为 Excel 里的一个"小程序",装上去之后能在 Excel 里多出一个功能面板,或者——这正是本文主角——一组自定义函数(custom function)=你在 Excel 里也能像 SUM 一样调用的一个小程序,输入参数就返回结果。它不挑系统,Windows、Mac、网页版 Excel 都能跑。
二、目标产出
跟着做完,你会拥有一份能直接运行的加载项骨架,里面有一个自定义函数 =TUTORIAL.ADD(数字1, 数字2),在任意单元格输入它就返回两数之和。
更重要的是,你学会了用 AI(比如 ChatGPT、Copilot、Kimi 这类大模型)来辅助产出三样东西:
- 一份真实的
manifest.xml(manifest=一份配置文件,相当于加载项的"身份证+说明书",告诉 Excel 这个加载项叫什么、从哪里加载代码、能提供哪些功能); - 一个自定义函数的 JavaScript(JavaScript=网页和 Office JS 加载项都在用的编程语言,本文代码就用它写)代码框架;
- 一套排查问题的提示词(prompt)=你喂给 AI 的指令,越具体 AI 给得越准。
环境前提先说清楚(踩坑最多的就是这里):
- 装 Node.js(建议 18 以上);
- 装 Yeoman 生成器(Yeoman 生成器=一个脚手架工具,能一键生成加载项的标准文件夹和文件结构,命令包叫
generator-office); - 一台能登录的 Office。自定义函数依赖 CustomFunctionsRuntime 1.1 这个 requirement set,Office 365 订阅版、较新的 Office 2021 零售版、近期 Mac 版均可;Office 2019 与批量授权 / LTSC 版通常不支持。动手前先到 Excel 的 文件 → 账户 看版本号 / build 号,对照官方支持矩阵确认。
三、案例实战
分四步。每一步都给你"提示词 + 真实代码",照抄就能用。
第一步:用 AI + 脚手架把项目骨架搭起来
先在命令行装工具:
bashnpm install -g yo generator-office
然后让 AI 帮你确认选型。给 AI 的提示词:
"我要用 generator-office 创建一个 Excel 自定义函数加载项。请告诉我
yo office交互里应该选哪个模板(Excel Custom Functions 一类),并解释生成出来的 src 目录里 taskpane、functions、commands 各自是干嘛的。"
跑 yo office,在模板里选「Excel Custom Functions」一类(有 JavaScript 和 TypeScript(TypeScript=JavaScript 的严格加强版,多了类型检查,适合大项目)两种,任选)。生成后你会拿到 manifest.xml、src/functions/、src/taskpane/ 等标准文件。
注意:新版模板默认用 JSDoc
@customfunction自动注册(见进阶六)。下面为了把原理讲透,我们用更直观的"JSON 元数据 +CustomFunctions.associate"经典写法。两套选一套,别混用。
第二步:写一份真实的 manifest.xml
manifest 是加载项的"身份证"。下面是一份可直接用的经典写法(XML manifest,对应"显式 JSON 元数据"注册模型)。把 <Id> 换成你自己的 GUID(搜"GUID 生成器"随便生成一个),Namespace 改成你想要的前缀,本地调试地址默认是 https://localhost:3000。
xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xsi:type="TaskPaneApp"> <Id>626fad87-8dc1-4a6a-98f8-5864bf21eafa</Id> <Version>1.0.0.0</Version> <ProviderName>程晓教程示例</ProviderName> <DefaultLocale>zh-CN</DefaultLocale> <DisplayName DefaultValue="我的自定义函数示例" /> <Description DefaultValue="AI 辅助开发的一个 Office JS 自定义函数加载项。" /> <Requirements> <Sets> <Set Name="CustomFunctionsRuntime" MinVersion="1.1" /> </Sets> </Requirements> <Hosts> <Host Name="Workbook" /> </Hosts> <DefaultSettings> <SourceLocation DefaultValue="https://localhost:3000/taskpane.html" /> </DefaultSettings> <Permissions>ReadWriteDocument</Permissions> <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0"> <Hosts> <Host xsi:type="Workbook"> <AllFormFactors> <ExtensionPoint xsi:type="CustomFunctions"> <Script> <SourceLocation resid="Functions.Script.Url" /> </Script> <Page> <SourceLocation resid="Functions.Page.Url" /> </Page> <Metadata> <SourceLocation resid="Functions.Metadata.Url" /> </Metadata> <Namespace resid="Functions.Namespace" /> </ExtensionPoint> </AllFormFactors> <DesktopFormFactor> <FunctionFile resid="Commands.Url" /> <ExtensionPoint xsi:type="PrimaryCommandSurface"> <OfficeTab id="TabHome"> <Group id="CommandsGroup"> <Label resid="CommandsGroup.Label" /> <Control xsi:type="Button" id="TaskPaneButton"> <Label resid="TaskPaneButton.Label" /> <Supertip> <Title resid="TaskPaneButton.Label" /> <Description resid="TaskPaneButton.Tooltip" /> </Supertip> <Icon> <bt:Image size="16" resid="Icon.16x16" /> <bt:Image size="32" resid="Icon.32x32" /> <bt:Image size="80" resid="Icon.80x80" /> </Icon> <Action xsi:type="ShowTaskpane"> <TaskpaneId>TaskPaneId1</TaskpaneId> <SourceLocation resid="Taskpane.Url" /> </Action> </Control> </Group> </OfficeTab> </ExtensionPoint> </DesktopFormFactor> </Host> </Hosts> <Resources> <bt:Images> <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png" /> <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png" /> <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png" /> </bt:Images> <bt:Urls> <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html" /> <bt:Url id="Functions.Script.Url" DefaultValue="https://localhost:3000/functions.js" /> <bt:Url id="Functions.Page.Url" DefaultValue="https://localhost:3000/functions.html" /> <bt:Url id="Functions.Metadata.Url" DefaultValue="https://localhost:3000/functions.json" /> </bt:Urls> <bt:ShortStrings> <bt:String id="CommandsGroup.Label" DefaultValue="我的加载项" /> <bt:String id="TaskPaneButton.Label" DefaultValue="打开说明面板" /> <bt:String id="Functions.Namespace" DefaultValue="TUTORIAL" /> </bt:ShortStrings> <bt:LongStrings> <bt:String id="TaskPaneButton.Tooltip" DefaultValue="点开查看自定义函数用法" /> </bt:LongStrings> </Resources> </VersionOverrides> </OfficeApp>
关键点:
<Namespace resid="Functions.Namespace" />决定你在 Excel 里怎么调用——函数名会变成TUTORIAL.ADD这样"前缀.函数名"的格式。Namespace 元素本身没有Name属性,前缀来自<bt:String id="Functions.Namespace" DefaultValue="TUTORIAL" />这条 ShortStrings 资源——也就是"标签放资源里、Manifest 只引 resid"的官方模式。图标文件(icon-16.png 等)要真放在assets目录里,否则加载会报资源缺失。
拿去让 AI 帮你审的提示词:
"检查下面这份 manifest.xml:Namespace 是否和我的函数注册 ID 一致?Script/Metadata/Page 的 localhost 路径是否和我的 dev server 端口一致?Resources 里每个 resid 是否都有对应定义?列出要改的地方。"
第三步:写自定义函数(functions.json + functions.js)
经典"显式元数据"模型下,函数要分两个文件:一个描述"长啥样"(functions.json),一个写"怎么算"(functions.js)。
functions.json(函数的"说明书",Excel 靠它显示参数提示和类型):
json{ "functions": [ { "id": "ADD", "name": "ADD", "description": "把两个数字相加。", "parameters": [ { "name": "first", "description": "第一个数", "type": "number" }, { "name": "second", "description": "第二个数", "type": "number" } ], "result": { "type": "number", "dimensionality": "scalar" } } ] }
functions.js(真正的计算逻辑,用 CustomFunctions.associate 把"说明书写的 ADD"和"下面的 add 函数"绑在一起。注意:functions.json 里的 id 与 CustomFunctions.associate 的第一个参数必须完全一致,都是 "ADD",否则 Excel 端会报 #NAME?):
javascript/* global CustomFunctions */
// 把两个数相加
function add(first, second) {
return first + second;
}
// 注册:让 Excel 知道 ADD 这个自定义函数对应上面这个函数
// 注意:第一个参数 "ADD" 必须和 functions.json 里的 id 完全一致
CustomFunctions.associate("ADD", add);关键点 1:
functions.json里的id与CustomFunctions.associate("ADD", add)的第一个参数必须逐字符一致(大小写也算),否则 Excel 端报#NAME?。关键点 2:
Office.onReady(...)的写法(Office.onReady=Office 加载完成后执行回调的官方入口)属于侧边面板 / 按钮命令那一侧的代码,应该放在commands.js里,由 manifest 里<FunctionFile resid="Commands.Url" />指向;不要混进functions.js——后者只负责"被 Excel 拉去算单元格的纯函数",不持有 UI 生命周期。
关键点:很多新手卡在"Excel 里敲
=TUTORIAL.ADD没反应"。注意——自定义函数第一次要在单元格里真的输入并回车才会加载;光打开面板不会触发。另外,并没有Excel.customfunction这个对象,注册用的是CustomFunctions.associate(经典写法),或者进阶里说的 JSDoc@customfunction(新写法)。
给 AI 的提示词(生成你自己的函数):
"用 CustomFunctions.associate 写一个自定义函数,命名空间 TUTORIAL,函数名 DISCOUNT:输入一组数字和一个折扣率,返回折后总价。给出 functions.json 的元数据(含参数类型和说明)和 functions.js 的完整代码。"
第四步:跑起来 + 让 AI 帮你 debug
在命令行:
bashnpm install npm run build npm run start
npm run start 通常会自动起本地服务并把加载项塞进 Excel(侧载 / sideload)。如果它没自动打开 Excel,就手动:Excel 里「插入 → 加载项 → 我的加载项 → 上传我的加载项」,选你的 manifest.xml。
在任意单元格输入 =TUTORIAL.ADD(3,5),回车,应该显示 8。
卡住了?把报错贴给 AI:
"我在 Excel 里输入 =TUTORIAL.ADD(3,5) 返回 #NAME? 错误。manifest 里 Namespace 是 TUTORIAL,functions.json 的 id 是 ADD,functions.js 用了 CustomFunctions.associate('ADD', add)。请列出 5 个最可能的原因和对应排查步骤。"
四、原理小结
一句话讲清底层逻辑:加载项本质是"一个跑在浏览器里的网页 + 一份告诉 Excel 去哪找它的 manifest"。当你在单元格输入 =TUTORIAL.ADD(3,5),Excel 按 manifest 里的 Namespace 和 Metadata 找到 functions.js,执行你注册的 add 函数,把返回值填回单元格——整个过程走的是 Office JS 这套官方 API,所以不依赖 VBA、也不挑操作系统。
五、避坑指南
- 别用不支持的 Excel 版硬试:自定义函数依赖
CustomFunctionsRuntime 1.1requirement set。Office 365 订阅版、较新的 Office 2021 零售版、近期 Mac 版基本可用;Office 2019 与批量授权 / LTSC 版通常不支持。动手前先到 Excel 文件 → 账户 看版本号 / build 号,再回去对官方 requirement sets 支持矩阵。 - Namespace 和函数名对不上:Excel 里调用格式是"前缀.函数名",前缀来自 manifest 的
<Namespace>,函数名来自 functions.json 的id(或associate的第一个参数),三者要一致,否则报#NAME?。 - localhost 端口要统一:manifest 里写的
https://localhost:3000必须和你的 dev server 端口一致;换了端口没改 manifest 是最常见加载失败原因。 - 图标资源不能少:manifest 的
<bt:Image>指向的 png 要真实存在,缺失会在上传时报"资源无效"。 - 函数要先"调用"才加载:自定义函数不像普通插件打开就生效,得在单元格里真正输入并回车一次,它才会被加载进来;调试时多看浏览器控制台(F12),别只盯 Excel。
六、进阶延展
- 想写更复杂的计算(比如异步拉取网页数据再返回),去学
CustomFunctions.associate的流式返回与取消机制,或者改用新写法:在functions.ts里用 JSDoc 注释@customfunction,让构建工具自动生成元数据,省掉手维护 functions.json 这一步。 - 如果你的场景是"清洗一堆杂乱表格",其实不一定非要写代码——先回头看 B26 讲的 Power Query(Power Query=Excel 里专门做数据清洗/转换的内置工具,背后用 M 语言)、B21/B23 的 PivotTable(数据透视表)与 B22 的 DAX(DAX=数据透视表做计算用的公式语言,全称 Data Analysis Expressions)度量值,很多时候点几下就搞定,比上自定义函数更轻。
- 再往深走,可以研究 TypeScript 严格类型、共享运行时(Shared Runtime),把自定义函数和任务面板做成同一进程通信,那是 L4 的话题了。