This commit is contained in:
toom1996
2026-09-13 21:39:50 +08:00
parent c85c561a99
commit d6cb616337
24 changed files with 1076 additions and 813 deletions

View File

@ -45,7 +45,7 @@ for (const d of (article?.details ?? []) as { look_index?: number; images?: unkn
}
// 未登录预览上限:详情页(图集)未登录最多展示前 PREVIEW_LIMIT 张图片;其余由登录门禁提示,
// 已登录(客户端 loadFull 拉全量)或图片本就不超上限时全部展示。前 PREVIEW_LIMIT 张服务端已渲染(SEO/无 JS 友好)。
// 已登录(客户端 loadAuthedState 拉全量)或图片本就不超上限时全部展示。前 PREVIEW_LIMIT 张服务端已渲染(SEO/无 JS 友好)。
const PREVIEW_LIMIT = 5
const previewImages = images.slice(0, PREVIEW_LIMIT)
@ -214,7 +214,7 @@ const homeHref = href(locale, ROUTES.home)
))
}
{/* 登录后追加的额外图片:extraImages 由客户端 loadFull() 在已登录时填充,未登录为空不渲染 */}
{/* 登录后追加的额外图片:extraImages 由客户端 loadAuthedState() 在已登录时填充,未登录为空不渲染 */}
<template x-for="(im, i) in extraImages" :key="im.url">
<figure
:data-image-index="5 + i"
@ -585,8 +585,9 @@ const homeHref = href(locale, ROUTES.home)
</div>
<script>
import { getUser, getArticleDetailAuthed, toAbs } from "@/lib/api"
import { isFavorited, toggleImageFavorite, checkFavorited } from "@/lib/favorites"
import { getArticleDetailAuthed, toAbs } from "@/lib/api"
import { getUser } from "@/lib/auth"
import { isFavorited, toggleImageFavorite } from "@/lib/favorites"
document.addEventListener("alpine:init", () => {
Alpine.data("articleView", () => ({
@ -609,6 +610,8 @@ const homeHref = href(locale, ROUTES.home)
isAuthed: false,
// 未登录门禁:后端截断图片集时为 true(详情页最多看 PREVIEW_LIMIT 张)
preview: false,
// 已登录态图集级(gallery,r=/s=)收藏态:由 loadAuthedState 从详情接口取回,广播给 fav-fab
galleryFav: false,
// 已登录后客户端补拉的全量图片(前 PREVIEW_LIMIT 张已由 SSR 服务端渲染)
extraImages: [] as { id: string; url: string; thumb: string; raw: string; name: string; lookIndex?: number }[],
@ -647,39 +650,53 @@ const homeHref = href(locale, ROUTES.home)
this.initImageFavs()
this.maybeLocateImageFromUrl()
// 已登录且后端未登录截断(preview):客户端拉全量图片集并追加渲染
if (getUser() && this.preview) this.loadFull()
// 登录后(如从门禁跳登录回跳)自动补全全量
window.addEventListener("auth:login", () => { this.isAuthed = true; if (this.preview) this.loadFull() })
// 已登录:用一次详情请求拿回完整图片集 + 收藏态(合并原三处 /me/favorites/checks 调用)
if (getUser()) this.loadAuthedState()
// 登录后(如从门禁跳登录回跳)自动补全全量 + 收藏态
window.addEventListener("auth:login", () => { this.isAuthed = true; if (getUser()) this.loadAuthedState() })
},
// 已登录用户:拉取完整图片集,把第 PREVIEW_LIMIT+1 张起的图片追加进 extraImages 渲染。
// 后端详情接口对已登录(带 Bearer)返回完整图片集,故此处能拿到全量。
async loadFull() {
if (!this.preview) return
// 已登录:用一次详情请求拿回「完整图片集 + 收藏态」,合并原三处 /me/favorites/checks 调用。
// gallery 级(r=)收藏态广播给页面右下角 fav-fab;image 级(i=)收藏态直接重建 favIds。
async loadAuthedState() {
if (!getUser()) return
try {
const full = await getArticleDetailAuthed(this.galleryUid)
if (!full || !Array.isArray(full.images)) return
const mapped = (full.images || []).slice(5).map((im: any) => ({
id: im.id || "",
url: toAbs(im.image || ""),
thumb: toAbs(im.thumb || im.image || ""),
raw: im.image || "",
name: im.name || "",
lookIndex: Number(im.look_index ?? 0),
}))
this.extraImages = mapped
// 后端详情接口对所有用户统一返回 high 高清样式(付费墙已取消),首屏图即为高清,
// 无需客户端再按 VIP 替换。仅把第 6 张起的图片追加进 extraImages 渲染。
await this.$nextTick()
this.imageCount = document.querySelectorAll(
"#article-gallery [data-image-index]"
).length
this.initImageFavs()
this.initThumbLazyLoading()
this.maybeLocateImageFromUrl()
// gallery 级收藏态 → 广播给 fav-fab(去掉单篇 /me/favorites/checks 查询)
this.galleryFav = !!full.favorited
window.dispatchEvent(
new CustomEvent("fav:set", { detail: { id: this.galleryUid, fav: this.galleryFav } }),
)
// image 级收藏态:以服务端 favorited 为准重建 favIds(覆盖 initImageFavs 的本地初值)
const favIds: string[] = []
const mark = (im: any) => { if (im && im.id && im.favorited) favIds.push(im.id) }
// 超预览上限才追加第 6 张起图片(前 5 张已由 SSR 渲染,仅补收藏态)
if (this.preview) {
this.extraImages = (full.images || []).slice(5).map((im: any) => ({
id: im.id || "",
url: toAbs(im.image || ""),
thumb: toAbs(im.thumb || im.image || ""),
raw: im.image || "",
name: im.name || "",
lookIndex: Number(im.look_index ?? 0),
}))
await this.$nextTick()
this.imageCount = document.querySelectorAll(
"#article-gallery [data-image-index]"
).length
;(full.images || []).slice(5).forEach(mark)
this.initThumbLazyLoading()
this.maybeLocateImageFromUrl()
}
// 前 5 张收藏态
;(full.images || []).slice(0, 5).forEach(mark)
this.favIds = favIds
} catch {
/* 失败维持截断态,门禁仍在 */
/* 失败维持本地/截断态,门禁仍在 */
}
},
@ -768,15 +785,12 @@ const homeHref = href(locale, ROUTES.home)
.filter(Boolean)
},
// 本地即时填充收藏态(未登录态,或登录态加载服务端数据前的占位,避免爱心闪烁)。
// 登录态的服务端校正已合并进 loadAuthedState(),不再单独请求 /me/favorites/checks。
initImageFavs() {
const ids = this.allImageFavIds()
if (!ids.length) return
// 本地先即时填充,避免爱心闪烁
this.favIds = ids.filter((id) => isFavorited(id))
// 登录态用服务端校正(换设备后与服务端一致)
checkFavorited(ids).then((hit) => {
this.favIds = ids.filter((id) => hit.has(id))
})
},
isFav(id: string) {