基于深度学习的人脸识别考勤系统实战:从ArcFace到活体检测
简介这是一套面向高校课程设计与毕业设计场景的深度学习人脸识别考勤系统完整项目包适合具备一定Python与神经网络基础、希望快速搭建可运行考勤demo的学生和开发者。资源共121个文件以31个py源码、18个pyc编译文件、37张jpg人脸样本图为主另含npy特征数据、csv考勤记录、txt说明及md文档压缩包约19.67MB结构覆盖数据采集、模型训练到系统集成的完整链路。项目采用YOLO进行人脸检测结合CNN完成特征提取与识别并配套Start.exe启动入口与前端页面便于直接运行体验。已有44人学习下载读者可从中获取数据预处理脚本、模型训练代码、特征文件与考勤记录样例理解实时识别流程、容错验证设计及前后端管理模块的实现思路为课程设计或毕业设计提供可复用的工程参考。1. 从一张打卡照片说起深度学习人脸识别考勤系统到底解决了什么早上八点五十公司门口排了二十多号人前台那台老指纹机开始集体翻车——手指脱皮、出汗、按偏了一个接一个识别失败。换成工牌刷卡又总有人忘带。后来我们上了一套基于深度学习的人脸识别考勤系统员工走到门口摄像头前站定一秒内完成比对后台自动写一条打卡记录。这套东西的核心不是摄像头数据库这么简单它要解决的是在真实办公场景下光照忽明忽暗、有人戴口罩、有人侧脸、有人戴眼镜反光系统还得稳定认出这是谁并且判断是本人活体而不是举着照片来代打卡。这个标题里的三个词——深度学习、人脸识别、考勤系统——分别对应算法层、感知层和业务层。深度学习负责把人脸映射成高维特征向量人脸识别负责比对和判定考勤系统负责把识别结果变成可查询、可导出、可对接薪资的记录。适合谁看想自己搭一套能跑起来的人脸考勤的开发者、需要给中小团队做门禁考勤的运维、以及正在做深度学习实战项目案例的学生。下面我按选型→搭环境→跑通识别→接考勤业务→避坑→进阶的顺序把这条链路拆开讲。2. 选型先定死人脸识别模型、检测器和考勤架构怎么配2.1 为什么是 ArcFace 而不是随便一个 CNN人脸识别不是普通图像分类。普通 CNN 做分类最后一层 softmax 把每个人当一个类别人一多类别就爆炸而且新增员工要重训。人脸识别的正确做法是特征提取度量学习把每张脸压成一个 512 维向量同一个人的向量距离近不同人距离远。ArcFace 是目前工程上最稳的损失函数之一它在角度空间上加了一个 margin让类间距离被强行拉开。相比早期的 Softmax、Center LossArcFace 在口罩、侧脸这类困难样本上的类间可分性明显更好。我一般会选 InsightFace 这套开源实现它把 RetinaFace 检测 ArcFace 识别打包好了模型文件直接可用。检测器用 RetinaFace 而不是 Haar 级联原因是 Haar 在侧脸和暗光下漏检严重RetinaFace 对小人脸和遮挡更鲁棒。识别主干用 ResNet50 或 MobileFaceNet服务器端用 ResNet50 精度高边缘设备门禁机那种算力用 MobileFaceNet速度快、模型小。2.2 考勤系统的三种架构别一上来就上微服务架构适用规模优点代价单机脚本SQLite10人以内半天能跑通无法多终端Flask/FastAPI MySQL50~500人易扩展、可对接要维护服务边缘盒子中心库多门店断网可离线识别部署成本高我的建议先用第二种把业务跑通识别服务和人脸库分离。识别服务只干一件事——收到一张图返回是谁相似度考勤业务服务负责打卡时间、迟到判定、报表。两者用 HTTP 或消息队列通信。这样以后换模型、加终端都不用动业务代码。热搜里常出现的人脸识别门禁机其实就是把识别服务塞进了一个带摄像头的边缘设备逻辑是一样的只是部署位置不同。2.3 人脸库怎么建注册照的质量决定上限很多人栽在这一步。注册照随手用手机拍一张就入库结果现场识别率惨不忍睹。注册照的要求正面、无遮挡、光照均匀、分辨率不低于 640×640、人脸区域占画面 1/3 以上。每人建议存 3~5 张不同角度取特征向量的平均值作为该人的底库向量。入库前一定要做质量过滤模糊的、侧脸超过 30 度的、过曝的直接拒绝重拍。这一步偷懒后面识别率上不去你会以为是模型不行其实是底库脏。3. 把环境配起来从零跑通 ArcFace 推理的最小步骤3.1 深度学习环境配置GPU 版和 CPU 版的取舍先明确一点推理阶段 CPU 也能跑只是慢。500 人的底库CPU 单张比对大概 200~400msGPU 能压到 20ms 以内。如果只是做项目验证CPU 版够用要上真实考勤建议 GPU 或带 NPU 的边缘盒子。下面是 Ubuntu 20.04 上配 GPU 版环境的典型流程这也是热搜里ubuntu20.04配置深度学习最常被问到的场景。# 1. 确认显卡驱动和 CUDA 版本匹配 nvidia-smi # 看驱动版本和最高支持的 CUDA # 2. 建独立环境别污染系统 Python conda create -n faceattend python3.9 -y conda activate faceattend # 3. 装 PyTorch版本要和 CUDA 对应这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 4. 装人脸识别工具链 pip install insightface onnxruntime-gpu opencv-python numpy逻辑说明nvidia-smi先确认驱动支持的 CUDA 上限避免装了跑不起来的版本。conda 建独立环境是因为不同项目对 numpy、onnxruntime 版本要求经常打架。PyTorch 的 index-url 必须和本机 CUDA 对应装错版本会报 CUDA driver version is insufficient。onnxruntime-gpu 是给 InsightFace 的 ONNX 模型做推理加速的如果只有 CPU换成onnxruntime即可。参数说明python3.9是兼容性最好的版本3.11 以上部分包还没轮子。CUDA 11.8 对应 PyTorch 2.x如果你驱动只支持到 CUDA 11.6就把 index-url 换成 cu116。3.2 用 InsightFace 跑通第一张人脸的检测和特征提取环境好了先别急着接考勤先确认模型能正确检测和出特征。下面这段是最小可运行代码。import cv2 import numpy as np from insightface.app import FaceAnalysis # 初始化指定检测和识别模型ctx_id0 表示用 GPU 0-1 表示 CPU app FaceAnalysis(namebuffalo_l, providers[CUDAExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) img cv2.imread(test.jpg) faces app.get(img) # 返回检测到的所有人脸对象 for face in faces: # face.bbox 是检测框face.embedding 是 512 维特征向量 print(bbox:, face.bbox) print(norm:, np.linalg.norm(face.embedding)) # 应接近 1 print(det_score:, face.det_score) # 检测置信度逻辑说明FaceAnalysis一次性加载检测、关键点、识别三个模型。app.get()内部先 RetinaFace 检测再对齐再送 ArcFace 出 512 维向量。det_size(640,640)是检测输入尺寸太小会漏掉远处的小脸太大速度下降。参数说明namebuffalo_l是 InsightFace 的模型包名包含检测和识别。ctx_id0用第一块 GPU没有 GPU 写 -1。det_score低于 0.5 的检测框建议直接丢弃那是误检。embedding的 L2 范数应该接近 1如果差很多说明模型没加载对。3.3 比对逻辑余弦相似度和阈值怎么定拿到两张脸的特征向量后用余弦相似度判断是不是同一个人。def cosine_sim(a, b): a a / np.linalg.norm(a) b b / np.linalg.norm(b) return float(np.dot(a, b)) # 假设 emb1 是底库向量emb2 是现场抓拍向量 sim cosine_sim(emb1, emb2) THRESHOLD 0.38 # ArcFace 常用阈值区间 0.35~0.45 print(same person if sim THRESHOLD else different)逻辑说明余弦相似度衡量两个向量的方向一致性人脸特征经过归一化后同人相似度通常 0.5 以上不同人 0.2 以下中间地带就是阈值要卡的地方。参数说明阈值不是拍脑袋定的。正确做法是拿一批已知同人/不同人的样本对画 ROC 曲线选误识率FAR和拒识率FRR平衡的点。安防场景宁可拒识也不能误识阈值调高到 0.45考勤场景怕员工打不上卡可以降到 0.35。这个值必须用你自己的数据标定网上抄的阈值只能当起点。4. 接上考勤业务从识别结果到一条打卡记录4.1 底库管理和 1:N 检索的实现考勤是 1:N 检索——来一张脸要和底库所有人比找出最像的那个。底库小的时候暴力遍历就行几百人以上建议用向量索引。import numpy as np class FaceGallery: def __init__(self): self.embs [] # 所有底库向量 self.names [] # 对应姓名/工号 def add(self, name, emb): self.embs.append(emb / np.linalg.norm(emb)) self.names.append(name) def search(self, query, topk1): if not self.embs: return None, 0.0 Q query / np.linalg.norm(query) G np.stack(self.embs) # (N, 512) sims G Q # 一次矩阵乘法算完所有相似度 idx int(np.argmax(sims)) return self.names[idx], float(sims[idx])逻辑说明把所有底库向量堆成矩阵一次矩阵乘法就能算出和所有人的相似度比 for 循环快几十倍。返回最相似的人名和分数。参数说明topk在考勤里一般取 1但建议同时看第二名分数如果第一名和第二名差距小于 0.05说明这次识别不可靠应该提示重试而不是硬判。底库超过 1 万人时np.stack的内存和计算会变重这时换 Faiss 的 IVF 索引把检索从 O(N) 降到近似 O(logN)。4.2 活体检测挡住举照片代打卡的那批人不做活体员工拿同事照片就能代打卡这套系统等于白做。工程上常用两种动作活体眨眼、转头和静默活体RGB 单帧判断。动作活体体验差但实现简单静默活体体验好但需要额外模型。中小团队我建议先上动作活体成本低。# 简化版连续帧中检测眨眼基于眼睛关键点纵横比 EAR def eye_aspect_ratio(eye_points): # eye_points 为 6 个眼部关键点坐标 v1 np.linalg.norm(eye_points[1] - eye_points[5]) v2 np.linalg.norm(eye_points[2] - eye_points[4]) h np.linalg.norm(eye_points[0] - eye_points[3]) return (v1 v2) / (2.0 * h) # EAR 低于阈值持续 2~3 帧再回升判定为一次眨眼逻辑说明眼睛睁开时 EAR 约 0.3闭合时降到 0.2 以下。连续帧里出现下降再回升就是一次眨眼。要求用户在摄像头前完成一次眨眼照片无法完成这个动作。参数说明EAR 阈值取 0.21 左右需按摄像头分辨率微调。要求眨眼次数设 1 次即可设多了用户会烦。注意戴眼镜反光可能干扰关键点必要时加红外摄像头做辅助。4.3 打卡记录落库和迟到判定识别通过后写一条记录。表结构别设计太复杂够用就行。CREATE TABLE attendance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(32) NOT NULL, punch_time DATETIME NOT NULL, similarity FLOAT, device_id VARCHAR(64), status TINYINT DEFAULT 0, -- 0正常 1迟到 2早退 INDEX idx_emp_time (emp_no, punch_time) );逻辑说明idx_emp_time联合索引是必须的考勤查询几乎都是某人某段时间的记录没这个索引数据一多就慢。similarity存下来方便事后排查误识。参数说明status的迟到判定不要写死在插入时建议用定时任务每天跑一次因为上班时间可能调整。同一个人 5 分钟内重复打卡要合并避免在摄像头前多站几秒就多条记录。device_id用于多终端场景定位是哪台设备打的卡。5. 避坑与排查那些让识别率一夜暴跌的原因5.1 现象白天好好的晚上识别率断崖下跌原因摄像头在暗光下自动拉高 ISO画面噪点暴增RetinaFace 检测框抖动对齐后的脸和底库差异大。解决门口加补光灯是最便宜的方案软件侧在检测前做一次直方图均衡化或 CLAHE能救回一部分。别指望模型自己扛训练数据里暗光样本本来就少。5.2 现象同一个人换个发型或戴眼镜就认不出原因底库只有一张正面照特征覆盖不足。解决每人入库 3~5 张包含戴眼镜和不戴眼镜、不同发型。检索时用底库向量的平均值或者取和底库所有向量的最大相似度。后者更稳代价是计算量翻几倍。5.3 现象两个人长得像经常互相打上对方的卡原因阈值定太低或者这两个人在特征空间里本来就近双胞胎、亲属。解决先调高阈值到 0.45 观察如果还混对这几个易混的人单独建一个二次确认名单识别到他们时要求额外刷工牌。工程上不要追求 100% 纯人脸混合验证更现实。5.4 现象服务跑几天就内存暴涨然后崩原因InsightFace 的 session 没释放或者每来一张图都新建 FaceAnalysis 对象。解决FaceAnalysis 全局初始化一次复用。图像处理完及时del并gc.collect()。用 FastAPI 的话注意别在请求处理函数里反复加载模型。5.5 现象GPU 显存够但推理还是很慢原因图片没缩放就送进模型一张 4000×3000 的图检测要好几秒。解决送检测前先把长边缩到 640~1280检测完再按比例映射回原图坐标。这一步能提速 5~10 倍是血泪经验。6. 进阶把识别率再往上抬的几个具体手段6.1 用质量分决定要不要重拍不是所有抓拍都值得送去比对。可以算一个质量分检测置信度、人脸尺寸、模糊度拉普拉斯方差、姿态角。低于阈值的直接提示请正对摄像头而不是硬比出一个错误结果。这个改动对体验提升很大因为用户知道该怎么配合。def face_quality(face, img): score face.det_score w face.bbox[2] - face.bbox[0] if w 80: # 人脸太小 score * 0.5 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) lap_var cv2.Laplacian(gray, cv2.CV_64F).var() if lap_var 100: # 太模糊 score * 0.5 return score逻辑说明把检测分、尺寸、清晰度乘起来得到一个综合质量分低于 0.4 就要求重拍。参数说明w 80和lap_var 100是按 1080P 摄像头调的换设备要重新标。6.2 阈值按人自适应全局一个阈值是妥协。更好的做法是给每个人维护一个历史相似度分布动态调整他的判定阈值。识别一直很稳的人阈值可以放宽经常在边缘徘徊的人阈值收紧并要求二次验证。这需要积累数据上线初期先用全局阈值跑一个月后再做自适应。6.3 模型更新和底库版本管理换模型意味着所有底库向量要重新提取因为不同模型的向量空间不通用。所以底库要带版本号模型升级时批量重算别新旧混用。我一般会在数据库里存model_version字段检索时只比对同版本的向量。这个习惯能省掉很多为什么升级后全乱了的排查时间。6.4 验证方法别只看准确率评估一套考勤系统光看识别准确率不够。要分开看误识率把别人认成你最严重、拒识率你本人打不上卡、活体通过率、平均识别耗时。用一批标注好的现场抓拍做测试集跑出这四个数再决定调阈值还是换模型。我吃过亏——早期只看准确率 99%上线后发现拒识率高达 8%每天都有员工打不上卡来找我。后来把阈值降了 0.03拒识率降到 2%误识率只涨了 0.1%这才是划算的买卖。做这类系统模型只是三分之一底库质量、活体策略、阈值标定才是决定成败的地方。我现在的习惯是任何参数改动前先留一份基线数据改完对比四个指标不靠感觉调。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

12V锂电池保护板原理与DW01+8205A实战设计

12V锂电池保护板原理与DW01+8205A实战设计

1. 为什么12V锂电池组必须配保护板——不是“可有可无”,而是“生死线”你手头那块标称12V、实际由3串或4串锂电芯组成的电池组,很可能正处在一场无声的慢性崩溃中。我见过太多人把12V锂电池当铅酸电池用:直接接上12V灯带、12V风扇、12V车载冰…

2026/10/7 13:10:11 阅读更多 →
用提示词工程驱动顶级AI编写可玩赛车游戏的全流程实践

用提示词工程驱动顶级AI编写可玩赛车游戏的全流程实践

1. 一句提示词生成可玩游戏:这不是魔术,是正确的工作流 先交代一下背景。我最近在折腾 AI 编程,手里正好有顶级模型的 API 额度,试着做了一件很多人只在短视频里看过的事:只给模型一句话,让它写一个能玩的《…

2026/10/7 13:10:11 阅读更多 →
Java双人联机森林冰火人:Swing+Socket大作业源码解析与避坑指南

Java双人联机森林冰火人:Swing+Socket大作业源码解析与避坑指南

简介:这是一份面向高校计算机相关专业学生的Java课程设计资源,以经典双人联机小游戏「森林冰火人」为选题,适合用作期末大作业、课程设计或毕业设计的参考项目。项目采用Java语言开发,代码注释较为完整,新手也能对照理…

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

最新新闻

零依赖、零网络、永不提交:delegate-skills 可审计信任模型与安全设计完整解析

零依赖、零网络、永不提交:delegate-skills 可审计信任模型与安全设计完整解析

零依赖、零网络、永不提交:delegate-skills 可审计信任模型与安全设计完整解析 【免费下载链接】delegate-skills Delegate a coding task to a separate coding agent CLI, review the diff, land the commit yourself — one per implementer. 项目地址: https:…

2026/10/7 15:08:13 阅读更多 →
Warp 会话恢复修复:WSL 与 MSYS2/Git Bash 的 PWD 保留实现解析

Warp 会话恢复修复:WSL 与 MSYS2/Git Bash 的 PWD 保留实现解析

桌面应用开发者工具人工智能AI 应用AI Agent代码智能体 【免费下载链接】warp Warp is an agentic development environment, born out of the terminal. 项目地址: https://gitcode.com/GitHub_Trending/wa/warp 点击查看 免费下载 Warp(项目仓库&…

2026/10/7 15:08:13 阅读更多 →
LOFIC上车、LFM补齐:豪威OX08D30重新定义国产前视CIS的“最优解”

LOFIC上车、LFM补齐:豪威OX08D30重新定义国产前视CIS的“最优解”

豪威发布OX08D30:TheiaCel车载CIS补齐LED闪烁抑制拼图,800万像素前视ADAS迎来“国产最优解” 核心结论:OX08D30的发布标志着豪威集团在车载前视CIS领域完成了从“像素领先”到“动态范围与LFM系统能力领先”的关键跨越。其核心竞争力不在单纯像素堆砌,而在于以单次曝光LOF…

2026/10/7 15:08:12 阅读更多 →
React性能优化:你以为是useMemo的事,其实是闭包的锅

React性能优化:你以为是useMemo的事,其实是闭包的锅

“明明用了useMemo,为什么每次props变化后组件还是疯狂重渲染?”——在重构一个数据可视化大屏时,我盯着火焰图里高频出现的陷入了沉思。这个组件接收10Hz的实时数据流,理论上应该只在数据变化时重新计算渲染,但实际却…

2026/10/7 15:08:12 阅读更多 →
新手出海:谷歌广告和SEO先砸钱还是先攒内容

新手出海:谷歌广告和SEO先砸钱还是先攒内容

去年底有个做户外家具的客户来问,独立站刚上线三周,广告预算还没动,团队却在纠结要不要先招个SEO专员。谷歌广告和谷歌SEO先做哪个,这个问题的答案不在渠道本身,而在两件事上:现金还能撑几个月,…

2026/10/7 15:08:11 阅读更多 →
__attribute__ 完全指南:GCC/Clang 扩展属性系统全面解析

__attribute__ 完全指南:GCC/Clang 扩展属性系统全面解析

摘要 C/C 语言标准本身对编译器优化、内存布局、符号可见性等底层行为的控制能力有限。为弥补这一不足,GCC 早在 20 世纪 90 年代就引入了 __attribute__((...)) 语法,允许开发者向编译器传递标准语法之外的元信息。Clang 出于兼容性考虑几乎全盘继承并扩展了这一机制。本文系统…

2026/10/7 15:07:11 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

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

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

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

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

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

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/7 14:34:12 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/7 14:34:13 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 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/7 14:34:12 阅读更多 →
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/7 11:43:46 阅读更多 →
黑夜航拍船只数据集训练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/7 13:34:55 阅读更多 →