news 2026/10/10 1:14:11

2024电工杯B题微电网储能优化配置:建模主线与求解代码避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
2024电工杯B题微电网储能优化配置:建模主线与求解代码避坑指南

简介:面向2024年电工杯B题数学建模赛题的完整解析资料包,适合参加数学建模竞赛的学生以及需要完成膳食营养评价与优化任务的研究者。资源围绕附件1、附件2中的一日食谱展开,系统讲解能量、蛋白质、脂肪、碳水化合物、维生素和矿物质摄入量的计算与达标判定,包括能量摄入量、营养素摄入量等公式的运用与逐项分析,并给出膳食结构评估、营养素缺乏或过量的调整策略,同时介绍基于平衡膳食原则的食谱优化模型与Python求解思路,帮助理解如何将营养学标准转化为数学约束并完成最优设计。资源包仅含1个docx文档,大小约32KB,文档内整合了完整解题过程、代码示例、调整前后的营养评价结果对比以及论文撰写建议,覆盖从问题分析、数据预处理、模型建立到求解评价的全流程,可直接对照学习或作为写作参考。该资料当前已有892人学习下载,内容紧凑且步骤清晰,适合希望快速掌握数学建模竞赛全流程和代码实现细节的读者。

1. 2024电工杯B题不是“算出来”而是“讲清楚”:先定建模主线再碰代码

2024电工杯B题这类园区微电网优化配置题,最容易让参赛队翻车的不是数学公式,而是拿到题后直接开写代码,最后交上去一个“能跑但说不清”的黑匣子。我自己的打法是反着来:先用两个小时把题目文字翻译成“目标函数 + 决策变量 + 约束条件”的清单,再决定用线性规划还是智能算法,最后才碰键盘。这篇笔记会按我真实的解题顺序走,从赛题拆解一直讲到可复现的完整代码骨架,并把求解器选型、SOC递推、倒送功率这几个高频坑单独拎出来说。适合第一次参加电工杯、或者有建模基础但还没独立写过完整求解代码的同学,跟着章节往下推,每一步都能直接搬进自己的论文和代码里。

2. 把赛题文字翻译成数学语言:目标函数、决策变量与三类约束怎么定

2.1 先读懂题目在问什么:配置层与运行层要分开建模

电工杯B题通常给的是园区一年的负荷曲线、光伏和风电的典型日出力数据,外加分时电价、变压器容量、储能单位造价这些参数,让你去“配置”储能或者风光储容量。这里的“配置”和“运行”是两件完全不同的事:配置层回答“装多少”,运行层回答“怎么充放”。很多队伍把这两个层次混在一个优化模型里,结果变量十几万个,求解器直接卡死。

我一般会先画一张分层关系图:外层是容量决策,内层是逐时段的充放电决策。容量决策决定储能额定功率和额定容量,运行层在给定容量下求每个时段的充放电功率,让整个生命周期收益最大。拆开之后,外层变量少,内层是标准线性规划,两层之间用“年化收益”这个数连接。这也是为什么两阶段框架在微电网优化里这么流行——它把“买多大电池”和“电池怎么用”解耦,代码逻辑清楚,论文里也容易画框图。

具体到建模,决策变量要分两类写清楚。容量类变量包括储能额定容量、额定功率,如果有光伏风电联合配置,还有光伏和风机的装机容量;运行类变量是每个时段的充放电功率、购售电功率。值得提醒的是,容量变量往往不是连续整数,储能容量通常按若干档位选,这时候模型会带整数变量,求解难度立刻上一个台阶。

2.2 目标函数怎么定才不会漏项:收益与成本的全口径盘点

目标函数是评卷老师第一个盯着看的地方。常见做法是最大化“全生命周期净收益”,或者最小化“年化综合费用”。这两个本质上等价,但口径必须统一。我踩过的坑是:收益按“元/天”算,成本却按“元/年”给,最后除出来一个离谱的回收期。

先把收益项分清楚。第一项是峰谷套利:在低谷电价时段充电,高峰电价时段放电,赚取价差;第二项是需量电费节省:很多园区按最大需量缴纳基本电费,储能削峰后可以把最大需量压下来,这是比峰谷套利更可观的一块收益;第三项如果有余电上网政策,还要加上光伏/风电上网收入。成本项则包括储能单位容量投资成本、单位功率投资成本、年运维成本,以及电池寿命损耗成本——第四项最容易被忽略,但评卷老师往往喜欢看到“考虑了寿命折损”这种细节。

我习惯把目标函数写成一张表而不是一长串公式:每一项收益/成本、计算单位、取值来源、是常数还是变量。表格列完再往公式里填,基本不会漏项。等写到代码里,这就是目标表达式的逐行注释来源。这里特别强调量纲统一:储能投资的单位成本按“元/kWh”给,但年收益按“元/年”算,投资成本一定要除以运行年限或者折算成年值,否则目标函数里两个数量级差着十万八千里,优化结果会被大数项牵着走。

2.3 约束条件清单:功率平衡、SOC递推与倒送限制

约束是这道题的真正考点。我按重要性排序,前三类是必须写的:功率平衡约束、储能SOC递推约束、充放电功率与容量约束;后两类是拿奖分水岭:充放电互斥约束和倒送功率限制。

功率平衡约束是每个时段都必须满足的等式:负荷功率等于光伏出力加风电出力、加储能放电功率、减储能充电功率、加从电网购入功率。这个式子如果写反了正负号,后面的结果全盘皆错。SOC递推约束则是储能的核心动态方程:下一时段荷电状态等于当前SOC加上充电效率折算的充电量,再减去放电量除以放电效率。初值SOC这里有个隐藏坑,下文避坑章节会专门讲。

倒送功率限制是B题里比较微妙的一条。园区配电网通常不允许功率反送,或者只允许反送不超过某个阈值,所以并网点功率要么大于等于零,要么小于等于一个给定的限值。这里有队伍会把不等式方向写反,导致光伏出力大的中午时段被“硬扣”,优化结果莫名其妙地多配储能。另外充放电互斥约束也容易被忽略:如果不加限制,线性规划会同时充电和放电来套利,虚增收益,后面避坑章节我会给具体处理方案。

提示:建模阶段不要急着写约束的完整矩阵形式,先用自然语言列清单,保证每个物理过程都有对应的约束表达式。我见过太多队伍在代码里漏了一个约束,结果论文公式和代码逻辑对不上,答辩时被追问到沉默。

3. 求解方案选型:线性规划还是粒子群?先过三道分水岭

3.1 三道分水岭问题:决定你用什么求解器

选求解器不是看哪个名气大,而是看模型数学性质。我每次建模完先问自己三个问题:有没有整数变量?目标函数和约束里有没有变量相乘?效率参数是不是常数?

第一道分水岭是整数变量。储能容量如果按档位选,或者充放电互斥用了0-1变量,模型就是混合整数规划。第二道是双线性项:如果目标函数里出现“容量×电价”这种容量变量和价格变量相乘,或者运行层和配置层没有解耦,模型就是非线性的。第三道是效率参数:如果充电效率恒定为0.95,那还是线性;如果考虑效率随SOC变化,就是非线性。回答完这三问,基本可以确定路线:全线性用scipy或PuLP,混合整数用CBC或Gurobi,非线性强就用粒子群或遗传算法。

以B题的典型设置来看,储能充放电效率通常给常数,分时电价也是给定的,收益项中没有容量和价格的乘积,因此主体模型是一个线性规划或混合整数线性规划。这也是为什么我不建议一上来就套粒子群——能用线性规划解决的问题,智能算法反而容易在约束处理上出幺蛾子。只有当你需要同时优化“容量档位”和“运行策略”,且运行策略内部还嵌套非线性约束时,才需要上智能算法。

3.2 智能算法不是万能的:两阶段框架“外层寻容量、内层算收益”

如果题目确实需要联合优化容量和运行策略,我推荐两阶段框架,而不是把变量全部塞进一个粒子里。外层用粒子群枚举储能容量或风光配比,内层对每一个候选容量调用线性规划求解最优运行收益,把收益值返回给外层作为适应度。这样做的好处是:内层LP保证运行最优,外层PSO只处理少量的容量变量,模型维度大幅降低。

我早期尝试过把几十个运行变量和容量变量全部塞进粒子群,结果每个粒子搜索空间巨大,跑一整夜也不收敛,而且约束违约后只能靠罚函数补救,罚函数系数又是玄学。换成两阶段之后,内层LP自带约束处理能力,外层只需要考虑容量边界和离散档位,收敛速度和稳定性都有明显改善。这个框架也更方便做敏感性分析——想分析储能成本变化的影响,只需在外层循环外部再套一层参数扫描。

下面是一个两阶段框架的骨架代码,内层用线性规划,外层用简单的网格搜索代替粒子群,方便你在自己机器上先跑通逻辑:

import numpy as np from scipy.optimize import linprog def inner_operation(cap_energy): """给定储能容量,求解全年最优运行策略(线性规划)""" # 简化:只优化一天48个时段的充放电,容量作为约束上界 n = 48 # 决策变量:[P_ch[0..47], P_dis[0..47], P_grid[0..47]] # 目标:最大化峰谷套利收益,这里取负号给linprog price = np.array([0.8] * 16 + [1.2] * 16 + [0.6] * 16) # 模拟分时电价 c = np.concatenate([price * 0.5, -price * 0.5, np.zeros(n)]) # 充电成本、放电收益、购电成本 # 约束:功率平衡等,这里仅示意SOC上下界 A_ub = [] b_ub = [] # 省略完整约束矩阵,核心是P_ch <= cap_energy/48, P_dis <= cap_energy/48 bounds = [(0, cap_energy / 48)] * n + [(0, cap_energy / 48)] * n + [(0, 1000)] * n res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs") return -res.fun # 返回最大收益 def outer_search(): """外层网格扫描容量,找收益拐点""" best_cap, best_profit = 0, 0 for cap in np.linspace(500, 5000, 20): profit = inner_operation(cap) if profit > best_profit: best_cap, best_profit = cap, profit return best_cap, best_profit

这段代码里,inner_operation接收储能容量,内层求解器算出该容量下的最大运行收益;outer_search用网格扫描代替粒子群,遍历容量候选值。注意linprog默认求解最小化,所以我给目标函数取了负号,最后再取反。如果你要跑真实数据,需要把功率平衡约束的完整矩阵补上,这里的A_ub是空占位。

3.3 求解器与参数怎么选:Gurobi、CBC还是scipy?

求解器选择看三个现实条件:许可证、模型规模、求解时间要求。学校有Gurobi/CPLEX许可证就优先商业求解器,B题这种规模的混合整数规划它们几乎是秒解;没有许可证就用开源方案,PuLP内置的CBC求解器处理几千个变量完全够用;如果模型是纯线性规划,scipy的highs方法是我最常用的,免安装、速度快、数值稳定。

参数设置上,最需要关注的是时间步长和整数变量的松弛容忍度。B题给的数据可能是15分钟一个点,一天96个点,一年就是35040个点,如果都放进运行层,矩阵规模直接爆炸。我一般先按1小时聚合成8760个点跑基线,等结果合理后再加密到15分钟验证精度。求解器本身的参数(如MIP Gap)默认值通常够用,但如果结果迟迟不收敛,可以把mip_rel_gap从默认的1e-4放宽到1e-3,速度提升明显且对最终经济性指标影响很小。

提示:不要迷信“用最贵的求解器就是好的”。B题评卷看重的是建模逻辑和结果分析能力,同样的题用scipy跑出漂亮结果,比用Gurobi但约束写错更有说服力。先跑通,再谈优化求解器。

4. 完整代码实现:从数据读取到求解器落地的模块化写法

4.1 数据模块:把Excel里的负荷与气象数据读成统一时间轴

B题原始数据通常是几个Excel表格,负载表、光伏出力表、风电出力表各自独立,时间戳还不一定对齐。这一步不处理好,后面所有优化结果都会变成“垃圾进、垃圾出”。我先写一个独立的数据模块,负责三件事:读取所有表、统一重采样、输出一个长表DataFrame。

import pandas as pd def load_data(load_path, pv_path, wind_path): # 读取原始表,假设时间列名为"time" df_load = pd.read_excel(load_path) df_pv = pd.read_excel(pv_path) df_wind = pd.read_excel(wind_path) # 统一时间列并设为索引 for df in [df_load, df_pv, df_wind]: df["time"] = pd.to_datetime(df["time"]) df.set_index("time", inplace=True) # 按15分钟对齐,缺失值前向填充(注意:B题数据一般是完整的,但保险起见) df_merged = pd.concat( [df_load.resample("15min").interpolate(), df_pv.resample("15min").interpolate(), df_wind.resample("15min").interpolate()], axis=1 ).fillna(method="ffill") return df_merged

这段代码的核心是resample("15min").interpolate()。先统一到同一个时间频率,再做线性插值。interpolate()用的是线性插值,适合光伏和负荷这种连续变化的曲线;如果数据有零值区间,比如夜间光伏出力为0,线性插值不会在0之间硬拉出负值,相对安全。最后fillna(method="ffill")处理头部缺失,避免第一行空值让后续约束矩阵维度错位。

参数说明:如果题目给的本身就是1小时数据,把"15min"改成"1h"即可。需要注意,重采样频率必须和电价时段边界对齐,否则峰谷套利的收益计算会差一个时段。我在实战中遇到过15分钟数据和电价时段边界错位半小时的情况,套利收益虚高,最后检查半天才发现是索引对齐问题。

4.2 模型构建:用PuLP写带SOC递推与互斥约束的线性规划

数据准备好之后进入建模模块。这里我用PuLP示范,因为它的变量和约束用中文能直接命名字典键,排查问题非常直观。下面的代码是一个缩略版,展示SOC递推和充放电互斥的写法,完整约束按你的题目需求扩充。

import pulp as pl def build_model(df, price, ess_cap, ess_power, eff=0.95): t_idx = df.index n = len(t_idx) prob = pl.LpProblem("ESS_Scheduling", pl.LpMaximize) # 决策变量:充电功率、放电功率、购电功率、SOC p_ch = pl.LpVariable.dicts("ch", t_idx, 0, ess_power) p_dis = pl.LpVariable.dicts("dis", t_idx, 0, ess_power) p_grid = pl.LpVariable.dicts("grid", t_idx, 0, None) soc = pl.LpVariable.dicts("soc", t_idx, 0.1, 1.0) # 充放电互斥的0-1变量 y=1表示充电,0表示放电 y = pl.LpVariable.dicts("y", t_idx, 0, 1, cat="Binary") # 目标:峰谷套利 + 减少购电成本(这里简化为最大化售电-购电) prob += pl.lpSum(price[t] * (p_dis[t] * eff - p_ch[t] - p_grid[t]) for t in t_idx) # SOC递推:soc[t+1] = soc[t] + eta_ch*P_ch/ess_cap - P_dis/(eff*ess_cap) for i in range(1, n): t_prev = t_idx[i-1] t_cur = t_idx[i] prob += soc[t_cur] == soc[t_prev] + ( eff * p_ch[t_prev] / ess_cap - p_dis[t_prev] / (eff * ess_cap) ) # 充放电互斥:P_ch <= M*y, P_dis <= M*(1-y) for t in t_idx: prob += p_ch[t] <= ess_power * y[t] prob += p_dis[t] <= ess_power * (1 - y[t]) return prob

这段代码里有两个关键点。第一是SOC递推约束的写法:充电效率乘在充电项上,放电效率除在放电项上,并且都用上一时段的功率更新当前SOC,避免循环引用。第二是充放电互斥约束使用Big-M法,这里的ess_power就是M值,因为充电功率上限不可能超过额定功率,用这个作为M既足够大又不至于让数值求解器产生病态。

参数说明:ess_cap是额定容量(kWh),ess_power是额定功率(kW),eff是效率。SOC上下限设为0.1和1.0,防止深度放电损伤电池,这个边界在论文里要写清楚。如果你要模拟电池寿命衰减,可以把SOC下限抬高到0.2,对比不同下限下的经济性,这是个很好的加分分析。

4.3 主程序闭环:跑批、导出结果、生成指标表

模型构建好了,主程序要完成“读数据-建模型-求解-写结果”的闭环。这里我习惯把多个容量方案放进一个循环里批量求解,输出每个方案的投资成本、运行收益、净收益,方便画对比图。

def main(): df = load_data("load.xlsx", "pv.xlsx", "wind.xlsx") price = {t: 0.8 if 0 <= t.hour <= 7 else 1.2 if 8 <= t.hour <= 11 else 0.6 for t in df.index} results = [] for cap in [500, 1000, 2000, 4000]: prob = build_model(df, price, ess_cap=cap, ess_power=cap / 2) prob.solve(pl.PULP_CBC_CMD(msg=False)) if pl.LpStatus[prob.status] == "Optimal": # 提取SOC和功率序列,计算年净收益 daily_profit = pl.value(prob.objective) invest_cost = cap * 800 / 10 # 假设800元/kWh,按10年折旧 net = daily_profit * 365 - invest_cost results.append({"cap": cap, "net": net, "profit": daily_profit}) pd.DataFrame(results).to_csv("output.csv", index=False)

PULP_CBC_CMD(msg=False)关闭求解器日志,不然四个方案跑完终端会刷屏。daily_profit * 365 - invest_cost把日收益年化再扣折旧成本,这里投资成本按10年直线折旧,和第二章目标函数的口径保持一致。输出CSV是为了后面画图方便,也方便论文里直接引用数据表。

提示:主程序里的price字典是我随手写的模拟电价,真实赛题会有明确的分时电价表。务必从题目附件里读电价,而不是在代码里手写,这是新手最容易犯的低级错误。

5. 避坑:B题里最典型的5个翻车点,每个我都替你们踩过

5.1 现象 → 原因 → 解决:先看代码层的5个高频坑

第一个坑是SOC初值设为0。现象是储能第一时段既不能放电也充不满电,优化结果收益偏低。原因是SOC从0起步,前面的时段全在充电,和真实运行场景不符。解决方法是把初始SOC设为0.5,或者干脆引入一个预热期:先跑前72小时作为“预热”,丢弃这段结果再统计收益。我倾向后者,论文里还能多写一句“避免初值影响”。

第二个坑是充放电同时发生。现象是优化结果里充电功率和放电功率在同一天都很大,峰谷套利收益虚高。原因是模型里没有互斥约束,求解器会“左手倒右手”刷收益。解决方法是加0-1变量互斥,也就是4.2节代码里的做法。如果你用纯连续变量不想引入整数变量,可以把每个时段的净功率写成一个变量:放电为正、充电为负,这样物理上就不可能同时充放,只是没法精确模拟充放电效率差异。

第三个坑是倒送功率约束方向写反。现象是光伏出力大的中午时段购电功率为负(倒送),但题目可能不允许倒送。原因是并网点功率平衡方程里正负号和不等式方向没对齐。解决方法是先明确“购电功率≥0”还是“购电功率≤限值”,在代码里把不等式写成p_grid[t] >= 0,如果确实允许倒送但有限值,就写p_grid[t] >= -limit。别小看这个负号,我见过队伍因此多配了两倍储能。

第四个坑是时间索引错位导致SOC递推链断裂。现象是求解器报Unbounded或者SOC出现负值。原因是重采样后索引里有重复时间戳,或者递推约束里t_prev和t_cur不是连续值。解决方法是读完数据后立即检查索引是否唯一且单调递增:df.index.is_monotonic_increasing。这一步只要花十秒钟,能省掉后面两小时排查。

第五个坑是量纲没统一,投资成本淹没在收益里。现象是优化结果永远选最大容量,因为成本项太小。原因是储能单位成本给的是元/kWh,但目标函数里收益按日算,成本按一次性算,两者直接相加。解决方法把投资成本折算成年值:annual_cost = total_cost * discount_rate / (1 - (1+discount_rate)^-years),这一步是经济性分析的基本功,论文里也更好看。

5.2 结果看着对但论文被打回:三类“软性失分”要提前堵住

第一类软失分是“只有数字,没有经济性分析”。评卷老师不想只看“最优容量是2000kWh”,他们想看回收期、内部收益率、敏感性分析。哪怕代码里只加三行计算,论文里多一张表,说服力立刻不同。第二类是“没有对比方案”:只有配置储能的结果,没有“不配置储能”的基准方案。没有基准,净收益就没有参照系。第三类是可视化粗糙:SOC曲线和功率曲线是最直观的验证手段,跑完直接把图导出来看,如果SOC在一天内剧烈波动到上下边界,运行策略一定有问题。

我还会做一个“极端场景测试”:把光伏出力全部设为0,看储能是不是自动转为纯峰谷套利模式;再把光伏拉满,看是否触发倒送约束。这两个测试如果通过了,模型在评卷老师眼里才是可信的系统,而不是“凑出来的曲线”。

注意:代码跑通不等于模型正确。我自己的验证顺序是:先看目标函数值在量级上是否合理,再看SOC曲线是否符合物理直觉,最后才看容量方案是否和经济预期一致。顺序反了容易浪费时间在调参而不是修模型上。

6. 收尾技巧:从能跑通到能拿奖的验证与可视化三板斧

第一板斧是画“储能运行日图”。选一个典型日(光伏高、负荷高峰明显的日子),把SOC、充电功率、放电功率、购电功率四条曲线画在一张图里。SOC曲线应该呈现“夜间低谷充电、白天高峰放电”的形态,如果SOC在白天频繁触顶或触底,说明容量边界设得不对或者互斥约束没起作用。

第二板斧是“敏感性分析表”。固定其他参数,把储能单位成本从600元/kWh按100元步长扫到1200元/kWh,记录每个成本下的最优容量和净收益。这张表放进论文,比任何文字都更有说服力。代码实现很简单,用for cost in range(600, 1300, 100)包住刚才的主程序循环即可。你会看到最优容量随着成本下降而上升,这就是“经济拐点”,是论文里最值得写的一个结论。

第三板斧是“基准对比”。永远先跑一个无储能方案作为基准,再跑含储能方案。净收益增幅除以储能投资得到“每单位容量带来的边际收益”,这个指标是答辩论证的核心抓手。我自己参赛时最常做的事就是先用一个极小的容量(比如1kWh)跑通全流程,再逐步放大,这样任何一个环节报错都能在分钟级定位。这也是我所有建模比赛保命的习惯,它让我少熬了不少夜。这几个验证步骤做完,你的论文就不再是“代码跑出来的结果”,而是一套经得起追问的逻辑闭环,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 1:13:38

PCA9422+ATmega1280构建硬件级电源管理闭环系统

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

作者头像 李华
网站建设 2026/10/10 1:13:29

海湾消防主机图形显示器4.0:从报警定位到平面图联动的实战指南

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

作者头像 李华
网站建设 2026/10/10 1:12:36

PCA9422 PMIC与PIC18F46K20 MCU的电源管理实战解析

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

作者头像 李华
网站建设 2026/10/10 1:11:36

SpringBoot+Vue校友社交系统:从部署到二次开发全攻略

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

作者头像 李华
网站建设 2026/10/10 1:10:38

Python手势识别实战:MediaPipe手部关键点检测与阈值调优

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

作者头像 李华