聊到给业务系统集成在线表格前端的同学估计都有点一言难尽。商业组件功能全但授权费用高、源码封闭想改个交互都得跟客服扯皮开源方案里 Luckysheet 算是不错的可它把大量逻辑绑在 DOM 和旧架构上玩到中后期总会觉得伸展不开。今天要聊的 Univer就是 Luckysheet 作者团队做的下一代项目一句话概括一套基于 TypeScript Canvas 渲染的模块化在线 Office 套件目前表格能力最成熟。这篇文章会从它要解决什么问题讲起再拆一拆底层的渲染、数据、公式、插件架构最后给一份能跑起来的接入实践和踩坑记录。如果你正在做 SaaS 产品、内部管理系统或者单纯想在项目里嵌入一个可控的电子表格能力Univer 值得花半小时摸清楚。它最大的价值不只是“又有一个在线 Excel 组件”而是把表格、文档、幻灯片统一到一个架构里同时用插件机制把渲染层、数据层和业务层彻底解耦——这意味着你可以只拿它的数据处理能力也可以只用它的界面甚至可以把它嵌到自己写的编辑器里。1. Univer 是什么它到底想解决什么问题1.1 从 Luckysheet 到 Univer一场“推倒重来”很多早期用户知道 Univer是因为熟悉 Luckysheet。Luckysheet 当年确实火过它用一个还算轻量的思路在浏览器里实现了类 Excel 的在线表格对中小型项目帮助很大。但它有一个根本问题底层基于 DOM 构建单元格数据量稍大、公式链一长渲染和计算性能就直线下降而且代码结构里 UI 逻辑和业务逻辑纠缠在一起后续想加协同、加自定义扩展越改越痛苦。Univer 走的是另一条路。它在设计初期就决定渲染引擎用 Canvas 重写数据模型独立成一套文档模型公式引擎做成可独立调用的纯 TypeScript 模块UI 层则通过插件系统按需组合。这种“推倒重来”的选择让 Univer 在架构上是面向未来十年的办公套件去做的而不是在一个老代码基础上打补丁。也正因如此它才能在极短时间里得到不少开源社区的关注。1.2 它能做什么适合谁用当前 Univer 完成度最高的是在线表格模块包括单元格编辑、样式、行列操作接近 Excel 的交互习惯公式引擎支持大量常用函数且计算过程异步化多实例能力同一个页面可以创建多个表格应用官方提供 React、Vue 等主流框架的接入封装插件体系可以自定义菜单、按钮、右键菜单、单元格操作等。它适合的人群我列个清单前端工程师需要在自己的业务系统里快速嵌入表格编辑能力团队技术负责人评估商业组件和开源方案时需要一个性能、授权、可维护性都说得过去的选项数据产品经理想低成本验证一个带表格编辑能力的 MVP而不是一上来就采购昂贵控件开源爱好者对在线 Office 技术栈渲染引擎、公式引擎、协同算法感兴趣想读代码入门。一句话如果你的需求只是“上传 Excel-导出 Excel”用 SheetJS 之类就够了但如果你需要“用户在线编辑 公式联动 深层次体验定制”Univer 才真正进入射程范围。2. 核心架构拆解为什么 Univer 敢用 Canvas 重写一切2.1 渲染引擎放弃 DOM 的取舍传统表格组件比如老版 Luckysheet或者一些自研小表格很容易走上“一个单元格一个节点”的路。这个方案在百行百列里还能扛一旦到几千行、十几列页面里的 DOM 节点数量直接爆炸滚动起来卡顿不说内存占用也感人。我见过一个集成此类组件的后台用户筛选完 5000 行数据整个页面拖动时像在看慢动作回放。Univer 的渲染思路可以类比成“黑板模式”整个画布按需绘制。它把表格区域绘制在 Canvas 上只有用户看到的可视区域才被真正渲染滚动、缩放时通过重绘而非新建节点来完成交互。这样做的直接收益是单元格数量对渲染性能的影响极大降低。那个在旧组件里卡成 PPT 的 5000 行数据在 Univer 的 Canvas 方案下基本可以流畅拖拽。Canvas 方案的代价也显而易见浏览器原生编辑光标、输入法交互、文本选择这些能力要自己重新实现可访问性也比 DOM 方案更难做。但 Univer 选择了一并解决这些“难啃的骨头”换来的是稳定性和扩展性的上限大幅提高。如果你以前做在线表格相关调研会发现 SpreadJS 之类商业产品也普遍走自定义绘制路线这已经是行业共识并不是 Univer 独有。2.2 数据模型与命令模式表格界面的背后Univer 维护了一套文档数据模型Document Model。它的核心不是把 Excel 的 XML 结构直接搬过来而是抽象成统一的表格单元Cell、行Row、列Column、工作表WorkSheet等数据对象并且所有数据操作都通过命令Command来触发。这个设计非常像游戏或编辑器里的“命令模式”任何一次“写 A1 单元格内容”的动作不是直接改内存里的值而是生成一条命令经过执行、记录进入操作栈。好处是显而易见的第一步就为 undo/redo撤销/重做铺好了路更进一步当你要实现多人协同编辑时每条命令都可以作为一个最小同步单元发送给远端、应用到远端文档上。Univer 的数据层分离让协同能力不是“后续能不能加”的问题而是“什么时候开放”的问题。2.3 公式引擎依赖图与异步计算公式是电子表格的灵魂Univer 把一个纯 TypeScript 写的公式引擎拆成了独立模块。为什么值得单列一段因为“能算公式”和“公式算得对、算得快、算错了能定位”是两码事。Univer 的公式引擎在计算时会构建依赖图比如 A1 是 1B1 是A1*2C1 是SUM(A1:B1)这时候 B1 和 C1 都依赖 A1。当你把 A1 改成 10那 B1、C1 需要重新计算但其他无关单元格不需要。它按依赖关系建立“计算链”尽量做到只算受影响的公式而不是整张表全部重算。这一点很重要尤其当表格里有大量 VLOOKUP、SUMIF 这些引用类公式时全量重算的耗时会很明显。Univer 还把计算过程做成了异步任务不阻塞界面操作算是踩在用户体感的刀刃上了。我自己在测试中特意建了一个 2000 行、每行三四个公式的表格修改左上角输入后联动刷新基本无感这在旧方案里不现实。2.4 插件机制UI 与业务解耦Univer 给使用者的另一大诱惑是插件体系。普通表格组件给什么界面就是什么界面想加一个自定义按钮、一个侧边栏面板相当折腾。Univer 把菜单、工具栏、右键菜单、快捷键都设计成了可注册的扩展点。举个例子你在做数据中台希望用户在表格里选中一个指标时右侧浮出指标说明卡片并且有一个“一键生成图表”按钮。在 Univer 里你可以注册一个插件只维护自己的 UI 组件监听表格的选择变化事件然后往侧边栏容器里插入内容。基础表格的渲染和编辑功能全部由预设插件提供。这种“只改业务层、不影响核心层”的体验是我认为 Univer 最值得升值的地方。3. 从零跑通 Univer安装、初始化、接入 React3.1 环境准备项目初始化这一节给一份当时实测能用的接入流程。需要说明的是Univer 还处在快速迭代期版本之间的 API 会有调整下面代码对应 1.x 早期阶段的做法现在接手项目时以官方最新 Quick Start 为准思路不变包名和初始化参数可能会有细微变化。先创建前端项目我用 Vite 的 React-TS 模板做演示npm create vitelatest univer-demo -- --template react-ts cd univer-demo npm install然后安装 Univer 相关依赖。最省事的方式是直接安装官方预设包它会帮你把表格、公式、UI 等核心插件装好如果你后续需要定制再按需换成底层模块。npm install univerjs/preset-sheets univerjs/core3.2 初始化一个最小可用的表格实例Univer 的思路是先创建一个 Univer 实例再往这个实例里注册各种插件。下面这段代码创建了一个带完整表格 UI 的应用挂载到页面上 id 为app的容器里。import { Univer } from univerjs/core; import { defaultTheme } from univerjs/design; import { SheetsUniver } from univerjs/preset-sheets; import univerjs/preset-sheets/lib/styles/index.css; const univer new Univer(); // 注册表格预设插件 univer.registerPlugin(SheetsUniver); // 创建并初始化一个表格应用 univer.createUnit(UniverSheetsPlugin, { container: app, theme: defaultTheme, });这里需要注意几点容器app必须已经在 DOM 里并且最好给它一个明确的高度。表格应用不像普通组件可以依赖内容撑开高度Canvas 渲染需要真实的容器尺寸来确定可视区域。引入样式文件很重要很多人直接照抄核心代码忘了引 CSS页面出来就是一堆怪异布局。createUnit这个动作语义上是在 Univer 实例里新建一个文档单元。类似地你可以多次调用实现同一个页面里多个表格独立共存。这段代码跑起来你就能看到一个有工具栏、公式栏、单元格区域和标签栏的在线表格界面可以输入数据、改样式、用基础公式。这就是 Univer 的“最小可用闭环”。3.3 写入数据与公式联动测试光有一个空表格没什么说服力我们来造点数据。直接在界面里手动输入当然可以但更常见的需求是页面初始化时就要把后端数据渲染到表格里。Univer 的数据写法跟 Excel 底层逻辑类似直接操作单元格的值。下面示例是在第一个工作表里写入 A11、A22、B1 里写入求和公式const workbook univer.getActiveWorkbook(); const worksheet workbook.getActiveWorksheet(); worksheet.getRange(0, 0).setValue(1); // A1 worksheet.getRange(1, 0).setValue(2); // A2 worksheet.getRange(0, 1).setValue(SUM(A1:A2)); // B1这里下标是 0 起点(0, 0)对应 A1(0, 1)对应 B1。写入公式后你可以看到 B1 单元格直接显示计算结果 3。公式字符串的语法与 Excel 保持一致等号开头、区域引用用冒号。我建议刚上手的人先用这个方式把公式和数据都跑通一遍再去研究导入导出。因为公式引擎能不能正常工作直接决定了你的核心体验是否成立。3.4 在 React 项目里正确销毁与重建在 SPA 项目里比较常见的坑是页面跳转后Univer 实例还挂在全局再次进入页面又创建新实例两个实例抢占同一个容器或者内存泄漏。所以接入 React 时必须做好生命周期管理。这里提供一个封装思路import { useEffect, useRef } from react; import { Univer } from univerjs/core; import { defaultTheme } from univerjs/design; import { SheetsUniver } from univerjs/preset-sheets; function SheetApp() { const containerRef useRefHTMLDivElement(null); useEffect(() { if (!containerRef.current) return; const univer new Univer(); univer.registerPlugin(SheetsUniver); univer.createUnit(SheetsUniver, { container: containerRef.current, theme: defaultTheme, }); // 组件卸载时销毁实例避免内存泄漏 return () { univer.dispose(); }; }, []); return ( div ref{containerRef} style{{ width: 100%, height: calc(100vh - 50px) }} / ); }univer.dispose()这一步是关键。我见过太多同事刚接 Univer 时只顾着创建、忘了清理结果在 React StrictMode 下“创建-销毁-再创建”之后第二遍表格直接白屏。原因就是旧的实例没有正确释放Canvas 资源还占着。如果你用的是 Vue思路完全一致在onMounted里初始化在onBeforeUnmount里调用univer.dispose()。只要抓住这两条生命周期框架差异基本不存在。4. 踩坑实录Univer 常见问题与排查方法4.1 白屏或表格高度异常这是接入初期遇到最多的问题。Univer 渲染需要容器具备真实的宽度和高度。如果你把容器挂在了一个display: none的弹窗里或者父级高度是 auto初始化时容器高度为 0表格自然什么都画不出来。排查建议三步走先在初始化之前用console.log(containerRef.current.clientHeight)打印容器高度确认不是 0如果容器在弹窗/折叠面板里等待弹窗打开后再初始化或先用固定尺寸容器验证检查全局 CSS 是否重置了 canvas 相关样式比如img { display: block }之类的规则可能影响内联元素。4.2 公式不生效显示成了普通文本碰到公式变成纯文本原因大概率出在写入方式上。Univer 对公式和普通文本是有明确区分的写入字符串时以等号开头的字符串才会被识别为公式。如果你把SUM(A1:A2)经过 JSON 解析或转义处理等号前面多了不可见字符公式就会失效。另一个常见原因是公式区域引用用错了写法。中英文冒号、括号混用也会导致公式解析失败。遇到这类问题建议先写一个最简单的11再逐步加引用不要一上来就堆复杂函数。这样能快速定位是引擎问题还是写法问题。4.3 自定义样式时界面被“打回原形”Univer 使用一套基于 CSS 变量的设计系统。很多人接入后喜欢在项目里加一条全局样式比如* { box-sizing: border-box }或者改掉所有 canvas 的outline但这类规则会波及 Univer 的 UI。我这里给一个建议不要直接用通配符去覆盖给 Univer 的容器单独加一个作用域类名再在类名下调整内部元素的样式。Univer 有自己的主题变量比如--primary-color、--background-color等修改这些变量比粗暴覆写具体元素更安全。真想换品牌色优先去设计系统层面做而不是写!important。4.4 大数据量场景下的性能优化Univer 的 Canvas 渲染已经解决了一部分性能问题但不代表你可以往里面塞 10 万行原始数据。我自己实测下来初始化时如果一次性写入超大数据集主要瓶颈反而在数据构建和命令记录。有一个现象值得注意创建矩形的写入操作会拆成多次命令如果循环逐格写入几千次 setValue 的操作栈会把性能打没。更合理的做法是按区域批量写入。Univer 提供了getRange().setValues()这类批量方法传入二维数组即可。你可以把数据组好一次性发出去减少命令数量。另外初始化过程可以关掉部分用于展示的功能比如公式联动计算等数据挂载完成后再整体重算。4.5 快捷键与中文输入法冲突最后分享一个比较隐蔽的坑。在中文输入法下用户敲击数字或字母时会经过一个“组词”阶段。Univer 在编辑态里处理键盘事件如果组件只顾着听原生 keydown很容易把输入法中间态的按键当成真正的编辑操作导致用户打着打着字就被吞了。目前 Univer 针对编辑状态做了组合键处理但仍然建议你在大规模投入前安排一轮真机中文输入法测试包括搜狗、微软拼音、Mac 自带输入法。如果发现个别场景和预期不符优先去 GitHub Issues 里搜输入法相关 issue而不是自己魔改编辑内核。5. 选型参考Univer 适合什么场景不适合什么场景5.1 和 Luckysheet、SpreadJS、OnlyOffice 的横向对比我把几个常用方案放在一起方便大家做选型方案开源/授权渲染方式定制成本协同能力适用场景UniverApache 2.0 全开源Canvas较低插件机制完善数据层预留路线图内有规划深度业务集成、SaaS 产品Luckysheet开源社区迭代趋缓DOM一般二次开发空间受限方案不成熟旧项目维护、轻量使用SpreadJS商业授权Canvas中等有成熟 API有配套方案预算充足、要求稳定OnlyOfficeAGPL/商业双授权Canvas较高整体是一套完整Office天然支持需要完整 Office 协作场景这里多说一句授权问题。OnlyOffice 的社区版是 AGPL 协议如果你的产品是内部使用AGPL 的约束相对可控但如果你的产品要对外部开放、提供 SaaS 服务AGPL 会带来很强的传染性风险需要非常谨慎。相比之下 Univer 的 Apache 2.0 对商业集成友好得多主流的闭源商业项目集成它几乎没有法律包袱。5.2 什么情况下我说“别急着选 Univer”Univer 虽然好但也不是万金油。如果你遇到下面这几种情况我反而建议你再想想你需要的是“完整 Office 兼容”。Univer 的表格能力确实在迅速迭代但目前它对复杂公式、图表、透视表的覆盖还没到和商业产品正面硬碰的阶段。如果你的客户明确要求“打开一个复杂 Excel 里所有元素都不能乱”还是得认真做一轮兼容性测试。你的产品需要一个“桌面级”的前端体验。有些场景要求表格像本地 Office 一样运行快捷键、右键菜单、打印、批注、审阅这些都要求高完成度Univer 目前还是 Web 为先的思路桌面端体验不完全对齐。团队缺乏 TypeScript 基础。Univer 的二次开发基本都得碰 TS如果你团队一直在用 Java 写页面、对前端工程化不熟上手成本会被放大。插件体系越灵活对开发者素质的要求就越高。5.3 我的建议路径如果你没有历史包袱新项目里确定要上在线表格我的建议路径是先在官方在线 Demo 把核心操作过一遍确认交互能接受再按第 3 节的步骤跑一个最小集成原型然后把你自己最复杂的几个 Excel 文件导入进去重点看公式、格式、合并单元格、数据验证这几个环节最后再做一次输入法、性能、异常操作的实测。整个过程大概两三天就能得出结论比拍脑袋买一套商业组件再翻车要稳妥得多。我在技术选型上吃过不少亏最大的体会是开源项目好不好不是看它的 Star 数和官方宣传而是挖两层之后看它给你留了多少扩展入口。Univer 至少在这个维度给了我足够的信心。最后再分享一个小技巧实践里最让我舒服的一个点是Univer 的初始化完全可以不用界面只注册数据相关的插件把它当成一个纯数据引擎用。也就是说你可以在自己的 UI 里画一个简易表格把数据同步给 Univer让它帮你算公式、做依赖管理和快照恢复。这种“无头表格”的玩法在对付复杂业务规则时非常有用。我在一个配置化平台里就是只用 Univer 管理“单元格之间的公式依赖”界面完全是自己的。算下来公式引擎的判断和联动刷新花费的调试时间比我自己写一套规则解析器省了不止一个量级。如果你接手 Univer 项目建议也可以照这个思路玩一次不为别的就为了体会一下“数据层和渲染层解耦”到底能带来什么样的自由。踩过几次坑之后我对这类项目的判断标准变得非常统一能不能让我只取我所需要的部分不被全家桶绑架。Univer 在这方面是真的做到了。
企业数字化 ERP 产品动态
相关推荐
paperclip:轻量级桌面信息暂存工具,提升碎片信息处理效率 1. 从“paperclip”这个词说起:一个被低估的桌面效率入口第一次看到“paperclip”这个项目标题,很多人脑子里蹦出来的可能是那个经典的曲别针图标,或者早年办公软件里那个总爱跳出来打断你写字的助手。但在我实际接触并把它用起来之后&#x… · 2026/11/3 15:25:58
LangChain+ChromaDB 崩溃排查:OOM、段错误与工程化配置 内核掉了。不是抛异常,不是栈回溯,是 Jupyter 的 kernel 直接变灰,或者终端里的 python 进程凭空消失,只留下一行Killed或者什么都没有。我第一次在 LangChain 里挂上 ChromaDB 做本地知识库时,就是这么被教育了一整晚… · 2026/11/3 15:24:57
Sigma-Delta ADC噪声整形:从1位量化到15位精度的系统设计 简介:《高性能sigma-delta ADC的设计与研究》PDF为西安电子科技大学微电子学与固体电子学博士学位论文,面向微电子、模拟集成电路及ADC设计方向的研究生、工程师与科研人员,聚焦过采样和噪声整形机制下的高精度模数转换器系统设计与实现。包内… · 2026/11/3 15:24:57
零基础18个月成为白帽黑客:网络安全实战进阶指南 1. 网络安全行业现状与学习价值2026年的网络安全领域正在经历前所未有的变革。随着量子计算技术的商用化进程加速,传统加密体系面临重构;物联网设备数量突破千亿级别,攻击面呈指数级扩张;AI驱动的自动化攻击工具让防御方压力倍增。… · 2026/11/3 16:05:13
Nano Banana边缘设备语言包升级与工作流引擎适配指南 我无法基于当前输入生成符合要求的博文。 原因如下: 输入中 项目正文为空 ( 项目正文: [通常比较零散、不完整的原始描述,可是任意领域内容] 部分完全未提供); 关键词仅列出三个泛称 (Google Flow… · 2026/11/3 16:05:13
Java 1.8 HashMap优化:哈希扰动、扩容与红黑树全解析 HashMap 这个类,写过几年业务代码的人基本天天在用,但真被问到 Java 1.8 对它做了哪些优化,很多人的第一反应还是那句背了无数遍的结论:加了红黑树。这话对,但太省事,甚至在不少场景下会把人带偏——因为真… · 2026/11/3 16:05:13
Java数字人工作台:DSH+A2A+XxxBuddy实战架构 1. 项目概述:这不是玩具,是能跑在真实环境里的Java数字人工作台“激动,睡不着!”——这句开场白不是营销话术,是我凌晨三点盯着终端里那个刚完成语音唤醒、能实时驱动唇形同步、还能调用本地知识库回答“Spring Bean生… · 2026/11/3 16:05:13
Git提交错误修正指南:从原理到实践 1. 问题场景:Git提交错误的常见困境 每个开发者都遇到过这种情况:刚敲完git commit -m "..."就发现提交了不该提交的文件,或者把敏感信息误推到了远程仓库。上周我就犯了个低级错误——把包含数据库密码的配置文件提交到了公司项目… · 2026/11/3 16:05:13
AZ-204 认证考试核心考点与答题技巧:从虚拟机迁移到 Key Vault 权限 简介:面向备考微软 AZ-204 Azure 开发人员认证的考生,这份 2022 年题库以 PDF 形式整理了认证考试的核心考点与真题演练,覆盖 Azure 资源管理、虚拟机迁移、ARM 模板部署、Key Vault 安全存储密码、AKS 容器部署、监控与身份访问管理等关键领… · 2026/11/3 16:04:11
双极步进电机控制实战: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