这两天给往网站文章做了个留言板功能,本意是一方面可以给一些网友提供一些帮助,另一方面给网页内容做一些补充,实现了基本的评论嵌套回复功能,还做了后台审核功能。 由于个人网站的备案性质不允许挂评论留言板,所以上线了短暂10分钟后突然意识到不能上就自己默默的下掉了留言互动模块。 但毕竟也是花了不少时间码出来的,所以这里把留言板实现的核心代码部分分享出来(基于blazor实现效果简直不要太简单)
先展示一下效果图 理论上可以多级树状展示
数据库表字段设计部分
| 字段名称 | 描述 |
|---|---|
| Id | 消息Id |
| TargetUrl | 目标网页地址 |
| Content | 用户留言内容 |
| UserId | 用户Id对应用户系统 |
| ParentId | 父级消息Id(默认为1楼) |
C#实体部分 (这部分代码用到Freesql作为数据库orm)
public class GuestBook { ////// 消息Id /// public Guid Id { get; set; } /// /// 目标网页地址 /// public string TargetUrl { get; set; } /// /// 留言内容 /// public string Content { get; set; } /// /// 发言用户Id /// public Guid UserId { get; set; } /// /// 用户信息(参考Freesql Navigate导航属性) /// [Navigate(nameof(UserId))] public UserMember GuestUser { get; set; } /// /// 关联父级别消息(即回复目标) /// public Guid ParentId { get; set; } = Guid.Empty; }
写一个用户评论的UI组件 核心在于实现一个递归UI子组件
(这里用了 Bootstrap Blazor UI)
- UI前台混合代码(比较简单)
<li class="mt-3"> <hr /> <div class="row g-2"> <div class="col-auto"> <Avata Size="Size.Medium" Url="@Model.GuestUser.ProfilePicture">Avatar> div> <div class="col"> <div> <div class="row"> <div class="col-6"> @{ if (Model.GuestUser.Type == "管理员") { <span class="text-info me-2">站长span> } else { if (string.IsNullOrWhiteSpace(Model.GuestUser.UserName) || Model.GuestUser.UserName.Length<=5) { <span class="text-info me-2">某某网友span> } else{ <span class="text-info me-2">*****@Model.GuestUser.UserName.Substring(5)span> } } } div> <div class="col-6 text-end"> @{ if (ShowReply) { <Anchor Target="anchor1"> <Butto Size="Size.ExtraSmall" OnClick="@(()=>OnSelectedItemsChangedAsync(Model))">回复Button> Anchor> } } <span class="small ms-2">发布于: @Model.CreateTime.ToString("yyyy-MM-dd")span> div> div> div> <div>@Model.Contentdiv> @{ if (Childs.HasItems()) { //这里实现一个循环递归 渲染出树形结构 <ul class="list-unstyled mb-0 border-top-1 border-light"> @{ foreach (var msg in Childs) { <_DisplayCard Model="msg" @bind-AllDatas="AllDatas" OnReplyChanged="OnReplyChanged" ShowReply="ShowReply"> } } ul> } } div> <hr class="m-0 border-light" /> div> li>
- UI组件后端代码
@code {
///
/// 传入当前级的留言
///
///
[Parameter]
public GuestBook Model { get; set; }
///
/// 传入当前级的留言包含的子留言
///
///
private IEnumerable Childs { get; set; }
///
/// 本页对应的所有留言信息
///
///
[Parameter]
public List AllDatas { get; set; }
///
/// 实现数据双向绑定
///
///
[Parameter]
public EventCallback> AllDatasChanged { get; set; }
[Parameter]
public bool ShowReply { get; set; }
protected override async Task OnParametersSetAsync()
{
if (AllDatas.HasItems())
{
Childs = AllDatas.Where(e => e.ParentId == Model.Id);
}
}
///
/// 选择项目变化事件
///
///
async Task OnSelectedItemsChangedAsync(GuestBook guestBook)
{
await OnReplyChanged.InvokeAsync(guestBook);
}
///
/// 回复点选目标事件
///
///
[Parameter]
public EventCallback OnReplyChanged { get; set; }
}
实现留言板列表页(此处只列UI前端部分代码)
@{
if (this.LoginUser is not null)
{
<div class="container">
<div class="pt-3" id="anchor1">
<div class="mb-3">
@{
//ParentReplyMessage 对应要回复消息
if (ParentReplyMessage is not null)
{
<div class="row">
<div class="col">
<div class="alert alert-info p-2 border-0">
回复 @ParentReplyMessage.GuestUser.UserName @ParentReplyMessage.CreateTime.ToString("yyyy/MM/dd") : @ParentReplyMessage.Content
div>
div>
<div class="col-auto">
<Button Size="Size.ExtraSmall" OnClick="OnDismissAsync" Color="Color.Secondary">i>Button>
div>
div>
}
}
BootstrapInput>
div>
<div class="text-center mb-3">
<Button Size="Size.Small" OnClick="SubmitContentAsync" IsKeepDisabled="true"><i class="fa fa-soild fa-message me-1">i>提交留言Button>
div>
div>
<h6 class="fw-bold">网友留言:h6>
<div class="mb-3">
@{
//ParentItems 对应第一级的留言列表
if (this.ParentItems.HasItems())
{
<ul class="list-unstyled mb-0">
@{
foreach (var msg in ParentItems)
{
<_DisplayCard Model="msg" @bind-AllDatas="AllDatas" OnReplyChanged="@((e)=>OnReplyChanged(e))" ShowReply="true">
}
}
ul>
} else {
<div class="text-center text-warning">
来都来了,要不留个言?
div>
}
}
div>
<Pagination PageCount="TotalPages" PageIndex="PageIndex" OnPageLinkClick="@((e)=>OnQueryAsync(e))" Alignment="Alignment.Center">Pagination>
div>
div>
}
}
文章结束
代码本质上就是一个组件循环递归嵌套算法逻辑。