feat(hide): 密码可见隐藏块,发帖双栏预览与门禁体验修复

增加密码解锁与游客签名 cookie;发帖页对齐 1440 并默认双栏预览;修复 locked 解析、按钮对比度与回复聚焦。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 05:46:42 +08:00
parent 01e2fd05ca
commit c44b0efa7d
23 changed files with 911 additions and 132 deletions

View File

@@ -30,6 +30,7 @@ type Handlers struct {
Chat *service.ChatService
Telemetry *service.TelemetryService
Analytics *service.AnalyticsService
HidePwd *service.HidePasswordCookie
}
// resolvePublishStatus 决定新帖/新评的初始状态:

View File

@@ -78,7 +78,8 @@ func (h *Handlers) PostDetail(c *gin.Context) {
viewerID = claims.ID
loadActor = h.actorLoader(claims.ID)
}
post, err := h.Post.GetByIDForViewer(uint(id), viewerID, loadActor)
pwdUnlocked := h.HidePwd.ReadUnlocked(c, uint(id))
post, err := h.Post.GetByIDForViewer(uint(id), viewerID, loadActor, pwdUnlocked)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
return
@@ -189,6 +190,56 @@ func (h *Handlers) UnlockPostContent(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"post": post})
}
// UnlockPostPasswordRequest 密码解锁隐藏块
type UnlockPostPasswordRequest struct {
Password string `json:"password" binding:"required,min=1,max=64"`
}
// UnlockPostPassword 输入密码解锁正文密码隐藏块(游客可用,签名 cookie 记住)
func (h *Handlers) UnlockPostPassword(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"})
return
}
var req UnlockPostPasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请输入密码"})
return
}
matched, err := h.Post.UnlockByPassword(uint(id), req.Password)
if err != nil {
if errors.Is(err, service.ErrPostNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
h.HidePwd.WriteUnlocked(c, uint(id), matched)
claims := middleware.CurrentUser(c)
var viewerID uint
var loadActor func() *service.Actor
if claims != nil {
viewerID = claims.ID
loadActor = h.actorLoader(claims.ID)
}
pwdUnlocked := h.HidePwd.ReadUnlocked(c, uint(id))
for _, i := range matched {
pwdUnlocked[i] = true
}
post, err := h.Post.GetByIDForViewer(uint(id), viewerID, loadActor, pwdUnlocked)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
return
}
if claims != nil {
post.Liked = h.Like.HasLiked(post.ID, claims.ID)
}
c.JSON(http.StatusOK, gin.H{"post": post})
}
// GetPointsBalance 当前用户积分余额
func (h *Handlers) GetPointsBalance(c *gin.Context) {
claims := middleware.CurrentUser(c)