feat: 两级匹配机制 - 全票通过即时匹配 + 滑完自动推荐得票最高
- 后端 GET /api/room/[id] 新增 findBestMatch,滑完后选出得票最高餐厅 - 平票时取高德评分更高的一家,永远不会出现"无结果"死局 - 返回 matchType (unanimous/best) 和 matchLikes 区分匹配类型 - 全票通过:绿色庆祝 + "大家一拍即合!" - 得票最高:橙色推荐 + "N/M 人想去这家" - 移除 noMatch 死局页面,简化 SwipeDeck 状态管理
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getRoomData } from "@/lib/store";
|
||||
import type { MatchType } from "@/types";
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
@@ -21,13 +22,29 @@ export async function GET(
|
||||
const allFinished =
|
||||
data.users.length > 0 &&
|
||||
data.users.every((u) => (data.swipeCounts[u] ?? 0) >= total);
|
||||
const noMatch = allFinished && data.match === null;
|
||||
|
||||
let match = data.match;
|
||||
let matchType: MatchType = null;
|
||||
let matchLikes = 0;
|
||||
|
||||
if (match) {
|
||||
matchType = "unanimous";
|
||||
matchLikes = data.users.length;
|
||||
} else if (allFinished) {
|
||||
const best = findBestMatch(data.likes, data.restaurants);
|
||||
if (best) {
|
||||
match = best.id;
|
||||
matchType = "best";
|
||||
matchLikes = best.likes;
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
roomId: id,
|
||||
userCount: data.users.length,
|
||||
match: data.match,
|
||||
noMatch,
|
||||
match,
|
||||
matchType,
|
||||
matchLikes,
|
||||
restaurants: data.restaurants,
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -38,3 +55,29 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function findBestMatch(
|
||||
likes: Record<string, string[]>,
|
||||
restaurants: { id: string; rating: number }[],
|
||||
): { id: string; likes: number } | null {
|
||||
let bestId: string | null = null;
|
||||
let bestLikes = 0;
|
||||
let bestRating = 0;
|
||||
|
||||
for (const r of restaurants) {
|
||||
const count = likes[r.id]?.length ?? 0;
|
||||
if (count === 0) continue;
|
||||
|
||||
if (
|
||||
count > bestLikes ||
|
||||
(count === bestLikes && r.rating > bestRating)
|
||||
) {
|
||||
bestId = r.id;
|
||||
bestLikes = count;
|
||||
bestRating = r.rating;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestId) return null;
|
||||
return { id: bestId, likes: bestLikes };
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function RoomPage() {
|
||||
const [userId, setUserId] = useState("");
|
||||
const [joined, setJoined] = useState(false);
|
||||
|
||||
const { userCount, match, noMatch, restaurants, mutate } =
|
||||
const { userCount, match, matchType, matchLikes, restaurants, mutate } =
|
||||
useRoomPolling(roomId);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,7 +52,9 @@ export default function RoomPage() {
|
||||
roomId={roomId}
|
||||
userId={userId}
|
||||
matchedRestaurantId={match}
|
||||
noMatch={noMatch}
|
||||
matchType={matchType}
|
||||
matchLikes={matchLikes}
|
||||
userCount={userCount}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user