feat: 两级匹配机制 - 全票通过即时匹配 + 滑完自动推荐得票最高
- 后端 GET /api/room/[id] 新增 findBestMatch,滑完后选出得票最高餐厅 - 平票时取高德评分更高的一家,永远不会出现"无结果"死局 - 返回 matchType (unanimous/best) 和 matchLikes 区分匹配类型 - 全票通过:绿色庆祝 + "大家一拍即合!" - 得票最高:橙色推荐 + "N/M 人想去这家" - 移除 noMatch 死局页面,简化 SwipeDeck 状态管理
This commit is contained in:
@@ -8,12 +8,18 @@ import {
|
||||
Navigation,
|
||||
Phone,
|
||||
Clock,
|
||||
Trophy,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { Restaurant } from "@/types";
|
||||
import { Restaurant, MatchType } from "@/types";
|
||||
|
||||
interface MatchResultProps {
|
||||
restaurant: Restaurant;
|
||||
matchType: MatchType;
|
||||
matchLikes: number;
|
||||
userCount: number;
|
||||
onReset: () => Promise<void>;
|
||||
resetting: boolean;
|
||||
}
|
||||
|
||||
function buildNavUrl(restaurant: Restaurant): string {
|
||||
@@ -24,10 +30,23 @@ function buildNavUrl(restaurant: Restaurant): string {
|
||||
return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(restaurant.name)}`;
|
||||
}
|
||||
|
||||
export default function MatchResult({ restaurant, onReset }: MatchResultProps) {
|
||||
export default function MatchResult({
|
||||
restaurant,
|
||||
matchType,
|
||||
matchLikes,
|
||||
userCount,
|
||||
onReset,
|
||||
resetting,
|
||||
}: MatchResultProps) {
|
||||
const isUnanimous = matchType === "unanimous";
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-linear-to-b from-emerald-500 to-teal-600 px-6 py-10"
|
||||
className={`fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto px-6 py-10 ${
|
||||
isUnanimous
|
||||
? "bg-linear-to-b from-emerald-500 to-teal-600"
|
||||
: "bg-linear-to-b from-amber-500 to-orange-500"
|
||||
}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
@@ -37,7 +56,11 @@ export default function MatchResult({ restaurant, onReset }: MatchResultProps) {
|
||||
animate={{ scale: 1, rotate: 0 }}
|
||||
transition={{ type: "spring", stiffness: 200, damping: 12, delay: 0.2 }}
|
||||
>
|
||||
<PartyPopper size={56} className="text-yellow-300" />
|
||||
{isUnanimous ? (
|
||||
<PartyPopper size={56} className="text-yellow-300" />
|
||||
) : (
|
||||
<Trophy size={56} className="text-yellow-200" />
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
@@ -50,12 +73,14 @@ export default function MatchResult({ restaurant, onReset }: MatchResultProps) {
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
className="mt-1 text-sm font-medium text-emerald-100"
|
||||
className={`mt-1 text-sm font-medium ${isUnanimous ? "text-emerald-100" : "text-amber-100"}`}
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.45 }}
|
||||
>
|
||||
Everyone agreed on this one
|
||||
{isUnanimous
|
||||
? "大家一拍即合!"
|
||||
: `${matchLikes}/${userCount} 人想去这家`}
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
@@ -139,7 +164,9 @@ export default function MatchResult({ restaurant, onReset }: MatchResultProps) {
|
||||
href={buildNavUrl(restaurant)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-2 rounded-full bg-white px-8 py-3 text-sm font-bold text-emerald-600 shadow-lg transition-colors hover:bg-emerald-50"
|
||||
className={`flex items-center justify-center gap-2 rounded-full bg-white px-8 py-3 text-sm font-bold shadow-lg transition-colors hover:bg-emerald-50 ${
|
||||
isUnanimous ? "text-emerald-600" : "text-orange-600"
|
||||
}`}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Navigation size={16} />
|
||||
@@ -159,13 +186,17 @@ export default function MatchResult({ restaurant, onReset }: MatchResultProps) {
|
||||
</motion.div>
|
||||
|
||||
<motion.button
|
||||
className="mt-4 text-sm font-medium text-emerald-200 underline underline-offset-2 hover:text-white"
|
||||
className={`mt-4 flex items-center gap-1.5 text-sm font-medium underline underline-offset-2 hover:text-white ${
|
||||
isUnanimous ? "text-emerald-200" : "text-amber-200"
|
||||
}`}
|
||||
onClick={onReset}
|
||||
disabled={resetting}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.8 }}
|
||||
>
|
||||
再来一轮
|
||||
<RotateCcw size={13} className={resetting ? "animate-spin" : ""} />
|
||||
{resetting ? "重置中..." : "再来一轮"}
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user