JavaWeb学生成绩管理系统:Servlet+JSP+MySQL+ECharts源码实战与避坑指南
简介这是一套面向JavaWeb初学者与课程设计开发者的学生成绩管理系统完整源码基于ServletJSPMySQL技术栈实现在原项目基础上新增了ECharts统计图模块可用于毕业设计、课程作业或框架入门练手。压缩包共344个文件约3.59MB其中86个Java源文件与86个class文件构成后端业务与DAO分层实现37个JSP页面负责视图渲染62个JS与28个CSS支撑前端交互与样式另含jar依赖、SQL脚本及字体图片等静态资源目录结构完整可直接导入运行。资源已有6724人学习下载热度较高。读者可从中获得一套可运行的成绩管理项目参考涵盖学生、教师、课程、成绩等模块的增删改查逻辑并借助新增的ECharts图表直观呈现成绩分布与统计结果便于理解Servlet请求流转、JDBC数据访问与前后端数据交互的完整链路适合对照学习与二次开发。1. 从一份“能跑的成绩管理系统”说起ServletJSPMySQL 到底能解决什么如果你手头正好有一份 JavaWeb 学生成绩管理系统的源码打开一看是 Servlet JSP MySQL 的组合还带 ECharts 统计图那这篇文章就是写给你的。我带过不少刚入门的同学他们拿到这类项目的第一反应往往是“这不就是个增删改查吗”结果一跑就翻车数据库连不上、中文乱码、图表空白、分页算错。其实这套技术栈虽然经典但坑一个不少。这个项目适合两类人一是课程设计或毕设需要交一个完整可演示系统的同学二是想通过一个真实项目把 Servlet 生命周期、JSP 作用域、JDBC 事务串起来的自学者。它不花哨但把 Web 开发最核心的请求响应链路走了一遍还额外加了 ECharts 做成绩分布和趋势统计演示效果比纯表格强不少。下面我就按“能干什么 → 怎么跑 → 怎么改”的顺序把这份源码拆开讲清楚。2. 项目骨架与请求链路一个成绩查询请求从浏览器到数据库再回来2.1 目录结构与技术栈定位拿到源码先别急着改代码把目录结构看清楚后面找文件能省一半时间。典型的 Maven 或普通 Web 项目结构大致如下StudentScoreSystem/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/score/ │ │ │ ├── controller/ # Servlet 控制器 │ │ │ ├── service/ # 业务逻辑 │ │ │ ├── dao/ # 数据库访问 │ │ │ ├── entity/ # 实体类 │ │ │ └── util/ # JDBC 工具、分页工具 │ │ ├── resources/ │ │ │ └── db.properties # 数据库连接配置 │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ └── web.xml # Servlet 映射若用注解可省略 │ │ ├── css/ js/ images/ │ │ ├── echarts/ # ECharts 静态资源 │ │ └── *.jsp # 页面 ├── pom.xml 或 lib/ └── sql/init.sql # 建表与初始数据技术栈很明确Servlet 负责接收请求和控制跳转JSP 负责渲染页面MySQL 存数据JDBC 做连接ECharts 在前端画图。没有 Spring 全家桶所以理解起来更直接但也要自己处理连接池、事务和编码。常见做法是用一个DBUtil类统一获取连接配合PreparedStatement防注入。2.2 一个查询请求的完整链路以“按学号查询成绩”为例请求从浏览器发出后依次经过 Servlet、Service、DAO最后回到 JSP。核心代码如下// ScoreServlet.java 片段 WebServlet(/score/list) public class ScoreServlet extends HttpServlet { private ScoreService scoreService new ScoreService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 设置编码防止中文乱码 req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); // 2. 获取分页与查询参数 String studentNo req.getParameter(studentNo); int page parseInt(req.getParameter(page), 1); int size parseInt(req.getParameter(size), 10); // 3. 调用业务层 PageResultScoreVO result scoreService.queryByStudentNo(studentNo, page, size); // 4. 存入请求作用域转发到 JSP req.setAttribute(pageResult, result); req.getRequestDispatcher(/score/list.jsp).forward(req, resp); } }这段代码里几个关键点req.setCharacterEncoding(UTF-8)必须放在获取参数之前否则中文参数会乱码分页参数用工具方法解析避免NumberFormatException结果用PageResult封装包含当前页数据和总记录数JSP 才能渲染分页条。Service 层通常做参数校验和事务控制DAO 层用PreparedStatement执行 SQL。查询语句类似SELECT s.student_no, s.name, c.course_name, sc.score FROM student s JOIN score sc ON s.id sc.student_id JOIN course c ON sc.course_id c.id WHERE s.student_no LIKE ? LIMIT ?, ?;注意LIMIT的两个参数是偏移量和条数偏移量 (page - 1) * size。如果这里算错分页就会出现重复或漏数据。2.3 ECharts 统计图的数据接口新增的 ECharts 统计图不是直接查数据库渲染而是通过一个独立的 Servlet 返回 JSON 数据。常见做法是// ChartServlet.java 片段 WebServlet(/chart/scoreDistribution) public class ChartServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType(application/json;charsetUTF-8); // 查询各分数段人数 ListMapString, Object data scoreService.getScoreDistribution(); // 手动拼接 JSON 或使用 Gson/Jackson String json new Gson().toJson(data); resp.getWriter().write(json); } }前端 JSP 里用 Ajax 请求这个接口再把数据塞给 ECharts 的option。这里容易踩的坑是 JSON 字段名和 ECharts 要求的name、value对不上导致图表空白。建议在 Service 层就把数据整理成[{name:优秀, value:12}, ...]的格式前端直接使用。3. 环境搭建与运行从零把项目跑起来的完整步骤3.1 数据库准备与配置修改第一步永远是数据库。找到sql/init.sql在 MySQL 中执行。常见做法是建一个名为score_db的库然后导入表结构和几条测试数据。执行命令mysql -u root -p sql/init.sql接着修改src/main/resources/db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/score_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码注意serverTimezone不能少否则 MySQL 8 会报时区错误。useUnicode和characterEncoding保证中文正常。如果用的是 MySQL 5.7驱动类名可以换成com.mysql.jdbc.Driver但建议统一用新驱动。3.2 Tomcat 部署与访问路径项目如果是 Maven 结构用mvn package打成 war 包丢进 Tomcat 的webapps目录如果是普通 Web 项目直接在 IDE 里配置 Tomcat 运行。启动后访问http://localhost:8080/StudentScoreSystem/login.jsp默认账号密码通常在init.sql里比如admin/123456。登录后进入成绩列表页再点“统计图”就能看到 ECharts 渲染的柱状图和饼图。如果 404检查web.xml或注解里的url-pattern是否和访问路径一致如果 500看 Tomcat 日志里的异常堆栈多半是数据库连接失败或 SQL 语法错误。3.3 常见启动失败与排查顺序启动失败不要慌按这个顺序查先看 Tomcat 控制台有没有ClassNotFoundException说明驱动 jar 没放进WEB-INF/lib再看db.properties是否被正确加载可以在DBUtil静态块里打印一下配置然后确认 MySQL 服务是否启动、端口是否被占用最后检查 JSP 页面里的% page contentTypetext/html;charsetUTF-8 %是否漏写。这一套走下来九成问题都能定位。4. 核心模块拆解登录、成绩 CRUD 与统计图表的实现细节4.1 登录鉴权与 Session 管理登录模块虽然简单但涉及 Session 和过滤器。核心 Servlet 如下WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, password); if (user ! null) { req.getSession().setAttribute(currentUser, user); resp.sendRedirect(req.getContextPath() /score/list); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }登录成功后把用户对象放进 Session后续页面通过session.getAttribute(currentUser)判断是否登录。常见做法是再加一个LoginFilter拦截所有/score/*和/chart/*请求未登录直接跳回登录页。过滤器里注意放行登录页和静态资源否则 CSS 和 ECharts 的 js 文件也会被拦截页面样式全丢。4.2 成绩增删改查与事务控制成绩的 CRUD 集中在ScoreService和ScoreDao。新增成绩时通常要同时更新学生总学分或平均分这就涉及事务。代码示例public boolean addScore(Score score) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 scoreDao.insert(conn, score); studentDao.updateAvgScore(conn, score.getStudentId()); conn.commit(); return true; } catch (Exception e) { DBUtil.rollback(conn); e.printStackTrace(); return false; } finally { DBUtil.close(conn); } }这里把Connection作为参数传给 DAO保证多个操作在同一个事务里。如果每个 DAO 方法自己获取连接事务就失效了。这是新手最容易翻车的地方看起来代码没错但数据不一致。参数说明setAutoCommit(false)关闭自动提交commit()提交rollback()回滚finally里关闭连接。注意DBUtil.close要判空避免空指针。4.3 ECharts 统计图的数据组装与前端渲染统计图分两种分数段分布柱状图和课程平均分折线图。后端返回 JSON前端渲染。后端组装数据时常见做法是用Map或自定义 VOpublic ListMapString, Object getScoreDistribution() { ListMapString, Object list new ArrayList(); // 假设查询结果每行是 grade 和 count String[] grades {优秀, 良好, 中等, 及格, 不及格}; for (String grade : grades) { MapString, Object map new HashMap(); map.put(name, grade); map.put(value, scoreDao.countByGrade(grade)); list.add(map); } return list; }前端 JSP 中// 使用 fetch 请求数据 fetch(${pageContext.request.contextPath}/chart/scoreDistribution) .then(response response.json()) .then(data { const chart echarts.init(document.getElementById(chart)); chart.setOption({ tooltip: {}, xAxis: { data: data.map(item item.name) }, yAxis: {}, series: [{ name: 人数, type: bar, data: data.map(item item.value) }] }); });注意echarts.init的容器必须有明确高度否则图表不显示。可以在 CSS 里写#chart { width: 600px; height: 400px; }。另外如果页面用了 iframe 或 tab 切换图表初始化时容器隐藏会导致尺寸为 0需要调用chart.resize()。5. 避坑与常见问题编码、连接池、分页和图表空白5.1 中文乱码现象、原因与解决现象表单提交后数据库里是问号或者页面显示乱码。原因请求编码、响应编码、数据库连接编码、JSP 页面编码不一致。解决在 Servlet 里req.setCharacterEncoding(UTF-8)和resp.setContentType(text/html;charsetUTF-8)JSP 顶部加% page contentTypetext/html;charsetUTF-8 languagejava %数据库 URL 加useUnicodetruecharacterEncodingUTF-8MySQL 建库时指定DEFAULT CHARSETutf8mb4。四者缺一不可。5.2 数据库连接失败驱动、URL 和权限现象启动报No suitable driver found或Access denied。原因驱动 jar 没放对位置或者 URL 写错或者密码不对。解决确认WEB-INF/lib下有mysql-connector-java-8.x.jarURL 里的库名、端口、时区参数正确用户名密码和 MySQL 一致。如果用的是 Tomcat 连接池还要检查context.xml里的配置。5.3 分页数据重复或丢失LIMIT 计算错误现象第二页出现第一页的数据或者最后一页少几条。原因偏移量算错或者总记录数查询条件不一致。解决偏移量 (page - 1) * size并且 count 语句的 where 条件和查询语句完全一致。建议把分页参数封装成Page对象统一计算。5.4 ECharts 图表空白容器高度与数据格式现象页面有图表区域但一片空白。原因容器没有高度或者 JSON 数据字段名不对或者请求 404。解决给容器设置固定高度检查接口返回的 JSON 是否包含name和value打开浏览器控制台看 Network 请求是否成功。如果用了fetch注意跨域和路径问题。5.5 Session 失效与过滤器拦截静态资源现象登录后刷新页面又跳回登录页或者 CSS 样式丢失。原因Session 超时或者过滤器拦截了静态资源。解决在web.xml里配置session-configsession-timeout30/session-timeout/session-config过滤器里用chain.doFilter放行.css、.js、.png等后缀的请求。6. 二次开发与改造思路从能跑到好用6.1 把 JDBC 换成连接池现在每次请求都新建连接并发一高就崩。改造思路引入 Druid 或 HikariCP在DBUtil里从数据源获取连接。以 Druid 为例加依赖后写一个DruidUtilpublic class DruidUtil { private static DataSource dataSource; static { try { Properties props new Properties(); props.load(DruidUtil.class.getClassLoader() .getResourceAsStream(druid.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }druid.properties里配url、username、password、initialSize、maxActive等。改造后记得把 DAO 里的DBUtil.getConnection()换成DruidUtil.getConnection()事务控制逻辑不变。这一步做完系统抗压能力明显提升。6.2 用 AJAX 替换整页刷新现在的增删改查都是表单提交后整页刷新体验一般。改造思路把 Servlet 的返回改成 JSON前端用fetch或axios异步提交局部更新表格。比如删除一条成绩async function deleteScore(id) { if (!confirm(确定删除)) return; const resp await fetch(/score/delete?id${id}, { method: POST }); const result await resp.json(); if (result.success) { // 局部移除表格行而不是刷新页面 document.querySelector(tr[data-id${id}]).remove(); } else { alert(删除失败 result.msg); } }后端 Servlet 里把forward改成resp.getWriter().write({\success\:true})。注意 JSON 字符串转义建议用 Gson 或 Jackson 统一处理。6.3 统计图增加筛选条件与导出现在的统计图是全局的可以改成按班级、按课程筛选。改造点在ChartServlet里接收classId和courseId参数传给 Service 层动态拼接 SQL 的 where 条件。前端加两个下拉框选择后重新请求接口并chart.setOption更新数据。另外可以加一个“导出 Excel”按钮用 POI 把查询结果写成 xlsx方便老师存档。导出时注意设置响应头Content-Disposition和Content-Type否则浏览器直接打开乱码。6.4 验证改造是否成功的几个检查点改完不要只看页面能打开按这几个点验证连接池是否生效——看日志里有没有DruidDataSource初始化信息事务是否正常——故意在插入后抛异常看数据有没有回滚AJAX 是否返回 JSON——浏览器 Network 里看响应类型图表筛选是否准确——切换班级后数据是否变化。我一般会写一个简单的TestDao跑一遍增删改查确认底层没问题再调前端。从那以后我每次拿到这类源码都强制先跑通数据库和登录再动任何一行代码。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

GAN原理解读与实战:生成器判别器博弈及训练避坑指南

GAN原理解读与实战:生成器判别器博弈及训练避坑指南

第一次接触《Generative Adversarial Nets》这篇论文的时候,我还在跑图像分类的实验。说实话,当时看到“生成器”和“判别器”这两个词,我第一反应是:这不就是造假和打假吗?后来真正动手实现之后才发现,这个…

2026/10/17 2:55:41 阅读更多 →
自动驾驶目标检测实战:YOLOv8与PySide6界面部署全流程解析

自动驾驶目标检测实战:YOLOv8与PySide6界面部署全流程解析

我最近在跑自动驾驶场景下的目标检测项目,把摄像头拍到的路面画面实时识别成车辆、行人、骑行者、交通灯这些目标,最后用PySide6做成一个带界面的演示系统。这套东西涉及YOLOv8/YOLOv7/YOLOv6/YOLOv5四个版本的选型对比、Python推理代码、训练数据集整理…

2026/10/17 2:55:41 阅读更多 →
DSH Codex Desktop 桌面版实测:把 DeepSeek Harness 装进本地,Codex 式体验与 MCP 细节拆解

DSH Codex Desktop 桌面版实测:把 DeepSeek Harness 装进本地,Codex 式体验与 MCP 细节拆解

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

2026/10/17 2:55:41 阅读更多 →

最新新闻

动态链表创建详解:头插法与尾插法及常见陷阱

动态链表创建详解:头插法与尾插法及常见陷阱

链表这块内容,我见过太多人卡在“静态数组能跑,一到动态就懵”的阶段。教材上把malloc和指针操作讲得云里雾里,真正上手写的时候,不是忘了给next赋值,就是把头指针弄丢导致整个链表找不回来。这篇就专门聊动态创建链表…

2026/10/17 3:42:55 阅读更多 →
STM32外部中断EXTI入门:按键控制LED的GPIO、NVIC与消抖实战

STM32外部中断EXTI入门:按键控制LED的GPIO、NVIC与消抖实战

简介:针对STM32入门阶段常见的按键检测延迟与CPU空耗问题,这份外部中断EXTI按键控制LED灯工程资料给出了完整的解决方案。资源采用标准库开发方式,面向单片机初学者与嵌入式竞赛备赛者,系统演示了如何将PA0配置为外部中断输入、通…

2026/10/17 3:42:55 阅读更多 →
ST-LINK Utility 完全指南:从烧录到救砖的嵌入式开发利器

ST-LINK Utility 完全指南:从烧录到救砖的嵌入式开发利器

很多人手里握着 ST-Link,绝大多数时候只是拿来给 IDE 点一下“下载”按钮,芯片里是什么状态、Flash 里到底写了什么、板子被锁死了怎么救,一概不清楚。ST-LINK Utility 这个工具,恰恰就是解决这些“下载按钮之外”问题的。它在老工…

2026/10/17 3:42:55 阅读更多 →
多模态推理服务架构重构:PD分离与KV Cache显存管理实战

多模态推理服务架构重构:PD分离与KV Cache显存管理实战

如果你维护过一个多模态问答服务,一定会遇到这样的场景:单看推理引擎的吞吐指标一切正常,可一旦有用户上传图片,整体延迟就开始失控,GPU显存忽高忽低,甚至偶发OOM。这不是模型的问题,也不是推理…

2026/10/17 3:42:55 阅读更多 →
Cursor零基础小白教程系列 - Cursor的AI辅助功能初体验:从智能补全到AI聊天

Cursor零基础小白教程系列 - Cursor的AI辅助功能初体验:从智能补全到AI聊天

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

2026/10/17 3:42:55 阅读更多 →
动态规划入门:记忆化搜索与状态转移方程实战拆解

动态规划入门:记忆化搜索与状态转移方程实战拆解

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

2026/10/17 3:41:55 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →