Просмотр исходного кода

添加点赞/点踩加积分功能

zkn 3 недель назад
Родитель
Сommit
c296e5e3a5

+ 4 - 0
.gitignore

@@ -54,3 +54,7 @@ shudao-go-backend/views/index.html
 .npm-cache
 
 shudao-vue-frontend/.playwright-cli/
+
+# Local regression tests not required for runtime deployment
+shudao-vue-frontend/src/views/Chat.feedbackPoints.test.js
+shudao-chat-py/tests/test_like_feedback_points.py

+ 97 - 13
shudao-chat-py/routers/total.py

@@ -4,7 +4,7 @@ from sqlalchemy.orm import Session
 from pydantic import BaseModel
 from typing import Optional
 from database import get_db
-from models.total import RecommendQuestion, PolicyFile, FunctionCard, HotQuestion, FeedbackQuestion
+from models.total import RecommendQuestion, PolicyFile, FunctionCard, HotQuestion, FeedbackQuestion, User
 from models.chat import AIMessage
 from models.user_data import UserData
 from services.oss_service import oss_service
@@ -170,25 +170,109 @@ async def submit_feedback(request: SubmitFeedbackRequest, req: Request, db: Sess
 
 
 class LikeDislikeRequest(BaseModel):
-    ai_message_id: int
-    action: str  # "like" 或 "dislike"
+    ai_message_id: Optional[int] = None
+    action: Optional[str] = None
+    id: Optional[int] = None
+    user_feedback: Optional[int] = None
+
+
+FEEDBACK_NONE = 0
+FEEDBACK_LIKE = 2
+FEEDBACK_DISLIKE = 3
+FEEDBACK_REWARD_POINTS = {
+    FEEDBACK_NONE: 0,
+    FEEDBACK_LIKE: 2,
+    FEEDBACK_DISLIKE: 1,
+}
+
+
+def _resolve_like_dislike_payload(data: LikeDislikeRequest):
+    message_id = data.ai_message_id or data.id
+    if not message_id:
+        return None, None, "缺少消息ID"
+
+    if data.user_feedback is not None:
+        feedback = int(data.user_feedback)
+    else:
+        action = (data.action or "").strip().lower()
+        if action in ("like", "2"):
+            feedback = FEEDBACK_LIKE
+        elif action in ("dislike", "3"):
+            feedback = FEEDBACK_DISLIKE
+        elif action in ("", "none", "cancel", "0"):
+            feedback = FEEDBACK_NONE
+        else:
+            return None, None, "反馈类型错误"
+
+    if feedback not in (FEEDBACK_NONE, FEEDBACK_LIKE, FEEDBACK_DISLIKE):
+        return None, None, "反馈类型错误"
+
+    return message_id, feedback, None
+
+
+def _find_current_points_holder(db: Session, user_info):
+    user = db.query(User).filter(
+        User.id == user_info.user_id,
+        User.is_deleted == 0,
+    ).first()
+    if user:
+        return user
+
+    return db.query(UserData).filter(
+        UserData.accountID == user_info.account,
+    ).first()
 
 
 @router.post("/like_and_dislike")
-async def like_and_dislike(request: LikeDislikeRequest, db: Session = Depends(get_db)):
-    """点赞/踩(对齐Go版本)"""
-    message = db.query(AIMessage).filter(
-        AIMessage.id == request.ai_message_id).first()
+async def like_and_dislike(data: LikeDislikeRequest, request: Request, db: Session = Depends(get_db)):
+    """Save AI message feedback and reward points to the current user."""
+    user_info = request.state.user
+    if not user_info:
+        return {"statusCode": 401, "msg": "未认证"}
+
+    message_id, feedback, error = _resolve_like_dislike_payload(data)
+    if error:
+        return {"statusCode": 400, "msg": error}
+
+    message = db.query(AIMessage).filter(AIMessage.id == message_id).first()
     if not message:
         return {"statusCode": 404, "msg": "消息不存在"}
 
-    # 将action转换为user_feedback:like=2(满意/赞), dislike=3(不满意/踩)
-    user_feedback = 2 if request.action == "like" else 3
-    message.user_feedback = user_feedback
-    message.updated_at = int(time.time())
-    db.commit()
+    if getattr(message, "user_id", user_info.user_id) != user_info.user_id:
+        return {"statusCode": 403, "msg": "无权评价该消息"}
 
-    return {"statusCode": 200, "msg": "success"}
+    points_holder = _find_current_points_holder(db, user_info)
+    if not points_holder:
+        return {"statusCode": 404, "msg": "未找到用户数据"}
+
+    previous_feedback = int(message.user_feedback or 0)
+    previous_points = FEEDBACK_REWARD_POINTS.get(previous_feedback, 0)
+    current_points = FEEDBACK_REWARD_POINTS.get(feedback, 0)
+    points_delta = current_points - previous_points
+
+    try:
+        message.user_feedback = feedback
+        message.updated_at = int(time.time())
+
+        new_balance = (points_holder.points or 0) + points_delta
+        points_holder.points = new_balance
+
+        db.commit()
+    except Exception as e:
+        db.rollback()
+        return {"statusCode": 500, "msg": f"反馈提交失败: {str(e)}"}
+
+    return {
+        "statusCode": 200,
+        "msg": "success",
+        "data": {
+            "ai_message_id": message_id,
+            "user_feedback": feedback,
+            "points_added": points_delta,
+            "points_delta": points_delta,
+            "new_balance": new_balance,
+        },
+    }
 
 
 @router.get("/get_user_data_id")

+ 3 - 9
shudao-vue-frontend/src/views/Chat.vue

@@ -369,7 +369,6 @@
                         class="action-btn thumbs-up-btn" 
                         :class="{ active: message.userFeedback === 'like' }"
                         @click="handleThumbsUp(message)"
-                        :title="message.userFeedback === 'like' ? '取消点赞' : '点赞'"
                       >
                         <img :src="likeIcon" alt="点赞" class="action-icon">
                       </button>
@@ -377,7 +376,6 @@
                         class="action-btn thumbs-down-btn"
                         :class="{ active: message.userFeedback === 'dislike' }"
                         @click="handleThumbsDown(message)"
-                        :title="message.userFeedback === 'dislike' ? '取消点踩' : '点踩'"
                       >
                         <img :src="dislikeIcon" alt="踩" class="action-icon">
                       </button>
@@ -4990,14 +4988,10 @@ const syncFeedbackToBackend = async (message) => {
     
     if (response.statusCode === 200) {
       console.log('反馈同步成功')
-      // 根据反馈类型显示不同提示
-      if (feedback === 2) {
-        ElMessage.success('点赞成功')
-      } else if (feedback === 3) {
-        ElMessage.success('点踩成功')
-      } else {
-        ElMessage.success('已取消反馈')
+      if (typeof response.data?.new_balance === 'number') {
+        userPointsBalance.value = response.data.new_balance
       }
+      ElMessage.success(feedback === 0 ? '已取消反馈' : '反馈成功')
     } else {
       console.error('反馈同步失败:', response.msg)
       ElMessage.error('反馈提交失败,请稍后重试')

+ 1 - 10
shudao-vue-frontend/src/views/mobile/m-Chat.vue

@@ -253,7 +253,6 @@
                       class="action-btn thumbs-up-btn" 
                       :class="{ active: message.userFeedback === 'like' }"
                       @click="handleThumbsUp(message)"
-                      :title="message.userFeedback === 'like' ? '取消点赞' : '点赞'"
                     >
                       <img :src="likeIcon" alt="点赞" class="action-icon">
                     </button>
@@ -261,7 +260,6 @@
                       class="action-btn thumbs-down-btn"
                       :class="{ active: message.userFeedback === 'dislike' }"
                       @click="handleThumbsDown(message)"
-                      :title="message.userFeedback === 'dislike' ? '取消点踩' : '点踩'"
                     >
                       <img :src="dislikeIcon" alt="踩" class="action-icon">
                     </button>
@@ -4268,14 +4266,7 @@ const syncFeedbackToBackend = async (message) => {
     
     if (response.statusCode === 200) {
     console.log('反馈同步成功')
-      // 根据反馈类型显示不同提示
-      if (feedback === 2) {
-        showToastMessage('点赞成功')
-      } else if (feedback === 3) {
-        showToastMessage('点踩成功')
-      } else {
-        showToastMessage('已取消反馈')
-      }
+      showToastMessage(feedback === 0 ? '已取消反馈' : '反馈成功')
     } else {
       console.error('反馈同步失败:', response.msg)
       showToastMessage('反馈提交失败,请稍后重试', 'error')