12 KiB
12 KiB
2026-09-13 工作日志
首页图片加载状态 + Swiper 初始化优化(14:5x)✅
用户建议对首页 Index.astro 的 Hot Brand / Street Style 图片加载与 Swiper 初始化做一层优化(7 点)。改动:
- 统一图片状态初始化逻辑:Hot Brand
hs-img-box与 Street Styless-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 兜底,dispatchimg-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-looksh1 渲染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)每次只单发一次请求(无循环),故三遍 = 三个独立调用点:
Item.astroarticleView.init()→initImageFavs()查 SSR 前 5 张图片(i/j 级,line 647→777)。- 同组件
loadFull()补全量图后再次initImageFavs()查全量图片(line 678→复用 777)。 en/item/[id].astrofav-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 photoskey 已删),补全该诊断章节。
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 详情接口」,匿名公开接口与列表接口绝不合并(保缓存/控成本)。