基於 abp vNext 和 .NET Core 開發部落格專案 - Blazor 實戰系列(七)

阿星Plus發表於2020-06-16

系列文章

  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 實戰系列(六)

上一篇完成了後臺分類模組的所有功能,本篇繼續將標籤模組和友情連結模組的增刪改查完成。

標籤管理

1

實現方式和之前的分類管理是一樣的,在Admin資料夾下面新增Tags.razor元件,設定路由@page "/admin/tags"

同樣的內容也需要放在AdminLayout元件下面,新增幾個引數:彈窗狀態bool Open、新增或更新時標籤欄位string tagName, displayName、更新時的標籤Idint id、API返回的標籤列表接收引數ServiceResult<IEnumerable<QueryTagForAdminDto>> tags

/// <summary>
/// 預設隱藏Box
/// </summary>
private bool Open { get; set; } = false;

/// <summary>
/// 新增或者更新時候的標籤欄位值
/// </summary>
private string tagName, displayName;

/// <summary>
/// 更新標籤的Id值
/// </summary>
private int id;

/// <summary>
/// API返回的標籤列表資料
/// </summary>
private ServiceResult<IEnumerable<QueryTagForAdminDto>> tags;
//QueryTagForAdminDto.cs
namespace Meowv.Blog.BlazorApp.Response.Blog
{
    public class QueryTagForAdminDto : QueryTagDto
    {
        /// <summary>
        /// 主鍵
        /// </summary>
        public int Id { get; set; }
    }
}

在初始化方法OnInitializedAsync()中獲取資料。

/// <summary>
/// 初始化
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
    var token = await Common.GetStorageAsync("token");
    Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

    tags = await FetchData();
}

/// <summary>
/// 獲取資料
/// </summary>
/// <returns></returns>
private async Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> FetchData()
{
    return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryTagForAdminDto>>>("/blog/admin/tags");
}

注意需要設定請求頭,進行授權訪問,然後頁面上繫結資料。

<AdminLayout>
    @if (tags == null)
    {
        <Loading />
    }
    else
    {
        <div class="post-wrap tags">
            <h2 class="post-title">-&nbsp;Tags&nbsp;-</h2>
            @if (tags.Success && tags.Result.Any())
            {
                <div class="categories-card">
                    @foreach (var item in tags.Result)
                    {
                        <div class="card-item">
                            <div class="categories">
                                <NavLink title="❌刪除" @onclick="@(async () => await DeleteAsync(item.Id))">❌</NavLink>
                                <NavLink title="?編輯" @onclick="@(() => ShowBox(item))">?</NavLink>
                                <NavLink target="_blank" href="@($"/tag/{item.DisplayName}")">
                                    <h3>@item.TagName</h3>
                                    <small>(@item.Count)</small>
                                </NavLink>
                            </div>
                        </div>
                    }
                    <div class="card-item">
                        <div class="categories">
                            <NavLink><h3 @onclick="@(() => ShowBox())">?~~~ 新增標籤 ~~~?</h3></NavLink>
                        </div>
                    </div>
                </div>
            }
            else
            {
                <ErrorTip />
            }
        </div>

        <Box OnClickCallback="@SubmitAsync" Open="@Open">
            <div class="box-item">
                <b>DisplayName:</b><input type="text" @bind="@displayName" @bind:event="oninput" />
            </div>
            <div class="box-item">
                <b>TagName:</b><input type="text" @bind="@tagName" @bind:event="oninput" />
            </div>
        </Box>
    }
</AdminLayout>

tags沒獲取到資料的時候顯示<Loading />元件內容,迴圈遍歷資料進行繫結,刪除按鈕繫結點選事件呼叫DeleteAsync()方法。新增和編輯按鈕點選事件呼叫ShowBox()方法顯示彈窗。新增的時候不需要傳遞引數,編輯的時候需要將當前item即QueryTagForAdminDto傳遞進去。

<Box>元件中繫結了標籤的兩個引數,是否開啟引數Opne和確認按鈕回撥事件方法SubmitAsync()

刪除標籤的方法DeleteAsync(...)如下:

// 彈窗確認
bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n??真的要幹掉這個該死的標籤嗎??");

if (confirmed)
{
    var response = await Http.DeleteAsync($"/blog/tag?id={id}");

    var result = await response.Content.ReadFromJsonAsync<ServiceResult>();

    if (result.Success)
    {
        tags = await FetchData();
    }
}

刪除之前進行二次確認,避免誤傷,刪除成功重新載入一遍資料。

彈窗的方法ShowBox(...)如下:

/// <summary>
/// 顯示box,繫結欄位
/// </summary>
/// <param name="dto"></param>
private void ShowBox(QueryTagForAdminDto dto = null)
{
    Open = true;
    id = 0;

    // 新增
    if (dto == null)
    {
        displayName = null;
        tagName = null;
    }
    else // 更新
    {
        id = dto.Id;
        displayName = dto.DisplayName;
        tagName = dto.TagName;
    }
}

最後在彈窗中確認按鈕的回撥事件方法SubmitAsync()如下:

/// <summary>
/// 確認按鈕點選事件
/// </summary>
/// <returns></returns>
private async Task SubmitAsync()
{
    var input = new EditTagInput()
    {
        DisplayName = displayName.Trim(),
        TagName = tagName.Trim()
    };

    if (string.IsNullOrEmpty(input.DisplayName) || string.IsNullOrEmpty(input.TagName))
    {
        return;
    }

    var responseMessage = new HttpResponseMessage();

    if (id > 0)
        responseMessage = await Http.PutAsJsonAsync($"/blog/tag?id={id}", input);
    else
        responseMessage = await Http.PostAsJsonAsync("/blog/tag", input);

    var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
    if (result.Success)
    {
        tags = await FetchData();
        Open = false;
    }
}

輸入引數EditTagInput

namespace Meowv.Blog.BlazorApp.Response.Blog
{
    public class EditTagInput : TagDto
    {
    }
}

最終執行新增或者更新資料都在點選事件中進行,將變數的值賦值給EditTagInput,根據id判斷走新增還是更新,成功後重新載入資料,關掉彈窗。

標籤管理頁面全部程式碼如下:

點選檢視程式碼
@page "/admin/categories"

<AdminLayout>
    @if (categories == null)
    {
        <Loading />
    }
    else
    {
        <div class="post-wrap categories">
            <h2 class="post-title">-&nbsp;Categories&nbsp;-</h2>
            @if (categories.Success && categories.Result.Any())
            {
                <div class="categories-card">
                    @foreach (var item in categories.Result)
                    {
                        <div class="card-item">
                            <div class="categories">
                                <NavLink title="❌刪除" @onclick="@(async () => await DeleteAsync(item.Id))">❌</NavLink>
                                <NavLink title="?編輯" @onclick="@(() => ShowBox(item))">?</NavLink>
                                <NavLink target="_blank" href="@($"/category/{item.DisplayName}")">
                                    <h3>@item.CategoryName</h3>
                                    <small>(@item.Count)</small>
                                </NavLink>
                            </div>
                        </div>
                    }
                    <div class="card-item">
                        <div class="categories">
                            <NavLink><h3 @onclick="@(() => ShowBox())">?~~~ 新增分類 ~~~?</h3></NavLink>
                        </div>
                    </div>
                </div>
            }
            else
            {
                <ErrorTip />
            }
        </div>

        <Box OnClickCallback="@SubmitAsync" Open="@Open">
            <div class="box-item">
                <b>DisplayName:</b><input type="text" @bind="@displayName" @bind:event="oninput" />
            </div>
            <div class="box-item">
                <b>CategoryName:</b><input type="text" @bind="@categoryName" @bind:event="oninput" />
            </div>
        </Box>
    }
</AdminLayout>

@code {
    /// <summary>
    /// 預設隱藏Box
    /// </summary>
    private bool Open { get; set; } = false;

    /// <summary>
    /// 新增或者更新時候的分類欄位值
    /// </summary>
    private string categoryName, displayName;

    /// <summary>
    /// 更新分類的Id值
    /// </summary>
    private int id;

    /// <summary>
    /// API返回的分類列表資料
    /// </summary>
    private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;

    /// <summary>
    /// 初始化
    /// </summary>
    /// <returns></returns>
    protected override async Task OnInitializedAsync()
    {
        var token = await Common.GetStorageAsync("token");
        Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

        categories = await FetchData();
    }

    /// <summary>
    /// 獲取資料
    /// </summary>
    /// <returns></returns>
    private async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> FetchData()
    {
        return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");
    }

    /// <summary>
    /// 刪除分類
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private async Task DeleteAsync(int id)
    {
        Open = false;

        // 彈窗確認
        bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n??真的要幹掉這個該死的分類嗎??");

        if (confirmed)
        {
            var response = await Http.DeleteAsync($"/blog/category?id={id}");

            var result = await response.Content.ReadFromJsonAsync<ServiceResult>();

            if (result.Success)
            {
                categories = await FetchData();
            }
        }
    }

    /// <summary>
    /// 顯示box,繫結欄位
    /// </summary>
    /// <param name="dto"></param>
    private void ShowBox(QueryCategoryForAdminDto dto = null)
    {
        Open = true;
        id = 0;

        // 新增
        if (dto == null)
        {
            displayName = null;
            categoryName = null;
        }
        else // 更新
        {
            id = dto.Id;
            displayName = dto.DisplayName;
            categoryName = dto.CategoryName;
        }
    }

    /// <summary>
    /// 確認按鈕點選事件
    /// </summary>
    /// <returns></returns>
    private async Task SubmitAsync()
    {
        var input = new EditCategoryInput()
        {
            DisplayName = displayName.Trim(),
            CategoryName = categoryName.Trim()
        };

        if (string.IsNullOrEmpty(input.DisplayName) || string.IsNullOrEmpty(input.CategoryName))
        {
            return;
        }

        var responseMessage = new HttpResponseMessage();

        if (id > 0)
            responseMessage = await Http.PutAsJsonAsync($"/blog/category?id={id}", input);
        else
            responseMessage = await Http.PostAsJsonAsync("/blog/category", input);

        var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
        if (result.Success)
        {
            categories = await FetchData();
            Open = false;
        }
    }
}

2

友鏈管理

3

實現方式都是一樣的,這個就不多說了,直接上程式碼。

先將API返回的接收引數和新增編輯的輸入引數新增一下。

//QueryFriendLinkForAdminDto.cs
namespace Meowv.Blog.BlazorApp.Response.Blog
{
    public class QueryFriendLinkForAdminDto : FriendLinkDto
    {
        /// <summary>
        /// 主鍵
        /// </summary>
        public int Id { get; set; }
    }
}

//EditFriendLinkInput.cs
namespace Meowv.Blog.BlazorApp.Response.Blog
{
    public class EditFriendLinkInput : FriendLinkDto
    {
    }
}
@page "/admin/friendlinks"

<AdminLayout>
    @if (friendlinks == null)
    {
        <Loading />
    }
    else
    {
        <div class="post-wrap categories">
            <h2 class="post-title">-&nbsp;FriendLinks&nbsp;-</h2>
            @if (friendlinks.Success && friendlinks.Result.Any())
            {
                <div class="categories-card">
                    @foreach (var item in friendlinks.Result)
                    {
                        <div class="card-item">
                            <div class="categories">
                                <NavLink title="❌刪除" @onclick="@(async () => await DeleteAsync(item.Id))">❌</NavLink>
                                <NavLink title="?編輯" @onclick="@(() => ShowBox(item))">?</NavLink>
                                <NavLink target="_blank" href="@item.LinkUrl">
                                    <h3>@item.Title</h3>
                                </NavLink>
                            </div>
                        </div>
                    }
                    <div class="card-item">
                        <div class="categories">
                            <NavLink><h3 @onclick="@(() => ShowBox())">?~~~ 新增友鏈 ~~~?</h3></NavLink>
                        </div>
                    </div>
                </div>
            }
            else
            {
                <ErrorTip />
            }
        </div>

        <Box OnClickCallback="@SubmitAsync" Open="@Open">
            <div class="box-item">
                <b>Title:</b><input type="text" @bind="@title" @bind:event="oninput" />
            </div>
            <div class="box-item">
                <b>LinkUrl:</b><input type="text" @bind="@linkUrl" @bind:event="oninput" />
            </div>
        </Box>
    }
</AdminLayout>

@code {
    /// <summary>
    /// 預設隱藏Box
    /// </summary>
    private bool Open { get; set; } = false;

    /// <summary>
    /// 新增或者更新時候的友鏈欄位值
    /// </summary>
    private string title, linkUrl;

    /// <summary>
    /// 更新友鏈的Id值
    /// </summary>
    private int id;

    /// <summary>
    /// API返回的友鏈列表資料
    /// </summary>
    private ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>> friendlinks;

    /// <summary>
    /// 初始化
    /// </summary>
    /// <returns></returns>
    protected override async Task OnInitializedAsync()
    {
        var token = await Common.GetStorageAsync("token");
        Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

        friendlinks = await FetchData();
    }

    /// <summary>
    /// 獲取資料
    /// </summary>
    /// <returns></returns>
    private async Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> FetchData()
    {
        return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>>("/blog/admin/friendlinks");
    }

    /// <summary>
    /// 刪除分類
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private async Task DeleteAsync(int id)
    {
        Open = false;

        // 彈窗確認
        bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n??真的要幹掉這個該死的分類嗎??");

        if (confirmed)
        {
            var response = await Http.DeleteAsync($"/blog/friendlink?id={id}");

            var result = await response.Content.ReadFromJsonAsync<ServiceResult>();

            if (result.Success)
            {
                friendlinks = await FetchData();
            }
        }
    }

    /// <summary>
    /// 顯示box,繫結欄位
    /// </summary>
    /// <param name="dto"></param>
    private void ShowBox(QueryFriendLinkForAdminDto dto = null)
    {
        Open = true;
        id = 0;

        // 新增
        if (dto == null)
        {
            title = null;
            linkUrl = null;
        }
        else // 更新
        {
            id = dto.Id;
            title = dto.Title;
            linkUrl = dto.LinkUrl;
        }
    }

    /// <summary>
    /// 確認按鈕點選事件
    /// </summary>
    /// <returns></returns>
    private async Task SubmitAsync()
    {
        var input = new EditFriendLinkInput()
        {
            Title = title.Trim(),
            LinkUrl = linkUrl.Trim()
        };

        if (string.IsNullOrEmpty(input.Title) || string.IsNullOrEmpty(input.LinkUrl))
        {
            return;
        }

        var responseMessage = new HttpResponseMessage();

        if (id > 0)
            responseMessage = await Http.PutAsJsonAsync($"/blog/friendlink?id={id}", input);
        else
            responseMessage = await Http.PostAsJsonAsync("/blog/friendlink", input);

        var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
        if (result.Success)
        {
            friendlinks = await FetchData();
            Open = false;
        }
    }
}

3

截至目前為止,還剩下文章模組的功能還沒做了,今天到這裡吧,明天繼續剛,未完待續...

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

相關文章