feat: 新增点踩功能,列表卡片和详情页支持 👍/👎 投票
- 后端: 新增 dislike_count 字段(模型/Schema/数据库迁移)
- 后端: 新增 POST /api/jokes/{id}/dislike 端点
- 后端: 管理后台统计新增 total_dislikes
- 前端: 新增 useVote 组合函数(localStorage 持久化防重复)
- 前端: JokeCard 列表卡片新增 👍/👎 可点击投票按钮
- 前端: 详情页 ❤️ 改为 👍 赞一下 / 👎 踩一脚 双按钮
This commit is contained in:
@@ -25,6 +25,7 @@ class Joke(Base):
|
||||
status = Column(String(20), default="pending")
|
||||
view_count = Column(Integer, default=0)
|
||||
like_count = Column(Integer, default=0)
|
||||
dislike_count = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ def joke_to_response(joke: Joke, db: Session = None) -> JokeResponse:
|
||||
status=joke.status,
|
||||
view_count=joke.view_count,
|
||||
like_count=joke.like_count,
|
||||
dislike_count=joke.dislike_count,
|
||||
created_at=joke.created_at,
|
||||
updated_at=joke.updated_at,
|
||||
type_names=type_names,
|
||||
@@ -224,6 +225,7 @@ def admin_stats(
|
||||
rejected_jokes = db.query(Joke).filter(Joke.status == "rejected").count()
|
||||
total_views = db.query(Joke).with_entities(func.sum(Joke.view_count)).scalar() or 0
|
||||
total_likes = db.query(Joke).with_entities(func.sum(Joke.like_count)).scalar() or 0
|
||||
total_dislikes = db.query(Joke).with_entities(func.sum(Joke.dislike_count)).scalar() or 0
|
||||
return {
|
||||
"total_jokes": total_jokes,
|
||||
"approved_jokes": approved_jokes,
|
||||
@@ -231,6 +233,7 @@ def admin_stats(
|
||||
"rejected_jokes": rejected_jokes,
|
||||
"total_views": total_views,
|
||||
"total_likes": total_likes,
|
||||
"total_dislikes": total_dislikes,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ def joke_to_response(joke: Joke, type_map: dict = None, crowd_map: dict = None)
|
||||
status=joke.status,
|
||||
view_count=joke.view_count,
|
||||
like_count=joke.like_count,
|
||||
dislike_count=joke.dislike_count,
|
||||
created_at=joke.created_at,
|
||||
updated_at=joke.updated_at,
|
||||
type_names=type_names,
|
||||
@@ -207,10 +208,8 @@ def get_random_joke(db: Session = Depends(get_db)):
|
||||
return joke_to_response(joke, type_map, crowd_map)
|
||||
|
||||
|
||||
@router.post("/{joke_id}/like")
|
||||
def like_joke(joke_id: int, db: Session = Depends(get_db)):
|
||||
"""为笑话点赞(仅允许已审核通过的笑话)"""
|
||||
# 先检查笑话是否存在且已审核
|
||||
def _vote_joke(joke_id: int, field: str, db: Session):
|
||||
"""通用投票逻辑:点赞或点踩(仅允许已审核通过的笑话)"""
|
||||
joke = db.query(Joke).filter(
|
||||
Joke.id == joke_id,
|
||||
Joke.status == "approved"
|
||||
@@ -218,8 +217,20 @@ def like_joke(joke_id: int, db: Session = Depends(get_db)):
|
||||
if not joke:
|
||||
raise HTTPException(status_code=404, detail="笑话不存在")
|
||||
# 使用原子更新避免并发竞态
|
||||
db.query(Joke).filter(Joke.id == joke_id).update({Joke.like_count: Joke.like_count + 1})
|
||||
update_data = {getattr(Joke, field): getattr(Joke, field) + 1}
|
||||
db.query(Joke).filter(Joke.id == joke_id).update(update_data)
|
||||
db.commit()
|
||||
# 获取更新后的值
|
||||
db.refresh(joke)
|
||||
return {"message": "点赞成功", "like_count": joke.like_count}
|
||||
return {"message": "操作成功", field: getattr(joke, field)}
|
||||
|
||||
|
||||
@router.post("/{joke_id}/like")
|
||||
def like_joke(joke_id: int, db: Session = Depends(get_db)):
|
||||
"""为笑话点赞(仅允许已审核通过的笑话)"""
|
||||
return _vote_joke(joke_id, "like_count", db)
|
||||
|
||||
|
||||
@router.post("/{joke_id}/dislike")
|
||||
def dislike_joke(joke_id: int, db: Session = Depends(get_db)):
|
||||
"""为笑话点踩(仅允许已审核通过的笑话)"""
|
||||
return _vote_joke(joke_id, "dislike_count", db)
|
||||
@@ -34,6 +34,7 @@ class JokeResponse(JokeBase):
|
||||
ai_level: str | None = None
|
||||
view_count: int
|
||||
like_count: int
|
||||
dislike_count: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
type_names: list[str] | None = None
|
||||
|
||||
Reference in New Issue
Block a user