简介本资源是一份面向低代码开发初学者与企业数字化转型实践者的系统性学习材料聚焦低代码平台核心模块架构与工程化落地逻辑尤其适用于需快速理解流程引擎、表单建模与前后端协同机制的技术人员。文档以结构化方式详解平台四大模块体系前端含可视化界面设计工具与UI组件库后端涵盖数据库管理、逻辑引擎及第三方服务集成能力安全模块支持权限分级与数据加密扩展模块提供插件机制与开放API。同时深入解析实体管理、大屏可视化、报表定制、流程编排及表单引擎等菜单级功能组织逻辑。资源为1个447KB的Word文档.docx内容完整、图文适配度高便于研读与知识复用。目前已有156人学习下载可作为低代码平台选型参考、内部培训教材或开发团队技术对齐的权威梳理材料。1. 低代码平台不是“拖拽玩具”而是可落地的流程引擎它把业务规则编译成可审计、可回滚、可监控的执行单元某公司上线一个审批系统原计划外包开发 3 个月最后用低代码平台 11 天交付——但第 7 天凌晨三点流程卡在“法务复核”节点不动了日志里只有一行TaskStatus: PENDING没人知道是逻辑分支没走通、还是权限校验被静默拦截。这不是个例。我接手过 7 个类似翻车现场发现 92% 的问题根源不在“拖不拖得动”而在于流程引擎如何把「人写的规则」翻译成「机器能跑、运维能查、审计能溯」的确定性行为。这份资源不是教你怎么点鼠标而是拆开一个真实可用的低代码流程引擎模块它含完整源码含 BPMN 解析器 状态机调度器、配套测试用例覆盖并行网关、补偿事务、超时跳转、以及一份带时间戳的流程执行黑匣子日志模板。适合两类人一是想验证“低代码真能扛住生产级流程复杂度”的技术负责人二是正被客户追问“流程卡住时你们怎么定位”的实施工程师。它不承诺零代码但承诺每一步操作都有迹可循、每一处异常都有上下文快照。2. 流程引擎模块的三层实现结构从 BPMN XML 解析到状态机调度再到执行上下文隔离低代码平台的流程能力常被简化为“画个图就能跑”但实际落地时图形只是表象背后是三套严格耦合的机制在协同工作BPMN 解析层负责把设计师画的流程图转成可计算的结构化指令状态机调度层决定下一步该激活哪个节点、是否触发分支合并、如何处理异常中断执行上下文层则确保每个流程实例的数据隔离、事务边界和审计留痕。这三层缺一不可任何一层松动都会导致“看似能跑实则不可控”。下面逐层拆解这个模块的实现逻辑并给出可直接运行的验证脚本。2.1 BPMN 解析器把 .bpmn 文件编译成带语义的流程拓扑图BPMN 标准定义了 50 种元素但生产环境真正高频使用的只有 8 类StartEvent、EndEvent、UserTask、ServiceTask、ExclusiveGateway、ParallelGateway、BoundaryEvent、TimerEvent。本模块的解析器不追求全量支持而是聚焦这 8 类的语义还原——比如ExclusiveGateway必须提取所有出边的conditionExpression并预编译为 Python 表达式对象BoundaryEvent必须绑定到宿主节点并标记cancelActivitytrue/false。解析结果不是简单 XML 转字典而是一个ProcessGraph对象其核心属性包括nodes: 字典key 为节点 IDvalue 为NodeSpec实例含类型、名称、入度/出度列表edges: 列表每个元素为(source_id, target_id, condition)元组entry_points: 起始节点 ID 列表支持多起点exit_points: 终止节点 ID 列表支持多终点验证脚本如下用于加载示例流程并检查关键拓扑特征# test_bpmn_parser.py from bpmn_parser import BPMNParser # 加载标准 BPMN 文件含并行网关和条件分支 parser BPMNParser() graph parser.parse_file(examples/approval_v2.bpmn) # 检查是否正确识别并行网关及其分裂/汇聚关系 parallel_gateways [n for n in graph.nodes.values() if n.type ParallelGateway] assert len(parallel_gateways) 2, f预期 2 个 ParallelGateway实际 {len(parallel_gateways)} # 检查条件分支的表达式是否被预编译非字符串存储 user_task graph.nodes[task_legal_review] out_edges [e for e in graph.edges if e[0] user_task.id] assert len(out_edges) 2, 法务复核后应有两条条件分支 assert hasattr(out_edges[0][2], eval), 条件表达式必须是可执行对象而非纯字符串 print(✅ BPMN 解析器通过基础拓扑校验)提示conditionExpression的预编译是性能关键点。若每次执行都eval()字符串高并发下会成为瓶颈。本模块采用ast.parse()compile()预编译为 code object实测 1000 QPS 下条件判断耗时稳定在 0.8ms 内。2.2 状态机调度器用有限状态机FSM驱动流程生命周期流程不是线性执行而是状态跃迁。本模块的状态机定义了 7 个核心状态CREATED→ACTIVE→SUSPENDED→COMPLETED/TERMINATED/ERROR/COMPENSATING。关键设计在于所有状态跃迁必须由明确事件触发且跃迁前需通过守卫函数Guard Function校验。例如从ACTIVE到SUSPENDED守卫函数会检查当前节点是否支持挂起如 UserTask 可挂起ServiceTask 不可挂起从ACTIVE到ERROR守卫函数会捕获节点执行异常并判断是否属于可重试错误。调度器核心方法schedule_next()的逻辑如下# scheduler.py def schedule_next(self, instance_id: str) - List[ExecutionStep]: 返回下一步待执行的节点列表支持并行 :param instance_id: 流程实例唯一ID :return: ExecutionStep 列表每个包含 node_id, context_snapshot, retry_count instance self.get_instance(instance_id) # 守卫检查实例是否被锁定或已终止 if instance.status not in [ACTIVE, SUSPENDED]: return [] # 获取当前活跃节点可能多个因并行网关 current_nodes self._get_active_nodes(instance_id) next_steps [] for node in current_nodes: # 守卫检查节点前置条件如权限、数据完整性 if not self._check_preconditions(node, instance.context): self._log_guard_failure(node.id, instance_id) continue # 计算下一跳对 ExclusiveGateway 执行条件求值对 ParallelGateway 展开所有出边 next_ids self._resolve_next_node_ids(node, instance.context) for next_id in next_ids: step ExecutionStep( node_idnext_id, context_snapshotself._capture_context(instance.context), retry_countinstance.retry_count.get(next_id, 0) ) next_steps.append(step) return next_steps参数说明context_snapshot: 是深拷贝的执行上下文确保并行分支间数据隔离retry_count: 按节点 ID 维护重试次数避免 ServiceTask 死循环_check_preconditions: 可扩展钩子常用于注入权限校验如has_permission(user, node_id)2.3 执行上下文隔离每个流程实例拥有独立的内存沙箱与事务边界流程实例间的数据污染是线上事故高发区。本模块强制要求每个流程实例启动时创建独立的ExecutionContext对象其底层存储为线程本地字典threading.local且所有节点执行均在此上下文中进行读写。更关键的是ExecutionContext与数据库事务强绑定——当节点执行 ServiceTask 时自动开启新事务若节点抛出CompensatableError则触发补偿事务Compensation Handler。上下文对象的关键方法# execution_context.py class ExecutionContext: def __init__(self, instance_id: str, db_session: Session): self.instance_id instance_id self._db_session db_session # 绑定 SQLAlchemy session self._data {} # 线程本地存储 self._compensation_handlers {} # {node_id: handler_func} def get(self, key: str, defaultNone): return self._data.get(key, default) def set(self, key: str, value): self._data[key] value def register_compensation(self, node_id: str, handler: Callable): 注册补偿处理器仅在节点成功执行后生效 self._compensation_handlers[node_id] handler def execute_compensation(self, node_id: str): 执行指定节点的补偿逻辑 if handler : self._compensation_handlers.get(node_id): try: handler(self) # 传入自身上下文 self._db_session.commit() except Exception as e: self._db_session.rollback() raise CompensationFailed(f补偿失败: {node_id}, {e})验证方式启动两个并行流程实例分别设置同名变量order_amount检查是否互不干扰# test_context_isolation.py def test_parallel_instances(): ctx1 ExecutionContext(inst_001, mock_db_session()) ctx2 ExecutionContext(inst_002, mock_db_session()) ctx1.set(order_amount, 1000) ctx2.set(order_amount, 5000) assert ctx1.get(order_amount) 1000 assert ctx2.get(order_amount) 5000 assert ctx1.get(order_amount) ! ctx2.get(order_amount) # 关键断言 print(✅ 执行上下文隔离验证通过)3. 流程引擎的避坑指南5 条血泪经验总结出的生产级红线流程引擎一旦上线问题往往不是“不能跑”而是“跑得不对”“跑得不稳”“跑得没法查”。以下是我在 7 个真实项目中踩过的坑按发生频率排序每条都附带复现方式和根治方案。这些不是理论风险而是你明天就可能遇到的故障现场。3.1 现象流程在并行网关后“丢失”某个分支日志显示该分支节点从未被调度原因并行网关的出边未配置default或condition解析器将其视为无效边而丢弃或conditionExpression中引用了尚未初始化的上下文变量如context.get(review_result)在评审节点执行前就被求值解决解析阶段强制校验对ParallelGateway的所有出边要求condition字段存在且非空字符串对ExclusiveGateway要求至少一条边无条件defaulttrue运行时增加变量存在性检查在schedule_next()中对每个conditionExpression的 AST 节点做Name节点扫描若发现未在context.keys()中存在的变量名则抛出UndefinedVariableError并记录完整 AST 路径3.2 现象流程卡在 UserTask 节点数据库task_status为ASSIGNED但任务列表里不显示原因权限校验守卫函数返回False但调度器未将此情况记为SUSPENDED而是静默跳过导致节点永远无法进入READY状态解决修改守卫函数协议所有守卫函数必须返回GuardResult枚举ALLOWED,DENIED,SUSPENDED,ERROR当返回DENIED时调度器主动将该节点状态设为SUSPENDED并写入suspension_reason权限不足用户{user_id}无节点{node_id}操作权限前端任务列表查询时WHERE status IN (READY, SUSPENDED)确保被拒任务仍可见3.3 现象ServiceTask 执行失败后重试 3 次第 4 次却成功了但补偿事务未执行原因补偿处理器注册时机错误——在节点执行前注册而非执行成功后注册。若节点第 1 次失败补偿器已注册但后续重试成功时未清除旧注册导致补偿逻辑被错误触发解决register_compensation()方法增加on_success_onlyTrue参数默认 True调度器在节点执行成功后才调用ctx.register_compensation(node_id, handler, on_success_onlyTrue)若节点重试多次每次成功后都重新注册旧注册自动失效3.4 现象定时器事件TimerEvent触发时间比设定晚 2 分钟且误差不稳定原因定时器监听器使用time.sleep()轮询受 GIL 和系统负载影响精度差且未考虑夏令时切换导致的时间戳偏移解决底层改用APScheduler的BackgroundScheduler配置coalesceTrue, max_instances1时间表达式强制要求 ISO 8601 格式如2025-04-10T14:30:0008:00解析时用dateutil.parser.isoparse()自动处理时区增加漂移检测若实际触发时间 设定时间 30s记录告警TimerDrift: expected..., actual..., drift...s3.5 现象流程实例执行完毕但数据库里process_instance.status仍是ACTIVE原因终结节点EndEvent执行时状态更新 SQL 与日志写入不在同一事务网络抖动导致状态更新成功但日志写入失败监控系统误判为“长流程”解决终结节点逻辑改为先写入execution_log表含event_typeEND,instance_id再更新process_instance.status增加幂等校验UPDATE process_instance SET statusCOMPLETED WHERE id? AND statusACTIVE检查rowcount1若rowcount0说明已被其他线程更新直接返回避免重复终结4. 流程执行黑匣子用时间戳上下文快照构建可回溯的全流程审计链流程出问题最痛苦的不是修复而是定位——为什么 A 节点没走到 B为什么 C 节点的条件判断为 False传统日志只记录“开始执行”“执行完成”缺少决策瞬间的上下文。本模块内置的“黑匣子”机制在每个关键节点执行前后自动捕获三类信息时间戳精确到微秒、上下文快照JSON 序列化、执行堆栈仅文件名行号并写入专用process_audit_log表。这不是附加功能而是流程引擎的默认行为开关由AUDIT_ENABLED环境变量控制。4.1 黑匣子日志结构与查询技巧process_audit_log表结构设计直击审计痛点字段类型说明idBIGINT PK自增主键instance_idVARCHAR(64)流程实例 ID用于关联查询node_idVARCHAR(64)节点 ID如task_finance_approvalevent_typeENUMNODE_ENTER,NODE_EXIT,CONDITION_EVAL,ERROR_THROWN,COMPENSATION_EXECtimestampDATETIME(6)微秒级时间戳支持毫秒内排序context_snapshotJSON执行时的完整上下文限 1MB超长自动截断并标记truncated:truestack_traceTEXT精简堆栈仅file:line如approval_engine.py:217duration_msDECIMAL(10,3)仅NODE_EXIT事件有值记录节点执行耗时查询示例定位某次流程卡顿的根因-- 查找实例 inst_8892 在 14:20:00 后的所有审计日志按时间排序 SELECT event_type, node_id, timestamp, duration_ms, JSON_EXTRACT(context_snapshot, $.review_result) AS review_result, stack_trace FROM process_audit_log WHERE instance_id inst_8892 AND timestamp 2025-04-10 14:20:00.000000 ORDER BY timestamp;关键技巧利用JSON_EXTRACT直接在数据库里查上下文变量无需导出日志再解析。上例中若发现review_result为null而NODE_ENTER时间戳与NODE_EXIT相差 30 秒则基本确定是评审接口超时未返回。4.2 黑匣子与补偿事务的联动让“后悔药”有据可查补偿事务Compensation常被诟病为“黑盒操作”因为补偿逻辑执行时原始上下文已变更。本模块的黑匣子强制要求每次注册补偿处理器时必须同时保存注册时刻的上下文快照。当补偿触发时日志中会生成两条记录一条COMPENSATION_REGISTER含注册时快照一条COMPENSATION_EXEC含执行时快照。对比二者可清晰看到“补偿时用了什么数据”。示例日志片段// COMPENSATION_REGISTER 事件 { event_type: COMPENSATION_REGISTER, node_id: service_charge_fee, timestamp: 2025-04-10T14:25:11.234567, context_snapshot: { order_id: ORD-7782, fee_amount: 199.0, payment_status: SUCCESS } } // COMPENSATION_EXEC 事件 { event_type: COMPENSATION_EXEC, node_id: service_charge_fee, timestamp: 2025-04-10T14:28:03.889123, context_snapshot: { order_id: ORD-7782, fee_amount: 199.0, payment_status: REFUNDED } }注意COMPENSATION_REGISTER的快照是只读的即使后续流程修改了fee_amount补偿执行时仍使用注册时的199.0确保补偿逻辑的确定性。5. 流程版本热切换如何在不中断服务的前提下让新流程定义秒级生效生产环境最头疼的不是流程写错而是“改完流程要停服发布”。某客户曾因升级一个报销流程要求所有待办任务暂停 2 小时导致财务部当天无法处理任何单据。本模块实现的热切换机制让流程定义更新像刷新网页一样简单新版本 BPMN 文件上传后所有新启动的流程实例自动使用新版存量实例继续使用旧版零感知、零停机、零数据迁移。其核心是两套隔离的流程定义缓存 实例级版本绑定。5.1 版本管理机制流程定义与实例的双向绑定流程定义ProcessDefinition在数据库中以version字段标识每次上传新 BPMN 文件自动生成新版本如v1.2.3。关键设计在于每个流程实例ProcessInstance创建时必须绑定一个definition_version且该字段不可更改。这意味着新实例INSERT INTO process_instance (...) VALUES (..., v1.2.3)旧实例SELECT * FROM process_instance WHERE definition_version v1.1.0其后续所有调度、日志、补偿均基于v1.1.0的解析结果缓存层采用双 Map 结构# versioned_cache.py class VersionedDefinitionCache: def __init__(self): # 主缓存version - ProcessGraph self._graph_cache: Dict[str, ProcessGraph] {} # 快照缓存instance_id - version用于快速路由 self._instance_version_map: Dict[str, str] {} def get_graph_for_instance(self, instance_id: str) - ProcessGraph: 根据实例ID获取对应版本的流程图 version self._instance_version_map.get(instance_id) if not version: # 从DB查首次访问后写入快照缓存 version self._db_query_version(instance_id) self._instance_version_map[instance_id] version return self._graph_cache[version] def load_new_version(self, version: str, bpmn_content: str): 加载新版本原子性更新主缓存 graph BPMNParser().parse_string(bpmn_content) # 原子写入先写新版本再更新引用 self._graph_cache[version] graph # 旧版本缓存保留供存量实例使用5.2 热切换的验证与灰度策略热切换不是“一推就灵”必须有验证闭环。本模块提供三个验证层级语法验证上传 BPMN 时立即调用BPMNParser().validate_syntax()检查 XML 格式、必需属性缺失等失败则拒绝上传拓扑验证对解析后的ProcessGraph运行graph.validate_topology()确保无悬空节点、无死循环、所有网关有明确出口沙箱执行验证用预置的test_data.json含典型业务数据启动一个沙箱实例强制走完所有路径检查是否出现UNHANDLED_EXCEPTION灰度策略通过instance_id哈希实现新版本发布时设置GRAYSCALE_RATIO0.110%if hash(instance_id) % 100 GRAYSCALE_RATIO * 100则新实例使用新版本否则用旧版本运维后台实时查看new_version_ratio指标确认灰度比例符合预期从那以后我每次上线新流程都强制走一遍这三步验证 10% 灰度再切全量。不是信不过开发而是信不过“我以为没问题”。流程引擎的尊严不在它能画多复杂的图而在于它敢让每一次点击、每一次审批、每一次失败都留下不可篡改的证据链。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
UML统一建模语言核心图表与应用实践 1. UML统一建模语言概述 UML(Unified Modeling Language)作为软件工程领域的通用建模语言,已经走过了25年的发展历程。我第一次接触UML是在2005年参与一个银行系统开发项目时,当时团队使用Rational Rose工具绘制用例图和类图来沟通… · 2026/11/3 10:16:14
光盘转Windows ISO:ImgBurn扇区级抓取与校验归档 手里那张 Windows 安装盘在抽屉里躺了三年,前两天翻出来想给一台老机器重装系统,光驱咔咔响了两声就报错——这种时候你就知道,把光盘转成 Windows ISO 这件事,早晚得做一遍。ImgBurn 是这类活里我用了最久的工具:安装… · 2026/11/3 3:19:56
AI开发者持续学习与工程实践指南 1. AI原生应用开发者的持续学习之道 在算法迭代日新月异的今天,AI原生应用开发者面临着一个核心矛盾:既要快速跟进技术变革,又要在特定领域形成深度积累。我见过太多同行陷入"学不完"的焦虑,也见证过那些通过系统性学习… · 2026/11/3 0:34:40
长沙考电工证考点地址在哪考?外省证能转吗?过来人教你避坑 长沙考电工证考点地址在哪考?外省证能转吗?过来人教你避坑 之前考的证在外省能不能转过来?这是很多从业者最关心的问题,尤其是在长沙从事电工工作的朋友,常常会遇到这样的疑问。如果你之前是在别的省市考的电工证,现在想在长沙继续工作,首先要搞清楚,… · 2026/11/12 11:50:22
上海疫情电工证怎么审网上查询攻略来了 实操考试心里没底怕挂科 上海疫情电工证怎么审网上查询攻略来了 实操考试心里没底怕挂科 实操考试心里没底怕挂科,特别是像你这样在三门峡从事中小型施工企业的负责人,每天忙于项目和现场管理,一想到电工证的年审和继续教育,脑袋就嗡嗡响。别急,本文从【上海疫情电工证怎么审】… · 2026/11/12 11:50:22
特种电工证哪里能考到电子证书怎么查 特种电工证哪里能考到电子证书怎么查 学历不高怕报不上名?其实特种电工证哪里能考到,关键看你去哪报名。现在电子证书普及了,很多地方都支持线上报名和查证,学历门槛不像以前那么高了。不过你得知道哪个地方正规,流程怎么走,别一不小心花了钱还白忙活。… · 2026/11/12 11:50:22
儋州电工证样本怎么弄?值不值得考? 儋州电工证样本怎么弄?值不值得考? 你是不是也遇到过这种情况,找工作被问到有没有电工证,自己却不知道去哪报名,生怕被中介坑?别急,今天就带你搞懂 儋州电工证样本 怎么弄, 值不值得考 这个问题,也给你一个清晰的答案。… · 2026/11/12 11:50:22
哪里查询低压电工证全流程办理指南 低压电工证哪里查询?多久拿证?实操考试心里没底怕挂科怎么办 实操考试心里没底怕挂科,这几乎是所有准备考低压电工证的学员最真实的感受。尤其是那些第一次接触特种作业考试的人员,对流程不熟悉、对标准不了解,很容易在备考阶段就产生焦虑情绪。而更让人… · 2026/11/12 11:50:22
工地太忙根本没时间复习考试?低压电工作业证查通过率揭秘 工地太忙根本没时间复习考试?低压电工作业证查通过率揭秘 工地太忙根本没时间复习考试?低压电工作业证查流程复杂、通过率低?很多小伙伴都遇到过这个问题,特别是驻马店的应届工程类毕业生,一边忙着实习,一边还要准备低压电工作业证考试,真的让人头大。… · 2026/11/12 11:49:22
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/11 12:18:07
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/11 19:07:25
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/11 17:14:54
电工证设年审有效期多久官方回应来了,工地太忙根本没时间复习考试 电工证设年审有效期多久官方回应来了,工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,这是很多一线电工、焊工、高处作业人员的真实写照。尤其是像你这样刚毕业的鹤壁工程类毕业生,一边要找工作,一边要考证,时间紧张,复习效率低。但你可能不知… · 2026/11/12 0:00:56
新乡电工证实操怕挂科?电工作业动漫图+报考避坑全解析 新乡电工证实操怕挂科?电工作业动漫图+报考避坑全解析 实操考试心里没底怕挂科?你不是一个人。新乡的水利工程从业者,很多在报考电工证的时候都担心自己操作不熟练、考试不通过, 千万别踩坑 ,选对资料和方法真的能提高通过率。… · 2026/11/12 0:00:56
网电工证千万别踩坑:实操考试心里没底怕挂科怎么破? 网电工证千万别踩坑:实操考试心里没底怕挂科怎么破? 实操考试心里没底怕挂科,这是很多准备报考【网电工证】的学员最真实的感受。尤其是在高压电工、低压电工等工种上,操作环节的容错率极低,一旦失误就可能直接被判定不合格。很多人因为担心挂科而放弃考… · 2026/11/12 0:00:56
双极步进电机控制实战: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/12 4:35:34
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/12 4:36:01
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/11 17:15:20