SpringBoot单体应用URL驱动弹窗实现方案
1. 项目概述为什么在单体SpringBoot里还要做“链接直达弹窗”这件事“非前后端分离的SpringBoot项目”这八个字一出来很多老开发心里就咯噔一下——页面是Thymeleaf或JSP渲染的后端直接吐HTML前端逻辑薄得像张纸连Vue都懒得装。这时候突然要实现“用户点一个链接直接跳转到某个页面同时自动弹出一个详情Dialog”听起来就像让一辆燃油三轮车跑出F1起步加速感理论上可行但每一步都得精打细算稍有不慎就是页面闪白、弹窗不显、参数丢失、刷新后状态消失。我去年在某高校教务系统改造中就撞上这个需求。不是不想前后端分离而是系统已稳定运行8年200个JSP页面、30个遗留Filter、5套不同年代的权限校验逻辑耦合在Controller里——推倒重来成本远超功能价值。业务方只提了一个朴素要求“招生老师发给考生的录取通知书链接点开就该是‘我的录取信息’页且立刻弹出带PDF预览的详情框不能让用户再点一次‘查看’按钮。”这个需求背后藏着三个真实痛点第一是传播效率链接必须承载完整上下文比如考生ID、批次号、审核状态不能依赖用户登录后手动筛选第二是交互预期现代用户对“点击即响应”有肌肉记忆二次操作会显著抬高放弃率第三是状态一致性弹窗内容必须与当前页面数据强绑定不能出现“页面显示已录取弹窗却提示待审核”的逻辑撕裂。关键词“链接直接跳转指定页面并弹出详情Dialog”拆解下来本质是URL驱动的客户端状态激活服务端负责把业务参数安全注入HTML前端负责在DOM就绪后精准触发UI行为。它不依赖API调用链不挑战现有MVC结构而是用最轻量的方式在单体架构的缝隙里嵌入现代化交互体验。适合所有正在维护传统Web系统的团队——你不需要重构只需要补上这一小块拼图。2. 整体设计思路绕过SPA思维在MVC里种下“状态种子”很多人第一反应是“加个JS监听URL哈希”但这是典型SPA思维误入歧途。在Thymeleaf渲染场景下页面跳转是服务端重定向302或服务端转发forward整个HTML文档被重新生成浏览器历史栈里压的是新URL但没有前端路由接管过程。指望window.location.hash在新页面生效前提是JS执行时机早于DOM渲染而Thymeleaf模板里JS通常放在/body底部——等JS加载完DOM早渲染完了。我们真正能抓住的锚点只有两个URL查询参数Query String和服务端渲染时的模板变量注入。设计核心就一句话让服务端在生成HTML时把“该弹窗”的指令和参数以可被前端JS无歧义读取的方式预先埋进页面DOM里。后续JS只需在DOMContentLoaded事件后检查这个“埋点”条件满足就执行弹窗逻辑。具体分三层实现服务端层Controller/Model接收原始请求参数如/admission/detail?studentId1001showDialogtrue校验合法性后将showDialogtrue及关联数据如admissionInfo对象一并塞进Model由Thymeleaf渲染到HTML中模板层Thymeleaf不写任何JS逻辑只用th:attr或th:data-*属性把需要的参数作为data属性挂载到某个稳定DOM节点如body或div idapp-root上。例如body th:attrdata-show-dialog${model.showDialog},>script th:inlinejavascript var pageConfig { showDialog: [[${model.showDialog}]], studentId: [[${model.studentId}]] }; /script这看似直观但存在三个硬伤第一是XSS风险若studentId来自用户输入未过滤[[${...}]]会原样输出/script闭合标签可能被注入第二是执行时机错位此脚本在head里而弹窗JS通常在/body若弹窗JS依赖pageConfig需额外加window.onload等待增加不确定性第三是维护成本每个需要弹窗的页面都要复制粘贴这段代码违反DRY原则。最优解是利用HTML5>!-- 在 layout.html 的 body 标签上统一添加 -- body th:attrdata-show-dialog${#strings.default(model.showDialog, false)}, >// utils/page-dialog-trigger.js (function() { use strict; // 检查是否满足弹窗条件 function shouldShowDialog() { const body document.body; if (!body) return false; // 读取data属性注意返回值是字符串 const showDialog body.dataset.showDialog; return showDialog true; // 严格等于字符串true } // 获取弹窗所需参数 function getDialogParams() { const body document.body; return { studentId: body.dataset.studentId || , batchCode: body.dataset.batchCode || }; } // 初始化弹窗此处以Bootstrap 5 Modal为例 function initDialog(params) { // 确保Modal DOM存在Thymeleaf已渲染好 const modalEl document.getElementById(admission-detail-modal); if (!modalEl) { console.warn(Dialog element #admission-detail-modal not found); return; } // 填充内容模拟AJAX加载实际应调用后端接口 const contentEl modalEl.querySelector(.modal-body); if (contentEl) { contentEl.innerHTML pstrong考生ID/strong${params.studentId}/p pstrong录取批次/strong${params.batchCode}/p pstrong状态/strongspan classbadge bg-success已录取/span/p div classmt-3 button classbtn btn-primary btn-sm onclickpreviewPdf(${params.studentId}) 预览录取通知书PDF /button /div ; } // 显示Modal const modal new bootstrap.Modal(modalEl); modal.show(); } // 页面加载完成后的主入口 function onPageLoad() { if (shouldShowDialog()) { const params getDialogParams(); // 添加防抖确保DOM完全就绪尤其当Modal在页面底部时 setTimeout(() { initDialog(params); }, 100); } } // 监听DOMContentLoaded事件 if (document.readyState loading) { document.addEventListener(DOMContentLoaded, onPageLoad); } else { onPageLoad(); } })();关键细节说明setTimeout(..., 100)不是偷懒而是必要保险。Thymeleaf渲染Modal HTML后Bootstrap JS可能尚未初始化其内部实例直接new bootstrap.Modal()会报错。100ms延迟让浏览器完成微任务队列实测99%场景下足够console.warn代替throw。若页面没写Modal DOM不影响主流程仅日志提醒符合“失败静默”原则参数传递用onclickpreviewPdf(xxx)而非事件委托。因为Modal内容是动态插入的事件委托需额外绑定而内联onclick在HTML插入时即绑定更可靠。3.3 弹窗组件选型轻量级自研 vs 成熟框架选型核心看两点项目已有技术栈和弹窗复杂度。如果项目已引入Bootstrap 4/5直接用其Modal组件是最快路径它自带遮罩、键盘ESC关闭、点击背景关闭等完备交互且CSS已全局加载无需额外资源。但若项目极简如只用jQuery或原生JS或弹窗需深度定制如拖拽调整大小、多步骤表单嵌套建议自研轻量Dialog。我维护的一个教育系统就用200行原生JS实现核心逻辑如下class SimpleDialog { constructor(options {}) { this.options { title: 详情, content: , width: 600px, ...options }; this.init(); } init() { // 创建DOM结构只创建一次重复调用复用 if (!this.el) { this.el document.createElement(div); this.el.className simple-dialog; this.el.innerHTML div classdialog-overlay/div div classdialog-box stylewidth:${this.options.width}; div classdialog-header h5${this.options.title}/h5 button classdialog-closetimes;/button /div div classdialog-body${this.options.content}/div div classdialog-footer button classbtn btn-secondary dialog-close-btn关闭/button /div /div ; // 绑定关闭事件 this.el.querySelector(.dialog-close).addEventListener(click, () this.hide()); this.el.querySelector(.dialog-close-btn).addEventListener(click, () this.hide()); this.el.querySelector(.dialog-overlay).addEventListener(click, (e) { if (e.target e.currentTarget) this.hide(); }); } } show() { if (this.el !this.el.classList.contains(show)) { document.body.appendChild(this.el); this.el.classList.add(show); document.body.style.overflow hidden; // 禁止背景滚动 } } hide() { if (this.el this.el.classList.contains(show)) { this.el.classList.remove(show); setTimeout(() { if (this.el.parentNode document.body) { document.body.removeChild(this.el); } }, 300); // 匹配CSS fade-out动画时间 document.body.style.overflow ; } } }配套CSS兼容IE11.simple-dialog { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1050; display: none; } .simple-dialog.show { display: block; } .dialog-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); animation: overlay-fade-in 0.3s; } .dialog-box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); animation: box-fade-in 0.3s; } keyframes overlay-fade-in { from { opacity: 0; } to { opacity: 1; } } keyframes box-fade-in { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }实操心得自研Dialog最大的坑是焦点管理。弹窗打开后键盘Tab键应只在弹窗内循环关闭后焦点应回到触发按钮。Bootstrap Modal内置了enforceFocus选项自研需手动实现focusableElements查找和focus()控制若项目无无障碍要求可暂不处理但务必在hide()后triggerBtn.focus()。4. 实操全流程从Controller编码到线上验证的7个关键步骤下面以一个真实可运行的案例演示从零开始实现全过程。假设项目使用Spring Boot 2.7 Thymeleaf 3.0 Bootstrap 5。4.1 步骤1定义Controller接收参数并注入Model// AdmissionController.java Controller RequestMapping(/admission) public class AdmissionController { Autowired private AdmissionService admissionService; /** * 处理录取详情页请求支持链接直达弹窗 * URL示例/admission/detail?studentId1001showDialogtrue */ GetMapping(/detail) public String showDetailPage( RequestParam(required false) String studentId, RequestParam(defaultValue false) boolean showDialog, Model model) { // 1. 参数校验业务规则 if (StringUtils.isBlank(studentId)) { model.addAttribute(error, 考生ID不能为空); return error/400; } try { // 2. 查询业务数据 AdmissionInfo info admissionService.findByStudentId(studentId); if (info null) { model.addAttribute(error, 未找到考生录取信息); return error/404; } // 3. 将数据注入Model供Thymeleaf渲染 model.addAttribute(admissionInfo, info); model.addAttribute(studentId, studentId); model.addAttribute(batchCode, info.getBatchCode()); // 4. 关键注入弹窗控制标志位 model.addAttribute(showDialog, String.valueOf(showDialog)); return admission/detail; // 渲染Thymeleaf模板 } catch (Exception e) { model.addAttribute(error, 系统繁忙请稍后重试); return error/500; } } }为什么用String.valueOf(showDialog)RequestParam默认将showDialogtrue转为布尔值true但Thymeleaf中th:attr需要字符串。若直接传boolean showDialogth:attrdata-show-dialog${showDialog}会渲染成>!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title录取详情/title !-- Bootstrap 5 CSS -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet /head body th:attrdata-show-dialog${#strings.default(showDialog, false)}, >spring: web: resources: static-locations: classpath:/static/,classpath:/public/确保/js/page-dialog-trigger.js放在src/main/resources/static/js/目录下Spring Boot会自动映射为/js/...。4.4 步骤4编写前端触发JSpage-dialog-trigger.js// src/main/resources/static/js/page-dialog-trigger.js (function() { use strict; function shouldShowDialog() { const body document.body; return body body.dataset.showDialog true; } function getDialogParams() { const body document.body; return { studentId: body?.dataset.studentId || , batchCode: body?.dataset.batchCode || }; } function initDialog(params) { const modalEl document.getElementById(admission-detail-modal); if (!modalEl) { console.warn(Dialog element #admission-detail-modal not found); return; } // 模拟异步加载详情实际应调用API const contentEl modalEl.querySelector(.modal-body); if (contentEl) { contentEl.innerHTML div classrow div classcol-md-6 pstrong考生ID/strong${params.studentId}/p pstrong录取批次/strong${params.batchCode}/p pstrong专业/strong计算机科学与技术/p /div div classcol-md-6 pstrong录取时间/strong2023-08-15/p pstrong报到日期/strong2023-09-01/p pstrong学费标准/strong¥5,800/学年/p /div /div hr div classalert alert-info strong温馨提示/strong请于9月1日前完成网上缴费并携带身份证、录取通知书原件到校报到。 /div ; } // 初始化并显示Modal const modal new bootstrap.Modal(modalEl); modal.show(); // 可选关闭后清理data属性避免多次触发 modalEl.addEventListener(hidden.bs.modal, function () { document.body.removeAttribute(data-show-dialog); }); } function onPageLoad() { if (shouldShowDialog()) { const params getDialogParams(); // 确保Bootstrap Modal JS已加载 if (typeof bootstrap ! undefined bootstrap.Modal) { initDialog(params); } else { // Bootstrap未加载延迟重试最多3次 let retryCount 0; const retry () { if (retryCount 3 typeof bootstrap undefined) { retryCount; setTimeout(retry, 200); } else if (typeof bootstrap ! undefined) { initDialog(params); } }; retry(); } } } if (document.readyState loading) { document.addEventListener(DOMContentLoaded, onPageLoad); } else { onPageLoad(); } })();为什么加Bootstrap加载检测CDN加载有网络延迟bootstrap.Modal可能未定义。此重试逻辑比setTimeout(..., 1000)更健壮避免因网速慢导致弹窗失败。4.5 步骤5测试URL构造与参数传递构造测试链接http://localhost:8080/admission/detail?studentId1001showDialogtrue在浏览器访问观察地址栏是否显示完整URL确认无重定向丢失参数页面源码中body标签是否有>// 在showDetailPage方法内添加 if (!studentId.matches(\\d{4,12})) { // 假设考生ID为4-12位数字 model.addAttribute(error, 非法的考生ID格式); return error/400; }同时Thymeleaf中所有用户数据渲染必须用th:text自动转义而非th:utext不转义!-- 安全自动转义 -- pstrong姓名/strongspan th:text${admissionInfo.name}张三/span/p !-- 危险若name含script会被执行 -- pstrong姓名/strongspan th:utext${admissionInfo.name}张三/span/p4.7 步骤7线上验证与灰度发布上线前必做三件事跨浏览器测试Chrome/Firefox/Edge/Safari尤其Safari对dataset支持较晚需确认版本移动端适配Modal在手机上是否居中、宽度是否合适Bootstrap 5默认响应式但需测试性能监控用Chrome DevTools的Lighthouse跑分确认新增JS未显著增加FCP首次内容绘制。灰度发布策略第一阶段只对内网IP如192.168.0.*开放showDialogtrue参数内部人员验证第二阶段对User-Agent含edu.的请求放行识别教育系统用户第三阶段全量开放但后端记录showDialogtrue的请求量若异常飙升如1分钟超1000次自动熔断并告警。实操心得某次上线后发现弹窗在iOS Safari上不显示排查发现是body上>function initDialog(params) { // ... 显示Modal代码 modal.show(); // 刷新URL移除showDialog参数不触发页面重载 const url new URL(window.location); url.searchParams.delete(showDialog); window.history.replaceState({}, , url); }这样用户刷新时URL已是干净状态不再触发。技巧3为SEO友好给弹窗内容加noscript降级搜索引擎爬虫不执行JS若弹窗是核心内容如录取通知书PDF需提供降级方案!-- 在Modal同级位置 -- noscript div classalert alert-warning pstrong重要通知/strong您有未读的录取通知书详情。/p a href/admission/detail?studentId1001showDialogtrue classbtn btn-primary点击查看/a /div /noscript既保证爬虫抓取又引导用户点击。技巧4监控弹窗转化率用performance.mark埋点想知道多少人看到弹窗后点了“下载PDF”在JS中加性能标记// 弹窗显示时 performance.mark(dialog_shown); // 下载按钮点击时 function downloadNotice() { performance.mark(download_clicked); // ... 下载逻辑 } // 页面卸载前上报需兼容性处理 window.addEventListener(beforeunload, () { const measures performance.getEntriesByType(measure); if (measures.length 0) { navigator.sendBeacon(/api/log, JSON.stringify({ event: dialog_conversion, duration: measures[0].duration })); } });最后分享一个小技巧这个方案不仅能做弹窗还能扩展为“页面锚点滚动”。比如/page?scrollTosection3JS读取>

相关新闻

智能家居标准如何成为新兴市场科技股估值的隐形变量

智能家居标准如何成为新兴市场科技股估值的隐形变量

这两年到新兴市场出差,和当地做投资的朋友聊,总绕不开两个话题:一个是当地股市的估值到底贵不贵,另一个是家里那堆智能设备什么时候能真正“连成一片”。表面看,一个是宏观金融问题,一个是消费电子问题&…

2026/10/13 14:13:07 阅读更多 →
零基础爬虫实战:百度百科深度与宽度优先策略详解

零基础爬虫实战:百度百科深度与宽度优先策略详解

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

2026/10/13 14:13:07 阅读更多 →
React+Spring Boot全栈实战:TTS语音合成宠物匹配项目

React+Spring Boot全栈实战:TTS语音合成宠物匹配项目

简介:PetMatch 是一份面向全栈开发学习者的 React Spring Boot 宠物配对应用实战项目,适合希望打通前端交互与后端服务、积累完整 Web 开发经验的中级开发者。项目以 JavaScript(React)构建组件化界面,结合 Spring Ja…

2026/10/13 14:13:07 阅读更多 →

最新新闻

基于模型的测试(MBT)实战:状态机建模与用例自动生成

基于模型的测试(MBT)实战:状态机建模与用例自动生成

1. 为什么我会推荐把MBT放进测试技术栈1.1 传统用例设计的三道坎做测试这行时间长了,你会慢慢发现一个有意思的现象:真正让人头疼的不是测试执行,而是测试设计。需求评审完了,开发方案定了,接下来就是用例设计。但用例…

2026/10/13 15:09:41 阅读更多 →
从数据抓取到统计回测:足球数据下载搜索统计系统设计

从数据抓取到统计回测:足球数据下载搜索统计系统设计

简介:这套面向足球爱好者和彩票玩家的数据处理套件,围绕赔率、盘口、竞彩数据,提供下载、检索与统计能力,适合跟踪赛事动态、辅助投注决策的用户。压缩包共18个文件、约1.53MB,exe主程序承担下载与查询核心工作&#x…

2026/10/13 15:09:41 阅读更多 →
PaperSpine 本地参考文献摄入(Local Reference Ingestion)实战指南:local-first 文献索引机制与源码级解析

PaperSpine 本地参考文献摄入(Local Reference Ingestion)实战指南:local-first 文献索引机制与源码级解析

AI 技能AI 写作人工智能深度研究AI 应用 【免费下载链接】PaperSpine PaperSpine5 — local-first, evidence-bound paper research, writing, figures, review and delivery. Download: https://wubing2023.github.io/PaperSpine/v5/ 项目地址: https://gitcode.co…

2026/10/13 15:09:41 阅读更多 →
AI如何“看懂“屏幕?typesafe-computer-use五步决策循环完全解析

AI如何“看懂“屏幕?typesafe-computer-use五步决策循环完全解析

【免费下载链接】typesafe-computer-use Computer use for about $0.0002 a step: OCR the screen, classify the next action with TypeSafe, click. macOS. 项目地址: https://gitcode.com/gh_mirrors/ty/typesafe-computer-use 点击查看 免费下载 typesafe-comp…

2026/10/13 15:09:41 阅读更多 →
Rune主题与外观定制:从主题包到壁纸的5个快速美化技巧

Rune主题与外观定制:从主题包到壁纸的5个快速美化技巧

【免费下载链接】rune the development environment for pros 项目地址: https://gitcode.com/gh_mirrors/rune13/rune 点击查看 免费下载 Rune 是一款面向专业开发者的键盘驱动 IDE,它的主题与外观定制能力远超想象:内置 16 个高质量主题、…

2026/10/13 15:09:41 阅读更多 →
掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

1. 为什么“会用AI工具”正在变成一项硬技能 这两年我身边做开发的朋友、做设计的朋友、甚至做运营和行政的朋友,都在聊同一件事:AI工具怎么用才能真正落到工作里。不是那种“打开网页问一句、复制答案”的浅层玩法,而是把它当成一个能持续产…

2026/10/13 15:08:40 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →