This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/internal/controller/space_controller.go
Victor_Jay c6da3932f3 fix: 修复 Space 模板缺少 formatCount/str 函数导致的启动 panic
- 在 theme/loader.go FuncMap 中添加 formatCount 和 str 函数
- formatCount 将 int64 格式化为中文计数(如 2.5万)
- 控制器 ActiveFolderID 改为 uint 类型,避免模板中 str 类型比较
- 控制器传递 ActiveFolder 对象,移除模板内查找逻辑
- 清理旧模板替换残留的孤儿 HTML/SVG 代码
2026-06-01 21:51:17 +08:00

274 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"net/http"
"strconv"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/service"
"github.com/gin-gonic/gin"
)
// SpaceController 用户空间控制器(统一页面:文章/关注/粉丝/收藏/设置)
type SpaceController struct {
spaceService spaceUseCase
followSvc followUseCase
favoriteSvc favoriteUseCaseForSpace
}
// NewSpaceController 构造函数
func NewSpaceController(spaceService spaceUseCase) *SpaceController {
return &SpaceController{spaceService: spaceService}
}
// WithFollowService 注入关注服务(可选依赖)
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)
if !ok {
common.RedirectToLogin(c)
return
}
c.Redirect(http.StatusFound, "/space/"+strconv.FormatUint(uint64(uid), 10))
}
// 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)
if err != nil {
c.HTML(http.StatusNotFound, "space/index.html", common.BuildPageData(c, gin.H{
"Title": "用户不存在",
"Error": "用户不存在",
"ExtraCSS": "/static/css/space.css",
}))
return
}
spaceUser, err := ctrl.spaceService.GetSpaceUser(uint(uid))
if err != nil || spaceUser == nil {
c.HTML(http.StatusNotFound, "space/index.html", common.BuildPageData(c, gin.H{
"Title": "用户不存在",
"Error": "用户不存在",
"ExtraCSS": "/static/css/space.css",
}))
return
}
// 当前登录用户
currentUID, _, _ := common.GetGinUser(c)
isOwnSpace := currentUID == uint(uid)
// 当前激活的 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), // 暂无阅读量统计
}
// ---- 根据 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
// 默认展示第一个收藏夹的内容
folderParam := c.DefaultQuery("folder", "")
var activeFolderID uint
if folderParam != "" {
fid, parseErr := strconv.ParseUint(folderParam, 10, 64)
if parseErr == nil {
activeFolderID = uint(fid)
}
}
if activeFolderID == 0 && len(folders.Folders) > 0 {
activeFolderID = folders.Folders[0].ID
}
var folderItems *service.FolderItemsResult
if activeFolderID > 0 {
items, itemErr := ctrl.favoriteSvc.ListFolderItems(uid, activeFolderID, 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
// 找到当前激活的收藏夹对象传给模板
var activeFolder *model.Folder
for i := range folders.Folders {
if folders.Folders[i].ID == activeFolderID {
activeFolder = &folders.Folders[i]
break
}
}
data["ActiveFolder"] = activeFolder
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, "隐私设置已更新")
}