feat: 实现评论系统(后端 + 前端)
- 新增 Comment/CommentMention 模型,Post 新增 CommentsCount 冗余计数器 - 新增 CommentRepo(CRUD + @搜索 LV3+ + 回复统计 + 文章作者查询) - 新增 CommentService(发表/回复/删除/@解析/通知 NotifyComment/NotifyCommentReply) - 新增 CommentController(6 个 API 端点),commentUseCase ISP 接口 - 新增评论路由(公开读取 + 需登录写入),依赖注入,错误哨兵,数据库迁移 - 前端评论区 HTML/CSS/JS:分页加载、回复折叠展开、内联回复表单、@提及自动补全
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user