Browse Source

预测积分

alexcdev1 6 months ago
parent
commit
874b1b98b9

+ 2 - 35
src/app/api/auth/login/route.js

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
 import bcrypt from "bcryptjs";
 import dbConnect from "../../../lib/dbConnect";
 import User from "../../../models/User";
+import { setCORSHeaders, handleError } from "../../../lib/apiUtils";
 
 export async function POST(request) {
   await dbConnect();
@@ -59,41 +60,7 @@ export async function POST(request) {
   }
 }
 
-// Handle OPTIONS request for CORS preflight
 export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400"); // 24 hours
-  return response;
-}
-
-// Helper function to set CORS headers
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*"); // 或者设置为特定域名
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  return response;
-}
-
-// 共享的错误处理逻辑
-function handleError(error) {
-  console.error("API错误:", error);
-  return NextResponse.json(
-    { success: false, error: error.message },
-    { status: 500 }
-  );
+  return setCORSHeaders(response);
 }

+ 2 - 35
src/app/api/auth/register/route.js

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
 import bcrypt from "bcryptjs";
 import dbConnect from "../../../lib/dbConnect";
 import User from "../../../models/User";
+import { setCORSHeaders, handleError } from "../../../lib/apiUtils";
 
 export async function POST(request) {
   await dbConnect();
@@ -54,41 +55,7 @@ export async function POST(request) {
   }
 }
 
-// Handle OPTIONS request for CORS preflight
 export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400"); // 24 hours
-  return response;
-}
-
-// Helper function to set CORS headers
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*"); // 或者设置为特定域名
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  return response;
-}
-
-// 共享的错误处理逻辑
-function handleError(error) {
-  console.error("API错误:", error);
-  return NextResponse.json(
-    { success: false, error: error.message },
-    { status: 500 }
-  );
+  return setCORSHeaders(response);
 }

+ 63 - 43
src/app/api/match/route.js

@@ -1,12 +1,10 @@
 import dbConnect from "../../lib/dbConnect";
 import Match from "../../models/Match";
 import { NextResponse } from "next/server";
+import { setCORSHeaders, handleError } from "../../lib/apiUtils";
 
 export async function GET(request) {
-  console.log("Database connected, starting query 444");
-
   await dbConnect();
-  console.log("Database connected, starting query 555");
 
   try {
     const { searchParams } = new URL(request.url);
@@ -56,7 +54,18 @@ export async function GET(request) {
           }
         }
 
-        console.log("Search Query:", searchQuery); // 添加日志
+        console.log("Search Query:", searchQuery);
+
+        // 更新比赛状态
+        const now = new Date();
+        await Match.updateMany(
+          {
+            date: { $lt: now },
+            time: { $lt: now.toTimeString().slice(0, 5) },
+            status: "未开始",
+          },
+          { $set: { status: "进行中" } }
+        );
 
         const skip = (current - 1) * pageSize;
         const totalMatches = await Match.countDocuments(searchQuery);
@@ -73,8 +82,19 @@ export async function GET(request) {
         break;
 
       case "getMatchDays":
-        const allMatches = await Match.find({}).sort("date");
-        const formattedDays = formatMatches(allMatches);
+        // 获取当前日期
+        const currentDate = new Date();
+        currentDate.setHours(0, 0, 0, 0);
+
+        // 查询未来的未开始比赛
+        const futureMatches = await Match.find({
+          date: { $gte: currentDate },
+          status: "未开始",
+        }).sort("date");
+
+        // 使用原有的 formatMatches 函数格式化比赛
+        const formattedDays = formatMatches(futureMatches);
+
         response = NextResponse.json({ success: true, data: formattedDays });
         break;
 
@@ -86,7 +106,28 @@ export async function GET(request) {
             { status: 400 }
           );
         }
-        const matches = await Match.find({ date: new Date(date) });
+
+        // 创建日期范围
+        const startDate = new Date(date);
+        startDate.setHours(0, 0, 0, 0);
+        const endDate = new Date(date);
+        endDate.setHours(23, 59, 59, 999);
+
+        // 更新比赛状态
+        await Match.updateMany(
+          {
+            date: { $lt: new Date() },
+            time: { $lt: new Date().toTimeString().slice(0, 5) },
+            status: "未开始",
+          },
+          { $set: { status: "进行中" } }
+        );
+
+        const matches = await Match.find({
+          date: { $gte: startDate, $lte: endDate },
+          status: "未开始",
+        }).sort({ time: 1 });
+
         response = NextResponse.json({ success: true, data: matches });
         break;
 
@@ -233,43 +274,9 @@ export async function DELETE(request) {
   }
 }
 
-// Handle OPTIONS request for CORS preflight
 export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400"); // 24 hours
-  return response;
-}
-
-// Helper function to set CORS headers
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*"); // 或者设置为特定域名
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  return response;
-}
-
-// 共享的错误处理逻辑
-function handleError(error) {
-  console.error("API错误:", error);
-  return NextResponse.json(
-    { success: false, error: error.message },
-    { status: 500 }
-  );
+  return setCORSHeaders(response);
 }
 
 function formatMatches(matches) {
@@ -287,9 +294,22 @@ function formatMatches(matches) {
     const month = String(date.getMonth() + 1).padStart(2, "0");
     const day = String(date.getDate()).padStart(2, "0");
 
+    const weekdays = [
+      "星期日",
+      "星期一",
+      "星期二",
+      "星期三",
+      "星期四",
+      "星期五",
+      "星期六",
+    ];
+
+    // 获取星期几
+    const weekday = weekdays[date.getDay()];
+
     return {
       id: index + 1,
-      title: `比赛日${index + 1}`,
+      title: weekday,
       date: `${year}-${month}-${day}`,
     };
   });

+ 220 - 0
src/app/api/point-history/route.js

@@ -0,0 +1,220 @@
+import dbConnect from "../../lib/dbConnect";
+import PointHistory from "../../models/PointHistory";
+import User from "../../models/User";
+import { NextResponse } from "next/server";
+import { setCORSHeaders, handleError } from "../../lib/apiUtils";
+
+// 添加积分修改记录
+export async function POST(request) {
+  await dbConnect();
+
+  try {
+    const { userId, points, reason, matchId } = await request.json();
+
+    if (!userId || points === undefined || !reason) {
+      return NextResponse.json(
+        { success: false, error: "用户ID、积分变化和修改原因都是必填项" },
+        { status: 400 }
+      );
+    }
+
+    const user = await User.findById(userId);
+    if (!user) {
+      return NextResponse.json(
+        { success: false, error: "未找到指定用户" },
+        { status: 404 }
+      );
+    }
+
+    const pointHistory = new PointHistory({
+      user: userId,
+      points: points,
+      reason: reason,
+      match: matchId || null, // 如果提供了 matchId,则保存;否则为 null
+    });
+
+    await pointHistory.save();
+
+    // 更新用户总积分
+    user.points += points;
+    await user.save();
+
+    const response = NextResponse.json(
+      {
+        success: true,
+        message: "积分修改记录已添加",
+        data: {
+          userId: user._id,
+          pointChange: points,
+          reason: reason,
+          currentPoints: user.points, // 返回更新后的总积分
+        },
+      },
+      { status: 201 }
+    );
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("添加积分修改记录时出错:", error);
+    return handleError(error);
+  }
+}
+
+// 查询积分修改历史
+export async function GET(request) {
+  await dbConnect();
+
+  try {
+    const { searchParams } = new URL(request.url);
+
+    const current = parseInt(searchParams.get("current")) || 1;
+    const pageSize = parseInt(searchParams.get("pageSize")) || 10;
+
+    const searchQuery = {};
+
+    for (const [key, value] of searchParams.entries()) {
+      if (["current", "pageSize"].includes(key)) continue;
+
+      switch (key) {
+        case "user":
+          try {
+            const userObj = JSON.parse(value);
+            if (userObj.username) {
+              const users = await User.find({
+                username: { $regex: userObj.username, $options: "i" },
+              }).select("_id");
+              searchQuery.user = { $in: users.map((u) => u._id) };
+            } else if (userObj._id) {
+              searchQuery.user = userObj._id;
+            }
+          } catch (e) {
+            console.error("解析用户参数时出错:", e);
+          }
+          break;
+        case "username":
+          const users = await User.find({
+            username: { $regex: value, $options: "i" },
+          }).select("_id");
+          searchQuery.user = { $in: users.map((u) => u._id) };
+          break;
+        case "userId":
+          searchQuery.user = value;
+          break;
+        case "points":
+          const pointsRange = value.split(",");
+          if (pointsRange.length === 2) {
+            const [min, max] = pointsRange.map(Number);
+            searchQuery.points = { $gte: min, $lte: max };
+          } else {
+            searchQuery.points = Number(value);
+          }
+          break;
+        case "reason":
+          searchQuery.reason = { $regex: value, $options: "i" };
+          break;
+        case "dateRange":
+          const [startDate, endDate] = value.split(",");
+          searchQuery.createdAt = {
+            $gte: new Date(startDate),
+            $lte: new Date(endDate),
+          };
+          break;
+        case "id":
+          searchQuery._id = value;
+          break;
+      }
+    }
+
+    console.log("Search Query:", searchQuery);
+
+    const total = await PointHistory.countDocuments(searchQuery);
+    const history = await PointHistory.find(searchQuery)
+      .sort({ createdAt: -1 })
+      .skip((current - 1) * pageSize)
+      .limit(pageSize)
+      .select("-__v")
+      .populate("user", "username");
+
+    const response = NextResponse.json(
+      {
+        success: true,
+        total,
+        data: history,
+      },
+      { status: 200 }
+    );
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("查询积分修改历史时出错:", error);
+    return handleError(error);
+  }
+}
+
+export async function DELETE(request) {
+  await dbConnect();
+
+  try {
+    const { searchParams } = new URL(request.url);
+    const id = searchParams.get("id");
+    const ids = searchParams.get("ids");
+
+    if (!id && !ids) {
+      return NextResponse.json(
+        { success: false, error: "需要提供id或ids参数" },
+        { status: 400 }
+      );
+    }
+
+    let deletedCount = 0;
+    let updatedUsers = new Set();
+
+    if (id) {
+      // 删除单个记录
+      const pointHistory = await PointHistory.findById(id);
+      if (pointHistory) {
+        updatedUsers.add(pointHistory.user.toString());
+        await PointHistory.findByIdAndDelete(id);
+        deletedCount = 1;
+      }
+    } else if (ids) {
+      // 批量删除记录
+      const idArray = ids.split(",");
+      const pointHistories = await PointHistory.find({ _id: { $in: idArray } });
+      pointHistories.forEach((ph) => updatedUsers.add(ph.user.toString()));
+      const result = await PointHistory.deleteMany({ _id: { $in: idArray } });
+      deletedCount = result.deletedCount;
+    }
+
+    // 更新用户积分
+    for (let userId of updatedUsers) {
+      const user = await User.findById(userId);
+      if (user) {
+        const totalPoints = await PointHistory.aggregate([
+          { $match: { user: user._id } },
+          { $group: { _id: null, total: { $sum: "$points" } } },
+        ]);
+        user.points = totalPoints.length > 0 ? totalPoints[0].total : 0;
+        await user.save();
+      }
+    }
+
+    const response = NextResponse.json(
+      {
+        success: true,
+        message: `成功删除 ${deletedCount} 条积分历史记录`,
+      },
+      { status: 200 }
+    );
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("删除积分历史记录时出错:", error);
+    return handleError(error);
+  }
+}
+
+export async function OPTIONS() {
+  const response = new NextResponse(null, { status: 204 });
+  return setCORSHeaders(response);
+}

+ 24 - 57
src/app/api/prediction/route.js

@@ -1,36 +1,30 @@
 import dbConnect from "../../lib/dbConnect";
 import Prediction from "../../models/Prediction";
 import { NextResponse } from "next/server";
+import { setCORSHeaders, handleError } from "../../lib/apiUtils";
 
 export async function GET(request) {
   await dbConnect();
 
   try {
     const { searchParams } = new URL(request.url);
-    const userId = searchParams.get("userId");
     const current = parseInt(searchParams.get("current") || "1");
     const pageSize = parseInt(searchParams.get("pageSize") || "10");
     const matchInfo = searchParams.get("matchInfo");
+    const username = searchParams.get("username");
 
-    console.log("userId", userId);
-
-    // if (!userId) {
-    //   const response = NextResponse.json({
-    //     success: true,
-    //     total: 0,
-    //     data: [],
-    //   });
-    //   return setCORSHeaders(response);
-    // }
+    console.log("searchParams", searchParams);
 
     let matchStage = {};
-    // if (userId) {
-    //   matchStage.user = new mongoose.Types.ObjectId(userId);
-    // }
+
+    // 处理 username 参数
+    if (username) {
+      matchStage.username = { $regex: username, $options: "i" };
+    }
 
     // 添加其他搜索条件
     for (const [key, value] of searchParams.entries()) {
-      if (["userId", "current", "pageSize", "matchInfo"].includes(key))
+      if (["current", "pageSize", "matchInfo", "username"].includes(key))
         continue;
 
       switch (key) {
@@ -57,16 +51,6 @@ export async function GET(request) {
     }
 
     let pipeline = [
-      { $match: matchStage },
-      {
-        $lookup: {
-          from: "matches",
-          localField: "match",
-          foreignField: "_id",
-          as: "matchDetails",
-        },
-      },
-      { $unwind: { path: "$matchDetails", preserveNullAndEmptyArrays: true } },
       {
         $lookup: {
           from: "users",
@@ -79,6 +63,20 @@ export async function GET(request) {
       {
         $addFields: {
           username: "$userInfo.username",
+        },
+      },
+      { $match: matchStage }, // 将 $match 阶段移到这里
+      {
+        $lookup: {
+          from: "matches",
+          localField: "match",
+          foreignField: "_id",
+          as: "matchDetails",
+        },
+      },
+      { $unwind: { path: "$matchDetails", preserveNullAndEmptyArrays: true } },
+      {
+        $addFields: {
           matchTime: {
             $concat: [
               {
@@ -367,38 +365,7 @@ export async function DELETE(request) {
   }
 }
 
-// Handle OPTIONS request for CORS preflight
 export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400"); // 24 hours
-  return response;
-}
-
-// Helper function to set CORS headers
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT,DELETE, OPTIONS"
-  );
-  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Range");
-  response.headers.set("Access-Control-Expose-Headers", "Content-Range");
-  return response;
-}
-
-function handleError(error) {
-  console.error(error);
-  return NextResponse.json(
-    { success: false, message: error.message },
-    { status: 500 }
-  );
+  return setCORSHeaders(response);
 }

+ 2 - 24
src/app/api/team/route.js

@@ -1,6 +1,7 @@
 import dbConnect from "../../lib/dbConnect";
 import Team from "../../models/Team";
 import { NextResponse } from "next/server";
+import { setCORSHeaders } from "../../lib/apiUtils";
 
 // Get team information
 export async function GET(request) {
@@ -129,30 +130,7 @@ export async function DELETE(request) {
   }
 }
 
-// Handle OPTIONS request for CORS preflight
 export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400"); // 24 hours
-  return response;
-}
-
-// Helper function to set CORS headers
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT,DELETE, OPTIONS"
-  );
-  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Range");
-  response.headers.set("Access-Control-Expose-Headers", "Content-Range");
-  return response;
+  return setCORSHeaders(response);
 }

+ 111 - 0
src/app/api/updateForMatch/route.js

@@ -0,0 +1,111 @@
+import dbConnect from "../../lib/dbConnect";
+import Prediction from "../../models/Prediction";
+import Match from "../../models/Match";
+import User from "../../models/User";
+import PointHistory from "../../models/PointHistory";
+import { NextResponse } from "next/server";
+import { setCORSHeaders, handleError } from "../../lib/apiUtils";
+
+export async function POST(request) {
+  await dbConnect();
+
+  try {
+    const { matchId } = await request.json();
+
+    if (!matchId) {
+      return NextResponse.json(
+        { success: false, error: "缺少比赛ID" },
+        { status: 400 }
+      );
+    }
+
+    const match = await Match.findById(matchId);
+    if (!match || match.status !== "已结束") {
+      return NextResponse.json(
+        { success: false, error: "比赛未找到或未结束" },
+        { status: 400 }
+      );
+    }
+
+    const predictions = await Prediction.find({ match: matchId });
+
+    for (const prediction of predictions) {
+      let pointsEarned = 0;
+      let reasons = [];
+
+      // 更新胜负预测结果
+      prediction.whoWillWinResult =
+        prediction.whoWillWin === match.result.whoWillWin
+          ? "correct"
+          : "incorrect";
+      if (prediction.whoWillWinResult === "correct") {
+        pointsEarned += match.pointRewards.whoWillWin;
+        reasons.push(`正确预测比赛胜负`);
+      }
+
+      // 更新首先得分预测结果
+      prediction.firstTeamToScoreResult =
+        prediction.firstTeamToScore === match.result.firstTeamToScore
+          ? "correct"
+          : "incorrect";
+      if (prediction.firstTeamToScoreResult === "correct") {
+        pointsEarned += match.pointRewards.firstTeamToScore;
+        reasons.push(`正确预测首先得分球队`);
+      }
+
+      // 更新总进球数预测结果
+      const actualTotalGoals = match.homeTeamScore + match.awayTeamScore;
+      prediction.totalGoalsResult =
+        prediction.totalGoals === actualTotalGoals ? "correct" : "incorrect";
+      if (prediction.totalGoalsResult === "correct") {
+        pointsEarned += match.pointRewards.totalGoals;
+        reasons.push(`正确预测总进球数`);
+      }
+
+      prediction.pointsEarned = pointsEarned;
+      prediction.isCorrect = pointsEarned > 0;
+
+      await prediction.save();
+
+      // 更新用户积分
+      if (pointsEarned > 0) {
+        const user = await User.findById(prediction.user);
+        if (user) {
+          const oldPoints = user.points;
+          const newPoints = oldPoints + pointsEarned;
+          user.points = newPoints;
+          await user.save();
+
+          // 创建积分历史记录
+          const reason = `${match.homeTeam.name} vs ${
+            match.awayTeam.name
+          } 比赛预测: ${reasons.join(", ")}`;
+          const pointHistory = new PointHistory({
+            user: user._id,
+            points: pointsEarned,
+            reason: reason,
+            match: match._id,
+            oldPoints: oldPoints,
+            newPoints: newPoints,
+          });
+          await pointHistory.save();
+        }
+      }
+    }
+
+    const response = NextResponse.json(
+      { success: true, message: "预测结果和用户积分已更新" },
+      { status: 200 }
+    );
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("更新预测结果时出错:", error);
+    return handleError(error);
+  }
+}
+
+export async function OPTIONS() {
+  const response = new NextResponse(null, { status: 204 });
+  return setCORSHeaders(response);
+}

+ 2 - 24
src/app/api/upload/route.js

@@ -1,18 +1,7 @@
 import { writeFile } from "fs/promises";
 import { NextResponse } from "next/server";
 import path from "path";
-
-// Helper function to set CORS headers
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Range");
-  response.headers.set("Access-Control-Expose-Headers", "Content-Range");
-  return response;
-}
+import { setCORSHeaders } from "../../lib/apiUtils";
 
 export async function POST(request) {
   try {
@@ -53,18 +42,7 @@ export async function POST(request) {
   }
 }
 
-// Handle OPTIONS request for CORS preflight
 export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400"); // 24 hours
-  return response;
+  return setCORSHeaders(response);
 }

+ 1 - 29
src/app/api/user/route.js

@@ -2,6 +2,7 @@ import dbConnect from "../../lib/dbConnect";
 import User from "../../models/User";
 import { NextResponse } from "next/server";
 import bcrypt from "bcryptjs";
+import { setCORSHeaders, handleError } from "../../lib/apiUtils";
 
 export async function GET(request) {
   await dbConnect();
@@ -45,13 +46,6 @@ export async function GET(request) {
 
     const skip = (current - 1) * pageSize;
     const totalUsers = await User.countDocuments(searchQuery);
-    // const usersData = await User.find(searchQuery, { password: 0 })
-    //   .sort({
-    //     role: -1, // 这会将 'admin' 排在前面,因为 'admin' 在字母顺序上比其他常见角色(如 'user')靠前
-    //     createdAt: 1,
-    //   })
-    //   .skip(skip)
-    //   .limit(pageSize);
     const usersData = await User.aggregate([
       { $match: searchQuery },
       { $project: { password: 0 } },
@@ -243,25 +237,3 @@ export async function OPTIONS() {
   const response = new NextResponse(null, { status: 204 });
   return setCORSHeaders(response);
 }
-
-function setCORSHeaders(response) {
-  response.headers.set("Access-Control-Allow-Origin", "*");
-  response.headers.set(
-    "Access-Control-Allow-Methods",
-    "GET, POST, PUT, DELETE, OPTIONS"
-  );
-  response.headers.set(
-    "Access-Control-Allow-Headers",
-    "Content-Type, Authorization"
-  );
-  response.headers.set("Access-Control-Max-Age", "86400");
-  return response;
-}
-
-function handleError(error) {
-  console.error("API错误:", error);
-  return NextResponse.json(
-    { success: false, error: error.message },
-    { status: 500 }
-  );
-}

+ 23 - 0
src/app/lib/apiUtils.js

@@ -0,0 +1,23 @@
+import { NextResponse } from "next/server";
+
+export function setCORSHeaders(response) {
+  response.headers.set("Access-Control-Allow-Origin", "*");
+  response.headers.set(
+    "Access-Control-Allow-Methods",
+    "GET, POST, PUT, DELETE, OPTIONS"
+  );
+  response.headers.set(
+    "Access-Control-Allow-Headers",
+    "Content-Type, Authorization"
+  );
+  response.headers.set("Access-Control-Max-Age", "86400");
+  return response;
+}
+
+export function handleError(error) {
+  console.error("API错误:", error);
+  return NextResponse.json(
+    { success: false, error: error.message },
+    { status: 500 }
+  );
+}

+ 0 - 2
src/app/lib/dbConnect.js

@@ -3,8 +3,6 @@ import mongoose from "mongoose";
 const MONGODB_URI = process.env.MONGODB_URI;
 const DB_NAME = process.env.MONGODB_DB_NAME || "mydatabase";
 
-console.log("MONGODB_URI 555", MONGODB_URI);
-
 if (!MONGODB_URI) {
   throw new Error(
     "Please define the MONGODB_URI environment variable inside .env.local"

+ 16 - 5
src/app/models/Match.js

@@ -24,11 +24,22 @@ const MatchSchema = new mongoose.Schema({
   },
   matchDay: Number,
   pickedBy: Number,
-  homeTeamScore: { type: Number, default: 0 }, // 主队得分
-  awayTeamScore: { type: Number, default: 0 }, // 客队得分
-  firstTeamToScore: {
-    type: String,
-    enum: ["home", "away", "no_goal"], // 哪个队伍先得分
+  homeTeamScore: { type: Number, default: null }, // 主队得分
+  awayTeamScore: { type: Number, default: null }, // 客队得分
+  result: {
+    whoWillWin: {
+      type: String,
+      enum: ["home", "away", "draw"],
+    },
+    firstTeamToScore: {
+      type: String,
+      enum: ["home", "away", "no_goal"],
+    },
+  },
+  pointRewards: {
+    whoWillWin: { type: Number, default: null },
+    firstTeamToScore: { type: Number, default: null },
+    totalGoals: { type: Number, default: null },
   },
 });
 

+ 12 - 0
src/app/models/PointHistory.js

@@ -0,0 +1,12 @@
+import mongoose from "mongoose";
+
+const PointHistorySchema = new mongoose.Schema({
+  user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
+  points: { type: Number, required: true },
+  reason: { type: String, required: true },
+  match: { type: mongoose.Schema.Types.ObjectId, ref: "Match" },
+  createdAt: { type: Date, default: Date.now },
+});
+
+export default mongoose.models.PointHistory ||
+  mongoose.model("PointHistory", PointHistorySchema);

+ 24 - 2
src/app/models/Prediction.js

@@ -3,18 +3,40 @@ import mongoose from "mongoose";
 const PredictionSchema = new mongoose.Schema({
   user: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, // 用户ID
   match: { type: mongoose.Schema.Types.ObjectId, ref: "Match" }, // 比赛ID
+
+  // 胜负预测
   whoWillWin: {
     type: String,
     enum: ["home", "away", "draw"], // 主队、客队或平局
   },
+  whoWillWinResult: {
+    type: String,
+    enum: ["correct", "incorrect", "pending"],
+    default: "pending",
+  },
+
+  // 首先得分预测
   firstTeamToScore: {
     type: String,
     enum: ["home", "away", "no_goal"], // 主队、客队或无进球
   },
+  firstTeamToScoreResult: {
+    type: String,
+    enum: ["correct", "incorrect", "pending"],
+    default: "pending",
+  },
   firstTeamToScoreLogo: String, // 首次得分球队的logo
+
+  // 总进球数预测
   totalGoals: Number, // 总进球数
-  pointsEarned: { type: Number, default: 0 }, // 用户预测获得的积分
-  isCorrect: { type: Boolean, default: false }, // 预测是否正确
+  totalGoalsResult: {
+    type: String,
+    enum: ["correct", "incorrect", "pending"],
+    default: "pending",
+  },
+
+  pointsEarned: { type: Number, default: null }, // 用户预测获得的积分
+  isCorrect: { type: Boolean, default: null },
 });
 
 export default mongoose.models.Prediction ||

+ 187 - 0
src/app/personal-center/page.jsx

@@ -0,0 +1,187 @@
+"use client";
+
+import React, { useState, useEffect, useCallback } from "react";
+import { useRouter } from "next/navigation";
+import { User, ChevronLeft, MessageSquare } from "lucide-react";
+import InfiniteScroll from "react-infinite-scroll-component";
+
+const PAGE_SIZE = 10;
+
+const PersonalCenter = () => {
+  const router = useRouter();
+  const [user, setUser] = useState(null);
+  const [predictions, setPredictions] = useState([]);
+  const [points, setPoints] = useState([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [predictionPage, setPredictionPage] = useState(1);
+  const [pointPage, setPointPage] = useState(1);
+  const [hasMorePredictions, setHasMorePredictions] = useState(true);
+  const [hasMorePoints, setHasMorePoints] = useState(true);
+
+  const fetchData = useCallback(async () => {
+    try {
+      setIsLoading(true);
+      const storedUser = JSON.parse(
+        localStorage.getItem("currentUser") || "null"
+      );
+      setUser(storedUser);
+
+      if (storedUser && storedUser.username) {
+        const predictionResponse = await fetch(
+          `/api/prediction?username=${encodeURIComponent(
+            storedUser.username
+          )}&current=${predictionPage}&pageSize=${PAGE_SIZE}`
+        );
+        const predictionData = await predictionResponse.json();
+
+        if (predictionData.success) {
+          setPredictions((prev) => [...prev, ...predictionData.data]);
+          setHasMorePredictions(predictionData.data.length === PAGE_SIZE);
+        } else {
+          console.error("Failed to fetch predictions:", predictionData.error);
+        }
+
+        const pointResponse = await fetch(
+          `/api/point-history?userId=${encodeURIComponent(
+            storedUser.id
+          )}&current=${pointPage}&pageSize=${PAGE_SIZE}`
+        );
+        const pointData = await pointResponse.json();
+
+        if (pointData.success) {
+          setPoints((prev) => [...prev, ...pointData.data]);
+          setHasMorePoints(pointData.data.length === PAGE_SIZE);
+        } else {
+          console.error("Failed to fetch point history:", pointData.error);
+        }
+      }
+    } catch (error) {
+      console.error("Error fetching data:", error);
+    } finally {
+      setIsLoading(false);
+    }
+  }, [predictionPage, pointPage]);
+
+  useEffect(() => {
+    fetchData();
+  }, [fetchData]);
+
+  const loadMorePredictions = () => {
+    setPredictionPage((prevPage) => prevPage + 1);
+  };
+
+  const loadMorePoints = () => {
+    setPointPage((prevPage) => prevPage + 1);
+  };
+
+  if (isLoading) {
+    return (
+      <div className="bg-blue-600 text-white min-h-screen p-6">Loading...</div>
+    );
+  }
+
+  if (!user) {
+    return (
+      <div className="bg-blue-600 text-white min-h-screen p-6">
+        User not found
+      </div>
+    );
+  }
+
+  return (
+    <div className="bg-blue-600 text-white min-h-screen p-6">
+      <div className="flex items-center mb-6">
+        <ChevronLeft className="cursor-pointer" onClick={() => router.back()} />
+        <h1 className="text-2xl font-bold ml-4">个人中心</h1>
+      </div>
+
+      <div className="bg-white text-black rounded-lg p-4 mb-6">
+        <div className="flex items-center">
+          <User className="w-12 h-12 text-blue-600 mr-4" />
+          <div>
+            <h2 className="text-xl font-bold">{user.username}</h2>
+            <p className="text-gray-600">积分: {user.points}</p>
+          </div>
+        </div>
+      </div>
+
+      <div className="bg-white text-black rounded-lg p-4 mb-6">
+        <h3 className="text-lg font-bold mb-2">预测记录</h3>
+        <div
+          id="predictionScroll"
+          style={{ height: "300px", overflow: "auto" }}
+        >
+          <InfiniteScroll
+            dataLength={predictions.length}
+            next={loadMorePredictions}
+            hasMore={hasMorePredictions}
+            loader={<h4>Loading...</h4>}
+            endMessage={<p className="text-center">没有更多记录了</p>}
+            scrollableTarget="predictionScroll"
+          >
+            {predictions.map((prediction) => (
+              <div key={prediction.id} className="mb-2 p-2 border-b">
+                <p className="font-bold">{prediction.matchInfo}</p>
+                <p>比赛时间: {prediction.matchTime}</p>
+                <p>
+                  胜负预测:{" "}
+                  {prediction.whoWillWin === "home"
+                    ? "主胜"
+                    : prediction.whoWillWin === "away"
+                    ? "客胜"
+                    : "平局"}
+                </p>
+                <p>
+                  首先得分:{" "}
+                  {prediction.firstTeamToScore === "home"
+                    ? "主队"
+                    : prediction.firstTeamToScore === "away"
+                    ? "客队"
+                    : "无进球"}
+                </p>
+                <p>总进球数预测: {prediction.totalGoals}</p>
+                <p>获得积分: {prediction.pointsEarned}</p>
+                <p>预测结果: {prediction.isCorrect ? "正确" : "错误"}</p>
+              </div>
+            ))}
+          </InfiniteScroll>
+        </div>
+      </div>
+
+      <div className="bg-white text-black rounded-lg p-4 mb-6">
+        <h3 className="text-lg font-bold mb-2">积分记录</h3>
+        <div id="pointScroll" style={{ height: "300px", overflow: "auto" }}>
+          <InfiniteScroll
+            dataLength={points.length}
+            next={loadMorePoints}
+            hasMore={hasMorePoints}
+            loader={<h4>Loading...</h4>}
+            endMessage={<p className="text-center">没有更多记录了</p>}
+            scrollableTarget="pointScroll"
+          >
+            {points.map((point) => (
+              <div key={point.id} className="mb-2">
+                <p>
+                  {new Date(point.createdAt).toLocaleDateString()}:{" "}
+                  {point.reason} - {point.points}分
+                </p>
+              </div>
+            ))}
+          </InfiniteScroll>
+        </div>
+      </div>
+
+      <div className="bg-white text-black rounded-lg p-4">
+        <h3 className="text-lg font-bold mb-2">最新活动</h3>
+        <p className="mb-2">🔉最新福利活动</p>
+        <p className="mb-2">🔜加入千人福利群</p>
+        <div className="flex items-center">
+          <MessageSquare className="w-5 h-5 mr-2" />
+          <p>在线客服</p>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default PersonalCenter;

+ 1 - 1
src/app/ui/MatchDays.jsx

@@ -67,7 +67,7 @@ const MatchDays = ({ onSelectMatch }) => {
                 onClick={() => searchMatches(index, item.date)}
                 className="text-white"
               >
-                <span className="block font-bold">{item.title}</span>
+                <span className="block mb-1">{item.title}</span>
                 <span className="block text-sm text-white mb-3">
                   {item.date}
                 </span>

+ 25 - 2
src/app/ui/MatchPrediction.jsx

@@ -8,7 +8,7 @@ import { format } from "date-fns";
 import zhCN from "date-fns/locale/zh-CN";
 
 import Image from "next/image";
-import { Minus, Plus, Play, X, Check, Clock } from "lucide-react";
+import { Minus, Plus, Play, X, Check, Clock, User } from "lucide-react";
 
 const defaultLogo = "/images/default_logo.png";
 
@@ -85,6 +85,10 @@ const MatchPrediction = ({ selectedDayMatches }) => {
     setCurrentUser(user);
   }, []);
 
+  const handlePersonalCenterClick = () => {
+    router.push("/personal-center");
+  };
+
   useEffect(() => {
     console.log("currentUser", currentUser);
     const fetchPredictions = async () => {
@@ -276,6 +280,25 @@ const MatchPrediction = ({ selectedDayMatches }) => {
 
   return (
     <div className="bg-blue-600 text-white p-6 max-w-md mx-auto  min-h-screen">
+      {/* Personal Center Entry */}
+      <div className="flex justify-end items-center mb-4">
+        {currentUser ? (
+          <div
+            className="flex items-center cursor-pointer"
+            onClick={handlePersonalCenterClick}
+          >
+            <User className="w-5 h-5 mr-2" />
+            <span className="text-sm font-bold">{currentUser.username}</span>
+          </div>
+        ) : (
+          <button
+            className="bg-white text-blue-600 px-3 py-1 rounded-full text-sm"
+            onClick={() => router.push("/login")}
+          >
+            登录
+          </button>
+        )}
+      </div>
       <>
         {selectedDayMatches.map((match) => {
           const formattedDate = format(new Date(match.date), "MM月dd日EEEE", {
@@ -538,7 +561,7 @@ const FirstTeamToScoreModal = ({
         </div>
       </div>
       <button
-        className="w-full bg-purple-600 text-white py-4 text-center font-bold"
+        className="w-full bg-blue-500 text-white py-4 text-center font-bold"
         onClick={onClose}
       >
         关闭