四、新闻后台实战:用Admin Blazor管理文章和专栏
前面三篇我们讲了:
- 第一篇:3分钟搭一个Blazor后台
- 第二篇:项目目录结构详解
- 第三篇:几十行代码搞定CRUD
今天来点实战,用一个真实的新闻管理模块做案例,把专栏管理和文章管理完整走一遍。看看关联表、枚举、富文本编辑器这些实际项目中一定会遇到的需求,在EasyAdminBlazor里怎么处理。
一、实体设计
1. 专栏实体(Classify)
/// <summary>
/// 随笔专栏
/// </summary>
[Table(Name = "blog_classify")]
public class Classify : EntityCreated
{
/// <summary>
/// 分类专栏名称
/// </summary>
[Column(StringLength = 50)]
[DisplayName("专栏名称")]
public string ClassifyName { get; set; } = string.Empty;
/// <summary>
/// 封面图
/// </summary>
[Column(StringLength = 100)]
[DisplayName("封面图")]
public string Thumbnail { get; set; } = string.Empty;
/// <summary>
/// 排序
/// </summary>
[DisplayName("排序")]
public int SortCode { get; set; }
/// <summary>
/// 文章数量(冗余字段,方便列表展示)
/// </summary>
[DisplayName("文章数量")]
public int ArticleCount { get; set; }
}
2. 文章实体(Article)
/// <summary>
/// 随笔
/// </summary>
[Table(Name = "blog_article")]
public class Article : EntityModified
{
/// <summary>
/// 所属专栏
/// </summary>
[DisplayName("随笔专栏")]
[Required]
public long? ClassifyId { get; set; }
/// <summary>
/// 导航属性:关联的专栏
/// </summary>
[Navigate(nameof(ClassifyId))]
public Classify Classify { get; set; } = default!;
/// <summary>
/// 标题
/// </summary>
[Column(StringLength = 200)]
[DisplayName("标题")]
[Required]
public string Title { get; set; } = string.Empty;
/// <summary>
/// 关键字
/// </summary>
[Column(StringLength = 400)]
[DisplayName("关键字")]
public string Keywords { get; set; } = string.Empty;
/// <summary>
/// 摘要
/// </summary>
[Column(StringLength = 500)]
[DisplayName("摘要")]
public string Excerpt { get; set; } = string.Empty;
/// <summary>
/// 正文(-2表示长文本,不限长度)
/// </summary>
[Column(StringLength = -2)]
[DisplayName("正文")]
[Required]
public string Content { get; set; } = string.Empty;
/// <summary>
/// 缩略图
/// </summary>
[Column(StringLength = 400)]
[DisplayName("缩略图")]
public string Thumbnail { get; set; } = string.Empty;
/// <summary>
/// 随笔类型(支持多选)
/// </summary>
[DisplayName("随笔类型")]
public ArticleType ArticleType { get; set; }
/// <summary>
/// 浏览量
/// </summary>
[DisplayName("浏览量")]
public int ViewHits { get; set; }
/// <summary>
/// 评论数量
/// </summary>
[DisplayName("评论数量")]
public int CommentQuantity { get; set; }
/// <summary>
/// 是否审核
/// </summary>
[DisplayName("是否审核")]
public bool IsAudit { get; set; }
/// <summary>
/// 是否推荐
/// </summary>
[DisplayName("是否推荐")]
public bool Recommend { get; set; }
/// <summary>
/// 是否置顶
/// </summary>
[DisplayName("是否置顶")]
public bool IsStickie { get; set; }
/// <summary>
/// 开启评论
/// </summary>
[DisplayName("开启评论")]
public bool Commentable { get; set; } = true;
/// <summary>
/// 发布时间
/// </summary>
[DisplayName("发布时间")]
public DateTime? PublishTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 随笔类型(Flags特性支持多选)
/// </summary>
[Flags]
public enum ArticleType
{
原创 = 1,
转载 = 2,
翻译 = 4
}
几个关键点:
[Navigate]特性标记导航属性,告诉 FreeSql 这是关联关系[Column(StringLength = -2)]表示不限长度的文本,适合存富文本内容[Flags]枚举支持多选组合,比如可以同时选“原创+翻译”
二、专栏管理页面
专栏相对简单,就是最基础的 CRUD:
@page "/Blog/Classify"
<AdminTable TItem="Classify" TKey="long"
OnBeforeQuery="OnBeforeQuery"
EditDialogSize="Size.Large"
ShowAdvancedSearch="false"
ShowImportButton
ShowExportButton
ShowExtendButtons
IsPagination
ShowSearch
IsMultipleSelect>
<TableColumns>
<TableColumn @bind-Field="context.ClassifyName" Text="专栏名称" Filterable="true" />
<TableColumn @bind-Field="context.Thumbnail" Text="封面图" />
<TableColumn @bind-Field="context.SortCode" Text="排序" />
<TableColumn @bind-Field="context.ArticleCount" Text="文章数量" />
<TableColumn @bind-Field="context.CreatedTime" Text="创建时间" />
</TableColumns>
</AdminTable>
@code {
private void OnBeforeQuery(AdminQueryEventArgs<Classify> e)
{
// 如果需要过滤条件,在这里写
}
}
就这么几行,专栏管理的完整 CRUD 就有了:增删改查、分页、搜索、导出 Excel,全自动。
三、文章管理页面(核心)
文章管理稍微复杂一点,涉及三个典型场景:
- 关联专栏:列表显示专栏名称,筛选用下拉框
- 枚举多选:文章类型支持多选筛选
- 富文本编辑:正文需要编辑器
完整代码:
@page "/Blog/Article"
@using EasyAdminBlazorDemo.Entities
@using EasyAdminBlazor.Components.Admin
<AdminTable TItem="Article" TKey="long"
OnBeforeQuery="OnBeforeQuery"
EditDialogSize="Size.ExtraLarge"
ShowImportButton
ShowExportButton
ShowExtendButtons
IsPagination
ShowSearch
ShowAdvancedSearch
IsMultipleSelect>
<TableColumns>
<!-- 1. 关联专栏:下拉筛选 + 显示名称 -->
<TableColumn @bind-Field="context.ClassifyId" Text="随笔专栏" Filterable="true">
<Template Context="v">
@v.Row.Classify?.ClassifyName
</Template>
<FilterTemplate>
<FilterProvider>
<AdminSelectEntityFilter TItem="Classify" GetText="x => x.ClassifyName" />
</FilterProvider>
</FilterTemplate>
</TableColumn>
<!-- 2. 标题 -->
<TableColumn @bind-Field="context.Title" Text="标题" Filterable="true" Searchable="true" />
<!-- 3. 文章类型:枚举多选筛选 -->
<TableColumn @bind-Field="context.ArticleType" Text="类型"
ComponentType="typeof(MultiSelect<ArticleType>)"
Filterable="true"
Searchable="true">
<FilterTemplate>
<FilterProvider ShowMoreButton="false">
<AdminMultiSelectFilter TValue="ArticleType" />
</FilterProvider>
</FilterTemplate>
</TableColumn>
<!-- 4. 浏览量 -->
<TableColumn @bind-Field="context.ViewHits" Text="浏览量" />
<!-- 5. 评论数量 -->
<TableColumn @bind-Field="context.CommentQuantity" Text="评论数量" />
<!-- 6. 是否审核(布尔值) -->
<TableColumn @bind-Field="context.IsAudit" Text="审核" />
<!-- 7. 创建时间 -->
<TableColumn @bind-Field="context.CreatedTime" Text="创建时间" />
</TableColumns>
<!-- 编辑弹窗模板:使用独立的编辑器组件 -->
<EditTemplate>
<ArticleEditTemplate Model="context" />
</EditTemplate>
</AdminTable>
@code {
private void OnBeforeQuery(AdminQueryEventArgs<Article> e)
{
// 预加载关联的专栏数据,避免N+1查询
e.Select.Include(a => a.Classify);
}
}
几个关键点解释
| 场景 | 代码 | 说明 |
|---|---|---|
| 列表显示关联字段 | <Template>@v.Row.Classify?.ClassifyName</Template> |
用 Template 显示导航属性的值 |
| 筛选关联表 | <AdminSelectEntityFilter TItem="Classify" .../> |
筛选器自动变成专栏下拉框 |
| 枚举多选筛选 | <AdminMultiSelectFilter TValue="ArticleType" /> |
支持多选的枚举筛选器 |
| 预加载关联数据 | e.Select.Include(a => a.Classify) |
避免循环查询,性能优化 |
四、富文本编辑器组件(ArticleEditTemplate)
方便代码的组织,我们单独抽一个组件出来(单独抽离编辑组件还有个原因就是因为BootstrapBlazor需要在新组件中才能使用交互能力,比如用一个字段控制另一个字段是否是显示/隐藏):
<Tab IsCard>
<TabItem Text="随笔">
<div class="row form-inline g-3">
<div class="col-12 col-sm-6 col-md-6">
<AdminSelectEntity TItem="Classify" TKey="long?" @bind-Value="Model.ClassifyId" GetText="e => e.ClassifyName" ShowSearch />
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Model.Title" type="text" maxlength="200" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<MultiSelect @bind-Value="Model.ArticleType" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Model.Keywords" type="text" maxlength="400" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<AdminDictSelect @bind-Value="Model.Source" ParentName="GENDER" />
</div>
<div class="col-12">
<Textarea @bind-Value="Model.Excerpt" maxlength="500"></Textarea>
</div>
<div class="col-6">
<AdminFileInput @bind-Value="Model.Thumbnail" />
</div>
<div class="col-6">
<DateTimePicker @bind-Value="Model.PublishTime" DisplayText="发布时间" IsEditable />
</div>
<div class="col-12">
<AdminEditor @bind-Value="Model.Content" />
</div>
</div>
</TabItem>
<TabItem Text="设置">
<div class="row form-inline g-3">
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Model.ViewHits" type="number" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Model.CommentQuantity" type="number" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<Switch @bind-Value="Model.IsAudit" OnColor="Color.Success" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<Switch @bind-Value="Model.Recommend" OnColor="Color.Success" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<Switch @bind-Value="Model.IsStickie" OnColor="Color.Success" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Model.WordNumber" type="number" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Model.ReadingTime" type="number" />
</div>
<div class="col-12 col-sm-6 col-md-6">
<Switch @bind-Value="Model.Commentable" OnColor="Color.Success" />
</div>
</div>
</TabItem>
</Tab>
@code {
[Parameter]
[NotNull]
public Article? Model { get; set; }
}
这个编辑器组件包含了:
- 下拉选择(专栏)
- 文本输入(标题、关键字、摘要)
- 多选枚举(文章类型)
- 开关按钮(是否审核、开启评论)
- 图片上传(缩略图)
- 富文本编辑器(需用nuget安装并启用EasyAdminBlazor.HtmlEditor扩展)
五、效果总结
做完上面这些,你得到的是一个完整的企业级新闻后台:
| 功能 | 专栏管理 | 文章管理 |
|---|---|---|
| 增删改查 | ✅ 自动 | ✅ 自动 |
| 分页排序 | ✅ 自动 | ✅ 自动 |
| 搜索筛选 | ✅ 自动 | ✅ 自动 |
| 关联表筛选 | - | ✅ 自动(专栏下拉) |
| 枚举多选 | - | ✅ 自动 |
| 富文本编辑 | - | ✅ 集成 |
| 图片上传 | ✅ | ✅ |
| Excel导出 | ✅ | ✅ |
你写了多少代码?
- 实体类:约150行
- 专栏页面:约30行
- 文章页面:约60行
- 编辑器组件:约50行
总计不到300行代码,一个完整的企业级博客后台就出来了。
没有 Controller、没有 Service、没有 DTO、没有 axios、没有前端路由、没有 loading 处理。
这就是 EasyAdminBlazor 的核心价值:让你专注业务逻辑,而不是重复的 CRUD。
下一篇预告
《Admin Blazor 权限系统:从配置到菜单按钮控制》
讲完 CRUD,下一篇文章讲讲权限控制——怎么配置角色、怎么分配菜单权限、怎么控制按钮显示隐藏。
这些都是后台系统的核心需求,但在 EasyAdminBlazor 里只要点点鼠标即可完成。
🔗 文档:https://easyadmim.wang-zhan.com.cn/doc
🔗 源码:https://gitee.com/gudufy/EasyAdminBlazor
EasyAdminBlazor —— 我自己接单的屠龙刀,现在也给你用。

浙公网安备 33010602011771号