// src/lib/api.ts — 后端 API 统一客户端(所有接口的唯一出入口) // // 集中封装全部 /api/v1 调用:公开查询(/api/v1/public/*)、账号体系(/api/v1/auth/*)、 // 以及构建期专用的 SSG 接口(/api/internal/ssg/*,内部契约,仅 Node 构建期使用)。 // 任何模块需要后端数据,都只从这里取,不要再自己写 fetch。 // // base URL 解析规则(全项目统一两个基准地址,命名见下): // BASE_API —— 公开/运行期 API 基址(浏览器运行期 + 按需渲染 SSR)。 // 空值 = 同源相对 /api;DEV 构建期固定走线上域名。 // BASE_API_SSG —— 构建期 Node 调 /api/internal/ssg/* 的「内部端口」(默认 8091), // 与 BASE_API 完全分离,绝不回落到公开/运行期端口(避免内部全量数据外泄)。 // // - getBase():公开接口 base。DEV(astro dev)→ 线上域名;其余 → BASE_API(空则同源相对 /api)。 // - ssrBase():按需渲染(on-demand)详情取数 base,BASE_API > 本地 8090 回落。 // - ssgBase():构建期 Node 调 /api/internal/ssg/*,只读 BASE_API_SSG。 // 加载即安装全局 fetch 拦截器:把 API 请求/响应记录到调试日志(控制台/文件)。 import "@/lib/http" // i18n:把"当前语言"透传到后端,让 /api 按 locale 选 *_en / *_cn 列。 import { getLocaleFromUrl } from "@/i18n/utils" import { DEFAULT_LOCALE } from "@/i18n/config" // 公开/运行期基址 BASE_API:Astro 7 中声明在 env.schema(access:public)的变量, // 浏览器端须从 astro:env/client 读取(不会像 PUBLIC_ 前缀那样自动进 import.meta.env)。 // 该模块在 SSR(服务端)也会回退到 process.env,因此服务端/客户端共用同一份值。 import { BASE_API } from "astro:env/client" // ===================== 类型 ===================== export interface BrandEntry { id: string // 后端返回 hashid 编码串(非自增主键),防爬虫顺序枚举 name: string // 英文名(name_en 回落 name) nameCn?: string // 中文名(name_cn,可能为空) display?: string // 展示名:中文语境下为 "英文名 (中文名)",否则为英文名 } export interface ArticleItem { id: string // hashid 编码串 brand_id: string // hashid 编码串 title: string cover: string brand_name: string image_count: number // 该走秀图片总数(列表卡片 "N+" 角标用) gallery: string[] // 由 images 映射而来的附图 URL 列表 } export interface ArticleQuery { page?: number sort?: string brandId?: string // 编码串;传入则附加 ?brand_id 查询参数 collection?: string // 单值:rtw/menswear/couture/resort/pre_fall season?: string // 单值:spring/fall year?: string | number } export interface ArticlePage { data: ArticleItem[] last_page: number preview?: boolean // 未登录且总量超预览上限时为 true,前端据此弹登录门禁 } // 后端单张图片(与 PublicArticleImage 对齐):主图或细节图。 export interface RawArticleImage { id?: string // 图片自身编码串(i/j 前缀),单图收藏用 image: string // 该接口场景的主用地址(详情=展示样式 !style:high;列表=缩略图 !style:thumb) thumb?: string // 缩略图地址(!style:thumb),仅详情接口返回:供大图灯箱左栏缩略图条 / 右下角细节缩略图等小尺寸场景用,省带宽 name?: string is_detail?: number // 0=主图 1=细节图(详情接口 images 仅含主图;细节在 details 分组) look_index?: number // 细节图归属的主图序号(与 images 主图的 look_index 一致) } // 某个主图(look)对应的细节图集合(与 RunwayLookDetails 对齐),详情页「细节入口」按需展开。 export interface RawLookDetails { look_index: number // 主图序号 images: RawArticleImage[] // 该主图的细节图列表 } // 后端文章原始结构(与 PublicArticle / PublicArticleDetail 对齐)。 export type RawArticle = { id: string | number // 后端为 hashid 编码串(string) title: string description?: string // 详情正文(仅详情接口返回) cover: string brand_name: string brand_id?: string | number // hashid 编码串 image_count?: number // 列表卡片 "N+" 角标用(仅列表接口返回);改造后仅计主图数 // 详情接口 images 仅含主图(look 图),默认展示;细节图在 details 分组,需经「细节入口」展开。 images?: RawArticleImage[] // 按主图分组的细节图(「细节入口」展开用);无细节时为空数组或不返回。 details?: RawLookDetails[] } export interface AuthUser { id: number username: string email: string /** 用户等级:free(普通)| vip(高级,图集详情页可见高清原图)。 */ tier: 'free' | 'vip' } // SSG 构建专用类型 export interface SsgPopularBrand { id: string article_id: string brand: string cover: string title: string } // ===================== base 解析 ===================== // 仅本模块内部使用(toAbs / request 等),不对外导出。 function getBase(): string { // 运行期/预渲染统一走 BASE_API(空则同源相对 /api) return (BASE_API || "").replace(/\/$/, "") } // 相对图片路径补成绝对(基于 getBase())。 export function toAbs(url: string): string { if (!url) return "" return /^https?:\/\//i.test(url) ? url : getBase() + url } // 按需渲染(on-demand SSR)详情 base:BASE_API > 本地 8090 回落。 export function ssrBase(): string { return (BASE_API || "http://localhost:8090").replace(/\/$/, "") } // ===================== i18n:请求语言透传 ===================== // 后端 /api/v1/public/* 与 /api/internal/ssg/* 都通过 ?locale=cn|en(优先级最高,高于 Accept-Language) // 决定 name / title / brand_name 选用 *_en 还是 *_cn 列(service.pickLocale 选列并缺翻译回落), // 出参字段名保持不变,故前端契约无需改动,仅需在请求上带上当前语言。 // // 当前语言来源分两端: // - 浏览器端(