基于 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 实战系列(一)
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)
上一篇完成了后台分类模块的所有功能,本篇继续将标签模块和友情链接模块的增删改查完成。
标签管理

实现方式和之前的分类管理是一样的,在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">- Tags -</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">- Categories -</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;
}
}
}

友链管理

实现方式都是一样的,这个就不多说了,直接上代码。
先将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">- FriendLinks -</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;
}
}
}

截至目前为止,还剩下文章模块的功能还没做了,今天到这里吧,明天继续刚,未完待续...
开源地址: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 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- 【Java】手把手理解CAS实现原理
先来看看概念,[CAS] 全称“CompareAndSwap”,中文翻译即“比较并替换”. 定义:CAS操作包含三个操作数 —— 内存位置(V),期望值(A),和新值(B). 如果内存位置的值与期望值 ...
- java后端解决跨域
1 package com.zys.boot_jeep.config; import org.springframework.context.annotation.Bean; import org.s ...
- UIAutomator2安装及连接
记录一下自己的偿试过程,内容来自:https://github.com/openatx/uiautomator2 d.service("uiautomator").stop()是因 ...
- EF用导航熟悉遍历从表时,删除主表出错
var entitys= Repository.Table.Where(a => ids.Contains(a.UUID)).ToList(); entitys.ForEach(a => ...
- windows+vs2017+C语言 引入mysql.h对MYSQL数据库的操作
mysql.h文件用作VS开发用,用来连接数据库.没有mysql.h文件,就无法调用mysql的东西 也无法用C语言对MYSQL操作. 一般安装了mysql之后,这个文件就在mysql目录的inclu ...
- NetCore项目实战篇08---Docker挂载mysql并连接.netCoreWeb
我们的项目之前在直接连接的mysql,今天我们将通过docker挂载mysql 并与我们开发的webapi项目连接. 1. 安装docker 下载地址: https://download.docker ...
- centos7 在docker下安装mongodb
第一步:安装 1.1 查找(查看)mongo相应的版本 [root@localhost ~]# docker search mongo INDEX NAME DESCRIPTION STARS OFF ...
- 五、Java - 集合
一.集合 Java 中的集合类存放于 java.util 包中,是一个存放对象的容器. 集合存放的是对对象的引用,对象本身还是存在于 JVM 堆内存中. 存放的是对象,即引用数据类型,对于基本数据类型 ...
- 【Hadoop】Hadoop的安装,本地模式、伪分布模式的配置
Download hadoop-2.7.7.tar.gz 下载稳定版本的hadoop-2.7.7.tar.gz(我用的2.6.0,但是官网只能下载2.7.7的了) Required Software ...
- Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
542. 01 矩阵 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离. 两个相邻元素间的距离为 1 . 示例 1: 输入: 0 0 0 0 1 0 0 0 0 输出: 0 0 0 ...