update
This commit is contained in:
490
src/components/StreetSnaps.astro
Normal file
490
src/components/StreetSnaps.astro
Normal file
@ -0,0 +1,490 @@
|
||||
---
|
||||
import Breadcrumb from "@/components/Breadcrumb.astro"
|
||||
import { getI18n } from '@/i18n/utils'
|
||||
import { clientHref, ROUTES } from '@/lib/routes'
|
||||
import { toAbs } from '@/lib/api'
|
||||
|
||||
const { locale, t } = getI18n(Astro)
|
||||
|
||||
// 年份筛选:近 10 年(含当年),末项兜底
|
||||
const currentYear = new Date().getFullYear()
|
||||
const yearFloor = currentYear - 9
|
||||
const yearOptions: { value: number | string; label: string }[] = []
|
||||
for (let y = currentYear; y >= yearFloor; y--) {
|
||||
yearOptions.push({ value: y, label: String(y) })
|
||||
}
|
||||
yearOptions.push({ value: `before${yearFloor}`, label: `${yearFloor}年前` })
|
||||
|
||||
// 排序选项:image_count 默认(按图片数降序)/ year_desc / year_asc
|
||||
const sortOptions: { value: string; label: string }[] = [
|
||||
{ value: "image_count", label: t('Most photos') },
|
||||
{ value: "year_desc", label: t('Newest year') },
|
||||
{ value: "year_asc", label: t('Oldest year') },
|
||||
]
|
||||
---
|
||||
|
||||
<div class="snapsroot w-full" x-data="streetSnapsPage()" data-locale={locale}>
|
||||
<!-- 顶部:极简眉头 -->
|
||||
<header class="snapshead">
|
||||
<div>
|
||||
<Breadcrumb>
|
||||
<span slot="current">{t('Street Style')}</span>
|
||||
</Breadcrumb>
|
||||
<h1 class="snapsh1">{t('All Street Snaps')}</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 已激活筛选 pills -->
|
||||
<div class="snapspills" x-show="pills.length > 0" x-cloak>
|
||||
<template x-for="(p, i) in pills" :key="i">
|
||||
<span class="snapspill">
|
||||
<span x-text="p.label"></span>
|
||||
<button class="x" type="button" @click="p.clear()">×</button>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 主体:左 sidebar(REFINE 筛选) + 右卡片网格 -->
|
||||
<div class="snapsbody">
|
||||
<!-- LEFT SIDEBAR -->
|
||||
<aside class="snapsaside">
|
||||
<div class="snapsaside-head">
|
||||
<h2 class="snapsaside-h">REFINE</h2>
|
||||
</div>
|
||||
|
||||
<div class="snapsaside-body">
|
||||
<!-- Year -->
|
||||
<section class="snapsg">
|
||||
<header class="snapsg-h" @click="toggleGroup('year')">
|
||||
<span>{t('Year')}</span>
|
||||
<span class="snapsg-chev" :class="{ 'is-collapsed': collapsedGroups.year }">
|
||||
<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.year">
|
||||
<ul class="snapschecks snapschecks--flat">
|
||||
{yearOptions.map((o) => (
|
||||
<li><label><input type="radio" name="snapYear" value={String(o.value)} :checked={'selectedYear === "' + String(o.value) + '"'} :class={`{'is-checked': selectedYear === '${String(o.value)}'`} @click.prevent={'selectedYear = selectedYear === "' + String(o.value) + '" ? "" : "' + String(o.value) + '"; loadPage(1)'} /> <span>{o.label}</span></label></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sort -->
|
||||
<section class="snapsg">
|
||||
<header class="snapsg-h" @click="toggleGroup('sort')">
|
||||
<span>{t('Sort')}</span>
|
||||
<span class="snapsg-chev" :class="{ 'is-collapsed': collapsedGroups.sort }">
|
||||
<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.sort">
|
||||
<ul class="snapschecks snapschecks--flat">
|
||||
{sortOptions.map((o) => (
|
||||
<li><label><input type="radio" name="snapSort" value={o.value} :checked={'selectedSort === "' + o.value + '"'} :class={`{'is-checked': selectedSort === '${o.value}'}`} @click.prevent={'selectedSort = selectedSort === "' + o.value + '" ? "image_count" : "' + 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>
|
||||
|
||||
<!-- MAIN:卡片网格(左封面 + 右标题 + 缩略图条带) -->
|
||||
<main class="snapsmain">
|
||||
<div class="snapscontent" :class="{ 'is-loading': loading }">
|
||||
|
||||
<!-- 骨架屏:结构与真实卡片完全一致,loading 期间占位,杜绝尺寸抖动 -->
|
||||
<template x-if="loading && items.length === 0">
|
||||
<div class="snapsgrid" aria-hidden="true">
|
||||
<template x-for="i in 4" :key="'skel-'+i">
|
||||
<div class="snapscard snapscard--skel">
|
||||
<div class="snapscard-left">
|
||||
<div class="snapscard-img"></div>
|
||||
</div>
|
||||
<div class="snapscard-right">
|
||||
<div class="snapscard-info">
|
||||
<div class="snapsline h-[41px] mb-[10px] rounded-[2px]"></div>
|
||||
<div class="snapsline h-[29px] mb-[14px] rounded-[2px]"></div>
|
||||
</div>
|
||||
<div class="snapscard-strip">
|
||||
<div class="snapscard-thumb"></div>
|
||||
<div class="snapscard-thumb"></div>
|
||||
<div class="snapscard-thumb"></div>
|
||||
<div class="snapscard-thumb"></div>
|
||||
<div class="snapscard-thumb"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty -->
|
||||
<template x-if="!loading && items.length === 0">
|
||||
<div class="snapsempty">No street snaps match the current filters</div>
|
||||
</template>
|
||||
|
||||
<!-- Card List -->
|
||||
<template x-if="!loading && items.length > 0">
|
||||
<div class="snapsgrid">
|
||||
<template x-for="it in items" :key="it.id">
|
||||
<a class="snapscard" :href="itemHref(it)" :aria-label="cardTitle(it)">
|
||||
<div class="snapscard-left">
|
||||
<div class="snapscard-img" :class="{ 'is-loaded': it._imgLoaded }">
|
||||
<span class="snapsspin" aria-hidden="true" x-html="SPIN_SVG"></span>
|
||||
<img :src="toAbsUrl(it.cover)" :alt="cardTitle(it)" loading="lazy" @load="it._imgLoaded = true" @error="it._imgLoaded = true" :class="{ 'is-loaded': it._imgLoaded }" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="snapscard-right">
|
||||
<div class="snapscard-info">
|
||||
<h3 class="snapscard-title" x-text="titleParts(it)[0]"></h3>
|
||||
<p class="snapscard-meta" x-text="titleParts(it)[1]"></p>
|
||||
</div>
|
||||
<div class="snapscard-strip">
|
||||
<template x-for="(slot, idx) in getCardSlots(it)" :key="idx">
|
||||
<div class="snapscard-thumb" :class="{ 'placeholder': !slot.src, 'is-loaded': slot.loaded }">
|
||||
<span class="snapsspin" aria-hidden="true" x-html="SPIN_SVG" x-show="slot.src"></span>
|
||||
<img x-show="slot.src" :src="toAbsUrl(slot.src)" alt="" loading="lazy" @load="slot.loaded = true" @error="slot.loaded = true" :class="{ 'is-loaded': slot.loaded }" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<nav class="snappager" x-show="lastPage > 1" aria-label="Pagination">
|
||||
<button class="pg pg-prev" type="button" :disabled="page <= 1 || loading" @click="prevPage()">‹ Prev</button>
|
||||
<template x-for="(p, i) in pageList" :key="i">
|
||||
<span class="pg-wrap">
|
||||
<span class="pg-ellipsis" x-show="p === '...'" x-text="'…'"></span>
|
||||
<button class="pg" type="button" x-show="p !== '...'" :class="{ 'is-current': p === page }" :disabled="p === page" @click="goPage(p)" x-text="p"></button>
|
||||
</span>
|
||||
</template>
|
||||
<button class="pg pg-next" type="button" :disabled="page >= lastPage || loading" @click="nextPage()">Next ›</button>
|
||||
</nav>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="snaps-year-opts" type="application/json" set:html={JSON.stringify({ yearOptions, sortOptions })}></script>
|
||||
|
||||
<style>
|
||||
@reference "tailwindcss";
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
.snapsroot {
|
||||
@apply m-auto max-w-[1600px] pt-7 text-[#0a0a0a];
|
||||
}
|
||||
|
||||
.snapshead {
|
||||
@apply flex justify-between items-end gap-6 py-5 pb-[22px];
|
||||
}
|
||||
.snapsh1 {
|
||||
@apply text-[clamp(34px,5.5vw,64px)] leading-[0.95] font-black tracking-[-0.02em] my-0 mb-[8px];
|
||||
}
|
||||
|
||||
/* 已激活筛选 pills */
|
||||
.snapspills {
|
||||
@apply flex flex-wrap gap-2 mb-4;
|
||||
}
|
||||
.snapspill {
|
||||
@apply inline-flex items-center gap-2 border-[1.5px] border-black p-[6px_10px] text-[10.5px] uppercase tracking-[0.14em] text-black bg-white;
|
||||
}
|
||||
.snapspill .x {
|
||||
@apply inline-flex w-3.5 h-3.5 items-center justify-center border-none bg-black text-white cursor-pointer text-xs leading-none;
|
||||
}
|
||||
|
||||
/* 主体:左 sidebar + 右网格 */
|
||||
.snapsbody {
|
||||
@apply flex gap-[36px] items-start max-md:flex-col max-md:gap-[18px];
|
||||
}
|
||||
|
||||
.snapsaside {
|
||||
@apply flex-none w-[240px] bg-white sticky top-5 max-h-[calc(100vh-40px)] rounded-none flex flex-col max-md:static max-md:max-h-none max-md:w-full;
|
||||
}
|
||||
.snapsaside-head {
|
||||
@apply flex items-center justify-between p-[14px_14px_12px] border-b border-black shrink-0;
|
||||
}
|
||||
.snapsaside-h {
|
||||
@apply text-[11px] tracking-[0.26em] uppercase font-bold m-0 leading-none;
|
||||
}
|
||||
.snapsaside-body {
|
||||
@apply p-[14px_14px_18px] overflow-y-auto flex-1 min-h-0 [scrollbar-width:thin] [scrollbar-color:#ccc_#fff];
|
||||
}
|
||||
|
||||
.snapsg {
|
||||
@apply border-t border-[#e6e6e6] py-[12px] pb-[6px] first-of-type:border-t-0 first-of-type:pt-1;
|
||||
}
|
||||
.snapsg-h {
|
||||
@apply flex justify-between items-center text-[11px] tracking-[0.22em] uppercase font-bold text-black mb-2 cursor-pointer select-none;
|
||||
}
|
||||
.snapsg-chev {
|
||||
@apply inline-block text-[9px] leading-none text-[#999] transition-transform duration-200 ml-1;
|
||||
}
|
||||
.snapsg-chev.is-collapsed { @apply -rotate-90; }
|
||||
|
||||
.snapschecks {
|
||||
@apply list-none mt-[10px] mb-0 p-0 max-h-[280px] overflow-y-auto [scrollbar-width:thin];
|
||||
}
|
||||
.snapschecks li {
|
||||
@apply flex items-center gap-2 py-[5px] text-[11.5px] uppercase tracking-[0.04em] text-black cursor-pointer select-none hover:bg-[#f6f6f6];
|
||||
}
|
||||
.snapschecks input[type="radio"] {
|
||||
@apply appearance-none w-3.5 h-3.5 border-[1.5px] border-black bg-white cursor-pointer shrink-0 rounded-full;
|
||||
}
|
||||
/* 选中态由 Alpine 状态驱动(.is-checked),不依赖原生 :checked 伪类:
|
||||
@click.prevent 会阻止原生选中,伪类不可靠;实心黑圆一眼可辨 */
|
||||
.snapschecks input[type="radio"].is-checked {
|
||||
@apply bg-white border-black;
|
||||
background-image: radial-gradient(circle, #000 36%, transparent 40%);
|
||||
}
|
||||
.snapschecks--flat li { @apply py-[4px]; }
|
||||
.snapschecks--flat li label { @apply inline-flex items-center gap-2 cursor-pointer w-full; }
|
||||
|
||||
.snapsclear {
|
||||
@apply mt-[22px] block w-full text-left pt-[16px] border-t border-black/10 text-[11px] uppercase tracking-[0.18em] text-[#666] cursor-pointer hover:text-black transition-colors duration-150;
|
||||
}
|
||||
|
||||
/* 主体内容区 */
|
||||
.snapsmain { @apply flex-1 min-w-0; }
|
||||
.snapscontent {
|
||||
@apply relative;
|
||||
}
|
||||
.snapscontent.is-loading::before {
|
||||
content: "";
|
||||
@apply absolute -top-1 left-0 h-[2px] w-[38%] bg-black z-10;
|
||||
animation: snapProg 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ===== 卡片网格(与 runway-looks 同款) ===== */
|
||||
.snapsgrid {
|
||||
@apply grid grid-cols-2 gap-[22px] relative max-[560px]:grid-cols-1;
|
||||
}
|
||||
|
||||
.snapscard {
|
||||
@apply relative flex gap-[14px] no-underline text-inherit bg-white p-3 transition-colors duration-200 hover:border-black max-[700px]:flex-col max-[700px]:gap-[10px];
|
||||
}
|
||||
.snapscard:hover .snapscard-img img {
|
||||
@apply scale-104;
|
||||
}
|
||||
|
||||
.snapscard-left {
|
||||
@apply flex-none w-[30%] min-w-[110px] max-[700px]:w-full max-[700px]:flex-none;
|
||||
}
|
||||
.snapscard-img {
|
||||
@apply relative w-full aspect-[3/4] bg-[#f1f1f1] overflow-hidden rounded-none;
|
||||
}
|
||||
.snapscard-img img {
|
||||
@apply w-full h-full object-cover transition-transform duration-600 opacity-0;
|
||||
}
|
||||
.snapscard-img img.is-loaded {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
.snapsspin {
|
||||
@apply absolute inset-0 flex items-center justify-center text-[#bdbdbd] opacity-100 transition-opacity duration-300 pointer-events-none z-10;
|
||||
}
|
||||
.snapscard-img.is-loaded .snapsspin,
|
||||
.snapscard-thumb.is-loaded .snapsspin {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
||||
.snapscard-right {
|
||||
@apply flex-1 min-w-0 flex flex-col justify-between max-[700px]:w-full;
|
||||
}
|
||||
.snapscard-info {
|
||||
@apply flex-none min-w-0 flex flex-col p-[2px_2px_0];
|
||||
}
|
||||
.snapscard-title {
|
||||
@apply text-[15.5px] font-bold leading-[1.32] my-0 mb-[10px] text-[#111] tracking-[-0.01em] line-clamp-2 h-[41px] overflow-hidden;
|
||||
}
|
||||
.snapscard-meta {
|
||||
@apply text-[9.5px] text-[#888] my-0 mb-[14px] leading-[1.55] tracking-[0.04em] uppercase line-clamp-2 h-[29px] overflow-hidden;
|
||||
}
|
||||
|
||||
.snapscard-strip {
|
||||
@apply grid grid-cols-5 gap-1 mt-[10px];
|
||||
}
|
||||
.snapscard-thumb {
|
||||
@apply relative aspect-[3/4] bg-[#f1f1f1] overflow-hidden;
|
||||
}
|
||||
.snapscard-thumb img {
|
||||
@apply w-full h-full object-cover opacity-0 transition-opacity duration-450 ease-in-out;
|
||||
}
|
||||
.snapscard-thumb img.is-loaded {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.snapscard-thumb.placeholder {
|
||||
@apply bg-[#f6f6f6];
|
||||
}
|
||||
|
||||
.snapscard--skel .snapscard-img,
|
||||
.snapscard--skel .snapscard-thumb,
|
||||
.snapscard--skel .snapsline {
|
||||
@apply bg-[#efefef];
|
||||
animation: snapShim 1.6s ease infinite;
|
||||
}
|
||||
|
||||
/* ===== 分页 ===== */
|
||||
.snappager { @apply flex flex-wrap items-center justify-center gap-2 mt-[38px]; }
|
||||
.pg {
|
||||
@apply min-w-[40px] h-[40px] px-3 border-[1.5px] border-black bg-white text-black text-[11px] uppercase tracking-[0.12em] font-bold cursor-pointer rounded-none transition-colors duration-200;
|
||||
}
|
||||
.pg.is-current { @apply bg-black text-white cursor-default; }
|
||||
.pg:disabled { @apply opacity-30 cursor-default; }
|
||||
.pg.enabled:hover { @apply bg-black text-white; }
|
||||
.pg-ellipsis { @apply px-1 text-[#999] select-none text-[11px]; }
|
||||
.pg-wrap { @apply inline-flex items-center; }
|
||||
|
||||
.snapsempty {
|
||||
@apply col-span-full p-[80px_20px] text-center text-xs tracking-[0.22em] uppercase text-[#999] border border-dashed border-[#ccc];
|
||||
}
|
||||
|
||||
@keyframes snapProg { 0% { left: -38%; } 100% { left: 100%; } }
|
||||
@keyframes snapShim { 0%, 100% { opacity: .85; } 50% { opacity: .55; } }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { getStreetSnaps, toAbs, type StreetSnapItem } from "@/lib/api"
|
||||
import { clientHref, ROUTES } from "@/lib/routes"
|
||||
const SPIN_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true" class="w-[22px] h-[22px] block"><g class="ring origin-[12px_12px] animate-spin" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="40 22"><circle cx="12" cy="12" r="9"></circle></g><g opacity="0.55" fill="none" stroke="currentColor" stroke-width="1"><rect x="8" y="9.6" width="8" height="5.4"></rect><circle cx="12" cy="12.3" r="1.6"></circle></g></svg>`
|
||||
|
||||
const DEFAULT_SORT = "image_count"
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("streetSnapsPage", () => {
|
||||
const optNode = document.getElementById("snaps-year-opts")
|
||||
const opts = optNode?.textContent ? JSON.parse(optNode.textContent) : { yearOptions: [], sortOptions: [] }
|
||||
|
||||
return {
|
||||
locale: '',
|
||||
SPIN_SVG,
|
||||
yearOptions: opts.yearOptions || [],
|
||||
sortOptions: opts.sortOptions || [],
|
||||
selectedYear: '' as string,
|
||||
selectedSort: DEFAULT_SORT as string,
|
||||
|
||||
// 侧栏分组折叠状态
|
||||
collapsedGroups: { year: false, sort: false } as Record<string, boolean>,
|
||||
|
||||
items: [] as (StreetSnapItem & { _imgLoaded?: boolean })[],
|
||||
loading: false,
|
||||
page: 1,
|
||||
lastPage: 1,
|
||||
reqToken: 0,
|
||||
|
||||
init() {
|
||||
this.locale = this.$el.dataset.locale || 'en'
|
||||
this.loadPage(1)
|
||||
},
|
||||
|
||||
// ===== 侧栏筛选 =====
|
||||
toggleGroup(key: string) {
|
||||
this.collapsedGroups[key] = !this.collapsedGroups[key]
|
||||
},
|
||||
clearAll() {
|
||||
this.selectedYear = ''
|
||||
this.selectedSort = DEFAULT_SORT
|
||||
this.loadPage(1)
|
||||
},
|
||||
|
||||
// 已激活筛选 chips(顶部 pills + 侧栏 Clear all 的可见性都依赖它)
|
||||
get pills() {
|
||||
const out: { label: string; clear: () => void }[] = []
|
||||
if (this.selectedYear) {
|
||||
const hit = this.yearOptions.find((o: any) => String(o.value) === String(this.selectedYear))
|
||||
out.push({
|
||||
label: "YEAR · " + (hit ? hit.label : this.selectedYear),
|
||||
clear: () => { this.selectedYear = ''; this.loadPage(1) },
|
||||
})
|
||||
}
|
||||
if (this.selectedSort !== DEFAULT_SORT) {
|
||||
const hit = this.sortOptions.find((o: any) => o.value === this.selectedSort)
|
||||
out.push({
|
||||
label: "SORT · " + (hit ? hit.label : this.selectedSort),
|
||||
clear: () => { this.selectedSort = DEFAULT_SORT; this.loadPage(1) },
|
||||
})
|
||||
}
|
||||
return out
|
||||
},
|
||||
|
||||
// 卡片右侧缩略图条带:固定 5 个槽位,缺图留 placeholder
|
||||
// 街拍 images 是 { image, name }[](与 runway 的 string[] gallery 不同)
|
||||
getCardSlots(it: StreetSnapItem) {
|
||||
const imgs = it.images || []
|
||||
const slots: { src: string; loaded: boolean }[] = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const im: any = imgs[i]
|
||||
slots.push({ src: im ? (im.image || im) : "", loaded: false })
|
||||
}
|
||||
return slots
|
||||
},
|
||||
|
||||
async loadPage(page: number) {
|
||||
const myToken = ++this.reqToken
|
||||
this.loading = true
|
||||
this.items = []
|
||||
try {
|
||||
const json = await getStreetSnaps({
|
||||
page,
|
||||
year: this.selectedYear || undefined,
|
||||
sort: this.selectedSort,
|
||||
})
|
||||
if (myToken !== this.reqToken) return
|
||||
const rows = json.data.map((item: StreetSnapItem) => ({ ...item, _imgLoaded: false }))
|
||||
this.lastPage = json.last_page
|
||||
this.page = page
|
||||
this.items = rows
|
||||
} catch {
|
||||
if (myToken !== this.reqToken) return
|
||||
this.items = []
|
||||
} finally {
|
||||
if (myToken === this.reqToken) this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
get pageList() {
|
||||
const total = this.lastPage
|
||||
const cur = this.page
|
||||
if (total <= 7) return Array.from({ length: total }, (_: any, i: number) => i + 1)
|
||||
const pages: (number | "...")[] = [1]
|
||||
const start = Math.max(2, cur - 1)
|
||||
const end = Math.min(total - 1, cur + 1)
|
||||
if (start > 2) pages.push("...")
|
||||
for (let i = start; i <= end; i++) pages.push(i)
|
||||
if (end < total - 1) pages.push("...")
|
||||
pages.push(total)
|
||||
return pages
|
||||
},
|
||||
|
||||
goPage(p: number) {
|
||||
if (this.loading) return
|
||||
if (p < 1 || p > this.lastPage || p === this.page) return
|
||||
this.loadPage(p)
|
||||
const main = this.$el.querySelector(".snapsmain")
|
||||
if (main) main.scrollIntoView({ behavior: "smooth", block: "start" })
|
||||
else window.scrollTo({ top: 0, behavior: "smooth" })
|
||||
},
|
||||
prevPage() { this.goPage(this.page - 1) },
|
||||
nextPage() { this.goPage(this.page + 1) },
|
||||
|
||||
toAbsUrl(url: string) { return toAbs(url) },
|
||||
itemHref(it: { id: string | number }) { return clientHref(this.locale, ROUTES.streetSnap(it.id)) },
|
||||
|
||||
// theimpression 标题含字面 \t:地点 \t 场次,拆成 [location, event]
|
||||
titleParts(it: StreetSnapItem): string[] {
|
||||
const parts = (it.title || "").split("\t")
|
||||
return [parts[0] || it.title || "", parts.slice(1).join(" ").trim()]
|
||||
},
|
||||
cardTitle(it: StreetSnapItem): string {
|
||||
return (it.title || "").replace(/\t/g, " · ")
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user