@ -155,28 +155,10 @@ const streetItems: { src: string; loc: string; event: string; id: string }[] = s
|
||||
{
|
||||
streetItems.length > 0 ? (
|
||||
<section class="ss-section">
|
||||
<!-- 顶部 banner:第 1 条最热街拍 -->
|
||||
{
|
||||
streetItems[0] && (
|
||||
<a class="ss-banner" href={href(locale, ROUTES.streetSnap(streetItems[0].id))}>
|
||||
<div class="ss-img-box" x-data="{ loaded: false, error: false, init() { const i = this.$el.querySelector('img'); if (i && i.complete) { this.loaded = i.naturalWidth > 0; this.error = i.naturalWidth === 0; } } }" :class="{ 'is-loaded': loaded || error }">
|
||||
<div class="ss-skel" aria-hidden="true" x-show="!loaded && !error"></div>
|
||||
<span class="ss-spin" aria-hidden="true" x-show="!loaded && !error">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="40 22" class="ss-ring" /></svg>
|
||||
</span>
|
||||
<img src={streetItems[0].src} alt={streetItems[0].loc} loading="eager" decoding="async" :class="{ 'is-error': error }" @load="loaded = true; $dispatch('img-ready')" @error="error = true; $dispatch('img-ready')" />
|
||||
<span class="ss-cap">
|
||||
<span class="ss-cap-loc">{streetItems[0].loc}</span>
|
||||
<span class="ss-cap-event">{streetItems[0].event}</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 2 列大图卡片网格:其余街拍 -->
|
||||
<!-- 街拍大图卡片网格 -->
|
||||
<div class="ss-grid">
|
||||
{
|
||||
streetItems.slice(1).map((g) => (
|
||||
streetItems.map((g) => (
|
||||
<a class="ss-card" href={href(locale, ROUTES.streetSnap(g.id))}>
|
||||
<div class="ss-img-box" x-data="{ loaded: false, error: false, init() { const i = this.$el.querySelector('img'); if (i && i.complete) { this.loaded = i.naturalWidth > 0; this.error = i.naturalWidth === 0; } } }" :class="{ 'is-loaded': loaded || error }">
|
||||
<div class="ss-skel" aria-hidden="true" x-show="!loaded && !error"></div>
|
||||
@ -372,12 +354,8 @@ const streetItems: { src: string; loc: string; event: string; id: string }[] = s
|
||||
|
||||
/* ===== 首页 Street Style 网格(去掉 swiper,改为 HBX 大图卡片,与街拍列表页一致) ===== */
|
||||
.ss-section { @apply mb-9; }
|
||||
.ss-banner {
|
||||
@apply relative block w-full overflow-hidden bg-[#f0f0f0] no-underline text-inherit mb-7;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
.ss-grid {
|
||||
@apply grid grid-cols-1 sm:grid-cols-2 gap-[14px] md:gap-[18px];
|
||||
@apply grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-[14px] md:gap-[18px];
|
||||
}
|
||||
.ss-card {
|
||||
@apply relative block w-full overflow-hidden bg-[#f0f0f0] no-underline text-inherit;
|
||||
@ -394,8 +372,7 @@ const streetItems: { src: string; loc: string; event: string; id: string }[] = s
|
||||
}
|
||||
.ss-img-box.is-loaded > img { opacity: 1; }
|
||||
.ss-img-box > img.is-error { opacity: 0 !important; }
|
||||
.ss-card:hover .ss-img-box > img,
|
||||
.ss-banner:hover .ss-img-box > img { transform: scale(1.04); }
|
||||
.ss-card:hover .ss-img-box > img { transform: scale(1.04); }
|
||||
|
||||
/* 骨架屏 + spinner(与首页 hs-* 平行) */
|
||||
.ss-skel {
|
||||
@ -425,22 +402,15 @@ const streetItems: { src: string; loc: string; event: string; id: string }[] = s
|
||||
gap: 5px; z-index: 3; pointer-events: none;
|
||||
}
|
||||
.ss-cap-loc {
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(18px, 2vw, 26px);
|
||||
font-weight: 700; line-height: 1.1; letter-spacing: -0.01em;
|
||||
text-shadow: 0 1px 4px rgba(0,0,0,0.55);
|
||||
}
|
||||
.ss-cap-event {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
font-size: clamp(10px, 0.8vw, 12px);
|
||||
font-weight: 500; text-transform: uppercase; letter-spacing: 0.16em; line-height: 1.4;
|
||||
opacity: 0.85; text-shadow: 0 1px 3px rgba(0,0,0,0.5);
|
||||
}
|
||||
.ss-banner .ss-cap { padding: 44px 28px 26px; }
|
||||
.ss-banner .ss-cap-loc { font-size: clamp(26px, 4vw, 48px); }
|
||||
@media (min-width: 768px) {
|
||||
.ss-banner .ss-cap { padding: 56px 36px 30px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
@ -303,6 +303,10 @@ const seasonOptions: { value: string; label: string }[] = [
|
||||
|
||||
init() {
|
||||
this.locale = this.$el.dataset.locale || 'en';
|
||||
// 支持从 header 下拉深链 ?collection=xxx 直接进入对应分类(与侧栏 COLLECTION 筛选一致)
|
||||
const ct = new URLSearchParams(window.location.search).get('collection')
|
||||
const ALLOWED = ['rtw', 'menswear', 'womenswear', 'couture', 'resort', 'pre_fall', 'bridal', 'swimwear']
|
||||
if (ct && ALLOWED.includes(ct)) this.selectedCt = ct
|
||||
this.loadPage(1)
|
||||
},
|
||||
|
||||
|
||||
@ -22,6 +22,29 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
{ value: "year_desc", label: t('Newest year') },
|
||||
{ value: "year_asc", label: t('Oldest year') },
|
||||
]
|
||||
|
||||
// 地区筛选:值即后端 street_snap.city 列存储的规范英文名(与 backfill_city 词典保持一致)。
|
||||
// 标签用 t(city) —— 英文环境回退显示英文(巴黎→Paris),中文环境显示中文(Paris→巴黎)。
|
||||
const cityOptions: { value: string; label: string }[] = [
|
||||
{ value: "Paris", label: t('Paris') },
|
||||
{ value: "New York", label: t('New York') },
|
||||
{ value: "Milan", label: t('Milan') },
|
||||
{ value: "London", label: t('London') },
|
||||
{ value: "Copenhagen", label: t('Copenhagen') },
|
||||
{ value: "Florence", label: t('Florence') },
|
||||
{ value: "Berlin", label: t('Berlin') },
|
||||
{ value: "Tokyo", label: t('Tokyo') },
|
||||
{ value: "Seoul", label: t('Seoul') },
|
||||
{ value: "Shanghai", label: t('Shanghai') },
|
||||
{ value: "Beijing", label: t('Beijing') },
|
||||
{ value: "Rome", label: t('Rome') },
|
||||
{ value: "Madrid", label: t('Madrid') },
|
||||
{ value: "Amsterdam", label: t('Amsterdam') },
|
||||
{ value: "Antwerp", label: t('Antwerp') },
|
||||
{ value: "Los Angeles", label: t('Los Angeles') },
|
||||
{ value: "Moscow", label: t('Moscow') },
|
||||
{ value: "Mumbai", label: t('Mumbai') },
|
||||
]
|
||||
---
|
||||
|
||||
<div class="snapsroot w-full" x-data="streetSnapsPage()" data-locale={locale}>
|
||||
@ -88,6 +111,23 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- City / 地区 -->
|
||||
<section class="snapsg">
|
||||
<header class="snapsg-h" @click="toggleGroup('city')">
|
||||
<span>{t('City')}</span>
|
||||
<span class="snapsg-chev" :class="{ 'is-collapsed': collapsedGroups.city }">
|
||||
<svg viewBox="0 0 12 12" width="11" height="11"><path d="M2.5 4.5l3.5 3 3.5-3" stroke="currentColor" fill="none" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
</header>
|
||||
<div class="snapsg-b" x-show="!collapsedGroups.city">
|
||||
<ul class="snapschecks snapschecks--flat">
|
||||
{cityOptions.map((o) => (
|
||||
<li><label><input type="radio" name="snapCity" value={o.value} :checked={'selectedCity === "' + o.value + '"'} :class={`{'is-checked': selectedCity === '${o.value}'}`} @click.prevent={'selectedCity = selectedCity === "' + o.value + '" ? "" : "' + o.value + '"; loadPage(1)'} /> <span>{o.label}</span></label></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button class="snapsclear" type="button" @click="clearAll()" x-show="pills.length > 0" x-cloak>Clear all ×</button>
|
||||
</div>
|
||||
</aside>
|
||||
@ -172,7 +212,7 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="snaps-year-opts" type="application/json" set:html={JSON.stringify({ yearOptions, sortOptions })}></script>
|
||||
<script id="snaps-year-opts" type="application/json" set:html={JSON.stringify({ yearOptions, sortOptions, cityOptions })}></script>
|
||||
|
||||
|
||||
|
||||
@ -193,11 +233,13 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
SPIN_SVG,
|
||||
yearOptions: opts.yearOptions || [],
|
||||
sortOptions: opts.sortOptions || [],
|
||||
cityOptions: opts.cityOptions || [],
|
||||
selectedYear: '' as string,
|
||||
selectedSort: DEFAULT_SORT as string,
|
||||
selectedCity: '' as string,
|
||||
|
||||
// 侧栏分组折叠状态:默认全部收起(不做持久化,每次进页都是关着的)
|
||||
collapsedGroups: { year: true, sort: true } as Record<string, boolean>,
|
||||
collapsedGroups: { year: true, sort: true, city: true } as Record<string, boolean>,
|
||||
|
||||
items: [] as (StreetSnapItem & { _imgLoaded?: boolean })[],
|
||||
loading: false,
|
||||
@ -207,6 +249,10 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
|
||||
init() {
|
||||
this.locale = this.$el.dataset.locale || 'en'
|
||||
// 支持从 header 下拉深链 ?city=xxx 直接进入对应地区(与侧栏 City 筛选一致)
|
||||
const city = new URLSearchParams(window.location.search).get('city')
|
||||
const allowed = (this.cityOptions || []).map((c: any) => c.value)
|
||||
if (city && allowed.includes(city)) this.selectedCity = city
|
||||
this.loadPage(1)
|
||||
},
|
||||
|
||||
@ -217,6 +263,7 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
clearAll() {
|
||||
this.selectedYear = ''
|
||||
this.selectedSort = DEFAULT_SORT
|
||||
this.selectedCity = ''
|
||||
this.loadPage(1)
|
||||
},
|
||||
|
||||
@ -237,6 +284,13 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
clear: () => { this.selectedSort = DEFAULT_SORT; this.loadPage(1) },
|
||||
})
|
||||
}
|
||||
if (this.selectedCity) {
|
||||
const hit = this.cityOptions.find((o: any) => o.value === this.selectedCity)
|
||||
out.push({
|
||||
label: "CITY · " + (hit ? hit.label : this.selectedCity),
|
||||
clear: () => { this.selectedCity = ''; this.loadPage(1) },
|
||||
})
|
||||
}
|
||||
return out
|
||||
},
|
||||
|
||||
@ -255,6 +309,7 @@ const sortOptions: { value: string; label: string }[] = [
|
||||
const json = await getStreetSnaps({
|
||||
page,
|
||||
year: this.selectedYear || undefined,
|
||||
city: this.selectedCity || undefined,
|
||||
sort: this.selectedSort,
|
||||
})
|
||||
if (myToken !== this.reqToken) return
|
||||
|
||||
Reference in New Issue
Block a user