fix: 外部 API 错误处理 + 导航 URL 校验 + reset/narrow 错误反馈

- #22: 高德 API fetch 加 try/catch,失败返回 503 而非泛化 500
- #23: buildNavUrl 校验 location.split(",") 结果长度和非空
- #24: handleReset/handleNarrow 检查 res.ok,失败时 toast 提示
This commit is contained in:
2026-02-26 20:20:59 +08:00
parent dfb3cfa136
commit cf88d3a1d2
5 changed files with 58 additions and 23 deletions
+7 -2
View File
@@ -15,8 +15,13 @@ export const GET = apiHandler(async (req) => {
url.searchParams.set("location", `${lng},${lat}`);
url.searchParams.set("extensions", "base");
const res = await fetch(url.toString());
const data = await res.json();
let data;
try {
const res = await fetch(url.toString());
data = await res.json();
} catch {
throw new ApiError("位置服务暂时不可用,请稍后重试", 503);
}
if (data.status !== "1" || !data.regeocode) {
return NextResponse.json({ name: null });
+8 -3
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { apiHandler } from "@/lib/api";
import { apiHandler, ApiError } from "@/lib/api";
import { requireAmapApiKey } from "@/lib/amap";
export const GET = apiHandler(async (req) => {
@@ -13,8 +13,13 @@ export const GET = apiHandler(async (req) => {
url.searchParams.set("keywords", keywords);
url.searchParams.set("datatype", "poi");
const res = await fetch(url.toString());
const data = await res.json();
let data;
try {
const res = await fetch(url.toString());
data = await res.json();
} catch {
throw new ApiError("位置服务暂时不可用,请稍后重试", 503);
}
if (data.status !== "1" || !data.tips) return NextResponse.json([]);
+7 -2
View File
@@ -141,8 +141,13 @@ export const POST = apiHandler(async (req) => {
url.searchParams.set("keywords", cuisine);
}
const amapRes = await fetch(url.toString());
const amapData = await amapRes.json();
let amapData;
try {
const amapRes = await fetch(url.toString());
amapData = await amapRes.json();
} catch {
throw new ApiError("位置服务暂时不可用,请稍后重试", 503);
}
let restaurants: Restaurant[] = [];
if (amapData.status === "1" && amapData.pois?.length > 0) {
+32 -14
View File
@@ -11,6 +11,7 @@ import { useRoomPolling } from "@/hooks/useRoomPolling";
import { getUserId } from "@/lib/userId";
import { joinRoom } from "@/lib/room";
import { getSceneConfig } from "@/lib/sceneConfig";
import { useToast } from "@/hooks/useToast";
export default function RoomPage() {
const params = useParams<{ id: string }>();
@@ -22,6 +23,7 @@ export default function RoomPage() {
const [joinFailed, setJoinFailed] = useState(false);
const [showLeaveConfirm, setShowLeaveConfirm] = useState(false);
const leavingRef = useRef(false);
const toast = useToast();
const {
userCount, match, matchType, matchLikes, runnerUps, likeCounts, swipeCounts,
@@ -74,22 +76,38 @@ export default function RoomPage() {
}, []);
const handleReset = useCallback(async () => {
await fetch(`/api/room/${roomId}/reset`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId }),
});
await mutate();
}, [roomId, userId, mutate]);
try {
const res = await fetch(`/api/room/${roomId}/reset`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || "重置失败");
}
await mutate();
} catch (e) {
toast.show(e instanceof Error ? e.message : "重置失败");
}
}, [roomId, userId, mutate, toast]);
const handleNarrow = useCallback(async (restaurantIds: string[]) => {
await fetch(`/api/room/${roomId}/reset`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId, restaurantIds }),
});
await mutate();
}, [roomId, userId, mutate]);
try {
const res = await fetch(`/api/room/${roomId}/reset`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId, restaurantIds }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || "缩小范围失败");
}
await mutate();
} catch (e) {
toast.show(e instanceof Error ? e.message : "操作失败");
}
}, [roomId, userId, mutate, toast]);
if (notFound || joinFailed) {
return (
+4 -2
View File
@@ -2,8 +2,10 @@ import type { Restaurant } from "@/types";
export function buildNavUrl(restaurant: Restaurant): string {
if (restaurant.location) {
const [lng, lat] = restaurant.location.split(",");
return `https://uri.amap.com/marker?position=${lng},${lat}&name=${encodeURIComponent(restaurant.name)}&callnative=1`;
const parts = restaurant.location.split(",");
if (parts.length === 2 && parts[0] && parts[1]) {
return `https://uri.amap.com/marker?position=${parts[0]},${parts[1]}&name=${encodeURIComponent(restaurant.name)}&callnative=1`;
}
}
return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(restaurant.name)}`;
}