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

This commit is contained in:
toom1996
2026-09-02 21:50:30 +08:00
parent 50e5775dc8
commit 64af75c0a9
23 changed files with 1730 additions and 285 deletions

View File

@ -3,6 +3,7 @@ import Layout from "@/layouts/Layout.astro"
import Item from "@/components/Item.astro"
import StreetSnap from "@/components/StreetSnap.astro"
import { getSsrArticle, getSsrStreetSnap } from "@/lib/api"
import { getI18n } from "@/i18n/utils"
// 按需 SSR:走秀(runway)与街拍(street snap)详情统一收口到 /item/[id]。
// 对外 id 带类型前缀(r=runway / s=snap,见后端 hashid.EncodeTyped),据此直接查对应表,无歧义、无需回落。
@ -11,6 +12,8 @@ export const prerender = false
const { id } = Astro.params
const isSnap = !!id && id[0] === "s"
const { t } = getI18n(Astro)
let article = null
let articleErr = null
let snap = null
@ -28,8 +31,93 @@ if (isSnap) {
article = data.article
articleErr = data.error
}
// 供客户端「收藏 / 历史」使用的精简元信息(服务端已拿到,直接透传,避免前端再发请求)。
const meta = isSnap
? { id: id ?? "", type: "street", title: snap?.title ?? "", cover: snap?.cover ?? "", brand: "" }
: { id: id ?? "", type: "runway", title: article?.title ?? "", cover: article?.cover ?? "", brand: article?.brand_name ?? "" }
const saveLabel = t("save")
const savedLabel = t("saved")
---
<Layout>
{article ? <Item article={article} error={articleErr} /> : <StreetSnap snap={snap} error={snapErr} />}
</Layout>
<style>
#fav-fab.is-active .fav-heart { fill: currentColor; }
</style>
{(article || snap) && (
<button id="fav-fab" type="button"
data-id={meta.id}
data-type={meta.type}
data-title={meta.title}
data-cover={meta.cover}
data-brand={meta.brand}
data-save-label={saveLabel}
data-saved-label={savedLabel}
class="fixed bottom-6 right-6 z-50 inline-flex items-center gap-2 rounded-full border border-black/40 bg-white/90 px-5 py-3 text-[11px] font-bold uppercase tracking-[0.2em] text-black shadow-sm backdrop-blur transition-colors hover:bg-black hover:text-white">
<svg class="fav-heart h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><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>
<span id="fav-label">{saveLabel}</span>
</button>
)}
{/* 注意:不能用 define:vars 传 meta —— 它会把脚本包成内联 IIFE 并把 import 塞进函数体内,
导致 SyntaxError(import 只能在模块顶层)。故改由 data-* 属性透传,脚本自行读取。 */}
<script>
import { getUser } from "@/lib/api"
import { isFavorited, toggleFavorite, checkFavorited, hydrateFavorited } from "@/lib/favorites"
import { recordHistory } from "@/lib/history"
const btn = document.getElementById("fav-fab")
const label = document.getElementById("fav-label")
if (btn) {
const meta = {
id: btn.dataset.id || "",
type: btn.dataset.type || "",
title: btn.dataset.title || "",
cover: btn.dataset.cover || "",
brand: btn.dataset.brand || "",
}
const saveLabel = btn.dataset.saveLabel || "save"
const savedLabel = btn.dataset.savedLabel || "saved"
if (meta.id) {
// 浏览历史:点开文章详情即记一条(登录态;列表页不再记)。同一篇反复打开,后端 upsert 刷新时间。
if (getUser()) recordHistory(meta.id)
const sync = () => {
const fav = isFavorited(meta.id)
if (label) label.textContent = fav ? savedLabel : saveLabel
btn.classList.toggle("is-active", fav)
}
sync()
// 登录态:异步问服务端当前篇是否已收藏并写回本地(换浏览器/设备后,爱心状态与服务端一致)
if (getUser()) {
checkFavorited([meta.id])
.then((set) => {
if (set.has(meta.id)) hydrateFavorited(meta.id, meta.type as "runway" | "street", meta.title, meta.cover, meta.brand)
sync()
})
.catch(() => {})
}
btn.addEventListener("click", () => {
if (!getUser()) {
window.location.href = "/login"
return
}
toggleFavorite({
id: meta.id,
type: meta.type,
title: meta.title,
cover: meta.cover,
brand: meta.brand,
} as any)
sync()
})
}
}
</script>