// src/lib/api.ts — 后端 API 统一客户端(所有接口的唯一出入口) // // 集中封装全部 /api/v1 调用:公开查询(/api/v1/public/*)、账号体系(/api/v1/auth/*)、 // 以及构建期专用的 SSG 接口(/api/v1/ssg/*,仅在 Node 构建期使用)。 // 任何模块需要后端数据,都只从这里取,不要再自己写 fetch。 // // base URL 解析规则(全项目统一两个基准地址,命名见下): // BASE_API —— 公开/运行期 API 基址(浏览器运行期 + 按需渲染 SSR)。 // 空值 = 同源相对 /api;DEV 构建期固定走线上域名。 // BASE_API_SSG —— 构建期 Node 调 /api/ssg/* 的「内部端口」(默认 8091), // 与 BASE_API 完全分离,绝不回落到公开/运行期端口(避免内部全量数据外泄)。 // // - getBase():公开接口 base。DEV(astro dev)→ 线上域名;其余 → BASE_API(空则同源相对 /api)。 // - publicBase():客户端运行期 base,仅 BASE_API(空 → 同源相对 /api)。 // - ssrBase():按需渲染(on-demand)详情取数 base,BASE_API > 本地 8090 回落。 // - ssgBase():构建期 Node 调 /api/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 // 展示名:中文语境下为 "英文名 (中文名)",否则为英文名 article_count: number } export interface ArticleItem { id: string // hashid 编码串 brand_id: string // hashid 编码串 title: string cover: string brand_name: string year: number image_count: number published_at: number collection_type?: string season?: string season_code?: string gallery: string[] } export interface ArticleQuery { page?: number size?: number sort?: string brand_ids?: string[] // 编码串数组,透传给后端解码 collection_type?: string[] season?: string[] year?: string[] with_images?: number } export interface ArticlePage { data: ArticleItem[] total: number last_page: number } // 后端文章原始结构(与 PublicArticle / PublicArticleDetail 对齐)。 export type RawArticle = { id: string | number // 后端为 hashid 编码串(string);示例数据回落为数字 title: string summary?: string description?: string cover: string brand_name: string brand_id?: string | number // hashid 编码串 year: number image_count: number published_at: number created_at?: number source_url?: string collection_type?: string season?: string season_code?: string images?: { id?: number; image: string; name?: string }[] } export interface AuthUser { id: number username: string email: string } // SSG 构建专用类型 export interface SsgPopularBrand { id: string article_id: string brand: string cover: string title: string } // ===================== base 解析 ===================== export function getBase(): string { // if (import.meta.env.DEV) return "https://seo.23cm.cn" // 运行期/预渲染统一走 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 } // 客户端运行期 base:仅 BASE_API(空 → 同源相对 /api)。 export function publicBase(): string { return (BASE_API || "").replace(/\/$/, "") } export function toAbsPublic(url: string): string { if (!url) return "" if (/^https?:\/\//i.test(url)) return url return publicBase() + url } // 按需渲染(on-demand SSR)详情 base:BASE_API > 本地 8090 回落。 export function ssrBase(): string { return (BASE_API || "http://localhost:8090").replace(/\/$/, "") } // ===================== i18n:请求语言透传 ===================== // 后端 /api/v1/public/* 与 /api/v1/ssg/* 都通过 ?locale=cn|en(优先级最高,高于 Accept-Language) // 决定 name / title / brand_name 选用 *_en 还是 *_cn 列(service.pickLocale 选列并缺翻译回落), // 出参字段名保持不变,故前端契约无需改动,仅需在请求上带上当前语言。 // // 当前语言来源分两端: // - 浏览器端(