Files
frontend_v2/.workbuddy/memory/2026-09-13.md
toom1996 d6cb616337 update
2026-09-13 21:39:50 +08:00

12 KiB
Raw Permalink Blame History

2026-09-13 工作日志

首页图片加载状态 + Swiper 初始化优化(14:5x)✅

用户建议对首页 Index.astro 的 Hot Brand / Street Style 图片加载与 Swiper 初始化做一层优化(7 点)。改动:

  • 统一图片状态初始化逻辑:Hot Brand hs-img-box 与 Street Style ss-img-box 两份完全重复的内联 x-data="{loaded,error,init()检查complete+naturalWidth,handleLoad,handleError}" 抽成共享 Alpine 组件 imgLoader,在 <script> 里 document.addEventListener('alpine:init', () => Alpine.data('imgLoader', () => ({...}))) 注册,两处 markup 改 x-data="imgLoader()"。保留 init 的 complete+naturalWidth 立即置位 与 @load/@error 兜底,dispatch img-ready 不变。
  • Swiper 初始化重写:initSwipers() 开头先 destroySwipers()(防重复/VT 旧实例泄漏);统一入口 setupSwipers() 用 requestAnimationFrame 延后一帧,挂到 DOMContentLoaded(首次)与 astro:page-load(VT 重新进入);swiper.update() 用 updateScheduled 标志 + rAF 合并(原 updateAllSwipers 的 RAF 合并保留并改名 scheduleSwiperUpdate);Tab 点击监听由 document.querySelectorAll('.tab').forEach(addEventListener) 改为 document 事件委托 e.target.closest('.tab'),修复 View Transitions 后新 DOM 上监听失效(旧 .tab 元素已 detach)的问题。
  • 验证:lint 0;dev server(:4321) 首页渲染 HTML 含 x-data="imgLoader()";打包模块 Index.astro?astro&type=script 含 imgLoader/astro:page-load/scheduleSwiperUpdate/destroySwipers。当前 SSG 数据下 Hot Brand 有图(1 个 imgLoader 渲染)、Street Style 列表为空走 ComingSoon(符合数据,非代码问题)。

i18n 字典审计:补缺失 + 删死条目 + 修中文 key(15:4x)✅

用户要求:找出没翻译的 i18n 补上、没用到的删掉。审计方法:解析 dictionary.ts 全部 key + 全代码字面量 t('...')/translate('...') 调用 + 对每个 key 在代码做存在性 grep(覆盖城市等变量用法)。

  • 缺失(代码 t() 用到但字典没有,cn 页回退英文):brands(RunwayLooks 设计师筛选)/Most photos/Newest year/Oldest year(StreetSnaps 排序)/Year(StreetSnaps 分组标题)/Sort(StreetSnaps 分组标题)。已补 { cn: ... }(品牌/照片最多/年份最新/年份最早/年份/排序)。
  • 中文当 key(英文态显示中文):'全部的 runway' 被 Layout 导航×2 与 RunwayLooks <h1> 硬编中文使用。改为英文 key 'all runway looks': { cn: '全部的 runway' },三处调用改为 t('all runway looks')(其中 RunwayLooks h1 由硬编改为 {t('all runway looks')})。utils.ts 文档注释示例同步改英文。
  • 死条目(0 次 t() 调用,连引号串都没出现):删 20 条 —— runway archive / pre-fall / account / email / coming soon / role / username / appearance / soon / now / all / image / gallery / light / on / off / default / hi / logout / street snaps(多为账户中心/外观设置/历史相对时间等半成品或未接 UI 的预留翻译,且有用同义 key 如 my account/sign out/street style 替代)。
  • 结果:字典 92 → 78 条;重跑审计确认 无中文真 key、无缺失、无死条目(仅剩注释里的 英文原文 示例误报,非真实 key)。
  • 验证:lint 0;/en/runway-looks h1 渲染 all runway looks、/cn/runway-looks 渲染 全部的 runway,i18n 正常。

前端 API 客户端拆分:api.ts → api/crypto/locale/auth 四文件(19:5x)✅

用户要求把 src/lib/api.ts 里的登录/权限与加密逻辑抽出去,让 api.ts 只负责业务取数。已拆分:

  • src/lib/crypto.ts:前端请求签名 clientSign()(HMAC-SHA256 → X-Sign/X-Sign-Ts/X-Sign-Nonce),原 CLIENT_SIGN_SECRET/CLIENT_SIGN_TTL 一并移入。
  • src/lib/locale.ts:请求语言透传 setApiLocale/currentLocale/withLocale(原在 api.ts)。抽出来是为了避免 api↔auth 循环依赖(auth.ts 的 fetchMe 也要用 currentLocale)。
  • src/lib/auth.ts:账号体系与鉴权 —— getUser/getAccessToken/getRefreshToken/clearSession/saveSession/refreshSession/authedFetch(内部)/authJson/fetchMe/login/logout + AuthUser 类型 + BASE_API_URL。
  • src/lib/api.ts:重写后只保留 类型定义(BrandEntry/ArticleItem/RawArticle…)/ base 解析 / request() 公开原语 / 全部业务取数(走秀/品牌/街拍/历史回查/SSG)。request() 改为 import { clientSign } from "./crypto"、import { getAccessToken, getRefreshToken, refreshSession } from "./auth"、import { withLocale, currentLocale } from "./locale"。
  • 消费方 import 已全量更新:getUser/login/logout/fetchMe/authJson/getAccessToken 改从 @/lib/auth 或 ./auth 导入;setApiLocale 改从 @/lib/locale;其余(getArticles/getBrands/getStreetSnaps/getSsrArticle/getHistoryMeta/toAbs/ssrBase/RawXxx 类型等)仍在 @/lib/api。涉及:StreetSnaps/StreetSnap/RunwayLooks/Item/Index.astro、en/cn 的 login/account/item/[id].astro、AuthModal.astro、Layout.astro、lib/favorites.ts、lib/history.ts。
  • 依赖方向无环:api→{crypto,auth,locale};auth→locale;locale→i18n。
  • 验证:astro build 退出码 0,全部路由预渲染成功,无 Vite/import 解析错误(Vite 会在命名导出缺失或路径错误时报错,故等于全链路导入校验通过)。本地无 TypeScript 二进制,未跑 tsc(npx tsc 会拉到无关包)。
  • 脚本教训:PowerShell 内联 node -e 对正则/双引号转义极不可靠,凡要正则或双引号一律写 .mjs 脚本文件再 node 执行。

Index.astro 代码排版整理(15:1x)✅

用户嫌 Index.astro 乱。纯排版,逻辑/class 未改:

  • 原来第 48 行把 tabbar 第一个 Runway <li> 与注释全挤在一行(其余两个 li 正常多行)→ 重排为三个 li 统一多行缩进。
  • 原来整段 <style>(含 Hot Brand / Street Style 全部样式)压成一行 → 重排为多行、按 section 注释分组、每条规则缩进 2 空格、keyframes 体换行。注意:@reference "tailwindcss"; 是 Tailwind v4 编译期指令,最终 HTML 里不会出现(被处理掉),样式已生效(.hs-swiper 在 SSR 输出出现 5 次)。
  • 三块面板(Runway/Social Media/Street Style)统一 2 空格缩进与 <!-- --> 注释;Hot Brand 的 skel/spin/img/caption 与 Street Style 同结构重排。
  • <script> 保持原样(已带分节注释,较整洁)。
  • 验证:lint 0;dev 首页 SSR 仍渲染 imgLoader(1)/tabbar(1)/hs-swiper(5 含 style)/hs-ring(2)/hs-cap-brand(3)/ss-cap-loc(1),结构完整。

item 页 /me/favorites/checks 调用三遍诊断(17:2x)🔍

用户问已登录访问 item 页为何 /me/favorites/checks 发三遍。checkFavorited()(favorites.ts:215)每次只单发一次请求(无循环),故三遍 = 三个独立调用点:

  1. Item.astro articleView.init()→initImageFavs() 查 SSR 前 5 张图片(i/j 级,line 647→777)。
  2. 同组件 loadFull() 补全量图后再次 initImageFavs() 查全量图片(line 678→复用 777)。
  3. en/item/[id].astro fav-fab 脚本 checkFavorited([meta.id]) 查整篇图集(r/s 级,line 105-106,粒度不同不可省)。 未登录 0 次(serverMode() 返 false 空返回;fav-fab 也跳过)。①② 为图片级、因图分批上 DOM 时序拆两次,可优化:init 仅本地 isFavorited 即时填充、服务端校正推到 loadFull 后统一一次 → 总 3→2。已向用户说明根因并给轻量优化方案,用户尚未确认是否改(待定)。同时清理 MEMORY.md 超长截断:精简合并、修正两处过时(底部门禁块已删 / login to view all photos key 已删),补全该诊断章节。

item 页收藏态合并进主接口的设计讨论(18:1x)💡

用户进一步问:把「文章(图集)收藏态」与「图片收藏态」都合并进「获取图片的接口」返回是否合理。分析结论:

  • 详情页(authed 全量接口)合并:合理、推荐。已登录走 getArticleDetailAuthed(带 Bearer,后端 detail handler 已有 user 态)拉全量图片,正好顺带返回 favorited(图集级 r/s)+ images[].favorited(图片级 i/j)。收益:item 页已登录从 3 次 /me/favorites/checks → 0 次、消除图片分批上 DOM 的时序两次查询、首屏爱心不闪。条件:仅附加在带 Bearer 的 authed 接口,匿名公开接口(getSsrArticle)不动(保留缓存/公开性);响应用结构化字段返回。
  • 列表页不合并:保持独立 /me/favorites/checks 批量校验。列表分页、公开匿名为主、量大,合并会让接口无法缓存且每 item join favorites 成本高;现有「进页查当前页可见 id」更划算。
  • 实时性:合并后收藏态随进页主接口一次性带出,页内点爱心走本地 optimistic + 后台同步即可,无需回查(跨设备校正需求弱)。
  • 落地成本:需改 Go 后端 detail handler(加 favorites JOIN/IN 查询)+ 前端删 Item.astro/StreetSnap.astro 三处 checkFavorited 调用、改读主接口响应。用户尚未确认是否落地(待定)。

item 页收藏态合并进主接口:已落地(19:0x)✅

用户确认「合并,并把没用到的接口去掉」。已完成前后端合并 + 清理:

  • 后端(Go,已 go build ./... 通过):走秀 article_handler.go:117-137 与街拍 street_snap_handler.go:99-119 的 Detail handler,在 uid:=middleware.UserIDFrom(c); ok 时把 detail.UID + 全部 detail.Images[].ID 一次性 h.favSvc.Check(Check 内部 WHERE user_id=? AND target_uid IN (?)),回写 detail.Favorited(图集级 r/s)+ detail.Images[i].Favorited(图片级 i/j)。匿名(ok=false)不动 → 公开接口保持可缓存。DTO:PublicArticleImage.Favorited / PublicStreetSnapDetail.Favorited(json:"favorited,omitempty")。
  • 前端(读取主接口响应,不再单独查):Item.astro/StreetSnap.astro 把 loadFull() 重写为 loadAuthedState()——登录态(getUser())即用一次 getArticleDetailAuthed/getStreetSnapDetailAuthed 拿回完整图片集 + 收藏态:图集级 fav 经 window.dispatchEvent(new CustomEvent('fav:set',{detail:{id, fav}})) 广播给右下角 fav-fab;图片级以 im.favorited 为准重建 favIds(覆盖 initImageFavs 的本地初值)。init() 与 auth:login 监听都改调 loadAuthedState()(不再限于 preview)。
  • fav-fab(en/cn/item/[id].astro):删 checkFavorited([meta.id]),改 window.addEventListener('fav:set', ...) 监听,fav=true 时 hydrateFavorited 写回本地 + sync() 刷新标签;初始仍本地 isFavorited 即时填充。
  • 接口清理:删后端唯一无调用的 dead route legacy.POST("/auth/favorites/check", ...)(单数 legacy 别名,前端从只用复数 /me/favorites/checks)。列表页 RunwayLooks.astro/StreetSnaps.astro 仍用 /me/favorites/checks 批量校正,该接口保留(按设计讨论:列表不分页合并)。
  • 结果:item 页已登录收藏校验请求由 3 次 → 0 次(合并进 1 次详情请求);read_lints 相关文件 0 错误;后端编译 0 错误。验证:dev(:4321) 登录后打开 item 页 Network 不再出现 /me/favorites/checks,爱心态随详情响应直接正确(图集+图片级)。
  • 关键约束(铁律补充):详情页收藏态只能合并进「带 Bearer 的 authed 详情接口」,匿名公开接口与列表接口绝不合并(保缓存/控成本)。