@ -1,10 +1,14 @@
|
||||
---
|
||||
import { getI18n } from '@/i18n/utils'
|
||||
import { href, PATH } from '@/lib/routes'
|
||||
|
||||
interface Props {
|
||||
module?: string
|
||||
moduleZh?: string
|
||||
note?: string
|
||||
}
|
||||
const { module = "This module", moduleZh = "", note = "" } = Astro.props
|
||||
const { locale } = getI18n(Astro)
|
||||
---
|
||||
|
||||
<section class="cs-wrap">
|
||||
@ -18,8 +22,8 @@ const { module = "This module", moduleZh = "", note = "" } = Astro.props
|
||||
{note || "该模块正在筹备中,尚未对外开放。站点正按模块逐个完善,你可以先浏览已上线的走秀档案。"}
|
||||
</p>
|
||||
<div class="cs-actions">
|
||||
<a class="cs-btn" href="/articles">浏览走秀档案 →</a>
|
||||
<a class="cs-btn-ghost" href="/">返回首页</a>
|
||||
<a class="cs-btn" href={href(locale, PATH.articles)}>浏览走秀档案 →</a>
|
||||
<a class="cs-btn-ghost" href={href(locale, PATH.home)}>返回首页</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import ComingSoon from "@/components/ComingSoon.astro"
|
||||
import { toAbs } from "@/lib/api"
|
||||
import { getI18n } from "@/i18n/utils"
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n'
|
||||
import { href, PATH } from '@/lib/routes'
|
||||
import { getSsgPopularBrands } from "@/lib/ssg"
|
||||
|
||||
// 国际化:一行取当前语言 + 翻译函数
|
||||
@ -66,7 +66,7 @@ const galleryItems: GalleryItem[] = popular
|
||||
D
|
||||
<span class="text-xs uppercase tracking-widest bg-zinc-200 text-zinc-600 px-2 py-0.5 rounded-full font-mono font-bold align-super">Soon</span>
|
||||
</li>
|
||||
<a href="/brands" class="tab text-[42px] font-extrabold cursor-pointer whitespace-nowrap hover:opacity-80">E</a>
|
||||
<a href={href(locale, PATH.brands)} class="tab text-[42px] font-extrabold cursor-pointer whitespace-nowrap hover:opacity-80">E</a>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -86,7 +86,7 @@ const galleryItems: GalleryItem[] = popular
|
||||
{
|
||||
galleryItems.map((g, i) => (
|
||||
<div class="swiper-slide hs-slide">
|
||||
<a class="hs-item" href={getRelativeLocaleUrl(locale, `/items/${g.articleId}`)} data-idx={i}>
|
||||
<a class="hs-item" href={href(locale, PATH.item(g.articleId))} data-idx={i}>
|
||||
<!-- Alpine 管理单张图片的加载/失败状态 -->
|
||||
<div
|
||||
class="hs-img-box"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
---
|
||||
import Breadcrumb from "@/components/Breadcrumb.astro"
|
||||
import { getBrands, getYears, type BrandEntry } from "@/lib/api"
|
||||
import { clientHref, PATH } from '@/lib/routes'
|
||||
import { getI18n } from '@/i18n/utils'
|
||||
|
||||
|
||||
@ -23,7 +24,6 @@ try {
|
||||
if (!yearChips.length) yearChips = yearSeed
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
<div class="showsroot w-full" x-data="showsPage()" data-locale={locale}>
|
||||
@ -185,7 +185,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
|
||||
<!-- Card List -->
|
||||
<template x-for="it in items" :key="it.id">
|
||||
<a class="showscard" :href="`/${locale}/runway-looks/${it.id}`" :aria-label="`${it.brand_name} ${it.title}`">
|
||||
<a class="showscard" :href="itemHref(it)" :aria-label="`${it.brand_name} ${it.title}`">
|
||||
<div class="showscard-left">
|
||||
<div class="showscard-img" :class="{ 'is-loaded': it._imgLoaded }">
|
||||
<span class="showsspin" aria-hidden="true" x-html="SPIN_SVG"></span>
|
||||
@ -270,7 +270,6 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
|
||||
</div>
|
||||
|
||||
<script id="showsbrands-data" type="application/json" set:html={JSON.stringify(sidebarBrands)}></script>
|
||||
|
||||
<style>
|
||||
@reference "tailwindcss";
|
||||
@ -585,7 +584,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
reqToken: 0,
|
||||
|
||||
init() {
|
||||
this.locale = this.$el.dataset.locale || 'zh-cn';
|
||||
this.locale = this.$el.dataset.locale || 'en';
|
||||
this.loadPage(1, false)
|
||||
},
|
||||
|
||||
@ -717,6 +716,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
if (kw) {
|
||||
this.modalLetter = ""
|
||||
this.modalLoading = true
|
||||
this.modalResults = []
|
||||
try {
|
||||
this.modalResults = await getBrands({ keyword: kw, size: 60 })
|
||||
} catch {
|
||||
@ -809,6 +809,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
|
||||
// 辅助辅助工具函数
|
||||
toAbsUrl(url: string) { return toAbs(url) },
|
||||
itemHref(it: { id: string | number }) { return clientHref(this.locale, PATH.runwayLooks(it.id)) },
|
||||
ctLabel(ct?: string) {
|
||||
const map: Record<string, string> = { rtw: "Ready-to-Wear", menswear: "Menswear", couture: "Couture", resort: "Resort", pre_fall: "Pre-Fall" }
|
||||
return ct && map[ct] ? map[ct] : (ct || "")
|
||||
|
||||
@ -2,20 +2,21 @@
|
||||
import type { RawArticle } from "@/lib/data"
|
||||
import Breadcrumb from "@/components/Breadcrumb.astro"
|
||||
import { getI18n } from "@/i18n/utils"
|
||||
import { href, PATH } from '@/lib/routes'
|
||||
import { ssrBase } from "@/lib/ssr"
|
||||
|
||||
interface Props {
|
||||
article: RawArticle | null
|
||||
apiBase?: string
|
||||
error?: "missing" | "failed" | null
|
||||
}
|
||||
|
||||
const { article, apiBase = "", error = null } = Astro.props
|
||||
const { locale, t, getRelativeLocaleUrl } = getI18n(Astro)
|
||||
const { article, error = null } = Astro.props
|
||||
const { locale, t } = getI18n(Astro)
|
||||
|
||||
const full = (u: string): string => {
|
||||
if (!u) return ""
|
||||
if (/^https?:\/\//i.test(u)) return u
|
||||
return apiBase.replace(/\/$/, "") + u
|
||||
return ssrBase().replace(/\/$/, "") + u
|
||||
}
|
||||
|
||||
const images = (article?.images ?? [])
|
||||
@ -31,8 +32,8 @@ const descParas = (article?.description || "")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const homeHref = getRelativeLocaleUrl(locale, "/")
|
||||
const archiveHref = getRelativeLocaleUrl(locale, "/shows")
|
||||
const homeHref = href(locale, PATH.home)
|
||||
const archiveHref = href(locale, PATH.shows)
|
||||
|
||||
const crumbs: { label: string; href: string }[] = [
|
||||
{
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
---
|
||||
import type { RawArticle } from "@/lib/data"
|
||||
import Breadcrumb from "@/components/Breadcrumb.astro"
|
||||
import { ssrBase } from "@/lib/ssr"
|
||||
|
||||
interface Props {
|
||||
article: RawArticle | null
|
||||
locale?: "en" | "cn"
|
||||
apiBase?: string
|
||||
error?: "missing" | "failed" | null
|
||||
}
|
||||
const { article, locale = "en", apiBase = "", error = null } = Astro.props
|
||||
const { article, locale = "en", error = null } = Astro.props
|
||||
|
||||
// 把相对图片地址补全为绝对地址(服务端完成,避免浏览器再拼)
|
||||
// 把相对图片地址补全为绝对地址(服务端完成,避免浏览器再拼;base 由 ssr.ts 统一解析)
|
||||
const full = (u: string): string =>
|
||||
!u ? "" : /^https?:\/\//i.test(u) ? u : apiBase.replace(/\/$/, "") + u
|
||||
!u ? "" : /^https?:\/\//i.test(u) ? u : ssrBase().replace(/\/$/, "") + u
|
||||
|
||||
// 图片数组:服务端预处理为灯箱可用的 {id,url,name}
|
||||
const images = (article?.images ?? []).map((im) => ({
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
import { getReports, fmtDate } from "@/lib/data"
|
||||
import { href, PATH } from '@/lib/routes'
|
||||
import { getI18n } from "@/i18n/utils"
|
||||
|
||||
// 文章展示创意布局页:复用你现有的 Layout 外壳。
|
||||
@ -8,8 +9,8 @@ import { getI18n } from "@/i18n/utils"
|
||||
|
||||
const reports = await getReports(30)
|
||||
const fmt = fmtDate
|
||||
const { locale, getRelativeLocaleUrl } = getI18n(Astro)
|
||||
const itemBase = getRelativeLocaleUrl(locale, "/items/").replace(/\/$/, "")
|
||||
const { locale } = getI18n(Astro)
|
||||
const itemBase = href(locale, "/items")
|
||||
---
|
||||
|
||||
<div class="archive" data-item-base={itemBase}>
|
||||
@ -24,7 +25,7 @@ const itemBase = getRelativeLocaleUrl(locale, "/items/").replace(/\/$/, "")
|
||||
<p>
|
||||
走秀趋势报告档案库。支持网格 / 杂志 / 时间轴三种创意视图自由切换,彩色配图由后端接口动态渲染。
|
||||
</p>
|
||||
<a href="/" class="archive-back">← Index</a>
|
||||
<a href={href(locale, PATH.home)} class="archive-back">← Index</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -68,7 +69,7 @@ const itemBase = getRelativeLocaleUrl(locale, "/items/").replace(/\/$/, "")
|
||||
style={`--ratio:${s.ratio}`}
|
||||
>
|
||||
<div class="card-datecol"><span class="card-date">{fmt(s.created_at)}</span></div>
|
||||
<a class="card-media" href={`${itemBase}/${s.id}`} aria-label={s.title}>
|
||||
<a class="card-media" href={href(locale, PATH.item(s.id))} aria-label={s.title}>
|
||||
<img src={s.cover} alt={s.title} loading="lazy" />
|
||||
{s.featured && <span class="card-flag">Featured</span>}
|
||||
</a>
|
||||
@ -266,18 +267,17 @@ const itemBase = getRelativeLocaleUrl(locale, "/items/").replace(/\/$/, "")
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { fetchArticlesList, toAbsPublic } from "@/lib/api"
|
||||
|
||||
const archive = document.querySelector<HTMLElement>(".archive")
|
||||
const gallery = document.getElementById("gallery") as HTMLElement | null
|
||||
const countEl = document.getElementById("count")
|
||||
const filterbar = document.getElementById("filterbar")
|
||||
const viewBtns = Array.from(document.querySelectorAll<HTMLButtonElement>(".viewswitch button"))
|
||||
const API_BASE = (import.meta.env.PUBLIC_API_BASE ?? "").replace(/\/$/, "")
|
||||
// 系列类型 / 季节筛选状态(对应 #typefilter / #seasonfilter 按钮;仅后端接口分支使用)
|
||||
let fType = ""
|
||||
let fSeason = ""
|
||||
const itemBase = archive?.dataset.itemBase || "/items"
|
||||
// 封面可能是完整外链(https://...),也可能是相对路径(/uploads/...),统一处理
|
||||
const toAbs = (url: string) => {
|
||||
if (!url) return ""
|
||||
return /^https?:\/\//i.test(url) ? url : API_BASE + url
|
||||
}
|
||||
|
||||
// 来自品牌索引的聚焦筛选(/brands 的品牌链接:?brand_id= 或 ?brand=)
|
||||
const _urlParams = new URLSearchParams(location.search)
|
||||
@ -420,18 +420,13 @@ const itemBase = getRelativeLocaleUrl(locale, "/items/").replace(/\/$/, "")
|
||||
}
|
||||
|
||||
const loadBackend = async () => {
|
||||
if (!API_BASE) return
|
||||
try {
|
||||
const size = _brandId ? 100 : 30
|
||||
const p = new URLSearchParams({ page: "1", size: String(size) })
|
||||
if (fType) p.set("collection_type", fType)
|
||||
if (fSeason) p.set("season", fSeason)
|
||||
if (_brandId) p.set("brand_id", _brandId)
|
||||
const url = `${API_BASE}/api/public/articles?${p.toString()}`
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) throw new Error("bad status")
|
||||
const json = await res.json()
|
||||
const rows = json?.data ?? []
|
||||
const rows = await fetchArticlesList({
|
||||
size: _brandId ? 100 : 30,
|
||||
collection_type: fType || undefined,
|
||||
season: fSeason || undefined,
|
||||
brand_id: _brandId || undefined,
|
||||
})
|
||||
if (!Array.isArray(rows) || rows.length === 0) return
|
||||
let items: Item[] = rows.map((r: any, i: number) => ({
|
||||
id: r.id,
|
||||
@ -439,7 +434,7 @@ const itemBase = getRelativeLocaleUrl(locale, "/items/").replace(/\/$/, "")
|
||||
brand: r.brand_name || "Unknown",
|
||||
year: r.year || "",
|
||||
created_at: r.published_at,
|
||||
cover: toAbs(r.cover || ""),
|
||||
cover: toAbsPublic(r.cover || ""),
|
||||
excerpt: r.summary || "",
|
||||
image_count: Number(r.image_count || 0),
|
||||
featured: false,
|
||||
|
||||
@ -12,6 +12,7 @@ type Brand = SsgBrand
|
||||
// · 无副标题、无数字 badge、无大字标题、无悬停黑底、无搜索框
|
||||
// 构建期 SSR 拉数据走 SSG 内部接口 /api/ssg/brands(不对外暴露,位于独立内部端口 8091);
|
||||
// base 地址 + token 拼接已在 @/lib/ssg 内封装。后端不可达则回落空数组(页面显示空索引)。
|
||||
const { locale } = getI18n(Astro)
|
||||
const brands = await getSsgBrands({ size: 300, only_with_articles: 1, featured: 1 })
|
||||
const loadError = brands.length === 0
|
||||
|
||||
@ -92,7 +93,7 @@ const total = orderedColumns.reduce((s, c) => s + c.items.length, 0)
|
||||
<ul class="brow-items">
|
||||
{col.items.map((b) => (
|
||||
<li>
|
||||
<a href={`/articles?brand_id=${b.id}`}>{primaryName(b)}</a>
|
||||
<a href={href(locale, PATH.articlesByBrand(b.id))}>{primaryName(b)}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user