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

58 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 详情接口」,匿名公开接口与列表接口绝不合并(保缓存/控成本)。