ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Univer 选型与实战:从 Canvas 渲染到在线表格落地

Univer 选型与实战:从 Canvas 渲染到在线表格落地 1. 为什么我最后的选型结果锁定了 Univer做管理后台这几年我发现自己最头疼的从来不是权限、不是流程图而是表格。尤其是那种“用户在系统里要像操作 Excel 一样操作数据”的需求要拖拽行列、要合并单元格、要写公式、要搞条件格式数据量还动辄五六万行。最开始我们用el-table硬扛扛到几千行就开始卡上万行直接白屏更别说在浏览器里模拟 Excel 的编辑体验了。后来团队要做的是一个在线数据填报与审核模块产品经理明确说了“我不要一套表单我要一个网页里的 Excel。”那段时间我几乎把能叫得上名的前端表格方案都摸了一遍其中最让我花心思的就是 univer——准确说是开源项目 Univer。它和我之前用过的各种“伪 Excel 表格组件”都不太一样底层用 Canvas 做渲染编辑、公式、协同做得像模像样而且是以插件化架构组织的可以说正是冲着“替代前端 Excel 体验”这个方向去的。这篇文章我不想重复官方文档而是想把自己从选型、接入、二次开发到生产落地这几个月攒下来的经验和坑一次性写清楚。如果你正在做类似的在线表格需求或者是被大数据量表格折磨的后台前端我相信你会需要这份笔记。1.1 项目背景后台表格模块带来的三个噩梦先说我们当时的具体场景。系统里有个“渠道价格维护”模块业务方需要同时维护不同区域、不同渠道、不同时间段的数千条报价记录。记录本身倒不算特别复杂但有几个硬要求第一要支持直接粘贴从 Excel 复制过来的数据第二某些价格字段要能够通过公式自动计算比如“含税价 不含税价 * (1 税率)”第三财务审核的时候希望价格低于阈值的单元格自动标红。听起来不算难但真正做起来就发现问题了。第一个噩梦是渲染性能。几万条数据如果用 DOM 表格渲染光创建节点就是几万个用户滚动一下就掉帧更别说同时打开四五个 Sheet 页签。第二个噩梦是公式计算。我们不能简单地把 Excel 文件上传后在服务端解析完就丢给前端展示因为用户改了单元格之后依赖这个单元格的公式必须在前端实时联动这是一个完整的计算引擎问题。第三个噩梦是协同编辑。多个运营同时维护一份报价表时后保存的人会把先保存的人的数据整个覆盖掉。这些问题不是普通表格组件能解决的我们需要的是“电子表格引擎”而不是“展示表格的组件”。也是在这次调研过程里Univer 第一次进入我的视野。1.2 先花三分钟讲清楚 Univer 是什么Univer 是一个基于 TypeScript 的开源电子表格基础套件。它不只是一个“画表格”的 UI 组件而是把表格从数据、渲染到交互完整拆成了一套插件化体系。你可以把它理解成前端里的“Figma 思路”界面是 Canvas 画出来的而不是一个个 DOM 节点拼出来的。这样带来的最直接好处就是在渲染几万行数据时浏览器不会因为节点爆炸而崩溃。它内部包含了几个核心模块univerjs/core负责数据结构和工作簿模型univerjs/sheets负责电子表格业务逻辑univerjs/sheets-ui负责交互和界面渲染univerjs/sheets-formula负责公式解析与计算。同时它还提供了协同编辑能力。多个用户打开同一个工作簿大家看到的是相同的数据任意一方修改后其他人能实时看到变化。这一点对很多内部系统来说是刚需而 Univer 默认就走通了这个链路。我的看法是Univer 的目标不是做一个“像 Excel 的前端表格”而是做“一套可嵌入 Web 应用的 Office 基础能力框架”。它起步比较快GitHub 上星标涨得也猛社区讨论活跃这正是我敢在生产项目里押注它的原因。1.3 选型时我为什么淘汰了 Luckysheet 和 SheetJS既然要在社区方案里选型我肯定绕不开几个老牌选手。这里把它们的优缺点说清楚也算给大家一个参照。先说你可能听过的 Luckysheet。它确实是一套功能很全的在线表格方案界面也接近 Excel公式、条件格式、筛选都能用。但问题是它的维护节奏比较慢我翻 issue 的时候看到不少需求挂了大半年没有动静。我担心的是我们团队没有精力去深度改一个活跃度不高的轮子万一遇到 bug 可能要自己啃很底层的代码。再说 SheetJS。它的强项是解析和生成 Excel 文件各种复杂的 xlsx 特性支持得很好社区用户极多。但它不是一个 UI 组件它不管渲染和交互你要自己画界面或者和其他表格组件配合使用。单独拿它满足“网页里操作 Excel”这个需求等于什么都得自己盖工作量大。还有一个容易混淆的选项是 Handsontable。它定位是数据网格不是电子表格引擎虽然好用但要实现公式、条件格式等 Excel 级能力很多依赖商业版插件。我在对比表里做了个简单的总结方案渲染方式公式引擎协同编辑维护活跃度最终评价LuckysheetDOM Canvas支持部分能力偏低功能全但维护慢SheetJS无 UI无无高适合做解析库HandsontableDOM商业插件商业插件高数据网格定位UniverCanvas内置内置高最接近目标Univer 当时最打动我的点倒不是每个单项都做到第一而是它把渲染、公式、协同这三件最麻烦的事都做进了一套体系里。我不用自己去拼“渲染用 A 方案、解析用 B 方案、协同用 C 方案”这种积木。另外它的插件化设计也让我比较放心万一某个模块不满足需求至少能在架构层面做扩展而不是推倒重来。2. 从零接入Univer 的最小工程示例选定方案后第一步自然是把项目跑起来。但这里我要先说一句Univer 的版本迭代很快API 调整也比较频繁。我接入的时候还是 0.1.x 初期现在已经出到更高版本所以下面这段代码主要是展示“接入思路和核心对象是怎么用的”具体 API 请大家以官方示例为准。这句话不是敷衍是因为我踩过太多“对着旧文档写新代码”的坑了。2.1 安装依赖与初始化如果按照我最早接的版本核心依赖是这样安装的npm install univerjs/core univerjs/sheets univerjs/sheets-ui univerjs/sheets-formula现在官方主推的写法可能已经变成用univerjs/presets做预设初始化会更省事。但不管哪种方式本质都是三步创建 Univer 实例注册插件创建工作簿。我当时写的初始化代码大致长这样import { Univer, UniverInstanceType } from univerjs/core; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsUIPlugin } from univerjs/sheets-ui; import { UniverSheetsFormulaPlugin } from univerjs/sheets-formula; const univer new Univer(); univer.registerPlugin(UniverSheetsUIPlugin, { container: app, locale: zhCN, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsFormulaPlugin); const workbook univer.createUnit(UniverInstanceType.UNIVER_SHEET, { sheetOrder: [sheet1], sheets: { sheet1: { rowCount: 100, columnCount: 20, cellData: { 0: { 0: { v: 项目, s: { b: true } }, 1: { v: 金额 } }, 1: { 0: { v: 服务费 }, 1: { v: 1000 } }, }, }, }, });这里有个很容易踩的坑container指向的 DOM 元素必须要有明确的宽度和高度。我一度遇到页面里表格区域灰蒙蒙一片查了半天才发现是容器高度为 0 导致的。Univer 不会像普通组件那样自动撑开父容器它只会根据传入容器尺寸做 Canvas 布局。另外注册插件顺序不一定有硬性要求但公式插件一定要在创建具体的工作簿之前注册否则后续对公式的操作会出现识别不到函数的情况。这个我在第五部分还会提到。2.2 理解 Univer 的核心对象接入一个复杂开源项目最忌讳的是“照着 demo 抄完就跑了”。你得把几个核心对象理解透后面改东西才知道往哪儿下手。Univer 的顶层对象是Univer它就像应用的主入口。通过registerPlugin可以给运行环境装上各种能力比如 UI 插件负责渲染工具栏和单元格公式插件负责公式计算。插件系统的好处是你不需要的模块可以不装产物体积能小一点逻辑也更干净。接着是createUnit这是创建文档实例的入口。一个Univer实例可以同时管理多个工作簿比如系统里同时打开了两张报表它们就各自是一个 unit。这样做的好处是切换文档时可以复用已注册的能力数据隔离却不受影响。再往下是工作簿的数据结构。Univer 不像某些表格库那样用二维数组直接表示数据而是用类似 JSON Schema 的方式描述整个工作簿。核心字段包括sheetOrder页签的顺序sheets所有工作表的数据集合key 是 sheet idrowCount/columnCount工作表的总行数和总列数cellData单元格数据用类似 Excel 引用的方式表示坐标mergeData合并单元格的区间集合rowData/columnData行高、列宽的覆盖配置。它把这个结构叫“工作簿快照snapshot”。这个设计的好处是整个文档可以序列化也就是说你可以把这份数据存到后端下次打开时直接恢复现场。我第一次理解到这个设计的时候挺惊喜的因为这意味着 Univer 的文档本质上是“数据驱动”的界面只是一个表现层。2.3 加载和导出 Excel 文件真实业务里不能总靠代码手写单元格更多是让用户上传一个现成的 xlsx 文件或者把当前编辑结果下载下来。这一块 Univer 也是通过插件处理的。加载的流程大概是拿到File对象后交给导入插件做解析解析完成后通过loadData把快照灌进 Univer 实例。导出则反过来调用导出插件从当前工作簿快照生成 xlsx 二进制数据。我当时写得非常顺的一句话是“导入导出看起来简单实际上坑全藏在细节里”。比如文件解析是在主线程做还是放到 Web Worker 做这直接决定了用户上传大文件时页面会不会卡死。Univer 某些版本已经支持在 Worker 里做解析但需要额外配置。如果你要处理的 xlsx 经常超过十兆我强烈建议把解析拆到 Worker 里去。还有一点导入导出插件对外部依赖的处理比较敏感尤其是构建时容易出现Cannot find module之类的错误。这时候不要急着搜报错先检查插件包的版本号是否和 core 包保持一致。Univer 的多包版本是强绑定的版本错位会导致很多莫名其妙的问题。3. 二次开发中真正要掌握的能力把 Univer 跑通只是第一步。真正让它在业务里发挥作用绕不开几个二次开发能力。我下面挑了四个最常被用到、也是我实际做过的场景。3.1 公式计算与自定义函数Univer 自带公式引擎这使得SUM(A1:A10)、VLOOKUP(...)这类公式能够在浏览器里实时计算。这也是我选它的核心原因之一因为多数后台系统的价格计算、汇总统计都依赖公式联动。但如果只是用内置公式产品经理很快会提出一个需求“我们要一个把金额乘以税率再取整的公式Excel 里面没有。”这时候就要注册自定义函数。我在项目里写过一个带税率计算的函数核心思路是给公式插件注册一个自定义方法const formulaPlugin univer.getPlugin(UniverSheetsFormulaPlugin); formulaPlugin.registerFunction(TAX, (value, rate) { return Math.round(value * rate * 100) / 100; });注册之后单元格里就可以写TAX(B2, 0.08)这样的公式。关键是它和内置公式一样当依赖单元格B2发生变化时结果会自动刷新。这一点让公式模块不再是展示而是真正参与业务计算。处理公式时需要注意一个很容易被忽略的细节公式返回值类型要保持稳定。Univer 对数字和字符串类型是有严格区分的如果你返回的数字是字符串格式后续参与加减乘除的时候可能计算不对甚至出现NaN。我在自定义函数里吃过这个亏后来所有返回值都明确用 Number 包一层。3.2 条件格式从配置到代码条件格式是财务场景里的高频功能。当时的需求是把“价格小于成本价”的单元格标红我一开始想让用户自己在工具栏里配但业务方觉得太麻烦要求系统默认就带上规则所以我研究了一下怎么通过代码给指定区域设置规则。伪代码思路大致是这样const sheet workbook.getActiveSheet(); sheet.addConditionalRule({ type: cellIs, operator: lessThan, formulas: [10], style: { fill: #ff5f56, color: #ffffff, }, range: B2:B100, });这段代码本身不复杂但背后有一个关键点条件格式规则最好能被序列化保存。因为用户下次打开这个文档时规则不应该丢失。Univer 的快照结构里是支持携带这套格式规则数据的所以我们在保存文档时会把整个快照直接存到后端而不是单独存每个格式配置。另外我建议规则里的阈值参数不要写死在代码里而是通过后端配置读取。因为业务方几乎一定会改如果你把 10 写死他们下个月说阈值要变成 15你还得发一版前端。把规则参数做成可配置能给自己省掉很多不必要的提测。3.3 协同编辑与权限协同是 Univer 的一大卖点但我必须说清楚Univer 提供的是“协同编辑的底层能力”而不是一套完整的业务系统。也就是说多端同步已经打通但“谁能改”“改的时候能不能撤销”这些权限策略需要你自己接入。我们在项目里做了两个层面的控制。第一层是文档级权限如果用户角色是“只读”我们在初始化时直接把工作簿设置成不可编辑状态。第二层是区域级权限某些敏感列不允许普通运营修改这个通过监听单元格变更事件来拦截如果发现修改发生在受保护区域内就主动回滚并提示权限不足。协同还有个现实问题Univer 默认的协同方案依赖于一个服务端做消息同步。如果你的系统只部署在内网多用户同时访问也没问题但要注意网络抖动带来的“短暂不一致”。按照我们实测观察大部分冲突最终能被合入但极端情况下仍可能出现某次操作被覆盖。所以在产品层面我会建议给重要文档做一个“操作审计日志”哪怕协同出问题至少能回溯是谁在什么时间改了哪个格。3.4 自定义右键菜单和单元格渲染表格类产品难逃一个宿命客户想要“专属按钮”“专属徽标”这种定制玩意儿。Univer 在这方面的可扩展性还是比较好的不过深入程度不同成本也不同。简单的场景是在右键菜单里加一个入口比如“申请审批”。这种可以通过 UI 插件的菜单 API 注册自己写一个菜单项点击之后触发业务逻辑难度不大。比较复杂的场景是自定义单元格渲染。如果你期望单元格内部不再只是文本而是画一个进度条、放一个状态灯这就需要在渲染层做文章。Univer 底层有一套自己的渲染器允许你在单元格上注册自定义渲染器但这个体系的学习曲线比普通组件 API 陡不少。我的建议是先想清楚是否非做不可。如果只是想要好看的视觉状态可以先通过条件格式做背景色变化或者用富文本样式搭配符号比如对勾、叹号来呈现。只有当你确实要在单元格里与用户交互、画图形时才值得去啃渲染层的文档。功能越炫维护成本越高这个取舍要提前跟产品对齐。4. 生产环境落地要过的那些关跑通 demo 和上线生产完全是两码事。很多项目在 Demo 阶段看起来很美好一接到“要兼容 IE”“要首屏秒开”“要支持几万行数据”这些要求时问题就暴露出来了。下面这几关是我实打实过过的。4.1 大数据量渲染的优化思路选 Univer 的一个重要原因就是它用 Canvas 渲染。和 DOM 表格相比Canvas 天然没有“节点数”的概念几万行数据也只是画在一张画布上。所以单纯看滚动流畅度Univer 确实要比传统的表格组件表现好得多。但这不代表你可以无脑往里面塞数据。几万行虽然画得动但用户编辑、选中、框选时的计算量还是跟数据规模成正比的。我们实际验证下来5 万行、20 列的表格打开后首屏渲染大概在 1 秒出头操作的手感还算能接受。但如果把行数拉到 50 万行即便渲染不崩交互也会变肉。如果你要支撑几十万行的数据我建议做两层优化。第一层是数据层后端做分页加载或者按需加载不要试图一次性把全量数据灌进前端。很多业务场景根本不需要用户一次看到全表给一个筛选条件让用户查出来一部分再展示压力会小很多。第二层是展示层合理设置行高、列宽和默认样式避免给大量单元格逐个设置复杂样式。样式覆盖越多渲染开销越大这跟浏览器渲染 DOM 的道理是一样的。4.2 主题定制与国际化放弃默认文案Univer 默认的界面是英文的而且界面组件不少包括工具栏、右键菜单、公式栏等。我们内网系统不可能让用户看英文所以第一件事就是把它切换成中文。这方面官方已经提供了多语言包基本上设置locale: zhCN就能把主界面换成中文。但要提醒一句语言包覆盖不了所有文本比如某些公式的错误提示、某些插件的默认配置还是可能以英文展示。如果产品方对语言统一性要求高这些偏角落的文本就得自己做文案映射。主题定制方面Univer 的样式体系是基于设计令牌的你可以通过配置品牌色、背景色等变量来统一系统观感。我不建议直接在样式表里覆盖它的类名因为构建后类名会被压缩成 hash今天能覆盖明天换个版本就失效了。用官方主题配置才是正路虽然学习成本略高但至少不会因为升级版本而出问题。4.3 打包体积和按需加载Univer 是个大块头这一点必须有心理准备。就算用了按需注册插件打包产物依然不轻。为什么因为 Canvas 渲染引擎、公式引擎、协同逻辑这些底层模块本身就很大不是靠 tree-shaking 能完全消掉的。我第一次接进项目的时候没做任何优化打完包一看主包体积直接涨了差不多 800KBgzip 后大概 300KB 多。对于内部管理系统来说这倒不是不可接受但如果你面对的是对首屏加载极其敏感的线上产品就必须处理。我的处理方案是把 Univer 单独拆出来动态加载只有进入“在线表格”页面时才加载相关代码而不是打进主包里影响所有访问。简单说就是用路由级的import()动态引入让表格模块延迟到真正需要时才下载。如果你用的是 Vite还可以把它单独配置成手动分包或者放到 CDN这样能明显减轻首屏压力。另一个经验是务必使用官方提供的按需导入入口不要一次性从univerjs/presets里把全套功能拉进来。虽然预设写起来很省事但它默认会带入很多你用不到的功能打包体积会更大。4.4 与现有前端框架的整合我们团队技术栈以 Vue 为主但 Univer 本身不限定框架它只管一个 DOM 容器。所以无论是 Vue 还是 React接入思路都差不多在组件挂载时创建 Univer 实例把容器元素传给它组件卸载时销毁实例。在 Vue 里比较关键的是生命周期控制。你不能把 Univer 实例放在普通变量里然后不管否则组件频繁切换时会出现多次创建、旧实例未销毁的问题导致页面里出现多个表格叠加的诡异情况。我在封装组件时专门做了一个实例管理模块确保每次进入页面时先dispose()旧实例再创建新的。还有一点如果你想在表格外部放一个按钮点按钮操作表格数据比如“新增一行”“批量导入”就需要把 Univer 实例暴露出来。最方便的方式是把它放到一个可注入的全局 store 里这样表格外的逻辑也能拿到工作簿引用调用对应 API。表格内部数据变化后通过事件回调同步给外面的业务逻辑。这个模式在 React 中就用useRef或 Context在 Vue 中就用一个独立的模块对象都能实现。5. 常见问题与排查技巧实录下面是这次接入过程中我觉得最有实用价值的部分常见问题和排查思路。整理成一个速查表能帮你少走一半弯路。5.1 高频问题速查表这是我当时踩过坑、或者帮同事排查后沉淀下来的问题清单问题现象常见原因解决办法表格区域空白页面加载后没有内容容器宽高为 0 或初始化时机不对确保容器有尺寸组件挂载后再创建实例公式不计算手输SUM没有结果公式插件未注册或版本不匹配检查插件版本统一升级到同一版本导入 xlsx 卡死上传大文件后页面无响应解析在主线程执行将解析任务放到 Web Worker右键菜单不弹出点击右键没有自定义菜单菜单注册方式版本兼容按当前版本文档重新注册中文输入法异常中文输入时单元格内容丢失输入法组合事件处理问题升级到修复版本的包或暂停输入中间态打包报错module 找不到插件间版本不匹配用官方推荐版本组合锁定版本号页面切换后表格重叠多次创建实例旧实例未销毁在组件卸载时调用dispose()协同后数据覆盖有人修改丢失业务缺少操作适配服务端加入冲突处理保留审计日志这些现象绝大多数不是 Univer 本身有 bug而是接入方式或版本管理的问题。对这类复杂开源项目第一原则永远是先确认所有univerjs/*包版本一致再谈其他。5.2 一次真实的排查公式不刷新我想单独说一次排查过程因为它对看懂 Univer 的架构挺有帮助的。有一次同事反馈某个单元格写了B2*C2第一次计算结果是正常的但把 B2 的值改掉之后结果没有自动刷新。听起来像是公式引擎失效了挺吓人的。我第一步去查版本发现在package.json里同事安装的univerjs/sheets-formula版本是0.1.6而 core 包已经升到了0.2.x。这种情况下公式插件的旧版本监听事件的能力在新版本 core 里已经不匹配数据变更事件根本推不到公式引擎那里。把版本统一升级之后这个问题立刻消失了。后来我们专门在 CI 里加了一条依赖检查限制所有univerjs/*包必须使用同一版本号范围防止再出现类似问题。这个坑给我的启发是Univer 现阶段还是一个快速演进的项目对待它不能像对待一个稳定五年的老库那样闭着眼睛装。每次升级前都去看一眼 changelog确认没有破坏性变更再动依赖。5.3 维护 Univer 项目的一点心得如果你决定在生产项目里引入 Univer我强烈建议做两件事。第一件事在官方 GitHub 仓库里学会搜 issue。很多问题其实已经有人遇到过并且维护者也在 issue 里给出了解释。搜索的时候不要只搜索报错文本也要把相关的包名和版本号带上命中率会更高。第二件事学会看源码。Univer 的源码虽然规模不小但架构清晰模块边界比较明显。遇到问题时直接在本地把源码下下来按调用栈去找对应模块往往比不断试配置项更高效。我第一次理解条件格式的数据结构时就是通过源码里类型定义倒推出来的比自己盲猜配置靠谱得多。另外尽量不要长期停留在一个陈旧版本。Univer 的功能和 bugfix 更新很频繁你如果在一个早期版本上开发了大半年后期升级的成本可能高到让你想重构。我的策略是每三个月评估一次是否有值得升级的版本如果没有就继续稳定运行如果有就安排一个专项来做升级适配。6. 影响范围分析Univer 能替代什么不能替代什么最后聊一聊这个项目对整个前端生态和业务系统的影响。我越来越觉得选型不能只看“今天能不能用”还要想清楚“它能覆盖多大范围”以及“哪些事它永远不做”。把边界弄清楚了才不会在项目中期产生不切实际的期望。6.1 它能解决的场景Univer 最主要的价值是把“电子表格体验”从桌面端搬到了 Web 端。对内部系统、数据中台、低代码平台这类产品来说这是一个非常大的补位。我见过很多团队做数据录入界面最后都做成了“用 el-table 画一个像 Excel 的东西”。用户想要的是合并单元格、冻结窗格、条件格式、公式联动而普通表格组件在这些需求面前显得很无力。Univer 这类项目出现后前端团队不用再从零写公式引擎也不用自己在 Canvas 上重造轮子它可以作为“在线表格底层设施”被直接集成进业务。对开发者来说它的插件化架构意味着你可以按需裁剪只想要展示就不装 UI 编辑相关的插件只想要公式就不启用协同。对产品来说它提供了接近 Excel 的操作体验用户培训成本会低很多或者说根本不用培训。如果往更大的范围看Univer 对低代码平台的价值尤其明显。低代码平台的常见组成里表单、表格、流程是不可或缺的。Univer 恰好补上了“表格引擎”这一环让平台搭建者可以快速获得电子表格能力而不是每次都要对接 Excel 文件解析再手动拼界面。6.2 它的边界和局限但我必须说一句公道话Univer 不是“下载下来就能开箱即用”的完整 Office 产品。它没有现成的后端服务存储、权限、协同服务端都要结合自己的系统去实现。它也不是万能的 Excel 解析器超复杂的 Excel 样式、图表、宏在导入时仍然可能丢失或变形。另外它的文档质量虽然一直在进步但相比一些成熟开源项目还是有差距。如果你要深入做二次开发很多时候需要直接阅读源码。这对团队的技术要求有一定门槛不是随便找个初级前端就能顺利驾驭的。所以我的结论是如果你的需求只是“后台的一个简单表格展示”Univer 反而是杀鸡用牛刀不如直接用 UI 库自带的表格组件。但如果你要做的是“网页里的 Excel”那我目前没看到比 Univer 更合适的开源选择。它对前端在线办公生态的影响确实是实打实的。最后再分享一个我个人的小建议如果团队决定上 Univer不要一上来就追求把所有功能都打开先分析业务里真正高频的能力只启用必要插件。这样既能控制体积也能把二次开发的复杂度控制在可维护范围内。我们项目就是从纯展示 简单公式起步又慢慢加了条件格式和协同一步步来的实测下来这个节奏最稳。
返回列表