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

This commit is contained in:
toom1996
2026-09-03 19:45:36 +08:00
parent 6902376bc2
commit 36f3ad039f
14 changed files with 672 additions and 124 deletions

View File

@ -57,6 +57,7 @@ export interface ArticleQuery {
export interface ArticlePage {
data: ArticleItem[]
last_page: number
preview?: boolean // 未登录且总量超预览上限时为 true,前端据此弹登录门禁
}
// 后端文章原始结构(与 PublicArticle / PublicArticleDetail 对齐)。
@ -208,44 +209,51 @@ async function request<T>(path: string, params?: URLSearchParams): Promise<T> {
// 走秀详情(含完整图片集)。base 默认走 ssrBase()(运行期按需渲染取数),也可显式传入。
// locale 显式传入时优先(按需渲染 SSR 用它避免并发串 locale),否则走 currentLocale()。
// 对外导出:详情取数 / 个人中心逐篇回查(浏览器端按需取 title/cover 渲染历史卡片)。
export async function fetchArticleById(
id: number | string,
base: string = ssrBase(),
locale?: string
): Promise<RawArticle | null> {
const b = base.replace(/\/$/, "")
if (!b) return null
const p = withLocale(undefined, locale)
const qs = p.toString() ? "?" + p.toString() : ""
try {
const res = await fetch(`${b}/api/v1/public/runway-looks/${id}${qs}`)
if (!res.ok) return null
const json: any = await res.json()
return (json?.data ?? null) as RawArticle | null
} catch {
return null
}
}
// 服务端按需渲染(on-demand)取文章详情:在 fetchArticleById 之上包揽
// 「缺 id / 取数失败 / 成功」三态,.astro 页面只调这一行即可。
// 服务端按需渲染(on-demand)取文章详情:直接 fetch 详情接口并透传 preview 标记。
// 内部 base 走 ssrBase()(运行期取数,BASE_API > 本地 8090)。
// locale 显式传入(如 Astro.currentLocale)以绑定当前语言,避免并发串 locale。
export type SsrArticleResult = {
article: RawArticle | null
error: "missing" | "failed" | null
preview?: boolean // 未登录且图片数超过预览上限时为 true,详情页据此弹「登录查看全部图片」门禁
imageTotal?: number // 图片集真实总数(后端截断前),详情页渲染「剩余 N 张」占位遮罩用
}
export async function getSsrArticle(
id: string | null | undefined,
locale?: string
): Promise<SsrArticleResult> {
if (!id) return { article: null, error: "missing" }
if (!id) return { article: null, error: "missing", preview: false, imageTotal: 0 }
const b = ssrBase().replace(/\/$/, "")
if (!b) return { article: null, error: "failed", preview: false, imageTotal: 0 }
const p = withLocale(undefined, locale)
const qs = p.toString() ? "?" + p.toString() : ""
try {
const article = await fetchArticleById(id, ssrBase(), locale)
return { article, error: article ? null : "failed" }
const res = await fetch(`${b}/api/v1/public/runway-looks/${id}${qs}`)
if (!res.ok) return { article: null, error: "failed", preview: false, imageTotal: 0 }
const json: any = await res.json()
return {
article: (json?.data ?? null) as RawArticle | null,
preview: !!json?.preview,
imageTotal: Number(json?.image_total ?? 0),
error: null,
}
} catch {
return { article: null, error: "failed" }
return { article: null, error: "failed", preview: false, imageTotal: 0 }
}
}
// 已登录用户详情取数:走 request()(自动附带 Bearer + locale 签名),
// 后端据此返回完整图片集(未登录截断到 PreviewLimit 张)。详情页未登录→登录后客户端补拉全量用。
export async function getArticleDetailAuthed(
id: string,
locale?: string
): Promise<RawArticle | null> {
try {
const json: any = await request(`/public/runway-looks/${encodeURIComponent(id)}`)
return (json?.data ?? null) as RawArticle | null
} catch {
return null
}
}
@ -260,12 +268,12 @@ export async function getArticles(q: ArticleQuery): Promise<ArticlePage> {
if (q.collection) p.set("collection", q.collection)
if (q.season) p.set("season", q.season)
if (q.year) p.set("year", String(q.year))
const j = await request<{ data?: any[]; last_page?: number }>("/public/runway-looks", p)
const j = await request<{ data?: any[]; last_page?: number; preview?: boolean }>("/public/runway-looks", p)
const mapped: ArticleItem[] = (j.data ?? []).map((it: any) => ({
...it,
gallery: Array.isArray(it.images) ? it.images.map((im: any) => im.image || im) : [],
}))
return { data: mapped, last_page: Number(j.last_page ?? 1) }
return { data: mapped, last_page: Number(j.last_page ?? 1), preview: !!j.preview }
}
// ===================== 品牌(/api/public/brands) =====================
@ -575,6 +583,7 @@ export interface StreetSnapItem {
export interface StreetSnapPage {
data: StreetSnapItem[]
last_page: number
preview?: boolean // 未登录且总量超预览上限时为 true,前端据此弹登录门禁
}
// 街拍详情原始结构(与 PublicStreetSnapDetail 对齐)。
@ -599,7 +608,7 @@ export async function getStreetSnaps(q: {
if (q.sort) p.set("sort", q.sort)
if (q.year) p.set("year", String(q.year))
if (q.city) p.set("city", q.city)
const j = await request<{ data?: any[]; last_page?: number }>("/public/street-snaps", p)
const j = await request<{ data?: any[]; last_page?: number; preview?: boolean }>("/public/street-snaps", p)
const mapped: StreetSnapItem[] = (j.data ?? []).map((it: any) => ({
id: String(it.id),
title: it.title ?? "",
@ -610,48 +619,54 @@ export async function getStreetSnaps(q: {
? it.images.map((im: any) => ({ id: im.id || "", image: im.image || im, name: im.name || "" }))
: [],
}))
return { data: mapped, last_page: Number(j.last_page ?? 1) }
return { data: mapped, last_page: Number(j.last_page ?? 1), preview: !!j.preview }
}
// 接口:GET /api/public/street-snaps/:id
// 街拍详情(含完整图片集)。base 默认走 ssrBase()(运行期按需渲染取数)。
// 对外导出:详情取数 / 个人中心逐篇回查。
export async function fetchStreetSnapById(
id: number | string,
base: string = ssrBase(),
locale?: string
): Promise<RawStreetSnap | null> {
const b = base.replace(/\/$/, "")
if (!b) return null
const p = withLocale(undefined, locale)
const qs = p.toString() ? "?" + p.toString() : ""
try {
const res = await fetch(`${b}/api/v1/public/street-snaps/${id}${qs}`)
if (!res.ok) return null
const json: any = await res.json()
return (json?.data ?? null) as RawStreetSnap | null
} catch {
return null
}
}
export type SsrStreetSnapResult = {
snap: RawStreetSnap | null
error: "missing" | "failed" | null
preview?: boolean // 未登录且图片数超过预览上限时为 true,详情页据此弹「登录查看全部图片」门禁
imageTotal?: number // 图片集真实总数(后端截断前),详情页渲染「剩余 N 张」占位遮罩用
}
// 服务端按需渲染(on-demand)取街拍详情:在 fetchStreetSnapById 之上包揽
// 「缺 id / 取数失败 / 成功」三态,.astro 页面只调这一行即可。
// 服务端按需渲染(on-demand)取街拍详情:直接 fetch 详情接口并透传 preview 标记。
export async function getSsrStreetSnap(
id: string | null | undefined,
locale?: string
): Promise<SsrStreetSnapResult> {
if (!id) return { snap: null, error: "missing" }
if (!id) return { snap: null, error: "missing", preview: false, imageTotal: 0 }
const b = ssrBase().replace(/\/$/, "")
if (!b) return { snap: null, error: "failed", preview: false, imageTotal: 0 }
const p = withLocale(undefined, locale)
const qs = p.toString() ? "?" + p.toString() : ""
try {
const snap = await fetchStreetSnapById(id, ssrBase(), locale)
return { snap, error: snap ? null : "failed" }
const res = await fetch(`${b}/api/v1/public/street-snaps/${id}${qs}`)
if (!res.ok) return { snap: null, error: "failed", preview: false, imageTotal: 0 }
const json: any = await res.json()
return {
snap: (json?.data ?? null) as RawStreetSnap | null,
preview: !!json?.preview,
imageTotal: Number(json?.image_total ?? 0),
error: null,
}
} catch {
return { snap: null, error: "failed" }
return { snap: null, error: "failed", preview: false, imageTotal: 0 }
}
}
// 已登录用户街拍详情取数:走 request()(自动附带 Bearer + locale 签名),返回完整图片集。
export async function getStreetSnapDetailAuthed(
id: string,
locale?: string
): Promise<RawStreetSnap | null> {
try {
const json: any = await request(`/public/street-snaps/${encodeURIComponent(id)}`)
return (json?.data ?? null) as RawStreetSnap | null
} catch {
return null
}
}