银行排队系统仿真实验:基于SimPy的M/M/c离散事件建模与参数分析
简介银行排队系统实验报告是一份面向数据结构课程学习者的C语言实践项目通过模拟多窗口银行客户到达、排队与离开过程帮助掌握队列结构、随机事件生成和平均逗留时间计算等核心知识点。资源包内共1个doc文档大小191KB内容涵盖实验设计要求、设计思路、头文件说明、主函数与各功能模块的源程序分析以及VIP与普通客户的到达/离开流程和运行界面展示结构完整可直接作为课程设计参考或实验报告模板。该资源已有901人学习浏览适合正在学习队列应用、需要完成相关实验或复习C语言编程的学生使用。整体篇幅紧凑既给出了可运行的程序框架也说明了从实际问题到算法实现的分析过程有助于提升数据结构的应用能力与编程思维。1. 银行排队系统实验报告先定实验目标再决定用什么模型很多人拿到“银行排队系统实验报告”这个题目第一反应是写一个取号叫号的小程序。但真正能落地、能写进答辩PPT的实验报告核心不在界面而在排队模型的参数敏感性分析——比如某网点高峰期客户平均要等15分钟那么增加一个窗口能把等待降到几分钟这个实验报告就是回答这类问题的。我一般用离散事件仿真来做而不是纯手算排队论公式。仿真的好处是能轻松处理到达率、服务率、窗口数量的组合变化输出等待时间、队列长度、窗口空闲率等指标。本文用PythonSimPy实现一个可复现的最小模型从建模、实验设计到写报告的踩坑点都覆盖。适合谁运筹学、工业工程、管理科学课程的学生或者需要评估网点运营方案的从业者。有现成取号数据的话也能按文中的方法校准模型。2. 搭建银行排队仿真模型从 M/M/c 队列到最小 SimPy 代码2.1 为什么银行排队首选 M/M/c 模型银行排队系统的本质是一个多服务台排队系统。经典排队论中M/M/c代表客户到达间隔服从负指数分布即到达过程是泊松流服务时间也服从负指数分布有c个平行服务台。银行网点天然满足这个特征客户随机到达服务时长受业务类型影响呈现较强的波动性。只要知道到达率λ单位时间到达客户数、服务率μ单位时间一个窗口能服务完的客户数和窗口数c就能用Erlang C公式估算平均等待时间。但Erlang C公式写起来并不轻松它涉及概率加权和而且只适合稳态条件。更麻烦的是稍微改变一个假设比如服务时间换成正态分布公式就得推翻重来。仿真的优势在于你只需要定义客户到达过程、服务过程、排队规则剩下交给事件调度。SimPy是Python生态里最常用的离散事件仿真库代码简洁适合作为实验报告的实现载体。我见过不少用Arena或AnyLogic做的但SimPy免费、可读性好、改参数不用重新拖拽控件对于课程实验和网点运营测算都够用。2.2 安装 SimPy 并搭出最小仿真骨架客户到达、排队、服务三件事先安装依赖然后写一个最小可运行的SimPy仿真。注意这里的代码不是最终的实验脚本而是用来验证环境是否正常的骨架。# 安装命令pip install simpy import simpy import random def customer(env, name, window): arrival_time env.now # 记录到达时刻 with window.request() as req: # 申请一个空闲窗口 yield req # 排队等待直到拿到窗口 wait_time env.now - arrival_time # 模拟服务时间负指数分布均值1/服务率 service_time random.expovariate(1.0/1.5) # 平均1.5分钟 yield env.timeout(service_time) print(f{name} 等待 {wait_time:.1f} 分钟服务 {service_time:.1f} 分钟) def setup(env, n_windows): window simpy.Resource(env, capacityn_windows) for i in range(10): # 先生成10个客户试试 env.process(customer(env, f客户{i}, window)) yield env.timeout(random.expovariate(1.0)) # 平均1分钟来一个 env simpy.Environment() env.process(setup(env, 3)) # 3个窗口 env.run(until30)逻辑说明这个骨架代码做了三件事——用simpy.Resource表示窗口池capacity是窗口数每个客户进程先申请资源window.request()如果没空闲窗口就会排队拿到资源后记录等待时间然后模拟一段服务时间最后释放资源。random.expovariate(1.0/1.5)生成的指数分布随机数均值是1.5分钟符合M/M/c对服务时间的假设。参数说明arrival_time是客户到达的仿真时刻env.now是SimPy内部时钟单位为分钟。setup里用env.timeout(random.expovariate(1.0))控制下一个客户到达的间隔平均1分钟一个客户。想改成每小时60人就把这个参数改成60/601.0想改成每小时100人就改成60/1000.6。骨架里只生成10个客户是为了快速验证流程没有报错正式实验要改成循环生成几百甚至几千个客户。运行后你会看到每个客户打印出自己的等待时间和服务时间这能帮助你确认随机数生成是否正常。2.3 参数表到达率、服务率、窗口数怎么设才合理实验参数不能拍脑袋。我一般这样设首先从网点历史数据中估计到达率取号机打印的号码单就是现成的数据源统计高峰时段比如9:00-11:00平均每小时取号数量服务率则看柜台平均业务办理时长把一天总业务量除以窗口营业总分钟数。如果没有真实数据用文献常见的值也行到达率λ60人/小时服务率μ40人/小时即平均服务1.5分钟窗口数c3。验证一下系统是否稳定系统利用率ρλ/(cμ)60/(3×40)0.5远小于1说明队列不会无限增长。如果想看到明显的排队把λ调到100此时ρ0.833等待时间会显著上升。下面这张表是我在实际实验中用的默认参数并给出了调整方向。参数默认值单位调整建议到达率 λ60人/小时高峰时段可调到80-120服务率 μ40人/小时若办理复杂业务多调低到30窗口数 c3个实验矩阵中取3、4、5仿真时长2000分钟必须包含足够客户见第4章随机种子42无固定种子可复现实验这里有个容易被忽略的细节到达率和服务率的单位要统一。如果你把到达率写成“人/分钟”服务率写成“人/小时”计算ρ就会错得离谱。我习惯统一用“人/小时”那么平均到达间隔就是60/λ分钟平均服务时间就是60/μ分钟。另外如果网点有午休或高峰低谷最好把到达过程按时段分段建模但初版实验里先按稳态做等报告结论稳定后再扩展成非平稳到达。3. 实验设计与数据采集让仿真结果能写进报告3.1 设计对照实验哪些参数组合需要测为什么实验报告需要有对比不能只跑一组参数。常见的做法是设计一个二维实验矩阵横轴是窗口数c纵轴是到达率λ。固定服务率μ之后每个组合运行一次仿真记录指标。我做实验时一般取c3、4、5三档λ60、80、100三档共9组。外加一组λ60、c3作为基准。这样既能看“加窗口的效果”也能看“客流增大的压力”。实验组到达率 λ (人/小时)窗口数 c固定参数基准603μ40, duration2000, warm_up200加窗口-1604同上加窗口-2605同上高峰-1803同上高峰-2804同上高峰-3805同上峰值-11003同上峰值-21004同上峰值-31005同上这个矩阵的设计逻辑是窗口数影响服务能力到达率影响需求压力两者交叉就能回答“高峰时加大窗口到底有没有用”。注意实验组的固定参数必须一致否则组间差异不能归因于你研究的变量。我在报告里会专门放一页参数设置表把上面这些固定条件写清楚评审一眼就能看出实验设计是否严谨。3.2 实现一个可统计指标的仿真函数等待时间、队列长度、空闲率直接贴出可以放进实验报告的完整函数。它会返回一个字典包含平均等待时间、平均队列长度、窗口空闲率三个核心指标。代码里的注释说明了每段逻辑的作用。import simpy import random import statistics def simulate_bank(lam60, mu40, num_windows3, duration2000, warm_up200, seed42): random.seed(seed) env simpy.Environment() window simpy.Resource(env, capacitynum_windows) wait_times [] queue_len [0] # 通过列表在回调中修改 busy_count [0] queue_samples [] busy_samples [] def customer(name): arrival env.now queue_len[0] 1 with window.request() as req: yield req queue_len[0] - 1 busy_count[0] 1 if env.now warm_up: wait_times.append(env.now - arrival) service_time random.expovariate(mu) # 服务时间均值1/mu分钟 yield env.timeout(service_time) busy_count[0] - 1 def monitor(): while True: queue_samples.append((env.now, queue_len[0])) busy_samples.append((env.now, busy_count[0])) yield env.timeout(0.5) # 每0.5分钟采样一次 def setup(): env.process(monitor()) i 0 while True: env.process(customer(i)) i 1 yield env.timeout(60 / lam) # 到达间隔均值分钟 env.process(setup()) env.run(untilduration) # 去掉预热期之前的采样点 filtered_queue [q for t, q in queue_samples if t warm_up] filtered_busy [b for t, b in busy_samples if t warm_up] avg_wait statistics.mean(wait_times) if wait_times else 0 avg_queue statistics.mean(filtered_queue) if filtered_queue else 0 utilization statistics.mean(filtered_busy) / num_windows return { avg_wait_min: avg_wait, avg_queue_len: avg_queue, utilization: utilization, customer_served: len(wait_times) } if __name__ __main__: result simulate_bank() print(result)逻辑说明simulate_bank函数把整个仿真封装起来。customer进程模拟一个客户的完整生命周期到达后先让队列长度加1然后申请窗口拿到窗口后队列长度减1、忙碌数加1记录等待时间只有当前时刻大于预热期才记录服务完再减少忙碌数。monitor进程每0.5分钟采样一次当前队列长度和忙碌窗口数用于计算平均队列长度和占用率。参数说明60 / lam是平均到达间隔。lam60时间隔1分钟lam100时间隔0.6分钟。random.expovariate(mu)生成的服务时间均值是1/mu分钟所以mu40时平均服务1.5分钟。duration2000表示仿真2000分钟约33小时warm_up200表示前200分钟的数据不纳入统计。返回结果直接是一个字典方便后续写进表格或绘制曲线。这里要提醒一句如果你需要统计“等待超过10分钟的客户比例”可以在customer函数里加一个计数器。比如在wait_times.append那一行之后判断一下wait值是否大于10然后累加到一个sla_breach列表。这个指标在最后一章会用来补充平均等待时间的不足。3.3 多次运行取均值随机种子与置信区间单次仿真有随机波动不能作为实验结论。我通常每个参数组合重复运行5次取平均值并计算标准差。SimPy的随机源来自Python标准库random所以只需要在每次运行前调用random.seed(seed run_index)即可保证每次实验可复现同时不同run之间又有独立序列。results [] for run_idx in range(5): r simulate_bank(lam100, mu40, num_windows4, duration2000, warm_up200, seed100 run_idx) results.append(r) avg_wait statistics.mean([r[avg_wait_min] for r in results]) std_wait statistics.stdev([r[avg_wait_min] for r in results]) print(f平均等待时间: {avg_wait:.2f} ± {std_wait:.2f} 分钟)这里用5次重复的原因很简单结果稳定且不耗时。如果每次仿真只跑几十秒可以重复10次计算95%置信区间。注意要固定种子范围如100到104否则别人无法复现你的实验。在实验报告里我会写“每个方案重复5次结果以均值±标准差形式给出”。这样比只跑一次可信得多。如果结果方差特别大别急着加重复次数先检查参数是否接近系统临界点。比如ρ0.95时队列长度波动会非常大这是排队系统的天然特性不是代码BUG。这种情况下报告里要明确标注“高负载场景下结果方差较大建议结合理论公式交叉验证”。4. 银行排队仿真的5个常见坑现象、原因与解决办法4.1 现象仿真刚开始时队列几乎为空平均等待时间虚低第一次跑仿真你可能发现平均等待时间只有1分钟而系统利用率ρ在0.8以上这显然不符合直觉。原因仿真从空系统启动前几百分钟队列才逐渐积累这段时间的数据拉低了整体平均值。解决办法设置预热期warm-up比如前200分钟或前200个客户不计入统计。上面的代码已经实现了按时间丢弃预热期数据你也可以按客户序号丢弃前N个客户。一个经验值是预热期至少覆盖到达间隔的100倍。4.2 现象同一组参数两次运行结果差30%以上如果你没有设置随机种子SimPy会从系统时间取种子每次运行都是不同随机序列。结果有波动正常但波动大到结论相反就不可接受。解决办法每次仿真前调用random.seed(...)并把种子值写进实验报告的附录。注意当你改变参数组合时最好也用不同的种子组避免所有结果来自同一个随机序列导致的自相关。我在实验里喜欢用seed 100 run_index这样的规则生成种子简单直接。4.3 现象把服务时间设成固定值导致等待时间被低估有些初学者为了省事直接写yield env.timeout(1.5)表示服务时间固定为1.5分钟。这相当于把指数分布换成了定长分布系统波动性降低平均等待时间会明显低于真实值。因为M/M/c模型假设服务时间服从指数分布而指数分布有较大概率出现长服务时间这才是排队的核心来源。解决办法用random.expovariate(mu)生成服务时间mu是每小时服务率。如果你的业务数据表明服务时间更接近正态分布那就改用random.gauss但报告里要明确说明这是M/G/c模型不能再用M/M/c的理论公式做对照。4.4 现象仿真时长太短到达稳态前就结束结果不稳定我见过有人把仿真时长设为500分钟结果队列长度曲线还在上升报告里得出一堆奇怪结论。原因排队系统需要时间进入稳态尤其当ρ接近1时比如ρ0.95系统需要极长时间才能达到平衡。解决办法在正式实验前做一个预实验把时间从500、1000、2000、4000分钟各跑一次看平均等待时间是否趋于稳定或者绘制累计平均等待时间曲线当曲线基本水平时再选定时长。我一般用2000分钟保底如果ρ超过0.9则改成4000分钟。4.5 现象报告图表没标单位表格不写参数设置答辩被指不严谨这是写报告时最常见的翻车点。仿真时间单位是分钟到达率是“人/小时”如果图中只写数字读者完全不知道纵轴是分钟还是秒。原因绘图时图例和坐标轴标签不完整实验表格里只写了参数组合没写固定参数。解决办法在每张图的横纵轴标注“时间分钟”、“平均等待时间分钟”在每组实验表格下方注明“服务率固定为40人/小时仿真时长2000分钟预热期200分钟重复5次取均值”。这个习惯一开始就要有别等报告写完再补。5. 让实验结论更可信用理论值校验仿真再用超阈值等待比例说话仿真结果写出来后最好先用排队论理论值验证模型是否正确。以M/M/3、λ60、μ40为例你可以用Erlang C公式计算理论平均等待时间或者直接用常见的Allen-Cunneen近似公式来估算。如果仿真值落在理论值±5%范围内说明仿真实现正确。我习惯在实验报告里放一张对比表左边是理论值右边是仿真值差值标注为误差百分比。这一步能让报告的专业度提升一个档次因为评审会认为你不只跑代码还做了模型验证。另一个更实用的指标是“等待时间超过某阈值的客户比例”。平均等待时间有时会掩盖问题比如平均8分钟但20%的客户等了20分钟以上。银行业务对等待很敏感一般设定SLA为“90%客户等待不超过10分钟”。你可以统计仿真结果中等待时间超过10分钟的客户占比作为方案对比的第二指标。改进一下代码在customer进程里加一个sla_breach计数器最后除以总客户数就能得到违约率。这个指标比单纯的平均值更能说明“加一个窗口是否真的解决客户抱怨”。最后说一个我自己的教训第一次做实验时我没设warm-up最长队列出现在仿真刚开始的前50分钟导致平均队列长度偏高让一个本来可行的加窗口方案被误判为无效。后来我直接把前三个小时的采样点全部丢弃结论就完全不同了。从那以后我的每一个仿真脚本先写warm-up再写主逻辑这个习惯沿用至今。希望这份操作顺序和代码能帮你少走弯路祝你的实验报告一次过。本文还有配套的精品资源点击获取

相关新闻

图书销售管理系统数据库设计:从ER图到MySQL建表完整实战

图书销售管理系统数据库设计:从ER图到MySQL建表完整实战

简介:适用于计算机相关专业数据库课程大作业的图书销售管理系统数据库设计资料,完整覆盖项目背景、需求分析、概念模型设计、逻辑模型设计、数据库建库与数据录入、SQL查询与更新操作、遇到的问题及解决方案等环节。文档以SQL Server为载体,围…

2026/10/11 14:00:15 阅读更多 →
为什么简历不能放照片、不能转Word?LaTeX简历模板resume-template FAQ完全速查(ATS优化3大疑问)

为什么简历不能放照片、不能转Word?LaTeX简历模板resume-template FAQ完全速查(ATS优化3大疑问)

【免费下载链接】resume-template The best LaTeX resume template. 项目地址: https://gitcode.com/gh_mirrors/resumetemplate3/resume-template 点击查看 免费下载 resume-template 是一个专业的 LaTeX 简历模板,帮你生成排版精美、ATS(招…

2026/10/11 14:00:15 阅读更多 →
金蝶专业版16.0安装程序避坑指南:环境检查与中间层配置实战

金蝶专业版16.0安装程序避坑指南:环境检查与中间层配置实战

简介:金蝶专业版16.0安装程序面向中小企业财务与信息化实施人员、金蝶软件初学者及需要本地部署环境的运维人员,用于搭建金蝶KIS云专业版V16.0的完整运行环境。压缩包共52个文件,约434.03MB,以rar分卷安装包为主,辅以j…

2026/10/11 14:00:15 阅读更多 →

最新新闻

进程地址空间深度剖析:虚拟地址转换、堆栈增长与内存问题定位

进程地址空间深度剖析:虚拟地址转换、堆栈增长与内存问题定位

写进程地址空间第一篇文章的时候,我把虚拟内存的整体框架拆开讲了一遍:从代码段到栈,从堆到内存映射段,把一张内存布局图硬生生画了半小时。文章发出后,有同学私信问我:既然地址空间只是个“虚拟”的概念&a…

2026/10/11 14:45:43 阅读更多 →
Excel多表合并三大方案:Power Query/VBA/Python实战指南

Excel多表合并三大方案:Power Query/VBA/Python实战指南

简介:本资源是一份面向Excel初学者与办公人员的实用VBA自动化教程,聚焦解决多工作表批量合并这一高频痛点问题。文档详细讲解了如何通过一段可直接运行的VBA代码,将多个结构一致的Excel工作簿(如各部门销售表、各门店日报等&#…

2026/10/11 14:45:43 阅读更多 →
Python协同过滤旅游推荐系统:从矩阵构建到线上验证的完整文档指南

Python协同过滤旅游推荐系统:从矩阵构建到线上验证的完整文档指南

简介:这份文档面向计算机相关专业学生与旅游推荐系统开发者,围绕“信息过载”下如何满足用户个性化出行需求展开,可作为毕业设计、课程设计或推荐算法入门项目的参考模板。压缩包内仅含1个docx文件,约8.64MB,内容为完整…

2026/10/11 14:45:43 阅读更多 →
深度学习100道选择题:知识图谱的压力测试仪

深度学习100道选择题:知识图谱的压力测试仪

简介:本资源是一套系统性的深度学习基础理论自测题集,面向人工智能初学者、高校学生及备考求职者,旨在帮助读者快速检验监督学习、无监督学习、模型评估、正则化、降维、目标检测等核心概念的掌握程度。题库共100道高质量单选题,覆…

2026/10/11 14:45:43 阅读更多 →
DSSS抗窄带干扰MATLAB仿真:从扩频增益到频域置零与半解析BER

DSSS抗窄带干扰MATLAB仿真:从扩频增益到频域置零与半解析BER

简介:这是一份面向无线通信学习者与MATLAB仿真实践者的DSSS扩频通信抗窄带干扰仿真代码包,聚焦直接序列扩频系统在窄带干扰环境下的建模与性能分析,适合通信工程、电子信息类专业学生及需要理解扩频抗干扰机制的开发者参考。包内共3个文件&am…

2026/10/11 14:45:43 阅读更多 →
单链表基础三题详解:删除节点、反转链表与找中间节点

单链表基础三题详解:删除节点、反转链表与找中间节点

链表这块内容,大学里第一次接触的时候觉得简单,无非是节点加指针。可真到动手写题的时候,删除节点能删丢一半,反转链表能绕成环,找中间节点还会因为奇偶数量吵半天。单链表综合练习里的“删除指定值节点”“反转链表”…

2026/10/11 14:44:43 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 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/11 10:45:37 阅读更多 →
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/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练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/11 14:36:54 阅读更多 →