基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
系列文章
- 基于 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");
}
}
实现过程比较简单,相信你绝对学会了。本篇就到这里了,未完待续...
开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial
基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)的更多相关文章
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- css实现双色饼图
from:wx--前端早读课 首先回想用css画三角形的方法: <div class="triangle"></div> .triangle { displ ...
- AJAX一
一.http协议 1.URL http://www.baidu.com/md/index.html 结构:协议+主机名称+目录结构+文件名称 URL完整的结构 <scheme>://< ...
- scrapy中间件之下载中间件使用(网易新闻爬取)
scrapy项目中的middlewarse.py中间件 爬虫中间件:目前先不介绍 下载中间件(需要在settings.py中开启) (1)请求处理函数:process_request(self, re ...
- .NetCore对接各大财务软件凭证API——用友系列(1)
一.前言 今天,我们转战用友系列的第一个产品---T+/Tplus.前两篇文章讲解分享的都是金蝶的产品,因为本身公司牵涉的业务有限,后续有金蝶其他产品的API对接业务时,会继续来分享经验. T+的AP ...
- table动态添加的tr 其click事件在IE兼容模式中不执行 jquery 1.9 的live事件 和获取 first last
http://www.css88.com/jqapi-1.9/first-of-type/index.html#p=//www.css88.com/jqapi-1.9/last-child-selec ...
- Django中的事务与ajax
一 事务与锁 1.行级锁 行级锁是由存储引擎实现的.如mysql里默认指定的InnoDB存储引擎,由它实现行级锁.InnoDB的行级锁定同样分为两种类型,共享锁(X)和排他锁(S). 对于UPDATE ...
- Spring 中基于 AOP 的 XML架构
Spring 中基于 AOP 的 XML架构 为了使用 aop 命名空间标签,你需要导入 spring-aop j架构,如下所述: <?xml version="1.0" e ...
- PAT-1064 Complete Binary Search Tree(完全二叉树)
A Binary Search Tree (BST) is recursively defined as a binary tree which has the following propertie ...
- 树状数组区间更新区间查询以及gcd的logn性质
题目描述 给你一个长为n的序列a m次查询 每次查询一个区间的所有子区间的gcd的和mod1e9+7的结果 输入描述: 第一行两个数n,m之后一行n个数表示a之后m行每行两个数l,r表示查询的区间 输 ...
- python 格式化输出(% VS format)
提到Python中的格式化输出方法,一般来说有以下两种方式: 1)% 格式说明由%和格式字符组成,如%f,%s,%d,它的作用是将数据按照指定的格式输出.格式说明是由“%”字符开始的. #1.输出字符 ...