feat: @提及功能优化 + 默认头像改用SVG图标

- 评论区有效@提及渲染为蓝色链接跳转/space/{uid},无效@保持纯文本
- 输入@触发悬浮窗,根据输入实时搜索用户(含头像+等级)
- 无匹配时显示未搜索到用户,选中后插入为@用户名 格式(尾部空格)
- 悬浮窗定位于输入光标上方,不遮挡输入内容
- 后端Comment新增Mentions字段,批量加载提及映射供前端判断
- SearchUsers/parseMentions 统一限制LV2+用户
- 所有默认头像由首字符文字改为统一SVG人形图标(nav/home/space/mention等7处)
This commit is contained in:
2026-06-03 00:49:24 +08:00
parent 2d05da6709
commit 4ac4f64f33
12 changed files with 249 additions and 57 deletions

View File

@ -17,15 +17,62 @@ func (r *CommentRepo) FindMentionsByComment(commentID uint) ([]model.CommentMent
return list, err
}
// SearchUsersByLevel 搜索指定等级及以上用户(用于@艾特),返回 uid/username/exp
// LoadMentionsForComments 批量加载多条评论的有效@提及映射username -> uid
// 传入的 comments 会被原地修改,填充 Mentions 字段
func (r *CommentRepo) LoadMentionsForComments(comments []model.Comment) error {
if len(comments) == 0 {
return nil
}
commentIDs := make([]uint, len(comments))
for i, c := range comments {
commentIDs[i] = c.ID
}
// 查询 comment_mentions JOIN users拿到 username 和 uid
type mentionRow struct {
CommentID uint
UID uint
Username string
}
var rows []mentionRow
err := r.db.Table("comment_mentions").
Select("comment_mentions.comment_id, comment_mentions.uid, users.username").
Joins("LEFT JOIN users ON users.id = comment_mentions.uid").
Where("comment_mentions.comment_id IN ?", commentIDs).
Find(&rows).Error
if err != nil {
return err
}
// 按 comment_id 分组
mentionMap := make(map[uint]map[string]uint)
for _, row := range rows {
if mentionMap[row.CommentID] == nil {
mentionMap[row.CommentID] = make(map[string]uint)
}
mentionMap[row.CommentID][row.Username] = row.UID
}
for i := range comments {
if m, ok := mentionMap[comments[i].ID]; ok {
comments[i].Mentions = m
}
}
return nil
}
// SearchUsersByLevel 搜索指定等级及以上用户(用于@艾特),返回 uid/username/avatar/exp
func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel int, limit int) ([]struct {
UID uint
Username string
Avatar string
Exp int
}, error) {
type result struct {
UID uint
Username string
Avatar string
Exp int
}
var list []result
@ -33,7 +80,7 @@ func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel int, limit int
threshold := model.LevelThresholds[minLevel]
err := r.db.Table("users").
Select("id, username, exp").
Select("id, username, avatar, exp").
Where("username LIKE ? AND exp >= ? AND deleted_at IS NULL", "%"+keyword+"%", threshold).
Order("exp DESC").
Limit(limit).
@ -42,14 +89,16 @@ func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel int, limit int
var res []struct {
UID uint
Username string
Avatar string
Exp int
}
for _, u := range list {
res = append(res, struct {
UID uint
Username string
Avatar string
Exp int
}{UID: u.UID, Username: u.Username, Exp: u.Exp})
}{UID: u.UID, Username: u.Username, Avatar: u.Avatar, Exp: u.Exp})
}
return res, err
}