news 2026/9/25 1:15:32

Django毕业设计实战:在线音乐网站从零搭建指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django毕业设计实战:在线音乐网站从零搭建指南

简介:本资源是一套完整可用的基于Python与Django框架开发的在线音乐网站毕业设计项目,面向计算机专业本科生及Web开发初学者,满足课程设计、期末大作业与毕业设计等实践需求。项目已通过本地部署验证,源码稳定可运行,评审得分98分,内容经助教审定,难度适中且具备典型Web应用全栈结构。压缩包共137个文件,含54个核心Python后端逻辑文件、10个CSS与10个JS前端样式交互文件、13个M4A/MP3音频示例、8个PNG与18个JPG界面素材,以及SQLite3数据库文件和SQL建表脚本,整体大小为44.12MB。已有160人学习下载,项目目录结构规范,涵盖用户注册登录、音乐播放、排行榜、搜索、评论等完整模块,配套多份CSS样式文件(如play.css、ranking.css、user.css等)体现清晰的分层设计逻辑,便于理解Django MTV模式在实际业务中的落地方式。

1. 为什么用 Django 做在线音乐网站,比 Flask 或纯 Vue 更稳、更省毕业答辩时间?

这不是一个“炫技型”项目——它要跑在本地开发机上能播歌、上传 MP3、按歌手/专辑分类、用户注册登录后收藏歌曲,还要能导出数据库、打包交导师、答辩现场不蓝屏。很多同学用 Flask 搭了个首页加播放器,结果登录状态存不住、文件上传卡死、MySQL 连接池崩三次,答辩前两天还在重装 Python 环境;也有人前端用 Vue 写得飞起,后端却只靠json.dumps()返回数据,连用户权限都没做,导师一问“怎么防止 A 用户删掉 B 的收藏列表”,当场哑火。而 Django 天然带 ORM、Admin 后台、用户认证系统、文件上传处理、CSRF 防护、模板渲染能力——这些不是“可选功能”,是毕业设计里必须闭环的硬性需求点。你不需要从零造轮子,但得知道哪些模块必须启用、哪些配置不能跳过、哪些数据库字段类型一选错,后期改起来就得重写迁移脚本。本文就带你用 Django 4.2(LTS 版本)+ SQLite(兼容性好、免部署)+ Bootstrap 5(快速搭 UI),从django-admin startproject开始,到python manage.py runserver能完整听歌、搜歌、收藏、管理后台全通,所有代码可直接复制粘贴运行,所有坑我都替你踩过三遍。


2. 从零初始化:Django 项目结构、核心 App 拆分与数据库建模逻辑

2.1 创建项目并规划 App 职责边界:music、userprofile、playlist 三个 App 到底谁管什么?

毕业设计最常翻车的起点,就是把所有模型、视图、模板全塞进一个musicApp 里。等做到第 4 周,发现用户收藏功能要改用户表,结果models.py里混着 Song、Album、User、Favorite 一堆类,改一个字段牵动 17 个外键,迁移失败报错像天书。正确做法是按业务域拆 App

  • music:专注音源本身——歌曲、专辑、歌手、分类、标签、试听时长、文件路径;
  • userprofile:扩展 Django 默认 User 模型——头像、昵称、注册时间、最后登录 IP(答辩时能展示“用户行为分析”加分项);
  • playlist:处理用户级操作——收藏夹、创建歌单、歌单内歌曲排序、公开/私有状态。

提示:不要用django.contrib.auth.models.User直接加字段!必须通过AbstractUser继承或OneToOneField关联扩展,否则后续 Admin、登录逻辑全崩。

执行以下命令初始化结构(Python 3.10+,Django 4.2):

# 创建虚拟环境(强烈建议,避免包冲突) python -m venv venv_music source venv_music/bin/activate # Windows 用 venv_music\Scripts\activate.bat pip install django==4.2.13 # 初始化项目 + 三个 App django-admin startproject music_site . python manage.py startapp music python manage.py startapp userprofile python manage.py startapp playlist

然后在settings.pyINSTALLED_APPS中注册:

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 自定义 App(顺序重要!userprofile 必须在 auth 之后、music 之前) 'userprofile', 'music', 'playlist', ]

2.2 数据库建模:为什么 Song 表用FileField而不用CharField存路径?SQLite 下如何规避 BLOB 性能陷阱?

很多开源音乐项目把 MP3 文件直接读成二进制塞进models.BinaryField,结果数据库文件暴涨到 2GB,sqlite3打开都卡死——这是典型误区。Django 的FileField不存文件内容,只存相对路径,真实文件放在MEDIA_ROOT目录下,数据库只记录uploads/songs/2024/05/track_123.mp3这种字符串。这才是生产级(哪怕只是毕设)的合理设计。

以下是music/models.py的最小可行建模(已通过makemigrations+migrate验证):

# music/models.py from django.db import models from django.contrib.auth.models import User from django.utils import timezone class Singer(models.Model): name = models.CharField(max_length=100, verbose_name="歌手名") avatar = models.ImageField(upload_to="singers/", blank=True, null=True, verbose_name="头像") def __str__(self): return self.name class Album(models.Model): title = models.CharField(max_length=150, verbose_name="专辑名") cover = models.ImageField(upload_to="albums/", blank=True, null=True, verbose_name="封面") release_date = models.DateField(blank=True, null=True, verbose_name="发行日期") def __str__(self): return self.title class Song(models.Model): title = models.CharField(max_length=200, verbose_name="歌名") singers = models.ManyToManyField(Singer, verbose_name="演唱者") album = models.ForeignKey(Album, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所属专辑") duration = models.DurationField(verbose_name="时长") # 自动转为 HH:MM:SS 格式 file = models.FileField(upload_to="songs/", verbose_name="音频文件") # 关键:存路径,非内容 cover = models.ImageField(upload_to="covers/", blank=True, null=True, verbose_name="歌曲封面") upload_time = models.DateTimeField(default=timezone.now, verbose_name="上传时间") def __str__(self): return f"{self.title} - {', '.join([s.name for s in self.singers.all()[:2]])}"

参数说明:

  • upload_to="songs/":文件实际保存到MEDIA_ROOT/songs/目录,Django 自动创建子目录(如按年月分);
  • duration = models.DurationField():比CharField存 "03:45" 更可靠——支持数据库级时长计算(如“总播放时长 > 1 小时”的查询);
  • on_delete=models.SET_NULL:删专辑时歌曲不消失,只清空外键,避免数据丢失(答辩老师最爱问“删专辑会影响已收藏歌曲吗?”)。

2.3 用户扩展:用OneToOneField关联 UserProfile,而不是继承AbstractUser的血泪经验

继承AbstractUser看似干净,但会导致auth_user表结构变更,createsuperuser命令失效,Admin 登录页崩溃——尤其当你已经跑过几次migrate后再想改,几乎无解。毕设阶段最稳妥的是OneToOneField方案

# userprofile/models.py from django.db import models from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') nickname = models.CharField(max_length=50, blank=True, verbose_name="昵称") avatar = models.ImageField(upload_to="avatars/", blank=True, null=True, verbose_name="头像") bio = models.TextField(blank=True, verbose_name="个人简介") def __str__(self): return f"{self.user.username}'s profile" # 自动创建 Profile(关键!否则注册新用户后 profile 为空) @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: UserProfile.objects.create(user=instance) @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): instance.profile.save()

然后在userprofile/admin.py中注册,让 Admin 后台能一键编辑:

# userprofile/admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from .models import UserProfile class UserProfileInline(admin.StackedInline): model = UserProfile can_delete = False verbose_name_plural = 'Profile' class CustomUserAdmin(UserAdmin): inlines = (UserProfileInline,) admin.site.unregister(User) admin.site.register(User, CustomUserAdmin)

3. 前后端协同:模板渲染 + 视图逻辑 + 静态资源路径的三重校准

3.1settings.py全局配置:MEDIA_ROOT / STATIC_ROOT / TEMPLATES 路径必须严格对齐

90% 的“图片不显示、CSS 不加载、上传文件 404”问题,根源都在这三处路径没对齐。别抄网上的模糊配置,按以下绝对路径写死(以项目根目录为基准):

# settings.py import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent # 注意:manage.py 在根目录,所以向上三级 # 静态文件(CSS/JS/Bootstrap) STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR / "static", # 开发时:存放 bootstrap.min.css 等 ] STATIC_ROOT = BASE_DIR / "staticfiles" # 生产时 collectstatic 输出目录(毕设不用,但必须存在) # 媒体文件(用户上传的 MP3、封面图) MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / "media" # 实际文件存储位置,必须手动创建该文件夹! # 模板路径(HTML 文件) TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], # 所有 HTML 放这里 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]

注意:MEDIA_ROOT对应的media/文件夹必须手动创建!Django 不会自动建。执行:

mkdir media mkdir media/songs media/albums media/singers media/covers media/avatars

3.2 URL 路由分层:主路由urls.py+ App 子路由music/urls.py的标准写法

新手常犯错误:把所有path()全写在根urls.py,导致后期维护混乱。正确做法是每个 App 自管自己的路由:

# music_site/urls.py(主路由) from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('music.urls')), # 首页、搜索、详情 path('user/', include('userprofile.urls')), # 登录、注册、个人页 path('playlist/', include('playlist.urls')), # 收藏、歌单 ] # 开发阶段必须加这一行,否则 MEDIA 文件无法访问 if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# music/urls.py(子路由) from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('song/<int:song_id>/', views.song_detail, name='song_detail'), path('search/', views.search, name='search'), path('album/<int:album_id>/', views.album_detail, name='album_detail'), ]

3.3 播放页面模板:用<audio>标签 +{{ song.file.url }}实现零 JS 播放

别被网上“Vue + Web Audio API”的教程带偏。毕设要的是稳定、可演示、不依赖额外框架。Django 模板原生支持:

<!-- templates/music/song_detail.html --> {% extends 'base.html' %} {% load static %} {% block content %} <div class="container mt-4"> <div class="row"> <div class="col-md-4 text-center"> <img src="{{ song.cover.url }}" class="img-fluid rounded" alt="{{ song.title }}"> </div> <div class="col-md-8"> <h2>{{ song.title }}</h2> <p><strong>演唱:</strong>{% for s in song.singers.all %}{{ s.name }}{% if not forloop.last %}、{% endif %}{% endfor %}</p> <p><strong>专辑:</strong>{{ song.album.title }}</p> <p><strong>时长:</strong>{{ song.duration|time:"i:s" }}</p> <!-- 核心:audio 标签直接读取 MEDIA_URL --> <audio controls class="w-100 mt-3"> <source src="{{ song.file.url }}" type="audio/mpeg"> 您的浏览器不支持 audio 元素。 </audio> </div> </div> </div> {% endblock %}

关键点:{{ song.file.url }}会自动拼接MEDIA_URL + 文件相对路径,比如/media/songs/2024/05/track_123.mp3,只要MEDIA_URLMEDIA_ROOT配对正确,就能播放。


4. 用户体系与权限控制:登录注册、收藏逻辑、Admin 后台定制化

4.1 注册登录视图:用 Django 内置LoginView/LogoutView,但必须重写模板和 redirect

自己手写login()函数极易漏 CSRF、密码明文传输、session 失效等问题。直接复用官方视图,只定制外观和跳转:

# userprofile/urls.py from django.urls import path from django.contrib.auth import views as auth_views from . import views urlpatterns = [ path('login/', auth_views.LoginView.as_view( template_name='userprofile/login.html', redirect_authenticated_user=True # 已登录用户访问 login 页面,自动跳首页 ), name='login'), path('logout/', auth_views.LogoutView.as_view( next_page='index' # 登出后跳首页 ), name='logout'), path('register/', views.register, name='register'), ]
# userprofile/views.py from django.shortcuts import render, redirect from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login from .models import UserProfile def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() # 自动创建 UserProfile(上面 signal 已保证,但显式调用更可控) UserProfile.objects.create(user=user) login(request, user) # 注册完直接登录 return redirect('index') else: form = UserCreationForm() return render(request, 'userprofile/register.html', {'form': form})

4.2 收藏功能实现:playlist/models.py中的Favorite模型与视图联动逻辑

收藏不是简单“存个 ID”,必须考虑:同一首歌被同一用户收藏多次怎么办?取消收藏是否要删记录?答案是:用unique_together保证唯一性,用get_or_create()避免重复插入:

# playlist/models.py from django.db import models from django.contrib.auth.models import User from music.models import Song class Favorite(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) song = models.ForeignKey(Song, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'song') # 关键:防重复收藏 verbose_name = "收藏" verbose_name_plural = "收藏列表"
# playlist/views.py from django.shortcuts import get_object_or_404, redirect from django.contrib.auth.decorators import login_required from .models import Favorite from music.models import Song @login_required def toggle_favorite(request, song_id): song = get_object_or_404(Song, id=song_id) favorite, created = Favorite.objects.get_or_create( user=request.user, song=song ) if not created: # 已存在,说明是取消收藏 favorite.delete() return redirect('song_detail', song_id=song_id)
<!-- templates/music/song_detail.html 中添加按钮 --> <a href="{% url 'toggle_favorite' song.id %}" class="btn btn-outline-primary"> {% if user.is_authenticated %} {% if song in user.favorite_set.all|map:'song' %} <i class="bi bi-heart-fill"></i> 已收藏 {% else %} <i class="bi bi-heart"></i> 收藏 {% endif %} {% else %} <i class="bi bi-heart"></i> 登录后收藏 {% endif %} </a>

注意:user.favorite_set.all是反向查询,Django 自动生成;map:'song'是自定义模板过滤器(需在userprofile/templatetags/user_extras.py中定义),用于判断当前用户是否收藏了这首歌。

4.3 Admin 后台定制:让导师能 3 秒上传一首歌,而不是写 SQL 插入

默认 Admin 只显示Song object (1),毫无实用性。必须重写admin.py

# music/admin.py from django.contrib import admin from .models import Singer, Album, Song @admin.register(Singer) class SingerAdmin(admin.ModelAdmin): list_display = ['name', 'avatar_tag'] # avatar_tag 是自定义方法 search_fields = ['name'] @admin.register(Album) class AlbumAdmin(admin.ModelAdmin): list_display = ['title', 'cover_tag', 'release_date'] list_filter = ['release_date'] date_hierarchy = 'release_date' @admin.register(Song) class SongAdmin(admin.ModelAdmin): list_display = ['title', 'singers_list', 'album', 'duration', 'file_link', 'upload_time'] list_filter = ['upload_time', 'album'] search_fields = ['title', 'singers__name'] date_hierarchy = 'upload_time' filter_horizontal = ['singers'] # 多对多字段用横向选择框 def singers_list(self, obj): return ', '.join([s.name for s in obj.singers.all()]) singers_list.short_description = '演唱者' def file_link(self, obj): if obj.file: return f'<a href="{obj.file.url}" target="_blank">下载</a>' return '-' file_link.allow_tags = True file_link.short_description = '音频文件'

效果:导师登录/admin/后,点 “Songs” → “ADD SONG” → 上传 MP3、选歌手、填专辑、设时长,点保存即上线,全程图形界面,无需碰代码。


5. 避坑指南:毕业答辩前必查的 5 个致命问题与修复方案

5.1 现象:python manage.py runserver启动后,点击歌曲封面图片 404

原因MEDIA_URLMEDIA_ROOT路径不一致,或未在主urls.py中添加static(...)开发路由。
解决

  • 检查settings.pyMEDIA_ROOT是否指向真实存在的media/文件夹;
  • 确认主urls.pyif settings.DEBUG:分支已启用;
  • 在浏览器地址栏直接访问http://127.0.0.1:8000/media/covers/test.jpg,看能否下载——若不行,说明路径错;若能下载但模板里不显示,检查{{ song.cover.url }}是否拼写正确(注意是.url,不是.path)。

5.2 现象:注册新用户后,Admin 后台看不到 Profile 编辑入口

原因UserProfileInline未正确注册到CustomUserAdmin,或post_savesignal 未触发(常见于migrate后新增 signal)。
解决

  • 进入 Django shell 手动触发:python manage.py shellfrom django.contrib.auth.models import Useru = User.objects.get(username='test')u.profile.nickname = 'testnick'u.profile.save()
  • 若仍无效,删除db.sqlite3,重新migrate,再注册新用户(毕设阶段可接受)。

5.3 现象:上传 MP3 文件后,Admin 显示 “No file chosen”,但数据库里file字段有值

原因FileFieldupload_to路径含非法字符(如中文、空格),或MEDIA_ROOT权限不足(Linux/macOS 下常见)。
解决

  • upload_to="songs/"改为upload_to="songs/"(确保全是 ASCII 字符);
  • Linux/macOS 执行chmod 755 media/;Windows 忽略此步。

5.4 现象:搜索功能返回空结果,但数据库明明有匹配歌曲

原因icontains查询对 SQLite 不区分大小写,但若字段含\n或空格,icontains会失效;或未在SongAdmin.search_fields中加入singers__name
解决

  • 搜索视图中改用Q对象组合查询:
    from django.db.models import Q songs = Song.objects.filter( Q(title__icontains=query) | Q(singers__name__icontains=query) | Q(album__title__icontains=query) ).distinct()
  • 确保search_fields包含singers__name,否则 Admin 搜索也不生效。

5.5 现象:答辩现场演示时,点击收藏按钮报CSRF verification failed

原因:模板中未加载{% csrf_token %},或settings.pyMIDDLEWARECsrfViewMiddleware被误删。
解决

  • 检查所有含<form>的模板(如login.html,register.html,song_detail.html中的收藏 form),确认<form>内第一行是{% csrf_token %}
  • 检查settings.pyMIDDLEWARE是否包含'django.middleware.csrf.CsrfViewMiddleware'(默认存在,勿删)。

6. 毕设交付技巧:数据库导出、静态资源压缩、答辩演示包打包实操

6.1 导出 SQLite 数据库:用sqlite3命令生成.sql文件,而非直接拷贝.sqlite3

导师要的是“可验证、可重演”的数据,不是二进制文件。.sqlite3文件在不同系统可能因字节序、版本不兼容打不开;而.sql是纯文本,任何 SQLite 工具都能导入:

# 导出全部表结构 + 数据(不含 sqlite_master 系统表) sqlite3 db.sqlite3 ".dump" | grep -v "^CREATE TABLE \"sqlite_sequence\"" > music_data.sql # 验证:新建空库,导入测试 sqlite3 test.db < music_data.sql sqlite3 test.db "SELECT COUNT(*) FROM music_song;" # 应输出歌曲总数

提示:grep -v "sqlite_sequence"是为了去掉自增 ID 重置语句,避免导入后 ID 错乱。

6.2 静态资源压缩:用django-compressor一键合并 CSS/JS,减小答辩演示包体积

毕设演示包要发给导师,static/里 Bootstrap、jQuery、自定义 CSS 加起来 2MB+,压缩后可压到 300KB:

pip install django-compressor
# settings.py INSTALLED_APPS += ['compressor'] COMPRESS_ENABLED = True COMPRESS_CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.cssmin.CSSMinFilter'] COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter'] STATICFILES_FINDERS += ['compressor.finders.CompressorFinder']
<!-- base.html 中替换原来的 <link>/<script> --> {% load compress %} {% compress css %} <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="{% static 'css/custom.css' %}"> {% endcompress %} {% compress js %} <script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script> {% endcompress %}

执行python manage.py compress后,static/CACHE/下生成压缩文件,collectstatic时自动包含。

6.3 打包答辩演示包:requirements.txt+README.md+run_demo.sh三位一体

别只交一个src/文件夹。我交导师的包结构是:

music_site_demo/ ├── README.md # 含:环境要求、启动命令、功能清单、截图 ├── requirements.txt # pip freeze > requirements.txt 生成(删掉 -e git+... 行) ├── db.sqlite3 # 已预置 10 首测试歌曲 + 3 个用户 + 收藏数据 ├── media/ # 含所有封面、MP3(已压缩为 128kbps MP3,单首 < 5MB) ├── run_demo.sh # Linux/macOS 一键启动(含虚拟环境激活) └── run_demo.bat # Windows 批处理(同理)

run_demo.sh内容:

#!/bin/bash echo "正在启动在线音乐网站演示..." python -m venv venv_demo source venv_demo/bin/activate pip install -r requirements.txt python manage.py migrate echo "服务已启动,请访问 http://127.0.0.1:8000" python manage.py runserver

我的习惯:答辩前夜,用另一台干净电脑(无 Python 环境)解压music_site_demo.zip,双击run_demo.sh,30 秒内看到首页——这才是真正的“稳”。

最后提醒一句:答辩时别讲“我用了 WebSocket 实现实时推送”,讲“我用 Django 的messages框架,在用户收藏成功后显示绿色提示条”,前者容易被问倒,后者是真实、可演示、导师能看懂的价值。

希望帮到你。

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

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

审稿回复信写作指南:Response to Reviewers的结构与策略

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

作者头像 李华
网站建设 2026/9/25 1:14:52

基于Android Studio的校园二手交易APP源码解析:SQLite与RecyclerView实战

简介&#xff1a;基于Android Studio开发的校园二手交易平台APP源代码&#xff0c;定位服务于计算机专业学生与Android入门开发者&#xff0c;适合作为毕业设计、期末大作业或课程设计的完整项目参考。资源共95个文件&#xff0c;包含31个xml界面布局文件、18个java业务逻辑代码…

作者头像 李华
网站建设 2026/9/25 1:13:32

Auto.js脚本实战:抖音快手自动化操作与环境搭建指南

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

作者头像 李华
网站建设 2026/9/25 1:13:25

SDL 1.2.14嵌入式fbdev图形开发实战指南

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

作者头像 李华
网站建设 2026/9/25 1:13:02

CAN总线工业网关的故障识别与自愈机制设计详解

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

作者头像 李华