四、新闻后台实战:用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 —— 我自己接单的屠龙刀,现在也给你用。

posted @ 2026-05-29 09:17  gudufy  阅读(22)  评论(0)    收藏  举报