From b04a511b26e558f9b8da27d9013c9101f011cc52 Mon Sep 17 00:00:00 2001 From: toom1996 Date: Thu, 17 Sep 2026 22:06:11 +0800 Subject: [PATCH] update --- .workbuddy/memory/2026-09-17.md | 105 +++++++++++++++++++ .workbuddy/memory/MEMORY.md | 14 ++- src/components/StreetSnapCard.astro | 2 +- src/components/views/RunwayLooksDetail.astro | 73 +++++++++++++ src/components/views/StreetSnapsDetail.astro | 72 +++++++++++++ src/lib/detailFav.ts | 59 +++++++++++ 6 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 .workbuddy/memory/2026-09-17.md create mode 100644 src/lib/detailFav.ts diff --git a/.workbuddy/memory/2026-09-17.md b/.workbuddy/memory/2026-09-17.md new file mode 100644 index 0000000..b4259b5 --- /dev/null +++ b/.workbuddy/memory/2026-09-17.md @@ -0,0 +1,105 @@ +# 2026-09-17 工作日志 + +## 修复详情页空壳 + 评估 components/views 目录 ✅(15:12–15:30) +用户问 `components/views/` 是否还有必要(pages 不就是 view 吗),并指出 detail 组件是空的,疑似之前拆文件拆错。 + +### 根因(已查清) +- 详情实现原本在 `src/components/views/ItemPage.astro`(一个组件用 `type` 参数通吃 runway/street,含收藏/历史脚本)。 +- commit `70a3304`(拆分)把 4 个 `[id].astro` 的 import 从 `ItemPage`(带 `type`)改成 `RunwayLooksDetail`/`StreetSnapsDetail`,但**这两个新文件建成空的(0 字节)**,且把 `ItemPage.astro` **删除了**。 +- 后果:走秀/街拍详情页(`prerender=false` 的动态路由)此后一直渲染空壳,无报错不易察觉。 +- 验证手段:`git log --all -- '*ItemPage*'` 定位 → `git show ad028c9:...ItemPage.astro` 找回 158 行完整实现;`70a3304` 该路径已不存在(在另一分支)。 + +### 修复(恢复原始可用结构) +- 从 `ad028c9` 恢复 `src/components/views/ItemPage.astro`(内容逐字还原,含 Props 接口、SSR 取数、TDK、收藏/历史 ` diff --git a/src/components/views/StreetSnapsDetail.astro b/src/components/views/StreetSnapsDetail.astro index e69de29..9d6e12c 100644 --- a/src/components/views/StreetSnapsDetail.astro +++ b/src/components/views/StreetSnapsDetail.astro @@ -0,0 +1,72 @@ +--- +import Layout from "@/layouts/Layout.astro" +import StreetSnapCard from "@/components/StreetSnapCard.astro" +import { getSsrStreetSnap } from "@/lib/ssr" +import { getI18n } from "@/lib/i18n" +import { config } from "@/lib/config" + +// 街拍(street snap)详情视图,en/cn 两页共用(语言由 Astro.currentLocale 运行时决定)。 +// 注意:export const prerender = false 必须留在页面文件(Astro 只读页面模块的该导出), +// 故 id 由页面从 Astro.params 取出后作为 prop 透传进来。 +interface Props { + id?: string +} + +const { id } = Astro.props +const { locale, t } = getI18n(Astro) + +let snap = null +let snapErr = null +let preview = false +let imageTotal = 0 + +const data = await getSsrStreetSnap(id, Astro.currentLocale) +snap = "snap" in data ? data.snap : null +snapErr = data.error +preview = data.preview ?? false +imageTotal = data.imageTotal ?? 0 + +// 供客户端「收藏 / 历史」使用的精简元信息(服务端已拿到,直接透传,避免前端再发请求)。 +const canonId = String(snap?.id ?? id ?? "") +const meta = { id: canonId, type: "street", title: snap?.title ?? "", cover: snap?.cover ?? "", brand: "" } + +// 详情页动态 TDK:标题用街拍标题,描述/关键词按语言生成(避免嵌套反引号,用拼接)。 +const SITE_NAME = config.siteName ?? "Xisoa" +const baseName = snap?.title || "" +const detailTitle = baseName || SITE_NAME +const detailDesc = locale === "cn" + ? `${detailTitle} —— 来自全球街头的时尚抓拍,收录于 ${SITE_NAME} 时尚档案。` + : `${detailTitle} — street style snaps from cities around the world, in the ${SITE_NAME} fashion archive.` +const detailKeywords = `${snap?.title || ""},street style,street snaps,fashion,${SITE_NAME}`.replace(/^,/, "") + +const saveLabel = t("save") +const savedLabel = t("saved") +--- + + + + + + + +{snap && ( + +)} + + diff --git a/src/lib/detailFav.ts b/src/lib/detailFav.ts new file mode 100644 index 0000000..67e8c1c --- /dev/null +++ b/src/lib/detailFav.ts @@ -0,0 +1,59 @@ +import { getUser } from "@/lib/auth" +import { isFavorited, toggleFavorite, hydrateFavorited } from "@/lib/favorites" +import { recordHistory } from "@/lib/history" + +/** + * 详情页「收藏 / 历史」按钮初始化。 + * 按钮本体(#fav-fab)由详情视图渲染,元信息经 data-* 透传(不依赖 define:vars,避免打包期 import 进 IIFE 内报错)。 + * runway / street 两类详情共用本逻辑,区别仅在于透传的 data-type 值。 + * 注意:本函数只在详情页(含 #fav-fab 的页面)调用一次。 + */ +export function initDetailFav() { + const btn = document.getElementById("fav-fab") + const label = document.getElementById("fav-label") + if (!btn) return + + 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) return + + // 浏览历史:点开文章详情即记一条(登录态;列表页不再记)。同一篇反复打开,后端 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() + + // 登录态收藏校正已合并进详情接口:articleView / streetSnapView 取到服务端收藏态后, + // 通过 fav:set 事件广播当前图集是否已收藏;此处监听并写回本地(换设备后与服务端一致)。 + window.addEventListener("fav:set", (e: Event) => { + const detail = (e as CustomEvent<{ id: string; fav: boolean }>).detail + if (!detail || detail.id !== meta.id) return + if (detail.fav) { + hydrateFavorited(meta.id, meta.type as "runway" | "street", meta.title, meta.cover, meta.brand) + } + sync() + }) + + btn.addEventListener("click", () => { + toggleFavorite({ + id: meta.id, + type: meta.type, + title: meta.title, + cover: meta.cover, + brand: meta.brand, + } as any) + sync() + }) +}