feat: 两级匹配机制 - 全票通过即时匹配 + 滑完自动推荐得票最高

- 后端 GET /api/room/[id] 新增 findBestMatch,滑完后选出得票最高餐厅
- 平票时取高德评分更高的一家,永远不会出现"无结果"死局
- 返回 matchType (unanimous/best) 和 matchLikes 区分匹配类型
- 全票通过:绿色庆祝 + "大家一拍即合!"
- 得票最高:橙色推荐 + "N/M 人想去这家"
- 移除 noMatch 死局页面,简化 SwipeDeck 状态管理
This commit is contained in:
2026-02-24 17:26:16 +08:00
parent bdab39d866
commit e2c3b869eb
6 changed files with 116 additions and 52 deletions
+46 -3
View File
@@ -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 };
}