基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)

1|0系列文章

  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 实战系列(六)
  27. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)
  28. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)
  29. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)
  30. 基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

上一篇完成了后台分类模块的所有功能,本篇继续将标签模块和友情链接模块的增删改查完成。

2|0标签管理

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|0友链管理

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


__EOF__

本文作者阿星Plus
本文链接https://www.cnblogs.com/meowv/p/13124533.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   阿星Plus  阅读(1030)  评论(2编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示