基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能
系列文章
- 基于.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然后就可以实现主题切换功能了~
实现效果
照例先看看实现的效果
| 默认主题 | 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链接并且引入
(同时前面的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的代码来自:http://lengyun.github.io/js/3-2-2dynamicAddCSS.html
搞定~
基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能的更多相关文章
- 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (16) 一些新功能 (监控/统计/配置/初始化)
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (18) 实现本地Typora文章打包上传
前言 九月太忙,只更新了三篇文章,本来这个功能是从九月初就开始做的,结果一直拖到现在国庆假期才有时间完善并且写文章~ 之前我更新了几篇关于 Python 的文章,有朋友留言问是不是不更新 .Net 了 ...
- 基于.NetCore开发博客项目 StarBlog - (19) Markdown渲染方案探索
前言 笔者认为,一个博客网站,最核心的是阅读体验. 在开发StarBlog的过程中,最耗时的恰恰也是文章的展示部分功能. 最开始还没研究出来如何很好的使用后端渲染,所以只能先用Editor.md组件做 ...
- 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 ... 基于. ...
- 基于.NetCore开发博客项目 StarBlog - (3) 模型设计
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
随机推荐
- 元素滚动 scroll 系列
定义 : scroll翻译过来就是滚动的,我们使用scroll系列的相关属性可以动态的得到该元素的大小.滚动距离等. 常用属性 : 需要用到页面滚动事件scroll因为是页面滚动,所以事件源是docu ...
- EmlParse:一款超轻量级的批量解析EML格式电子邮件的工具
工具特点 1.绿色纯天然,无任何依赖库,文件大小不到150K: 2.可批量解析EML格式的电子邮件: 3.可提取EML文件中的正文和附件到指定目录: 4.可生成HTML格式的邮件列表清单,方便用户进行 ...
- 云平台短信验证码通知短信java/php/.net开发实现
一.本文目的 大部分平台都有一个接入发送短信验证码.通知短信的需求.虽然市场上大部分平台的接口都只是一个非常普通的HTTP-GET请求,但终归有需要学习和借鉴使用的朋友. 本文的初衷是主要提供学习便利 ...
- Day 005:PAT练习--1047. 编程团体赛(20)
编程团体赛的规则为:每个参赛队由若干队员组成:所有队员独立比赛:参赛队的成绩为所有队员的成绩和:成绩最高的队获胜.现给定所有队员的比赛成绩,请你编写程序找出冠军队. 输入格式: 输入第一行给出一个正整 ...
- ABP框架入门
技术要求 在开始使用 ABP 框架之前,您需要在计算机上安装一些工具. IDE/编辑器 本书假设您使用的是Visual Studio 2022(支持 .NET 6.0 的 v10.0)或更高版本.如果 ...
- 前后端分离,简单JWT登录详解
前后端分离,简单JWT登录详解 目录 前后端分离,简单JWT登录详解 JWT登录流程 1. 用户认证处理 2. 前端登录 3. 前端请求处理 4. 后端请求处理 5. 前端页面跳转处理 6. 退出登录 ...
- [AcWing 68] 0到n-1中缺失的数字
点击查看代码 class Solution { public: int getMissingNumber(vector<int>& nums) { if (nums.empty() ...
- Oracle查看表空间大小
遇到报错 java.sql.SQLException: ORA-01653: 表 MESHIS.HIS_RET_LOT_FQC 无法通过 8 (在表空间 MESHIS_DATA_TBS 中) 扩展 a ...
- 为 ASP.NET Core (6.0)服务应用添加ApiKey验证支持
这个代码段演示了如何为一个ASP.NET Core项目中添加Apikey验证支持. 首先,通过下面的代码创建项目 dotnet new webapi -minimal -o yourwebapi 然后 ...
- FinClip小程序+Rust(三):一个加密钱包
一个加密货币钱包,主要依赖加密算法构建.这部分逻辑无关iOS还是Android,特别适合用Rust去实现.我们看看如何实现一个生成一个模拟钱包,准备供小程序开发采用 前言 在之前的内容我们介绍了整 ...

