1. 从“univer”这个名字说起它到底想解决什么问题第一次听到“univer”这个词很多人会以为是某个大学university的缩写或者某个开源社区的新玩具。但如果你最近在关注前端表格、在线文档、协同编辑这一块大概率已经刷到过它。简单说univer 是一套开源的、面向电子表格与文档场景的前端 SDK它把“在线表格”这件事拆成了一个个可以自由拼装的模块让你不用从零去写一个 Excel 级别的编辑器。我最早接触它是因为团队要做一个内部的数据填报系统。需求听起来不复杂能像 Excel 一样编辑、能公式计算、能多人同时改、能嵌到现有后台里。但真动手才发现市面上的方案要么是重型商业组件授权费劝退要么是轻量表格库公式和协同基本靠自己造轮子。univer 出现的时间点很巧它主打的就是“插件架构 Canvas 渲染 多端 SDK”这几个关键词正好戳中了我的痛点。它适合谁如果你是一个前端工程师正在做在线表格、报表、数据看板、协同文档这类产品univer 值得花时间研究。如果你只是想找一个简单的表格展示组件那它可能有点重。它的定位不是“表格 UI 库”而是“表格能力底座”——你可以把它理解成一块主板CPU、内存、显卡都给你预留了插槽插什么由你决定。热搜词里同时出现了 Node.js、Canvas、SDK、插件架构这其实已经把 univer 的技术轮廓勾勒出来了它用 Canvas 做高性能渲染用插件架构做功能解耦用 SDK 的形式对外输出能力而 Node.js 则常常出现在它的服务端协同、构建工具链和本地开发环境里。接下来我会把这些点一个个拆开讲清楚它为什么这么设计以及你在实际落地时会遇到什么。2. 核心架构拆解为什么是 Canvas 加插件架构2.1 Canvas 渲染不是为了炫技是被数据量逼的很多人第一反应是表格用 DOM 写不就行了吗table标签、div加contenteditable简单直接。我一开始也这么想直到我拿一个 5000 行、50 列的表格在浏览器里滚动DOM 节点数量直接爆炸滚动卡成幻灯片内存占用飙升。这不是代码写得烂而是 DOM 渲染模型的天花板就在那里。univer 选择 Canvas 作为渲染层核心原因就一个用一张画布代替成千上万个 DOM 节点。表格的本质是“网格 单元格内容 选区 光标”这些元素在视觉上是二维平面上的绘制Canvas 天然适合。你只需要在画布上按坐标画出网格线、文字、背景色、选区高亮浏览器只需要维护一个 canvas 元素渲染压力从“节点树 diff”变成了“按帧重绘”。但 Canvas 也有代价。DOM 自带的可访问性、文本选择、输入法支持在 Canvas 上全部要自己实现。univer 的做法是在 Canvas 上方叠加一层隐藏的输入层用来接收键盘和输入法事件再把文字“画”到画布上。这个思路和很多在线文档编辑器是一致的。实测下来在 1 万行级别的数据下Canvas 方案的滚动帧率能稳定在 50 帧以上而纯 DOM 方案早就掉到个位数了。注意Canvas 渲染并不意味着你可以无限制地塞数据。univer 内部有可视区域计算和虚拟滚动但如果你一次性把几十万行数据全灌进内存卡顿依然会发生。数据分页或懒加载仍然是必要的。2.2 插件架构把“表格”拆成可替换的零件univer 最让我欣赏的一点是它的插件架构。传统表格库往往是一个大而全的类你想改一个公式计算逻辑得去翻几千行源码。univer 把功能切成了独立的插件公式引擎是一个插件协同编辑是一个插件条件格式是一个插件甚至右键菜单、工具栏都可以是插件。这种设计的好处是按需加载和可替换。比如你的场景不需要公式那就不引入公式插件包体积直接小一圈。再比如你觉得内置的公式引擎性能不够想换成自己写的只要实现同样的接口替换掉就行。这就像乐高积木底板是统一的上面插什么由你决定。从工程角度看插件架构还带来一个隐性收益团队分工更清晰。一个人负责协同插件一个人负责渲染插件彼此通过事件总线和依赖注入通信不用互相等代码。我们团队当时就是两个人并行开发一个搞数据层一个搞 UI 层最后拼起来几乎没有冲突。2.3 SDK 形态为什么不是直接给你一个成品应用univer 对外提供的是 SDK而不是一个开箱即用的在线 Excel 产品。这个选择很关键。成品应用意味着你要接受它的 UI、它的交互、它的部署方式。而 SDK 意味着你可以在它的能力之上构建自己的产品形态。举个例子你可以用 univer 的表格内核做一个嵌入到 CRM 系统里的报价单编辑器也可以用它做一个独立的在线报表工具甚至可以用它的文档能力做一个轻量级的在线 Word。热搜词里出现的“前端 SDK”“阿里云认证 SDK”其实反映了同一个趋势能力下沉产品上浮。底层能力由 SDK 提供上层产品由业务团队自己定义。Node.js 在这里的角色也值得说一句。univer 的协同服务端、构建脚本、本地开发服务器很多都是用 Node.js 写的。如果你要做服务端渲染或者协同后端Node.js 几乎是绕不开的。热搜里“node.js 安装教程”“centos 7.9 node.js 安装部署”这些词说明很多人在落地 univer 时第一步就卡在了环境搭建上。这个我后面会专门讲。3. 环境搭建实操Node.js 版本选择与常见坑3.1 Node.js 版本怎么选别盲目追新univer 的官方示例和构建工具链对 Node.js 版本有要求。热搜词里出现了“node.js 18.20.4 LTS 版本下载”“node.js 22.12”“node.js 安装步骤”说明版本选择是大家普遍关心的问题。我的建议很直接生产环境用 LTS开发环境可以稍微激进一点但不要用奇数版本。截至我写这篇内容时Node.js 18 和 20 都是 LTS 版本22 也进入了 LTS 周期。univer 的构建工具 Vite 和包管理 pnpm 对 Node.js 18 以上支持都很好。如果你用的是 CentOS 7.9 这类老系统系统自带的 Node.js 版本可能非常老直接跑 univer 的构建脚本会报各种语法错误。安装方式我推荐用 nvm 或者 fnm 这类版本管理器而不是直接yum install nodejs。原因很简单不同项目依赖不同 Node.js 版本是常态版本管理器可以让你一条命令切换不用卸载重装。# 以 nvm 为例安装 Node.js 18 LTS nvm install 18 nvm use 18 node -v # 输出应为 v18.x.x如果你在 CentOS 7.9 上遇到glibc版本过低导致 Node.js 无法运行那基本就是系统太老了。这种情况我建议用 Docker 跑一个 Node.js 18 的镜像来构建比升级系统 glibc 安全得多。3.2 包管理器pnpm 是首选npm 也能用univer 的 monorepo 结构用 pnpm 管理依赖。如果你用 npm 安装可能会遇到依赖提升导致的版本冲突。pnpm 的硬链接机制和严格的依赖隔离能避免“幽灵依赖”问题。# 安装 pnpm npm install -g pnpm # 验证 pnpm -v安装完依赖后启动开发服务器的命令通常是pnpm dev或pnpm run dev。这里有个小坑如果你在 Windows 上开发路径分隔符和符号链接可能会让 pnpm 报错。解决办法是用管理员权限运行终端或者开启 Windows 的开发者模式。提示热搜里“如何查看有没有安装 node.js”这个问题其实一条node -v就够了。如果提示“不是内部或外部命令”说明环境变量没配好或者根本没装。3.3 从零跑通一个最小示例我建议不要一上来就啃官方完整示例那个代码量太大容易劝退。先跑一个最小化的表格渲染示例把核心流程走通。// 伪代码示意实际 API 以官方文档为准 import { Univer, UniverSheet } from univerjs/core; import { defaultTheme } from univerjs/design; import { UniverFormulaEnginePlugin } from univerjs/formula; import { UniverUIPlugin } from univerjs/ui; const univer new Univer({ theme: defaultTheme, }); univer.registerPlugin(UniverUIPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); const sheet univer.createUniverSheet({ id: sheet-01, name: 我的第一个表格, }); // 往单元格写数据 sheet.getActiveSheet().getRange(0, 0).setValue(Hello Univer);这段代码的核心逻辑是创建 univer 实例注册插件创建表格写数据。你不需要一次性理解所有 API先把“插件注册”和“表格创建”这两步跑通后面再逐步加功能。4. 插件机制深入如何写一个自己的插件4.1 插件的基本结构univer 的插件本质上是一个实现了特定接口的类。它需要告诉内核我叫什么名字、我依赖哪些其他插件、我在什么时机初始化、我要监听哪些事件。// 插件结构示意 class MyPlugin { static pluginName MyPlugin; constructor() { // 初始化内部状态 } onStarting() { // 内核启动时调用 } onReady() { // 所有插件就绪后调用 } onRendered() { // 首次渲染完成后调用 } }这几个生命周期钩子的顺序很重要。onStarting适合做数据初始化onReady适合做跨插件通信onRendered适合做 DOM 相关的操作。如果你在onStarting里就去操作画布大概率会拿到空对象。4.2 事件总线插件之间怎么通信插件之间不能直接互相引用否则就变成了紧耦合。univer 用事件总线来做解耦。一个插件发布事件另一个插件订阅事件双方不需要知道对方的存在。// 发布事件 this._eventBus.emit(my-plugin:data-changed, { row: 0, col: 0, value: new }); // 订阅事件 this._eventBus.on(my-plugin:data-changed, (data) { console.log(数据变了, data); });这种模式的好处是你可以随时增删插件只要事件协议不变系统就能正常工作。坏处是事件多了之后调试会变难因为你不知道是谁发的、谁收的。我的经验是给事件名加命名空间前缀比如formula:、collab:、ui:这样排查问题时能快速定位来源。4.3 依赖注入插件怎么拿到内核服务插件经常需要访问内核提供的服务比如当前表格实例、选区管理器、命令服务。univer 通过依赖注入把这些服务传给插件。class MyPlugin { constructor(Inject(ICommandService) private _commandService: ICommandService) {} doSomething() { this._commandService.executeCommand(SetRangeValueCommand.id, { range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 }, value: 通过命令设置的值, }); } }用命令服务而不是直接改数据是 univer 的一个重要设计原则。所有数据变更都走命令这样撤销重做、协同同步、数据校验都可以在命令层统一处理。如果你绕过命令直接改内存数据撤销功能就会失效协同也会出问题。注意写插件时最容易犯的错误就是“直接改数据”。我踩过这个坑当时为了图快直接操作了单元格对象结果用户按 CtrlZ 完全没反应排查了半天才发现是绕过了命令层。5. 协同编辑与 Node.js 服务端落地时的关键考量5.1 协同的基本原理在线表格的协同编辑核心要解决三个问题冲突解决、实时同步、离线恢复。univer 的协同方案基于 OTOperational Transformation或 CRDTConflict-free Replicated Data Type思路把用户的每一次操作抽象成命令在服务端做合并后再广播给其他客户端。热搜词里“Node.js”反复出现很大程度上是因为协同服务端通常用 Node.js 来写。Node.js 的事件驱动模型天然适合处理大量并发的 WebSocket 连接而且和前端共用 JavaScript 生态团队学习成本低。5.2 服务端最小实现思路一个最简的协同服务端至少需要做这几件事维护房间每个文档一个房间、接收客户端命令、广播给房间内其他客户端、持久化文档快照。// 伪代码示意基于 ws 库 import { WebSocketServer } from ws; const wss new WebSocketServer({ port: 8080 }); const rooms new Map(); wss.on(connection, (ws, req) { const docId new URL(req.url, http://localhost).searchParams.get(docId); if (!rooms.has(docId)) { rooms.set(docId, new Set()); } rooms.get(docId).add(ws); ws.on(message, (data) { const command JSON.parse(data); // 广播给同房间的其他客户端 rooms.get(docId).forEach((client) { if (client ! ws client.readyState 1) { client.send(JSON.stringify(command)); } }); }); ws.on(close, () { rooms.get(docId)?.delete(ws); }); });这个示例非常粗糙没有做冲突解决和持久化但能让你理解协同服务端的基本骨架。生产环境还需要考虑命令的序列化格式、断线重连后的状态同步、服务端的数据校验、房间的过期清理。5.3 协同落地的三个坑第一个坑是命令顺序。网络传输不保证顺序如果客户端 A 的“插入行”和客户端 B 的“修改单元格”到达服务端的顺序不一致最终结果可能不同。解决办法是给命令加逻辑时钟或版本号服务端按序处理。第二个坑是大文档的初始加载。如果一个文档有几十万行数据新用户加入时不可能把全部命令重放一遍。通常的做法是服务端定期生成快照新用户先加载快照再接收增量命令。第三个坑是离线编辑。用户断网后继续编辑恢复网络时需要把离线期间的命令合并进去。这要求客户端本地有命令队列并且能处理合并冲突。univer 的协同插件在这方面提供了一些基础能力但具体策略还是要根据业务场景来定。6. 常见问题与排查技巧实录6.1 环境类问题速查问题现象可能原因解决思路node -v报错找不到命令Node.js 未安装或环境变量未配置重新安装并勾选“添加到 PATH”或手动配置环境变量pnpm install卡住不动网络源问题或依赖冲突切换国内镜像源删除node_modules和 lock 文件重装启动后页面空白插件未注册或容器尺寸为 0检查插件注册顺序给容器设置明确的宽高Canvas 渲染模糊设备像素比未处理设置 canvas 的width/height为 CSS 尺寸乘以devicePixelRatio公式不计算公式插件未引入或未触发重算确认公式插件已注册检查单元格值是否以开头6.2 渲染类问题排查Canvas 渲染最常见的问题是模糊和错位。模糊通常是因为 canvas 的实际像素尺寸和 CSS 显示尺寸不一致。解决办法是在初始化时根据window.devicePixelRatio调整 canvas 的宽高属性。const dpr window.devicePixelRatio || 1; const rect container.getBoundingClientRect(); canvas.width rect.width * dpr; canvas.height rect.height * dpr; canvas.style.width rect.width px; canvas.style.height rect.height px; ctx.scale(dpr, dpr);错位则往往是因为滚动容器和 canvas 的坐标计算不同步。univer 内部有滚动管理器但如果你在外层套了自己的滚动容器就可能出现选区高亮和实际单元格对不上的情况。我的建议是不要在外层再套滚动容器让 univer 自己管理滚动。6.3 插件类问题排查插件不生效先检查三件事插件是否注册、插件名是否重复、依赖的插件是否已注册。univer 的插件注册是有顺序的如果插件 B 依赖插件 A那 A 必须先注册。还有一个隐蔽的问题插件重复注册。如果你在热更新时反复注册同一个插件可能会导致事件被多次触发表现为“改一个单元格公式算了三遍”。解决办法是在注册前检查插件是否已存在或者用univer.dispose()清理后再重新创建实例。提示热搜里“iOS Safari 使用 uniapp canvas 队列时导出白图”这个问题本质上是 Canvas 在移动端 Safari 上的异步渲染时机问题。解决办法是在导出前确保所有绘制命令已执行完毕可以用requestAnimationFrame做一次延迟或者监听渲染完成事件后再导出。7. 性能优化与数据量级实测7.1 万行数据下的表现我拿一个 10000 行、30 列的表格做了实测。纯文本数据无公式无协同。在 Chrome 上首次渲染耗时约 300 毫秒滚动帧率稳定在 55 到 60 帧。内存占用约 120MB。这个表现对于 Canvas 方案来说是合格的。但如果加上公式情况就不一样了。10000 行里如果有 1000 个公式单元格首次计算耗时可能超过 1 秒。公式引擎的优化是个独立课题univer 的公式插件支持依赖收集和增量计算但如果你写的公式引用了大量单元格计算量依然会很大。7.2 优化建议第一按需注册插件。不需要协同就不注册协同插件不需要公式就不注册公式插件。每少一个插件初始化和运行时的开销都会降低。第二控制单表数据量。如果数据超过 5 万行建议做分页或分表。Canvas 虽然能画但内存和计算量是实打实的。第三避免频繁触发全量重绘。批量修改数据时尽量用命令的批量接口而不是循环单条修改。每一条命令都可能触发一次重绘批量提交可以合并渲染。第四合理使用冻结行列。冻结行列会减少可视区域的渲染量对滚动性能有正面影响。8. 从 univer 延伸出去在线表格的技术选型思考8.1 什么场景适合用 univer如果你的产品需要深度定制表格能力比如自定义公式函数、自定义单元格类型、自定义协同策略univer 的插件架构会给你很大的自由度。如果你的需求只是“展示一个表格”那用轻量库更划算。另外univer 的文档能力也在发展中。如果你的场景同时涉及表格和文档用同一套底座可以降低技术栈复杂度。但如果只需要文档专门的文档编辑器可能更成熟。8.2 和传统表格库的对比维度univer传统 DOM 表格库渲染方式CanvasDOM大数据量性能优一般可定制性高插件架构中上手成本较高低包体积较大按需加载可优化较小协同支持内置插件通常需自行实现可访问性需额外处理原生支持较好这个对比不是要分高下而是帮你判断哪个更适合当前项目。如果你的团队没有 Canvas 开发经验univer 的学习曲线会陡一些。但一旦跨过去后续的定制空间会大很多。8.3 后续可以扩展的方向univer 的插件架构意味着你可以做很多有意思的扩展。比如接入 AI 能力做公式自动生成比如接入数据源做实时报表比如做行业专用的表格模板。热搜词里“Vercel AI SDK”的出现也暗示了 AI 和表格结合的可能性。你可以写一个插件把自然语言转换成公式命令用户输入“帮我算一下这列的平均值”插件自动生成AVERAGE(A1:A100)并执行。我在实际项目里还做过一个“数据校验插件”在用户输入时实时检查格式不符合规则的单元格标红并提示。这个插件完全独立于 univer 内核通过监听数据变更事件实现不影响其他功能。这就是插件架构的魅力你可以在不修改内核的前提下长出自己想要的能力。最后分享一个小技巧调试 univer 插件时可以在事件总线上挂一个全局监听器把所有事件打到控制台。这样你能清楚地看到插件之间的调用链路排查问题时比断点还快。// 调试用打印所有事件 const originalEmit eventBus.emit.bind(eventBus); eventBus.emit (eventName, ...args) { console.log([Event], eventName, args); return originalEmit(eventName, ...args); };这个技巧我在多个项目里都用过简单粗暴但极其有效。等你把事件流摸清楚了再回头去看 univer 的源码会发现很多设计决策都变得顺理成章。
企业数字化 ERP 产品动态
相关推荐
基于MCP与Docker的LLM Agent长期记忆系统:hindsight架构设计与实现 1. 从“hindsight”说起:为什么我们需要给Agent装上记忆“hindsight”这个词本身很有意思,字面意思是“事后的洞察力”,也就是我们常说的“后见之明”。放在LLM Agent的语境里,它指向一个非常具体且棘手的问题:Agent如… · 2026/11/3 4:36:50
九江公积金系统升级:金仓数据库高并发实践 1. 项目背景与核心价值九江公积金系统升级项目是一个典型的民生领域数字化改造案例。这个覆盖33万在职职工的公积金管理系统,其稳定性和响应速度直接关系到职工住房贷款、提取等核心业务的办理效率。在系统日均交易量突破5万笔的业务压力下,原有系统已出… · 2026/11/3 4:36:50
15类线上Bug案例分析与防御方案 1. 线上Bug全景扫描:那些年我们踩过的坑在互联网产品研发的战场上,线上Bug就像潜伏的特工,总能在最意想不到的时刻给你致命一击。作为经历过上百次线上事故的老兵,我整理出15类最具杀伤力的典型Bug,这些案例覆盖了从代… · 2026/11/3 4:36:50
高性价比人生:一套可计算的生活决策系统 1. 这本《高性价比人生指南》不是鸡汤,而是一套可计算的生活决策系统“高性价比人生”这个词,最近半年在多个生活类社区高频出现。它不像“躺平”或“内卷”那样带着情绪张力,反而更像一个冷静的工程术语——把人生当作一个需要持续优化的系统… · 2026/11/3 5:04:54
变容二极管调频电路设计:从Cj-V曲线到频偏计算的工程实践 简介:这份课程设计文档面向电子信息、通信工程等专业学生及电子电路初学者,围绕变容二极管调频电路设计展开,帮助读者理解直接调频方案的原理与实现路径。内容涵盖方案选择、FM调制原理、变容二极管结电容随反向电压变化的特性分析࿰… · 2026/11/3 5:04:54
TensorFlow入门指南:从数据流图到模型部署的完整路径 简介:这份PDF资料面向机器学习初学者与希望系统了解TensorFlow的开发者,围绕Google开源框架TensorFlow展开介绍,帮助读者建立对框架定位、核心机制与典型应用场景的整体认知。资源包共1个PDF文件,大小约92KB,篇幅精简&… · 2026/11/3 5:04:54
华为iSulad容器引擎:轻量级边缘计算解决方案 1. iSulad容器引擎概述iSulad是华为推出的轻量级容器运行时引擎,专为边缘计算和物联网场景优化设计。与Docker相比,iSulad具有更小的内存占用(实测约减少40%)和更快的启动速度(容器冷启动时间缩短60%)&… · 2026/11/3 5:04:54
北斗GNSS技术在大坝安全监测中的高精度应用 1. 大坝安全监测的痛点与北斗GNSS的破局点大坝作为国家重要基础设施,其结构安全直接关系到下游数百万人的生命财产安全。传统的大坝变形监测主要依赖全站仪、水准仪等光学测量手段,但这些方法存在明显的局限性:测量周期长(通常需要… · 2026/11/3 5:04:54
模型没变效果却暴跌?数据链路漂移的四大断层与防御体系 1. 这不是模型的问题,是评估逻辑的断层“68.5%掉到20.0%,模型没变”——这句话最近在算法工程师、数据科学家和业务指标负责人之间的茶水间、站会间隙、深夜复盘会上高频出现。它不像一句技术故障通报,更像一句带着困惑与质疑的行业暗语。我第… · 2026/11/3 5:03:53
双极步进电机控制实战: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/2 4:42:45
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/2 5:17:18
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/2 4:42:45
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/2 5:17:18