refactor(P1): 5 项代码质量改进 — 消除重复、拆分巨型组件、统一基础设施

Task 4: 统一 amap.ts 为完整 API 客户端
- 扩展 amap.ts 为统一客户端(amapFetch 8s 超时 + 错误处理)
- 导出 searchPlaceText/searchPlaceAround/getInputTips/reverseGeocode/getTransitDirection
- 精简 4 个 location route 为单行调用,blindboxPlanGen 删除 ~80 行内联 API 代码

Task 2: 抽取 ShareCardShell 消除三兄弟重复
- 新建 ShareCardShell.tsx 共享外框/背景/品牌头/QR 底部
- RestaurantShareCard 406→268 行,BlindboxShareCard 341→173 行,BlindboxPlanShareCard 277→159 行

Task 3: 拆分 BlindboxPlan.tsx (742→371 行)
- 提取 planUtils.ts (guessCategory + formatDuration)
- 提取 PoiSearchField / SortablePlanItem / PlanItemEditModal 三个独立组件

Task 1: 拆分 blindbox/[code]/page.tsx 上帝组件 (1300→509 行)
- 提取 useBlindboxRoom / useBlindboxIdeas / useBlindboxPlan / useBlindboxDraw 四个 hooks
- 提取 BlindboxPoolPhase / BlindboxRevealPhase 两个子组件
- 主页面仅保留 phase 协调 + hook 组装 + 子组件渲染

Task 5: 统一 SWR 数据获取层
- 新建 fetcher.ts (FetchError 携带 status,401 不重试)
- 新建 useBlindboxRooms / useAchievements / useFavorites SWR hooks
- useRoomPolling 改用共享 fetcher
- blindbox 大厅/成就/个人中心页面删除手写 fetch 样板代码
- JWT 过期时自动弹出登录框而非反复重试
This commit is contained in:
2026-03-02 18:05:06 +08:00
parent ce76980fe5
commit 6bb0e65d4c
34 changed files with 2759 additions and 2669 deletions
+180
View File
@@ -0,0 +1,180 @@
import { QRCodeSVG } from "qrcode.react";
import type { ReactNode } from "react";
export interface ShareCardTheme {
emoji: string;
tagline: string;
bgColor: string;
gradientBorder: string;
accentLine: string;
glows: { top: number; right: number; width: number; height: number; color: string }[];
qrFgColor: string;
}
export default function ShareCardShell({
theme,
cardRef,
bgDataUrl,
children,
}: {
theme: ShareCardTheme;
cardRef: React.RefObject<HTMLDivElement | null>;
bgDataUrl?: string | null;
children: ReactNode;
}) {
const shareUrl =
typeof window !== "undefined" ? window.location.origin : "nowhatever.app";
return (
<div
ref={cardRef}
style={{
width: 340,
padding: 1.5,
borderRadius: 20,
background: theme.gradientBorder,
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
}}
>
<div
style={{
borderRadius: 18.5,
background: theme.bgColor,
position: "relative",
overflow: "hidden",
}}
>
{/* Background image */}
{bgDataUrl && (
<img
src={bgDataUrl}
alt=""
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
objectFit: "cover",
opacity: 0.12,
}}
/>
)}
{/* Decorative glows */}
{theme.glows.map((g, i) => (
<div
key={i}
style={{
position: "absolute",
top: g.top,
right: g.right,
width: g.width,
height: g.height,
borderRadius: "50%",
background: `radial-gradient(circle, ${g.color}, transparent 70%)`,
}}
/>
))}
{/* Brand header */}
<div
style={{
padding: "14px 20px 12px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
position: "relative",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ fontSize: 18 }}>{theme.emoji}</span>
<div>
<div
style={{
fontSize: 13,
fontWeight: 800,
color: "#ffffff",
letterSpacing: "0.02em",
}}
>
NoWhatever
</div>
<div
style={{
fontSize: 9,
fontWeight: 600,
color: "rgba(255,255,255,0.35)",
letterSpacing: "0.15em",
marginTop: 1,
}}
>
{theme.tagline}
</div>
</div>
</div>
</div>
{/* Thin accent line */}
<div
style={{
height: 1,
margin: "0 20px",
background: theme.accentLine,
}}
/>
{/* Card-specific content */}
{children}
{/* QR footer */}
<div
style={{
padding: "14px 20px 16px",
display: "flex",
alignItems: "center",
gap: 14,
borderTop: "1px solid rgba(255,255,255,0.04)",
}}
>
<div
style={{
padding: 5,
borderRadius: 8,
background: "#ffffff",
flexShrink: 0,
}}
>
<QRCodeSVG
value={shareUrl}
size={52}
level="M"
bgColor="#ffffff"
fgColor={theme.qrFgColor}
/>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div
style={{
fontSize: 12,
fontWeight: 700,
color: "rgba(255,255,255,0.7)",
}}
>
便
</div>
<div
style={{
fontSize: 10,
color: "rgba(255,255,255,0.25)",
marginTop: 3,
}}
>
{shareUrl.replace(/^https?:\/\//, "")}
</div>
</div>
</div>
</div>
</div>
);
}