news 2026/10/7 1:12:56

JavaWeb期末大作业实战:在线购书系统从环境搭建到部署上线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb期末大作业实战:在线购书系统从环境搭建到部署上线

简介:这是一套面向高校计算机相关专业学生的JavaWeb期末大作业完整项目,主题为在线购书系统,采用JSP+Servlet+MySQL技术栈并严格遵循MVC三层架构,适合作为课程设计、期末大作业或毕业设计参考,新手也能快速上手。压缩包共75个文件,约47.77MB,包含14个JSP页面、12个Java源文件及对应class文件、1个SQL数据库脚本,另有CSS样式、JS脚本、XML配置、图片素材及项目展示视频等,覆盖前端页面、后端逻辑与数据库全链路。目前已有349人学习下载,热度较高。项目代码注释详尽,功能完善、界面美观、操作简单,下载后简单部署即可运行,能帮助读者快速理解MVC分层思想、Servlet请求处理与数据库交互流程,是期末冲刺高分、课程设计落地的实用参考方案。

1. 在线购书系统为什么成了 JavaWeb 期末作业的“标准答案”

每年期末,总有一批人对着“javaweb 期末大作业”这几个字发愁。选题太简单怕拿不到高分,太复杂又怕两周做不完。翻遍各种“javaweb 项目完整案例 mysql”,你会发现出镜率最高的就是在线购书系统——图书列表、购物车、下单、后台管理,功能闭环完整,业务逻辑又不至于复杂到失控。它天然适合用 jsp+Servlet+mysql 配合 mvc 三层架构来落地,每一层职责清晰,答辩时老师问“你的 MVC 体现在哪”你能直接指给他看。

这篇文章不讲空泛概念,而是把一套能跑通、能答辩、能拿高分的在线购书系统从环境搭建到核心功能实现完整拆一遍。适合正在找 JavaWeb 大作业选题的学生,也适合想用一个小项目把 Servlet 和 JSP 串起来的新手。读完你至少能拿到一套可复现的代码骨架,知道每个参数为什么这么设,以及那些让我熬夜调 bug 的坑到底出在哪。

2. 环境搭建与项目骨架:从零把工程跑起来

2.1 JDK、Tomcat、MySQL 的版本选择与安装要点

很多人第一步就翻车,不是代码写错,而是环境版本打架。我一般推荐这套组合:JDK 8 或 JDK 11、Tomcat 8.5 或 9.0、MySQL 5.7 或 8.0。别一上来就追最新版,Tomcat 10 把 javax.servlet 改成了 jakarta.servlet,你网上抄的代码全部报红,这就是典型的“版本玄学”。

MySQL 安装时记住三个关键点:第一,字符集选 utf8mb4,不选 utf8,否则中文和特殊符号会出问题;第二,root 密码设一个你记得住的,后面 JDBC 连接要用;第三,安装完成后确认服务已启动,Windows 下可以用net start mysql检查,如果提示“服务无法启动”,大概率是 my.ini 配置路径写错了或者端口 3306 被占用。

Tomcat 的配置相对简单,解压后配好 JAVA_HOME 环境变量即可。验证方式是进入 bin 目录执行 startup.bat(Windows)或 startup.sh(Linux),浏览器访问 localhost:8080 看到 Tomcat 欢迎页就算成功。如果 8080 被占用,改 conf/server.xml 里的 Connector port 就行。

提示:JDK 安装路径不要带空格和中文,不然后面 Tomcat 启动可能报 “JAVA_HOME is not defined correctly”。

2.2 用 IDEA 创建 JavaWeb 项目并配置 Tomcat

现在主流做法是用 IDEA 创建项目,而不是 Eclipse。打开 IDEA,新建 Project,选择 Java Enterprise,勾选 Web Application,确保勾选了 Servlet 支持。创建完成后目录结构应该是这样的:

项目根目录/ ├── src/ │ └── com/bookstore/ │ ├── controller/ │ ├── service/ │ ├── dao/ │ ├── entity/ │ └── util/ ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ │ ├── css/ │ ├── js/ │ └── index.jsp

接下来配置 Tomcat:Run → Edit Configurations → 点 + 号 → Tomcat Server → Local。在 Server 标签页指定 Tomcat 安装目录,在 Deployment 标签页点 + 号添加 Artifact,选择 xxx:war exploded。Application context 建议设成/bookstore,这样访问路径就是localhost:8080/bookstore/。

2.3 数据库建表与 JDBC 连接配置

在线购书系统的数据库至少需要四张表:用户表、图书表、订单表、订单明细表。建表 SQL 如下:

CREATE DATABASE bookstore DEFAULT CHARACTER SET utf8mb4; USE bookstore; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100), phone VARCHAR(20), role TINYINT DEFAULT 0 COMMENT '0普通用户 1管理员', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, author VARCHAR(100), price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, category VARCHAR(50), cover_img VARCHAR(255), description TEXT ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT '0待付款 1已付款 2已发货 3已完成', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT NOT NULL, quantity INT NOT NULL, subtotal DECIMAL(10,2), FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (book_id) REFERENCES book(id) );

JDBC 工具类是整个 DAO 层的基础,我一般写成静态加载驱动 + 获取连接 + 关闭资源三个方法:

package com.bookstore.util; import java.sql.*; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/bookstore?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL驱动加载失败", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (ps != null) ps.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }

URL 里的useSSL=false是必须的,MySQL 8.0 默认要求 SSL 连接,不加这个参数会报 “mysql ssl连接错误”。serverTimezone=Asia/Shanghai解决时区问题,不加会差 8 小时。驱动类名从 MySQL 8.0 开始是com.mysql.cj.jdbc.Driver,老版本是com.mysql.jdbc.Driver,写错了直接 ClassNotFoundException。

3. MVC 三层架构落地:Servlet 控制器、Service 业务、DAO 数据访问

3.1 为什么 MVC 是在线购书系统的最优解

MVC 三层架构的核心思想是职责分离。Model 层负责数据和业务逻辑,View 层负责展示,Controller 层负责接收请求和调度。在 JavaWeb 里,Servlet 天然就是 Controller,JSP 是 View,JavaBean + Service + DAO 构成 Model。

为什么购书系统特别适合 MVC?因为它的业务场景天然分层。比如“用户下单”这个操作,Controller 只需要接收参数、调用 Service、转发到结果页面;Service 负责检查库存、计算总价、生成订单;DAO 负责往 orders 和 order_item 表插数据。如果不用 MVC,把所有逻辑塞进一个 JSP 或者一个 Servlet 里,代码会变成几百行的意大利面条,改一个地方崩三个地方。

具体到包结构,我一般这样划分:

层包名职责典型类
Controllercom.bookstore.controller接收请求、参数校验、页面跳转BookServlet、OrderServlet
Servicecom.bookstore.service业务逻辑、事务控制BookService、OrderService
DAOcom.bookstore.dao数据库增删改查BookDao、OrderDao
Entitycom.bookstore.entity数据载体Book、User、Order

3.2 图书列表与详情页的 Servlet + JSP 实现

先看图书列表功能。BookServlet 接收/book?action=list请求,调用 Service 获取图书列表,存入 request 域,转发到 book_list.jsp。

package com.bookstore.controller; import com.bookstore.entity.Book; import com.bookstore.service.BookService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/book") public class BookServlet extends HttpServlet { private BookService bookService = new BookService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("list".equals(action)) { listBooks(req, resp); } else if ("detail".equals(action)) { detailBook(req, resp); } } private void listBooks(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String category = req.getParameter("category"); List<Book> books = bookService.getBooksByCategory(category); req.setAttribute("books", books); req.getRequestDispatcher("/book_list.jsp").forward(req, resp); } private void detailBook(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id = Integer.parseInt(req.getParameter("id")); Book book = bookService.getBookById(id); req.setAttribute("book", book); req.getRequestDispatcher("/book_detail.jsp").forward(req, resp); } }

@WebServlet("/book")是 Servlet 3.0 的注解方式,不需要在 web.xml 里配。action参数用来区分同一 Servlet 内的不同操作,这是 JavaWeb 里最常见的分发模式。forward是服务器内部跳转,浏览器地址栏不变,request 域的数据能带到 JSP 页面。

对应的 book_list.jsp 用 JSTL 遍历展示:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>图书列表</title></head> <body> <h2>全部图书</h2> <table border="1"> <tr><th>封面</th><th>书名</th><th>作者</th><th>价格</th><th>操作</th></tr> <c:forEach items="${books}" var="book"> <tr> <td><img src="${pageContext.request.contextPath}/images/${book.coverImg}" width="60"/></td> <td>${book.title}</td> <td>${book.author}</td> <td>¥${book.price}</td> <td> <a href="${pageContext.request.contextPath}/book?action=detail&id=${book.id}">详情</a> <a href="${pageContext.request.contextPath}/cart?action=add&id=${book.id}">加入购物车</a> </td> </tr> </c:forEach> </table> </body> </html>

${pageContext.request.contextPath}获取项目上下文路径,避免硬编码/bookstore。JSTL 的<c:forEach>比 JSP 脚本片段<% %>更清晰,答辩时也是加分项。

3.3 购物车与订单模块的业务逻辑与事务处理

购物车我一般用 Session 存储,结构是Map<Integer, CartItem>,key 是图书 id,value 是包含图书信息和数量的对象。这样不需要建购物车表,实现简单且性能好。

package com.bookstore.controller; import com.bookstore.entity.Book; import com.bookstore.entity.CartItem; import com.bookstore.service.BookService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.util.HashMap; import java.util.Map; @WebServlet("/cart") public class CartServlet extends HttpServlet { private BookService bookService = new BookService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); HttpSession session = req.getSession(); Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } if ("add".equals(action)) { int bookId = Integer.parseInt(req.getParameter("id")); if (cart.containsKey(bookId)) { cart.get(bookId).setQuantity(cart.get(bookId).getQuantity() + 1); } else { Book book = bookService.getBookById(bookId); cart.put(bookId, new CartItem(book, 1)); } resp.sendRedirect(req.getContextPath() + "/cart?action=view"); } else if ("view".equals(action)) { req.getRequestDispatcher("/cart.jsp").forward(req, resp); } } }

下单是整个系统里最需要事务控制的地方。生成订单要同时往 orders 表和 order_item 表插入数据,还要扣减库存,三步操作必须在一个事务里完成,否则会出现订单生成了但库存没扣、或者订单明细丢失的情况。

public boolean createOrder(int userId, Map<Integer, CartItem> cart) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 计算总价 BigDecimal total = BigDecimal.ZERO; for (CartItem item : cart.values()) { total = total.add(item.getBook().getPrice() .multiply(new BigDecimal(item.getQuantity()))); } // 2. 插入订单主表 String orderSql = "INSERT INTO orders(user_id, total_price, status) VALUES(?,?,0)"; PreparedStatement orderPs = conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS); orderPs.setInt(1, userId); orderPs.setBigDecimal(2, total); orderPs.executeUpdate(); ResultSet rs = orderPs.getGeneratedKeys(); int orderId = 0; if (rs.next()) orderId = rs.getInt(1); // 3. 插入订单明细并扣库存 String itemSql = "INSERT INTO order_item(order_id, book_id, quantity, subtotal) VALUES(?,?,?,?)"; String stockSql = "UPDATE book SET stock = stock - ? WHERE id = ? AND stock >= ?"; for (CartItem item : cart.values()) { PreparedStatement itemPs = conn.prepareStatement(itemSql); itemPs.setInt(1, orderId); itemPs.setInt(2, item.getBook().getId()); itemPs.setInt(3, item.getQuantity()); itemPs.setBigDecimal(4, item.getBook().getPrice() .multiply(new BigDecimal(item.getQuantity()))); itemPs.executeUpdate(); PreparedStatement stockPs = conn.prepareStatement(stockSql); stockPs.setInt(1, item.getQuantity()); stockPs.setInt(2, item.getBook().getId()); stockPs.setInt(3, item.getQuantity()); int affected = stockPs.executeUpdate(); if (affected == 0) { conn.rollback(); return false; // 库存不足 } } conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { try { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException ignored) {} } }

conn.setAutoCommit(false)开启事务,所有操作成功后commit(),任何一步失败就rollback()。扣库存的 SQL 用了stock >= ?条件,防止超卖。Statement.RETURN_GENERATED_KEYS用来获取刚插入订单的自增 id,这是 JDBC 里获取主键的标准做法。

4. 避坑与排查:那些让我熬夜的报错和异常

4.1 中文乱码:从请求到响应到数据库的三层排查

现象:页面输入的图书名称存到数据库变成问号,或者从数据库读出来显示乱码。

原因:乱码可能出现在三个环节——请求编码、响应编码、数据库编码。任何一个环节不是 utf8mb4 都会出问题。

解决:请求端在 Servlet 的 doGet/doPost 第一行加req.setCharacterEncoding("UTF-8");响应端设resp.setContentType("text/html;charset=UTF-8");JSP 页面头部声明<%@ page contentType="text/html;charset=UTF-8" %>;数据库连接 URL 加characterEncoding=utf8mb4;建库建表时指定DEFAULT CHARACTER SET utf8mb4。五个地方全部对齐,乱码基本绝迹。

4.2 404 与 500:路径写错和空指针的典型场景

现象:访问/book?action=list报 404,或者页面报 500 错误。

原因:404 通常是@WebServlet的路径和访问路径不匹配,或者 JSP 文件放错了目录(必须放在 web 根目录下,不能放在 WEB-INF 里直接访问)。500 最常见的是空指针——比如req.getParameter("id")返回 null 然后直接Integer.parseInt()。

解决:404 先检查 Servlet 注解路径和浏览器地址栏是否一致,再检查 web.xml 的 metadata-complete 是否设成了 true(设成 true 会忽略注解)。500 看堆栈信息定位到具体行,参数解析前先判空:

String idStr = req.getParameter("id"); if (idStr == null || idStr.isEmpty()) { resp.sendRedirect(req.getContextPath() + "/book?action=list"); return; } int id = Integer.parseInt(idStr);

4.3 MySQL 8.0 驱动加载失败与 SSL 连接错误

现象:启动项目报ClassNotFoundException: com.mysql.jdbc.Driver或者Communications link failure。

原因:MySQL 8.0 的驱动类名变了,而且默认要求 SSL 连接。另外很多人忘了把 mysql-connector-java 的 jar 包放到 WEB-INF/lib 目录下。

解决:驱动类名改成com.mysql.cj.jdbc.Driver,URL 加useSSL=false&allowPublicKeyRetrieval=true。jar 包版本建议用 8.0.28 以上,放到 WEB-INF/lib 后在 IDEA 的 Project Structure → Modules → Dependencies 里确认已添加。如果还报错,检查 MySQL 服务是否启动、端口是否被防火墙拦截。

4.4 Session 购物车数据丢失与 JSP 页面刷新问题

现象:加入购物车后刷新页面,购物车空了;或者 JSP 页面加载完后需要手动刷新一次才显示数据。

原因:Session 默认超时时间是 30 分钟,但如果你在代码里调用了session.invalidate()或者浏览器禁用了 Cookie,Session ID 无法保持。JSP 刷新问题通常是 forward 和 redirect 混用导致的。

解决:购物车场景不要调 invalidate,只在用户退出登录时清空。如果浏览器禁用 Cookie,可以用 URL 重写resp.encodeURL()保持 Session。JSP 页面数据不显示,检查是不是用了 redirect 而不是 forward——redirect 会丢失 request 域数据,改用 forward 即可。

5. 从能跑到能答辩:分页查询、图片上传与部署上线的进阶技巧

5.1 图书列表分页查询的 SQL 与 Servlet 参数设计

一个只有列表没有分页的购书系统,答辩时老师大概率会问“数据量大了怎么办”。分页的核心是 MySQL 的LIMIT offset, size,配合COUNT(*)算总页数。

public List<Book> getBooksByPage(int page, int size) { List<Book> list = new ArrayList<>(); String sql = "SELECT * FROM book ORDER BY id DESC LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, (page - 1) * size); ps.setInt(2, size); ResultSet rs = ps.executeQuery(); while (rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPrice(rs.getBigDecimal("price")); book.setStock(rs.getInt("stock")); book.setCoverImg(rs.getString("cover_img")); list.add(book); } } catch (SQLException e) { e.printStackTrace(); } return list; } public int getTotalCount() { String sql = "SELECT COUNT(*) FROM book"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { if (rs.next()) return rs.getInt(1); } catch (SQLException e) { e.printStackTrace(); } return 0; }

Servlet 里接收page和size两个参数,默认 page=1、size=8。总页数用(total + size - 1) / size计算,传给 JSP 渲染分页链接。注意LIMIT的 offset 是从 0 开始的,所以是(page-1)*size,这是最容易算错的地方。

5.2 图书封面图片上传与坐标定位展示

图书封面用文件上传实现,Servlet 3.0 的@MultipartConfig注解配合req.getPart()即可,不需要引入 commons-fileupload。

@WebServlet("/upload") @MultipartConfig(maxFileSize = 5 * 1024 * 1024) public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part part = req.getPart("cover"); String fileName = System.currentTimeMillis() + "_" + part.getSubmittedFileName(); String savePath = getServletContext().getRealPath("/images"); part.write(savePath + File.separator + fileName); resp.getWriter().write("上传成功:" + fileName); } }

maxFileSize限制 5MB,getRealPath("/images")获取 web 目录下 images 文件夹的绝对路径。前端表单必须加enctype="multipart/form-data",否则getPart返回 null。

如果要做封面图上叠加标签或者热区定位,JSP 里可以用 CSS 的position: absolute配合百分比坐标,或者用 HTML 的<map>和<area>标签做图片热区。常见做法是在后端存封面图的同时,额外存一组坐标数据(x, y, width, height),前端渲染时用绝对定位覆盖上去。

5.3 项目打包部署到 Tomcat 的完整流程

IDEA 里开发完成后,部署上线有两种方式。第一种是直接在 IDEA 的 Artifact 配置里选 war 包,Build → Build Artifacts 生成 war 文件,然后丢到 Tomcat 的 webapps 目录下,启动 Tomcat 自动解压部署。第二种是配 Tomcat 的 manager 页面,用网页上传 war 包。

部署前检查三件事:数据库连接 URL 改成服务器地址而不是 localhost;WEB-INF/lib 下所有依赖 jar 都打进去了;web.xml 的 welcome-file-list 配了默认首页。部署后访问服务器IP:8080/项目名/验证。

我自己的习惯是每次部署前先在本地用mvn clean package或者 IDEA 的 Build Artifacts 重新打一次包,确保没有编译错误。另外数据库导出成 sql 文件一起带着,换台机器导入就能跑。这套在线购书系统从环境搭建到部署上线,我前前后后做了三遍,每一遍都会发现新的细节问题。第一遍卡在 MySQL 驱动加载,第二遍卡在事务回滚没生效,第三遍卡在分页的 offset 算错。每次翻车都让我对 Servlet 的生命周期和 JDBC 的事务机制理解更深一层。如果你也在做这个作业,别怕报错,把堆栈信息从第一行读到最后一行,答案通常就在里面。希望帮到你。

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

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

空心杯电机内部结构拆解:有刷无刷对比与DIY实践

/* 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:11:57

Python Web项目管理信息系统:Flask+SQLAlchemy实战与避坑指南

/* 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:11:36

智能车原理图分析:从电路符号到系统行为的解码指南

/* 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:10:57

DDPG在无人机边缘计算卸载中的动态决策优化

/* 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:10:50

85C1磁电式电流表接线与分流器实操指南

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

作者头像 李华