133 lines
20 KiB
Markdown
133 lines
20 KiB
Markdown
# 2026-09-14
|
||
|
||
## 前端 API 客户端二次拆分:SSG 构建期取数抽到 ssg.ts ✅
|
||
用户嫌 api.ts 杂乱,要求把构建期 SSG 接口单独成文件。
|
||
- 新建 `src/lib/ssg.ts`:`SsgPopularBrand` 类型 + `getSsgIndexRunway`/`getSsgHotBrands`/`getSsgStreetSnapPopular`(原 api.ts 末尾「SSG 构建专用」段)。import `requestSsg` from `./request`、`import type { BrandEntry, StreetSnapItem } from "./api"`(类型仅用,不引入运行期依赖,无环)。
|
||
- `api.ts`:删除上述类型与 3 个函数、`SsgPopularBrand` 类型;import 去掉 `requestSsg`;文件头注释移除 SSG 分组。
|
||
- 消费方改 import:`Index.astro`、`RunwayLooks.astro` 的 `getSsg*` 改从 `@/lib/ssg` 导入。
|
||
- 验证:`astro build` 退出码 0,全部路由预渲染成功(Vite 导入解析 = 全链路校验)。lint 0。
|
||
|
||
## ⚠️ 重要对账:auth.ts 鉴权函数并未真正拆出(纠正 09-13 总结)
|
||
09-13 总结称 login/logout/fetchMe 已拆到 `auth.ts`,但 09-14 读磁盘:`auth.ts` 只含会话存储原语(getUser/getAccessToken/saveSession/clearSession 等),`login`/`logout`/`fetchMe`/`enforceIdleLogout` 仍在 `api.ts`(约 387–465 行)。即上次的鉴权拆分未落盘(或已被还原)。已同步修正 MEMORY.md,避免以后再绕回去。
|
||
|
||
## 待用户决定是否继续
|
||
- 是否要把 `login`/`logout`/`fetchMe`/`enforceIdleLogout` 真挪到 `auth.ts`(完成上次未落地的拆分)?
|
||
- `getSsrArticle`/`getSsrStreetSnap`(运行期 SSR 按需,非 SSG)是否也要单独成 `ssr.ts`?目前与 `getArticleDetailAuthed`/`getStreetSnapDetailAuthed` 成对留在 api.ts。
|
||
|
||
## 端点路径集中到 endpoints.ts ✅(11:30)
|
||
用户要求把 `/api/v1/xxx` 端点字面量用 map/const 集中,且要与页面路由 `ROUTES` 区分。
|
||
- 新建 `src/lib/endpoints.ts`:导出 `endpoints`(分 `public`/`auth`/`me`/`ssg` 四组;动态 id 用函数如 `runwayLook(id)`;`V1="/api/v1"`、`SSG="/api/internal/ssg"` 前缀仅此定义一次)。`as const` 保证字面路径类型。
|
||
- 与 `src/lib/routes.ts` 的 `ROUTES`(前端跳转 URL)严格区分:endpoints=fetch 接口路径;ROUTES=页面路由(带 /en /cn)。命名上选 `endpoints` 而非 `apiRoutes`/`routes`,避免与 Astro 页面路由(及既有 `routes.ts`)混淆。
|
||
- 替换范围:api.ts 21 处、ssg.ts 3 处(含 `?limit=` 模板)、request.ts 1 处(refresh)。共 25 处端点字面量清零;grep 确认仅剩注释与 endpoints.ts 定义常量。
|
||
- 坑:`enforceIdleLogout` 那行 `auth: false }).catch` 有空格,首轮替换因 old_str 缺空格未命中(工具误报成功),已补替换;`getSsr*` 的 `${b}/api/v1/...` 模板改为 `${b}${endpoints.xxx(id)}`。
|
||
- 验证:`astro build` 退出码 0,全部 11 路由预渲染成功(SSG 三端点经新常量正常解析)。
|
||
|
||
## 用户问「api 地址要不要抽象出来」→ 已落地核查(10:5x)
|
||
- 结论:地址**已经**集中在 `src/lib/config/`(目录,非单文件):`index.ts` 导出 `config`(`validate(isProd?prod:dev)`)、`types.ts` 定义 `AppConfig`、`env.dev.ts`/`env.prod.ts` 两份值。无 `config.ts` 单文件,import `"./config"` 解析到 `config/index.ts`。
|
||
- 三把址:`config.baseApi`(浏览器运行期,request.ts:30)/ `config.baseApiSsr`(SSR 绝对 URL,request.ts:41,api.ts 经 ssrBase())/ `config.baseApiSsg`(构建期,request.ts:183)。**全站无直接 `import.meta.env.BASE_API` 读取**(memory 早先说的 `.env BASE_API` 已不实,dev/prod 值在 env.*.ts 硬编码)。
|
||
- 仅两处「还散」:① `/api/v1` 版本前缀每个端点内联(api.ts 头注释有意为之,便于全局替换升 v2);② `SSG_TOKEN` 故意读 `import.meta.env.SSG_TOKEN`(密钥,不进客户端 bundle,安全设计勿动)。
|
||
- 用户尚未决定:是否抽 `const API_PREFIX="/api/v1"` 常数。
|
||
|
||
## clientSign 安全性讨论(12:0x)→ 未改代码
|
||
- 实证:`client-sign-secret-change-me` **明文出现在产物** `dist/client/_astro/api.BMQlzPjg.js`(esbuild 只压缩不加密字符串)。`crypto.ts` 是 HMAC 软门槛,非加密。
|
||
- 结论:拒绝 VM 混淆/指纹(对内容站负收益,且救不了静态密钥);真正提难度靠服务端。建议方向=**服务端下发滚动密钥 + TTL/nonce 防重放 + IP 失败限流**(需动 backend_v2 的 `middleware.ClientSign`)。
|
||
- 附带发现(**待修**):`crypto.ts:9` `CLIENT_SIGN_SECRET` 是**硬编码 dev 值、未走 env** → 生产要么功能失效要么形同虚设。
|
||
|
||
## 全站代码审计(12:19 起,只读,未改任何文件)→ 报告已出
|
||
范围:js/css/libs/api/复用/目录/命名/性能。产出分级清单(artifact: code-review-plan.md)。核心发现:
|
||
- **P0-1 ⭐ 生产 SSR 仍在跑调试日志**:`lib/http.ts:16` `ENABLED = DEV || import.meta.env.SSR || DEBUG_API`——`import.meta.env.SSR` 在构建出的 Node 端恒 true,故生产每请求都 `res.clone()+await text()` 且 `writeLog()` 往容器 `logs/api-debug.log` **追加写入**(磁盘无限增长)。改法:ENABLED 去掉 SSR 分支。
|
||
- **重复地图**:① `Item.astro`(1161行) vs `StreetSnap.astro`(645行) 灯箱逻辑逐行重复(仅 `#article-gallery`↔`#snap-gallery`),Item 多 density/detailLb → 抽 `createGalleryView()`;② `RunwayLooks` vs `StreetSnaps` 的 pills/goPage/prevPage/nextPage/pageList/markFavs/favState/toggleFav 同构 → 继续下沉 `looks-grid.ts`;③ `full()`(ssrBase 绝对化) 在 Item:17 / StreetSnap:18 逐字相同 → 抽 `ssrAbs()` 到 request.ts;④ `toAbsUrl(){return toAbs(url)}` 是空包装可直接删;⑤ account.astro 的 `favPageList`/`histPageList` 同一段逻辑写两遍。
|
||
- **cn/en 页面**:6 对文件里 8 个**逐字节相同**,`account.astro` 仅差 1 行注释;语言差异 100% 在运行时(`Astro.currentLocale`),页面无任何语言分支 → 可抽共享页面组件。
|
||
- **项目未启用 View Transitions**(Layout 无 ClientRouter,全局搜无):故 `Index.astro:579` 的 `astro:page-load` 监听是死代码(保留无害)。
|
||
- **死配置/过时注释**:`astro.config.mjs` 空 `build{}` 块 + 第 29 行「英文在根路径」注释错误(prefixDefaultLocale:true 实际在 /en);`routes.ts:18-20` 过时注释(称街拍详情走 /street-snaps/[id],实际统一 /item/[id]);`Item.astro:1084` `// TODO 这是干啥的`;`pages/index.astro:3` 「应该跳转到 /en」未实现。
|
||
- **遗留文件**:`_tmp_verify_hist.mjs`/`_tmp_verify2.mjs`/`dev.log`/`server.err`/`footertail.txt`/`README.md`(Astro 模板残留)。`.gitignore` 未覆盖 `_tmp*`/`*.err`/`footertail.txt` → 误入库风险。删除属破坏性操作,**待用户确认**。
|
||
- **配置**:`package.json` `name:"test"`、**无 lint/format/check 脚本**(tsconfig 用 strict 却无处触发校验)。
|
||
- **纠正子代理误判**:dictionary 的 key 大小写差异(`t('Home')` vs dict `home`)**不是死 key**——`translate` 走 `key.toLowerCase()` 查表,都能命中。勿再当死 key 处理。
|
||
- 未深入:`favorites.ts`(8.3KB)、`history.ts`(6.5KB)、`look-grid.css`(8.9KB) 仅做了引用面检查,未逐行审。
|
||
- 用户尚未选择要做哪几项。
|
||
|
||
## i18n 目录迁入 lib 并合并 ✅(17:26,方案 A + 合并 lib/locale)
|
||
用户问「i18n 移到 lib 合理吗 + 合成 i18n.ts」。判定:移入 lib 合理(本就是基础设施层,且 `lib/locale.ts` 原本反向 import i18n);合成单文件要当心 dictionary 会无限增长。
|
||
- **新结构**:`src/lib/i18n/index.ts`(配置+工具+API语言透传,原 config.ts+utils.ts+lib/locale.ts 三合一)+ `src/lib/i18n/dictionary.ts`(纯数据,独立保留,随 UI 增长)。
|
||
- **删除**:`src/i18n/{config,utils,dictionary}.ts` + `src/lib/locale.ts` + 空目录 `src/i18n`。
|
||
- 消费方 16 处 import 改写:`@/i18n/utils`→`@/lib/i18n`、`@/i18n/config`→`@/lib/i18n`、`@/lib/locale`→`@/lib/i18n`、`lib/{ssr,request}.ts` 内 `./locale`→`./i18n`。`Layout.astro` 三段合并成一行 `import { getI18n, LOCALE_NAMES, setApiLocale } from '@/lib/i18n'`。
|
||
- **循环依赖注意(良性)**:`index.ts` import `translate` from `./dictionary`,`dictionary.ts` import `DEFAULT_LOCALE/Locale` from `./index`。均「函数体内才用」,模块求值不触发 TDZ,安全。已在 index.ts 头注释标注。
|
||
- 验证:`npm run build` Complete!;`npx astro check` **0 errors / 0 warnings / 5 hints**(hints 全是预存 `is:inline` 提示,非本次引入)。
|
||
|
||
## 清理 5 个 astro check hint ✅(19:18)
|
||
用户要求清掉上一轮残留的 5 hint。实际构成(非全 is:inline,前轮口误更正):
|
||
- `env.d.ts:8` ts(80003):原 `import type * as Alpine from "alpinejs"` → 改 `import type Alpine from "alpinejs"`(默认导入)。安全依据:tsconfig 继承 astro/strict 且 TS 主动提示 ts(80003) 即证明 esModuleInterop 已开,默认导入被允许;原注释"不能用默认导入"是过时误解,已重写。
|
||
- `LoadingScreen.astro:10` ts(6133)×2:`getI18n(Astro)` 解构出的 `locale`/`getRelativeLocaleUrl` 未用 → 改为 `const { t } = getI18n(Astro)`。
|
||
- `RunwayLooks.astro:263` / `StreetSnaps.astro:219` astro(4000):两个 JSON 数据 `<script>` 带属性被当 is:inline → 显式加 `is:inline` 消除歧义(语义本就该内联)。
|
||
- 复验 `npx astro check`:**0 errors / 0 warnings / 0 hints**。
|
||
|
||
## 清理 lib 未使用导出 ✅(19:46)
|
||
用户要求删 lib 内没用到的函数。子代理扫全 19 个 lib 文件导出符号后,交叉核对结论:
|
||
- **唯一真死代码**:`crypto.ts` 的 `CLIENT_SIGN_TTL`(导出 const,全项目零引用,注释称"调试时读取"但无人读)。已删 + 清过时注释。`clientSign` 仍用硬编码 30s 逻辑(TTL 仅服务端校验,前端不回传)。
|
||
- **导出但只被本模块内部调用(不算没用到,保留)**:`auth.getAccessExpiresAt`(被 isAccessExpiring 调)、`http.loggedFetch`/`installApiLogger`(installApiLogger 模块底自动安装时调)、`i18n.useTranslations`(被 getI18n 调)、`i18n.getLocaleFromUrl`/`LOCALES`(被本模块调)。这些是模块自身 API 面,勿当死代码删。
|
||
- 验证:`npm run build` Complete,`astro check` **0/0/0**。
|
||
|
||
## 详情页灯箱重构:抽共享工厂 + 共享灯箱组件 + 样式统一 ✅(15:5x)
|
||
用户要求「灯箱也帮我改改,顺便看样式怎么和全站更契合」。
|
||
- **新建 `src/lib/gallery.ts`**:`createGalleryView(cfg)` 工厂,装走秀/街拍详情页**逐行同构**的图集+大图灯箱逻辑(约 250 行)。cfg 只有 4 项差异:`rootSelector` / `gallerySelector` / `favType` / `fetchDetail`。导出 `GalleryView`(= `ReturnType<typeof createGalleryView>`)、`GalleryImageRaw`、`GalleryDetailFetcher`、`GalleryExtraImage`。
|
||
- **新建 `src/components/GalleryLightbox.astro`**:共享灯箱标记(约 150 行),props 仅 `images`(SSR 首屏图,需带 thumb)+ `aspect`(`2/3` 走秀 / `3/4` 街拍,两个字面量都写在组件里保证 JIT 扫得到);走秀独有的「细节图簇」经具名插槽 `slot="details"` 注入。
|
||
- **`Item.astro` 1161→约 850 行、`StreetSnap.astro` 645→约 330 行**;两者 `<style>` 整块删除。
|
||
- **共用原子样式上提 `src/styles/global.css`**:`.fav-btn`(爱心描边/实心)、`.locate-flash`、`@media (hover:none)`、`.no-scrollbar`。原因:Astro 组件 `<style>` 会加作用域哈希,而 `.fav-btn` 同时出现在网格图、灯箱、列表卡片,组件作用域**跨组件命中不了**。
|
||
- **`RawStreetSnap.images` 补 `thumb?: string`**(api.ts),使街拍灯箱缩略图条也能用 240px 缩略图。
|
||
|
||
### ⭐ 新踩的坑:对象展开会让 `Alpine.data` 的泛型 T 退化成 `{}`
|
||
`Alpine.data("x", () => ({ ...createGalleryView(cfg), density: 5 }))` → T 推断失败 → `this` 变成 `InferInterceptors<{}> & XDataContext & Magics<{}>` → **所有 `this.xxx` 报 ts(2339)「属性不存在」**(本次一次报 69 个)。
|
||
**根因**:`@types/alpinejs` 的 `data<T extends {[key in keyof T]: T[key]}>(...)` 自引用约束,从「对象字面量」能推断,从「展开表达式」推断不出来。
|
||
**修法**:给回调**显式标注返回类型**——`(): ReturnType<typeof createGalleryView> & ArticleExtras => ({...})`。`this` 于是取上下文类型,全部解析。Item 需为此写出 `ArticleExtras` 类型(约 28 个成员)。
|
||
**顺带三条工厂约束**(已写进 gallery.ts 头注释):
|
||
1. 工厂内**不用 Alpine 魔法**(`$el`/`$nextTick`)→ 改 `cfg.rootSelector` 查根节点 + `requestAnimationFrame` 等渲染。
|
||
2. **getter 会被展开求值成静态值** → `currentFavId`/`currentFavUrl` 改成**方法**,模板里写 `currentFavId()`。
|
||
3. 共享初始化叫 **`galleryInit()`** 而非 `init()`(组件自己的 `init()` 会覆盖它)。
|
||
|
||
### 灯箱样式统一(用户明确要求"更契合")
|
||
以 `Item.astro` 的灯箱为基准(它本来就是编辑风),把 `StreetSnap.astro` 的旧版对齐过来。产物 HTML 核对:旧样式 `hover:scale-105` / `bg-gradient-to-b` / `rounded-[6px]` / `bg-white/75` / `✕`字符 **全部为 0**。
|
||
统一项:计数改零填充 `01 / 05` + `tracking-[0.22em]` 直角细框;缩略图激活态 `ring-1 ring-black`(去掉 `scale-[1.03]`+`ring-2`);nav/关闭按钮改直角 + `hover:bg-black hover:text-white`(去掉圆角药丸 + scale 悬停);关闭图标改 SVG(去掉 `✕` 字符);缩略图条去掉顶部渐变;文案字号/字距对齐。
|
||
**唯一一处超出"纯统一"的改动**:灯箱的单图收藏按钮由**直角改圆形**,对齐全站收藏语义(列表卡片 `.fav-toggle`、详情页 `#fav-fab` 都是 `rounded-full`)。用户若不认同一行可回退。
|
||
|
||
### 验证手段(新增,值得复用)
|
||
`item/[id]` 是 `prerender=false`,**dist 里没有 HTML**,没法用产物对比。改法:**临时建一个 prerender 的验证页**(`src/pages/zzverify.astro`)直接渲染目标组件、喂假数据 → `npm run build` → 用 PowerShell 正则统计产物 HTML 里的关键类名/绑定次数 → 验完删页重建。
|
||
本次核对结果:`id="lb-thumbs"`×2、`id="lb-details"`×1(仅走秀)、`currentFavId()`×4、走秀缩略图 `aspect-[2/3]`×6 / 街拍 `aspect-[3/4]`×6(6 = 5 张 SSR + 1 个 x-for 模板)。
|
||
- 最终验证:`npm run build` exit 0;`npx astro check` **0 errors / 0 warnings / 3 hints**(与基线一致,hints 是既有 `is:inline` 提示)。
|
||
- 坑:`astro check` 输出会被 PowerShell 吞;用 `Out-File -Encoding utf8` 落盘再用文件工具读;`Get-Content` 必须带 `-Encoding utf8`(否则被安全策略拦)。
|
||
|
||
## 用户「全都帮我改下吧」→ 已落地的优化(13:xx–14:xx)
|
||
全部通过 `npm run build`(exit 0)+ `npx astro check`(exit 0)+ 产物 HTML 对比验证。
|
||
|
||
### P0
|
||
- **http.ts 生产关闭调试日志**:`ENABLED` 去掉 `import.meta.env.SSR` 分支(该值在构建出的 Node 端恒 true → 生产每请求都 clone+读 body,且 `writeLog` 往容器 `logs/api-debug.log` 无限追加)。现仅 `DEV || DEBUG_API==="true"`。
|
||
- 清理死配置/过时注释 5 处:`astro.config.mjs` 空 `build{}` 块 + 第29行「英文在根路径」错误注释(实际 /en);`routes.ts:18-20` 过时注释;`Item.astro` 的 `// TODO 这是干啥的`;`pages/index.astro:3`「应该跳转到 /en」未实现。
|
||
- **抽 `ssrAbs()` 到 request.ts**,删掉 Item.astro/StreetSnap.astro 各自本地 `full()`(逐字相同)。
|
||
- `package.json`:`name: "test"` → `frontend-v2`,加 `private: true` + `"check": "astro check"`;devDeps 加 `@astrojs/check`/`typescript`/`@types/node`。
|
||
- `.gitignore` 补 `_tmp*`、`*.err`。
|
||
- 删遗留文件:`_tmp_verify_hist.mjs`、`_tmp_verify2.mjs`、`dev.log`、`server.err`、`footertail.txt`。**`README.md`(Astro 模板残留)未动**;`_tmp_backfill.log` 非本次创建,保留(已被 ignore)。
|
||
|
||
### ⭐ 类型检查从 0 到有:60 errors → 0
|
||
- **根因**:`@types/alpinejs` 的 `data<T extends { [key in keyof T]: T[key] }, A extends unknown[]>(name, cb: (...a:A)=>AlpineComponent<T>)` 自引用约束使 `T` 推断失败 → 属性退化成 `unknown`、方法内 `this` 退化成 `{}`。`Alpine.store` 则是 `Stores` 为 `[key: string|symbol]: unknown`,内联字面量失去上下文类型。
|
||
- **修法**:① `Alpine.data` 的属性用 `as` 显式标注(如 `hotBrands as {...}[]`、`favIds: [] as string[]`、`user: null as AuthUser|null`);② `Alpine.store("auth", …)` 改为先 `const authStore: AuthStore = {...}` 再注册(AuthModal 一处修掉 16 个错);③ swiper 样式声明要放**非模块** d.ts(`src/types/swiper-css.d.ts`,env.d.ts 有 `export {}` 是模块,简写 ambient 声明不生效);④ `e.target` 用 `(e.target as Element|null)?.closest()`;⑤ `var el` → 收窄后另存 `const screen = el` 供闭包用(闭包内不保留收窄)。
|
||
- 剩余 3 hints(`is:inline` 提示、`env.d.ts` 默认导入建议)属噪音,未处理。
|
||
|
||
### P1
|
||
- **新建 `src/lib/ssr.ts`**:把 `getSsrArticle`/`getSsrStreetSnap` + `SsrArticleResult`/`SsrStreetSnapResult` 从 api.ts 迁出,与 ssg.ts 对称(ssg=构建期 / ssr=运行期按需,均服务端可信内部取数、不经签名)。api.ts 去掉 `ssrBase`/`withLocale` 导入。
|
||
- **`login/logout/fetchMe/enforceIdleLogout` 有意留在 api.ts**:它们要经 `request()`,而 request.ts 依赖 auth.ts 的令牌读取;搬进 auth.ts 会形成 `auth ↔ request` 循环依赖(**这就是 09-13 那次拆分没落地的真正原因**)。已在 api.ts 头注释写明。
|
||
- **cn/en 页面去重**:新建 `src/components/pages/{LoginPage,AccountPage,ItemPage}.astro`(用 `Copy-Item` 复制而非手抄,避免 600+ 行转写误差),`src/pages/{en,cn}/{login,account,item/[id]}.astro` 变 4 行薄包装。`ItemPage` 收 `id` prop —— **`export const prerender = false` 必须留在页面文件**(Astro 只读页面模块的该导出)。`pages/{en,cn}/{index,runway-looks,street-snaps}` 本来就是 5 行包装,未动(去重后反而更长)。
|
||
- **account.astro 分页去重**:`favPageList`/`histPageList` 同一段逻辑写两遍 → 抽 `buildWindowPages(cur,last)` 到 `looks-grid.ts`(与 `buildPageList` 形态不同:前者输出 `{label,page}`,后者输出 `number|"..."`,已在注释里写明勿混用)。
|
||
|
||
### P2
|
||
- **IntersectionObserver 泄漏**:`initThumbLazyLoading()` 在 `loadAuthedState()` 后会被再调一次,每次 `new` 一个 observer 且旧的没断 → Item/StreetSnap 各加模块级 `let thumbObserver: IntersectionObserver|null`,重建前 `thumbObserver?.disconnect()`。用模块级单例(非 Alpine data 属性)以免把 observer 塞进响应式代理。
|
||
|
||
### ⚠️ 纠正审计报告的两处错误结论(子代理给的,勿照做)
|
||
1. **dictionary 的「死 key」判断是错的**:`t('Home')`/`t('Previous')`/`t('No article ID specified')` 大小写与 dict 全小写不一致,但 `translate()` 走 `key.toLowerCase()` 查表,**全部能命中,不是死 key**。
|
||
2. **`toAbsUrl()` 不是「空包装、可直接删」**:模板里有 `:src="toAbsUrl(it.cover)"`(RunwayLooks:219/230),而 **Alpine 表达式访问不到模块 import 符号**(见 MEMORY 铁律 #4),所以 `toAbsUrl` 是必须存在的 data 方法,删了图片就加载不出来。
|
||
|
||
### 未做(下次可继续)
|
||
- **P1-1 详情页灯箱工厂**(Item.astro 1161 行 vs StreetSnap.astro 645 行,约 300 行逐行重复 → 抽 `createGalleryView()`):**最大重复源,但风险最高**。灯箱是交互核心,且 `item/[id]` 是 `prerender=false`(dist 无 HTML)→ **无法用产物 HTML 对比验证**,只能靠真跑浏览器。建议单独一轮做。
|
||
- **P1-3 详情页重复 HTML 片段**(SSR 首屏图与 extraImages 两份 figure 结构):同上,会动模板结构。
|
||
- **P0-4 列表页逻辑下沉**(pagination/fav mixin):净减行数只有约 35 行(mixin 机制本身要 ~75 行),且会让 UI 工具层 import 数据层;另有 Alpine `this` 推断的不确定性。收益/风险比不划算,暂缓。
|
||
- **P2-5 补 any 类型**(account.astro 的 `user: null as any`、`favorites: [] as any[]` 等)未动。
|
||
- `favorites.ts`(8.3KB)、`history.ts`(6.5KB)、`look-grid.css`(8.9KB) 仍未逐行审。
|