天天看點

基于.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

然後就可以實作主題切換功能了~

同時所有項目代碼已經上傳GitHub,歡迎各位大佬Star/Fork!

  • 網站位址:http://blog.deali.cn
  • 部落格後端+前台項目位址:https://github.com/Deali-Axy/StarBlog
  • 管理背景前端項目位址:https://github.com/Deali-Axy/StarBlog-Admin

實作效果

照例先看看實作的效果

預設主題 quartz主題
基于.NetCore開發部落格項目 StarBlog - (14) 實作主題切換功能
基于.NetCore開發部落格項目 StarBlog - (14) 實作主題切換功能
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的雙向綁定,把主題渲染在頂部菜單上(同時高亮目前主題)

也就是這個地方

基于.NetCore開發部落格項目 StarBlog - (14) 實作主題切換功能
<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

搞定~

微信公衆号:「程式設計實驗室」

專注于網際網路熱門新技術探索與團隊靈活開發實踐,包括架構設計、機器學習與資料分析算法、移動端開發、Linux、Web前後端開發等,歡迎一起探讨技術,分享學習實踐經驗。

繼續閱讀