Vue3+TS对接海康摄像头:从RTSP取流到播放器组件封装实战
很多做前端的朋友第一次接到监控对接的需求时第一反应都是“不就是放个视频流嘛”真上手才发现从设备取流、协议转换、到浏览器播放、再到Vue3TS工程里的组件封装每一步都有坑等着你。这篇就把我做海康平台摄像头对接的全过程拆开讲从协议原理到可落地的代码再到排错经验一次性说清楚。1. 项目背景与整体技术选型1.1 这个需求到底在解决什么项目来源很典型某企业后台管理系统需要接入园区内的海康监控摄像头实现实时预览、历史回放和设备状态查看。业务端技术栈已经定了Vue3 TypeScript所以所有摄像头相关的能力都必须在这套技术栈下落地。表面上看需求是“播放监控画面”但实际拆开有三层问题取流层摄像头设备本身不会直接吐给浏览器能播的协议需要先搞清楚海康设备支持哪些取流方式。转发层浏览器不能直接播放RTSP流必须经过协议转换这个转换放在哪、怎么做直接影响延迟和稳定性。展示层Vue3TS项目里如何封装播放器组件、管理生命周期、处理断流重连这层最容易被忽视但恰恰是影响用户体验的关键。1.2 方案选型为什么没有用官方Web控件最早期接触过海康对接的同事可能知道海康以前有Web控件方案需要装插件ActiveX时代的东西依赖IE或者老版本浏览器新项目基本直接Pass。后来海康开放平台提供了Web SDK但实际用下来它对现代前端框架的适配程度一般很多方法还是基于全局对象的方式TS类型也不完善侵入性很强。我最后采用的是“后端代理取流 前端协议播放”的方案后端从海康设备拉取RTSP流转成HTTP-FLV或HLS格式前端用flv.js或hls.js播放。为什么这么选后面第2章会详细讲协议链路这里先给结论这个方案跨浏览器兼容性好不依赖插件延迟可控而且前后端职责清晰后续加设备、加功能都方便。选Vue3TS不是因为“新项目就要用新技术”而是确实有收益组合式API适合把播放器的初始化、重连、销毁逻辑收敛成一个hook或组件TS类型能提前暴露流地址格式错误、播放器实例状态不一致这类问题。实际开发中类型提示帮我省了不少排查时间。2. 必须先看懂的取流链路与协议基础2.1 海康设备的几种取流通道海康摄像头设备端主要提供两类通道实时预览通道和回放通道。每路通道根据码流类型又分成主码流和子码流主码流分辨率高、码率大适合全屏预览或存储子码流分辨率低、码率小适合多画面同时预览的场景。对接时要根据业务场景选择合适的码流比如同时显示16宫格时全部拉主码流带宽和性能很容易撑不住。设备常见的取流方式有三种RTSP取流最底层、最通用的方式海康私有协议和标准RTSP都支持URL格式大概是rtsp://用户名:密码设备IP:554/Streaming/Channels/101其中101里的1表示通道101表示主码流02则是子码流。开放平台API取流通过海康开放平台的接口使用AppKey和Secret签名换取播放地址返回的通常是HLS或RTMP格式的地址好处是不用直接暴露设备IP和端口适合公网或跨网络场景。ISAPI手动配置海康设备的HTTP接口可以查询通道信息、配置码流参数偏管理面一般不直接用来取流播放。项目里如果设备都在内网RTSP取流最稳定如果监控区域和后端服务器不在同一网络走开放平台API更安全。这两种方式我在项目里都用过后面会分别讲。2.2 从RTSP到浏览器能播放的格式中间改了什么这是整个项目最核心的概念搞不懂这个后面全是瞎调。浏览器原生不支持RTSP因为这种协议基于TCP或UDP传输是长连接、有状态的流媒体协议而浏览器的video标签只支持HTTP协议的流媒体格式。所以必须有一层“协议转换代理”后端服务器主动去连设备的RTSP地址拉取视频数据再转封装成浏览器能吃的格式通过HTTP协议转发给前端。常见的三种输出格式HLS切片机制延迟一般在3到10秒优点是兼容性最好iOS Safari原生就能播PC端用hls.js也能播。缺点是切片和播放有天然延迟不适合需要快速响应的场景。HTTP-FLV基于HTTP的FLV封装延迟可以做到1到3秒实时性明显更好PC浏览器配合flv.js播放非常成熟。缺点是iOS Safari原生不支持但国内主流业务场景大多有PC端大屏需求所以我没有因为移动端兼容性而放弃这个方案。WebRTC延迟最低能做到500毫秒以内适合对实时性要求极高的场景但对后端转流服务器的要求更高当时项目没有这么极端的实时性需求所以没有采用。项目最终选择了HTTP-FLV为主HLS作为移动端备选。这个决策基于一个实际约束大屏展示和操作台都是PC环境实时性比移动端兼容性更优先。2.3 前端播放器选型flv.js还是自研封装现在前端播放监控流主流选择是flv.js配合video标签或者直接用西瓜播放器这类封装好的组件。西瓜播放器底层也可以接flv.js好处是自带了控制条、截图、倍速等UI能力省去自己写控制逻辑的功夫。我们项目最终采用的方式是播放器内核用flv.jsUI层没有引入重型播放器组件而是自己封装了一个轻量的Vue组件。原因是监控场景的控制需求比较固定播放、暂停、全屏、截图、声音开关自己封装百来行代码就能覆盖还避免了第三方组件样式和交互定制成本。如果你不想自己写UI用现成的播放器组件也完全可以看团队的定制需求。3. 实操搭建Vue3 TS 工程与播放器组件封装3.1 初始化工程与安装核心依赖项目基于Vite创建Vue3 TS工程这一步不展开太多主要说依赖安装npm install flv.js npm install -D types/flv.js这里有个TypeScript的细节flv.js官方其实自带类型声明但社区版本有时声明文件路径对不上安装types/flv.js是更稳妥的做法避免在tsconfig.json里手动写declare module。如果只做HLS播放还需要安装hls.js用法和flv.js类似这里先以FLV为主讲。工程初始化完后建议在src/types目录下统一放流媒体相关的类型声明文件后面封装组件时类型会清爽很多。3.2 封装一个带断流重连的播放器组件这是整个前端部分的核心。直接放完整代码再逐段解释关键点。template div classmonitor-player video refvideoRef classmonitor-player__video :mutedmuted :autoplayautoplay controls /video div v-ifstatus loading classmonitor-player__loading 正在连接视频流... /div div v-else-ifstatus error classmonitor-player__error span视频流加载失败/span button clickhandleRetry重新连接/button /div /div /template script setup langts import { ref, reactive, onMounted, onBeforeUnmount, watch } from vue import flvjs from flv.js interface PlayerProps { url: string muted?: boolean autoplay?: boolean retryInterval?: number maxRetry?: number } const props withDefaults(definePropsPlayerProps(), { muted: true, autoplay: true, retryInterval: 3000, maxRetry: 5 }) const videoRef refHTMLVideoElement() let player: flvjs.Player | null null let retryCount 0 let retryTimer: number | null null const status refloading | playing | error(loading) function destroyPlayer() { if (player) { player.pause() player.unload() player.detachMediaElement() player.destroy() player null } if (retryTimer ! null) { window.clearTimeout(retryTimer) retryTimer null } } function initPlayer() { if (!videoRef.value || !props.url) return destroyPlayer() status.value loading if (!flvjs.isSupported()) { status.value error console.error(当前浏览器不支持FLV播放) return } player flvjs.createPlayer( { type: flv, isLive: true, url: props.url, hasAudio: false, cors: true }, { enableStashBuffer: false, stashInitialSize: 128 } ) player.attachMediaElement(videoRef.value) player.load() player.on(flvjs.Events.ERROR, (errorType: string, errorDetail: string) { console.warn(flv错误:, errorType, errorDetail) handlePlayError() }) } function handlePlayError() { if (status.value error) return status.value error scheduleRetry() } function scheduleRetry() { if (retryCount props.maxRetry) { status.value error return } retryCount 1 retryTimer window.setTimeout(() { console.log(第${retryCount}次重连) initPlayer() }, props.retryInterval) } function handleRetry() { retryCount 0 initPlayer() } watch( () props.url, () { retryCount 0 initPlayer() } ) onMounted(initPlayer) onBeforeUnmount(() { destroyPlayer() }) /script style scoped .monitor-player { position: relative; width: 100%; height: 100%; background-color: #000; overflow: hidden; } .monitor-player__video { width: 100%; height: 100%; object-fit: contain; } /* 省略部分样式细节 */ /style这段代码有几个值得说的设计点。第一点flvjs.Events.ERROR事件里做了分级处理记录错误、切换UI状态、安排定时重连。监控摄像头断流是常态设备重启、网络抖动都会导致播放中断如果没有自动重连用户每隔几分钟就要手动刷新页面体验非常差。第二点destroyPlayer里必须把player.pause()、unload()、detachMediaElement()、destroy()依次调一遍少一步都可能造成内存泄漏或者下次播放异常。特别是Vue3组件频繁挂载销毁的场景不清理干净页面会越来越卡。第三点onBeforeUnmount里销毁播放器并清理定时器这是最容易踩坑的地方。开发者往往只记得初始化忘了销毁结果组件离开页面后video还在后台拉流既费带宽又费性能。3.3 从海康平台获取实时流地址的对接细节前端组件拿到url才能播这个url怎么来是整个对接里最容易出问题的一环。分两种方式说。方式一后端通过RTSP直接拉流再转成HTTP-FLV。这个方案里前端不需要关心设备IP和端口只需要后端暴露一个统一格式的播放地址比如http://your-server.com/live/device/{deviceId}.flv后端拉流转发的实现如果你们团队用的是Java业界常用的方案是基于Netty封装的海康转流工具如果用的是Node.js可以用ffmpeg子进程把RTSP转成FLV然后通过HTTP中间件推流。我这次项目里后端是Java转流服务的关键在于保持长连接、处理断线重连和鉴权这些不必在前端考虑但前端需要知道后端给的url不是一成不变的有些实现在设备断开后会清掉对应流通道需要前端自动重连来触发重新拉流。方式二通过海康开放平台API取流。流程是后端用AppKey和Secret生成签名调用海康平台接口获取该设备的HLS地址然后返回给前端。这个方式的安全性好不暴露设备内网地址但有个细节要特别注意海康平台返回的播放地址是有有效期的过期之后前端再请求旧地址会直接黑屏。所以前端要做一层地址过期刷新逻辑“播放失败但不报错”时要主动重新拉取地址再播放。我在项目里实际采用的是“双重播放地址”PC端用后端转出的HTTP-FLV地址移动端用海康平台返回的HLS地址。组件里加一个type属性根据platform判断用flv.js还是hls.js去播放。3.4 TS类型封装与全局声明技巧TypeScript在这个项目里的作用不只是类型提示更重要的是把播放器实例和流地址这两种不稳定状态管住。我建了一个src/hooks/useMonitorPlayer.ts把组件里的播放逻辑抽出来方便多个页面共用。大概结构是这样import { ref, unref, type Ref } from vue import flvjs from flv.js interface UseMonitorPlayerOptions { url: Refstring | string videoEl: RefHTMLVideoElement | null onError?: (error: any) void onRetry?: (count: number) void } export function useMonitorPlayer(options: UseMonitorPlayerOptions) { const status refidle | loading | playing | error(idle) // ... 初始化、销毁逻辑 return { status, retry, destroy } }这样组件代码瘦身业务页面里只需要const { status, retry, destroy } useMonitorPlayer({ url: playUrl, videoEl })除hook以外还需要给window上挂的全局方法写类型声明。有些海康设备页面还会用ActiveX控件或老SDK的全局对象这类对象没有npm类型包只能自己声明。在src/types/global.d.ts里这样写export {} declare global { interface Window { WebControl?: any JSPlugin?: any } }这算是前端对接监控平台的老操作了先声明再使用避免ts编译报红。4. 联调踩坑与常见问题排错实录4.1 黑屏但有声音或者干脆没有画面这类问题我联调时遇到不下三次原因五花八门但排查思路基本固定。第一步先不从前端查直接用VLC或者其他流媒体工具拉同样的RTSP/FLV地址确认源流是否正常。如果VLC也黑屏说明问题出在后端拉流或转流环节设备或网络原因居多如果VLC正常只有页面异常才需要查前端代码。第二步打开浏览器控制台看flv.js的日志。flv.js本身会输出比较详细的日志能看到是否连接成功、是否收到数据帧。如果日志显示MediaError通常是解码问题比如H.265编码的流浏览器不原生支持需要硬解或转码。海康很多摄像头默认就是H.265编码这是个大坑。解决方式是在设备配置里把编码改成H.264或者后端转码时统一转成H.264。第三步确认摄像头子码流和主码流的编码参数。之前遇到一次主码流是H.265子码流是H.264前端大屏拉主码流黑屏改成拉子码流就正常。这种问题不在代码而在配置容易让人排查很久。4.2 跨域与鉴权踩得最深的一个坑前端播放器的跨域问题比普通API请求更隐蔽。普通请求跨域会直接报CORS错误一眼就能定位但视频流跨域有时候表现成“能连上但一直loading”或者“第一帧出不来”。原因是flv.js请求流地址时浏览器会对跨域请求做预检如果后端没有返回正确的跨域头请求可以被发出但数据不会正常接收。排查方式是打开Network面板看视频流请求的Response Headers里有没有Access-Control-Allow-Origin。后端转流服务统一加上跨域响应头这是标准解法Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, OPTIONS如果流地址还涉及鉴权比如需要在URL上带token更稳妥的做法是把鉴权放在Cookie或请求头里避免token出现在浏览器历史记录或日志里。不过后端实现HTTP-FLV转发时自定义Header需要后端配合如果后端不好改折中办法是生成一次性鉴权URL有效期2分钟过期后由前端重新拉取地址。4.3 延迟越来越高和内存持续增长HTTP-FLV播放延迟问题主要出在flv.js的缓存机制上。enableStashBuffer默认开启会把接收到的数据先缓存一段再交给播放器相当于一个缓冲池导致延迟累加。设成false能显著降低延迟但对网络稳定性要求更高网络抖动时更容易卡顿。我的实践是把这个参数做成可配置项在设置面板里让用户选择“流畅优先”还是“实时优先”。内存增长问题需要分清是播放器内部泄漏还是组件反复创建导致的。如果用Vue DevTools排查发现未加载页面的组件也被创建了播放实例那就要检查路由懒加载和组件销毁逻辑。另外flv.js在直播流长时间播放后如果开启了缓存机制内存占用会缓步上升这是播放器库本身的问题一般通过定期重载播放器或限制连续播放时长来规避。我整理了一张自查表每次排查都按这个顺序过一遍问题现象最先检查的环节常用工具或命令黑屏无声音源流地址是否可直接播放VLC播放器拉流验证播放卡顿网络带宽和转流服务负载后端日志、服务器带宽监控延迟累加flv.js缓存参数播放器配置项有声音无画面编码格式、容器格式查看流媒体编码信息组件销毁后占内存播放器是否彻底destroy浏览器Memory面板对比4.4 TypeScript类型不匹配导致的编译失败flv.js使用过程中有一个TS编译常见问题flvjs.createPlayer的返回类型是Player但事件回调参数类型定义比较宽泛直接在回调里使用参数时容易报类型错误。解决办法是定义自己的事件处理类型不直接用官方类型推导import flvjs from flv.js type FlvErrorHandler ( errorType: flvjs.ErrorTypes, errorDetail: flvjs.ErrorDetails ) void const onError: FlvErrorHandler (type, detail) { // 处理逻辑 }这种“定义接口再写实现”的方式在封装组件和Hook时特别管用类型定义本身就是一份代码文档。5. 一点个人体会这套对接方案跑下来最大的感受是监控对接技术难点并不全在写代码上而是在理清一条完整的链路从设备取流、服务转发、再到前端渲染与重连兜底。每一步都对应一个容易被忽略的细节比如H.265编码不兼容、跨域请求头缺失、缓存导致的延迟累积、组件销毁不彻底带来的内存泄漏。如果以后再接到类似的项目我可能会在第一版就评估一下WebRTC的接受度毕竟海康的新型号设备对WebRTC取流的支持越来越完善延迟和画质控制都比HTTP-FLV方案有优势。但那套方案对服务器的要求更高也要看团队运维能力。对于直接上手的朋友我的建议是先把第2章的链路原理吃透再动手写组件代码。原理通了后面遇到问题至少有排查方向不会一头扎进代码里瞎猜。代码实现上组件化、Hook化、类型化这三件事做好后面扩展多屏预览、历史回放、云台控制都会顺手很多。

相关新闻

Pygame弹球动画的三大核心:delta-time、状态机与时间轴

Pygame弹球动画的三大核心:delta-time、状态机与时间轴

1. 为什么一个弹球动画,值得花三小时重写五遍?“Pygame——动画的灵活控制,弹弹球实例”——看到这个标题,我第一反应不是点开,而是把键盘推远了一点。不是因为它太简单,恰恰相反,它太容易被做“…

2026/10/14 20:00:08 阅读更多 →
SkyWalking Java Agent在Kubernetes中的6种生产级部署模式

SkyWalking Java Agent在Kubernetes中的6种生产级部署模式

1. 项目概述:为什么Java微服务团队绕不开SkyWalking Agent的K8s部署问题在某公司落地微服务架构的第三年,我接手了三个核心Java应用的可观测性升级任务——不是加监控大盘,而是让每个Pod里跑的Spring Boot服务,能自动上报调用链、…

2026/10/14 20:00:08 阅读更多 →
电力系统可靠性评估MATLAB实现:蒙特卡洛模拟与LOLE/EENS指标解析

电力系统可靠性评估MATLAB实现:蒙特卡洛模拟与LOLE/EENS指标解析

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

2026/10/14 20:00:08 阅读更多 →

最新新闻

3天从85%降到20%!这3个降AIGC平台让我导师直夸原创

3天从85%降到20%!这3个降AIGC平台让我导师直夸原创

还记得上周三凌晨两点,当我第三次收到知网AIGC检测报告时,手心都在冒汗——85%的AI相似度,40%的查重率,这意味着我的毕业论文根本达不到盲审要求。导师直接在我的初稿上批注“学术合规性存疑,建议重写”,那…

2026/10/14 20:50:15 阅读更多 →
Simple Live跨平台播放实现:media_kit与mpv的清晰度、硬件解码策略

Simple Live跨平台播放实现:media_kit与mpv的清晰度、硬件解码策略

【免费下载链接】dart_simple_live 简简单单的看直播(SimpleLive续) 项目地址: https://gitcode.com/gh_mirrors/dart/dart_simple_live 点击查看 免费下载 Simple Live(simple_live_app)是一款"简简单单看直播&…

2026/10/14 20:50:15 阅读更多 →
自考论文降AI率工具实测:8款改写器对比与避坑指南

自考论文降AI率工具实测:8款改写器对比与避坑指南

我上个月帮一位考友改了篇自考本科毕业论文,过学校查重平台后跳出一行小字:“疑似AI生成内容占比偏高”,把人吓够呛,但那篇文章确实是他自己一个字一个字敲出来的。这绝不是个例。2026年自考季临近,找我咨询降AI率工具…

2026/10/14 20:50:15 阅读更多 →
AI配音如何处理多音字?文字转语音发音错误的解决方法

AI配音如何处理多音字?文字转语音发音错误的解决方法

使用AI配音制作视频时,有些句子看起来没有问题,生成语音后却会出现读音错误。尤其是多音字、人名、地名和专业术语,即使上下文明确,语音合成也不一定能准确判断。如果旁白中出现错误发音,不仅影响听感,还可…

2026/10/14 20:50:15 阅读更多 →
智慧校园供应商考察报告怎么写?关键要素与评估标准全解析

智慧校园供应商考察报告怎么写?关键要素与评估标准全解析

去年帮一所高校做智慧校园数据中台的选型,前后面谈了六家供应商,每家都拍着胸脯说自家平台架构先进、接口开放、实施经验丰富。可等到考察报告写完、提交评审会的时候,我心里清楚,六家里面至少有两家在“先进架构”四个字上掺了水…

2026/10/14 20:50:15 阅读更多 →
yolov8-face 核心算子全解析:NMS、边界框格式转换与掩码处理的源码级指南

yolov8-face 核心算子全解析:NMS、边界框格式转换与掩码处理的源码级指南

人工智能计算机视觉深度学习 【免费下载链接】yolov8-face yolov8 face detection with landmark 项目地址: https://gitcode.com/gh_mirrors/yo/yolov8-face 点击查看 免费下载 本文是 ops.md 参考文档的深度展开,以 ultralytics/yolo/utils/ops.py 为…

2026/10/14 20:49:14 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/14 7:53:34 阅读更多 →