上一篇完成了分類標籤友鏈的列表查詢頁面數據綁定,還剩下一個文章詳情頁的數據沒有綁,如今簡單的解決掉。javascript
以前已經添加了四個參數:year、month、day、name,用來組成咱們最終的URL,繼續添加一個參數用來接收API返回的數據。html
[Parameter] public int year { get; set; } [Parameter] public int month { get; set; } [Parameter] public int day { get; set; } [Parameter] public string name { get; set; } /// <summary> /// URL /// </summary> private string url => $"/{year}/{(month >= 10 ? month.ToString() : $"0{month}")}/{(day >= 10 ? day.ToString() : $"0{day}")}/{name}/"; /// <summary> /// 文章詳情數據 /// </summary> private ServiceResult<PostDetailDto> post;
而後在初始化方法OnInitializedAsync()
中請求數據。前端
/// <summary> /// 初始化 /// </summary> protected override async Task OnInitializedAsync() { // 獲取數據 post = await Http.GetFromJsonAsync<ServiceResult<PostDetailDto>>($"/blog/post?url={url}"); }
如今拿到了post數據,而後在HTML中綁定便可。java
@if (post == null) { <Loading /> } else { @if (post.Success) { var _post = post.Result; <article class="post-wrap"> <header class="post-header"> <h1 class="post-title">@_post.Title</h1> <div class="post-meta"> Author: <a itemprop="author" rel="author" href="javascript:;">@_post.Author</a> <span class="post-time"> Date: <a href="javascript:;">@_post.CreationTime</a> </span> <span class="post-category"> Category:<a href="/category/@_post.Category.DisplayName/">@_post.Category.CategoryName</a> </span> </div> </header> <div class="post-content" id="content"> @((MarkupString)_post.Html) </div> <section class="post-copyright"> <p class="copyright-item"> <span>Author:</span> <span>@_post.Author</span> </p> <p class="copyright-item"> <span>Permalink:</span> <span><a href="/post@_post.Url">https://meowv.com/post@_post.Url</a></span> </p> <p class="copyright-item"> <span>License:</span> <span>本文采用<a target="_blank" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> 知識共享 署名-非商業性使用-禁止演繹(CC BY-NC-ND)國際許可協議 </a>進行許可</span> </p> </section> <section class="post-tags"> <div> <span>Tag(s):</span> <span class="tag"> @if (_post.Tags.Any()) { @foreach (var tag in _post.Tags) { <a href="/tag/@tag.DisplayName/"># @tag.TagName</a> } } </span> </div> <div> <a @onclick="async () => await Common.BaskAsync()">back</a> <span>· </span> <a href="/">home</a> </div> </section> <section class="post-nav"> @if (_post.Previous != null) { <a class="prev" rel="prev" @onclick="@(async () => await Common.NavigateTo($"/post{_post.Previous.Url}, true))" href="/post@_post.Previous.Url">@_post.Previous.Title</a> } @if (_post.Next != null) { <a class="next" rel="next" @onclick="@(async () => await Common.NavigateTo($"/post{_post.Next.Url}", true))" href="/post@_post.Next.Url"> @_post.Next.Title </a> } </section> </article> } else { <ErrorTip /> } }
其中有幾個地方須要注意一下:git
咱們從post對象中取到的文章內容HTML,直接顯示是不行了,須要將其解析爲HTML標籤,須要用到MarkupString
。github
而後頁面上有一個後退按鈕,這裏我在Common.cs
中寫了一個方法來實現。瀏覽器
/// <summary> /// 後退 /// </summary> /// <returns></returns> public async Task BaskAsync() { await InvokeAsync("window.history.back"); }
還有就是上一篇和下一篇的問題,將具體的URL傳遞給NavigateTo()
方法,而後跳轉過去便可。緩存
在Common.cs
中將以前文章建立RenderPage()
方法修改爲NavigateTo()
。這個命名更好一點。cookie
/// <summary> /// 跳轉指定URL /// </summary> /// <param name="uri"></param> /// <param name="forceLoad">true,繞過路由刷新頁面</param> /// <returns></returns> public async Task NavigateTo(string url, bool forceLoad = false) { _navigationManager.NavigateTo(url, forceLoad); await Task.CompletedTask; }
如今數據算是綁定完了,可是遇到了一個大問題,就是詳情頁面的樣式問題,由於用到了Markdown,因此以前是加載了許多JS文件來處理的。那麼如今確定行不通了,因此關於詳情頁的樣式問題暫時擱淺,讓我尋找一下好多解決方式。網絡
如今顯示是沒有問題了,就是不太好看,還有關於添加文章的功能,不知道有什麼好的 Markdown 編輯器能夠推薦我使用。
到這裏Blazor的前端展現頁面已經所有弄完了,接下來開始寫後臺相關的頁面。
關於後臺管理的全部頁面都放在Admin文件夾下,在Pages文件夾下新建Admin文件夾,而後先添加兩個組件頁面:Admin.razor
、Auth.razor
。
Admin.razor
爲後臺管理的首頁入口,咱們在裏面直接添加幾個預知的連接並設置其路由。
@page "/admin" <div class="post-wrap"> <h2 class="post-title">- 博客內容管理 -</h2> <ul> <li> <a href="/admin/post"><h3>📝~~~ 新增文章 ~~~📝</h3></a> </li> <li> <a href="/admin/posts"><h3>📗~~~ 文章管理 ~~~📗</h3></a> </li> <li> <a href="/admin/categories"><h3>📕~~~ 分類管理 ~~~📕</h3></a> </li> <li> <a href="/admin/tags"><h3>📘~~~ 標籤管理 ~~~📘</h3></a> </li> <li> <a href="/admin/friendlinks"><h3>📒~~~ 友鏈管理 ~~~📒</h3></a> </li> </ul> </div>
裏面的a標籤所對應的頁面尚未添加,等作到的時候再加,先手動訪問這個頁面看看,當成功受權後就跳到這個頁面來。
關於受權,由於以前在API中已經完成了基於Github的JWT模式的認證受權模式,因此這裏我想作一個無感的受權功能,爲何說無感呢,由於在我使用GitHub登陸的過程當中,若是以前已經登陸過且沒有清除瀏覽器cookie數據,下次再登陸的時候會默認直接登陸成功,從而達到無感的。
實現邏輯其實也很簡單,我這裏用到了Common.cs
中以前添加的公共方法設置和獲取localStorage
的方法,我會將token等信息放入localStorage
中。
我設置的路由是:/auth
。這個路由須要和 GitHub OAuth App 的回調地址一致,當登陸成功,會回調跳到配置的頁面並攜帶code參數。
在獲取請求參數這塊須要引用一個包:Microsoft.AspNetCore.WebUtilities
,添加好後在_Imports.razor
添加引用:@using Meowv.Blog.BlazorApp.Shared
。
默認仍是顯示加載中的組件:<Loading />
。
而後在@code{}
中編寫代碼,添加頁面初始化函數。
/// <summary> /// 初始化 /// </summary> /// <returns></returns> protected override async Task OnInitializedAsync() { // localStorage中access_token值 var access_token = await Common.GetStorageAsync("access_token"); // access_token有值 if (!string.IsNullOrEmpty(access_token)) { // 獲取token var _token = await Http.GetFromJsonAsync<ServiceResult<string>>($"/auth/token?access_token={access_token}"); if (_token.Success) { // 將token存入localStorage await Common.SetStorageAsync("token", _token.Result); // 跳轉至後臺首頁 await Common.NavigateTo("/admin"); } else { // access_token失效,或者請求失敗的狀況下,從新執行一次驗證流程 await AuthProcessAsync(); } } else //access_token爲空 { await AuthProcessAsync(); } }
先去獲取localStorage
中的access_token值,確定會有兩種狀況,有或者沒有,而後分別去走不一樣的邏輯。
當access_token有值,就能夠直接拿access_token去取token的值,理想狀況請求成功拿到了token,這時候能夠將token存到瀏覽器中,而後正常跳轉至後臺管理首頁,還有就是取token失敗了,失敗了就有多是access_token過時了或者出現異常狀況,這時候咱們不去提示錯誤,直接拋棄全部,從新來一遍認證受權的流程,放在一個單獨的方法中AuthProcessAsync()
。
而當access_token沒值那就好辦了,也去來一遍認證受權的流程便可。
驗證流程AuthProcessAsync()
的代碼。
/// <summary> /// 驗證流程 /// </summary> /// <returns></returns> private async Task AuthProcessAsync() { // 當前URI對象 var uri = await Common.CurrentUri(); // 是否回調攜帶了code參數 bool hasCode = QueryHelpers.ParseQuery(uri.Query).TryGetValue("code", out Microsoft.Extensions.Primitives.StringValues code); if (hasCode) { var access_token = await Http.GetFromJsonAsync<ServiceResult<string>>($"/auth/access_token?code={code}"); if (access_token.Success) { // 將access_token存入localStorage await Common.SetStorageAsync("access_token", access_token.Result); var token = await Http.GetFromJsonAsync<ServiceResult<string>>($"/auth/token?access_token={access_token.Result}"); if (token.Success) { // 將token存入localStorage await Common.SetStorageAsync("token", token.Result); // 成功認證受權,跳轉至後臺管理首頁 await Common.NavigateTo("/admin"); } else { // 沒有權限的人,回到首頁去吧 await Common.NavigateTo("/"); // 輸出提示信息 Console.WriteLine(token.Message); } } else { // 出錯了,回到首頁去吧 await Common.NavigateTo("/"); // 輸出提示信息 Console.WriteLine(access_token.Message); } } else { // 獲取第三方登陸地址 var loginAddress = await Http.GetFromJsonAsync<ServiceResult<string>>("/auth/url"); // 跳轉到登陸頁面 await Common.NavigateTo(loginAddress.Result); } }
驗證流程的邏輯先獲取當前URI對象,判斷URI中是否攜帶了code參數,從而能夠知道當前頁面是回調的過來的仍是直接請求的,獲取當前URI對象放在Common.cs
中。
/// <summary> /// 獲取當前URI對象 /// </summary> /// <returns></returns> public async Task<Uri> CurrentUri() { var uri = _navigationManager.ToAbsoluteUri(_navigationManager.Uri); return await Task.FromResult(uri); }
在剛纔添加的包Microsoft.AspNetCore.WebUtilities
中爲咱們封裝好了解析URI參數的方法。
使用QueryHelpers.ParseQuery(...)
獲取code參數的值。
當沒有值的時候,直接取請求登陸地址,而後若是登陸成功就會跳轉到攜帶code參數的回調頁面。這樣流程就又回到了 驗證流程 開始的地方了。
登陸成功,此時code確定就有值了,那麼直接根據code獲取access_token,存入localStorage
,正常狀況拿到access_token就去生成token,而後也存入localStorage
,成功受權能夠跳到後臺管理首頁了。
其中若是有任何一個環節出現問題,直接跳轉到網站首頁去。若是受權不成功確定是你在瞎搞(不接受任何反駁🤣🤣),趕忙回到首頁去吧。
如今流程走完,去看看效果。
GitHub在國內的狀況你們知道,有時候慢甚至打不開,有時候仍是挺快的,還好今天沒掉鏈子,我遇到過好幾回壓根打不開的狀況,獲取能夠針對網絡很差的時候咱們換成其它的驗證方式,這個之後有機會再優化吧。
這個時候會發現,其實咱們壓根不須要打開/auth
走驗證流程,直接訪問/admin
就能夠進來管理首頁,這是極其不合理的。那豈不是誰知道地址誰都能進來瞎搞了。因此咱們能夠在 Shared 文件夾下添加一個權限驗證的組件:AdminLayout.razor
。用來判斷是否真的登陸了。
新建一個bool類型的變量 isLogin。默認確定是false,此時可讓頁面轉圈圈,使用<Loading />
組件。當isLogin = true
的時候咱們才展現具體的HTML內容。
那麼就須要用到服務端組件RenderFragment
,他有一個固定的參數名稱ChildContent
。
判斷是否登陸的方法能夠寫在初始化方法中,這裏還少了一個API,就是判斷當前token的值是否合法,合法就表示已經成功執行了驗證流程了。token不存在或者不合法,直接拒絕請求返回到首頁去吧。
整個代碼以下:
@if (!isLogin) { <Loading /> } else { @ChildContent } @code { /// <summary> /// 展現內容 /// </summary> [Parameter] public RenderFragment ChildContent { get; set; } /// <summary> /// 是否登陸 /// </summary> private bool isLogin { get; set; } /// <summary> /// 初始化 /// </summary> /// <returns></returns> protected override async Task OnInitializedAsync() { var token = await Common.GetStorageAsync("token"); if (string.IsNullOrEmpty(token)) { isLogin = false; await Common.NavigateTo("/"); } else { // TODO:判斷token是否合法,先默認都是正確的 isLogin = true; } } }
使用這個組件也很方便了,咱們後臺全部頁面都引用AdminLayout
,將展現內容傳遞給就好了,成功驗證後就會展現HTM內容。
在Admin.razor
中使用。
@page "/admin" <AdminLayout> <div class="post-wrap"> <h2 class="post-title">- 博客內容管理 -</h2> <ul> <li> <a href="/admin/post"><h3>📝~~~ 新增文章 ~~~📝</h3></a> </li> <li> <a href="/admin/posts"><h3>📗~~~ 文章管理 ~~~📗</h3></a> </li> <li> <a href="/admin/categories"><h3>📕~~~ 分類管理 ~~~📕</h3></a> </li> <li> <a href="/admin/tags"><h3>📘~~~ 標籤管理 ~~~📘</h3></a> </li> <li> <a href="/admin/friendlinks"><h3>📒~~~ 友鏈管理 ~~~📒</h3></a> </li> </ul> </div> </AdminLayout>
如今清除掉瀏覽器緩存,去請求/admin
試試。
完美,比較簡單的實現了驗證是否登陸的組件。其中還有許多地方能夠優化,就交給你們去自行完成了😎。