feat: 实现评论系统(后端 + 前端)

- 新增 Comment/CommentMention 模型,Post 新增 CommentsCount 冗余计数器
- 新增 CommentRepo(CRUD + @搜索 LV3+ + 回复统计 + 文章作者查询)
- 新增 CommentService(发表/回复/删除/@解析/通知 NotifyComment/NotifyCommentReply)
- 新增 CommentController(6 个 API 端点),commentUseCase ISP 接口
- 新增评论路由(公开读取 + 需登录写入),依赖注入,错误哨兵,数据库迁移
- 前端评论区 HTML/CSS/JS:分页加载、回复折叠展开、内联回复表单、@提及自动补全
This commit is contained in:
2026-06-01 13:24:16 +08:00
parent b44d7ca5c3
commit b7acc91f8c
15 changed files with 1416 additions and 7 deletions

View File

@ -713,3 +713,241 @@
margin-top: 2px;
font-weight: 500;
}
/* ========== 评论区 ========== */
.comments-section {
max-width: 800px;
margin: 32px auto 0;
}
.comments-title {
font-size: 18px;
font-weight: 600;
color: #111;
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 1px solid #e5e7eb;
}
.comments-count {
color: #6366f1;
}
.comment-form {
margin-bottom: 24px;
}
.comment-input {
width: 100%;
padding: 12px 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
color: #1f2937;
resize: vertical;
font-family: inherit;
box-sizing: border-box;
}
.comment-input:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.comment-submit-btn {
margin-top: 10px;
}
.comment-login-hint {
text-align: center;
padding: 24px;
color: #9ca3af;
font-size: 14px;
}
.comment-login-hint a {
color: #6366f1;
text-decoration: none;
}
.comments-list {
display: flex;
flex-direction: column;
gap: 0;
}
/* 单条评论卡片 */
.comment-card {
padding: 16px 0;
border-bottom: 1px solid #f3f4f6;
}
.comment-card:last-child {
border-bottom: none;
}
.comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.comment-author {
font-size: 14px;
font-weight: 600;
color: #111;
}
.comment-time {
font-size: 12px;
color: #9ca3af;
}
.comment-body {
font-size: 14px;
line-height: 1.7;
color: #374151;
word-break: break-word;
margin-bottom: 8px;
}
.comment-body .reply-to {
color: #6366f1;
margin-right: 6px;
}
.comment-actions {
display: flex;
gap: 16px;
align-items: center;
}
.comment-action-btn {
background: none;
border: none;
font-size: 12px;
color: #9ca3af;
cursor: pointer;
padding: 2px 0;
}
.comment-action-btn:hover {
color: #6366f1;
}
/* 回复列表(内联) */
.comment-replies {
margin-top: 10px;
padding-left: 36px;
border-left: 2px solid #e5e7eb;
}
.comment-reply-card {
padding: 10px 0 10px 16px;
border-bottom: 1px solid #f9fafb;
}
.comment-reply-card:last-child {
border-bottom: none;
padding-bottom: 0;
}
.comment-reply-body {
font-size: 13px;
line-height: 1.6;
color: #4b5563;
}
.comment-reply-to {
color: #6366f1;
margin-right: 4px;
}
/* 展开回复按钮 */
.comment-toggle-replies {
background: none;
border: none;
color: #6366f1;
font-size: 12px;
cursor: pointer;
padding: 4px 0;
margin-top: 4px;
}
.comment-toggle-replies:hover {
text-decoration: underline;
}
/* 回复输入框 */
.comment-reply-form {
display: flex;
gap: 8px;
margin-top: 10px;
padding-left: 36px;
}
.comment-reply-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 13px;
font-family: inherit;
resize: vertical;
}
.comment-reply-input:focus {
outline: none;
border-color: #6366f1;
}
.comment-reply-submit {
padding: 6px 14px;
font-size: 13px;
flex-shrink: 0;
}
/* 加载更多 */
.comments-load-more {
display: block;
margin: 16px auto 0;
}
/* @提及下拉 */
.mention-dropdown {
position: absolute;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
max-height: 240px;
overflow-y: auto;
z-index: 10000;
min-width: 200px;
}
.mention-item {
padding: 8px 14px;
font-size: 13px;
cursor: pointer;
color: #374151;
}
.mention-item:hover,
.mention-item.active {
background: #f5f3ff;
color: #6366f1;
}
.mention-item .mention-username {
font-weight: 500;
}
.mention-item .mention-uid {
color: #9ca3af;
font-size: 11px;
margin-left: 8px;
}