FastAPI和Django免写前端:用Swagger、Admin、Gradio自动生成项目首页
先说一个经常会遇到的场景。用FastAPI和Django做后端项目接口、模型、业务逻辑都写好了最后却卡在一个特别不起眼的地方首页。运营要一个能看数据的面板测试要一个能调接口的页面领导要一个能点按钮演示的环境。手里只有命令行和Swagger JSON谁会满意以前我也老老实实去写Vue、搭前端框架直到发现Python生态里其实有一套“自动生成首页”的打法FastAPI自带Swagger UIDjango自带Admin后台需要自定义交互的时候再用Gradio挂一块面板。很多场景下真的可以不用再为首页单独写前端代码。这篇内容就来聊聊这套思路怎么落地。适合正在做Python Web项目、天天被前端需求缠身的同学也适合刚学FastAPI和Django、想搞清楚框架自带功能到底能省多少事的入门者。我会把原理、配置、代码、踩过的坑都摊开来讲保证照着能复现。1. 先看清需求你要的其实不是一个炫酷首页而是能干活的面板1.1 首页的真实用途分类很多项目里说的“首页”根本不是一个高度定制的营销落地页更多是下面这几种内部数据维护页运营要录数据、改状态、查订单这种页面本质是CRUD。接口调试/演示页前后端还没联调或者产品经理想看看这个接口能干什么需要页面能填参数、发请求、看返回结果。对外展示/自助工具页非技术人员输入几个条件点一下按钮拿到结论比如订单查询、报表生成、AI问答。这三种需求对应的解决方案其实都能靠框架自带的“自动UI”顶上去。不要把“首页”和“前端工程”划等号先想清楚你究竟需要的是哪一种交互。1.2 自动生成方案与手写前端的取舍手写前端有它的优势可定制程度高、交互体验顺滑。但它的代价也很实际要维护Node环境、要写路由、要处理跨域、要联调联调再联调。对一个内部工具型页面来说一个模板组件库带来的维护成本可能已经超过了页面本身的价值。自动生成方案的优势在于“从模型出发”。FastAPI的路由和Pydantic模型天然能生成OpenAPI文档Django的模型自带头Admin管理Gradio只需要Python函数就能拼出一个可交互界面。它们不需要额外的js代码也不需要在前后端之间拆代码仓库。我列过一个权衡表基本每次开工前都会对一遍方案适合场景要不要写前端定制能力学习成本FastAPI Swagger/ReDoc接口调试、内部演示不用中等极低Django Admin数据维护后台不用高低Gradio面板对外产品型页面、自助查询不用少量Python布局高中手写Vue/React高度定制对外页面要写最高高结论很直接除非甲方明确要求“这个页面必须按设计稿还原”否则先用自动生成方案顶着完全够用。2. FastAPI项目Swagger就是一个开箱即用的交互首页2.1 Swagger页面从哪来很多人把/docs当作“给后端自己看的文档”但它的本质远不止于此。FastAPI启动时会把所有路由、参数、响应结构汇总成一份OpenAPI JSON再由Swagger UI读取后渲染成一个可交互页面。这个页面里每个接口都是一个“表单”能填参数、能点执行、能看响应。可以这样理解你写的每个路由函数函数签名里的每个参数Pydantic模型的每个字段都已经在自动生成页面里了。你真正要做的不是去写前端而是把这些“已有信息”调教得更容易用。给一个最基础但很实用的FastAPI项目from fastapi import FastAPI from pydantic import BaseModel, Field app FastAPI( title订单服务, description这是自动生成的首页可以直接在这里测试下单接口, version1.0.0, docs_url/docs, redoc_url/redoc, ) class OrderCreate(BaseModel): item_name: str Field(..., description商品名称) quantity: int Field(1, ge1, le99, description下单数量) remark: str Field(, description备注) app.post(/v1/orders, tags[订单管理], summary创建订单) def create_order(order: OrderCreate): return {code: 0, message: ok, data: {quantity: order.quantity, item: order.item_name}}启动后访问/docs你会得到一个完全可用的交互页面。这个页面不需要你在HTML里写任何代码但它已经是名副其实的“首页”了。2.3 让接口表单更友好的三个动作Swagger能不能当好首页关键不在于框架而在于你给的“元信息”够不够。实操中我一般做三件事。第一写好summary和description。不要觉得这只是注释在Swagger页面里这些文字会直接展示给测试、运营和产品看。描述里最好写清楚“这个接口能做什么、什么样的参数是合法值”。app.post( /v1/orders, tags[订单管理], summary创建订单, description提交商品名称和数量后创建一笔订单数量限制在1到99之间。, )第二给Pydantic字段补上examples。这能避免所有小白用户问“参数到底填什么”。填了example之后Swagger表单里会出现一个示例值点一下就能填充。class OrderCreate(BaseModel): item_name: str Field(..., description商品名称, examples[Python速成指南]) quantity: int Field(1, ge1, le99, description下单数量, examples[2])第三用tags把接口分组。接口一多页面就乱。通过路由装饰器的tags参数配合openapi_tags元数据Swagger左侧会生成分组折叠菜单体验不输正经前端做的导航栏。tags_metadata [ {name: 订单管理, description: 订单创建、查询、取消}, {name: 用户管理, description: 用户注册、登录、资料维护}, ] app FastAPI( title业务服务端, openapi_tagstags_metadata, )做完这三件事Swagger页面已经承担起了大部分内部协作的交互需求。2.4 从目录结构看框架自动化的红利经常有人在热搜里搜“fastapi项目目录结构”说明大家不只想知道接口怎么写更想知道项目怎么组织才能让自动生成页面不乱。我目前比较稳的一套结构是这样app/ ├── main.py # FastAPI实例、openapi_tags、挂载点 ├── core/ # 配置、依赖项 ├── models/ # Pydantic模型接口入参和出参 ├── routers/ # 按业务拆分路由 ├── services/ # 业务逻辑 └── static/ # 如果后面要自定义首页放这里router层和service层分开的意义在于Swagger页面上的路由摘要、参数校验都在routers层体现而业务逻辑藏在service层里页面结构会非常清爽。你不用为首页单独建一套前端工程后端代码本身就是页面结构的来源。3. Django项目Admin后台和原生模板也能变成可用首页3.1 Django Admin这份“免费午餐”有多划算Django圈子有句老话Admin是Django最好的隐藏功能之一。好多初学者觉得Admin只是个给开发人员看数据表的地方实际上它完全可以当做一个业务后台首页。原理在于Django会根据已注册的Model自动生成增删改查页面。你只需要把模型登记到admin.pyAdmin后台就会为每个模型生成列表页、详情页、编辑表单搜索、筛选、分页全都自带。运营同事日常改数据、补字段、查订单状态这一套已经覆盖了80%的需求。# models.py from django.db import models class Order(models.Model): item_name models.CharField(max_length128, verbose_name商品名称) status models.CharField(max_length16, defaultpending, verbose_name状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间)# admin.py from django.contrib import admin from .models import Order admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display (id, item_name, status, created_at) list_filter (status,) search_fields (item_name,) ordering (-created_at,) list_per_page 20访问/admin/登录后就能看到一个可以直接干活的订单管理首页。全程没有写一行前端代码。3.2 用ModelAdmin把后台改得像业务系统Django Admin真正能打的点是你可以用Python声明式地控制页面细节效果堪比手动写了一套后台。常用配置项我列几个都是实际项目里最高频的配置作用使用建议list_display列表页显示的字段把最重要的字段放前面list_filter侧边筛选状态、分类、日期这类枚举型字段search_fields搜索框查询字段放商品名、订单号fieldsets编辑页分组把信息分组展示降低混乱感readonly_fields只读字段创建时间这类不允许改的字段actions自定义批量操作如批量标记已发货注意一点list_display里不能随便放ManyToManyField和不能为空的外键否则列表页会有很多次查询。我一般通过方法字段来展示外键关联数据比如在OrderAdmin里加一个自定义方法admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display (id, item_name, status, owner_name, created_at) admin.display(description下单人) def owner_name(self, obj): return obj.owner.username if obj.owner else -这样既避免了查询开销页面又更贴近业务系统。3.3 再加一个与前端不冲突的数据看板如果Admin只是内部员工用那问题不大。但有些场景下还需要一个“打开首页就能看到今天订单量”的页面这时候不必上前后端分离框架。用Django原生的模板视图几十行代码就能搞定。# views.py from django.shortcuts import render from django.db.models import Count, Sum from .models import Order def dashboard(request): total Order.objects.count() status_count ( Order.objects.values(status) .annotate(countCount(id)) .order_by(status) ) context { total: total, status_count: status_count, } return render(request, dashboard/index.html, context)!-- templates/dashboard/index.html -- !DOCTYPE html html langzh head meta charsetUTF-8 title业务看板/title /head body h1今日概览/h1 p订单总数{{ total }}/p ul {% for row in status_count %} li{{ row.status }}{{ row.count }}/li {% endfor %} /ul /body /html把dashboard注册到URL它就是你的首页。不依赖任何静态资源打包工具就是一个模板文件部署到正式环境几乎零成本。4. 默认首页不够用用Gradio快速补齐产品级页面4.1 Gradio不只是模型演示工具提到Gradio很多人第一反应是机器学习Demo。但它的本质其实是一个“用Python函数生成Web界面”的库。输入框、按钮、下拉、图片、图表、JSON展示全部都有现成组件。只要写一个普通Python函数它的参数和返回值就能自动变成页面控件。这种特性放到FastAPI和Django项目里非常香。比如我要做一“订单查询首页”需求是输入订单号回显订单状态和金额。手写前端至少要做一个表单、一个请求、一个结果区用Gradio只需要定义一个Python函数。import gradio as gr def search_order(order_id: str): # 这里可以是你的业务查询函数 if order_id ABC123: return {status: 已支付, amount: 199.0} return {status: 未找到, amount: 0} demo gr.Interface( fnsearch_order, inputsgr.Textbox(label输入订单号, placeholder例如 ABC123), outputsgr.JSON(label订单信息), title订单查询, )这段代码不需要任何JS就能生成一个可以输入的网页。4.2 在FastAPI里用挂载方式接入GradioGradio官方提供了挂载到FastAPI的能力可以直接复用同一个端口避免开一堆服务窗口。import gradio as gr from fastapi import FastAPI app FastAPI(title业务聚合服务) def query_weather(city: str): # 使用你已有的Python业务逻辑 return f{city}晴26℃ demo gr.Interface( fnquery_weather, inputsgr.Textbox(label城市), outputsgr.Textbox(label天气信息), title天气查询窗口, ) app gr.mount_gradio_app(app, demo, path/home)启动后访问/home就能直接看到这个自定义面板。只要不跟FastAPI路由冲突path可以随便指定也可以挂载多个Gradio应用分别放在/home、/tools、/report等路径下。4.3 在Django项目中接入Gradio的两种方法Django没有和FastAPI一样天然的ASGI挂载方式但实际项目中我常用两种方案解决。第一种是iframe嵌入。Gradio作为独立服务跑在7860端口Django模板里用iframe把它嵌进来。这种做法最省事适合内部工具场景。iframe srchttp://127.0.0.1:7860/ stylewidth:100%; height:600px; border:0;/iframe第二种是代理转发适合不想暴露端口的情况。在Nginx里把/home路径代理到Gradio进程location /home/ { proxy_pass http://127.0.0.1:7860/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; }用Nginx代理时一定要带上Upgrade和Connection头否则Gradio的WebSocket能力会失效页面里用到实时交互时会一直转圈。4.4 把Gradio从“玩具”调到“像产品”Gradio默认样式比较朴素但它的定制能力远不止默认Interface。我用几个技巧把它调得更像产品用Blocks替代Interface布局更灵活。用gr.Markdown在页面顶部写说明文字。用theme参数换一套更舒服的配色。用gr.Row和gr.Column控制组件排列。一个稍微像样的产品首页长这样import gradio as gr def search_order(order_id: str, need_history: bool False): if not order_id: return {error: 订单号不能为空} result {status: 已支付, amount: 199.0} if need_history: result[history] [2025-01-01 下单, 2025-01-02 支付] return result with gr.Blocks(title业务助手, themegr.themes.Soft()) as demo: gr.Markdown(# 业务查询中心\n输入订单号即可查询订单状态和金额。) with gr.Row(): order_id gr.Textbox(label订单号, placeholderABC123) need_history gr.Checkbox(label同时查看历史记录) submit_btn gr.Button(查询) result gr.JSON(label查询结果) submit_btn.click( fnsearch_order, inputs[order_id, need_history], outputsresult, ) app gr.mount_gradio_app(create_app(), demo, path/service)这种布局不需要懂CSS写起来跟搭积木一样但看起来已经是一个正经的内部工具页面了。5. 实操中碰到的坑和排查实录5.1 Gradio挂载FastAPI时路由没生效我第一次用gr.mount_gradio_app时发现/home一直404。原因很简单我在定义FastAPI路由之前就挂载了Gradio或者挂载时把原有app覆盖掉了。正确做法是确保Gradio挂载放在最后from fastapi import FastAPI app FastAPI(title聚合服务) app.get(/health) def health(): return {status: ok} # 所有FastAPI路由定义完毕后再挂载Gradio app gr.mount_gradio_app(app, demo, path/home)如果你有一个单独的函数create_app()用它来作为挂载对象更靠谱。5.2 uvicorn日志丢失问题的排查热搜里有一条“uvicorn fastapi 日志丢失问题”这个坑我踩过。现象是启动正常、接口能调但控制台不打印访问日志或者只有零星请求日志。常见原因有几个。一是你直接用了logging.basicConfig()把日志级别搞得太高或者格式覆盖了uvicorn默认配置。二是启动了access_logFalse。三是用了Gunicorn时日志发送到了stderr但主进程没接管。我现在的稳定做法import uvicorn if __name__ __main__: uvicorn.run( main:app, host0.0.0.0, port8000, reloadTrue, access_logTrue, log_levelinfo, )如果还嫌访问日志太乱可以单独为uvicorn的error logger设置配置文件但别把uvicorn.access禁掉否则排查问题会很痛苦。5.3 Django Admin静态文件样式丢失本地好好的一上服务器Admin页面样式就全没了。这个问题的根因是只运行了runserver没执行collectstatic。简单的处理方式是python manage.py collectstatic --noinput然后确认项目里的STATIC_ROOT和STATIC_URL配置正确。如果使用Django自带的admin还需要确保django.contrib.staticfiles在INSTALLED_APPS里。别图省事直接在生产环境关闭DEBUG然后把静态文件交给Nginx处理这是一个必须要练的基本功。5.4 安全和权限别省自动生成首页虽然省事但不要让它裸奔。FastAPI的Swagger页面在正式环境如果不想暴露可以设置docs_urlNone禁止访问或者加一层依赖校验from fastapi import Depends, HTTPException, Header async def check_docs_auth(authorization: str Header(...)): if authorization ! secret-token: raise HTTPException(status_code401, detailno auth) app FastAPI( title内部服务, docs_url/docs, dependencies[Depends(check_docs_auth)], )Django Admin默认要求登录才能访问但要注意账号权限不要把is_staff和is_superuser统统给所有人。Gradio也提供了auth参数demo.launch( server_name0.0.0.0, server_port7860, auth[(admin, password123)], )当然launch这种形式在Django代理方案里是主体在FastAPI挂载方案里就不需要额外launch了直接用挂载方式跑在同一个进程更省事。5.5 核心优先做对剩下的都是效率问题这几年做项目我个人体会最深的是很多“首页需求”不是要一套完整前端而是要先有一个能点、能看、能反馈的交互物。FastAPI的Swagger、Django的Admin、Gradio的自定义面板正好分别覆盖了“接口调试、后台管理、自助工具”这三个高频场景。把这些框架自带的“自动UI”利用好比加班写一套Vue首页更快交付也更容易保持后端代码和页面的一致性。如果你的项目也卡在“首页不想写代码”这一步我的建议是先别急着搭构建工具。把服务端接口写规范把Pydantic模型和Django Model的元信息写清楚再决定是否用Gradio补一层自定义交互。你会发现后端代码写好了页面就已经完成了一大半。

相关新闻

Django视图选型与生产部署:FBV/CBV取舍及Nginx+uWSGI实战

Django视图选型与生产部署:FBV/CBV取舍及Nginx+uWSGI实战

很多人写Django视图,第一反应是“能用就行”。FBV随手写个函数、return一句render,项目跑起来也算顺顺利利。可一旦业务复杂起来,同一个列表页要分页、要筛选、要权限控制,你再从头手写一遍逻辑,写到第三遍就忍不住想骂…

2026/10/11 0:54:07 阅读更多 →
Python里的None差点让我加班到天亮

Python里的None差点让我加班到天亮

凌晨两点,盯着日志里那个诡异的 None,我意识到自己又栽在了这个「老熟人」手里——一个本以为是基础知识的坑,却在生产环境的异步任务中爆发,差点让整个数据 pipeline 瘫痪。 你以为的None,真的只是你以为吗&#xff1…

2026/10/11 0:54:07 阅读更多 →
Vue的computed属性居然还能这么坑?

Vue的computed属性居然还能这么坑?

上周线上环境突然报警,一个高频使用的订单汇总页面出现数据错乱。定位后发现,竟是 Vue 的 computed 属性在响应式依赖更新时出现「短路」现象——这个看似人畜无害的特性,在特定场景下会悄悄埋下定时炸弹。今天掏心窝子聊聊这个深坑&#xff…

2026/10/11 0:54:07 阅读更多 →

最新新闻

全新可重分析!代谢组质谱专用

全新可重分析!代谢组质谱专用

摘要代谢组学研究覆盖极为广阔的化学空间,产生大量实验数据,在化学、生物学及相关领域具备极高复用潜力。本文开发全新代谢组学质谱数据库MB‑POST,设计理念区别于现有平台,旨在充分释放上述数据价值。MB‑POST实现面向重分析的元…

2026/10/11 1:47:39 阅读更多 →
一物一码系统沉淀下来的用户数据、扫码数据和流向数据归谁?

一物一码系统沉淀下来的用户数据、扫码数据和流向数据归谁?

一物一码系统沉淀下来的用户数据、扫码数据和流向数据归谁? 太长不看版 “一物一码数据归谁”不宜简单回答为“归品牌方”或“归服务商”。要先区分用户提交的信息、扫码记录、产品与渠道流向记录、系统日志,再分别明确数据处理角色、访问和导出权限、使…

2026/10/11 1:47:39 阅读更多 →
设备密钥泄露了怎么办?Spring Boot 物联网平台的密钥轮换与设备禁用实战

设备密钥泄露了怎么办?Spring Boot 物联网平台的密钥轮换与设备禁用实战

正在搭物联网设备接入平台的时候,我遇到一个此前从没想过的问题:一机一密上线之后,设备密钥泄露了怎么办? 第一反应是"改密码不就行了",但往下想一步就发现没那么简单:我的平台库里只存密钥的 SH…

2026/10/11 1:47:39 阅读更多 →
餐饮 SaaS 优惠券系统架构演进(六)从优惠券模块到营销平台:Coupon / Promotion / Pricing / Benefit 的领域拆分与迁移路线

餐饮 SaaS 优惠券系统架构演进(六)从优惠券模块到营销平台:Coupon / Promotion / Pricing / Benefit 的领域拆分与迁移路线

基于当前项目源码反推领域边界:不是先建四个微服务,而是先把四类业务不变量拆清,再用 Strangler Pattern 渐进迁移,确保每一步都可验证、可回滚。DDD / Bounded ContextPricing EngineTransactional OutboxStrangler Migration结论…

2026/10/11 1:47:39 阅读更多 →
DataGridView 树形结构实战:WinForms中实现层级展示与交互

DataGridView 树形结构实战:WinForms中实现层级展示与交互

简介:面向C# WinForms开发者的实用教程资源,针对DataGridView控件无法直接展示层次化数据的问题,给出在Visual Studio 2012环境中通过自定义扩展实现树形列表的完整示例。资源共32个文件,以10个cs源代码文件为核心,涵盖…

2026/10/11 1:47:39 阅读更多 →
Agent Skills:把教了它二十遍的活,打包成一个文件

Agent Skills:把教了它二十遍的活,打包成一个文件

有个活我教了它不下二十遍。 每次都得重新交代一遍:先跑哪个命令、输出扔到哪个目录、结果按什么格式整理、注意那两个例外。说完它做得挺好,下次再问,又是从零开始。 后来我把这套话写成了一个文件。现在我只说一句"按那个来"&…

2026/10/11 1:46:38 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 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/10 5:23:50 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →