@ -6,68 +6,20 @@ import { getShows, seriesLabel } from "../lib/data"
|
||||
// 默认 tab(Shows)走 SSR:构建期从接口预取真实数据与多图,首屏立即可见。
|
||||
// 其余 tab(Backstage / Events / Street)点击时才由前端请求接口动态渲染,避免一次性拉全量拖慢首屏。
|
||||
const shows = await getShows(20)
|
||||
// 精选轮播:取前 5 场秀,做成「左图右文」卡片式轮播(Featured Carousel)
|
||||
const slides = shows.slice(0, 5)
|
||||
// 右侧品牌栏:跨分页拉取「与轮播不同品牌」的其他精选品牌,按品牌去重取 2 个
|
||||
const API_BASE = (import.meta.env.PUBLIC_API_BASE || "").replace(/\/$/, "")
|
||||
const slideBrands = new Set(slides.map(s => s.brand))
|
||||
const rail: typeof shows = []
|
||||
const railSeen = new Set<string>()
|
||||
if (API_BASE) {
|
||||
for (let pg = 1; pg <= 6 && rail.length < 6; pg++) {
|
||||
try {
|
||||
const rr = await fetch(`${API_BASE}/api/public/articles?page=${pg}&size=40&with_images=5`)
|
||||
if (!rr.ok) break
|
||||
const rj = await rr.json()
|
||||
const items: any[] = rj?.data ?? []
|
||||
for (const r of items) {
|
||||
const b = r.brand_name || "Unknown"
|
||||
if (slideBrands.has(b) || railSeen.has(b)) continue
|
||||
railSeen.add(b)
|
||||
rail.push({
|
||||
id: r.id, brand: b, title: r.title || "",
|
||||
collection_type: r.collection_type, season_code: r.season_code,
|
||||
year: Number(r.year) || 0,
|
||||
count: r.image_count || 0, img: r.cover || "",
|
||||
gallery: (r.images || []).map((im: any) => im.image || "").filter(Boolean).slice(0, 5),
|
||||
})
|
||||
if (rail.length >= 2) break
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
const railHot = [...rail].sort((a, b) => (b.count || 0) - (a.count || 0))
|
||||
// 最新取前 2;最热取 count 最高的 2;若两者完全相同(库内品牌少),改取最新之外的其它品牌,保证切换有可见差异
|
||||
const latestSet = rail.slice(0, 2)
|
||||
let hotSet = railHot.slice(0, 2)
|
||||
if (hotSet.length === 2 && latestSet.length === 2 &&
|
||||
hotSet[0].brand === latestSet[0].brand && hotSet[1].brand === latestSet[1].brand) {
|
||||
const lb = new Set(latestSet.map((r) => r.brand))
|
||||
const others = rail.filter((r) => !lb.has(r.brand)).slice(0, 2)
|
||||
if (others.length === 2) hotSet = others
|
||||
}
|
||||
|
||||
// 拼装右侧卡片的文案字段
|
||||
function railEyebrow(r: typeof shows[number]) {
|
||||
const m = (r.title || "").match(/(\d{4}).*?(Spring|Summer|Fall|Autumn|Winter|Resort|Pre-Fall|Cruise)/i)
|
||||
const season = m ? `${m[1]} ${m[2]}` : (r.season_code || "")
|
||||
const yr = r.year ? `${r.year}` : ""
|
||||
return [yr || (m ? m[1] : ""), season].filter(Boolean).join(" · ").toUpperCase() || "Featured Runway"
|
||||
}
|
||||
function railTagline(r: typeof shows[number]) {
|
||||
return `${seriesLabel(r.collection_type)}${r.season_code ? " · " + r.season_code : ""}`
|
||||
}
|
||||
function railDesc(r: typeof shows[number]) {
|
||||
const yr = r.year || ((r.title || "").match(/\b(19|20)\d{2}\b/) || [""])[0]
|
||||
const seasonTxt = (() => {
|
||||
const m = (r.title || "").match(/(Spring|Summer|Fall|Autumn|Winter|Resort|Pre-Fall|Cruise)/i)
|
||||
if (m) {
|
||||
const map: Record<string, string> = { Spring: "春", Summer: "夏", Fall: "秋冬", Autumn: "秋冬", Winter: "冬", Resort: "度假", "Pre-Fall": "早秋", Cruise: "度假" }
|
||||
return (map[m[1].toLowerCase().replace(/(^|\s)\w/g, (s) => s.toUpperCase())] || map[m[1]] || "") + (yr || "")
|
||||
}
|
||||
return yr ? `${yr} 发布` : ""
|
||||
})()
|
||||
return { seasonTxt: seasonTxt || (yr ? `${yr} 发布` : "") }
|
||||
// 横向滚动走秀画廊:扁平化所有 show 的图片(cover + gallery 去重),
|
||||
// 每张卡片 = 一个文章(show)的封面,不展开 gallery,避免同一文章重复出现。
|
||||
type GalleryItem = { src: string; brand: string; articleId: number; year: number; title: string; collection_type?: string }
|
||||
const galleryItems: GalleryItem[] = []
|
||||
for (const s of shows) {
|
||||
if (!s.img) continue
|
||||
galleryItems.push({
|
||||
src: s.img,
|
||||
brand: s.brand,
|
||||
articleId: s.id,
|
||||
year: s.year,
|
||||
title: s.title,
|
||||
collection_type: s.collection_type,
|
||||
})
|
||||
}
|
||||
---
|
||||
|
||||
@ -104,150 +56,39 @@ function railDesc(r: typeof shows[number]) {
|
||||
<a href="/shows" class="sec-more">全部走秀 →</a>
|
||||
</div>
|
||||
|
||||
<!-- 系列快速筛选(前端本地过滤,data-ct 对应 collection_type) -->
|
||||
<div class="seriesbar" id="seriesbar">
|
||||
<button class="schip on" data-ct="">全部</button>
|
||||
<button class="schip" data-ct="rtw">成衣</button>
|
||||
<button class="schip" data-ct="menswear">男装</button>
|
||||
<button class="schip" data-ct="couture">高定</button>
|
||||
<button class="schip" data-ct="resort">度假</button>
|
||||
<button class="schip" data-ct="pre_fall">早秋</button>
|
||||
</div>
|
||||
|
||||
<!-- 精选:左轮播(图上文下)+ 右品牌栏(横版卡片 ×2) -->
|
||||
{slides.length > 0 && (
|
||||
<section class="hero-carousel" id="heroCarousel" data-count={slides.length}>
|
||||
<div class="hc-stage">
|
||||
<div class="hc-rotator">
|
||||
{slides.map((s, i) => (
|
||||
<div class="hc-slide" data-index={i} data-ct={s.collection_type || "rtw"} style={i === 0 ? "" : "display:none"}>
|
||||
<a class="hc-media" href={`/article?id=${s.id}`} aria-label={s.brand}>
|
||||
<img src={s.gallery[0] || s.img} alt={s.brand} class="hc-img" loading={i === 0 ? "eager" : "lazy"} />
|
||||
<span class="prod-tag tl">Featured</span>
|
||||
</a>
|
||||
<div class="hc-text">
|
||||
<div class="hc-info">
|
||||
<span class="hc-eyebrow">{((s.title || "").match(/(\d{4}).*?(Spring|Summer|Fall|Autumn|Winter|Resort|Pre-Fall|Cruise)/i) || []).slice(1,3).join(" ") || s.season_code || "Featured Runway"}</span>
|
||||
<div class="hc-head">
|
||||
<h2 class="hc-brand">{s.brand}</h2>
|
||||
<p class="hc-tagline">{seriesLabel(s.collection_type)}{s.season_code ? " · " + s.season_code : ""}</p>
|
||||
</div>
|
||||
<div class="hc-bar-row">
|
||||
<a class="hc-bar" href={`/article?id=${s.id}`}>
|
||||
<span>Brand Analysis</span>
|
||||
<span class="hc-bar-plus">+</span>
|
||||
</a>
|
||||
<span class="hc-meta-count">{s.count} Look{s.count > 1 ? "s" : ""}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hc-controls">
|
||||
<span class="hc-index"><span class="hc-cur">{String(i + 1).padStart(2, "0")}</span> / {String(slides.length).padStart(2, "0")}</span>
|
||||
<div class="hc-nav">
|
||||
<button class="hc-arrow hc-prev" type="button" aria-label="上一张">←</button>
|
||||
<div class="hc-dots">
|
||||
{slides.map((_, di) => (
|
||||
<button class="hc-dot" type="button" data-i={di} aria-label={`第 ${di + 1} 张`} class:list={["hc-dot", di === 0 && "on"]}></button>
|
||||
))}
|
||||
<!-- 横向滚动画廊:左右滑动浏览;图片加载前骨架占位防偏移 -->
|
||||
{galleryItems.length > 0 && (
|
||||
<section class="hs-section">
|
||||
<div class="hs-stage">
|
||||
<div class="swiper hs-swiper" id="hsSwiper">
|
||||
<div class="swiper-wrapper">
|
||||
{galleryItems.map((g, i) => (
|
||||
<div class="swiper-slide hs-slide">
|
||||
<a class="hs-item" href={`/article?id=${g.articleId}`} data-idx={i}>
|
||||
<div class="hs-img-box">
|
||||
<div class="hs-skel" aria-hidden="true"></div>
|
||||
<span class="hs-spin" aria-hidden="true">
|
||||
<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="hs-ring" /></svg>
|
||||
</span>
|
||||
<img src={g.src} alt={g.brand} loading={i < 4 ? "eager" : "lazy"} decoding="async" />
|
||||
<span class="hs-caption">
|
||||
<span class="hs-cap-brand">{g.brand}</span>
|
||||
<span class="hs-cap-meta">{g.year ? g.year : ""}{g.collection_type ? " · " + seriesLabel(g.collection_type) : ""}</span>
|
||||
</span>
|
||||
</div>
|
||||
<button class="hc-arrow hc-next" type="button" aria-label="下一张">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{rail.length > 0 && (
|
||||
<aside class="hc-rail">
|
||||
<div class="hc-rail-head">
|
||||
<div class="hc-rail-tabs">
|
||||
<button type="button" class="on" data-sort="latest">最新</button>
|
||||
<button type="button" data-sort="hot">最热</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hc-rail-list" id="railList">
|
||||
<div class="rail-set" data-set="latest">
|
||||
{latestSet.map((r) => (
|
||||
<article class="rail-card">
|
||||
<div class="rail-text">
|
||||
<span class="rail-eyebrow">{railEyebrow(r)}</span>
|
||||
<h3 class="rail-brand">{r.brand}</h3>
|
||||
<p class="rail-tag">{railTagline(r)}</p>
|
||||
<a class="rail-bar" href={`/article?id=${r.id}`}>
|
||||
<span>Brand Analysis</span>
|
||||
<span class="rail-bar-plus">+</span>
|
||||
</a>
|
||||
<div class="rail-desc">{r.brand}:{railDesc(r).seasonTxt || "近期新作"} <span class="rail-dot">·</span> {r.count} 张</div>
|
||||
</div>
|
||||
<a class="rail-thumb" href={`/article?id=${r.id}`} aria-label={r.brand}>
|
||||
<img src={r.gallery[0] || r.img} alt={r.brand} loading="lazy" />
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<div class="rail-set" data-set="hot" style="display:none">
|
||||
{hotSet.map((r) => (
|
||||
<article class="rail-card">
|
||||
<div class="rail-text">
|
||||
<span class="rail-eyebrow">{railEyebrow(r)}</span>
|
||||
<h3 class="rail-brand">{r.brand}</h3>
|
||||
<p class="rail-tag">{railTagline(r)}</p>
|
||||
<a class="rail-bar" href={`/article?id=${r.id}`}>
|
||||
<span>Brand Analysis</span>
|
||||
<span class="rail-bar-plus">+</span>
|
||||
</a>
|
||||
<div class="rail-desc">{r.brand}:{railDesc(r).seasonTxt || "近期新作"} <span class="rail-dot">·</span> {r.count} 张</div>
|
||||
</div>
|
||||
<a class="rail-thumb" href={`/article?id=${r.id}`} aria-label={r.brand}>
|
||||
<img src={r.gallery[0] || r.img} alt={r.brand} loading="lazy" />
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
)}
|
||||
<div class="hs-controls flex justify-between items-center text-[13px] font-bold">
|
||||
<div class="hs-arrow hs-prev cursor-pointer hover:opacity-60 transition-opacity duration-200" role="button" tabindex="0">Previous</div>
|
||||
<div class="hs-arrow hs-next cursor-pointer hover:opacity-60 transition-opacity duration-200" role="button" tabindex="0">Next</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<!-- 卡片网格(轮播之外的剩余 shows) -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6" id="showgrid">
|
||||
{shows.slice(5).map((s) => (
|
||||
<article class="prod-card group" data-article-id={s.id} data-ct={s.collection_type || "rtw"}>
|
||||
<div class="prod-main">
|
||||
<img src={s.gallery[0] || s.img} alt={s.brand} class="prod-main-img" loading="lazy" />
|
||||
<span class="prod-tag tl">Runway</span>
|
||||
{s.count > 1 && <span class="prod-tag tr">{s.count} Pics</span>}
|
||||
<a href={`/article?id=${s.id}`} class="prod-viewall">查看全部 {s.count} 张 →</a>
|
||||
</div>
|
||||
<div class="prod-thumbs">
|
||||
{( s.gallery.length ? s.gallery : [s.img] ).map((g, gi) => (
|
||||
<button class="prod-thumb" type="button" aria-label={`查看第 ${gi + 1} 张`}>
|
||||
<img src={g} alt="" loading="lazy" />
|
||||
</button>
|
||||
))}
|
||||
{s.count > (s.gallery.length || 1) && (
|
||||
<a href={`/article?id=${s.id}`} class="prod-more">+{s.count - (s.gallery.length || 1)}</a>
|
||||
)}
|
||||
</div>
|
||||
<div class="prod-info">
|
||||
<div class="prod-name">{s.brand}</div>
|
||||
<div class="prod-meta">
|
||||
<span class="prod-chip">{seriesLabel(s.collection_type)}</span>
|
||||
{s.season_code && <span class="prod-chip">{s.season_code}</span>}
|
||||
</div>
|
||||
<div class="prod-sub">{s.title}</div>
|
||||
<div class="prod-foot">
|
||||
<a href={`/article?id=${s.id}`} class="prod-cta">查看 {s.count} 张 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<div class="showmore-wrap">
|
||||
<button id="showmore" class="showmore" type="button">查看更多走秀 ↓</button>
|
||||
</div>
|
||||
<div class="empty" id="showempty" hidden>该系列暂无可展示的走秀</div>
|
||||
</div>
|
||||
|
||||
<!-- 灵感 面板(暂未上线) -->
|
||||
@ -265,12 +106,6 @@ function railDesc(r: typeof shows[number]) {
|
||||
<ComingSoon module="街拍" moduleZh="Street" note="街拍板块正在筹备中,敬请期待。你可以先浏览已上线的走秀档案。" />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 底部/两侧切换按钮(保持你原来的) -->
|
||||
<div class="flex justify-between items-center text-[13px] font-bold mt-12">
|
||||
<div class="cursor-pointer hover:opacity-60 transition-opacity duration-200">Previous</div>
|
||||
<div class="cursor-pointer hover:opacity-60 transition-opacity duration-200">Next</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style is:global>
|
||||
@ -296,83 +131,8 @@ function railDesc(r: typeof shows[number]) {
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
|
||||
/* 卡片悬停上浮 + 阴影(恢复此前被误删的规则) */
|
||||
.prod-card:hover { @apply -translate-y-1 shadow-[0_16px_38px_rgba(0,0,0,0.13)]; }
|
||||
|
||||
/* 主图容器:固定 3:4 比例 + 骨架占位,图片未加载时尺寸恒定、显示加载动画 */
|
||||
.prod-main {
|
||||
@apply relative w-full aspect-[3/4] overflow-hidden bg-[#f0f0f0];
|
||||
background-image: linear-gradient(100deg, #f0f0f0 30%, #e6e6e6 50%, #f0f0f0 70%);
|
||||
background-size: 200% 100%;
|
||||
animation: img-shimmer 1.3s ease-in-out infinite;
|
||||
}
|
||||
.prod-main.is-loaded { animation: none; }
|
||||
/* 主图:绝对铺满容器并裁切,保证不同分辨率图展示一致、容器高度恒定 */
|
||||
.prod-main-img {
|
||||
@apply absolute inset-0 w-full h-full object-cover block origin-center;
|
||||
transition: transform .55s ease, opacity .22s ease;
|
||||
}
|
||||
/* 缩略图容器:固定 3:4 比例 + 骨架占位 */
|
||||
.prod-thumb {
|
||||
@apply bg-[#f0f0f0];
|
||||
background-image: linear-gradient(100deg, #f0f0f0 30%, #e6e6e6 50%, #f0f0f0 70%);
|
||||
background-size: 200% 100%;
|
||||
animation: img-shimmer 1.3s ease-in-out infinite;
|
||||
}
|
||||
.prod-thumb.is-loaded { animation: none; }
|
||||
@keyframes img-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
|
||||
|
||||
/* (.js 门控的淡入与 is-error 占位规则已移至下方 <style is:global>,避免组件作用域破坏 .js 选择器) */
|
||||
/* ===== 卡片 + 缩略图轮播 混合布局 ===== */
|
||||
:root { --brand: #cf5a44; }
|
||||
.prod-card {
|
||||
@apply flex flex-col bg-white border border-[#ececec] rounded-[6px] overflow-hidden transition-[transform,box-shadow] duration-300 will-change-transform;
|
||||
}
|
||||
.prod-card:hover .prod-main-img { @apply scale-105; }
|
||||
.prod-card:hover .prod-main { @apply shadow-[0_12px_26px_rgba(0,0,0,0.18)]; }
|
||||
.prod-tag {
|
||||
@apply absolute z-[2] font-mono text-[10px] uppercase tracking-[.18em] px-[8px] py-[4px];
|
||||
}
|
||||
.prod-tag.tl { @apply top-[10px] left-[10px] bg-white text-black; }
|
||||
.prod-tag.tr { @apply top-[10px] right-[10px] bg-black text-white flex gap-[5px] items-center; }
|
||||
.prod-tag.tr::before { content: ""; @apply w-[6px] h-[6px] rounded-full bg-white; }
|
||||
.prod-thumbs {
|
||||
@apply flex gap-2 px-[10px] pt-[10px] pb-[6px] overflow-x-auto;
|
||||
scrollbar-width: none; -ms-overflow-style: none;
|
||||
}
|
||||
.prod-thumbs::-webkit-scrollbar { display: none; }
|
||||
.prod-thumb {
|
||||
@apply flex-none w-[78px] aspect-[3/4] h-auto p-0 m-0 border-2 border-transparent bg-[#f2f2f2] cursor-pointer rounded-[4px] overflow-hidden opacity-75 transition-[border-color,opacity,transform] duration-200;
|
||||
}
|
||||
.prod-thumb img { @apply w-full h-full object-cover block; }
|
||||
.prod-thumb:hover { @apply opacity-100; }
|
||||
.prod-thumb.active { @apply border-[var(--brand)] opacity-100; }
|
||||
.prod-info { @apply px-[12px] pt-[8px] pb-[14px] flex flex-col gap-[6px] flex-1; }
|
||||
.prod-name { @apply text-[16px] font-extrabold leading-[1.2] text-[#111]; }
|
||||
.prod-sub { @apply text-[11px] uppercase tracking-[.14em] text-[#999] font-mono; }
|
||||
.prod-tags { @apply flex gap-[6px] flex-wrap; }
|
||||
.prod-badge {
|
||||
@apply text-[10px] uppercase tracking-[.08em] px-[8px] py-[3px] border border-[#ddd] rounded-full text-[#666] bg-white;
|
||||
}
|
||||
.prod-badge.new { @apply bg-[#111] text-white border-[#111]; }
|
||||
.prod-badge.hot { @apply bg-[var(--brand)] text-white border-[var(--brand)]; }
|
||||
.prod-foot { @apply flex items-center justify-between mt-auto pt-[4px]; }
|
||||
.prod-price { @apply text-[15px] font-extrabold tracking-[.02em] text-[#111]; }
|
||||
.prod-cta { @apply text-[11px] uppercase tracking-[.14em] font-bold no-underline; color: var(--brand); }
|
||||
.prod-card:hover .prod-cta { @apply underline; }
|
||||
|
||||
/* 缩略图条末尾的"更多"入口:提示还有更多图片,点击进入详情看全部 */
|
||||
.prod-more {
|
||||
@apply flex-none w-[78px] aspect-[3/4] h-auto flex items-center justify-center bg-[var(--brand)] text-white no-underline rounded-[4px] text-[13px] font-extrabold tracking-[.02em] transition-[filter,transform] duration-200;
|
||||
}
|
||||
.prod-more:hover { @apply brightness-[1.08] -translate-y-[2px]; }
|
||||
|
||||
/* 主图悬停时浮现"查看全部 N 张"浮层(桌面 hover 生效,移动端不拦截 pinch) */
|
||||
.prod-viewall {
|
||||
@apply absolute inset-0 z-[3] flex items-end justify-center pb-[16px] text-white text-[12px] font-bold tracking-[.14em] uppercase no-underline opacity-0 pointer-events-none transition-opacity duration-[250ms] ease-[ease];
|
||||
background: linear-gradient(to top, rgba(0,0,0,.6), rgba(0,0,0,0) 55%);
|
||||
}
|
||||
.prod-card:hover .prod-viewall { @apply opacity-100 pointer-events-auto; }
|
||||
|
||||
/* 标签页激活态:品牌色下划线动画 */
|
||||
.tab { @apply relative pb-[6px]; }
|
||||
@ -403,88 +163,144 @@ function railDesc(r: typeof shows[number]) {
|
||||
.sec-more { @apply text-[12px] uppercase tracking-[.14em] font-bold no-underline text-[#111] transition-opacity duration-200; }
|
||||
.sec-more:hover { @apply opacity-60; }
|
||||
|
||||
/* 系列快速筛选 */
|
||||
.seriesbar { @apply flex items-center gap-2 overflow-x-auto mb-7 pb-1; scrollbar-width: none; -ms-overflow-style: none; }
|
||||
.seriesbar::-webkit-scrollbar { display: none; }
|
||||
.schip {
|
||||
@apply flex-none font-mono text-[11px] uppercase tracking-[.12em] px-[14px] py-[6px] rounded-full border border-[#ddd] bg-white text-[#666] cursor-pointer transition-colors duration-200;
|
||||
/* 横向滚动画廊(Runway Gallery):水平 flex 容器,每张图按统一卡片尺寸展示、
|
||||
左右滑动浏览。图片用 object-fit:cover 适配卡片框(不再因原图宽高比过宽);
|
||||
加载前骨架 shimmer 占位,加载完图片淡入、骨架淡出。 */
|
||||
.hs-section { @apply mb-9; }
|
||||
.hs-head {
|
||||
@apply flex items-end justify-between gap-4 pb-4 mb-4 border-b border-[#ececec];
|
||||
}
|
||||
.schip:hover { @apply text-[#111] border-[#111]; }
|
||||
.schip.on { @apply bg-[#111] text-white border-[#111]; }
|
||||
.hs-title { @apply font-serif text-[28px] leading-none font-extrabold text-[#111] m-0 tracking-[-.01em]; }
|
||||
.hs-sub { @apply font-mono text-[12px] uppercase tracking-[.14em] text-[#999] mt-2 mb-0; }
|
||||
.hs-stage { @apply relative; }
|
||||
.hs-controls {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
z-index: 6;
|
||||
padding: 0 8px;
|
||||
pointer-events: none; /* 容器不挡拖拽,仅按钮可点 */
|
||||
mix-blend-mode: difference; /* 随背景反相,始终可见 */
|
||||
color: #fff; /* 白色源色经 difference 后随底图反相 */
|
||||
}
|
||||
.hs-controls .hs-arrow { pointer-events: auto; }
|
||||
.hs-arrow { @apply select-none; } /* 仅保留 Swiper 绑定所需的类,视觉完全由内联类名控制 */
|
||||
.hs-arrow.swiper-button-disabled { pointer-events: none; } /* 禁用时保持原色,仅不可点击 */
|
||||
|
||||
/* 精选 feat group:橄榄绿 editorial 重排(保留左大图轮播 + 右品牌栏) */
|
||||
.hero-carousel { @apply relative mb-9; }
|
||||
.hc-stage { @apply relative flex flex-col; }
|
||||
@media (min-width: 768px) { .hc-stage { flex-direction: row; gap: 32px; align-items: stretch; } }
|
||||
.hc-rotator { display: flex; flex-direction: column; min-height: 0; }
|
||||
@media (min-width: 768px) { .hc-rotator { flex: 1 1 0%; min-height: 560px; } }
|
||||
.hc-slide { display: flex; flex-direction: column; flex: 1 1 0%; min-height: 0; gap: 20px; }
|
||||
.hc-media {
|
||||
position: relative; flex: 1 1 0%; min-height: 320px; width: 100%; overflow: hidden;
|
||||
border-radius: 2px; background: #f0f0f0; display: block; text-decoration: none;
|
||||
/* Swiper 容器:高度恒定(内容区固定);宽度由 slide 自适应撑开 */
|
||||
.hs-swiper {
|
||||
width: 100%;
|
||||
height: clamp(500px, 80vh, 900px); /* 固定高度,不随图片尺寸变化(已调高) */
|
||||
padding: 0 4px;
|
||||
}
|
||||
@media (min-width: 768px) { .hc-media { min-height: 480px; } }
|
||||
.hc-img { @apply absolute inset-0 w-full h-full object-cover block origin-center transition-transform duration-[600ms] ease-out; }
|
||||
.hc-media:hover .hc-img { @apply scale-[1.03]; }
|
||||
.hc-text { @apply flex flex-col gap-[18px] flex-shrink-0; }
|
||||
.hc-info { @apply flex flex-col gap-3; }
|
||||
.hc-eyebrow { @apply block font-mono text-[11px] uppercase tracking-[.24em] text-[#8a8455]; }
|
||||
.hc-head { @apply flex items-baseline gap-4 flex-wrap; }
|
||||
.hc-brand { @apply font-serif font-extrabold text-[#14110d] m-0 tracking-[-.02em] leading-[1.0]; font-size: 34px; }
|
||||
@media (min-width: 768px) { .hc-brand { font-size: 46px; } }
|
||||
.hc-tagline { @apply font-mono text-[11px] uppercase tracking-[.18em] text-[#8a8455] m-0; }
|
||||
.hc-bar-row { @apply flex items-center gap-4 pt-[14px] border-t border-[#e6e2d8]; }
|
||||
.hc-bar {
|
||||
@apply inline-flex items-center justify-between gap-4 px-4 py-[10px] text-white font-mono text-[10px] uppercase tracking-[.2em] font-bold no-underline border transition-colors duration-200;
|
||||
background: #8a8455; border-color: #8a8455;
|
||||
}
|
||||
.hc-bar:hover { @apply no-underline; background: #fff; color: #8a8455; }
|
||||
.hc-bar-plus { @apply text-[16px] leading-none font-light; }
|
||||
.hc-meta-count { @apply font-mono text-[11px] uppercase tracking-[.16em] text-[#9a937f]; }
|
||||
.hc-controls { @apply flex items-center justify-between pt-[14px] border-t border-[#e6e2d8]; }
|
||||
.hc-index { @apply font-mono text-[12px] tracking-[.2em] text-[#9a937f]; }
|
||||
.hc-index .hc-cur { @apply font-bold; color: #8a8455; }
|
||||
.hc-nav { @apply flex items-center gap-4; }
|
||||
.hc-arrow {
|
||||
@apply w-9 h-9 rounded-full flex items-center justify-center cursor-pointer bg-white no-underline select-none transition-colors duration-200;
|
||||
border: 1px solid #8a8455; color: #8a8455;
|
||||
}
|
||||
.hc-arrow:hover { background: #8a8455; color: #fff; }
|
||||
.hc-dots { @apply flex gap-2 items-center; }
|
||||
.hc-dot { @apply w-2 h-2 rounded-full border-0 p-0 cursor-pointer transition-colors duration-200; background: #dcd8cc; }
|
||||
.hc-dot.on { background: #8a8455; }
|
||||
.hs-swiper .swiper-wrapper { align-items: center; } /* 卡片在固定高度的容器内垂直居中 */
|
||||
|
||||
/* 右侧品牌栏(横版卡片,图左文右,×2 等高) */
|
||||
.hc-rail { @apply flex flex-col min-h-0; }
|
||||
@media (min-width: 768px) { .hc-rail { width: 34%; flex-shrink: 0; border-left: 1px solid #e6e2d8; padding-left: 24px; } }
|
||||
.hc-rail-head { @apply mb-4; }
|
||||
.hc-rail-tabs { @apply flex gap-3; }
|
||||
.hc-rail-tabs button { @apply font-mono text-[11px] uppercase tracking-[.14em] bg-none border-0 p-0 cursor-pointer transition-colors duration-200; color: #b8b2a2; }
|
||||
.hc-rail-tabs button.on { @apply font-bold pb-[2px] border-b; color: #8a8455; border-color: #8a8455; }
|
||||
.hc-rail-list { @apply flex flex-col flex-1 overflow-y-auto min-h-0; scrollbar-width: thin; scrollbar-color: #dcd8cc #fff; }
|
||||
.hc-rail-list::-webkit-scrollbar { @apply w-[6px]; }
|
||||
.hc-rail-list::-webkit-scrollbar-thumb { @apply bg-[#dcd8cc] rounded-full; }
|
||||
.rail-set { @apply list-none m-0 p-0 flex flex-col flex-1 min-h-0 gap-4; }
|
||||
.rail-card { @apply flex flex-row-reverse flex-1 min-h-0 gap-3 items-stretch; }
|
||||
.rail-text { @apply w-[42%] flex flex-col justify-between min-w-0 py-[2px]; }
|
||||
.rail-eyebrow { @apply block font-mono text-[10px] uppercase tracking-[.16em] text-[#8a8455]; }
|
||||
.rail-brand { @apply font-serif font-extrabold text-[#14110d] m-0 mt-[2px] leading-[1.02] tracking-[-.01em]; font-size: 22px; }
|
||||
.rail-tag { @apply font-mono text-[10px] uppercase tracking-[.12em] m-0 mt-[2px]; color: #9a937f; }
|
||||
.rail-bar {
|
||||
@apply flex items-center justify-between w-full mt-3 px-3 py-[8px] text-white font-mono text-[10px] uppercase tracking-[.16em] font-bold no-underline border transition-colors duration-200;
|
||||
background: #8a8455; border-color: #8a8455;
|
||||
/* 每张 slide:尺寸完全由内部图片决定(slidesPerView:"auto" 取自身宽) */
|
||||
.hs-swiper .hs-slide {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.rail-bar:hover { @apply no-underline; background: #fff; color: #8a8455; }
|
||||
.rail-bar-plus { @apply text-[14px] leading-none font-light; }
|
||||
.rail-desc { @apply font-mono text-[10px] mt-2 leading-[1.5] tracking-[.02em]; color: #9a937f; }
|
||||
.rail-dot { @apply mx-[3px]; color: #ccc; }
|
||||
.rail-thumb {
|
||||
position: relative; flex: 1; min-height: 160px; border-radius: 2px; overflow: hidden;
|
||||
background: #f0f0f0; display: block; text-decoration: none;
|
||||
|
||||
.hs-item {
|
||||
display: block;
|
||||
height: auto; /* 高度交给图片框,按图片比例自适应 */
|
||||
width: max-content; /* 宽度收缩到图片自身宽度 */
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.hs-img-box {
|
||||
position: relative;
|
||||
display: block;
|
||||
/* 加载前默认占位:统一高度的 3:2 横向占位,避免塌缩;加载后由 JS 按真实比例写入精确 px */
|
||||
height: clamp(500px, 80vh, 900px);
|
||||
/* aspect-ratio: 3 / 2; */
|
||||
background: #f0f0f0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 图片:填满图片框;图片框的比例由 JS 按真实宽高比设定,故完整显示、不裁切、不变形 */
|
||||
.hs-img-box > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
.hs-img-box.is-loaded > img { opacity: 1; }
|
||||
|
||||
/* 骨架占位:绝对覆盖 img-box,shimmer 动画;加载完淡出(与图片淡入同步) */
|
||||
.hs-skel {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background-image: linear-gradient(100deg, #f0f0f0 30%, #e6e6e6 50%, #f0f0f0 70%);
|
||||
background-size: 200% 100%;
|
||||
animation: hs-shimmer 1.3s ease-in-out infinite;
|
||||
transition: opacity 0.35s ease;
|
||||
}
|
||||
.hs-img-box.is-loaded .hs-skel { opacity: 0; }
|
||||
|
||||
/* 加载中旋转环(细线灰调) */
|
||||
.hs-spin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #bdbdbd;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.hs-img-box.is-loaded .hs-spin { opacity: 0; }
|
||||
.hs-spin svg { display: block; }
|
||||
.hs-ring { transform-origin: 12px 12px; animation: hs-rotate 0.8s linear infinite; }
|
||||
@keyframes hs-rotate { to { transform: rotate(360deg); } }
|
||||
@keyframes hs-shimmer {
|
||||
from { background-position: 200% 0; }
|
||||
to { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* 悬停浮现品牌标签 */
|
||||
.hs-caption {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 28px 16px 12px;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 12px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
z-index: 3;
|
||||
font-family: monospace;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hs-item:hover .hs-caption { opacity: 1; }
|
||||
.hs-cap-brand {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.hs-cap-meta {
|
||||
font-size: 11px;
|
||||
opacity: 0.85;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hs-swiper { height: clamp(340px, 56vh, 540px); }
|
||||
.hs-img-box { height: clamp(340px, 56vh, 540px); }
|
||||
.hs-head { flex-direction: column; align-items: flex-start; gap: 12px; }
|
||||
}
|
||||
@media (min-width: 768px) { .rail-thumb { min-height: 0; } }
|
||||
.rail-thumb img { @apply absolute inset-0 w-full h-full object-cover transition-transform duration-[600ms] ease-out; }
|
||||
.rail-thumb:hover img { @apply scale-[1.04]; }
|
||||
|
||||
/* 卡片上的系列 / 季节标签 */
|
||||
.prod-meta { @apply flex gap-2 flex-wrap mt-1; }
|
||||
@ -493,28 +309,24 @@ function railDesc(r: typeof shows[number]) {
|
||||
}
|
||||
.empty { @apply text-center text-[13px] text-[#999] py-16 font-mono uppercase tracking-[.12em]; }
|
||||
|
||||
/* 查看更多(首页走秀栏内联分页) */
|
||||
.showmore-wrap { @apply flex justify-center mt-10; }
|
||||
.showmore {
|
||||
@apply font-mono uppercase tracking-[.16em] text-[12px] px-[28px] py-[14px] border border-[#111] bg-white text-[#111] rounded-full cursor-pointer transition-colors duration-200;
|
||||
}
|
||||
.showmore:hover { @apply bg-[#111] text-white; }
|
||||
.showmore[disabled] { @apply opacity-40 cursor-default; }
|
||||
|
||||
/* (图片堆方案已移除,改由卡片内缩略图条展示多图) */
|
||||
/* (图片堆方案已移除,改由横向画廊展示多图) */
|
||||
|
||||
</style>
|
||||
|
||||
<style is:global>
|
||||
/* JS 启用时主图/缩略图先透明,加载完成淡入;失败保留占位骨架(is:global 避免组件作用域误伤 .js 选择器) */
|
||||
.js .prod-main-img { @apply opacity-0; }
|
||||
.js .prod-main-img.is-loaded { @apply opacity-100; }
|
||||
.js .prod-thumb img { @apply opacity-0; }
|
||||
.js .prod-thumb img.is-loaded { @apply opacity-100; }
|
||||
.prod-main-img.is-error, .prod-thumb img.is-error { opacity: 0 !important; }
|
||||
/* JS 启用时画廊图片先透明,加载完成淡入;失败保留占位骨架(is:global 避免组件作用域误伤 .js 选择器) */
|
||||
.js .hs-img-box > img { opacity: 0; }
|
||||
.js .hs-img-box.is-loaded > img { opacity: 1; }
|
||||
.hs-img-box > img.is-error { opacity: 0 !important; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Swiper from "swiper";
|
||||
import { Navigation, FreeMode, Keyboard } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/free-mode";
|
||||
|
||||
// 标签页交互:默认 Shows 为 SSR;点击其它标签时按需请求接口渲染(懒加载)
|
||||
const API_BASE = (import.meta.env.PUBLIC_API_BASE || "http://localhost:8090").replace(/\/$/, "");
|
||||
const tabs = Array.from(document.querySelectorAll<HTMLElement>("#tabbar .tab"));
|
||||
@ -599,59 +411,59 @@ function railDesc(r: typeof shows[number]) {
|
||||
</article>`;
|
||||
}
|
||||
|
||||
function initCardInteractions(root: ParentNode) {
|
||||
root.querySelectorAll<HTMLElement>(".prod-card").forEach((card) => {
|
||||
const main = card.querySelector<HTMLImageElement>(".prod-main-img");
|
||||
const thumbs = Array.from(card.querySelectorAll<HTMLElement>(".prod-thumb"));
|
||||
if (!main || !thumbs.length) return;
|
||||
thumbs[0].classList.add("active");
|
||||
thumbs.forEach((t) => {
|
||||
t.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const img = t.querySelector("img");
|
||||
const src = img?.getAttribute("src") || "";
|
||||
if (!src || main.getAttribute("src") === src) return;
|
||||
main.classList.remove("is-loaded");
|
||||
main.parentElement?.classList.remove("is-loaded");
|
||||
main.style.opacity = "0";
|
||||
window.setTimeout(() => {
|
||||
main.setAttribute("src", src);
|
||||
main.style.opacity = "1";
|
||||
}, 180);
|
||||
thumbs.forEach((x) => x.classList.remove("active"));
|
||||
t.classList.add("active");
|
||||
});
|
||||
});
|
||||
main.addEventListener("load", () => {
|
||||
main.classList.add("is-loaded");
|
||||
main.parentElement?.classList.add("is-loaded");
|
||||
});
|
||||
enablePinch(card.querySelector<HTMLElement>(".prod-main"));
|
||||
});
|
||||
root.querySelectorAll<HTMLImageElement>(".prod-main-img, .prod-thumb img").forEach(watchImg);
|
||||
}
|
||||
// function initCardInteractions(root: ParentNode) {
|
||||
// root.querySelectorAll<HTMLElement>(".prod-card").forEach((card) => {
|
||||
// const main = card.querySelector<HTMLImageElement>(".prod-main-img");
|
||||
// const thumbs = Array.from(card.querySelectorAll<HTMLElement>(".prod-thumb"));
|
||||
// if (!main || !thumbs.length) return;
|
||||
// thumbs[0].classList.add("active");
|
||||
// thumbs.forEach((t) => {
|
||||
// t.addEventListener("click", (e) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// const img = t.querySelector("img");
|
||||
// const src = img?.getAttribute("src") || "";
|
||||
// if (!src || main.getAttribute("src") === src) return;
|
||||
// main.classList.remove("is-loaded");
|
||||
// main.parentElement?.classList.remove("is-loaded");
|
||||
// main.style.opacity = "0";
|
||||
// window.setTimeout(() => {
|
||||
// main.setAttribute("src", src);
|
||||
// main.style.opacity = "1";
|
||||
// }, 180);
|
||||
// thumbs.forEach((x) => x.classList.remove("active"));
|
||||
// t.classList.add("active");
|
||||
// });
|
||||
// });
|
||||
// main.addEventListener("load", () => {
|
||||
// main.classList.add("is-loaded");
|
||||
// main.parentElement?.classList.add("is-loaded");
|
||||
// });
|
||||
// enablePinch(card.querySelector<HTMLElement>(".prod-main"));
|
||||
// });
|
||||
// root.querySelectorAll<HTMLImageElement>(".prod-main-img, .prod-thumb img").forEach(watchImg);
|
||||
// }
|
||||
|
||||
async function loadPanel(name: string) {
|
||||
const panel = panels.find((p) => p.getAttribute("data-panel") === name);
|
||||
if (!panel || panel.getAttribute("data-loaded") === "true" || panel.getAttribute("data-loading") === "true") return;
|
||||
panel.setAttribute("data-loading", "true");
|
||||
const grid = panel.querySelector<HTMLElement>(".grid");
|
||||
if (grid) grid.innerHTML = skeletonHTML(8);
|
||||
try {
|
||||
const all = await ensurePanelData();
|
||||
const [a, b] = PANEL_SLICE[name] || [0, 8];
|
||||
const items = all.slice(a, b);
|
||||
if (grid) {
|
||||
grid.innerHTML = items.map((it, i) => cardHTML(it, i, LABEL[name] || "Show")).join("");
|
||||
initCardInteractions(grid);
|
||||
}
|
||||
panel.setAttribute("data-loaded", "true");
|
||||
} catch {
|
||||
if (grid) grid.innerHTML = `<div class="text-sm text-gray-400 p-6">内容加载失败,请重试</div>`;
|
||||
}
|
||||
panel.setAttribute("data-loading", "false");
|
||||
}
|
||||
// async function loadPanel(name: string) {
|
||||
// const panel = panels.find((p) => p.getAttribute("data-panel") === name);
|
||||
// if (!panel || panel.getAttribute("data-loaded") === "true" || panel.getAttribute("data-loading") === "true") return;
|
||||
// panel.setAttribute("data-loading", "true");
|
||||
// const grid = panel.querySelector<HTMLElement>(".grid");
|
||||
// if (grid) grid.innerHTML = skeletonHTML(8);
|
||||
// try {
|
||||
// const all = await ensurePanelData();
|
||||
// const [a, b] = PANEL_SLICE[name] || [0, 8];
|
||||
// const items = all.slice(a, b);
|
||||
// if (grid) {
|
||||
// grid.innerHTML = items.map((it, i) => cardHTML(it, i, LABEL[name] || "Show")).join("");
|
||||
// initCardInteractions(grid);
|
||||
// }
|
||||
// panel.setAttribute("data-loaded", "true");
|
||||
// } catch {
|
||||
// if (grid) grid.innerHTML = `<div class="text-sm text-gray-400 p-6">内容加载失败,请重试</div>`;
|
||||
// }
|
||||
// panel.setAttribute("data-loading", "false");
|
||||
// }
|
||||
|
||||
tabs.forEach((tab) => {
|
||||
// Brands 是导航型标签(<a href="/brands">):点击直接跳转品牌档案页,不切换面板
|
||||
@ -669,80 +481,6 @@ function railDesc(r: typeof shows[number]) {
|
||||
});
|
||||
});
|
||||
|
||||
// 首页发布会面板:系列快速筛选(前端本地过滤,按 data-ct 显示/隐藏)
|
||||
let homeFType = ""
|
||||
const seriesApply = (function () {
|
||||
const bar = document.getElementById("seriesbar");
|
||||
if (!bar) return () => {};
|
||||
const grid = document.getElementById("showgrid");
|
||||
const empty = document.getElementById("showempty");
|
||||
const chips = Array.from(bar.querySelectorAll<HTMLButtonElement>(".schip"));
|
||||
function apply(ct: string) {
|
||||
homeFType = ct;
|
||||
const ok = (el: Element | null) => !!el && (!ct || (el.getAttribute("data-ct") || "") === ct);
|
||||
let vis = 0;
|
||||
if (grid) grid.querySelectorAll<HTMLElement>(".prod-card").forEach((c) => {
|
||||
const m = ok(c); c.style.display = m ? "" : "none"; if (m) vis++;
|
||||
});
|
||||
// 精选轮播(Featured)始终展示,不参与系列筛选隐藏
|
||||
if (empty) empty.hidden = vis > 0;
|
||||
}
|
||||
chips.forEach((b) => b.addEventListener("click", () => {
|
||||
chips.forEach((x) => x.classList.toggle("on", x === b));
|
||||
apply(b.getAttribute("data-ct") || "");
|
||||
}));
|
||||
return apply;
|
||||
})();
|
||||
|
||||
// 首页发布会面板:查看更多(前端分页追加,带系列筛选参数)
|
||||
(function () {
|
||||
let homePage = 2;
|
||||
let homeBusy = false;
|
||||
const btn = document.getElementById("showmore") as HTMLButtonElement | null;
|
||||
if (!btn) return;
|
||||
btn.addEventListener("click", async () => {
|
||||
if (homeBusy) return;
|
||||
homeBusy = true;
|
||||
const orig = btn.textContent;
|
||||
btn.textContent = "加载中…";
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const p = new URLSearchParams({ page: String(homePage), size: "8", with_images: "5" });
|
||||
if (homeFType) p.set("collection_type", homeFType);
|
||||
const res = await fetch(`${API_BASE}/api/public/articles?${p.toString()}`);
|
||||
const json = await res.json();
|
||||
const rows = json?.data ?? [];
|
||||
const grid = document.getElementById("showgrid");
|
||||
if (grid && rows.length) {
|
||||
const html = rows.map((r: any) => {
|
||||
const g = (r.images || []).map((im: any) => toAbs(im.image || "")).filter(Boolean).slice(0, 5);
|
||||
const it = {
|
||||
id: r.id, brand: r.brand_name || "Unknown", title: r.title || "",
|
||||
season: r.year ? "AW" + String(r.year).slice(-2) : "",
|
||||
season_code: r.season_code || "", collection_type: r.collection_type || "",
|
||||
year: r.year, img: toAbs(r.cover || ""), count: r.image_count || 0, gallery: g,
|
||||
};
|
||||
return cardHTML(it, 99, "Runway");
|
||||
}).join("");
|
||||
grid.insertAdjacentHTML("beforeend", html);
|
||||
initCardInteractions(grid);
|
||||
seriesApply(homeFType);
|
||||
homePage++;
|
||||
if (rows.length < 8) { btn.textContent = "没有更多了"; return; }
|
||||
} else {
|
||||
btn.textContent = "没有更多了"; return;
|
||||
}
|
||||
btn.textContent = orig;
|
||||
btn.disabled = false;
|
||||
} catch {
|
||||
btn.textContent = orig;
|
||||
btn.disabled = false;
|
||||
} finally {
|
||||
homeBusy = false;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// 启用 JS 标记:用于渐进增强的占位符淡入(无 JS 时图片直接显示,不影响可用性)
|
||||
document.documentElement.classList.add("js");
|
||||
|
||||
@ -766,7 +504,7 @@ function railDesc(r: typeof shows[number]) {
|
||||
}
|
||||
|
||||
// 初始化 SSR 的 Shows 面板卡片交互(缩略图切换 / 占位淡入 / 双指缩放)
|
||||
initCardInteractions(document);
|
||||
// initCardInteractions(document);
|
||||
|
||||
// 移动端双指缩放主图(pinch-to-zoom)+ 单指平移
|
||||
function enablePinch(box: HTMLElement | null) {
|
||||
@ -810,55 +548,51 @@ function railDesc(r: typeof shows[number]) {
|
||||
});
|
||||
}
|
||||
|
||||
// 首页精选轮播(左图右文卡片式)
|
||||
// 横向滚动画廊(Swiper):容器高度固定,按每张图片原始宽高比自适应该卡尺寸
|
||||
(function () {
|
||||
const root = document.getElementById("heroCarousel");
|
||||
if (!root) return;
|
||||
const slides = Array.from(root.querySelectorAll<HTMLElement>(".hc-slide"));
|
||||
const total = slides.length;
|
||||
if (total <= 1) {
|
||||
root.querySelectorAll<HTMLElement>(".hc-controls").forEach((c) => (c.style.display = "none"));
|
||||
return;
|
||||
}
|
||||
let idx = 0;
|
||||
let timer: number | undefined;
|
||||
const dots = Array.from(root.querySelectorAll<HTMLButtonElement>(".hc-dot"));
|
||||
function go(n: number) {
|
||||
idx = (n + total) % total;
|
||||
slides.forEach((s, i) => { s.style.display = i === idx ? "" : "none"; });
|
||||
// 每屏各自带一套 controls,只高亮当前可见 slide 内的圆点
|
||||
const visDots = slides[idx].querySelectorAll<HTMLElement>(".hc-dot");
|
||||
visDots.forEach((d, i) => d.classList.toggle("on", i === idx));
|
||||
}
|
||||
function start() { stop(); timer = window.setInterval(() => go(idx + 1), 5000); }
|
||||
function stop() { if (timer) window.clearInterval(timer); }
|
||||
root.querySelectorAll<HTMLButtonElement>(".hc-prev").forEach((b) =>
|
||||
b.addEventListener("click", () => { go(idx - 1); start(); })
|
||||
);
|
||||
root.querySelectorAll<HTMLButtonElement>(".hc-next").forEach((b) =>
|
||||
b.addEventListener("click", () => { go(idx + 1); start(); })
|
||||
);
|
||||
dots.forEach((d) =>
|
||||
d.addEventListener("click", () => { go(Number(d.dataset.i)); start(); })
|
||||
);
|
||||
root.addEventListener("mouseenter", stop);
|
||||
root.addEventListener("mouseleave", start);
|
||||
start();
|
||||
})();
|
||||
const el = document.getElementById("hsSwiper");
|
||||
if (!el) return;
|
||||
|
||||
// 右侧品牌栏:最新 / 最热 切换(事件委托,稳健绑定)
|
||||
(function () {
|
||||
const wrap = document.querySelector(".hc-rail-tabs");
|
||||
const railList = document.getElementById("railList");
|
||||
if (!wrap || !railList) return;
|
||||
const sets = railList.querySelectorAll<HTMLElement>(".rail-set");
|
||||
wrap.addEventListener("click", (e) => {
|
||||
const btn = (e.target as HTMLElement).closest("button");
|
||||
if (!btn) return;
|
||||
const sort = btn.dataset.sort;
|
||||
if (!sort) return;
|
||||
wrap.querySelectorAll("button").forEach((b) => b.classList.toggle("on", b === btn));
|
||||
sets.forEach((s) => { s.style.display = s.dataset.set === sort ? "" : "none"; });
|
||||
let swiper: any = null;
|
||||
let updateScheduled = false;
|
||||
// 图片尺寸随加载/视口变化,需在下一帧通知 Swiper 重新测量布局(防偏移)
|
||||
const scheduleUpdate = () => {
|
||||
if (updateScheduled) return;
|
||||
updateScheduled = true;
|
||||
requestAnimationFrame(() => { updateScheduled = false; if (swiper) swiper.update(); });
|
||||
};
|
||||
|
||||
// 图片加载:按真实比例设定尺寸 + 完成淡入;失败保留灰底占位
|
||||
const imgs = el.querySelectorAll<HTMLImageElement>(".hs-img-box img");
|
||||
imgs.forEach((img) => {
|
||||
const box = img.closest(".hs-img-box") as HTMLElement | null;
|
||||
const done = () => {img.classList.add("is-loaded"); box?.classList.add("is-loaded"); scheduleUpdate(); };
|
||||
const fail = () => {
|
||||
if (box) { box.style.width = "360px"; box.style.height = (el.clientHeight || 600) + "px"; }
|
||||
img.style.display = "none";
|
||||
box?.classList.add("is-loaded");
|
||||
scheduleUpdate();
|
||||
};
|
||||
if (img.complete) { if (img.naturalWidth > 0) done(); else fail(); }
|
||||
else { img.addEventListener("load", done, { once: true }); img.addEventListener("error", fail, { once: true }); }
|
||||
});
|
||||
|
||||
// 箭头按钮位于容器外的 header 内,直接传元素以确保正确绑定
|
||||
const prevBtn = el.parentElement?.querySelector<HTMLElement>(".hs-prev") || document.querySelector(".hs-prev");
|
||||
const nextBtn = el.parentElement?.querySelector<HTMLElement>(".hs-next") || document.querySelector(".hs-next");
|
||||
|
||||
swiper = new Swiper(el, {
|
||||
modules: [Navigation, FreeMode, Keyboard],
|
||||
slidesPerView: "auto", // 每张 slide 取自身宽度(由图片宽高比决定)
|
||||
spaceBetween: 4, // slide 间距
|
||||
grabCursor: true, // 可拖拽光标
|
||||
speed: 220, // 箭头/吸附过渡时长(ms),调小让拖拽松手后更快吸附
|
||||
freeMode: { enabled: true, sticky: true, momentumBounce: false, momentumRatio: 0.5 }, // 自由拖拽 + 松手粘性吸附(momentumRatio 调小减短滑行)
|
||||
keyboard: { enabled: true },
|
||||
navigation: { prevEl: prevBtn, nextEl: nextBtn, disabledClass: "swiper-button-disabled" },
|
||||
updateOnImagesReady: true, // 图片就绪后自动 update
|
||||
observer: true,
|
||||
observeSlideChildren: true,
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
@ -20,15 +20,6 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
---
|
||||
|
||||
<Layout title="All Shows — Portfolio Studio">
|
||||
<!-- 首屏绘制前同步侧边栏折叠状态,杜绝刷新时的闪烁 -->
|
||||
<script is:inline>
|
||||
try {
|
||||
if (localStorage.getItem("showsaside-collapsed") === "1") {
|
||||
document.documentElement.classList.add("showsaside-collapsed")
|
||||
}
|
||||
} catch (e) {}
|
||||
</script>
|
||||
|
||||
<div class="showsroot w-full" x-data="showsPage()">
|
||||
<!-- 顶部:极简眉头 -->
|
||||
<header class="showshead">
|
||||
@ -36,7 +27,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
<nav class="showscrumb" aria-label="Breadcrumb">
|
||||
<a href="/">Home</a>
|
||||
<span class="sep" aria-hidden="true">/</span>
|
||||
<a href="/shows">Runway Archive</a>
|
||||
<a href="/shows">All Shows</a>
|
||||
<span class="sep" aria-hidden="true">/</span>
|
||||
<span class="cur" aria-current="page" x-text="crumbYear"></span>
|
||||
</nav>
|
||||
@ -57,17 +48,12 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
<!-- 主体:左 sidebar + 右 4 列商品 grid -->
|
||||
<div class="showsbody">
|
||||
<!-- LEFT SIDEBAR -->
|
||||
<aside class="showsaside" :class="{ 'is-collapsed': asideCollapsed }">
|
||||
<aside class="showsaside">
|
||||
<div class="showsaside-head">
|
||||
<h2 class="showsaside-h">REFINE</h2>
|
||||
<button class="showsaside-toggle" type="button" aria-label="折叠/展开筛选" @click="toggleAside()">
|
||||
<svg viewBox="0 0 12 12" width="11" height="11" aria-hidden="true">
|
||||
<path d="M7.5 3l-4 3 4 3" stroke="currentColor" fill="none" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="showsaside-body" x-show="!asideCollapsed">
|
||||
<div class="showsaside-body">
|
||||
<!-- Designer 多选 -->
|
||||
<section class="showsg">
|
||||
<header class="showsg-h" @click="toggleGroup('designers')">
|
||||
@ -103,7 +89,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
</template>
|
||||
</li>
|
||||
</ul>
|
||||
<a class="showsmore" x-show="hasMoreBrands && !keyword" @click="brandsExpanded = !brandsExpanded" x-text="brandsExpanded ? 'Show fewer ▴' : `Show all ${allBrands.length} designers ▾`"></a>
|
||||
<a class="showsmore" @click="openBrandModal()" x-text="'浏览全部品牌 +'"></a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -194,7 +180,7 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
|
||||
<!-- Empty State -->
|
||||
<template x-if="!loading && items.length === 0">
|
||||
<div class="showsempty">No shows match the current filters · 暂无符合条件的秀场</div>
|
||||
<div class="showsempty">No shows match the current filters</div>
|
||||
</template>
|
||||
|
||||
<!-- Card List -->
|
||||
@ -235,6 +221,59 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 品牌筛选弹窗:搜索 + A-Z 跳转 + 热门推荐,按需加载,避免一次拉全量 -->
|
||||
<div class="showsmodal" x-show="modalOpen" x-cloak>
|
||||
<div class="showsmodal-mask" @click="closeBrandModal()"></div>
|
||||
<div class="showsmodal-panel">
|
||||
<header class="showsmodal-head">
|
||||
<h3>全部品牌</h3>
|
||||
<button class="showsmodal-x" type="button" @click="closeBrandModal()" aria-label="关闭">✕</button>
|
||||
</header>
|
||||
<div class="showsmodal-search">
|
||||
<input
|
||||
class="showsmodal-input"
|
||||
type="text"
|
||||
x-model="modalKeyword"
|
||||
@input.debounce.300ms="modalSearch()"
|
||||
placeholder="搜索品牌名…"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div class="showsmodal-az">
|
||||
<template x-for="l in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'.split('')" :key="l">
|
||||
<button class="az" type="button" :class="{ active: modalLetter === l }" @click="modalPickLetter(l)" x-text="l"></button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="showsmodal-body">
|
||||
<div class="showsmodal-hint" x-show="!modalKeyword && !modalLetter">热门推荐</div>
|
||||
<div class="showsmodal-loading" x-show="modalLoading" x-html="SPIN_SVG"></div>
|
||||
<ul class="showsmodal-list">
|
||||
<template x-for="b in modalResults" :key="b.id">
|
||||
<li>
|
||||
<label class="showsmodal-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="b.id"
|
||||
:checked="modalSelected.includes(b.id)"
|
||||
:disabled="!modalSelected.includes(b.id) && modalMaxReached"
|
||||
@change="toggleModalBrand(b.id)"
|
||||
/>
|
||||
<span class="showsmodal-name" x-text="b.name"></span>
|
||||
<span class="showsmodal-ct" x-text="b.article_count ? b.article_count : ''"></span>
|
||||
</label>
|
||||
</li>
|
||||
</template>
|
||||
<li class="showsmodal-empty" x-show="!modalLoading && modalResults.length === 0">未找到匹配的品牌</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="showsmodal-foot">
|
||||
<span class="showsmodal-counter" x-text="`已选 ${modalSelected.length} / 最多 5 个`"></span>
|
||||
<button class="showsmodal-ok" type="button" @click="confirmBrandModal()">确定</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@ -286,10 +325,6 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
.showsaside {
|
||||
@apply flex-none w-[240px] bg-white sticky top-5 max-h-[calc(100vh-40px)] rounded-none transition-[flex-basis] duration-250 ease-in-out flex flex-col max-md:static max-md:max-h-none max-md:w-full;
|
||||
}
|
||||
html.showsaside-collapsed .showsaside,
|
||||
.showsaside.is-collapsed {
|
||||
@apply flex-none w-[44px];
|
||||
}
|
||||
|
||||
.showsaside-head {
|
||||
@apply flex items-center justify-between p-[14px_14px_12px] border-b border-black shrink-0;
|
||||
@ -297,25 +332,10 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
.showsaside-h {
|
||||
@apply text-[11px] tracking-[0.26em] uppercase font-bold m-0 leading-none transition-opacity duration-200;
|
||||
}
|
||||
html.showsaside-collapsed .showsaside-h,
|
||||
.showsaside.is-collapsed .showsaside-h {
|
||||
@apply opacity-0 pointer-events-none w-0 overflow-hidden;
|
||||
}
|
||||
|
||||
.showsaside-toggle {
|
||||
@apply appearance-none bg-white border-[1.5px] border-black w-[22px] h-[22px] inline-flex items-center justify-center cursor-pointer rounded-none text-black p-0 transition-transform duration-250 ease-in-out hover:bg-black hover:text-white;
|
||||
}
|
||||
html.showsaside-collapsed .showsaside-toggle,
|
||||
.showsaside.is-collapsed .showsaside-toggle {
|
||||
@apply ml-auto rotate-180;
|
||||
}
|
||||
|
||||
.showsaside-body {
|
||||
@apply p-[14px_14px_18px] overflow-y-auto flex-1 min-h-0 [scrollbar-width:thin] [scrollbar-color:#ccc_#fff];
|
||||
}
|
||||
html.showsaside-collapsed .showsaside-body {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.showsg {
|
||||
@apply border-t border-[#e6e6e6] py-[12px] pb-[6px] first-of-type:border-t-0 first-of-type:pt-1;
|
||||
@ -479,11 +499,62 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
|
||||
@keyframes hbxProg { 0% { left: -38%; } 100% { left: 100%; } }
|
||||
@keyframes hbxShim { 0%, 100% { opacity: .85; } 50% { opacity: .55; } }
|
||||
/* ===== 品牌筛选弹窗 ===== */
|
||||
.showsmodal {
|
||||
@apply fixed inset-0 z-[60] flex items-center justify-center p-[16px];
|
||||
}
|
||||
.showsmodal-mask {
|
||||
@apply absolute inset-0 bg-black/45;
|
||||
}
|
||||
.showsmodal-panel {
|
||||
@apply relative w-full max-w-[680px] max-h-[86vh] bg-white flex flex-col border border-black;
|
||||
}
|
||||
.showsmodal-head {
|
||||
@apply flex items-center justify-between px-[16px] py-[13px] border-b border-black shrink-0;
|
||||
}
|
||||
.showsmodal-head h3 {
|
||||
@apply m-0 text-[13px] tracking-[0.2em] uppercase font-bold;
|
||||
}
|
||||
.showsmodal-x {
|
||||
@apply appearance-none bg-white border border-black w-[24px] h-[24px] inline-flex items-center justify-center cursor-pointer text-[12px] leading-none;
|
||||
}
|
||||
.showsmodal-x:hover { @apply bg-black text-white; }
|
||||
.showsmodal-search { @apply px-[16px] pt-[12px] pb-[8px] shrink-0; }
|
||||
.showsmodal-input {
|
||||
@apply w-full border border-black px-[10px] py-[8px] text-[13px] outline-none;
|
||||
}
|
||||
.showsmodal-input:focus { @apply ring-1 ring-black; }
|
||||
.showsmodal-az {
|
||||
@apply flex flex-wrap gap-[4px] px-[16px] pb-[10px] border-b border-[#e6e6e6] shrink-0;
|
||||
}
|
||||
.showsmodal-az .az {
|
||||
@apply appearance-none bg-white border border-[#ccc] w-[22px] h-[22px] text-[11px] cursor-pointer leading-none;
|
||||
}
|
||||
.showsmodal-az .az.active { @apply bg-black text-white border-black; }
|
||||
.showsmodal-body { @apply flex-1 min-h-0 overflow-y-auto px-[16px] py-[8px]; }
|
||||
.showsmodal-hint {
|
||||
@apply text-[11px] tracking-[0.2em] uppercase text-[#888] py-[6px] pb-[8px];
|
||||
}
|
||||
.showsmodal-loading { @apply flex justify-center py-[28px] text-[#bdbdbd]; }
|
||||
.showsmodal-list { @apply list-none m-0 p-0; }
|
||||
.showsmodal-row {
|
||||
@apply flex items-center gap-[8px] w-full cursor-pointer py-[7px] border-b border-[#f0f0f0] text-[13px];
|
||||
}
|
||||
.showsmodal-name { @apply flex-1 truncate; }
|
||||
.showsmodal-ct { @apply text-[#999] text-[11px] ml-auto; }
|
||||
.showsmodal-empty { @apply py-[28px] text-center text-[#999] text-[13px]; }
|
||||
.showsmodal-foot {
|
||||
@apply flex items-center justify-between px-[16px] py-[12px] border-t border-black shrink-0 text-[12px];
|
||||
}
|
||||
.showsmodal-ok {
|
||||
@apply appearance-none bg-black text-white border border-black px-[22px] py-[8px] cursor-pointer text-[12px] tracking-[0.1em];
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Alpine from "alpinejs"
|
||||
import { getArticles, toAbs, type ArticleItem, type BrandEntry } from "../lib/api"
|
||||
import { getArticles, getBrands, toAbs, type ArticleItem, type BrandEntry } from "../lib/api"
|
||||
|
||||
const ICON_USER = `<svg viewBox="0 0 12 12" width="9" height="9" aria-hidden="true"><circle cx="6" cy="4" r="2" stroke="currentColor" fill="none" stroke-width="1"/><path d="M2 10c0-2 1.8-3 4-3s4 1 4 3" stroke="currentColor" fill="none" stroke-width="1" stroke-linecap="round"/></svg>`
|
||||
const ICON_DATE = `<svg viewBox="0 0 12 12" width="9" height="9" aria-hidden="true"><rect x="2" y="3" width="8" height="7" stroke="currentColor" fill="none" stroke-width="1"/><path d="M2 6h8M4 2v2M8 2v2" stroke="currentColor" fill="none" stroke-width="1" stroke-linecap="round"/></svg>`
|
||||
@ -499,7 +570,6 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
ICON_USER,
|
||||
ICON_DATE,
|
||||
SPIN_SVG,
|
||||
asideCollapsed: localStorage.getItem("showsaside-collapsed") === "1",
|
||||
collapsedGroups: {
|
||||
designers: localStorage.getItem("showsg-designers") === "1",
|
||||
collection: localStorage.getItem("showsg-collection") === "1",
|
||||
@ -513,6 +583,17 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
keyword: "",
|
||||
brandsExpanded: false,
|
||||
|
||||
// 品牌筛选弹窗状态
|
||||
modalOpen: false,
|
||||
modalKeyword: "",
|
||||
modalLetter: "",
|
||||
modalResults: [] as BrandEntry[],
|
||||
modalLoading: false,
|
||||
modalSelected: [] as number[],
|
||||
get modalMaxReached() {
|
||||
return this.modalSelected.length >= 5
|
||||
},
|
||||
|
||||
items: [] as (ArticleItem & { _imgLoaded?: boolean })[],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
@ -620,12 +701,6 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
},
|
||||
|
||||
// 事件处理方法
|
||||
toggleAside() {
|
||||
this.asideCollapsed = !this.asideCollapsed
|
||||
localStorage.setItem("showsaside-collapsed", this.asideCollapsed ? "1" : "")
|
||||
document.documentElement.classList.toggle("showsaside-collapsed", this.asideCollapsed)
|
||||
},
|
||||
|
||||
toggleGroup(group: "designers" | "collection" | "season" | "year") {
|
||||
this.collapsedGroups[group] = !this.collapsedGroups[group]
|
||||
localStorage.setItem(`showsg-${group}`, this.collapsedGroups[group] ? "1" : "")
|
||||
@ -642,6 +717,73 @@ if (!yearChips.length) yearChips = yearSeed
|
||||
this.loadPage(1, false)
|
||||
},
|
||||
|
||||
// ===== 品牌筛选弹窗方法 =====
|
||||
async openBrandModal() {
|
||||
this.modalSelected = [...this.selectedBrands]
|
||||
this.modalKeyword = ""
|
||||
this.modalLetter = ""
|
||||
this.modalOpen = true
|
||||
await this.modalLoadHot()
|
||||
},
|
||||
closeBrandModal() {
|
||||
this.modalOpen = false
|
||||
},
|
||||
async modalLoadHot() {
|
||||
this.modalLoading = true
|
||||
try {
|
||||
const list = await getBrands({ featured: 1, size: 50 })
|
||||
this.modalResults = list.length ? list : this.allBrands.slice(0, 50)
|
||||
} catch {
|
||||
this.modalResults = this.allBrands.slice(0, 50)
|
||||
} finally {
|
||||
this.modalLoading = false
|
||||
}
|
||||
},
|
||||
async modalSearch() {
|
||||
const kw = this.modalKeyword.trim()
|
||||
if (kw) {
|
||||
this.modalLetter = ""
|
||||
this.modalLoading = true
|
||||
try {
|
||||
this.modalResults = await getBrands({ keyword: kw, size: 60 })
|
||||
} catch {
|
||||
this.modalResults = []
|
||||
} finally {
|
||||
this.modalLoading = false
|
||||
}
|
||||
} else if (this.modalLetter) {
|
||||
await this.modalPickLetter(this.modalLetter)
|
||||
} else {
|
||||
await this.modalLoadHot()
|
||||
}
|
||||
},
|
||||
async modalPickLetter(l: string) {
|
||||
this.modalLetter = l
|
||||
this.modalKeyword = ""
|
||||
this.modalLoading = true
|
||||
try {
|
||||
const letter = l === "#" ? "OTHER" : l
|
||||
this.modalResults = await getBrands({ letter, size: 200 })
|
||||
} catch {
|
||||
this.modalResults = []
|
||||
} finally {
|
||||
this.modalLoading = false
|
||||
}
|
||||
},
|
||||
toggleModalBrand(id: number) {
|
||||
const idx = this.modalSelected.indexOf(id)
|
||||
if (idx >= 0) this.modalSelected.splice(idx, 1)
|
||||
else {
|
||||
if (this.modalSelected.length >= 5) return
|
||||
this.modalSelected.push(id)
|
||||
}
|
||||
},
|
||||
confirmBrandModal() {
|
||||
this.selectedBrands = [...this.modalSelected]
|
||||
this.modalOpen = false
|
||||
this.refreshFilter()
|
||||
},
|
||||
|
||||
clearAll() {
|
||||
this.selectedBrands = []
|
||||
this.selectedCt = []
|
||||
|
||||
Reference in New Issue
Block a user