update
Some checks failed
deploy / deploy (push) Has been cancelled

This commit is contained in:
toom1996
2026-09-02 21:50:30 +08:00
parent 50e5775dc8
commit 64af75c0a9
23 changed files with 1730 additions and 285 deletions

View File

@ -207,8 +207,8 @@ async function request<T>(path: string, params?: URLSearchParams): Promise<T> {
// 接口:GET /api/public/runway-looks/:id
// 走秀详情(含完整图片集)。base 默认走 ssrBase()(运行期按需渲染取数),也可显式传入。
// locale 显式传入时优先(按需渲染 SSR 用它避免并发串 locale),否则走 currentLocale()。
// 仅本模块内部使用(getSsrArticle 在其之上包一层),不对外导出。
async function fetchArticleById(
// 对外导出:详情取数 / 个人中心逐篇回查(浏览器端按需取 title/cover 渲染历史卡片)。
export async function fetchArticleById(
id: number | string,
base: string = ssrBase(),
locale?: string
@ -383,6 +383,31 @@ async function authedFetch(url: string, opts: RequestInit = {}): Promise<Respons
return res
}
// 带鉴权的 JSON 请求:自动附 Bearer、遇 401 静默刷新一次后重试,
// 成功返回响应体中的 data 字段;非 2xx 抛出(含后端 message)。
// 收藏/个人中心等需要登录的写操作统一走这里。
export async function authJson<T = any>(method: string, path: string, body?: unknown): Promise<T> {
const url = `${BASE_API_URL}/api/v1${path}`
const headers: Record<string, string> = { "Content-Type": "application/json" }
const res = await authedFetch(url, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
})
if (!res.ok) {
let msg = `API ${res.status} ${method} ${path}`
try {
const j = await res.json()
if (j?.message) msg = String(j.message)
} catch {
/* 响应体非 JSON,沿用默认错误信息 */
}
throw new Error(msg)
}
const json: any = await res.json()
return (json?.data ?? null) as T
}
export async function fetchMe(): Promise<AuthUser | null> {
if (typeof localStorage === "undefined") return null
if (!getAccessToken()) return null
@ -417,6 +442,10 @@ export async function login(input: { account: string; password: string }): Promi
const user: any = json?.user
if (!access || !refresh || !user) throw new Error("login failed")
saveSession(access, refresh, user)
// 登录成功广播:列表页在未登录时进页没记图集,登录后由监听补记(与「进页面就记」一致)。
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:login"))
}
return user as AuthUser
}
@ -454,6 +483,40 @@ export async function logoutAll(): Promise<void> {
clearSession()
}
// ===================== 浏览历史:单篇详情回查(账户页 My History 渲染用) =====================
// 历史只存文章对外编码串(r= 走秀 / s= 街拍),展示时逐篇回查公开详情接口取 title/cover/brand。
// 走 request():自动带 locale 与前端签名,登录态附带 Bearer。
export interface HistoryMeta {
title: string
cover: string
brand: string
type: "street" | "runway"
}
// 单篇回查:编码串自带类型(s= 前缀街拍,其余走秀)。失败返回 null,由调用方容忍。
export async function getHistoryMeta(id: string): Promise<HistoryMeta | null> {
const sid = String(id)
const type = sid.startsWith("s=") ? ("street" as const) : ("runway" as const)
const path =
type === "street"
? `/public/street-snaps/${encodeURIComponent(sid)}`
: `/public/runway-looks/${encodeURIComponent(sid)}`
try {
const j = await request<{ data?: any }>(path)
const d = j?.data
if (!d) return null
return {
title: (d.title || "").toString(),
cover: (d.cover || "").toString(),
brand: (d.brand_name || d.brand || "").toString(),
type,
}
} catch {
return null
}
}
// ===================== SSG 构建专用(/api/ssg/*,仅 Node 构建期) =====================
function ssgEnv(name: string): string | undefined {
@ -564,8 +627,8 @@ export async function getStreetSnaps(q: {
// 接口:GET /api/public/street-snaps/:id
// 街拍详情(含完整图片集)。base 默认走 ssrBase()(运行期按需渲染取数)。
// 仅本模块内部使用(getSsrStreetSnap 在其之上包一层),不对外导出。
async function fetchStreetSnapById(
// 对外导出:详情取数 / 个人中心逐篇回查。
export async function fetchStreetSnapById(
id: number | string,
base: string = ssrBase(),
locale?: string