update
This commit is contained in:
@ -3,20 +3,20 @@
|
||||
// 纯主图(无细节图)不渲染。数量走 Alpine 的 detailCount()(响应式)—— 未登录取前 PREVIEW_LIMIT 张
|
||||
// 的细节子树,登录后由 gallery:full 重建映射,两态共用同一条渲染路径。
|
||||
//
|
||||
// look:Alpine 表达式,表示该主图的 look_index。SSR 首屏传字面量(如 "3"),
|
||||
// 客户端补拉的 x-for 里传表达式(如 "im.lookIndex")。
|
||||
// mainId:Alpine 表达式,表示该主图自身 id。SSR 首屏传字面量(如 "i0008CZYI"),
|
||||
// 客户端补拉的 x-for 里传表达式(如 "im.id")。
|
||||
interface Props {
|
||||
look: string
|
||||
mainId: string
|
||||
}
|
||||
const { look } = Astro.props
|
||||
const count = `detailCount(${look})`
|
||||
const { mainId } = Astro.props
|
||||
const count = `detailCount(${mainId})`
|
||||
---
|
||||
|
||||
<button
|
||||
type="button"
|
||||
x-show={`${count} > 0`}
|
||||
x-cloak
|
||||
@click.stop={`openDetailLb(${look})`}
|
||||
@click.stop={`openDetailLb(${mainId})`}
|
||||
:title={`${count} + ' detail' + (${count} > 1 ? 's' : '')`}
|
||||
:aria-label={`${count} + ' detail' + (${count} > 1 ? 's' : '')`}
|
||||
class="absolute left-2 bottom-2 z-10 flex items-center gap-1 px-1.5 py-1 bg-white/85 backdrop-blur-sm border border-black/15 text-[#111] text-[10px] uppercase tracking-[0.14em] cursor-pointer hover:bg-black hover:text-white transition-colors"
|
||||
|
||||
@ -32,7 +32,6 @@ interface GalleryImage {
|
||||
thumb: string
|
||||
raw: string
|
||||
name: string
|
||||
lookIndex: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@ -68,7 +67,7 @@ interface Props {
|
||||
preview: boolean
|
||||
/** 占位遮罩格数(补足「剩余 N 张」) */
|
||||
lockedCount: number
|
||||
/** 递给客户端的副图投影(look_index + detail),与登录后完整 images 同构解析 */
|
||||
/** 递给客户端的副图投影(id + detail),与登录后完整 images 同构解析 */
|
||||
detailPayload: unknown
|
||||
/** 图集元信息:写进 data-*,供收藏 / 灯箱使用 */
|
||||
galleryUid: string
|
||||
@ -186,7 +185,7 @@ const hasImages = previewImages.length > 0
|
||||
{previewImages.map((im, i) => (
|
||||
<figure
|
||||
data-image-index={i}
|
||||
data-look-index={im.lookIndex}
|
||||
data-main-id={im.id}
|
||||
data-image-url={im.url}
|
||||
data-image-name={im.name}
|
||||
data-fav-id={im.id}
|
||||
@ -222,7 +221,7 @@ const hasImages = previewImages.length > 0
|
||||
</button>
|
||||
|
||||
{/* 副图数量角标(共享组件):有副图的主图左下角标「张数」,点它直接开副图灯箱 */}
|
||||
<DetailCountBadge look={String(im.lookIndex)} />
|
||||
<DetailCountBadge mainId={im.id} />
|
||||
</div>
|
||||
|
||||
{showCaptions && im.name && (
|
||||
@ -264,7 +263,7 @@ const hasImages = previewImages.length > 0
|
||||
<template x-for="(im, i) in extraImages" :key="im.url">
|
||||
<figure
|
||||
:data-image-index="5 + i"
|
||||
:data-look-index="im.lookIndex"
|
||||
:data-main-id="im.id"
|
||||
:data-image-url="im.url"
|
||||
:data-image-name="im.name"
|
||||
:data-fav-id="im.id"
|
||||
@ -294,7 +293,7 @@ const hasImages = previewImages.length > 0
|
||||
<path d="M12 21C12 21 4 14.5 4 8.8C4 6.1 6.1 4 8.7 4C10.3 4 11.5 4.8 12 6C12.5 4.8 13.7 4 15.3 4C17.9 4 20 6.1 20 8.8C20 14.5 12 21 12 21Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<DetailCountBadge look="im.lookIndex" />
|
||||
<DetailCountBadge mainId="im.id" />
|
||||
</div>
|
||||
{showCaptions && (
|
||||
<template x-if="im.name">
|
||||
@ -325,9 +324,9 @@ const hasImages = previewImages.length > 0
|
||||
)
|
||||
}
|
||||
|
||||
{/* 大图灯箱(共享):当前主图的副图经 details 插槽注入。 */}
|
||||
{/* 大图灯箱(共享):当前主图的副图经 details 插槽注入。左栏缩略图条已移除(见 GalleryLightbox)。 */}
|
||||
{hasImages && (
|
||||
<GalleryLightbox images={previewImages} aspect={aspect}>
|
||||
<GalleryLightbox>
|
||||
<LbDetailsStrip slot="details" aspect={aspect} />
|
||||
</GalleryLightbox>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
---
|
||||
// 走秀 / 街拍详情页共享的全屏大图灯箱:左栏缩略图条 + 大图 + 上/下·左/右翻页 + 单图收藏。
|
||||
// 走秀 / 街拍详情页共享的全屏大图灯箱:大图 + 左/右翻页 + 单图收藏。
|
||||
//
|
||||
// 左栏「所有图片缩略图条」已移除:翻页/定位已由顶部计数 + 左右箭头 + 键盘 ←/→ + ?img= 深链完整覆盖,
|
||||
// 缩略条属冗余导航;去掉后主图可用面积更大。主图翻页实际读的是图集网格里 [data-image-index] 的
|
||||
// figure(第 6 张起的 extraImages 也已在网格里渲染),与左栏无关,故移除不影响翻页。
|
||||
//
|
||||
// 视觉遵循全站「黑白编辑风」:直角细框、小字 + 宽字距、零填充计数、hover 反色(bg-black + 白字)。
|
||||
// 交互逻辑全部在 src/lib/gallery.ts(createGalleryView);本组件只负责标记。
|
||||
@ -7,17 +11,7 @@
|
||||
// 各页独有的附加层通过具名插槽 details 注入(走秀页塞「当前 look 的细节图簇」,街拍页不传)。
|
||||
import { getI18n } from "@/lib/i18n"
|
||||
|
||||
interface Props {
|
||||
/** SSR 首屏图片(前 PREVIEW_LIMIT 张),需带 thumb 供左栏缩略图条使用 */
|
||||
images: { thumb: string }[]
|
||||
/** 缩略图比例。后端素材统一 2:3(说明见 DetailGalleryView 的 aspect prop),默认值即可 */
|
||||
aspect?: "2/3" | "3/4"
|
||||
}
|
||||
const { images, aspect = "2/3" } = Astro.props
|
||||
const { t } = getI18n(Astro)
|
||||
|
||||
// 两个字面量都出现在本文件源码里,保证 Tailwind v4 JIT 能扫描到
|
||||
const thumbAspect = aspect === "3/4" ? "aspect-[3/4]" : "aspect-[2/3]"
|
||||
---
|
||||
|
||||
<div
|
||||
@ -61,67 +55,10 @@ const thumbAspect = aspect === "3/4" ? "aspect-[3/4]" : "aspect-[2/3]"
|
||||
</button>
|
||||
|
||||
<div class="flex-1 min-h-0 flex items-stretch overflow-hidden relative">
|
||||
<div
|
||||
id="lb-thumbs"
|
||||
class="w-[76px] sm:w-[118px] flex-none overflow-y-auto overflow-x-hidden relative scroll-smooth p-[32px_8px] sm:p-[38px_18px_38px_20px] border-r border-black/10 no-scrollbar"
|
||||
>
|
||||
{images.map((im, i) => (
|
||||
<button
|
||||
type="button"
|
||||
data-lb-index={i}
|
||||
data-thumb-url={im.thumb}
|
||||
@click={`showLb(${i})`}
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[var(--c-gray-200)] bg-cover bg-center`}
|
||||
x-bind:class="lbIndex === Number($el.dataset.lbIndex) ? 'opacity-100 ring-1 ring-black' : ''"
|
||||
aria-label={`${i + 1}`}
|
||||
>
|
||||
<span class="absolute left-1 top-1 z-10 bg-black/70 px-1 py-0.5 text-[8px] sm:text-[9px] tracking-[0.16em] text-white backdrop-blur-sm">
|
||||
{String(i + 1).padStart(2, "0")}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
<template x-for="(im, i) in extraImages" :key="im.url">
|
||||
<button
|
||||
type="button"
|
||||
:data-lb-index="5 + i"
|
||||
:data-thumb-url="im.thumb"
|
||||
@click="showLb(5 + i)"
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[var(--c-gray-200)] bg-cover bg-center`}
|
||||
x-bind:class="lbIndex === Number($el.dataset.lbIndex) ? 'opacity-100 ring-1 ring-black' : ''"
|
||||
:aria-label="String(5 + i + 1)"
|
||||
>
|
||||
<span class="absolute left-1 top-1 z-10 bg-black/70 px-1 py-0.5 text-[8px] sm:text-[9px] tracking-[0.16em] text-white backdrop-blur-sm" x-text="String(5 + i + 1).padStart(2, '0')"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="prevLb()"
|
||||
class="absolute left-3 sm:left-5 top-2 z-20 w-[52px] sm:w-[72px] h-[30px] sm:h-[34px] border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Previous image')}
|
||||
>
|
||||
<svg class="w-4 h-4 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
<path d="M6 14l6-6 6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="nextLb()"
|
||||
class="absolute left-3 sm:left-5 bottom-2 z-20 w-[52px] sm:w-[72px] h-[30px] sm:h-[34px] border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Next image')}
|
||||
>
|
||||
<svg class="w-4 h-4 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
<path d="M6 10l6 6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="prevLb()"
|
||||
class="absolute left-[82px] sm:left-[130px] top-1/2 -translate-y-1/2 z-20 w-10 h-10 border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
class="absolute left-2 sm:left-5 top-1/2 -translate-y-1/2 z-20 w-10 h-10 border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Previous image')}
|
||||
>
|
||||
<svg class="w-5 h-5 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
|
||||
@ -21,7 +21,7 @@ interface Props {
|
||||
const { article, error = null, preview = false, imageTotal = 0 } = Astro.props
|
||||
const { locale, t } = getI18n(Astro)
|
||||
|
||||
// 后端详情接口 images 仅返回主图(look 图),每张主图的细节图嵌套在它自己的 detail 子数组下。
|
||||
// 后端详情接口 images 仅返回主图(走秀主图),每张主图的细节图嵌套在它自己的 detail 子数组下。
|
||||
// 故此处 images 即为主图集合,默认只渲染它们;detail 随主图一起带上,供「细节入口」展开。
|
||||
const images = (article?.images ?? [])
|
||||
.map((im) => ({
|
||||
@ -32,16 +32,15 @@ const images = (article?.images ?? [])
|
||||
thumb: ssrAbs(im.thumb || im.image || ""),
|
||||
raw: im.image || "", // 相对路径:收藏快照存相对地址,不把宿主/端口写死进库
|
||||
name: im.name || "",
|
||||
lookIndex: Number(im.look_index ?? 0),
|
||||
}))
|
||||
.filter((im) => im.url)
|
||||
|
||||
// 递给浏览器端 Alpine 的细节图数据:只序列化「带细节图的主图」投影(不带主图自身 URL,
|
||||
// 避免把整份 images 重复塞进 HTML)。元素形状与 images 元素一致(look_index + detail),
|
||||
// 避免把整份 images 重复塞进 HTML)。元素形状与 images 元素一致(id + detail),
|
||||
// 故 Alpine 的 buildDetailMapFrom() 能与「登录后拿回的完整 images」共用同一套解析逻辑。
|
||||
const detailPayload = (article?.images ?? [])
|
||||
.filter((im) => (im.detail?.length ?? 0) > 0)
|
||||
.map((im) => ({ look_index: Number(im.look_index ?? 0), detail: im.detail }))
|
||||
.map((im) => ({ id: im.id ?? "", detail: im.detail }))
|
||||
|
||||
// 未登录预览上限:详情页(图集)未登录最多展示前 PREVIEW_LIMIT 张图片;其余由登录门禁提示,
|
||||
// 已登录(客户端 loadAuthedState 拉全量)或图片本就不超上限时全部展示。前 PREVIEW_LIMIT 张服务端已渲染(SEO/无 JS 友好)。
|
||||
|
||||
@ -30,15 +30,14 @@ const images = (snap?.images ?? [])
|
||||
thumb: ssrAbs(im.thumb || im.image || ""),
|
||||
raw: im.image || "", // 相对路径:收藏快照存相对地址,不把宿主/端口写死进库
|
||||
name: im.name || "",
|
||||
lookIndex: Number(im.look_index ?? 0),
|
||||
}))
|
||||
.filter((im) => im.url)
|
||||
|
||||
// 递给浏览器端 Alpine 的副图数据:只序列化「带副图的主图」投影(look_index + detail),
|
||||
// 供 lib/galleryDetail.ts 构建 look_index → 副图 映射(与登录后完整 images 同构解析)。
|
||||
// 递给浏览器端 Alpine 的副图数据:只序列化「带副图的主图」投影(id + detail),
|
||||
// 供 lib/galleryDetail.ts 构建 主图id → 副图 映射(与登录后完整 images 同构解析)。
|
||||
const detailPayload = (snap?.images ?? [])
|
||||
.filter((im) => (im.detail?.length ?? 0) > 0)
|
||||
.map((im) => ({ look_index: Number(im.look_index ?? 0), detail: im.detail }))
|
||||
.map((im) => ({ id: im.id ?? "", detail: im.detail }))
|
||||
|
||||
// 未登录预览上限:详情页(图集)未登录最多展示前 PREVIEW_LIMIT 张图片;其余由登录门禁提示,
|
||||
// 已登录(客户端 loadAuthedState 拉全量)或图片本就不超上限时全部展示。前 PREVIEW_LIMIT 张服务端已渲染(SEO/无 JS 友好)。
|
||||
|
||||
Reference in New Issue
Block a user