JSTL 标签库实战:从脚本片段到标签封装的核心技巧
1. 从脚本片段到标签封装JSTL 到底解决了什么问题很多人第一次接触 JSTL是在维护一个老项目的时候。打开 JSP 文件满屏的% for(...) { %、% if(...) { %和% request.getAttribute(xxx) %逻辑和页面混在一起改一个字段名要在好几个文件里翻来覆去地找。这种写法在早期确实能跑但一旦页面结构复杂起来维护成本会急剧上升。JSTL 的出现本质上就是为了把这种“Java 代码嵌在 HTML 里”的混乱局面收拾干净。JSTL 全称是 JavaServer Pages Standard Tag Library翻译过来就是 JSP 标准标签库。它做的事情说起来很简单用一套类似 HTML 的标签替代 JSP 页面里那些脚本片段。比如原本要写% if(user ! null) { %用 JSTL 就变成c:if test${not empty user}。看起来只是换了个写法但背后的意义完全不同——页面里不再有 Java 语法前端人员也能看懂逻辑判断和循环被封装成标签职责边界清晰了。我刚开始学的时候有个疑问既然有 EL 表达式了为什么还要 JSTL后来才明白EL 负责的是“取值”比如${user.name}把数据从作用域里拿出来而 JSTL 负责的是“控制”比如循环、条件判断、格式化输出。两者配合使用才能把 JSP 页面写得干净。只靠 EL你没法做循环也没法做条件分支页面照样得靠脚本片段撑着。JSTL 的核心价值可以归纳成三点。第一是可读性标签的语义比 Java 代码更直观c:forEach一眼就知道是循环不用去数花括号。第二是可维护性页面逻辑和 Java 代码分离改样式的人不用碰逻辑改逻辑的人不用管 HTML。第三是标准化JSTL 是规范的一部分不同容器比如 Tomcat、Jetty都支持不像自定义标签那样绑死在某个项目里。适合学 JSTL 的人其实很明确如果你在维护传统的 JSP 项目或者需要快速搭建一个基于 JSP 的后台管理页面JSTL 是绕不开的。即使现在前后端分离是主流理解 JSTL 也能帮你读懂大量遗留系统这在接手老项目时特别有用。接下来我会从环境搭建开始一步步把 JSTL 的核心标签、常见坑点和实战技巧讲清楚。2. 环境准备中最容易被忽略的两个细节2.1 依赖引入不只是放一个 jar 包那么简单JSTL 的使用需要引入对应的 jar 包。在早期版本中通常需要两个 jarjstl.jar和standard.jar。这两个包分别提供了 API 接口和具体实现。很多人第一次配置时只放了一个结果页面报ClassNotFoundException或者标签完全不被解析排查半天才发现是包不全。如果你用的是 Maven 项目依赖配置大概是这样dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency注意版本号。JSTL 1.2 是 Java EE 6 之后比较通用的版本支持 EL 2.2 的一些特性。如果你用的是更老的容器可能需要 1.1 版本这时候依赖坐标会不一样通常还要额外引入standard标签库的实现包。我建议先确认你的 Servlet 容器版本再决定用哪个版本的 JSTL不要盲目复制网上的配置。还有一个细节jar 包必须放在WEB-INF/lib目录下而不是项目的其他位置。有些人习惯把 jar 放在项目根目录的lib文件夹里然后在 IDE 里配置构建路径本地跑起来没问题但部署到服务器上就找不到类了。原因是 Web 应用运行时只认WEB-INF/lib和容器的公共类路径IDE 的构建路径配置不会被打包进去。这个坑我踩过不止一次后来养成习惯凡是 Web 项目的依赖一律放WEB-INF/lib。2.2 标签库声明uri 写错一个字符都白搭引入 jar 包之后还需要在 JSP 页面顶部声明标签库。标准写法是% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %这行代码里有两个关键点。prefixc是前缀你可以改成别的比如jstl但通常大家都用c因为 core 库的标签用得最多短前缀写起来方便。uri是标签库的唯一标识必须和 jar 包里META-INF目录下 tld 文件中定义的 uri 完全一致。我见过有人把http://java.sun.com/jsp/jstl/core写成http://java.sun.com/jstl/core少了一个/jsp结果标签全部失效页面直接把c:forEach当普通文本输出。不同标签库对应的 uri 也不一样。核心库是core格式化库是fmt函数库是functions数据库操作库是sqlXML 处理库是xml。实际开发中c和fmt用得最多functions偶尔用来做字符串处理sql和xml基本不建议在页面里用原因后面会讲。提示如果你在页面里写了标签但没声明 uri容器会直接忽略标签把它当成普通 HTML 标签输出到浏览器。这时候你会在页面上看到c:if这样的文字而不是执行结果。遇到这种情况第一件事就是检查 taglib 声明。3. 核心标签库的实战用法拆解3.1 c:forEach循环里的 varStatus 是个宝藏c:forEach是 JSTL 里使用频率最高的标签没有之一。基本用法很直观c:forEach items${userList} varuser tr td${user.name}/td td${user.email}/td /tr /c:forEachitems指定要遍历的集合var是每次迭代的变量名。这比脚本片段里的for(User user : userList)清爽多了。但很多人不知道的是c:forEach还有一个varStatus属性它提供了一个LoopTagStatus对象里面藏着不少有用的信息。varStatus常用的属性有四个index表示当前索引从 0 开始count表示当前是第几次迭代从 1 开始first和last是布尔值判断是不是第一条或最后一条。这些在渲染表格时特别有用。比如给奇数行和偶数行加不同的背景色c:forEach items${userList} varuser varStatusstatus tr class${status.index % 2 0 ? even : odd} td${status.count}/td td${user.name}/td /tr /c:forEach还有一个场景是分页导航。你可以在循环里用status.last判断是否到了最后一条然后在最后一条后面追加“加载更多”的按钮。这种写法比在 Java 代码里拼 HTML 要灵活得多。另外c:forEach还可以指定begin、end和step用来做固定次数的循环。比如你想渲染 1 到 10 的数字c:forEach begin1 end10 vari span${i}/span /c:forEach这种用法在生成序号、分页页码时很常见。注意end是包含的所以begin1 end10会输出 1 到 10 共十个数。3.2 c:if 与 c:choose条件判断的两种姿势c:if是最简单的条件标签只有一个test属性值为布尔表达式c:if test${not empty user} p欢迎${user.name}/p /c:if这里用了not empty来判断 user 是否为空。empty是 EL 里的关键字能同时判断 null、空字符串和空集合比写user ! null user.name ! 简洁得多。我建议在 JSTL 里判断空值时优先用empty少用 null因为empty的语义更准确能覆盖更多边界情况。c:if有个局限它没有else分支。如果你需要 if-else 逻辑就得用c:choosec:choose c:when test${user.role admin} p管理员面板/p /c:when c:when test${user.role editor} p编辑面板/p /c:when c:otherwise p普通用户面板/p /c:otherwise /c:choosec:choose相当于 switch 语句c:when是 casec:otherwise是 default。注意c:choose里只能直接放c:when和c:otherwise不能放其他标签或文本否则会报错。这个限制在写复杂页面时容易忽略比如你想在 choose 外面包一层 div得把 div 放在 choose 的外面而不是里面。还有一个细节c:when的test属性是按顺序判断的一旦某个条件为真后面的 when 就不会再执行了。所以如果你有多个条件可能同时为真要注意排列顺序把最具体的条件放在前面。3.3 c:set 与 c:remove作用域操作的轻量方案c:set用来在指定作用域里设置变量。基本用法c:set vartotal value${price * quantity} scoperequest /scope可以是page、request、session、application默认是page。这个标签在页面里做临时计算时很有用比如你从数据库查出一批数据想在页面里算个总和就可以用c:set存起来后面直接引用。但要注意c:set设置的是作用域属性不是局部变量。它的生命周期取决于 scope 的设置。如果你设成session那这个值会一直保留到会话结束可能造成内存泄漏。我见过有人在循环里用c:set往 session 里塞数据结果每次请求都往 session 里加东西最后内存爆掉。所以除非确实需要跨请求共享否则尽量用默认的page作用域。c:remove用来删除作用域里的变量c:remove vartotal scoperequest /这个标签用得不多但在某些场景下有用。比如你在页面开头设置了一个临时变量用完之后想清理掉避免影响后续的页面片段。不过大多数情况下page 作用域的变量在页面结束后会自动失效不需要手动清理。3.4 fmt 格式化库日期和数字的显示问题fmt:formatDate和fmt:formatNumber是格式化库里的两个核心标签。先说日期fmt:formatDate value${order.createTime} patternyyyy-MM-dd HH:mm:ss /pattern属性和 Java 的SimpleDateFormat是一样的规则。这里有个坑如果value是java.util.Date类型直接格式化没问题但如果是从数据库取出来的java.sql.Timestamp在某些容器里可能会有时区问题。我遇到过页面显示的日期比实际时间少了 8 小时的情况排查后发现是数据库连接串里没设时区导致 JDBC 驱动把时间当成了 UTC。解决办法是在连接串里加上serverTimezoneAsia/Shanghai或者在 Java 代码里统一转成Date再传给页面。数字格式化也很实用fmt:formatNumber value${product.price} pattern#,##0.00 /#,##0.00表示千分位分隔保留两位小数。如果你要做货币显示还可以用typecurrency它会根据 locale 自动加上货币符号。不过在实际项目里我一般建议在 Java 层就把数字格式化好页面只负责展示。原因是 JSTL 的格式化依赖 locale 设置如果服务器和用户的 locale 不一致显示结果可能不符合预期。4. 自定义标签与 functions 库的进阶玩法4.1 functions 库字符串处理的快捷方式functions库提供了一批静态方法用来在 EL 表达式里做字符串操作。使用前需要声明% taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions %常用的函数有fn:length()获取长度fn:contains()判断包含fn:substring()截取子串fn:trim()去空格fn:toUpperCase()转大写等。比如你想在页面上显示用户名的首字母${fn:toUpperCase(fn:substring(user.name, 0, 1))}这比在 Java 代码里写一堆substring和toUpperCase要直观。但要注意fn:length()对集合和字符串都有效对 null 会返回 0不会抛异常。这个特性在判断空集合时很有用比如c:if test${fn:length(userList) 0}。不过 functions 库的函数数量有限复杂的字符串处理还是建议放在 Java 层。页面里做太多逻辑维护起来照样头疼。我的原则是简单的展示逻辑用 JSTL复杂的业务逻辑回 Java。4.2 自定义标签什么时候值得自己写JSTL 虽然覆盖了大部分常见场景但总有它做不到的事情。比如你想在页面上渲染一个权限按钮逻辑是“如果用户有权限就显示按钮否则显示禁用状态”这个逻辑在多个页面里重复出现。这时候可以考虑写一个自定义标签。自定义标签的开发流程大致是先写一个继承SimpleTagSupport的 Java 类实现doTag()方法然后写一个 tld 文件描述标签的属性和行为最后在 JSP 里声明并使用。听起来步骤不少但实际写起来并不复杂。关键是要判断值不值得——如果这个逻辑只在两三个地方用直接复制 JSTL 代码更省事如果到处都在用封装成标签才能体现价值。我个人的经验是自定义标签适合封装“与页面结构强相关、但逻辑相对独立”的功能比如分页条、权限按钮、树形菜单。不适合封装业务逻辑因为业务逻辑应该放在 Service 层页面标签只负责展示。5. 那些年我踩过的 JSTL 坑5.1 标签不解析从现象到根因的排查链路页面直接输出c:forEach标签文本这是最典型的“标签不解析”问题。排查思路可以按这个顺序走第一步检查 taglib 声明。uri 是否写对prefix 是否和后面使用的一致。我见过有人声明了prefixc但页面里写的是jstl:forEach这种低级错误在复制粘贴时很容易出现。第二步检查 jar 包。确认jstl.jar和standard.jar如果版本需要都在WEB-INF/lib下。可以解压 jar 包看META-INF目录下有没有对应的 tld 文件。如果 tld 文件缺失标签库就无法被识别。第三步检查 web.xml 的版本。有些老项目的web.xml声明的是 Servlet 2.3 规范这个版本默认不解析 EL 和 JSTL。解决办法是把web.xml的 schema 声明升级到 2.4 或更高版本。这个坑特别隐蔽因为项目能正常启动只是标签不生效很多人会往 jar 包方向排查忽略了 web.xml 的版本问题。第四步检查容器版本。极老的容器可能不支持 JSTL 1.2需要降级到 1.1。这种情况现在比较少见但如果你的项目部署在很旧的环境里还是要留意。5.2 EL 表达式不生效isELIgnored 的陷阱EL 表达式${user.name}直接原样输出没有被解析成实际值。这个问题通常和isELIgnored属性有关。在 JSP 页面的 page 指令里可以设置isELIgnoredfalse来强制启用 EL% page isELIgnoredfalse %如果这个属性设成了true或者 web.xml 里的版本声明导致默认忽略 EL那${...}就会被当成普通文本。解决办法有两个一是在页面里显式设置isELIgnoredfalse二是升级 web.xml 的版本声明。我一般推荐后者因为统一在 web.xml 里配置不用每个页面都写一遍。还有一个可能是 EL 版本和容器不匹配。比如容器支持 EL 3.0但项目里引入了旧版本的 EL 实现 jar导致冲突。这种情况需要检查WEB-INF/lib下有没有多余的 EL 相关 jar有的话删掉让容器用自己的实现。5.3 循环里的性能问题别在页面里查数据库JSTL 的c:forEach用起来太方便以至于有人会在循环里调用数据库查询。比如遍历订单列表每个订单再去查用户信息c:forEach items${orderList} varorder c:set varuser value${userService.findById(order.userId)} / p${user.name}/p /c:forEach这种写法在数据量小的时候看不出问题一旦订单上百条页面加载就会变得极慢。原因是每次循环都发起一次数据库查询N 条数据就是 N1 次查询。正确的做法是在 Java 层一次性把关联数据查出来组装成完整的对象再传给页面。页面只负责展示不负责取数据。这个坑的本质是把业务逻辑写到了页面里。JSTL 再方便也只是展示层的工具不该承担数据访问的职责。我后来给自己定了个规矩页面里只出现${...}取值和c:...控制不出现任何 Service 或 DAO 的调用。6. 从 JSTL 到现代模板引擎的过渡思路现在新项目基本不用 JSP 了Thymeleaf、Freemarker、Velocity 这些模板引擎更受欢迎。但如果你手头有 JSP 项目要维护或者需要快速改一个老系统JSTL 还是得会。而且 JSTL 的很多设计思想在现代模板引擎里都能找到影子。比如 Thymeleaf 的th:each和 JSTL 的c:forEach思路一致都是把循环逻辑封装成标签属性。Thymeleaf 的th:if和c:if也是同样的道理。区别在于 Thymeleaf 的模板可以直接在浏览器里打开预览而 JSP 必须经过容器解析。这个差异决定了 Thymeleaf 更适合前后端协作而 JSP 更适合纯后端渲染。如果你已经熟悉 JSTL过渡到 Thymeleaf 的成本其实不高。核心概念是相通的作用域取值、条件判断、循环遍历、格式化输出。需要适应的主要是语法差异比如 Thymeleaf 用th:*属性而不是标签表达式用${...}但支持更复杂的 OGNL 语法。我个人的建议是老项目继续用 JSTL不要为了技术升级而升级稳定压倒一切新项目直接上 Thymeleaf 或前后端分离方案没必要再走 JSP 的老路。但无论用哪种技术理解“展示层只负责展示”这个原则比会用某个具体标签更重要。最后分享一个我在实际维护 JSP 项目时的小技巧把常用的 JSTL 代码片段整理成一个common.jsp用% include filecommon.jsp %引入。这样 taglib 声明只需要写一次页面里直接使用标签即可。这个做法能减少重复代码也避免了每个页面都写一遍 taglib 声明容易写错的问题。不过要注意include指令是在编译时合并的如果common.jsp里有变量定义可能会和页面里的变量冲突所以common.jsp里最好只放 taglib 声明和常量定义不要放逻辑代码。

相关新闻

告别API依赖!GitHub上那些可以完全本地运行的优质开源AI项目盘点:用TaoToken统一Key打通本地与云端

告别API依赖!GitHub上那些可以完全本地运行的优质开源AI项目盘点:用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/12 17:55:23 阅读更多 →
共享单车数据集VOC格式实战:从标注解析到YOLO训练全流程

共享单车数据集VOC格式实战:从标注解析到YOLO训练全流程

简介:这份共享单车数据集采用VOC格式整理,面向从事目标检测算法练习、课程设计或毕业设计的学生与开发者,可用于训练和验证单车识别模型,解决真实街景中共享单车标注样本不足的问题。压缩包共272个文件,包含136张jpg实…

2026/10/12 17:55:23 阅读更多 →
Python机器学习预测电影首周票房实战指南

Python机器学习预测电影首周票房实战指南

简介:这是一套面向计算机专业本科生的机器学习实战项目资源,聚焦电影票房预测这一典型回归建模任务,适用于毕业设计、课程设计及期末大作业场景。资源包含完整可运行的Python源码、清洗后的TMDB 5000电影数据集(含credits与movies…

2026/10/12 17:55:23 阅读更多 →

最新新闻

OpenCV车道线检测实战:霍夫变换参数调优与避坑指南

OpenCV车道线检测实战:霍夫变换参数调优与避坑指南

简介:这份资源面向计算机视觉初学者与自动驾驶入门开发者,围绕OpenCV车道实时检测这一典型任务,提供可直接运行的示例代码与配套讲解。内容覆盖视频逐帧读取、多边形掩码构建、图像阈值化、霍夫线变换检测车道以及结果视频合成等完整流程&…

2026/10/12 19:25:05 阅读更多 →
DeepSeek本地部署保姆级教程:Ollama、可视化WebUI与RAG知识库投喂实战

DeepSeek本地部署保姆级教程:Ollama、可视化WebUI与RAG知识库投喂实战

简介:针对DeepSeek官方服务频繁响应迟缓、宕机的问题,这份面向零基础用户的保姆级教程,完整演示了如何在个人电脑上本地部署DeepSeek,并配套可视化界面与数据投喂训练。教程从安装Ollama讲起,覆盖根据显存容量挑选合适…

2026/10/12 19:25:05 阅读更多 →
大模型私有化部署生产环境实战:显存计算、推理框架选型与性能优化

大模型私有化部署生产环境实战:显存计算、推理框架选型与性能优化

1. 大模型私有化部署到底在解决什么问题第一次听到“把大模型私有化部署到生产环境”这个需求,很多人的第一反应是:直接调云端API不香吗?按token付费,不用管显卡,不用操心运维,何必自己折腾?我一…

2026/10/12 19:25:05 阅读更多 →
C++单元测试从零到实战:框架选型、构建集成与踩坑指南

C++单元测试从零到实战:框架选型、构建集成与踩坑指南

我翻了翻过去几个项目的提交记录,有个数据挺有意思:接入单元测试之前,每次大版本迭代,光回归测试就得占掉两三天,而且经常是改完一个模块,另一个模块莫名其妙就出问题,查半天发现是内部接口被悄…

2026/10/12 19:25:05 阅读更多 →
aider 接入 OpenRouter 调用 Claude 大模型:TaoToken 统一 Key 配置与连通性验证

aider 接入 OpenRouter 调用 Claude 大模型: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/12 19:25:04 阅读更多 →
Python接口逆向实战:网页翻译请求签名分析与批量调用实现

Python接口逆向实战:网页翻译请求签名分析与批量调用实现

1. 项目缘起与整体设计思路做数据采集这行的朋友大概都有体会,翻译类接口的逆向分析算是入门级里比较典型的一类练手项目。我最早接触这类需求是在做一个多语言内容聚合的小工具,需要批量把一批短文本从中文转成英文,再转成日文做对照。当时第…

2026/10/12 19:24:03 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →