feat: 添加用户个人资料编辑功能,优化页面布局与导航
1. 后端新增用户资料更新接口,支持修改昵称、头像、邮箱和签名 2. 前端新增资料编辑表单页面,适配响应式布局 3. 优化帖子详情页布局与移动端适配逻辑 4. 重构首页侧边栏导航,使用卡片式板块展示 5. 移除全局板块缓存,调整Header导航移除板块菜单 6. 补充用户签名展示功能,完善用户信息返回字段
This commit is contained in:
@@ -224,11 +224,13 @@ func (h *Handlers) ChangePassword(c *gin.Context) {
|
||||
// meUserBody 统一的用户信息响应体(Me/Login/Refresh 共用形状)
|
||||
func meUserBody(user *model.User) gin.H {
|
||||
return gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"avatar": user.Avatar,
|
||||
"role": user.Role,
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"avatar": user.Avatar,
|
||||
"signature": user.Signature,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,43 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UpdateProfileRequest 更新资料请求
|
||||
type UpdateProfileRequest struct {
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Email string `json:"email"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// UpdateProfile 更新当前登录用户的资料(昵称、头像、邮箱、签名)
|
||||
func (h *Handlers) UpdateProfile(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
var req UpdateProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数格式错误"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.Auth.UpdateProfile(claims.ID, req.Nickname, req.Avatar, req.Email, req.Signature)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的完整资料(含 email/signature,供前端刷新展示)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"avatar": user.Avatar,
|
||||
"signature": user.Signature,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// UserProfile 获取用户资料:基本信息 + 统计 + 发帖列表(分页)
|
||||
func (h *Handlers) UserProfile(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
@@ -58,6 +95,7 @@ func (h *Handlers) UserProfile(c *gin.Context) {
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"avatar": user.Avatar,
|
||||
"signature": user.Signature,
|
||||
"role": user.Role,
|
||||
"created_at": user.CreatedAt,
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ type User struct {
|
||||
Password string `gorm:"size:128;not null" json:"-"`
|
||||
Nickname string `gorm:"size:64" json:"nickname"`
|
||||
Avatar string `gorm:"size:512" json:"avatar"`
|
||||
Signature string `gorm:"size:255;default:''" json:"signature"` // 个性签名
|
||||
Role Role `gorm:"size:16;default:user" json:"role"`
|
||||
Banned bool `gorm:"default:false" json:"banned"`
|
||||
TokenVersion int `gorm:"default:0" json:"-"` // token 版本号,改密码/封禁时递增使旧 JWT 失效
|
||||
|
||||
@@ -92,6 +92,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
{
|
||||
api.POST("/logout", h.Logout)
|
||||
api.POST("/change-password", h.ChangePassword)
|
||||
api.PUT("/profile", h.UpdateProfile)
|
||||
api.POST("/posts", middleware.RateLimitMiddleware(limiter, service.RatePost), h.CreatePost)
|
||||
api.PUT("/posts/:id", h.UpdatePost)
|
||||
api.DELETE("/posts/:id", h.DeletePost)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/model"
|
||||
@@ -491,3 +492,47 @@ func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword strin
|
||||
// 递增 token_version,使所有旧 JWT 和 refresh token 失效
|
||||
return s.IncrementTokenVersion(userID)
|
||||
}
|
||||
|
||||
// UpdateProfile 更新用户资料(昵称、头像、邮箱、签名)
|
||||
// - nickname 非空且不超过 64 字符
|
||||
// - email 可为空;非空时需符合邮箱格式且不与他人重复
|
||||
// - avatar 可为空 URL
|
||||
// - signature 不超过 255 字符
|
||||
func (s *AuthService) UpdateProfile(userID uint, nickname, avatar, email, signature string) (*model.User, error) {
|
||||
nickname = strings.TrimSpace(nickname)
|
||||
if nickname == "" {
|
||||
return nil, errors.New("昵称不能为空")
|
||||
}
|
||||
if len(nickname) > 64 {
|
||||
return nil, errors.New("昵称不能超过 64 个字符")
|
||||
}
|
||||
email = strings.TrimSpace(email)
|
||||
if email != "" {
|
||||
if !strings.Contains(email, "@") || len(email) > 128 {
|
||||
return nil, errors.New("邮箱格式不正确")
|
||||
}
|
||||
// 邮箱唯一性校验(排除自身)
|
||||
var count int64
|
||||
s.db.Model(&model.User{}).Where("email = ? AND id <> ?", email, userID).Count(&count)
|
||||
if count > 0 {
|
||||
return nil, errors.New("该邮箱已被使用")
|
||||
}
|
||||
}
|
||||
if len(avatar) > 512 {
|
||||
return nil, errors.New("头像链接过长")
|
||||
}
|
||||
if len(signature) > 255 {
|
||||
return nil, errors.New("签名不能超过 255 个字符")
|
||||
}
|
||||
|
||||
updates := map[string]any{
|
||||
"nickname": nickname,
|
||||
"avatar": avatar,
|
||||
"email": email,
|
||||
"signature": signature,
|
||||
}
|
||||
if err := s.db.Model(&model.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetUserByID(userID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user