如果你最近也在给公司的后台系统加一个“在线编辑 Excel / Word”的功能,大概率会碰到 Univer、Jspreadsheet、OnlyOffice 这三个名字。它们都号称能做 Web 表格,但实际用起来完全是三条路线。我花了差不多两周时间把这几个方案都跑了一遍,从最简单的嵌入式表格到完整私有化文档服务都过了一遍,踩了不少坑,也把每个方案适合的场景摸清楚了。
这文章不是官方文档复读,是我作为开发者的实战整理。Univer 适合想在 React/Vue 项目里深度定制表格交互的人,Jspreadsheet 适合只想快速嵌入一个不用太重依赖的表格控件的人,OnlyOffice 则适合需要把“一套完整 Office”部署到自己服务器、还要支持多人协同编辑的团队。我会把三个项目都快速介绍一遍,重点放在 OnlyOffice 的 Docker 安装、Java 后端集成、在线协同编辑和常见安装问题上,最后给你一张可以直接抄作业的选型表。
1. 先把三个项目看清:谁解决什么问题
1.1 Univer:可嵌入的现代办公组件
Univer 是近年起来的一个开源办公套件项目,定位不是“一个部署好的网站”,而是一套可以嵌进现有前端项目的组件库。它同时支持表格、文档、幻灯片三种类型,底层用 Canvas 自绘渲染,不是拿一个textarea模拟出来的假 Excel。这带来一个很实际的好处:你可以在不切页面的情况下,把表格做到自己的 Web 应用里,还能自己定制工具栏、右键菜单、数值格式、公式计算这些细节。
我第一次用 Univer 时,最大的感受是它“现代感”很强。React 技术栈、TypeScript 类型导出、插件化架构,几乎每个功能都是一个插件,按需加载。比如你要做数据录入页面,不需要加载幻灯片模块,只需要引入 Sheet 相关的 preset 就行。它的公式引擎是自带实现的,支持很多 Excel 常用函数,所以做轻量级财务计算、排班表、项目计划这类场景非常顺手。
还有一个容易被忽视的点:Univer 的协议是 Apache 2.0,社区版可以放心商用,只要保留版权声明。对于喜欢定制 UI、且团队有一定前端能力的人来说,Univer 是一个下限很低、上限很高的方案。但它的缺点是项目迭代非常快,API 变化也很频繁,我后面会专门说。
1.2 Jspreadsheet:轻量级在线表格控件
Jspreadsheet 和 Univer 是另一个方向。它是一个纯粹的 JavaScript 表格控件,没有厚重的插件体系,也没有文档和幻灯片,专注“表格”这一件事。它的加载方式很简单,一个 JS 文件加一个 CSS,然后在页面里初始化一个基于div的电子表格,就能快速做出可编辑、可排序、可筛选的数据表格。
Jspreadsheet 最吸引人的地方是轻。它的社区版运行起来几乎不挑浏览器,也不要求你用什么前端框架,原生 JS 就能跑,jQuery 早就不是必要条件了。它非常适合做“表单里的表格组件”,比如配置项列表、批量录入区、报表数据预览。你可以把服务端返回的 JSON 数组直接塞给它,也可以在用户改完每个格子后触发事件,自己把数据同步回后端。
但轻量也意味着边界要自己守住。Jspreadsheet 不是完整 Office 产品,它没有 Word、没有 PPT,公式能力和 Excel 兼容度比 Univer / OnlyOffice 弱一档。它的社区版开源协议是 GPL 类,如果你的产品需要完全闭源商用,最好先核实当前版本协议,或者买商业授权。这也是很多公司最后没有选它的原因。
1.3 OnlyOffice:开源、可私有化的完整 Office 套件
OnlyOffice 跟前面两个完全不是一个量级。它是一套完整的办公套件,包含文档、表格、演示文稿,还提供配套的文档服务端(Document Server)。你把 Document Server 部署好之后,浏览器里打开就是一个长得非常接近桌面 Office 的在线编辑器。更重要的是,它能做到多人同时编辑同一个文件,还能完整兼容.docx、.xlsx、.pptx这类 Microsoft Office 格式。
我理解 OnlyOffice 的定位是这样的:如果你需要给企业内部做一个“私有化在线 Office”,不想用各种云端协作平台,只想数据留在自己服务器里,OnlyOffice 是开源社区里成熟度最高的选择。它不是一个简单的编辑器组件,而是一个有服务端、有存储/缓存交互、有回调机制的产品级解决方案。
OnlyOffice 社区版对个人和小团队是友好的,官方 Docker 镜像一条命令就能启动,但在生产环境里需要规划好 JWT 签名、回调地址、文件下载地址、WebSocket 长连接等问题,否则会出现“文档打不开”“保存不回写”这类经典问题。这部分我会在第 3、4 节展开讲。
1.4 三选一之前,先看一张对比表
| 维度 | Univer | Jspreadsheet | OnlyOffice |
|---|---|---|---|
| 产品形态 | 前端组件库 | 前端表格控件 | 独立服务端 + 前端编辑器 |
| 能编辑什么 | 表格/文档/幻灯片 | 表格 | 文档/表格/演示文稿 |
| 部署成本 | 低,打包进前端项目 | 最低,引入 JS/CSS 即可 | 高,需部署独立服务 |
| 协同编辑 | 需按官方方案实现/依赖配套 | 基本不支持 | 原生支持多人协同 |
| 与 Office 格式兼容 | 中等 | 较弱 | 很高 |
| 定制能力 | 强,插件化 | 一般,靠事件/配置 | 一般,前端代码不像开源组件那样随意改 |
| 引用场景 | 要做成产品内的“编辑模块” | 快速加个表格面板 | 企业私有化在线办公 |
这个表格不是绝对的,比如 Univer 在文档和幻灯片方面还在快速迭代,Jspreadsheet 的 Pro 版功能会更丰富,但我认为选型的第一逻辑是:你到底要一个“表格控件”,还是要一套“文档系统”?前者在 Jspreadsheet 和 Univer 里选,后者基本就是 OnlyOffice。
2. 快速跑通两个前端表格方案
2.1 Univer 十分钟接入你的 React 项目
Univer 官方文档一直强调“preset”概念,我建议新手先不要手动拼一堆插件,直接用现成的预设包。假设你已经有一个 Vite + React 项目,先安装依赖:
npm install @univerjs/preset-sheets @univerjs/preset-sheets-ui @univerjs/core @univerjs/design不同版本导入路径可能有差异,以你安装的版本为准。初始化一个简单的表格:
import { LocaleType } from '@univerjs/core'; import { UniverSheetsPreset } from '@univerjs/preset-sheets'; import { UniverSheetsUIPreset } from '@univerjs/preset-sheets-ui'; import { createRoot } from 'react-dom/client'; const univer = new Univer({ locale: LocaleType.ZH_CN }); univer.registerPlugin(UniverSheetsPreset, { container: 'app', // 这里可以配置默认工作表结构 sheets: [ { name: 'Sheet1', rowCount: 100, columnCount: 20, cellData: { 0: { 0: { v: '项目名称' }, 1: { v: '负责人' }, 2: { v: '进度' }, }, 1: { 0: { v: '在线表格预研' }, 1: { v: '张三' }, 2: { v: '80%' }, }, }, }, ], });把这段代码放进 React 的useEffect里,或者直接放在组件挂载后执行,页面上就会渲染出一个可以编辑的表格。别被“注册插件”这个词吓到,实际体验就是配置一个对象。核心思路是:Univer 负责渲染和计算,你负责往cellData里喂数据。
Univer 里读取表格数据也类似,通过univer.getCurrentUniverSheet()等 API 获取当前实例,再调用getSheetData()之类的方法。这里的 API 名在版本迭代中改过几次,所以我不写死,你照着官方示例跑一遍即可。新手最容易遇到的问题是“渲染出来了但公式不算”,这时候先检查公式单元格是否设置了f属性,比如{ v: 1, f: '=SUM(A1:A5)' },有些版本需要显式配置。
2.2 Jspreadsheet 五步嵌入现有页面
Jspreadsheet 的上手路径比 Univer 短得多。先引样式和脚本,我用的是社区版 CDN:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.min.css" /> <script src="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.min.js"></script>然后在页面里放一个容器:
<div id="spreadsheet"></div>初始化:
jspreadsheet(document.getElementById('spreadsheet'), { data: [ ['产品A', '已上线', '2024-03-01'], ['产品B', '开发中', '2024-05-20'], ], columns: [ { title: '产品名称', width: 160 }, { title: '状态', width: 120, type: 'dropdown', source: ['已上线', '开发中'] }, { title: '计划时间', width: 140, type: 'calendar' }, ], onchange: function (el, cell, value, row, col) { console.log('单元格变更:', row, col, value); }, });如果你想用 JSON 数据填充,只需要把data换成对象数组,再在columns里配置name字段即可。比如后端返回[{name: '产品A', status: '已上线'}],初始化时可以用data: data.map(row => [row.name, row.status]),或者直接使用官方支持的对象数据映射模式。
Jspreadsheet 的license配置项在社区版 4.x 里需要注意,有些版本不填会弹出版权提示,你需要去官网申请一个免费的社区版密钥。这一步经常被人忽略,实际初始化时看到提示会很懵。
2.3 这两个方案最容易踩的坑
先说 Univer。它最大的坑是版本碎片化。0.x 到 1.x 之间,API 和插件注册方式都有变化,你搜索到的很多教程是旧版写法,直接复制过来在最新版上跑不通。我的建议是:打开官方文档的“Playground”,先用它提供的模板跑通,再往你的项目里迁移,不要自己从零拼 API。
其次是“样式和主题容易失控”。Univer 默认样式已经不错,但企业接入时往往想调整主色、字体、行高,这需要了解它的设计令牌(Design Token)。如果你只是临时集成,建议先用默认主题,不要一上来就套自定义 CSS,否则会花大量时间在样式微调上。
Jspreadsheet 的问题更多集中在边界能力上。它面对超大表格时,性能不如 Canvas 渲染方案,一次塞几万行数据会卡。另外中文输入法下,有些老版本单元格在拼音组词过程中会丢失焦点,实测在 Chrome 和微软拼音里偶尔会出现,需要升级到最新版并测试。如果你们产品里用户大量使用中文输入,一定要在真机上用中文输入法完整打一遍字,再决定是否采用。
3. OnlyOffice 安装部署:从 Docker 到避开常见安装问题
3.1 一条 Docker 命令跑起 OnlyOffice
OnlyOffice 的官方 Docker 镜像把整套 Document Server 打包好了,最简安装其实就一条命令:
sudo docker run -i -t -d \ --name onlyoffice_document_server \ -p 8080:80 \ -e JWT_ENABLED=true \ -e JWT_SECRET=your-strong-secret-key \ --restart=always \ onlyoffice/documentserver启动后访问http://你的服务器IP:8080/welcome,能看到 OnlyOffice 欢迎页说明服务已经起来了。这里的端口映射我用了8080:80,把宿主机的 8080 映射到容器内的 80。官方默认文档里给的是-p 80:80,但实际部署时 80 端口经常被 Nginx、Apache 占用,换成 8080 能减少冲突,后面用 Nginx 反代到对外域名即可。
JWT_SECRET是这段命令里最重要的东西。从 OnlyOffice 7.2 开始,JWT 默认启用,后端集成时必须跟这个密钥保持一致,否则在编辑器里打开文档会报签名错误。可以用openssl rand -hex 32生成一个随机密钥,不要用123456这种弱口令。
3.2 安装阶段最容易翻车的三个细节
第一个坑:容器一直重启,日志里报内存不足。OnlyOffice 的 Docker 容器至少需要 2GB 内存,我刚开始在一台 1GB 的小机器上测试,启动几分钟后进程就没了。解决办法是先确保宿主机内存充足,也可以用 Docker 限制容器最小内存,有时需要清理残留缓存。如果你是在本地虚拟机里试,建议直接分 4GB 内存给虚拟机。
第二个坑:端口映射改了之后,编辑器加载不出来。有些教程直接用http://localhost:8080访问,但在集成到前端页面时,OnlyOffice 内部的加载地址可能还会引用容器里的服务地址。如果出现编辑器一直在加载、控制台报跨域或文件找不到,优先检查是不是后端生成document.url和编辑器 API 地址不一致。生产环境强烈建议用域名 + HTTPS,而不是直接暴露 IP 端口。
第三个坑:镜像下载很慢,或者卡在某个中间层。OnlyOffice 官方镜像体积比较大,几百 MB 到 1GB 都有,受网络环境影响明显。如果你网络不稳定,可以在 Docker 配置里换一个国内镜像加速器,或者在半夜网络好的时候先手动拉一遍。拉取过程中不要中断,中断后重新拉取可能产生残层,导致容器启动后行为诡异。
3.3 用 docker-compose 直接落地一套可维护环境
生产环境我习惯用 docker-compose 来管理,而不是裸docker run。下面是一个社区版可用的最小 compose 文件:
version: "3" services: onlyoffice: image: onlyoffice/documentserver:latest container_name: onlyoffice_document_server restart: always ports: - "127.0.0.1:8080:80" environment: - JWT_ENABLED=true - JWT_SECRET=${JWT_SECRET} volumes: - ./data/logs:/var/log/onlyoffice - ./data/data:/var/www/onlyoffice/Data - ./data/lib:/var/lib/onlyoffice - ./data/db:/var/lib/postgresql这里我把端口绑定限制在127.0.0.1:8080,意味着宿主机外部不能直接访问 8080,只能通过 Nginx 等反向代理访问。这个做法可以避免文件服务端口裸奔,也算是一层安全防护。后面用 Nginx 在 443 端口做 TLS 终结:
server { listen 443 ssl; server_name doc.example.com; ssl_certificate /etc/nginx/ssl/doc.example.com.pem; ssl_certificate_key /etc/nginx/ssl/doc.example.com.key; client_max_body_size 100m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }Nginx 配置里最后两行升级 WebSocket 是 OnlyOffice 协同编辑的关键,如果没有这两行,多人同时打开文档时会出现连接不断中断、光标不同步的现象。很多 OnlyOffice 在线协同出问题,根源不是 OnlyOffice 本身,而是反向代理把 WebSocket 掐断了。
4. OnlyOffice Java 开发集成与在线协同编辑
4.1 在线协同编辑,到底是怎么协同的
OnlyOffice 的协同编辑模型可以这样理解:你有一个自研系统负责文件存储和业务权限,OnlyOffice Document Server 负责真正的编辑渲染和多人同步。当用户打开一个文档时,浏览器通过 iframe 加载 OnlyOffice 编辑器,编辑器再去 Document Server 拉取文件内容。Document Server 会为每个文档维护一个实时状态,多人之间的光标、修改、保存都由它来处理。
这个模型的好处是,你的系统不需要实现复杂的 CRDT 或 OT 算法,只需要在文件保存时接住 Document Server 的回调,把最新内容写回你自己的存储里。听起来很简单,但实际集成的难点在于:Document Server 需要能访问到你系统提供的文件下载地址,你的系统也需要能访问到 Document Server 下载新文件的地址。如果两边都在内网,就需要提前规划好网络互通;如果有公网域名,则要保证回调地址不是localhost。
4.2 后端生成签名配置文件(Java 示例)
如果你是 Java 后端,集成 OnlyOffice 的基本流程是:先建一个接口,根据文件 ID 查询文件真实路径,构造 OnlyOffice 编辑器配置对象,然后用 JWT 签名,最后返回给前端。前端拿到这个配置初始化编辑器。
先引入 JJWT 依赖(也可以换成你项目里已有的 JWT 库):
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>然后写一个生成配置的 Service:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.HashMap; import java.util.Map; public class OnlyOfficeConfigBuilder { private final String jwtSecret; private final String documentServerUrl; public OnlyOfficeConfigBuilder(String jwtSecret, String documentServerUrl) { this.jwtSecret = jwtSecret; this.documentServerUrl = documentServerUrl; } public Map<String, Object> buildConfig(String fileId, String fileName, String fileType, String downloadUrl, String callbackUrl, String userName) { Map<String, Object> document = new HashMap<>(); document.put("fileType", fileType); // xlsx, docx, pptx document.put("key", fileId + "-v1"); // 每次内容变化最好更新 key document.put("title", fileName); document.put("url", downloadUrl); // OnlyOffice 能访问到的下载地址 document.put("permissions", Map.of( "edit", true, "download", true, "print", true )); Map<String, Object> editorConfig = new HashMap<>(); editorConfig.put("mode", "edit"); editorConfig.put("lang", "zh-CN"); // 多语言 editorConfig.put("callbackUrl", callbackUrl); editorConfig.put("user", Map.of( "id", "user-001", "name", userName )); // 可选:自定义展示 Map<String, Object> customization = new HashMap<>(); customization.put("autosave", true); customization.put("chat", false); editorConfig.put("customization", customization); Map<String, Object> config = new HashMap<>(); config.put("document", document); config.put("editorConfig", editorConfig); // 生成 JWT Key key = new SecretKeySpec(jwtSecret.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.HS256.getJcaName()); String token = Jwts.builder() .setClaims(config) .signWith(key, SignatureAlgorithm.HS256) .compact(); config.put("token", token); return config; } }这里有两个关键设计。第一是key,它不是文件 ID 本身,而是文档内容的版本标识。OnlyOffice 用这个 key 判断文档是否变化,如果你每次都固定成同一个 key,多用户打开时可能看到旧缓存;如果每次编辑都变,又会导致协作者被踢。比较合理的做法是“文件 ID + 版本号”,保存成功后再更新版本号。
第二是downloadUrl,这个 URL 必须能让 OnlyOffice 服务器直接访问到,不能像浏览器里那样用localhost。如果你在 Docker 容器里跑后端,容器名或者内网 IP 反而比localhost可靠。
4.3 前端嵌入 OnlyOffice 编辑器
前端部分反而简单。页面里放一个占位div,引入 Document Server 提供的api.js,请求后端接口拿到配置,然后初始化:
<div id="office-editor" style="width: 100%; height: 100vh;"></div> <script src="https://doc.example.com/web-apps/apps/api/documents/api.js"></script> <script> fetch('/api/onlyoffice/config?fileId=123') .then((res) => res.json()) .then((config) => { const docEditor = new DocsAPI.DocEditor('office-editor', config); // 监听文档加载完成 docEditor.on('onDocumentReady', () => { console.log('文档已就绪'); }); // 监听保存状态 docEditor.on('onRequestSave', () => { console.log('用户手动触发了保存'); }); }); </script>前端这一步踩坑主要集中在跨域和 HTTPS。如果 Document Server 用了域名和证书,而你的业务系统是http://localhost,浏览器会因为混合内容或 iframe 跨域限制导致编辑器加载不出来。我建议本地联调时,用一个临时 HTTPS 证书给业务系统也套上一层,或者用同一个域名下的不同路径反向代理。
关于多语言,editorConfig.lang在 OnlyOffice 里支持很多值,比如zh-CN、en、ru、ja、de等。这个配置直接影响编辑器界面语言、右键菜单语言和部分多语言文本。如果你要面向国际用户,可以在后端根据用户偏好动态传入,不需要写死。
4.4 保存回调与协同状态处理
OnlyOffice 会在文档保存时向前端页面里配置的callbackUrl发送一个 POST 请求。你的后端需要实现这个回调接口,否则用户编辑完发现数据没有落库,很致命。回调请求的body里包含status字段,常用取值如下:
| status | 含义 | 处理建议 |
|---|---|---|
| 1 | 正在编辑,仅通知 | 记录在线用户数或心跳,不落库 |
| 2 | 需要保存 | 从 body 里的url下载最新文件,覆盖存储 |
| 3 | 保存出错 | 记录日志并告警 |
| 4 | 关闭,无变更 | 不需要处理 |
| 6 | 正在保存 | 可做轻量日志,不要重复下载 |
| 7 | 强制保存 | 和 status=2 一样处理 |
一个标准的回调接口长这样:
@PostMapping("/api/onlyoffice/callback") public ResponseEntity<Map<String, Object>> callback(@RequestParam("fileId") String fileId, @RequestBody Map<String, Object> body) { Integer status = (Integer) body.get("status"); if (status != null && (status == 2 || status == 6 || status == 7)) { String downloadUrl = (String) body.get("url"); byte[] fileContent = restTemplate.getForObject(downloadUrl, byte[].class); if (fileContent != null) { fileService.saveFile(fileId, fileContent); } } Map<String, Object> response = new HashMap<>(); response.put("error", 0); return ResponseEntity.ok(response); }注意:OnlyOffice 要求回调接口最终返回的 JSON 里必须包含"error": 0,否则它会认为保存失败并反复重试。我见过有人在这里顺手把业务数据也返回了,结果 OnlyOffice 一直重试,把日志刷爆了。
协同编辑是否生效,除了回调之外,还要看editorConfig.mode是不是edit。如果文档被其他人以只读方式打开,permissions.edit会被覆盖,新打开的用户看到的工具栏就没有保存按钮。另外社区版对同时连接人数有限制,一般是 20 个连接左右,真实企业场景如果超过这个规模,要么拆分文档服务,要么升级商业版本。
5. 常见问题速查与场景选型建议
5.1 高频问题解决速查表
整理一下这段时间踩到的高频问题,方便你排错:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| OnlyOffice 容器反复重启 | 宿主机内存不足 | 确保至少 2GB 可用内存,用free -h确认 |
| 打开文档提示“文档下载失败” | document.url指向了前端访问不到的地址 | 把 URL 改成 OnlyOffice 服务器能访问到的内网地址 |
| JWT 签名报错,编辑器无法加载 | JWT_SECRET 不一致或算法不是 HS256 | 后端和 Document Server 设置为同一个 secret |
| 多人打开文档,光标不同步 | 反向代理没配置 WebSocket Upgrade | 在 Nginx location 里加proxy_set_header Upgrade $http_upgrade |
| 编辑器一直打转,控制台报跨域 | 业务系统和 Office 域名不同 | 统一用域名 + HTTPS,或使用同域名反代 |
| 文档能打开,但保存不生效 | 回调接口没返回{"error":0} | 检查回调 Controller 返回格式 |
| Univer 打开大数据量表格卡顿 | 一次性渲染了太多行/列 | 分页加载数据,或先评估是否真的需要一次展示十万行 |
| Jspreadsheet 中文输入丢字 | 老版本 IME 兼容问题 | 升级到最新社区版,并在中文浏览器实测 |
5.2 什么场景选什么方案(含多语言和“好不好用”)
如果你现在还在犹豫“OnlyOffice 到底好不好用”,我的看法是:对比的基准很重要。如果你拿它跟一个普通的 Web 表格控件比,会觉得它太重、部署麻烦、回调逻辑复杂;但如果你拿它跟“自研一个在线 Word”比,OnlyOffice 就是省了几百人日的最优解。它好不好用,取决于你愿不愿意按它的规则来:容器部署、JWT 签名、回调地址,这三点理顺之后,后面就很顺。
选型最后再给一次建议:
- 如果你只是要在后台管理页面里嵌入一个“类 Excel”的录入表格,用户不需要导入导出
.xlsx,也不在乎公式有多全,用Jspreadsheet就够了,半天接入。 - 如果你的产品是面向用户的一套系统,表格是你这个产品的重要模块,需要自定义 UI、权限、数据处理,而且团队前端比较强,优先考虑Univer。
- 如果你要做企业内部文档协作,或者说你的系统里需要在线预览和编辑
.docx、.xlsx这些真实 Office 文件,那直接OnlyOffice,别考虑用前两者硬撑。
在实际操作中,我自己最终落地方案是“OnlyOffice 出正式文件编辑,Jspreadsheet 做轻量数据录入”,两者不冲突,完全可以同时存在。我给 OnlyOffice 配了中英文两种lang,前端根据用户语言动态传参;Jspreadsheet 那边则固定中文,因为只服务内部运营。
最后分享一个小经验:无论选哪个方案,第一天上手时不要急着加权限、加自定义功能,先把最小的 Demo 跑通。OnlyOffice 就先用官方 Docker 镜像启动,再用一个简单页面把编辑器打开,把保存回调打通。基础链路稳定之后,再叠加登录、权限、多语言、版本管理这些外围功能。这个顺序反过来的话,你会在定位问题时非常痛苦。