news 2026/10/3 9:03:51

用Django打造电脑配置推荐系统:从规则建模到部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Django打造电脑配置推荐系统:从规则建模到部署实战

这台电脑怎么配?——这几乎是每个装机群每天都会出现的问题,也是不少计算机专业学生毕业设计题目里反复出现的一道经典题。我把这个题目用 Django 完整实现过一版,从需求分析到推荐引擎再到部署上线,一路踩了不少坑。这篇文章把完整思路写出来,适合三类人看:一是正在为课程设计或者毕设选题发愁的同学,二是想用 Django 练手、做点真正有业务逻辑项目的开发新手,三是打算做类似"导购型 Web app"却不知道推荐逻辑怎么落地的朋友。文章不会只给增删改查,我会把最关键的选配规则建模、评分函数、参数库搭建全部讲透。

1. 为什么个人电脑选配适合用Django开发:选题与框架的双向选择

很多人第一眼看到"选配 app",脑子里蹦出来的就是配件列表加购物车,最多再来一个登录注册,本质上还是 CRUD。但实际做下来你会发现,这个题目的核心难点根本不在增删改查,而在于怎么把"装机经验"翻译成计算机能理解和执行的结构化规则。这部分才是选题的价值所在。

1.1 这个题目真正考察的能力

任何一个毕业设计或课程设计,考核的都是三件事:领域建模能力、业务规则处理能力和工程落地能力。电脑选配恰好把这三点全占满了。先说领域建模——电脑配件不是孤立的,CPU、主板、内存、显卡、电源、机箱之间存在大量约束关系,如何用数据模型表达清楚,直接决定系统质量。然后是业务规则——同样预算下,有人想要游戏帧率,有人想要多核渲染,有人想要安静低功耗,这就需要推荐策略可配置,不能写死在代码里。最后是工程落地——Django 的 ORM、Admin、模板、DRF 这些现成组件能帮你把精力集中在真正有挑战的部分。

如果你只是做一个普通的"配件信息展示平台",说实话,任何框架都能做,面试或答辩时也很难出彩。但如果你把"推荐引擎"做成系统的内核,让用户输入预算和用途后真的能拿到一套物理上可点亮、性价比又合理的配置单,那这就是一个有技术亮点的项目,而不是"又一个管理系统"。

1.2 Django 在这个场景下的三个不可替代点

第一个点是 Django Admin。硬件参数库的维护是整个系统的日常核心工作,配件型号动辄几百上千条,手动在数据库里改 SQL 根本不现实。Django Admin 自带列表筛选、搜索、分页、富文本编辑,注册好模型后,运营维护人员(或者你自己)可以直接在后台录入 CPU、显卡、主板数据,不需要写任何页面。这个成本是其他框架很难给的。

第二个点是 ORM 对动态筛选查询的支持。推荐引擎需要反复执行"在某价格区间内找符合某种接口类型的配件"这类查询,Django ORM 的链式 filter 配合 Q 对象、JSONField 的键查询,可以让筛选逻辑非常灵活地拼接,不用写一堆原生 SQL。

第三个点是模板与接口的可进可退。你可以先用 Django 模板全栈开发,一个 HTML 页面 + 一个视图函数就能跑通演示;后期如果想把前端换成 Vue 或者套壳做成小程序,加一个 Django REST Framework 就能把推荐接口原样暴露出去。这个延展性对"毕设之后再扩展"来说太舒服了。

1.3 和其他语言方案的横向对比

标题里提到了 Java、PHP、C#,这几个方案我也都了解过,各有优势,但在这个具体题目上 Django 确实更合适。我简单说下对比结论。

技术方案推荐引擎表达Admin 后台开发速度主要短板
Django(Python)规则形态灵活,评分函数写起来顺手内置,零成本非常快性能上限不如编译型语言,但本项目足够
Spring Boot(Java)强类型约束严格,适合大型工程需另写或引入第三方较慢,配置多对快速迭代和脚本化数据处理不够轻便
Laravel/ThinkPHP(PHP)也能实现,语法体现规则稍绕有配套管理后台插件快数据处理、算法扩展生态弱于 Python
ASP.NET(C#)可用,LINQ 表达查询很强有 Admin 类库但不成熟中Linux 部署折腾,社区贡献偏企业级

这张表不是想说服所有人都转 Django,而是说在"个人电脑选配"这种需要快速验证规则、大量处理配件数据、又要有一个像样后台的师生场景里,Python 整体舒适度最高。我自己用 Django 从零到最后跑通只花了两周,其中一半时间花在数据整理而不是写代码上,这个效率换 Java 系很难达到。

2. 选配引擎的核心:把DIY经验翻译成约束与评分规则

这一章是整个项目的灵魂。你可以偷偷不写漂亮的页面,但绝不能不做选配规则。那些外包或者代做做得敷衍的版本,基本都是在"配件列表"里选出几样然后简单求和,没有真正的兼容性校验——这种系统在实际装机场景里是没有任何价值的,评委一旦追问就露馅。

2.1 兼容性约束的四条主线

电脑能点亮、能稳定运行,首先必须满足物理和电气上的兼容。我做的时候把约束分成了四类,建模阶段想清楚这一类,后面所有代码都好写。

第一条是插槽约束。典型场景是 CPU 和主板的对应关系:Intel 的 LGA1700 平台、AMD 的 AM5 平台,不同代际 CPU 需要不同芯片组主板支持。显卡和主板之间则要考虑 PCIe 插槽,虽然现在兼容性宽松,但老主板配新卡的 BIOS 兼容问题还是值得校验。

第二条是电气功耗约束。电源额定功率必须大于整机峰值功耗并留有一定余量。经验算法是:电源功率推荐值 = (CPU 最大功耗 + 显卡最大功耗 + 50W 基础功耗) × 1.2。这个 1.2 倍余量是多年装机实践的经验值,直接记在规则参数里,而不是写进代码。

第三条是内存世代约束。DDR4 和 DDR5 的插槽物理上不兼容,主板支持哪个世代、支持几个通道、最高频率多少,都要和内存条参数校验。很多新手抄作业时会漏掉这个约束,因为 CPU、主板大家都关注,内存条却经常被当成无脑可配的部件。

第四条是物理尺寸与散热约束。机箱支持的主板规格(ATX、M-ATX、ITX)是否匹配,散热器高度是否在机箱限高内,显卡长度是否放得进机箱,这些在真实装机中都是硬约束。在做数据模型时,我建议至少把主板规格和机箱规格建进去,散热器限高和显卡长度可以作为扩展字段留着。

2.2 评分规则:从"能开机"到"值得买"

满足了兼容性只是及格线,一个选配系统真正的价值在于对"性价比"的判断。这里我用了一个非常朴素的思路:给每个配件打一个综合性能分,再算出"每花一块钱能买到多少性能分",得分高的方案优先推荐。

具体做法是:CPU 用核心数、线程数、基础频率、加速频率算一个基础 Bench 分,也可以直接参考 PassMark 或 Cinebench 的公开分数;显卡用显存容量、显存位宽、CUDA/流处理器数量、核心频率作为输入。为了避免不同品牌数据单位不同导致的偏差,我实际用的是外部评分数据作为基准——在参数表里直接存一个 benchmark_score 字段,来源可以是自己跑分采集,也可以参考公开评测数据。

然后定义评分函数:

def cost_performance(part): """性价比分数 = 性能分 / 价格,并做归一化处理""" if not part.benchmark_score or not part.price: return 0 return round(part.benchmark_score / part.price, 6)

整机评分则需要根据用户场景给不同维度加权重,比如游戏主机把显卡权重设为 0.5,CPU 设为 0.3;办公主机反过来。这套权重不放在代码里,而是放进一张 usage_profile 表,让用户选择"游戏 / 办公 / 视频剪辑"时切换不同的权重向量。这就是把业务规则数据化的思路,也让后期调参变得特别方便。

2.3 用表结构承载规则:不写死在视图里

我在初版项目里踩过一个很大的坑:把兼容性判断写成了 Python 里的 if-else 链条,比如if cpu.socket == 'LGA1700' and motherboard.socket == 'LGA1700'。看起来没毛病,但每次新增一款配件、新增一种新接口(比如从 DDR4 升级到 DDR5),我都要改代码重新部署,而且代码越来越像意大利面。

后来我改成了一张 CompatibilityRule 表,用"规则配置"代替"代码分支"。每条规则记录:源配件类型、目标配件类型、比较字段、比较方式、期望值。比如 CPU 与主板兼容规则可以记为:source_type="cpu"、target_type="motherboard"、condition_key="socket"、condition_op="eq"、condition_value="LGA1700"。推荐引擎遍历候选配件对时,只需要查这张表逐条校验即可。新硬件上市后,管理员在后台录一条新规则就完事,完全不用动代码。

有人会说这太像自制规则引擎,是不是过度设计?我的判断是:如果这个项目只是交作业,确实有点重;但如果你想在答辩时展示"业务规则可配置化"的思路,或者以后往推荐系统方向深挖,这个设计绝对值得。而且它让整个校验逻辑变得可测试,每一条规则都能单独验证,这本身就是加分项。

3. Django模型与接口落地:一块一块搭积木

规则想清楚了,代码实现就是水到渠成的事。这一章我按模型、推荐接口、管理后台和页面三个层面讲,尽量给出能直接用的代码骨架。

3.1 数据模型设计:让 ORM 直接表达配件与规则

配件表我用了"宽表 + JSONField"的混合方案。公共字段如类型、品牌、型号、价格变成独立列,方便查询和排序;动态规格比如 CPU 的缓存、显卡的流处理器数量,全部塞进spec_json,避免为每种配件建一张表。一开始我尝试过每种配件一张表(CPU 表、显卡表、主板表),后来发现推荐引擎处理时要在五六张表之间做 union 和类型转换,非常痛苦。换成单一 Part 表加类型字段后,统一查询和评分全部简化了。

class Part(models.Model): PART_TYPES = [ ('cpu', 'CPU'), ('gpu', '显卡'), ('motherboard', '主板'), ('memory', '内存'), ('storage', '硬盘'), ('psu', '电源'), ('case_', '机箱'), ('cooler', '散热器'), ] part_type = models.CharField('配件类型', max_length=20, choices=PART_TYPES) name = models.CharField('型号名称', max_length=200) brand = models.CharField('品牌', max_length=50) price = models.PositiveIntegerField('参考价格(元)') benchmark_score = models.FloatField('综合性能分', default=0) spec_json = models.JSONField('详细规格', default=dict, blank=True) created_at = models.DateTimeField('创建时间', auto_now_add=True) updated_at = models.DateTimeField('更新时间', auto_now=True) class Meta: db_table = 'part' constraints = [ models.UniqueConstraint( fields=['part_type', 'name'], name='uniq_part_type_name' ) ] def __str__(self): return f'[{self.get_part_type_display()}] {self.name}' def score(self): return self.benchmark_score / self.price if self.price else 0

CompatibilityRule 表我单独建了一个模型,前面的章节已经提到过它的结构。这里还要补一张 Build 表,记录每次推荐的完整配置单,方便用户保存方案、对比方案,这也是答辩时展示"系统有数据闭环"的重要证据——用户行为数据沉淀下来,后期可以做很多分析,比如"5000 元档位里最常被同时选中的 CPU 和显卡组合"。

class Build(models.Model): user = models.ForeignKey('auth.User', on_delete=models.CASCADE, null=True, blank=True) purpose = models.CharField('使用场景', max_length=20, default='general') budget = models.PositiveIntegerField('预算(元)') parts = models.ManyToManyField(Part, through='BuildItem', related_name='builds') total_price = models.PositiveIntegerField('总价') created_at = models.DateTimeField(auto_now_add=True)

3.2 推荐接口的实现:约束校验 + 评分排序

推荐接口是核心,我给它设计了四个步骤:候选集生成、约束过滤、组合评分、结果返回。候选集生成直接用 Django ORM 按价格上限筛选;约束过滤遍历候选 CPU、主板、内存等组合,逐一查询 CompatibilityRule 做校验;组合评分就调用上一章讲的权重公式。

这里我给你一个简化的推荐主流程代码片段,组合逻辑但思路足够参考:

def generate_recommendations(budget, purpose='game'): parts_pool = {} for ptype in ['cpu', 'gpu', 'motherboard', 'memory', 'storage', 'psu']: parts_pool[ptype] = Part.objects.filter( part_type=ptype, price__lte=budget ) best_build = None best_score = 0 for cpu in parts_pool['cpu']: for gpu in parts_pool['gpu']: for mobo in parts_pool['motherboard']: if not check_compatible('cpu', cpu, 'motherboard', mobo): continue if not check_compatible('gpu', gpu, 'motherboard', mobo): continue memory = pick_memory(mobo, parts_pool['memory']) psu = pick_psu(cpu, gpu, parts_pool['psu']) if memory is None or psu is None: continue total = cpu.price + gpu.price + mobo.price + memory.price + psu.price if total > budget: continue build_score = compute_score(cpu, gpu, memory, purpose) if build_score > best_score: best_score = build_score best_build = (cpu, gpu, mobo, memory, psu, total) return best_build

这个全排列写法在配件数量小的时候完全够用,但如果一个类型下几百个配件,三层嵌套就非常慢了。我在实际项目里做了两处优化:一是先用接口类型做粗筛,比如挑主板时只保留和候选 CPU 插槽匹配的型号,把候选集从 100 缩小到 10;二是把预算分配做成比例预切分,比如游戏场景下显卡占总预算 40%、CPU 占 25%,这样游戏显卡的候选集从一开始就只查价格在budget * 0.4附近的型号。性能优化空间很大,你完全可以在答辩时把这部分当成亮点讲。

3.3 管理后台与前端页面:最少代码实现可用闭环

模型注册进 Django Admin 后,我顺手自定义了 list_filter、search_fields 和 inlines,让配件录入和管理员筛选方便一点。前端页面我最初用的是 Django 模板加一点点原生 JavaScript,主页面是一个预算输入框加一个使用场景下拉框,用户点击"生成配置"后异步请求推荐接口,返回的配置单表格展示,并且可以一键保存到 "我的方案" 列表。

如果你打算以后接小程序或者 App 端,我建议直接用 Django REST Framework 把推荐接口封装成标准 JSON API,前端用什么技术栈都可以对接。毕设的话,模板 + 轻量 JS 已经很有演示效果,没必要一上来就整前后端分离,那只会增加后端和联调的工作量。

4. 硬件参数库的构建:没有数据,推荐引擎就是空转

推荐引擎写得再漂亮,没有高质量的配件参数数据也只能是一个演示壳子。我在这部分花的时间比写代码还多,这里把数据构建的经验分享给大家。

4.1 参数数据从哪来

常见来源有三个:公开硬件评测站点的数据、电商商品参数页、以及自己手工维护的表格。最稳妥的做法是手工整理一份 CSV 然后通过脚本导入,我第一次做了接近 300 条主流配件数据,覆盖了 CPU、显卡、主板、内存、电源五种核心配件,完全够演示和答辩用。如果时间紧张,也可以先只做 CPU 和显卡两类数据,因为它们在推荐评分里权重最高。

有人问为什么不直接爬电商参数页?可以,但要注意这不是项目的核心工作,而且页面结构调整频繁、反爬验证多,花两小时写爬虫可能只够抓 20 条有效数据,性价比太低。手工整理还能顺便保证数据清洗质量。如果你确实想用半自动方式,我的经验是至少准备一个统一的导入模板,字段固定为:part_type, name, brand, price, socket, tdp, power_draw, length, benchmark_score,然后用 Django management command 批量导入。

4.2 参数规范化与去重

硬件参数的规范化是一个容易出彩的地方。比如 CPU 插槽的写法,有的数据源写"LGA1700",有的写"Socket LGA1700",如果不做统一,规则校验时明明兼容的两个配件会被判为不兼容。我的做法是在导入脚本里加一个 normalize 阶段,把所有枚举型字段统一为小写带连字符的规范表达,比如intel_lga1700、amd_am5。

去重也值得说一句。同一个型号在电商页面可能因为套装或颜色不同出现多条记录,我用(part_type, name)唯一约束兜底,导入前先跑一遍查重脚本,重复数据合并到最新价格即可。型号命名规则建议统一:品牌 + 系列 + 型号,比如"Intel Core i5-13490F",不要一会儿写"i5-13490F",一会儿写"i5 13490F",否则用户体验和后续分析都会受影响。

4.3 数据验证与批量导入

我写了两个 Django management commands:import_parts用于从 CSV 导入配件,validate_parts用于扫描并报告潜在不一致,比如 CPU 的 TDP 插座和主板支持的插座不匹配、电源功率小于整机预估功耗 300 瓦以上等。这些校验规则本身就是兼容性规则的补充,也能在录入阶段提前发现数据问题,避免推荐出明显不合理的配置。

这里有个小技巧:Django Model 的clean()方法可以在 Admin 表单保存时自动触发校验。比如电源额定功率字段小于 300W 时给出 warning,主板型号里出现"ITX"却声称支持 ATX 机箱时直接报错。写几个validation_error判断逻辑,后台录入人员就能得到即时反馈,数据质量自然提上去了。

5. 从本地跑通到部署上线:我踩过的典型坑

本地runserver跑得飞快,不代表项目真的能用。我把项目部署到服务器之后遇到了一串经典问题,这里列出几个最有共性的,如果你只是本地演示,这部分可以先收藏。

5.1 生产环境组合与数据库切换

我用的是 Nginx + Gunicorn + Django 4.2 LTS + PostgreSQL。项目一开始用的 SQLite,因为零配置,但部署后推荐接口并发稍微一高就容易出现锁库问题,还出现过查询超时。切到 PostgreSQL 成本其实很低,只需要改 DATABASES 配置和装一个 psycopg 依赖,然后python manage.py migrate就完成迁移。内存型数据库或者云数据库也可以,但对毕设来说 PostgreSQL 完全够了。

顺便说一句环境隔离。我建了一个.env文件存放 SECRET_KEY、数据库密码、DEBUG 开关,用 django-environ 读取,绝不让敏感信息出现在 settings.py 里。这个习惯你可以从现在开始养成,不光是这个项目,以后工作也受益。

5.2 静态文件目录与DEBUG开关

这是我第一次部署时卡得最久的问题:开发时DEBUG=True,静态文件由 Django 自动处理;一上生产把 DEBUG 关掉,整个 Admin 后台 CSS、JS 全丢了,页面光秃秃的。原因就是 Django 在DEBUG=False时不再托管静态文件,你需要先python manage.py collectstatic,把所有静态文件集中到 STATIC_ROOT,再由 Nginx 直接服务。

我当时配的 Nginx 落点是/var/www/your_project/static/,location 块要写在 Django 代理之前。这个坑真的非常经典,建议所有 Django 初学者提前记住配置顺序:location /static/ { alias ...; }放在location / { proxy_pass ...; }前面,两者不能反过来。

5.3 ORM查询性能与缓存

推荐结果的展示列表里有一个很常见的性能问题:N+1 查询。比如展示 Build 的每个配件名称和价格时,不加优化会先查到 Build 再逐个查 Part,配件一多查询次数直线上升。解决办法是配置 ManyToManyField 后用prefetch_related('parts'),一条语句把关联数据全取回来。我在项目里给 Build 列表接口加了 prefetch 之后,页面响应时间从接近 1 秒降到了 100 毫秒以内。

另一个优化是给推荐接口加缓存。硬件参数表不会频繁更新,而推荐计算比较耗时,我直接用 Django cache 框架给"同一预算 + 同一使用场景"的推荐结果加了一个 30 分钟缓存,用预算数字加场景名拼缓存 key。效果立竿见影,第二次点击基本是瞬时返回,答辩演示时观感特别好。

6. 答辩与展示:让评委觉得这个项目有含金量

最后说点项目之外的软实力。很多同学代码写得不错,答辩一紧张就变成"我做了个系统,可以登录、可以录入、可以查询",完全讲不出亮点。这一章我给出我实际用过的一套展示策略。

6.1 演示脚本怎么设计

不要一上来就登录进后台点来点去,那是最催眠的演示方式。我的顺序是:先用 1 分钟讲选配的核心痛点——"用户想要 6000 元游戏主机,但如果只按价格选配件,很容易选到互相不兼容的组合";然后现场演示输入预算 6000 元、选择游戏场景,生成一套配置单,马上切换预算到 4000 元,让观众看到推荐结果明显变化,说明系统不是写死的;最后打开后台的 CompatibilityRule 表,指着一条 CPU 与主板的兼容规则说"这类规则是可配置的,新品上市只要加一条记录就行,不需要改代码"。

这套演示逻辑其实暗含了"发现问题-解决问题-证明方案有效"的完整链路,比单纯列功能有说服力得多。

6.2 论文与说明文档的架构

如果你的课程要求写设计文档或毕业论文,我的章节建议是:需求分析里重点写清楚"传统人工装机的信息过载问题"和"现有电商导购系统不做兼容性校验"这两个痛点;系统设计部分把规则引擎和数据模型单独成章;核心算法部分详细描述评分函数和约束校验流程;测试部分除了功能测试,一定要加上"规则正确性测试",比如构造已知兼容和不兼容的配件组合,断言推荐引擎的行为符合预期。这部分是很多同学忽略的,写上就能拉开差距。

6.3 几个高频答辩问题与应对思路

第一个高频问题:"你的推荐结果一定装得起来吗?"——回答思路是承认物理尺寸和散热这类约束覆盖还不全面,但系统是规则驱动的,说明新增规则即可完善。第二个问题:"和别人做了一个商城系统有什么区别?"——回答核心是推荐引擎和规则可配置化。第三个问题:"数据库字段为什么这么设计?"——参考答案是单一 Part 表加 JSONField 是为了统一查询和简化推荐逻辑,兼容性规则独立成表是为了可扩展性。不要怕问题刁钻,只要你能指着代码和表结构解释设计动机,评委一般都不会为难你。

最后说点实在的

这个项目做完,我最深的体会是:技术框架都是现成的,真正值钱的是你愿不愿意把一个领域里的"隐性经验"拆解成"显性规则"。装机这件事,老手靠直觉,新手靠问人,而你的系统就是把直觉变成可执行代码的过程。如果你做到后面发现推荐结果还需要人工修正,不要沮丧,那恰恰说明你已经开始像做产品的人一样思考了。最后分享一个小技巧:无论答辩老师问什么,只要你能打开数据库表,把一条兼容规则和他对应的字段指给他看,这道题你就已经赢了。

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

电商评论情感分析实战:从清洗到Streamlit看板的工程落地

简介:这是一套基于Python实现的电商评论情感分析系统,面向数据分析初学者、课程设计学生及毕业设计开发者,聚焦真实电商场景下的文本情感判别与产品口碑挖掘。资源包含1380个文件,主体为478个Python脚本(含Streamlit可…

作者头像 李华
网站建设 2026/10/3 9:03:29

Transformer架构原理深度解析:从注意力机制到工业落地

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

作者头像 李华
网站建设 2026/10/3 9:03:03

Spring Boot 3.4整合Swagger与Mybatis-plus实战:版本选型与踩坑

Spring Boot 3.4 发布之后,我第一次升级手头项目就卡在了 Swagger 上:旧的 springfox 依赖直接起不来,Mybatis-plus 的 starter 也反复报版本冲突。折腾了两天,最后把整套整合方案从依赖到配置重新理了一遍,才稳定落地…

作者头像 李华
网站建设 2026/10/3 9:02:53

用Pandas做数据清洗与数据预处理的完整实战指南

做了快五年数据相关的工作,有个体会越来越深:模型调参调到头也就那样,真正决定上限的往往是训练集本身的质量。我记得有一次用一套挺复杂的模型做预测,结构照论文搭的,调参工具也用得很熟练,可精度就是卡着…

作者头像 李华
网站建设 2026/10/3 9:01:49

基于Pascal文法的编译器前端实战:从词法分析到解释执行

简介:这份资源是面向计算机专业学生与编译原理学习者的Pascal文法编译器课程设计完整实现,围绕词法分析、语法分析、语义检查与代码生成等核心环节展开,适合正在做课程设计或希望动手理解编译器构造流程的中高级学习者。压缩包共140个文件&am…

作者头像 李华
网站建设 2026/10/3 9:01:36

棉花折叠胚胎的发育编程机制与演化启示

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

作者头像 李华