系列文章
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 使用 abp cli 搭建專案
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 給專案瘦身,讓它跑起來
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 完善與美化,Swagger登場
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 資料訪問和程式碼優先
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 自定義倉儲之增刪改查
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 統一規範API,包裝返回模型
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 再說Swagger,分組、描述、小綠鎖
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 接入GitHub,用JWT保護你的API
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 異常處理和日誌記錄
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 使用Redis快取資料
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 整合Hangfire實現定時任務處理
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 用AutoMapper搞定物件對映
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(一)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(二)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(三)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(一)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(二)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(三)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(四)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(五)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - Blazor 實戰系列(一)
上一篇搭建了 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 = true
,QrCodeCssClass = "hidden"
,當IsHidden = false
,QrCodeCssClass = 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> · Light</NavLink>
</div>
<div class="menu-toggle" @onclick="ToggleNavMenu">☰ 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"> · @currentTheme</NavLink>
</div>
<div class="menu-toggle" @onclick="ToggleNavMenu">☰ 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"> · @currentTheme</NavLink>
</div>
<div class="menu-toggle" @onclick="ToggleNavMenu">☰ 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");
}
}
實現過程比較簡單,相信你絕對學會了。本篇就到這裡了,未完待續...