作者介紹
陳超超
Ant Design Blazor 專案貢獻者 擁有十多年從業經驗,長期基於.Net技術棧進行架構與開發產品的工作,Ant Design Blazor 專案貢獻者,現就職於正泰集團
寫專欄開頭老規矩了,所以……先來段廣告 《進擊吧!Blazor!》是本人與張善友老師合作的Blazor零基礎入門系列影片,此係列能讓一個從未接觸過Blazor的程式設計師掌握開發Blazor應用的能力。
影片地址:https://space.bilibili.com/483888821/channel/detail?cid=151273
演示程式碼:https://github.com/TimChen44/Blazor-ToDo
本系列文章是基於《進擊吧!Blazor!》直播內容編寫,升級.Net5,改進問題,講解更全面。
更多學習資料:https://aka.ms/LearnBlazor
從這次分享開始我透過製作一個ToDo應用來介紹Balzor的開發。
準備工作
專案準備
- 開啟上一次分享內容建立專案
- 2.修改
wwwrootcssapp.css
檔案,只保留以下程式碼用於配置程式發生未捕獲異常時的提示樣式
#blazor-error-ui { background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
- 修改index.htm檔案,移除對‘bootstrap’樣式的引用,因為我們使用ant-design-blazor來做UI
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet"/><!--此行程式碼刪除-->
引入ant-design-blazor包
✨ 特性
提煉自企業級中後臺產品的互動語言和視覺風格。
開箱即用的高質量 Razor 元件,可在多種託管方式共享。
支援基於 WebAssembly 的客戶端和基於 SignalR 的服務端 UI 事件互動。
支援漸進式 Web 應用(PWA)
使用 C# 構建,多正規化靜態語言帶來高效的開發體驗。
⚙️ 基於 .NET Standard 2.1/.NET 5,可直接引用豐富的 .NET 類庫。 可與已有的 ASP.NET Core MVC、Razor Pages 專案無縫整合。
專案地址:https://github.com/ant-design-blazor/ant-design-blazor
文件地址:https://antblazor.com/
安裝
- 用NuGet安裝AntDesign包
Install-Package AntDesign -Version 0.5.3
- 在
Program.cs
中註冊:
public static async Task Main(string[] args)
{
//其他程式碼 builder.Services.AddAntDesign();
await builder.Build().RunAsync();
}
- 在
wwwroot/index.html
中引入靜態檔案:
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet">
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
- 在
_Imports.razor
中加入名稱空間
@using AntDesign
- 為了動態地顯示彈出元件,需要在
App.razor
末尾新增一個<AntContainer />
元件。
<AntContainer /> <!--新增在這裡-->
路由
在頁面中切換,必定使用路由,我們先了解一下blazor
的路由機制 App.razor
檔案
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
在上面第一行把當前專案的程式集賦值給了 Router
元件的 AppAssembly
屬性,這樣程式在啟動時檢索程式集中所有的頁面用於路由,路由資訊透過頁面檔案頂部的 @page
標記進行定義。還可以透過 AdditionalAssemblies
屬性支援多個程式集。 Route裡面有兩個模板屬性,分別是路由命中和未命中顯示的內容、RouteView
元件用於顯示路由的頁面,這裡從 Router
接收 routeData
以及任何所需的引數。 DefaultLayout="@typeof(MainLayout)"
定義了預設佈局。
佈局檔案及選單
編輯 Shared/MainLayout.razor
檔案,製作程式的佈局以及選單。
@inherits LayoutComponentBase
<Layout>
<Sider Style="overflow: auto;height: 100vh;position: fixed;left: 0;">
<div class="logo">
進擊吧!Blazor!
</div>
<Menu Theme="MenuTheme.Dark">
<MenuItem RouterLink="/">
主頁
</MenuItem>
<MenuItem RouterLink="/today" RouterMatch="NavLinkMatch.Prefix">
我的一天
</MenuItem>
<MenuItem RouterLink="/search" RouterMatch="NavLinkMatch.Prefix">
全部
</MenuItem>
</Menu>
</Sider>
<Layout Class="site-layout">
@Body
</Layout>
</Layout>
<style>
<!--為了減少文件程式碼量,此處省略樣式程式碼,大家可以直接從本專案原始碼檢視,後面的示例程式碼採用相同模式,將不再贅述-->
</style>
Layout
頁面佈局元件
Layout元件幫助文件:https://antblazor.com/zh-CN/components/layout
Menu 選單元件 Theme="MenuTheme.Dark"黑色主題
Menu元件幫助文件:https://antblazor.com/zh-CN/components/menu
MenuItem
選單項元件 RouterLink="/"
路由地址 RouterMatch="NavLinkMatch.Prefix"
路由匹配模式,透過匹配 URL 來切換 active CSS 類,這有助於在導航選單中顯示那個頁面是活動頁。 NavLinkMatch.All
:NavLink 在與當前整個 URL 匹配的情況下處於活動狀態。 NavLinkMatch.Prefix(預設)
:NavLink 在與當前 URL 的任何字首匹配的情況下處於活動狀態。
@Body
透過這個固定語法在佈局中標記指定呈現內容的位置。
主頁
編輯 Pages/Index.razor
檔案
@page "/"
<Result Icon="smile-outline" Title="@("進擊吧!Blazor!")"></Result>"
這個主頁左邊是選單,右邊是內容,符合上一節佈局格式,因為主頁路由地址是/,所以預設就開啟了。
@page "/"
頁面路由地址
Result
結果元件,用於反饋一系列操作任務的處理結果,主頁雖然是不反饋結果,不過當成ToDo應用門面效果還不錯
Result元件幫助文件:https://antblazor.com/zh-CN/components/result
我的一天
一個用於顯示和維護當天待辦事項的介面 建立Pages/ToDay.razor
檔案
@page "/today"
<PageHeader Title="@("我的一天")" Subtitle="@DateTime.Now.ToString("yyyy年MM月dd日")"></PageHeader>
啟動後點選左邊的“我的一天”選單就可以導航到剛剛建立的頁面,目前就只有一個頁頭。
@page "/today"
設定當前頁路由地址為/today
PageHeader
頁頭資訊
PageHeader元件幫助文件:https://antblazor.com/zh-CN/components/pageheader
待辦列表
ToDo的靈魂那就是待辦列表了,那麼三步走:先上程式碼,再看效果,最後講解
@inject TaskServices TaskSvr
@foreach (var item in taskDtos)
{
<Card Bordered="true" Size="small" Class="task-card">
<div class="task-card-item">
<div class="title">
<Text Strong> @item.Title</Text>
<br />
<Text Type="@TextElementType.Secondary">@item.Description</Text>
</div>
</div>
</Card>
}
@code{
private List<TaskDto> taskDtos = new List<TaskDto>();
protected async override Task OnInitializedAsync()
{
taskDtos = await TaskSvr.LoadToDay();
await base.OnInitializedAsync();
}
}
效果圖
透過OnInitializedAsync
方法中使用TaskSvr.LoadToDay()
載入待辦資料後存入taskDtos
變數,最後透過@foreach
遍歷taskDtos
集合,以Card
元件作為容器,使用@item.Title
和@item.Description
將資料單項繫結到介面顯示。
@foreach (var item in taskDtos) { }
這個和C#
中的foreach
功能相同 @
標記可以把變數值單向繫結到頁面中 @code{}
在razor
語法中用於標記{}
中可以插入c#
程式碼
@inject TaskServices TaskSvr
透過依賴注入TaskServices
服務
關於依賴注入會在下一章節專題介紹,此處就不展開了
Card
卡片容器 Bordered="true"
顯示卡片邊框 Size="small"
小尺寸卡片
Card元件幫助文件:https://antblazor.com/zh-CN/components/card
標記重要
有些待辦肯定比其他待辦更重要,所以增加一個標記重要的按鈕,老規矩:先上程式碼,再看效果,最後講解
<Text Type="@TextElementType.Secondary">@item.Description</Text>
</div>
<!--從這裡開始插入以下程式碼-->
<div class="star" @onclick="x => OnStar(item)">
<Icon Type="star" Theme="@(item.IsImportant ? "fill" : "outline")" />
</div>
private void OnStar(TaskDto task)
{
task.IsImportant = !task.IsImportant;
}
用div
包裹一個Icon
元件,然後在div
上註冊@onclick
點選事件,當點選後會觸發private void OnStar(TaskDto task)
方法,並將當前專案item
作為引數傳入,方法中修改了TaskDto
的IsImportant
屬性值,透過@(item.IsImportant ? "fill" : "outline")
單向繫結,實現修改Icon
元件的Theme
樣式在fill
和outline
切換。
@()
相比@
標記,它可以在()
括號中使用單行程式碼進行單向繫結。 @onclick
事件繫結,除了onclick
還有很多,詳見ASP.NET Core Blazor事件處理
Icon
語義化的向量圖形。 Type="star"
圖示名稱
Icon元件幫助文件:https://antblazor.com/zh-CN/components/icon
計劃時間
既然是待辦,那麼必然有一個計劃開始時間PlanTime
,以及一個截至時間Deadline
,所以老規矩,三步走:先上程式碼,再看效果,最後講解
<Text Type="@TextElementType.Secondary">@item.Description</Text>
</div>
<!--從這裡開始插入以下程式碼-->
<div class="date">
@item.PlanTime.ToShortDateString()
<br />
@{
int? days = (int?)item.Deadline?.Subtract(DateTime.Now.Date).TotalDays;
}
<span style="color:@(days switch { _ when days > 3 => "#ccc", _ when days > 0 => "#ffd800", _ => "#ff0000" })">
@item.Deadline?.ToShortDateString()
</span>
</div>
上面顯示計劃日期PlanTime
,下面顯示Deadline
,並透過與當前時間對比,根據時間差決定顯示方式。
days switch { _ when days > 3 => "#ccc", _ when days > 0 => "#ffd800", _ => "#ff0000" }
這是switch
表示式寫法,可以簡化程式碼,如果使用if
程式碼將比較臃腫,程式碼如下
@if (days > 3)
{
<span style="color:#ccc">
@item.Deadline?.ToShortDateString()
</span>
}
else if (days > 0)
{
<span style="color:#ff6a00">
@item.Deadline?.ToShortDateString()
</span>
}
else
{
<span style="color:#ff0000">
@item.Deadline?.ToShortDateString()
</span>
}
待辦詳情
列表只適合檢視待辦概要,需要檢視詳情還需獨立頁面,所以我們做一個抽屜詳情頁,那麼我們三步走 編輯ToDay.razor
檔案
<div class="title" @onclick="x=>OnCardClick(item)">
<Text Strong> @item.Title</Text>
<br />
<Text Type="@TextElementType.Secondary">@item.Description</Text>
</div>
1
2
3
4
5
[Inject] public DrawerService DrawerSrv { get; set; }
async void OnCardClick(TaskDto task)
{
var options = new DrawerOptions()
{
Title = task.Title,
Width = 450,
};
await DrawerSrv.CreateDialogAsync<TaskInfo, TaskDto, TaskDto>(options, task);
await InvokeAsync(StateHasChanged);
}
新建TaskInfo.razor
檔案
@inherits DrawerTemplate<TaskDto, TaskDto>
<Form Model="this.Options" LabelCol="new ColLayoutParam() {Span = 8 }">
<FormItem Label="標題">
<Input @bind-Value="context.Title" />
</FormItem>
<FormItem Label="計劃日期">
<DatePicker @bind-Value="context.PlanTime" Picker="@DatePickerType.Date" />
</FormItem>
<FormItem Label="截至日期">
<DatePicker @bind-Value="context.Deadline" Picker="@DatePickerType.Date" />
</FormItem>
<FormItem Label="描述">
<TextArea @bind-Value="context.Description" MinRows="4" />
</FormItem>
<FormItem Label="重要">
<Switch @bind-Value="context.IsImportant" />
</FormItem>
<FormItem Label="完成">
<Switch @bind-Value="context.IsFinish" />
</FormItem>
</Form>
在之前的<div class="title">
中新增@onclick="x=>OnCardClick(item)"
註冊點選事件觸發async void OnCardClick(TaskDto task)
方法,然後使用DrawerSrv.CreateDialogAsync
方法開啟一個抽屜,抽屜中包含TaskInfo
元件,當抽屜關閉時用InvokeAsync
更新頁面。
async/await
非同步等待,可以讓非同步操作的程式碼變成同步編碼風格,此處CreateDialogAsync
是一個非同步過程,透過它讓他進行非同步等待,只有在抽屜關閉後才會繼續執行後面的await InvokeAsync(StateHasChanged);
程式碼,這語法可以避免大量的回撥程式碼,簡化程式碼。
StateHasChanged
在一般情況下狀態發生了改變,blazor
會自動更新繫結內容,但是如果在不同執行緒或者某些情況修改了狀態,blazor
可能無法跟蹤改變,導致介面沒有重新整理繫結內容,這時我們就可以使用StateHasChanged
方法顯示的更新狀態。
@inherits DrawerTemplate<TaskDto, TaskDto>
抽屜元件必須要繼承DrawerTemplate
類,前面一個TaskDto
是抽屜開啟時需要傳入的引數型別,後面一個TaskDto
是抽屜關閉時返回的型別。
[Inject] public DrawerService DrawerSrv { get; set; }
依賴注入抽屜服務
Drawer元件幫助文件:https://antblazor.com/zh-CN/components/drawer
Form
表單元件 Model="this.Options"
表單繫結的物件
Form元件幫助文件:https://antblazor.com/zh-CN/components/form
新增待辦
要做的事情永遠做不完,因為我們每天不停的在增加待辦
<div class="task-input">
<DatePicker Picker="@DatePickerType.Date" @bind-Value="@newTask.PlanTime" />
<Input @bind-Value="@newTask.Title" OnkeyUp="OnInsert" />
</div>
TaskDto newTask = new TaskDto() { PlanTime = DateTime.Now.Date };
void OnInsert(KeyboardEventArgs e)
{
if (e.Code == "Enter")
{
taskDtos.Add(newTask);
newTask = new TaskDto() { PlanTime = DateTime.Now.Date };
}
}
將newTask
繫結到DatePicker
和Input
元件,然後註冊OnkeyUp
事件,透過處理事件時採用if (e.Code == "Enter")
判斷回車,當回車時將newTask加入taskDtos
集合,並創新新的newTask
用於下一次新增。
@bind-Value
雙向繫結Value
屬性,這個可以讓元件中的資料更改和變數的值雙向更新。
DatePicker
輸入或選擇日期的控制元件。 Picker="@DatePickerType.Date"
日期選擇模式
元件幫助文件:https://antblazor.com/zh-CN/components/datepicker
Input
透過滑鼠或鍵盤輸入內容,是最基礎的表單域的包裝。 OnkeyUp="OnInsert"
鍵盤按鍵抬起事件,如果沒有明確指定引數,那麼他會帶上KeyboardEventArgs
引數,不同的事件的引數不同,詳見ASP.NET Core Blazor 事件處理
Input元件幫助文件:https://antblazor.com/zh-CN/components/input
刪除待辦
世上沒有反悔藥,但是程式的世界,反悔就是家常便飯,so,上程式碼
<span style="color:@(days switch { _ when days > 3 => "#ccc", _ when days > 0 => "#ffd800", _ => "#ff0000" })">
@item.Deadline?.ToShortDateString()
</span>
</div>
<!--從這裡開始插入以下程式碼-->
<div class="del" @onclick="async e=>await OnDel(item)">
<Icon Type="rest" Theme="outline" />
</div>
[Inject] public ConfirmService ConfirmSrv { get; set; }
public async Task OnDel(TaskDto task)
{
if (await ConfirmSrv.Show($"是否刪除任務 {task.Title}", "刪除", ConfirmButtons.YesNo, ConfirmIcon.Info) == ConfirmResult.Yes)
{
taskDtos.Remove(task);
}
}
這裡使用ConfirmSrv
服務提供的訊息框功能,並藉助await
的特性,無需回撥,直接判斷返回值是否是ConfirmResult.Yes
,然後刪除選擇任務。
ConfirmSrv.Show
快捷地彈出一個內建的確認框。
modal元件幫助文件:https://antblazor.com/zh-CN/components/modal
完成待辦
我的一天待辦最後一個功能,完成它,gogogo
<Card Bordered="true" Size="small" Class="task-card">
<div class="task-card-item">
<!--從這裡開始插入以下程式碼-->
@{
var finishClass = new ClassMapper().Add("finish").If("unfinish", () => item.IsFinish == false);
}
<div class="@(finishClass.ToString())" @onclick="x => OnFinish(item)">
<Icon Type="check" Theme="outline" />
</div>
private void OnFinish(TaskDto task)
{
task.IsFinish = !task.IsFinish;
}
這個功能的實現方式與“標記重要”功能相似,區別是它透過修改樣式來顯示與隱藏完成標記。
ClassMapper
類是AntDesignBlazor
中自帶的class工具,它透過鏈式程式碼可以根據條件組合成需要的class .Add("finish")
新增名字為finish
的class .If("unfinish", () => item.IsFinish == false)
根據表示式item.IsFinish == false
值決定是否新增名字為unfinish
的class
全部待辦
想要檢視所有待辦,那麼就做一個“全部”介面,繼續程式碼➡效果➡講解三步走 建立TaskSearch.razor
檔案
@page "/search"
@inject TaskServices TaskSvr
<PageHeader Title="@("全部待辦事項")" Subtitle="@($"數量:{datas?.Count}")"></PageHeader>
<Search @bind-Value="title" OnSearch="OnSearch"></Search>
<Spin Spinning="@isLoading">
<Table DataSource="@datas">
<AntDesign.Column @bind-Field="@context.Title" Sortable>
@context.Title
@if (context.IsImportant)
{
<Tag Color="orange">重要</Tag>
}
</AntDesign.Column>
<AntDesign.Column @bind-Field="@context.Description" />
<AntDesign.Column @bind-Field="@context.PlanTime" Sortable />
<AntDesign.Column @bind-Field="@context.Deadline" Sortable />
<AntDesign.Column @bind-Field="@context.IsFinish">
@if (context.IsFinish)
{
<Icon Type="check" Theme="outline" />
}
</AntDesign.Column>
</Table>
</Spin>
private bool isLoading = false;
protected async override Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await OnSearch();
}
private async Task OnSearch()
{
isLoading = true;
datas = await TaskSvr.LoadSearch(title);
isLoading = false;
}
private string title;
List<TaskDto> datas = new List<TaskDto>();
在OnInitializedAsync
中使用OnSearch()
方法將資料載入datas
,介面使用Table
元件顯示載入的資料。
Spin
用於頁面和區塊的載入中狀態。 Spinning="@isLoading"
設定載入狀態。
Spin元件幫助文件:https://antblazor.com/zh-CN/components/spin
Table
展示行列資料 DataSource="@datas"
表格中需要顯示的資料透過DataSource
繫結
AntDesign.Column
表格中的列 @bind-Field="@context.Title"
列顯示的欄位,支援模板
Table元件幫助文件:https://antblazor.com/zh-CN/components/table
Tag
進行標記和分類的小標籤。 Color="orange"
標籤顯示為橘色
Tag元件幫助文件:https://antblazor.com/zh-CN/components/tag
程式啟動動畫
因為WebAssembly
啟動前需要一些時間下載程式碼,這個時候瀏覽器預設是白屏,這會讓使用者覺得網路不暢或者系統發生了問題,影響客戶體驗,所以我們通常會在啟動時加入一個啟動等待動畫,這個只需要簡單修改index.html
即可
<body>
<app>
<div class="loading">
<!--此處加入blazor完成啟動前需要顯示的載入動畫-->
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss"> </a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
次回預告
到這裡我們把待辦工具的介面做好了,但是所有資料都是模擬的,下一次我們將透過HttpClient
實現前後端資料互動,以及使用EF Code
進行超級簡單的資料庫增刪改查。