如何通过.NetCore开发实现StarBlog博客项目的主题切换功能?

2026-04-11 11:281阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1957个文字,预计阅读时间需要8分钟。

如何通过.NetCore开发实现StarBlog博客项目的主题切换功能?

系列文章 + 基于.NetCore开发博客项目 StarBlog + (1) 为什么需要自己写一个博客?系列文章 + 基于.NetCore开发博客项目 StarBlog + (2) 环境准备和创建项目系列文章 + 基于.NetCore开发博客项目 StarBlog + (3) 模型设计 + “

系列文章
  • 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客?
  • 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目
  • 基于.NetCore开发博客项目 StarBlog - (3) 模型设计
  • 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入
  • 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目
  • 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表
  • 基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面
  • 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示
  • 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入
  • 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流
  • 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计
  • 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译
  • 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能
  • 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能
  • ...
前言

近期一直在写代码,给博客新增了一些功能,本来想写一篇文章一起介绍的,不过好像篇幅会太长,想想还是分开写好了~

本文介绍主题切换功能,StarBlog博客页面基于Bootstrap5实现,Bootstrap本身的设计是比较简洁大方的,不过看久了也会腻,自己调css太麻烦,好在Bootstrap世界有个东西叫bootswatch,提供了几十套主题,我只需要npm install然后就可以实现主题切换功能了~

同时所有项目代码已经上传GitHub,欢迎各位大佬Star/Fork!

  • 网站地址:blog.deali.cn
  • 博客后端+前台项目地址:github.com/Deali-Axy/StarBlog
  • 管理后台前端项目地址:github.com/Deali-Axy/StarBlog-Admin
实现效果

照例先看看实现的效果

默认主题 quartz主题

PS:目前浅色主题可以比较好的适配,深色主题还在适配中,部分页面的显示效果不佳

思路

bootswatch切换主题的方式是引入其提供的bootstrap.css文件,覆盖Bootstrap默认的样式

我在DjangoStarter中实现的切换主题用的是Django的TemplateTag,它可以在模板渲染的时候根据用户选择的主题,加入对应的css文件引用

理论上使用AspNetCore MVC实现的StarBlog项目也是可以用这种方式实现主题切换的,不过当时开发时遇到一些处理起来比较麻烦的问题,所以我决定改为暴露主题API,通过JS动态加载css的方式实现。

添加依赖

开始代码~

首先添加bootswatch依赖,需要和Bootstrap版本对应,本项目使用的Bootstrap版本是5.1.3,所以这个bootswatch版本也需要同步使用5.1.3

yarn add bootswatch

gulpfile.js中配置自动复制

//使用 npm 下载的前端组件包 const libs = [ {name: "bootswatch", dist: "./node_modules/bootswatch/dist/**/*.*"}, ];

StarBlog.Web目录下执行gulp move命令,gulp会自动把bootswatch相关文件复制到wwwroot/lib目录中,方便接下来的使用

关于使用NPM和Gulp管理静态资源的详情,可以参考前面的这篇文章:Asp-Net-Core开发笔记:使用NPM和gulp管理前端静态文件

编写Service

StarBlog.Web/Services中添加ThemeService.cs

首先是定义Theme模型

public class Theme { public string Name { get; set; } public string Path { get; set; } public string CssUrl { get; set; } }

然后ThemeService,扫描wwwroot/lib/bootswatch中的所有主题,同时把Bootstrap默认主题也加入

public class ThemeService { public const string BootstrapTheme = "Bootstrap"; private const string CssUrlPrefix = "/lib/bootswatch/dist"; public List<Theme> Themes { get; set; } = new() { new Theme {Name = BootstrapTheme, Path = "", CssUrl = ""} }; public ThemeService(IWebHostEnvironment env) { var themePath = Path.Combine(env.WebRootPath, "lib", "bootswatch", "dist"); foreach (var item in Directory.GetDirectories(themePath)) { var name = Path.GetFileName(item); Themes.Add(new Theme { Name = name, Path = item, CssUrl = $"{CssUrlPrefix}/{name}/bootstrap.min.css" }); } } }

然后注册为单例服务就OK了

builder.Services.AddSingleton<ThemeService>(); 写个接口

然后还需要写一个接口

StarBlog.Web/Apis目录下新增个ThemeController.cs,代码很简单,只有一个action,获取全部主题

/// <summary> /// 页面主题 /// </summary> [ApiController] [Route("Api/[controller]")] [ApiExplorerSettings(GroupName = "common")] public class ThemeController : ControllerBase { private readonly ThemeService _themeService; public ThemeController(ThemeService themeService) { _themeService = themeService; } [HttpGet] public List<Theme> GetAll() { return _themeService.Themes; } } 前端实现

主题的后端部分完成了,前端需要完成三部分功能

  • 请求接口,获取全部主题列表
  • 设置主题,动态引入css
  • 保存当前选择的主题,下次打开页面时自动引入

为了方便DOM操作,我使用了Vue,在Views/Shared/_Layout.cshtml底部引入

<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/lib/vue/dist/vue.js"></script> <script src="~/js/site.js"></script>

先在来写最后一个引入的site.js代码,使用vue,网页打开时通过fetch函数加载主题列表,然后显示在页面上

还有切换主题时将当前主题的名称和css链接保存在localStorage中,下次加载页面的时候可以自动引入

let app = new Vue({ el: '#vue-header', data: { currentTheme: '', themes: [] }, created: function () { fetch('/Api/Theme') .then(res => res.json()) .then(res => { this.themes = res.data }) // 读取本地主题配置 let theme = localStorage.getItem('currentTheme') if (theme != null) this.currentTheme = theme }, methods: { setTheme(themeName) { let theme = this.themes.find(t => t.name === themeName) loadStyles(theme.cssUrl) this.currentTheme = themeName localStorage.setItem('currentTheme', themeName) localStorage.setItem('currentThemeCssUrl', theme.cssUrl) // 换主题之后最好要刷新页面,不然可能样式冲突 location.reload() } } })

这里加载了主题,通过vue的双向绑定,把主题渲染在顶部菜单上(同时高亮当前主题)

也就是这个地方

<div class="px-3 py-2 border-bottom mb-3"> <div class="container d-flex flex-wrap justify-content-center"> <form class="col-12 col-lg-auto mb-2 mb-lg-0 me-lg-auto" asp-controller="Search" asp-action="Blog"> <input type="search" class="form-control" placeholder="Search..." aria-label="Search" name="keyword"> </form> <div class="text-end"> <span class="dropdown me-2"> <a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false"> Themes </a> <ul class="dropdown-menu" aria-labelledby="dropdownMenuLink"> <li v-for="theme in themes"> <a v-if="theme.name===currentTheme" class="dropdown-item active">{{theme.name}}</a> <a v-else class="dropdown-item" v-on:click="setTheme(theme.name)">{{theme.name}}</a> </li> </ul> </span> </div> </div> </div>

最后,还需要在页面刷新的时候读取主题配置,然后自动加载当前主题

因为动态切换主题会导致一些样式冲突啥的,所以需要在页面还没加载完成的时候先引入

因此我又写了个site.preload.js,放在页面的<head>部分

<script src="~/js/site.preload.js"></script>

代码如下,先读取当前主题,如果有设置过主题就读取CSS链接并且引入

如何通过.NetCore开发实现StarBlog博客项目的主题切换功能?

(同时前面的site.js中也有使用到这个loadStyles函数)

// 动态加载CSS function loadStyles(url) { let link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = url; let head = document.getElementsByTagName("head")[0]; head.appendChild(link); } let currentTheme = localStorage.getItem('currentTheme') if (currentTheme !== 'Bootstrap') { let themeCssUrl = localStorage.getItem('currentThemeCssUrl') if (themeCssUrl != null) loadStyles(themeCssUrl) }

PS:动态加载CSS的代码来自:lengyun.github.io/js/3-2-2dynamicAddCSS.html

搞定~

微信公众号:「程序设计实验室」 专注于互联网热门新技术探索与团队敏捷开发实践,包括架构设计、机器学习与数据分析算法、移动端开发、Linux、Web前后端开发等,欢迎一起探讨技术,分享学习实践经验。

本文共计1957个文字,预计阅读时间需要8分钟。

如何通过.NetCore开发实现StarBlog博客项目的主题切换功能?

系列文章 + 基于.NetCore开发博客项目 StarBlog + (1) 为什么需要自己写一个博客?系列文章 + 基于.NetCore开发博客项目 StarBlog + (2) 环境准备和创建项目系列文章 + 基于.NetCore开发博客项目 StarBlog + (3) 模型设计 + “

系列文章
  • 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客?
  • 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目
  • 基于.NetCore开发博客项目 StarBlog - (3) 模型设计
  • 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入
  • 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目
  • 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表
  • 基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面
  • 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示
  • 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入
  • 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流
  • 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计
  • 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译
  • 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能
  • 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能
  • ...
前言

近期一直在写代码,给博客新增了一些功能,本来想写一篇文章一起介绍的,不过好像篇幅会太长,想想还是分开写好了~

本文介绍主题切换功能,StarBlog博客页面基于Bootstrap5实现,Bootstrap本身的设计是比较简洁大方的,不过看久了也会腻,自己调css太麻烦,好在Bootstrap世界有个东西叫bootswatch,提供了几十套主题,我只需要npm install然后就可以实现主题切换功能了~

同时所有项目代码已经上传GitHub,欢迎各位大佬Star/Fork!

  • 网站地址:blog.deali.cn
  • 博客后端+前台项目地址:github.com/Deali-Axy/StarBlog
  • 管理后台前端项目地址:github.com/Deali-Axy/StarBlog-Admin
实现效果

照例先看看实现的效果

默认主题 quartz主题

PS:目前浅色主题可以比较好的适配,深色主题还在适配中,部分页面的显示效果不佳

思路

bootswatch切换主题的方式是引入其提供的bootstrap.css文件,覆盖Bootstrap默认的样式

我在DjangoStarter中实现的切换主题用的是Django的TemplateTag,它可以在模板渲染的时候根据用户选择的主题,加入对应的css文件引用

理论上使用AspNetCore MVC实现的StarBlog项目也是可以用这种方式实现主题切换的,不过当时开发时遇到一些处理起来比较麻烦的问题,所以我决定改为暴露主题API,通过JS动态加载css的方式实现。

添加依赖

开始代码~

首先添加bootswatch依赖,需要和Bootstrap版本对应,本项目使用的Bootstrap版本是5.1.3,所以这个bootswatch版本也需要同步使用5.1.3

yarn add bootswatch

gulpfile.js中配置自动复制

//使用 npm 下载的前端组件包 const libs = [ {name: "bootswatch", dist: "./node_modules/bootswatch/dist/**/*.*"}, ];

StarBlog.Web目录下执行gulp move命令,gulp会自动把bootswatch相关文件复制到wwwroot/lib目录中,方便接下来的使用

关于使用NPM和Gulp管理静态资源的详情,可以参考前面的这篇文章:Asp-Net-Core开发笔记:使用NPM和gulp管理前端静态文件

编写Service

StarBlog.Web/Services中添加ThemeService.cs

首先是定义Theme模型

public class Theme { public string Name { get; set; } public string Path { get; set; } public string CssUrl { get; set; } }

然后ThemeService,扫描wwwroot/lib/bootswatch中的所有主题,同时把Bootstrap默认主题也加入

public class ThemeService { public const string BootstrapTheme = "Bootstrap"; private const string CssUrlPrefix = "/lib/bootswatch/dist"; public List<Theme> Themes { get; set; } = new() { new Theme {Name = BootstrapTheme, Path = "", CssUrl = ""} }; public ThemeService(IWebHostEnvironment env) { var themePath = Path.Combine(env.WebRootPath, "lib", "bootswatch", "dist"); foreach (var item in Directory.GetDirectories(themePath)) { var name = Path.GetFileName(item); Themes.Add(new Theme { Name = name, Path = item, CssUrl = $"{CssUrlPrefix}/{name}/bootstrap.min.css" }); } } }

然后注册为单例服务就OK了

builder.Services.AddSingleton<ThemeService>(); 写个接口

然后还需要写一个接口

StarBlog.Web/Apis目录下新增个ThemeController.cs,代码很简单,只有一个action,获取全部主题

/// <summary> /// 页面主题 /// </summary> [ApiController] [Route("Api/[controller]")] [ApiExplorerSettings(GroupName = "common")] public class ThemeController : ControllerBase { private readonly ThemeService _themeService; public ThemeController(ThemeService themeService) { _themeService = themeService; } [HttpGet] public List<Theme> GetAll() { return _themeService.Themes; } } 前端实现

主题的后端部分完成了,前端需要完成三部分功能

  • 请求接口,获取全部主题列表
  • 设置主题,动态引入css
  • 保存当前选择的主题,下次打开页面时自动引入

为了方便DOM操作,我使用了Vue,在Views/Shared/_Layout.cshtml底部引入

<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/lib/vue/dist/vue.js"></script> <script src="~/js/site.js"></script>

先在来写最后一个引入的site.js代码,使用vue,网页打开时通过fetch函数加载主题列表,然后显示在页面上

还有切换主题时将当前主题的名称和css链接保存在localStorage中,下次加载页面的时候可以自动引入

let app = new Vue({ el: '#vue-header', data: { currentTheme: '', themes: [] }, created: function () { fetch('/Api/Theme') .then(res => res.json()) .then(res => { this.themes = res.data }) // 读取本地主题配置 let theme = localStorage.getItem('currentTheme') if (theme != null) this.currentTheme = theme }, methods: { setTheme(themeName) { let theme = this.themes.find(t => t.name === themeName) loadStyles(theme.cssUrl) this.currentTheme = themeName localStorage.setItem('currentTheme', themeName) localStorage.setItem('currentThemeCssUrl', theme.cssUrl) // 换主题之后最好要刷新页面,不然可能样式冲突 location.reload() } } })

这里加载了主题,通过vue的双向绑定,把主题渲染在顶部菜单上(同时高亮当前主题)

也就是这个地方

<div class="px-3 py-2 border-bottom mb-3"> <div class="container d-flex flex-wrap justify-content-center"> <form class="col-12 col-lg-auto mb-2 mb-lg-0 me-lg-auto" asp-controller="Search" asp-action="Blog"> <input type="search" class="form-control" placeholder="Search..." aria-label="Search" name="keyword"> </form> <div class="text-end"> <span class="dropdown me-2"> <a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false"> Themes </a> <ul class="dropdown-menu" aria-labelledby="dropdownMenuLink"> <li v-for="theme in themes"> <a v-if="theme.name===currentTheme" class="dropdown-item active">{{theme.name}}</a> <a v-else class="dropdown-item" v-on:click="setTheme(theme.name)">{{theme.name}}</a> </li> </ul> </span> </div> </div> </div>

最后,还需要在页面刷新的时候读取主题配置,然后自动加载当前主题

因为动态切换主题会导致一些样式冲突啥的,所以需要在页面还没加载完成的时候先引入

因此我又写了个site.preload.js,放在页面的<head>部分

<script src="~/js/site.preload.js"></script>

代码如下,先读取当前主题,如果有设置过主题就读取CSS链接并且引入

如何通过.NetCore开发实现StarBlog博客项目的主题切换功能?

(同时前面的site.js中也有使用到这个loadStyles函数)

// 动态加载CSS function loadStyles(url) { let link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = url; let head = document.getElementsByTagName("head")[0]; head.appendChild(link); } let currentTheme = localStorage.getItem('currentTheme') if (currentTheme !== 'Bootstrap') { let themeCssUrl = localStorage.getItem('currentThemeCssUrl') if (themeCssUrl != null) loadStyles(themeCssUrl) }

PS:动态加载CSS的代码来自:lengyun.github.io/js/3-2-2dynamicAddCSS.html

搞定~

微信公众号:「程序设计实验室」 专注于互联网热门新技术探索与团队敏捷开发实践,包括架构设计、机器学习与数据分析算法、移动端开发、Linux、Web前后端开发等,欢迎一起探讨技术,分享学习实践经验。