1. 从“univer”这个名字说起它到底解决了什么问题第一次听到 univer 这个名字很多人会以为是某个大学项目或者某个开源社区的小众玩具。但如果你最近在关注在线表格、在线文档、协同编辑这类方向这个名字出现的频率正在肉眼可见地变高。univer 是一个开源的、面向电子表格与文档场景的前端渲染与协同引擎它把 Canvas 绘图、公式计算、协同编辑、插件化架构这几件事揉在了一起目标很明确让开发者能在自己的产品里用相对低的成本嵌入一套接近专业办公软件的表格能力。这件事为什么值得单独拿出来讲因为过去想在 Web 里做一套像样的表格路子其实很窄。要么用现成的商业组件贵且不透明要么自己基于 DOM 硬写行数一多就卡到怀疑人生要么用 Canvas 自己画但画完之后公式、选区、复制粘贴、协同这些全得自己补工作量直接爆炸。univer 的价值就在于它把“Canvas 渲染引擎 数据模型 公式引擎 协同层 插件体系”打包成了一套 SDK你拿到的是一个可以二次开发的底座而不是一个只能看不能改的成品。它适合谁三类人最应该关注。第一类是前端工程师尤其是做过或想做在线表格、报表、低代码平台的人univer 的 Facade API 和插件机制能让你少走很多弯路。第二类是 Node.js 方向的服务端开发者因为 univer 的协同和公式计算有一部分是可以放到服务端跑的前后端如何分工是个很实际的问题。第三类是产品和技术负责人需要评估“自研表格”和“接入 univer”之间的成本差。这篇文章我会按一个真实落地项目的思路来写从架构选型、核心概念、实操接入、到踩坑排查尽量把我知道的都倒出来。2. 整体架构与选型思路为什么是 Canvas 加 Facade API2.1 Canvas 渲染这条路的必然性先聊渲染。表格这个东西本质上是一个二维网格每个单元格可能有文本、样式、边框、合并、条件格式。用 DOM 来做最直观一个单元格一个 div 或者 td简单表格确实够用。但一旦行数上万、列数上百DOM 节点数量就是几十万级别浏览器的布局和重绘压力会直接把你按在地上。我实测过一个两万行的 DOM 表格滚动的时候帧率掉到个位数用户体验基本没法看。Canvas 的思路是把整个表格画在一张画布上只渲染可视区域内的单元格滚动时重绘。这样节点数量恒定性能可控。univer 走的就是这条路它内部维护了一套渲染调度把单元格的绘制、选区高亮、滚动条、冻结行列这些都统一在 Canvas 上处理。代价是Canvas 里的一切都是“画”出来的没有 DOM 那样的天然可访问性和事件冒泡所以选区、点击、输入这些交互都得自己实现一套命中检测和事件分发。univer 帮你把这部分做完了这是它比“自己从零画 Canvas”省事的关键。提示如果你的场景表格规模很小几百行以内其实没必要上 Canvas 方案DOM 表格的开发效率和可维护性反而更高。Canvas 的收益是在大数据量下才体现出来的。2.2 Facade API 的设计哲学univer 对外暴露的核心接口叫 Facade API。这个词直译是“门面”在软件设计里指的是用一个统一的高层接口把底层复杂的子系统包起来。为什么需要这个因为 univer 内部模块非常多渲染引擎、数据模型、公式引擎、命令系统、插件容器如果每个模块都直接暴露给使用者学习成本会高到劝退。Facade API 做的事情就是把“创建一个表格”“设置某个单元格的值”“监听选区变化”“注册一个自定义函数”这些高频操作收敛成一组语义清晰的调用。你不需要知道底层命令是怎么派发的也不需要关心渲染是怎么调度的调 API 就行。这种设计对二次开发特别友好因为它把稳定接口和内部实现解耦了univer 内部重构的时候只要 Facade API 不变你的代码就不用动。我个人的判断是选型的时候一定要看一个库有没有这种“稳定门面”。很多开源项目把内部类直接暴露出来用起来爽但升级一次就崩一次。univer 在这一点上做得比较克制值得肯定。2.3 插件化架构带来的扩展空间univer 的另一个核心设计是插件化。它本体只提供最基础的表格能力公式、协同、导入导出、条件格式这些都是以插件形式存在的。这意味着两件事一是你可以按需加载不用为一个只用基础表格的场景引入整个公式引擎二是你可以写自己的插件把业务逻辑挂进去。插件化的代价是心智负担。你得理解插件的生命周期、依赖关系、注册顺序。我见过有人把插件注册顺序搞错导致公式引擎在数据模型之前初始化结果公式一直算不出来。这类问题在后面排查章节我会细说。3. 核心概念拆解数据模型、命令系统与协同层3.1 数据模型一切从 Workbook 开始univer 的数据模型是围绕 Workbook 组织的。一个 Workbook 里有多个 Worksheet每个 Worksheet 里有单元格数据、行列信息、样式、合并区域等。这个结构和 Excel 的心智模型基本一致所以如果你熟悉 Excel 的对象模型上手会很快。关键点在于univer 的数据模型是“可序列化”的。也就是说整个 Workbook 可以转成一个 JSON 对象存到后端、传到前端、做快照、做版本对比都没问题。这一点对协同场景至关重要因为协同的本质就是“把变更同步给所有人”而变更必须能表达成可传输的数据。单元格的值、公式、样式在模型里是分开存的。值是一层公式是一层样式又是一层。这种分层设计的好处是改样式不会触发公式重算改值才会。理解这个分层对后面做性能优化很有帮助。3.2 命令系统所有修改都走命令univer 里对数据的任何修改都不是直接改对象而是通过“命令”来做的。比如设置单元格值是派发一个 SetRangeValuesCommand插入一行是派发一个 InsertRowCommand。为什么这么设计因为命令是可记录、可撤销、可同步的。可撤销好理解CtrlZ 就是撤销上一个命令。可同步是协同的基础本地执行一个命令把这个命令广播给其他客户端其他客户端执行同样的命令状态就一致了。如果直接改对象你没法知道“改了什么”也就没法同步。这个设计对使用者的影响是你不能直接worksheet.getCell(0,0).value 1得走 Facade API 提供的方法。刚开始会觉得绕但习惯之后会发现这是对的因为它保证了所有变更都在可控范围内。3.3 协同层命令广播与冲突处理协同是 univer 的一个重点能力。它的基本思路是每个客户端本地先执行命令保证即时响应然后把命令发给协同服务服务再广播给其他客户端。这就是所谓的“乐观更新”用户感觉不到延迟。冲突处理方面univer 采用的是基于命令的合并策略。因为命令本身携带了操作意图比如“把 A1 设为 1”所以合并的时候可以按一定规则决定谁赢。这里不展开太深但你要知道一个现实协同的难点从来不是技术而是产品语义。两个人同时改一个单元格到底谁赢、要不要提示、要不要保留历史这些是产品决策不是技术能自动解决的。注意如果你的协同场景对一致性要求极高比如财务对账乐观更新可能不够需要考虑加锁或者服务端仲裁。univer 提供了扩展点但具体策略得自己定。4. 实操接入从零跑起一个 univer 表格4.1 环境准备与依赖安装先说环境。univer 是前端库所以你需要一个 Node.js 环境来跑构建工具。Node.js 版本建议用 18 LTS 或更高我实测 18.20.4 和 22.x 都能正常跑。安装步骤不复杂去官网下载对应系统的安装包一路下一步就行。装完之后在终端敲node -v和npm -v能输出版本号就说明装好了。如果你是在 CentOS 这类服务器上做构建注意 Node.js 的安装方式。用包管理器装的版本往往偏旧建议用 nvm 或者官方提供的二进制包。我踩过一次坑服务器上 Node 版本太低构建的时候各种语法报错排查了半天才发现是版本问题。依赖安装这块univer 是拆成多个包发布的核心包加上你需要的插件包。基础接入一般需要核心包、渲染包、公式包如果需要公式、协同包如果需要协同。用 npm 或 yarn 装都行注意版本要对齐不同包之间版本不匹配是常见问题。npm install univerjs/core univerjs/design univerjs/engine-formula univerjs/sheets univerjs/sheets-formula4.2 初始化一个最小可用的表格装完依赖接下来是初始化。univer 的初始化分两步先创建 Univer 实例并注册插件再创建 Workbook 并挂到 DOM 上。下面是一个最小示例。import { Univer, LocaleType, merge } from univerjs/core; import { UniverFormulaEnginePlugin } from univerjs/engine-formula; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsFormulaPlugin } from univerjs/sheets-formula; const univer new Univer({ locale: LocaleType.ZH_CN, theme: defaultTheme, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverSheetsFormulaPlugin); univer.createUnit(UniverInstanceType.UNIVER_SHEET, { id: my-workbook, sheets: { sheet-01: { id: sheet-01, name: Sheet1, cellData: { 0: { 0: { v: Hello }, 1: { v: Univer } }, 1: { 0: { v: 1 }, 1: { v: 2 }, 2: { f: SUM(A2:B2) } }, }, }, }, });这段代码做了几件事创建实例、注册插件、创建一个带数据的 Workbook。注意cellData的结构外层 key 是行号内层 key 是列号v是值f是公式。这个结构就是前面说的可序列化数据模型。4.3 挂载到页面与样式处理创建完 Workbook还得把它渲染到页面上。univer 需要一个容器元素然后你调用渲染相关的方法把表格挂上去。容器的高度必须明确否则 Canvas 不知道画多大。我见过有人容器没设高度结果表格一片空白排查半天。div iduniver-container stylewidth: 100%; height: 600px;/div样式方面univer 的组件库有自己的 CSS需要引入。如果你用的是构建工具直接在入口文件 import 对应的样式文件即可。漏引样式会导致表格布局错乱这是新手常见问题。4.4 用 Facade API 做常见操作挂载完成后日常开发大部分时间是在和 Facade API 打交道。举几个高频操作。读取单元格值const workbook univerAPI.getActiveWorkbook(); const worksheet workbook.getActiveSheet(); const range worksheet.getRange(0, 0); const value range.getValue();设置单元格值worksheet.getRange(0, 0).setValue(新值);批量设置worksheet.getRange(0, 0, 3, 3).setValues([ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]);监听选区变化univerAPI.getActiveWorkbook().getActiveSheet().onSelectionChanged((selection) { console.log(当前选区, selection); });这些 API 的设计思路是链式的getRange拿到区域对象再调方法。熟悉之后写起来很顺。5. 公式引擎与自定义函数让表格真正“活”起来5.1 内置公式的覆盖范围univer 的公式引擎覆盖了大部分常用函数SUM、AVERAGE、IF、VLOOKUP、COUNTIF 这些都有。它的计算是增量的也就是说你改了一个单元格只有依赖它的公式会重算不是全表重算。这个设计对性能很关键否则一个大表格改一个格子就卡死。公式的依赖关系是引擎自动维护的。你写A1B1引擎会记录 C1 依赖 A1 和 B1A1 变了就重算 C1。这个依赖图是动态更新的插入删除行列的时候会重建。理解这一点对排查“公式不更新”的问题很有帮助。5.2 注册自定义函数的完整流程内置函数不够用的时候可以注册自定义函数。univer 提供了注册接口你需要定义函数名、参数、计算逻辑。下面是一个简单示例实现一个把两个数相乘再取整的函数。import { IFunctionInfo, FunctionType } from univerjs/engine-formula; const myFuncInfo: IFunctionInfo { functionName: MYMUL, functionType: FunctionType.Math, description: 两数相乘取整, parameters: [ { name: a, detail: 第一个数, type: number }, { name: b, detail: 第二个数, type: number }, ], }; univerAPI.registerFunction(myFuncInfo, (a, b) { return Math.floor(a * b); });注册完之后在单元格里写MYMUL(3, 4)就能得到 12。自定义函数的价值在于你可以把业务逻辑封装成公式让非技术人员也能用。比如做报表的时候把一些复杂的计算规则做成函数业务人员直接写公式就行。提示自定义函数的计算逻辑要尽量纯不要在里面做异步操作或者依赖外部状态否则公式重算的时候行为不可预测。5.3 公式性能的几个关键点公式是性能大户。我总结几个经验。第一避免大范围的整列引用比如SUM(A:A)这种在数据量大的时候会拖慢计算。第二VLOOKUP 这类查找函数如果查找范围很大考虑用 INDEXMATCH 或者加辅助列。第三循环引用要避免虽然引擎会检测但检测本身也有开销。还有一个容易被忽略的点公式的易失性。像 NOW()、RAND() 这类函数每次重算都会变会导致依赖它们的公式频繁重算。如果表格里大量使用这类函数性能会明显下降。能用静态值就用静态值。6. 协同编辑落地从单机到多人6.1 协同服务的基本形态univer 的协同需要一个服务端来中转命令。这个服务端的职责是接收客户端发来的命令广播给同一文档的其他客户端同时维护一份文档的当前状态。服务端可以用 Node.js 写因为 univer 的核心逻辑有一部分是可以在 Node 环境跑的前后端能复用代码。服务端的核心是房间管理。每个文档对应一个房间房间里有若干客户端连接。客户端加入房间时服务端把当前文档快照发过去客户端加载后再接收增量命令。这个流程和大多数协同系统类似。6.2 命令的序列化与传输命令在传输前要序列化。univer 的命令对象本身是普通对象JSON 序列化就行。但要注意命令里可能包含一些不需要传输的字段比如本地的临时状态。传输前最好做一次清洗只发必要的数据。传输协议用 WebSocket 比较合适因为协同需要双向实时通信。消息格式建议加一个类型字段区分“命令”“快照请求”“光标位置”等。光标位置这种高频但轻量的数据可以和命令走不同的通道避免互相影响。6.3 冲突场景与处理策略前面提过协同的难点在产品语义。举几个实际场景。两个人同时改 A1一个改成 1一个改成 2。乐观更新下本地都先显示自己的值服务端广播后后到的命令覆盖先到的最终两人都看到 2。这个行为可接受但如果用户在意就得加提示。再比如一个人删了一行另一个人正在这行里输入。删除命令到达后输入的那行没了输入框要关闭。这类边界情况需要在客户端做处理监听命令类型做相应的 UI 响应。我的建议是协同功能上线前一定要做压力测试和异常测试。网络抖动、断线重连、消息乱序这些在真实环境里都会遇到。univer 提供了重连机制但业务层的状态恢复得自己写。7. 常见问题与排查技巧实录7.1 表格不显示或显示空白这是最高频的问题。排查顺序第一容器有没有明确高度Canvas 需要知道画多大第二样式文件有没有引入漏引会导致布局塌陷第三Workbook 有没有正确创建并挂载检查 createUnit 的返回值第四控制台有没有报错插件注册顺序错误经常在这里暴露。7.2 公式不计算或计算结果不对先确认公式引擎插件有没有注册。然后检查公式语法univer 的公式语法和 Excel 基本一致但个别函数可能有差异。再检查依赖关系如果公式引用的单元格是空的结果可能是 0 或者空。最后看是不是循环引用引擎会检测但可能不报错只是不计算。7.3 协同不同步排查思路先确认 WebSocket 连接是否正常看网络面板有没有断线。再确认命令有没有发出去在发送处打日志。然后看服务端有没有收到并广播。最后看接收端有没有正确执行命令。这条链路任何一环断了都会导致不同步。常见原因是命令序列化失败比如命令里带了函数或者循环引用JSON.stringify 会报错。7.4 性能问题排查表现象可能原因排查方法解决方向滚动卡顿渲染单元格过多看可视区域外是否也在渲染检查虚拟滚动配置输入延迟公式重算频繁看是否有易失性函数替换为静态值内存增长命令历史未清理看撤销栈是否无限增长限制历史条数协同延迟命令体积过大看单条命令大小拆分或压缩命令首屏慢快照过大看初始加载数据量分片加载或懒加载7.5 几个我踩过的坑第一个坑是插件注册顺序。公式引擎必须在表格插件之后注册否则公式引擎找不到数据模型。这个顺序在文档里不一定写得很显眼但错了就是不行。第二个坑是 Node.js 版本。前面提过低版本 Node 会导致构建失败而且报错信息往往指向具体语法不直接说是版本问题容易误导。第三个坑是 Canvas 的导出。univer 的表格是画在 Canvas 上的如果你想导出成图片不能直接截 DOM得用 Canvas 的 toDataURL。但要注意跨域图片会污染画布导致导出失败。如果表格里有外部图片需要服务端代理或者设置跨域头。第四个坑是移动端适配。Canvas 在移动端的触摸事件和桌面端不一样univer 做了处理但如果你自定义了交互要额外测试。iOS Safari 上有个已知问题是 Canvas 尺寸过大时导出白图解决办法是限制导出尺寸或者分块导出。8. 扩展方向univer 还能怎么用univer 的插件体系意味着它的边界是可以扩展的。我想到几个方向。一是和低代码平台结合把表格作为数据展示和编辑的组件通过 Facade API 和平台的数据源打通。二是做报表工具利用公式引擎和自定义函数让业务人员配置计算规则。三是做数据采集把表格作为录入界面配合校验插件做数据质量控制。还有一个方向是服务端计算。univer 的公式引擎可以在 Node.js 里跑这意味着你可以把一些重计算放到服务端前端只负责展示。这在数据量大、计算复杂的场景下很有价值。比如财务模型几万个公式放前端算会卡放服务端算完把结果推给前端就流畅了。Node.js 在这里的角色不只是构建工具它可以是协同服务端、计算服务端、导入导出服务端。如果你本来就是 Node.js 方向univer 的服务端扩展是个很自然的切入点。9. 一些个人体会用 univer 做项目最大的感受是“底座很稳但上层要自己搭”。它把最难的部分渲染、公式、协同做完了但业务逻辑、交互细节、性能调优这些还是得自己来。这不是缺点反而是它的定位决定的——它是一个 SDK不是一个成品。选型的时候我建议先想清楚自己的场景。如果只是要一个能展示数据的表格用现成的组件库更省事。如果要的是可深度定制、能协同、能跑公式的表格引擎univer 值得投入时间研究。学习曲线是有的但一旦理解了它的数据模型和命令系统后面的事情就顺了。最后分享一个小技巧调试的时候把 univer 的命令日志打开能看到每一条命令的派发和执行。这对理解它的工作机制特别有帮助排查问题的时候也能快速定位是哪条命令出了问题。这个日志在开发环境开着生产环境关掉对性能影响不大。
企业数字化 ERP 产品动态
相关推荐
终端渲染技术:需求驱动的状态设定与性能优化 1. 终端渲染技术概述终端渲染技术是现代命令行界面(CLI)开发中的核心环节,它决定了文本在终端中的呈现方式和交互体验。传统终端渲染受限于历史兼容性要求,往往采用静态、预设的渲染策略,这在实际开发中造成了诸多限制。ANSI转义序列作为终端… · 2026/11/3 14:57:12
AI工程从零构建:可交付、可运维、可演进的系统基座 1. 这不是“搭个模型”,而是重建AI系统的地基很多人看到“AI Engineering from Scratch”这个标题,第一反应是:“哦,手写一个神经网络?用NumPy实现反向传播?”——这完全误解了“from scratch”的真实含义。… · 2026/11/3 14:57:12
开发者必知的时间处理:GMT、UTC与时区陷阱 1. 为什么时间处理是开发者的噩梦? 上周团队里一个刚毕业的开发者提交了一段看似简单的代码:将用户本地时间转换为UTC时间存储。测试环境一切正常,上线后却陆续收到巴西用户的投诉——他们的预约时间全部错乱了4小时。这让我想起自己刚入行时… · 2026/11/3 14:56:11
Paperxie:一站式论文写作工具的功能解析与使用技巧 1. 项目概述:一站式论文写作解决方案去年帮学弟改毕业论文时,我看着他同时开着Word、LaTeX、绘图软件和十几个参考文献网页的电脑桌面,突然意识到:为什么没有一个工具能把论文写作的全流程整合在一起?直到发现Paperxie… · 2026/11/3 23:22:50
TEB局部规划器调参与排障:从图优化原理到典型症状排查 局部运动规划器TEB(Timed Elastic Band)在ROS里是个很特殊的存在:它不是靠采样、打分、挑最优那种"试出来"的规划方式,而是把一条由全局规划器给出的粗路径,连同时间信息一起当成一根有弹性的橡皮筋… · 2026/11/3 23:22:50
单例模式深度解析:从双重检查锁到容器单例的工程实践 单例模式大概是所有设计模式里最容易被“三分钟学会”的一个:面试能背、代码能写、用起来也不复杂。但我这些年 review 过的代码里,出问题最多的恰恰是它。有人把一个带可变状态的缓存类写成单例,结果两条业务线互相把对方的数据覆盖了&#… · 2026/11/3 23:22:50
线段树维护max-plus矩阵解决链上最大权独立集单点修改 P3097 这道题放在待做题单里很久了,一直没动,因为标题里“最优挤奶”看着像道很快就能写完的题。真正促使我去做它,是有人在群里说了一句“会了这题,线段树维护矩阵那一套基本就入门了”。我当晚就写完了,然后调了两个… · 2026/11/3 23:22:50
科研论文高效写作工具链与自动化排版实战 1. 论文写作新范式:从焦虑到高效的全流程解决方案读研三年,我见过太多同学在论文初稿阶段崩溃——文献堆满桌面却无从下笔,实验数据杂乱不知如何呈现,格式调整能耗费整周时间。直到去年帮导师评审本科生论文时,发现有个… · 2026/11/3 23:22:50
集体好奇心驱动的AI研发:群体智能如何重塑技术探索 1. 集体好奇心驱动的AI研发范式变革去年参与某开源大模型项目时,我亲眼见证了一个有趣现象:当社区开发者偶然发现模型在特定语境下会产生诗化输出后,短短两周内涌现出87种诗歌生成改进方案。这种自发的探索热情,正是集体好奇心&am… · 2026/11/3 23:21:49
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46