03-BACnet对象模型详解
目录BACnet 对象模型详解设备、对象与属性一、BACnet 对象模型概述对象模型的核心价值自描述的聚合数据点二、标准对象类型对象的标识类型 实例号对象实例号的几个要点三、标准属性点对象AI/AO/AV/BI/BO/BV 等共有的属性Analog Input 对象的典型属性四、设备对象Device Object设备实例号的几个要点五、对象模型的 C 语言表示六、属性访问的 C 语言实现七、对象模型的扩展性八、小结BACnet 对象模型详解设备、对象与属性一、BACnet 对象模型概述BACnet 的核心抽象是对象模型。在 BACnet 看来网络上的所有数据和行为都被建模为对象每个对象拥有一组属性。这种设计将楼宇设备中千差万别的物理量温度、湿度、开关状态、设定值等统一为标准化的数据结构使不同厂商设备可以无歧义地交换信息。BACnet 对象模型包含三个层次设备Device └── 对象Object └── 属性Property这是一个包含关系设备里有对象对象里有属性不是分类关系。三者各自的角色层次角色能否独立寻址类比设备物理硬件是所有对象的容器通过设备实例号寻址文件柜对象设备内的一个数据点或功能单元通过对象标识符类型实例寻址柜子里的文件夹属性对象内的一个数据字段不能必须依附于对象文件夹里的文件需要注意属性不是对象。对象有自己的标识符Object Identifier会出现在设备的 Object List 中可以被独立创建和删除属性只有一个编号如 Present Value 85必须依附于某个对象不能脱离对象单独访问。读数据时需要先指定对象再指定属性两级定位。以一台空调控制器为例它的内部结构大致如下设备空调控制器Device Instance 100 │ ├── 对象1Analog Input 0回风温度传感器 │ ├── 属性Present Value 24.5 ← 当前温度 │ ├── 属性Units ℃ ← 工程单位 │ └── 属性High Limit 30 ← 报警上限 │ ├── 对象2Analog Input 1送风温度传感器 │ └── 属性Present Value 18.2 │ ├── 对象3Binary Output 0风机启停控制 │ └── 属性Present Value Active ← 风机正在运行 │ └── 对象4Device 100设备自身信息 ├── 属性Vendor Name 某某厂商 └── 属性Object List [AI-0, AI-1, BO-0, Device-100]要读取回风温度路径是找到设备 100 → 找到 Analog Input 0 → 读取 Present Value → 得到 24.5。这就是 BACnet 读写操作的基本路径设备 → 对象 → 属性三层逐级定位。每个 BACnet 设备至少包含一个设备对象以及若干个其他类型的对象用于表示设备所监控或控制的物理量。对象模型的核心价值自描述的聚合数据点理解了对象模型的结构自然会产生一个问题为什么要这么设计与传统协议如 Modbus相比对象模型的优势在哪里传统协议的方式数据是分散的在 Modbus 里一个温度点的信息散落在不同寄存器中而且很多信息协议本身根本不携带信息在哪里说明温度值寄存器 40001只有一个原始数值单位℃不在协议里靠集成商在 SCADA 里手动配置报警上限寄存器 40010或没有可能是另一个寄存器也可能根本没有描述回风温度不在协议里靠集成商在 SCADA 里手动命名传感器是否故障不在协议里靠集成商自己判断也就是说Modbus 只传一个裸数字至于这个数字是什么单位、叫什么名字、什么时候该报警——协议不管全靠集成商在工程现场手动配置。换一个项目、换一个人就得重新配一遍。BACnet 的方式相关信息聚合成一个对象同样的温度点在 BACnet 里是一个 Analog Input 对象所有相关信息都聚合在一起Analog Input 0回风温度 ├── Present Value 24.5 ← 数值 ├── Units ℃ ← 单位协议自带不用外部配 ├── High Limit 30 ← 报警上限协议自带 ├── Low Limit 10 ← 报警下限协议自带 ├── Deadband 0.5 ← 报警死区 ├── Description 回风温度 ← 描述协议自带 ├── Event State Normal ← 当前是否报警 ├── Status Flags (正常) ← 是否故障/被覆盖/越限 └── Out Of Service false ← 是否处于维护状态关键区别自描述 vs 需要外部配置传统协议ModbusBACnet数值有有Present Value单位没有靠外部配有Units 属性报警限可能没有或散在别的寄存器有High/Low Limit 属性描述没有靠外部命名有Description 属性故障状态没有靠外部判断有Status Flags / Event State设备自我描述不能能Device 对象带厂商、型号、固件版本等BACnet 对象是自描述的——用 ReadProperty 读一个对象不仅能拿到数值还能拿到单位、报警限、描述、状态不需要任何外部配置文件。这种聚合带来三个直接好处减少外部配置不用在 SCADA 里手动填单位、报警限、描述设备自己就告诉你了。避免配置不一致传统协议里现场改了报警限但 SCADA 没同步就会出错BACnet 里报警限就在对象里读出来就是当前值。跨厂商即插即用换一台不同厂商的设备只要对象类型一样上位机不用改任何配置就能读到完整的温度信息。一句话总结传统协议传的是散落的裸数据BACnet 传的是聚合的、自描述的数据点——把一个物理量相关的所有信息值、单位、限值、描述、状态打包在一起让数据点自己会说话。这正是对象模型的精髓。二、标准对象类型BACnet 标准至今已定义 60 余种标准对象类型BACnet-2020编号 0–62涵盖照明、电梯、门禁、审计等对象族每种对象对应一类楼宇自动化场景。下表列出最常用的几种对象类型类型编号用途Analog Input模拟量输入0读取模拟量传感器如温度、压力、流量Analog Output模拟量输出1控制模拟量执行器如调节阀开度Analog Value模拟量值2软件计算的模拟量如设定值Binary Input二进制输入3读取开关量传感器如水流开关、运行状态Binary Output二进制输出4控制开关量执行器如风机启停、灯具开关Binary Value二进制值5软件计算的开关量Multi-state Input多态输入13多状态反馈如风机档位Multi-state Output多态输出14多状态控制如三档风机控制Schedule日程17时间表控制Program程序16内置控制程序Device设备8设备本身的信息Trend Log趋势记录20历史数据记录对象的标识类型 实例号每个对象都由一个 32 位的对象标识符Object Identifier唯一标识其中高 10 位为对象类型低 22 位为对象实例号对象类型Object Type10 位标识对象种类如 Analog Input 0、Device 8。标准对象类型编号固定如上表厂商可定义私有对象类型编号 ≥ 128以满足特殊需求。对象实例号Object Instance22 位范围 0 ~ 4194303用于在同一设备内区分同类型的多个对象。对象实例号的几个要点1. 唯一性范围设备内 同类型对象实例号只在同一设备内、同一对象类型间唯一。不同类型的对象可以复用同一个实例号。例如一台设备里可以同时存在Analog Input实例 0回风温度Analog Input实例 1送风温度Binary Output实例 0风机启停Binary Output实例 1水阀开关这里Analog Input 0和Binary Output 0实例号都是 0但因为类型不同并不冲突。组合(类型, 实例)即可在一个设备内唯一定位一个对象。2. 分配方式对象实例号通常由设备厂商在出厂时设定默认值或由集成商在工程调试时通过配置工具修改写入非易失存储后开机自动加载。正常运行期间一般不改动——改了会导致所有引用该对象的联动、趋势记录、COV 订阅失效。和设备实例号一样属于配一次、用很久的参数。3. 跨网络定位对象三段式地址(对象类型, 对象实例号)只能在一个设备内定位对象。要在整个 BACnet 网络中定位一个对象还需要加上设备实例号形成三段式地址设备实例号 对象类型 对象实例号例如设备 100 的 Analog Input 1就唯一确定了全网中的某个温度采集点。BACnet 的 ReadProperty 等服务在请求报文中携带的就是这三个参数。三、标准属性每个对象都拥有一组属性属性是 BACnet 数据交互的最小单位。所有对象都共享一些通用属性同时每种对象类型还有自己的专属属性。点对象AI/AO/AV/BI/BO/BV 等共有的属性注真正所有对象类型都必选的只有Object Identifier、Object Name、Object Type三个。下表中的Present Value、Status Flags、Event State等主要适用于点对象模拟量/二进制/多态的输入、输出、值Device、File、Notification Class 等对象并不一定具备这些属性。属性数据类型必选/可选说明Object IdentifierBACnetObjectIdentifier必选对象唯一标识Object NameCharacterString必选对象名称Object TypeBACnetObjectType必选对象类型DescriptionCharacterString可选对象描述Present Value视对象类型而定必选对象当前值Status FlagsBACnetStatusFlags必选状态标志故障、覆盖、越限、报警Event StateBACnetEventState必选事件状态正常、报警、故障等ReliabilityBACnetReliability可选可靠性状态Out Of ServiceBoolean必选是否处于维护状态Update IntervalUnsigned可选数据更新间隔Analog Input 对象的典型属性属性说明Present Value当前测量值浮点数Units工程单位如 ℃、Pa、kWhCov IncrementCOV 通知的最小变化量High Limit / Low Limit报警上下限Deadband报警死区Resolution测量精度Device Type传感器型号四、设备对象Device Object每个 BACnet 设备必须且只能包含一个 Device 对象它描述了设备本身的信息是设备发现和管理的入口。Device 对象的关键属性包括属性说明Object Identifier设备对象标识格式为(8, 设备实例号)类型固定为 8Vendor Identifier厂商编号由 ASHRAE 分配Vendor Name厂商名称Product Name产品名称Model Name产品型号Firmware Revision固件版本Application Software Version应用软件版本Protocol Version支持的 BACnet 协议版本Protocol Services Supported支持的服务列表位图Protocol Object Types Supported支持的对象类型列表位图Object List设备中所有对象的列表Max APDU Length Accepted设备能接收的最大 APDU 长度Segmentation Supported是否支持报文分段APDU Segment Timeout分段超时设备实例号的几个要点设备实例号Device Instance是 BACnet 网络中设备的唯一标识范围 0 ~ 419430322 位。它不是 Device 对象的一个独立属性而是 Device 对象的 Object Identifier 的实例号部分——因为 Device 对象的类型固定为 8所以(8, 设备实例号)中真正起区分作用的只有实例号。1. 唯一性范围整个 BACnet 互联网络与对象实例号设备内同类型唯一不同设备实例号必须在整个 BACnet 互联网络范围内唯一——跨越所有网络号、所有链路层。如果两台设备实例号相同Who-Is 会收到两条相同实例号的 I-Am地址绑定表会混乱系统无法正确寻址。2. 分配方式出厂时厂商通常给一个默认值常见为 0 或最大值不是最终使用的值。系统集成商在工程调试时通过 WriteProperty 修改 Device 对象的Object_Identifier属性来设定写入非易失存储后开机自动加载。正常运行期间一般不改动——改了会导致所有引用该设备的绑定、联动、COV 订阅失效相当于改名代价很大。3. 在寻址中的作用设备实例号是三段式地址设备实例号 对象类型 对象实例号的第一段。要在整个 BACnet 网络中定位一个对象必须先通过设备实例号找到设备再由对象类型和对象实例号定位设备内的具体对象。五、对象模型的 C 语言表示以下代码展示如何用 C 结构体表示 BACnet 对象模型#include stdint.h #include stdbool.h ​ /* 对象类型枚举 */ typedef enum { OBJ_ANALOG_INPUT 0, OBJ_ANALOG_OUTPUT 1, OBJ_ANALOG_VALUE 2, OBJ_BINARY_INPUT 3, OBJ_BINARY_OUTPUT 4, OBJ_BINARY_VALUE 5, OBJ_DEVICE 8, OBJ_MULTI_STATE_INPUT 13, OBJ_MULTI_STATE_OUTPUT 14, /* ... 其他类型 ... */ } bacnet_object_type_t; ​ /* 对象标识符类型 实例号共 32 位 */ typedef struct { uint32_t type : 10; /* 对象类型10 位 */ uint32_t instance: 22; /* 对象实例号22 位 */ } bacnet_object_identifier_t; ​ /* 工程单位部分 */ typedef enum { UNITS_DEGREES_CELSIUS 62, UNITS_PASCAL 113, UNITS_KILOWATT_HOURS 19, UNITS_PERCENT 98, } bacnet_units_t; ​ /* 状态标志 */ typedef struct { bool in_alarm; /* 报警中 */ bool fault; /* 故障 */ bool overridden; /* 被覆盖 */ bool out_of_service; /* 维护中 */ } bacnet_status_flags_t; ​ /* 模拟量输入对象 */ typedef struct { bacnet_object_identifier_t object_id; const char *object_name; const char *description; float present_value; /* 当前值 */ bacnet_units_t units; /* 工程单位 */ bacnet_status_flags_t status_flags; /* 状态标志 */ uint8_t event_state; /* 事件状态 */ bool out_of_service; /* 维护状态 */ float cov_increment; /* COV 变化量 */ float high_limit; /* 报警上限 */ float low_limit; /* 报警下限 */ float deadband; /* 死区 */ } bacnet_analog_input_t; ​ /* 设备对象 */ typedef struct { bacnet_object_identifier_t object_id; /* (8, device_instance) */ uint16_t vendor_id; const char *vendor_name; const char *product_name; const char *model_name; const char *firmware_revision; uint32_t protocol_version; uint16_t max_apdu_length_accepted; bool segmentation_supported; /* Object List 由运行时维护此处省略 */ } bacnet_device_object_t;六、属性访问的 C 语言实现下面是一个通用的读属性函数示例展示如何按属性编号访问对象/* 属性编号部分 */ typedef enum { PROP_OBJECT_IDENTIFIER 75, PROP_OBJECT_NAME 77, PROP_OBJECT_TYPE 79, PROP_PRESENT_VALUE 85, PROP_STATUS_FLAGS 111, PROP_EVENT_STATE 36, PROP_OUT_OF_SERVICE 81, PROP_UNITS 117, PROP_DESCRIPTION 28, PROP_HIGH_LIMIT 45, PROP_LOW_LIMIT 59, PROP_DEADBAND 25, PROP_COV_INCREMENT 22, } bacnet_property_id_t; ​ /* 读取 Analog Input 对象的属性 */ int analog_input_read_property(const bacnet_analog_input_t *obj, bacnet_property_id_t prop, uint8_t *out_buf, uint16_t *out_len) { switch (prop) { case PROP_OBJECT_IDENTIFIER: memcpy(out_buf, obj-object_id, sizeof(obj-object_id)); *out_len sizeof(obj-object_id); return 0; case PROP_OBJECT_NAME: strncpy((char *)out_buf, obj-object_name, *out_len); *out_len (uint16_t)strlen(obj-object_name); return 0; case PROP_PRESENT_VALUE: memcpy(out_buf, obj-present_value, sizeof(float)); *out_len sizeof(float); return 0; case PROP_UNITS: memcpy(out_buf, obj-units, sizeof(obj-units)); *out_len sizeof(obj-units); return 0; case PROP_STATUS_FLAGS: memcpy(out_buf, obj-status_flags, sizeof(obj-status_flags)); *out_len sizeof(obj-status_flags); return 0; default: return -1; /* 不支持的属性 */ } }七、对象模型的扩展性BACnet 对象模型具有良好的扩展性体现在两个方面新增对象类型厂商可定义私有对象类型编号 ≥ 128用于表示特殊设备的功能。私有对象在标准对象无法覆盖的场景下使用。新增属性厂商也可为标准对象添加私有属性编号 ≥ 512注意与私有对象类型的 ≥ 128 区分用于厂商专有功能扩展。这种扩展机制使 BACnet 既能保证标准设备间的互操作又能为厂商差异化功能留出空间。但需要注意私有扩展应作为附加而非替代否则会破坏互操作性。八、小结BACnet 对象模型是整个协议的基石。设备—对象—属性的三层抽象将楼宇设备中纷繁复杂的数据统一为标准化的数据结构使不同厂商设备能够无歧义地交换信息。理解对象类型、对象实例号、属性编号这三个核心概念是阅读 BACnet 报文、开发 BACnet 应用的前提。后续文章将在此基础上展开服务机制——即如何通过 BACnet 服务对对象属性进行读写、订阅与控制。

相关新闻

MySQL安全配置:secure-file-priv原理、配置与实战指南

MySQL安全配置:secure-file-priv原理、配置与实战指南

1. 项目概述:理解secure-file-priv的来龙去脉如果你在 MySQL 里尝试执行SELECT ... INTO OUTFILE /tmp/result.csv或者使用LOAD DATA INFILE命令时,突然蹦出来一个错误:“ERROR 1290 (HY000): The MySQL server is running with the --secure…

2026/10/13 11:39:10 阅读更多 →
三步把游戏从书房搬上任意屏幕:Sunshine自托管游戏串流完整指南

三步把游戏从书房搬上任意屏幕:Sunshine自托管游戏串流完整指南

三步把游戏从书房搬上任意屏幕:Sunshine自托管游戏串流完整指南 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 深夜想窝在客厅沙发上打游戏,电脑却在书房&…

2026/10/4 19:07:51 阅读更多 →
Grok 4.6 1.5万亿参数升级:SFT与RLHF技术栈深度解析与实践指南

Grok 4.6 1.5万亿参数升级:SFT与RLHF技术栈深度解析与实践指南

最近在跟进大语言模型的技术迭代时,发现 Grok 系列模型又有了新动作。对于开发者而言,无论是想将其集成到自己的应用中,还是单纯研究其技术架构,了解其核心变化都至关重要。本文将围绕 Grok 4.6 的发布,深入解析其 1.5…

2026/10/12 18:50:11 阅读更多 →

最新新闻

AI助手会话记录批量导出实战:从API到浏览器脚本的完整方案

AI助手会话记录批量导出实战:从API到浏览器脚本的完整方案

1. 把需求掰开揉碎:你要的批量导出到底是什么时不时就有人来问:能不能把AI助手左侧那一长串会话记录一次性导出?注意,这里的“会话”指的是左边列表里的每一个独立条目,而不是某个会话里的几十轮来回。每条会话都有自己…

2026/10/13 11:39:13 阅读更多 →
OneLive私有化直播系统搭建全攻略:从推流到播放的完整实践

OneLive私有化直播系统搭建全攻略:从推流到播放的完整实践

第一次把 OneLive 这个名字写在架构图上,是在帮一个朋友做线上分享活动的时候。他当时需要的不是某个直播平台的账号,而是一套能装在自有服务器上、由自己完全控制推拉流、录制和权限的直播项目。OneLive 这个名字就这么随手起了:一场直播、一…

2026/10/13 11:39:13 阅读更多 →
Open Code Review:从形式评审到协作基础设施的范式升级

Open Code Review:从形式评审到协作基础设施的范式升级

1. “open-code-review”不是个工具,而是一套被低估的协作范式你有没有遇到过这样的场景:团队里新来一位同事,第一天就提了三份PR,每份都改了二十多个文件,commit message写的是“fix bug”;或者自己花三天…

2026/10/13 11:39:13 阅读更多 →
基于人脸识别的照片视频管理器:C#稳定版64位实现

基于人脸识别的照片视频管理器:C#稳定版64位实现

简介:这是一款用C#编写的本地照片与视频管理工具,面向需要按人物整理个人影像资料的普通用户与开发者。它支持jpg、jpeg、png、bmp等图片格式以及avi、mp4、mpg、mpeg等视频格式,核心能力包括人脸库录入、照片视频人脸自动识别,以…

2026/10/13 11:39:13 阅读更多 →
AI编程智能体分级:决策权归属决定项目成败

AI编程智能体分级:决策权归属决定项目成败

1. 为什么“智能体分级”不是技术炫技,而是项目落地的生死线最近帮某高校实验室做一套教学辅助系统时,团队最初信心满满:用最新开源大模型Agent框架,两周就能跑通自动出题、批改、反馈闭环。结果卡在第三天——学生提交一道开放性…

2026/10/13 11:39:13 阅读更多 →
大模型与电机控制之间:三层分布式架构的工业落地实践

大模型与电机控制之间:三层分布式架构的工业落地实践

1. 从“一句话控制电机”的幻觉说起这两年大模型火得不行,不少做工业自动化的朋友私下问我:既然大模型连代码都能写,能不能让它直接控制电机?把产线上的PLC换掉,用自然语言让机械臂干这干那,听起来实在太有…

2026/10/13 11:38:11 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →