fix: 刷新页面后恢复滑动进度,防止重复 swipe

服务端 GET room 返回 swipeCounts,前端据此恢复 currentIndex、
swipeHistory 和引导状态;swipe API 增加幂等性检查,跳过已滑过的卡片。
This commit is contained in:
2026-02-24 18:40:42 +08:00
parent d83e5ec6c4
commit 998d0a4e15
6 changed files with 20 additions and 4 deletions
+1
View File
@@ -51,6 +51,7 @@ export async function GET(
matchType, matchType,
matchLikes, matchLikes,
likeCounts, likeCounts,
swipeCounts: data.swipeCounts,
restaurants: data.restaurants, restaurants: data.restaurants,
}); });
} catch (e) { } catch (e) {
+7
View File
@@ -20,6 +20,13 @@ export async function POST(
const rid = String(restaurantId); const rid = String(restaurantId);
const updated = await atomicUpdateRoom(id, (data) => { const updated = await atomicUpdateRoom(id, (data) => {
const restaurantIndex = data.restaurants.findIndex((r) => r.id === rid);
const alreadySwiped =
restaurantIndex >= 0 &&
restaurantIndex < (data.swipeCounts[userId] ?? 0);
if (alreadySwiped) return data;
if (action === "like") { if (action === "like") {
if (!data.likes[rid]) { if (!data.likes[rid]) {
data.likes[rid] = []; data.likes[rid] = [];
+3 -1
View File
@@ -15,7 +15,7 @@ export default function RoomPage() {
const [joined, setJoined] = useState(false); const [joined, setJoined] = useState(false);
const { const {
userCount, match, matchType, matchLikes, likeCounts, restaurants, mutate, userCount, match, matchType, matchLikes, likeCounts, swipeCounts, restaurants, mutate,
} = useRoomPolling(roomId); } = useRoomPolling(roomId);
useEffect(() => { useEffect(() => {
@@ -34,6 +34,7 @@ export default function RoomPage() {
await mutate(); await mutate();
}, [roomId, mutate]); }, [roomId, mutate]);
const initialIndex = swipeCounts[userId] ?? 0;
const ready = joined && userId && restaurants.length > 0; const ready = joined && userId && restaurants.length > 0;
if (!ready) { if (!ready) {
@@ -52,6 +53,7 @@ export default function RoomPage() {
restaurants={restaurants} restaurants={restaurants}
roomId={roomId} roomId={roomId}
userId={userId} userId={userId}
initialIndex={initialIndex}
matchedRestaurantId={match} matchedRestaurantId={match}
matchType={matchType} matchType={matchType}
matchLikes={matchLikes} matchLikes={matchLikes}
+7 -3
View File
@@ -13,6 +13,7 @@ interface SwipeDeckProps {
restaurants: Restaurant[]; restaurants: Restaurant[];
roomId: string; roomId: string;
userId: string; userId: string;
initialIndex: number;
matchedRestaurantId: string | null; matchedRestaurantId: string | null;
matchType: MatchType; matchType: MatchType;
matchLikes: number; matchLikes: number;
@@ -25,6 +26,7 @@ export default function SwipeDeck({
restaurants, restaurants,
roomId, roomId,
userId, userId,
initialIndex,
matchedRestaurantId, matchedRestaurantId,
matchType, matchType,
matchLikes, matchLikes,
@@ -32,13 +34,15 @@ export default function SwipeDeck({
userCount, userCount,
onReset, onReset,
}: SwipeDeckProps) { }: SwipeDeckProps) {
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(initialIndex);
const [showMatch, setShowMatch] = useState(false); const [showMatch, setShowMatch] = useState(false);
const [localMatchId, setLocalMatchId] = useState<string | null>(null); const [localMatchId, setLocalMatchId] = useState<string | null>(null);
const [resetting, setResetting] = useState(false); const [resetting, setResetting] = useState(false);
const [bubble, setBubble] = useState(""); const [bubble, setBubble] = useState("");
const [guideVisible, setGuideVisible] = useState(true); const [guideVisible, setGuideVisible] = useState(initialIndex === 0);
const [swipeHistory, setSwipeHistory] = useState<string[]>([]); const [swipeHistory, setSwipeHistory] = useState<string[]>(
() => restaurants.slice(0, initialIndex).map((r) => r.id),
);
const swipeFnRef = useRef<((direction: SwipeDirection) => void) | null>(null); const swipeFnRef = useRef<((direction: SwipeDirection) => void) | null>(null);
const swipingRef = useRef(false); const swipingRef = useRef(false);
const prevLikeCounts = useRef<Record<string, number>>({}); const prevLikeCounts = useRef<Record<string, number>>({});
+1
View File
@@ -30,6 +30,7 @@ export function useRoomPolling(roomId: string) {
matchType: data?.matchType ?? null, matchType: data?.matchType ?? null,
matchLikes: data?.matchLikes ?? 0, matchLikes: data?.matchLikes ?? 0,
likeCounts: data?.likeCounts ?? {}, likeCounts: data?.likeCounts ?? {},
swipeCounts: data?.swipeCounts ?? {},
restaurants: data?.restaurants ?? [], restaurants: data?.restaurants ?? [],
isLoading, isLoading,
error, error,
+1
View File
@@ -24,5 +24,6 @@ export interface RoomStatus {
matchType: MatchType; matchType: MatchType;
matchLikes: number; matchLikes: number;
likeCounts: Record<string, number>; likeCounts: Record<string, number>;
swipeCounts: Record<string, number>;
restaurants: Restaurant[]; restaurants: Restaurant[];
} }