Yuan天空
Yuan天空
  • 我的主页
  • 博客随笔
  • 软件作品
    • TK精灵(进程守护)
    • 远程运维助手
    • DicomStoreSCU
    • TKOCR(离线OCR)
    • 更多...
  • 学习资源
  • 网址收藏
  • HTML模板
专注.NET开发技术领域
  1. 主页
  2. 博客
  3. 发布页

基于BootstrapBlazor实现一个用户评论留言板组件效果

网页评论留言板网页评论互动文章评论组件Blazor留言板Blazor评论BootstrapBlazor
Blazor 2025-04-09 24
博客正文

这两天给往网站文章做了个留言板功能,本意是一方面可以给一些网友提供一些帮助,另一方面给网页内容做一些补充,实现了基本的评论嵌套回复功能,还做了后台审核功能。 由于个人网站的备案性质不允许挂评论留言板,所以上线了短暂10分钟后突然意识到不能上就自己默默的下掉了留言互动模块。 但毕竟也是花了不少时间码出来的,所以这里把留言板实现的核心代码部分分享出来(基于blazor实现效果简直不要太简单)

先展示一下效果图 理论上可以多级树状展示

尝试用Blazor实现一个用户评论留言板组件效果

数据库表字段设计部分

字段名称 描述
Id 消息Id
TargetUrl 目标网页地址
Content 用户留言内容
UserId 用户Id对应用户系统
ParentId 父级消息Id(默认为1楼)

C#实体部分 (这部分代码用到Freesql作为数据库orm)

public class GuestBook 
{
    /// <summary>
    /// 消息Id 
    /// </summary>
    public Guid Id { get; set; }
    /// <summary>
    /// 目标网页地址
    /// </summary>
    public string TargetUrl { get; set; }
    /// <summary>
    /// 留言内容
    /// </summary>
    public string Content { get; set; }
    /// <summary>
    /// 发言用户Id
    /// </summary>
    public Guid UserId { get; set; }
    /// <summary>
    /// 用户信息(参考Freesql Navigate导航属性)
    /// </summary>
    [Navigate(nameof(UserId))]
     public UserMember GuestUser { get; set; }
     /// <summary>
     /// 关联父级别消息(即回复目标)
     /// </summary>
     public Guid ParentId { get; set; } = Guid.Empty;
}

写一个用户评论的UI组件 核心在于实现一个递归UI子组件(这里用了 Bootstrap Blazor UI)

  1. 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.Content</div>
            @{
                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"></_DisplayCard>
                            }
                        }
                   </ul>
                }
            }
      </div>
	  <hr class="m-0 border-light" />
  </div>
</li>
  1. UI组件后端代码
@code {
	/// <summary>
	/// 传入当前级的留言
	/// </summary>
	/// <returns></returns>
	[Parameter]
 	public GuestBook Model { get; set; }
	/// <summary>
	/// 传入当前级的留言包含的子留言
	/// </summary>
	/// <returns></returns>
	private IEnumerable<GuestBook> Childs { get; set; }
    /// <summary>
	/// 本页对应的所有留言信息
	/// </summary>
	/// <returns></returns>
	[Parameter]
	public List<GuestBook> AllDatas { get; set; }
	/// <summary>
	/// 实现数据双向绑定
	/// </summary>
	/// <returns></returns>
    [Parameter]
    public EventCallback<List<GuestBook>> 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);
		}
	}
	/// <summary>
	/// 选择项目变化事件
	/// </summary>
	/// <returns></returns>
	async Task OnSelectedItemsChangedAsync(GuestBook guestBook)
	{
		await OnReplyChanged.InvokeAsync(guestBook);
	}
	/// <summary>
	/// 回复点选目标事件
	/// </summary>
	/// <returns></returns>
	[Parameter]
	public EventCallback<GuestBook> 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"><iclass="fa fa-soild fa-trash"></i></Button>
							</div>
						</div>
					}
				}
	<BootstrapInput @bind-Value="Content" IsTrim="true" IsAutoFocus="true" Clearable="true" PlaceHolder="输入留言内容"></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"></_DisplayCard>
				}
			}
			</ul>
		} else {
				<div class="text-center text-warning">
 					来都来了,要不留个言?
				</div>
			  	}
		}
		</div>
		<div class="">
		      <!--分页组件-->
			<Pagination PageCount="TotalPages" PageIndex="PageIndex" OnPageLinkClick="@((e)=>OnQueryAsync(e))" Alignment="Alignment.Center"></Pagination>
		</div>
	</div>
	}
}

文章结束

代码本质上就是一个组件循环递归嵌套算法逻辑。

同类文章
个人使用Blazor与BootstrapBlazor重构网站的开发体验太棒了
Loading...
个人使用Blazor与BootstrapBlazor重构网站的开发体验太棒了
问题反馈/学习建议
1. 文明上网,理性表达,营造舒适的学习氛围
2. 请不要反馈提交与本页主题无关内容
标题目录
  • 先展示一下效果图 理论上可以多级树状展示
  • 数据库表字段设计部分
  • C#实体部分 (这部分代码用到Freesql作为数据库orm)
  • 写一个用户评论的UI组件 核心在于实现一个递归UI子组件(这里用了 Bootstrap Blazor UI)
  • 实现留言板列表页(此处只列UI前端部分代码)
  • 文章结束