6

基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能 - 程序设计实验室

 1 year ago
source link: https://www.cnblogs.com/deali/p/16441294.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

基于.NetCore开发博客项目 StarBlog

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

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

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

照例先看看实现的效果

默认主题 quartz主题
866942-20220703231515754-63013766.png
866942-20220703231524832-315816700.png

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的双向绑定,把主题渲染在顶部菜单上(同时高亮当前主题)

也就是这个地方

866942-20220703231549950-1308309569.png
<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

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK