update
Some checks failed
deploy / deploy (push) Has been cancelled

This commit is contained in:
toom1996
2026-08-29 20:17:13 +08:00
parent a133413d74
commit e24bfe164d
29 changed files with 598 additions and 1517 deletions

View File

@ -3,6 +3,7 @@ import Breadcrumb from "@/components/Breadcrumb.astro"
import { getI18n } from '@/i18n/utils'
import { clientHref, ROUTES } from '@/lib/routes'
import { toAbs } from '@/lib/api'
import "@/styles/look-grid.css"
const { locale, t } = getI18n(Astro)
@ -173,186 +174,12 @@ const sortOptions: { value: string; label: string }[] = [
<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>`
import { SPIN_SVG, makeCardSlots, buildPageList } from "@/lib/looks-grid"
const DEFAULT_SORT = "image_count"
@ -414,15 +241,10 @@ const sortOptions: { value: string; label: string }[] = [
},
// 卡片右侧缩略图条带:固定 5 个槽位,缺图留 placeholder
// 街拍 images 是 { image, name }[](与 runway 的 string[] gallery 不同)
// 街拍 images 是 { image, name }[](与 runway 的 string[] gallery 不同),先抽成纯 URL 数组
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
const imgs = (it.images || []).map((im: any) => (im && (im.image || im)) || "")
return makeCardSlots(imgs)
},
async loadPage(page: number) {
@ -449,17 +271,7 @@ const sortOptions: { value: string; label: string }[] = [
},
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
return buildPageList(this.lastPage, this.page)
},
goPage(p: number) {
@ -474,7 +286,8 @@ const sortOptions: { value: string; label: string }[] = [
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)) },
// 街拍详情统一收口到 /item/[id](与服务端 /item 回落逻辑对应)
itemHref(it: { id: string | number }) { return clientHref(this.locale, ROUTES.item(it.id)) },
// theimpression 标题含字面 \t:地点 \t 场次,拆成 [location, event]
titleParts(it: StreetSnapItem): string[] {