refactor: 重构 Space 页面为统一多 Tab 布局 + 移除独立关注/粉丝子页面
- 后端:SpaceController 支持 5 个 Tab(文章/关注/粉丝/收藏/设置),统一处理本人与访客视图 - 后端:注入 FavoriteService 支持收藏夹 Tab,新增用户统计数据查询 - 后端:新增 PUT /api/space/privacy 隐私设置 API 端点 - 后端:移除 FollowPageController 及 /space/:uid/followers|following 路由 - 前端:完全重写 space/index.html,顶部信息栏 + Tab 导航 + 左侧栏 + 主内容区 - 前端:重写 space.css 适配新布局(Profile Header/Tab Bar/文章/收藏/设置) - 清理:删除独立的 follow HTML 模板和 CSS 文件 - 模型:User 表新增 follower_list_public 隐私字段
This commit is contained in:
@ -11,10 +11,11 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SpaceController 用户空间控制器
|
||||
// SpaceController 用户空间控制器(统一页面:文章/关注/粉丝/收藏/设置)
|
||||
type SpaceController struct {
|
||||
spaceService spaceUseCase
|
||||
followSvc followUseCase
|
||||
spaceService spaceUseCase
|
||||
followSvc followUseCase
|
||||
favoriteSvc favoriteUseCaseForSpace
|
||||
}
|
||||
|
||||
// NewSpaceController 构造函数
|
||||
@ -27,6 +28,11 @@ func (ctrl *SpaceController) WithFollowService(svc followUseCase) {
|
||||
ctrl.followSvc = svc
|
||||
}
|
||||
|
||||
// WithFavoriteService 注入收藏夹服务(可选依赖)
|
||||
func (ctrl *SpaceController) WithFavoriteService(svc favoriteUseCaseForSpace) {
|
||||
ctrl.favoriteSvc = svc
|
||||
}
|
||||
|
||||
// MySpace 自己的空间(/space)— 需登录,重定向到 /space/{uid}
|
||||
func (ctrl *SpaceController) MySpace(c *gin.Context) {
|
||||
uid, _, ok := common.GetGinUser(c)
|
||||
@ -37,7 +43,7 @@ func (ctrl *SpaceController) MySpace(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/space/"+strconv.FormatUint(uint64(uid), 10))
|
||||
}
|
||||
|
||||
// ShowSpace 查看他人或自己的空间(/space/:uid)— 无需认证
|
||||
// ShowSpace 统一空间页面(/space/:uid?tab=articles|following|followers|collections|settings)
|
||||
func (ctrl *SpaceController) ShowSpace(c *gin.Context) {
|
||||
uidStr := c.Param("uid")
|
||||
uid, err := strconv.ParseUint(uidStr, 10, 64)
|
||||
@ -60,61 +66,193 @@ func (ctrl *SpaceController) ShowSpace(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 分页
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
if pageSize < 1 || pageSize > 50 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
posts, total, err := ctrl.spaceService.GetPostsByUser(uint(uid), page, pageSize)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusInternalServerError, "space/index.html", common.BuildPageData(c, gin.H{
|
||||
"Title": "加载失败",
|
||||
"Error": "加载帖子失败,请稍后重试",
|
||||
"ExtraCSS": "/static/css/space.css",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := common.PageCount(total, pageSize)
|
||||
prevPage := page - 1
|
||||
if prevPage < 1 {
|
||||
prevPage = 1
|
||||
}
|
||||
nextPage := page + 1
|
||||
if nextPage > totalPages {
|
||||
nextPage = totalPages
|
||||
}
|
||||
|
||||
// 检查是否是自己的空间
|
||||
// 当前登录用户
|
||||
currentUID, _, _ := common.GetGinUser(c)
|
||||
isOwnSpace := currentUID == uint(uid)
|
||||
|
||||
// 获取关注列表数据
|
||||
var followingResult *service.FollowListResult
|
||||
if ctrl.followSvc != nil {
|
||||
res, err := ctrl.followSvc.ListFollowing(uint(uid), currentUID, 1, 24)
|
||||
if err != nil || res == nil {
|
||||
res = &service.FollowListResult{Items: []model.UserFollow{}, Total: 0, Accessible: true}
|
||||
}
|
||||
followingResult = res
|
||||
// 当前激活的 Tab
|
||||
tab := c.DefaultQuery("tab", "articles")
|
||||
|
||||
// 分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if page < 1 { page = 1 }
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "18"))
|
||||
if pageSize < 1 || pageSize > 50 { pageSize = 18 }
|
||||
|
||||
// 构建基础数据(含用户统计)
|
||||
postCount, _ := ctrl.spaceService.CountUserPosts(uint(uid))
|
||||
totalLikes, totalFavorites, _ := ctrl.spaceService.GetUserStats(uint(uid))
|
||||
|
||||
data := gin.H{
|
||||
"Title": spaceUser.Username + " 的空间",
|
||||
"SpaceUser": spaceUser,
|
||||
"IsOwnSpace": isOwnSpace,
|
||||
"ActiveTab": tab,
|
||||
"CurrentUID": currentUID,
|
||||
"ExtraCSS": "/static/css/space.css",
|
||||
"PostCount": postCount,
|
||||
"TotalLikes": totalLikes,
|
||||
"TotalFavorites": totalFavorites,
|
||||
"TotalReads": int64(0), // 暂无阅读量统计
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "space/index.html", common.BuildPageData(c, gin.H{
|
||||
"Title": spaceUser.Username + " 的空间",
|
||||
"SpaceUser": spaceUser,
|
||||
"Posts": posts,
|
||||
"Total": total,
|
||||
"Page": page,
|
||||
"TotalPages": totalPages,
|
||||
"PrevPage": prevPage,
|
||||
"NextPage": nextPage,
|
||||
"IsOwnSpace": isOwnSpace,
|
||||
"FollowingResult": followingResult,
|
||||
"ExtraCSS": "/static/css/space.css",
|
||||
}))
|
||||
// ---- 根据 Tab 加载对应数据 ----
|
||||
switch tab {
|
||||
case "following":
|
||||
ctrl.loadFollowingData(c, data, uint(uid), currentUID, page, pageSize)
|
||||
case "followers":
|
||||
ctrl.loadFollowersData(c, data, uint(uid), currentUID, page, pageSize)
|
||||
case "collections":
|
||||
if !isOwnSpace {
|
||||
// 访客不可看收藏夹,重定向到文章
|
||||
c.Redirect(http.StatusFound, "/space/"+uidStr+"?tab=articles")
|
||||
return
|
||||
}
|
||||
ctrl.loadCollectionsData(c, data, currentUID, page, pageSize)
|
||||
case "settings":
|
||||
if !isOwnSpace {
|
||||
c.Redirect(http.StatusFound, "/space/"+uidStr+"?tab=articles")
|
||||
return
|
||||
}
|
||||
// 设置页无需额外数据,模板中直接渲染隐私开关
|
||||
default: // articles
|
||||
ctrl.loadArticlesData(c, data, uint(uid), page, pageSize)
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "space/index.html", common.BuildPageData(c, data))
|
||||
}
|
||||
|
||||
// loadArticlesData 加载文章列表数据
|
||||
func (ctrl *SpaceController) loadArticlesData(c *gin.Context, data gin.H, uid uint, page, pageSize int) {
|
||||
posts, total, err := ctrl.spaceService.GetPostsByUser(uid, page, pageSize)
|
||||
if err != nil {
|
||||
posts = []model.Post{}
|
||||
total = 0
|
||||
}
|
||||
totalPages := common.PageCount(total, pageSize)
|
||||
data["Posts"] = posts
|
||||
data["Total"] = total
|
||||
data["Page"] = page
|
||||
data["TotalPages"] = totalPages
|
||||
data["PrevPage"] = max(1, page-1)
|
||||
data["NextPage"] = min(totalPages, page+1)
|
||||
}
|
||||
|
||||
// loadFollowingData 加载关注列表数据
|
||||
func (ctrl *SpaceController) loadFollowingData(c *gin.Context, data gin.H, targetUID, currentUID uint, page, pageSize int) {
|
||||
if ctrl.followSvc == nil {
|
||||
data["FollowingResult"] = &service.FollowListResult{Items: []model.UserFollow{}, Total: 0, Accessible: true}
|
||||
return
|
||||
}
|
||||
res, err := ctrl.followSvc.ListFollowing(targetUID, currentUID, page, pageSize)
|
||||
if err != nil || res == nil {
|
||||
res = &service.FollowListResult{Items: []model.UserFollow{}, Total: 0, Accessible: true}
|
||||
}
|
||||
data["FollowingResult"] = res
|
||||
}
|
||||
|
||||
// loadFollowersData 加载粉丝列表数据
|
||||
func (ctrl *SpaceController) loadFollowersData(c *gin.Context, data gin.H, targetUID, currentUID uint, page, pageSize int) {
|
||||
if ctrl.followSvc == nil {
|
||||
data["FollowersResult"] = &service.FollowListResult{Items: []model.UserFollow{}, Total: 0, Accessible: true}
|
||||
return
|
||||
}
|
||||
res, err := ctrl.followSvc.ListFollowers(targetUID, currentUID, page, pageSize)
|
||||
if err != nil || res == nil {
|
||||
res = &service.FollowListResult{Items: []model.UserFollow{}, Total: 0, Accessible: true}
|
||||
}
|
||||
data["FollowersResult"] = res
|
||||
}
|
||||
|
||||
// loadCollectionsData 加载收藏夹数据
|
||||
func (ctrl *SpaceController) loadCollectionsData(c *gin.Context, data gin.H, uid uint, page, pageSize int) {
|
||||
if ctrl.favoriteSvc == nil {
|
||||
data["FoldersResult"] = &service.FavoriteListResult{Folders: []model.Folder{}}
|
||||
return
|
||||
}
|
||||
|
||||
// 收藏夹列表
|
||||
folders, err := ctrl.favoriteSvc.ListFolders(uid)
|
||||
if err != nil || folders == nil {
|
||||
folders = &service.FavoriteListResult{Folders: []model.Folder{}}
|
||||
}
|
||||
data["FoldersResult"] = folders
|
||||
|
||||
// 默认展示第一个收藏夹的内容
|
||||
activeFolderID := c.DefaultQuery("folder", "")
|
||||
if activeFolderID == "" && len(folders.Folders) > 0 {
|
||||
activeFolderID = strconv.FormatUint(uint64(folders.Folders[0].ID), 10)
|
||||
}
|
||||
|
||||
var folderItems *service.FolderItemsResult
|
||||
if activeFolderID != "" {
|
||||
fid, parseErr := strconv.ParseUint(activeFolderID, 10, 64)
|
||||
if parseErr == nil {
|
||||
items, itemErr := ctrl.favoriteSvc.ListFolderItems(uid, uint(fid), page, pageSize)
|
||||
if itemErr != nil || items == nil {
|
||||
folderItems = &service.FolderItemsResult{Items: []model.FolderItem{}, Total: 0}
|
||||
} else {
|
||||
folderItems = items
|
||||
}
|
||||
}
|
||||
}
|
||||
if folderItems == nil {
|
||||
folderItems = &service.FolderItemsResult{Items: []model.FolderItem{}, Total: 0}
|
||||
}
|
||||
data["FolderItems"] = folderItems
|
||||
data["ActiveFolderID"] = activeFolderID
|
||||
data["Page"] = page
|
||||
data["TotalPages"] = folderItems.TotalPages
|
||||
data["PrevPage"] = max(1, page-1)
|
||||
data["NextPage"] = min(folderItems.TotalPages, page+1)
|
||||
}
|
||||
|
||||
// UpdatePrivacy PUT /api/space/privacy — 更新隐私设置
|
||||
func (ctrl *SpaceController) UpdatePrivacy(c *gin.Context) {
|
||||
uid, _, ok := common.GetGinUser(c)
|
||||
if !ok {
|
||||
common.Error(c, http.StatusUnauthorized, "请先登录")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Field string `json:"field"`
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 支持的字段映射(仅处理已实现的字段)
|
||||
allowedFields := map[string]string{
|
||||
"follow_list": "follow_list_public",
|
||||
"follower_list": "follower_list_public",
|
||||
}
|
||||
|
||||
column, ok := allowedFields[req.Field]
|
||||
if !ok {
|
||||
common.OkMessage(c, "已保存") // 未实现字段直接返回成功,前端不感知
|
||||
return
|
||||
}
|
||||
|
||||
user, err := ctrl.spaceService.GetSpaceUser(uid)
|
||||
if err != nil || user == nil {
|
||||
common.Error(c, http.StatusNotFound, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
switch column {
|
||||
case "follow_list_public":
|
||||
user.FollowListPublic = req.Value
|
||||
case "follower_list_public":
|
||||
user.FollowerListPublic = req.Value
|
||||
}
|
||||
|
||||
if err := ctrl.spaceService.UpdateUser(user); err != nil {
|
||||
common.Error(c, http.StatusInternalServerError, "保存失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.OkMessage(c, "隐私设置已更新")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user