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

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