基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)

2{icon} {views}

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)
  20. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)
  21. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(一)
  22. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)
  23. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(三)
  24. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(四)
  25. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(五)
  26. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)
  27. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(七)
  28. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(八)
  29. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)
  30. 基於 abp vNext 和 .NET Core 開發博客項目 – 終結篇之發布項目

上一篇搭建了 Blazor 項目並將整體框架改造了一下,本篇將完成用 C# 代碼代替 JavaScript 實現幾個小功能,說是代替但並不能完全不用 JavaScript,應該說是盡量不用吧。

二維碼显示與隱藏

可以看到,當我鼠標移入的時候显示二維碼,移出的時候隱藏二維碼。

這個功能如果是用JavaScript來完成的話,肯定首先想到的是HTML的 Mouse 事件屬性,那麼在Blazor中也是一樣的,給我們實現了各種on*事件。

打開index.razor頁面,給微信圖標那個 NavLink 標籤添加兩個事件,@onmouseover@onmouseout

...
<NavLink class="link-item weixin" title="掃碼關注微信公眾號:『阿星Plus』查看更多。" @onmouseover="Hover" @onmouseout="Hover">
    <i class="iconfont iconweixin"></i>
</NavLink>
...

當鼠標移入移出的時候都執行我們自定義的一個方法Hover()

C# 代碼寫在@code{}花括號中,實現显示和隱藏原理是利用css,默認是隱藏的,當显示的時候將具有隱藏屬性的class值去掉就可以了。

所以,可以添加兩個字段,一個用於判斷當前是否處於隱藏狀態,一個用來存儲class的值。

/// <summary>
/// 是否隱藏
/// </summary>
private bool IsHidden = true;

/// <summary>
/// 二維碼CSS
/// </summary>
private string QrCodeCssClass => IsHidden ? "hidden" : null;

IsHidden = trueQrCodeCssClass = "hidden",當IsHidden = falseQrCodeCssClass = null

那麼在Hover()方法中,不斷修改IsHidden的值就可以實現效果了。

/// <summary>
/// 鼠標移入移出操作
/// </summary>
private void Hover() => IsHidden = !IsHidden;

最後將QrCodeCssClass變量賦值給二維碼圖片所在的div上。

...
<div class="qrcode @QrCodeCssClass">
    <img src="https://static.meowv.com/images/wx_qrcode.jpg" />
</div>
...

大功告成,index.razor完整代碼如下:

@page "/"

<div class="main">
    <div class="container">
        <div class="intro">
            <div class="avatar">
                <a href="javascript:;"><img src="https://static.meowv.com/images/avatar.jpg"></a>
            </div>
            <div class="nickname">阿星Plus</div>
            <div class="description">
                <p>
                    生命不息,奮鬥不止
                    <br>Cease to struggle and you cease to live
                </p>
            </div>
            <div class="links">
                <NavLink class="link-item" title="Posts" href="posts">
                    <i class="iconfont iconread"></i>
                </NavLink>
                <NavLink target="_blank" class="link-item" title="Notes" href="https://notes.meowv.com/">
                    <i class="iconfont iconnotes"></i>
                </NavLink>
                <NavLink target="_blank" class="link-item" title="API" href="https://api.meowv.com/">
                    <i class="iconfont iconapi"></i>
                </NavLink>
                <NavLink class="link-item" title="Manage" href="/account/auth">
                    <i class="iconfont iconcode"></i>
                </NavLink>
                <NavLink target="_blank" class="link-item" title="Github" href="https://github.com/Meowv/">
                    <i class="iconfont icongithub"></i>
                </NavLink>
                <NavLink class="link-item weixin" title="掃碼關注微信公眾號:『阿星Plus』查看更多。" @onmouseover="Hover" @onmouseout="Hover">
                    <i class="iconfont iconweixin"></i>
                </NavLink>
                <div class="qrcode @QrCodeCssClass">
                    <img src="https://static.meowv.com/images/wx_qrcode.jpg" />
                </div>
            </div>
        </div>
    </div>
</div>

@code {
    /// <summary>
    /// 是否隱藏
    /// </summary>
    private bool IsHidden = true;

    /// <summary>
    /// 二維碼CSS
    /// </summary>
    private string QrCodeCssClass => IsHidden ? "hidden" : null;

    /// <summary>
    /// 鼠標移入移出操作
    /// </summary>
    private void Hover() => IsHidden = !IsHidden;
}

菜單显示與隱藏

菜單是在小屏幕上才會出現的,相信看完了二維碼的显示與隱藏,這個菜單的显示與隱藏就好辦了吧,實現方法是一樣的,菜單按鈕是在頭部組件Header.razor中的,包括主題切換功能,所以下面代碼都在Header.razor裏面。

@code {
    /// <summary>
    /// 下拉菜單是否打開
    /// </summary>
    private bool collapseNavMenu = false;

    /// <summary>
    /// 導航菜單CSS
    /// </summary>
    private string NavMenuCssClass => collapseNavMenu ? "active" : null;

    /// <summary>
    /// 显示/隱藏 菜單
    /// </summary>
    private void ToggleNavMenu() => collapseNavMenu = !collapseNavMenu;
}

默認是不打開的,collapseNavMenu = false。然後根據collapseNavMenu值為NavMenuCssClass給定不同的class。

...
<nav class="navbar-mobile">
    <div class="container">
        <div class="navbar-header">
            <div>
                <NavLink class="menu-item" href="" Match="NavLinkMatch.All">阿星Plus</NavLink>
                <NavLink>&nbsp;·&nbsp;Light</NavLink>
            </div>
            <div class="menu-toggle" @onclick="ToggleNavMenu">&#9776; Menu</div>
        </div>
        <div class="menu @NavMenuCssClass">
            <NavLink class="menu-item" href="posts">Posts</NavLink>
            <NavLink class="menu-item" href="categories">Categories</NavLink>
            <NavLink class="menu-item" href="tags">Tags</NavLink>
            <NavLink class="menu-item apps" href="apps">Apps</NavLink>
        </div>
    </div>
</nav>
...

與二維碼显示與隱藏唯一區別就是這裡是點擊按鈕,不是移入移出,所以菜單显示與隱藏需要用到@onclick方法。

主題切換

哇,這個主題切換真的是一言難盡,當切換主題的時候需要記住當前的主題是什麼,當刷新頁面或者跳轉其他頁面的時候,主題狀態是需要一致的,默認是白色主題,當切換暗黑色主題后其實是在body上加了一個class。

在Blazor實在是不知道用什麼辦法去動態控制body的樣式,所以這裏我想到了一個辦法,寫幾個全局的JavaScript方法,然後再Blazor中調用,要知道,他們是可以互相調用的,於是問題迎刃而解。

添加app.js文件,放在 /wwwroot/js/ 下面。

var func = window.func || {};

func = {
    setStorage: function (name, value) {
        localStorage.setItem(name, value);
    },
    getStorage: function (name) {
        return localStorage.getItem(name);
    },
    switchTheme: function () {
        var currentTheme = this.getStorage('theme') || 'Light';
        var isDark = currentTheme === 'Dark';

        if (isDark) {
            document.querySelector('body').classList.add('dark-theme');
        } else {
            document.querySelector('body').classList.remove('dark-theme');
        }
    }
};

這裏寫了三個方法,設置localStorage:setStorage(name,value),獲取localStorage:getStorage(name),切換主題:switchTheme(),localStorage 是瀏覽器以 name:value 形式的本地存儲對象。

switchTheme主要做的事情就是,判斷當前主題如果是暗黑,就給body加上對應的class,如果不是就去掉。

然後在 index.html 中引用。

...
<body>
    <app>
        <div class="loader"></div>
    </app>
    <script src="js/app.js"></script>
    <script src="_framework/blazor.webassembly.js"></script>
</body>
...

有了這個三個全局的JavaScript方法,切換主題就變得簡單多了,看代碼。

...
/// <summary>
/// 當前主題
/// </summary>
private string currentTheme;

/// <summary>
/// 初始化
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
    currentTheme = await JSRuntime.InvokeAsync<string>("window.func.getStorage", "theme") ?? "Light";

    await JSRuntime.InvokeVoidAsync("window.func.switchTheme");
}
...

注意在Blazor調用JavaScript方法需要注入IJSRuntime接口,@inject IJSRuntime JSRuntime

新建一個變量currentTheme,在生命周期函數初始化的時候去調用JavaScript中的getStorage方法,獲取當前主題,考慮到第一次訪問的情況,可以給一個默認值為Light,表示白色主題,然後再去調用switchTheme,執行切換主題的方法。這樣頁面就會根據localStorage的值來確定當前的主題。

...
/// <summary>
/// 切換主題
/// </summary>
private async Task SwitchTheme()
{
    currentTheme = currentTheme == "Light" ? "Dark" : "Light";

    await JSRuntime.InvokeVoidAsync("window.func.setStorage", "theme", currentTheme);

    await JSRuntime.InvokeVoidAsync("window.func.switchTheme");
}
...

SwitchTheme()是切換主題的方法,當我們點擊input按鈕時可以任意切換,並且主題還要實時跟着變化。

當點擊按鈕執行SwitchTheme()時候改變currentTheme的值,然後將currentTheme傳遞給JavaScript方法setStorage,最後再次執行切換主題的JavaScript方法即可。

此時變量currentTheme也發揮了不少作用,在小屏幕下會显示當前主題的名稱,Dark or Light,可以直接將currentTheme在HTML中賦值即可。

並且我們input是checkbox類型,當是黑色主題的時候需要時選中的狀態,白色主題的時候不選中,這裏就可以利用checked屬性這樣寫:checked="@(currentTheme == "Dark")"

<nav class="navbar">
    <div class="container">
       ...
        <div class="menu navbar-right">
           ...
            <input id="switch_default" type="checkbox" class="switch_default" @onchange="SwitchTheme" checked="@(currentTheme == "Dark")" />
            <label for="switch_default" class="toggleBtn"></label>
        </div>
    </div>
</nav>
<nav class="navbar">
    <div class="container">
       ...
        <div class="menu navbar-right">
           ...
            <input id="switch_default" type="checkbox" class="switch_default" @onchange="SwitchTheme" checked="@(currentTheme == "Dark")" />
            <label for="switch_default" class="toggleBtn"></label>
        </div>
    </div>
</nav>
<nav class="navbar-mobile">
    <div class="container">
        <div class="navbar-header">
            <div>
                <NavLink class="menu-item" href="" Match="NavLinkMatch.All">阿星Plus</NavLink>
                <NavLink @onclick="SwitchTheme">&nbsp;·&nbsp;@currentTheme</NavLink>
            </div>
            <div class="menu-toggle" @onclick="ToggleNavMenu">&#9776; Menu</div>
        </div>
        <div class="menu @NavMenuCssClass">
            ...
        </div>
    </div>
</nav>

OK,搞定,快去試試吧。

優化代碼

現在看起來亂亂的,並且設置獲取localStorage屬於公共的方法,說不定以後也能用到,我們將其封裝一下,便於日後的調用,不然要寫好多重複的代碼。

在Blazor項目根目錄添加文件夾Commons,在文件夾下添加一個Common.cs,目前用到了IJSRuntime,用構造函數注入,然後寫幾個公共的方法。

//Common.cs
using Microsoft.JSInterop;
using System.Threading.Tasks;

namespace Meowv.Blog.BlazorApp.Commons
{
    public class Common
    {
        private readonly IJSRuntime _jsRuntime;

        public Common(IJSRuntime jsRuntime)
        {
            _jsRuntime = jsRuntime;
        }

        /// <summary>
        /// 執行無返回值方法
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public async ValueTask InvokeAsync(string identifier, params object[] args)
        {
            await _jsRuntime.InvokeVoidAsync(identifier, args);
        }

        /// <summary>
        /// 執行帶返回值的方法
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="identifier"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public async ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object[] args)
        {
            return await _jsRuntime.InvokeAsync<TValue>(identifier, args);
        }

        /// <summary>
        /// 設置localStorage
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task SetStorageAsync(string name, string value)
        {
            await InvokeAsync("window.func.setStorage", name, value);
        }

        /// <summary>
        /// 獲取localStorage
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public async Task<string> GetStorageAsync(string name)
        {
            return await InvokeAsync<string>("window.func.getStorage", name);
        }
    }
}

然後需要在Program.cs中注入。

using Meowv.Blog.BlazorApp.Commons;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Meowv.Blog.BlazorApp
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

            builder.Services.AddSingleton(typeof(Common));

            await builder.Build().RunAsync();
        }
    }
}

緊接着在_Imports.razor中注入使用Common@inject Commons.Common Common

改造一下Header.razor,全部代碼如下:

<header>
    <nav class="navbar">
        <div class="container">
            <div class="navbar-header header-logo">
                <NavLink class="menu-item" href="/" Match="NavLinkMatch.All">
                    阿星Plus
                </NavLink>
            </div>
            <div class="menu navbar-right">
                <NavLink class="menu-item" href="posts">Posts</NavLink>
                <NavLink class="menu-item" href="categories">Categories</NavLink>
                <NavLink class="menu-item" href="tags">Tags</NavLink>
                <NavLink class="menu-item apps" href="apps">Apps</NavLink>
                <input id="switch_default" type="checkbox" class="switch_default" @onchange="SwitchTheme" checked="@(currentTheme == "Dark")" />
                <label for="switch_default" class="toggleBtn"></label>
            </div>
        </div>
    </nav>
    <nav class="navbar-mobile">
        <div class="container">
            <div class="navbar-header">
                <div>
                    <NavLink class="menu-item" href="" Match="NavLinkMatch.All">阿星Plus</NavLink>
                    <NavLink @onclick="SwitchTheme">&nbsp;·&nbsp;@currentTheme</NavLink>
                </div>
                <div class="menu-toggle" @onclick="ToggleNavMenu">&#9776; Menu</div>
            </div>
            <div class="menu @NavMenuCssClass">
                <NavLink class="menu-item" href="posts">Posts</NavLink>
                <NavLink class="menu-item" href="categories">Categories</NavLink>
                <NavLink class="menu-item" href="tags">Tags</NavLink>
                <NavLink class="menu-item apps" href="apps">Apps</NavLink>
            </div>
        </div>
    </nav>
</header>

@code {
    /// <summary>
    /// 下拉菜單是否打開
    /// </summary>
    private bool collapseNavMenu = false;

    /// <summary>
    /// 導航菜單CSS
    /// </summary>
    private string NavMenuCssClass => collapseNavMenu ? "active" : null;

    /// <summary>
    /// 显示/隱藏 菜單
    /// </summary>
    private void ToggleNavMenu() => collapseNavMenu = !collapseNavMenu;

    /// <summary>
    /// 當前主題
    /// </summary>
    private string currentTheme;

    /// <summary>
    /// 初始化
    /// </summary>
    /// <returns></returns>
    protected override async Task OnInitializedAsync()
    {
        currentTheme = await Common.GetStorageAsync("theme") ?? "Light";

        await Common.InvokeAsync("window.func.switchTheme");
    }

    /// <summary>
    /// 切換主題
    /// </summary>
    private async Task SwitchTheme()
    {
        currentTheme = currentTheme == "Light" ? "Dark" : "Light";

        await Common.SetStorageAsync("theme", currentTheme);

        await Common.InvokeAsync("window.func.switchTheme");
    }
}

實現過程比較簡單,相信你絕對學會了。本篇就到這裏了,未完待續…

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準