Files
frontend_v2/.workbuddy/memory/MEMORY.md
toom1996 64af75c0a9
Some checks failed
deploy / deploy (push) Has been cancelled
update
2026-09-02 21:50:30 +08:00

10 KiB
Raw Blame History

项目长期记忆(Project MEMORY.md)

模块策略

  • 站点按模块逐个完善,当前主线为「发布会模块」:首页 index + 走秀档案(RunwayLooks)+ 街拍(StreetSnaps)。
  • 品牌索引页已下线(2026-08-26):brands.astro 与孤儿组件已删;品牌仅经走秀页侧栏 filter + 品牌弹窗呈现(热门 30 走 SSG,搜索/字母走运行时 /api/v1/public/brands)。
  • 导航死链清理(2026-08-30):删 /latest-projects、/about、/contact 三处(Layout/routes/dictionary 三处同步清)。保留未动的死链:/portfolio、/login、/register(用户未要求动)。
  • 「header 别动」= 不擅自改导航结构/视觉;但用户主动批准的调整(Runway 下拉分类、Street Style 减城、删死链)均属已授权,不算违规。

架构总览

  • 前端:d:/project/frontend_v2 — Astro SSG + Tailwind v4 + Alpine.js。构建 npm run build(SSG 取数走 ssgRequest,失败回落空/假数据不影响构建)。
  • 后端:d:/project/backend_v2 — Go + Gin + GORM(MySQL)。公开引擎 :8090(/api/v1/public/*、/api/v1/auth/*);SSG 引擎 :8091(仅 127.0.0.1,/api/v1/ssg/*,SSGToken 中间件,nginx 不反代)。
  • SSG 端点仅剩:/api/v1/ssg/index/runway(首页 runway 区块)与 /api/v1/ssg/brands/hot(走秀页热门品牌,固定 30 条)。
  • 接口 i18n:后端按 ?locale=cn|en 选列(pickLocale 缺翻译回落另一语言);前端取数统一复用带 locale 原语(request/ssgRequest),绝不裸写 fetch;语言以 URL 前缀为准。
  • 走秀标题 title_cn 已规则回填(幂等);description_cn 空白属预期(中文页回落英文)。
  • 后端连接池:已设 conn_max_idle_time(config.yml 默认 60s,须 < MySQL wait_timeout)主动回收空闲连接,避免空闲连接被 MySQL 回收后池子持死连接报 invalid connection。本地后端若长时间空闲后接口报 invalid connection,先重启后端(cd d:/project/backend_v2 && go build -o server-bin ./cmd/server 后杀旧进程重启)。
  • 前端 dev server 不热加载 .env:改 BASE_API 后必须 astro dev stop + astro dev --background 重启才生效。浏览器旧 bundle 仍用旧 BASE_API,需硬刷新(Ctrl+Shift+R);旧 token 对新后端无效,需重新登录。改 BASE_API 指向本地/线上后端后,若历史/收藏/登录"没反应",先查 dev server 是否重启、浏览器是否硬刷新+重登录。

关键约定

  • 视觉语言:黑白极简编辑风(monospace 小标签 + Georgia 衬线大标题 + 细线),HBX / 时尚档案调性。纯黑白,禁用暖米色/柔和阴影;字体全走 Tailwind 体系,禁止自写字体栈。
  • 品牌名前端一律只显示英文(display = nameEn);走秀标题/描述仍按 locale 选列。
  • API 命名哲学(用户原则):「一个功能一个接口」,查询参数仅限同资源列表筛选/分页;跨资源/独立功能一律独立路径(如 brands/hot 而非 ?featured=1)。
  • CSS:能 @apply 就 @apply;<style> 顶部 @reference "tailwindcss";。Tailwind v4 无零值类。
  • Layout 把 <slot/> 包在 flex-1 flex flex-col;页内根容器禁用 m-auto(cross-axis margin:auto 触发 shrink-to-fit → 抖动),用 w-full + max-w-[x] mx-auto。

Astro <script> 坑

  • 作用域隔离:frontmatter 的 import/变量在打包型 <script> 拿不到,需脚本顶部重新 import。
  • 不做模板插值:打包型 <script> 内 {t('...')} 不执行 → t is not defined;预翻译用 define:vars 或 data 属性注入,模板区 {t()} 正常。
  • define:vars + import = 致命组合(已踩坑):define:vars 会把脚本包成内联 IIFE 并注入 const meta=...,而原本脚本顶部的 import 语句被塞进函数体内 → import 在 IIFE/非模块上下文非法 → 整段脚本 SyntaxError 完全不执行(连带 recordHistory/事件绑定全失效)。凡脚本里要 import,绝不能用 define:vars。改用 data-* 属性在模板里透传变量,脚本内 btn.dataset.xxx 读取(Layout.astro 已用此模式)。en/cn item/[id].astro 曾因此导致详情页浏览历史/收藏全不工作,已修复。
  • 片段页(无 <html>/<head> 的 .astro)的 import '@/styles/global.css' 在 build 时不会被注入 head → 产物缺 <link rel=stylesheet> 整页无样式。login.astro 曾因此丢样式,已补完整文档结构修复。

数据分类

  • 走秀 brand_runway:collection_type(rtw/menswear/couture/resort/pre_fall) + season(spring/fall/null) + season_code(SS26…),title 规则回填(25,206 行,幂等)。公开列表 ?collection_type=&season=&season_code=&year= 筛选。地理维度暂不做。
  • 街拍 street_snap.city(2026-08-30 落地):DB 列 VARCHAR(128) + 精确匹配 WHERE city = ?;前端 header 下拉 ?city= + 侧栏 City pills + 深链选中。规范值存「首字母大写英文」(Paris/New York/Copenhagen…),前后端大小写敏感须一致;dev 库仅 12 行样本(Paris 8 / Copenhagen 4),其余城市本地为空属预期。

前端复用

  • 详情页统一 /item:src/pages/{locale}/item/[id].astro,服务端 getSsrArticle→getSsrStreetSnap 回落;ROUTES.article/streetSnap 均指向 /item/${id}。列表回链指向 /runway-looks、/street-snaps。
  • 列表组件同构,抽 src/styles/look-grid.css + src/lib/looks-grid.ts(SPIN_SVG / makeCardSlots / buildPageList);品牌筛选弹窗 BrandModal.astro(仅 RunwayLooks)。卡片标记在 Alpine x-for 内客户端渲染,不能提成 Astro 组件。

账号体系(2026-08-31)

  • 仅预置内部账号,不开放注册(前端无注册入口)。seed_users/main.go 创建两个账号:admin(密码取 SEED_ADMIN_PASSWORD,缺省 Studio#2026!Admin)+ root/root(测试弱密码,uid 通常为 8)。本地库 db_dev 确有 root/root;实测 POST /api/v1/auth/login {account:"root",password:"root"} 打到 http://localhost:8090 返回 200,GET /auth/history 显示 root 账号下已有 street-snaps/runway-looks 两条真实图集历史。不要再说"本地无 root / root 登录必失败"——那是误判(2026-09-02 因一次 401 错判,已纠正)。前端 .env 当前 BASE_API=http://localhost:8090。
  • 双令牌 JWT:access 默认 7 天(168h)(无状态,前端请求用)+ refresh 默认 30d(720h)(随机串仅存 SHA256 哈希落库 refresh_tokens)。登录即吊销该用户全部既有 refresh(互斥登录)。
    • 接口(/api/v1/auth/,除 /me 外公开):POST /login→{access_token,refresh_token,expires_in,user};POST /refresh→{access_token,expires_in}(refresh 复用不轮换);POST /logout(单设备);POST /logout-all→{revoked:N}(踢下线);GET /me(Bearer)。
    • 前端 api.ts:saveSession/clearSession/refreshSession/fetchMe(authedFetch 遇 401 静默 refresh 一次)/logout(吊销当前 refresh 再清本地)/logoutAll。
    • 未做 token_version 即时吊销(access 在 7 天窗口内仍有效),如需即时失效再加。
  • 硬门禁:RunwayLooks/StreetSnaps 筛选与翻页入口调 requireLogin(),未登录弹内嵌登录表单,登录成功 loadPage(1) 刷新;首屏 init() 的 loadPage(1) 不拦。
  • 个人中心 account.astro:报头(方块字母头像+用户名大标题) + 两 tab(Saved looks / My History),已移除 Overview/Sessions & Security/Settings/退出入口/偏好设置/编辑资料(2026-09-01 精简)。
    • 收藏已服务端化(2026-09-02):后端表 favorites + /api/v1/auth/favorites(GET 分页 ?page&per_page / POST 新增幂等 / DELETE /:target_uid / POST /check 批量校验当前页收藏态),按 user_id 隔离,target_uid 存前端编码串(runway r= / street s=)。前端 src/lib/favorites.ts:账户页用 fetchFavoritesPage() 服务端分页;列表卡片用 checkFavorited(ids) 仅把当前页可见 id 发给后端打勾(与收藏总量解耦,10 万级也常数级);收藏/取消本地乐观 + 后台同步(失败不回滚本地)。favorites 表含复合索引 idx_user_created(user_id,created_at) 支撑深翻页。
    • 浏览历史(2026-09-02 晚收敛为「文章级」,用户澄清纠正):语义=「点开过哪篇文章」,记录时机在 item 详情页(recordHistory(meta.id),meta.id 是文章对外编码串 r=走秀 / s=街拍,编码串自带类型、无需 kind);RunwayLooks/StreetSnaps 列表页进页不再记(此前按"图集=列表页"实现是理解错,已纠正)。后端 histories 表收敛为 (user_id, target_uid, viewed_at):kind/target_type/title/cover/brand 五列已 DROP(008 建表脚本同步改 + migrate_refresh 对存量库 ALTER,实测 GET items 仅 {id,viewed_at});唯一键 uniq(user_id,target_uid);保留 HISTORY_CAP=2000 FIFO(文章会积累,有存在意义)。接口 /api/v1/auth/history:GET 分页 / POST {target_uid} upsert / DELETE /:target_uid / DELETE 清空,按 user_id 隔离;kind 参数与校验已随收敛移除(kind=all 字面过滤坑随之消失)。账户页 My History:服务端分页拿 id 列表后逐篇回查公开详情(api.ts 的 getHistoryMeta(id) → /public/runway-looks|street-snaps/:id 取 title/cover/brand;用户选"只存 id、展示回查"),卡片跳回文章详情,无 kind 筛选 tab。前端 src/lib/history.ts:recordHistory(id) / fetchHistoryPage(page,perPage)(服务端分页+回查,单条回查失败容忍回落本地)。
    • 待办服务端化:users 加 display_name/bio、refresh_tokens 列表接口。
  • 视觉:登录/个人中心沿用站点黑白极简编辑风(monospace 小标签 + 细线下划线输入框 + Georgia 衬线大标题 + hover:opacity-60)。

部署

  • Docker + Gitea Actions:前端 Dockerfile/nginx.conf/docker-compose.yml/deploy.sh/.gitea/workflows/deploy.yml/.env.example/DEPLOY.md + 后端 Dockerfile。nginx 拦截 /api/v1/ssg/ 404;健康检查 /api/v1/public/brands。