本文件记录由 Claude Opus 4.6 完成的所有代码修改及整改建议。
tsc --noEmit和npm run build均已通过验证。
原始加载动画使用 CSS box-shadow 像素画技术,通过两帧 <i> 元素交替显示来模拟跑步,视觉上较为粗糙。用户要求参考 GitHub 风格的加载动画,将其升级为流畅、可爱的矢量动画。
| 文件 | 改动类型 | 说明 |
|---|---|---|
components/CuteLoadingScreen.tsx |
重写 | 像素 div 结构替换为内联 SVG 矢量角色 |
index.css (行 1199-1330) |
替换 | 所有 .pixel-runner-* 样式替换为 .runner-* SVG 动画样式 |
SVG viewBox="0 0 200 110"
├── 地面线 (line y=100, 半透明 #8a9a5b)
├── 滚动地面标记 (.runner-ground-marks)
│ └── 5 个 rect,间距 50px,宽度略有变化增加自然感
├── 角色阴影 (.runner-shadow, ellipse)
├── 灰尘粒子 × 3 (.runner-dust-a/b/c)
└── 角色弹跳组 (.runner-bounce)
├── 后方手臂 (.runner-arm-b, 深色 #6b7a3c)
├── 后方腿部 (.runner-leg-b, 深色 #6b7a3c)
├── 身体 (ellipse cx=100 cy=68, 品牌色 #8a9a5b)
├── 前方腿部 (.runner-leg-f, 品牌色 #8a9a5b)
├── 前方手臂 (.runner-arm-f, 品牌色 #8a9a5b)
├── 围巾领口 (rect #e8837c)
├── 围巾飘尾 (.runner-scarf-tail, path #e8837c)
├── 头部 (circle r=14, 肤色 #fae8d4)
├── 头发 (path, 深绿 #7a8a4b)
├── 眼睛组 (.runner-blink)
│ ├── 左眼 + 高光
│ └── 右眼 + 高光
├── 腮红 × 2 (ellipse, #e8837c 30% 透明)
└── 微笑 (path, #3d3d3d)| 动画名 | 目标元素 | 时长 | 效果 |
|---|---|---|---|
runner-bounce |
.runner-bounce |
0.45s infinite | 角色整体上下弹跳 3px |
runner-ground-scroll |
.runner-ground-marks |
0.8s linear infinite | 地面标记向左滚动 50px(无缝循环) |
runner-shadow-pulse |
.runner-shadow |
0.45s infinite | 阴影随弹跳缩放 + 透明度变化 |
runner-leg-f |
.runner-leg-f |
0.45s alternate | 前腿摆动 -28° → +32°(从臀部 103,78 为轴心) |
runner-leg-b |
.runner-leg-b |
0.45s alternate | 后腿摆动 +32° → -28°(反相,轴心 97,78) |
runner-arm-f |
.runner-arm-f |
0.45s alternate | 前臂摆动 +28° → -32°(与后腿同相,轴心 103,58) |
runner-arm-b |
.runner-arm-b |
0.45s alternate | 后臂摆动 -32° → +28°(与前腿同相,轴心 97,58) |
runner-scarf-wave |
.runner-scarf-tail |
0.6s alternate | 围巾尾巴旋转 -6° + 平移 -2px |
runner-blink |
.runner-blink |
3s step-end infinite | 每 3 秒眨眼一次(scaleY 0.1) |
runner-dust-1 |
.runner-dust-a |
0.9s infinite | 灰尘粒子从脚后飘出(无延迟) |
runner-dust-2 |
.runner-dust-b |
0.9s infinite (delay 0.3s) | 灰尘粒子(延迟 0.3s) |
runner-dust-3 |
.runner-dust-c |
0.9s infinite (delay 0.6s) | 灰尘粒子(延迟 0.6s) |
四肢使用 CSS translate + rotate 组合动画 实现:
- 肢体在 SVG 中画在原点
(0,0),无自身坐标 - CSS
@keyframes中同时设置translate(pivotX, pivotY)定位到关节点 +rotate(angle)绕关节旋转 alternate模式实现往复摆动,无需 JS 干预
对侧协调规则(自然跑步姿态):
- 前腿 + 后臂:同相位(同时向前/向后)
- 后腿 + 前臂:同相位(与上组反相)
弹跳频率 = 肢体摆动频率的 2 倍(每步着地弹一次)。
| 部位 | 颜色 | 说明 |
|---|---|---|
| 身体/前肢 | #8a9a5b |
APP 品牌主色(鼠尾草绿) |
| 后方肢体 | #6b7a3c |
深一色阶,制造前后层次 |
| 头发 | #7a8a4b |
略深绿 |
| 皮肤 | #fae8d4 |
暖色肤色 |
| 围巾 | #e8837c |
珊瑚色暖色系点缀 |
| 腮红 | #e8837c @ 30% |
同围巾色,低透明度 |
| 五官 | #3d3d3d |
深灰(明暗模式通用) |
| 地面/灰尘 | #8a9a5b @ 12-20% |
品牌色低透明度 |
以下样式未改动,维持加载卡片的整体外观一致:
.cute-loading-card:卡片容器(圆角、边框、阴影、居中布局).cute-loading-text:底部提示文字样式
- 文件:
components/Layout.tsx+index.html - 改动: 删除
<div className="fixed inset-0 pointer-events-none opacity-[0.02] z-0 paper-texture">及index.html中的.paper-textureCSS 类 - 原因:
opacity: 0.02人眼几乎不可见,但消耗 GPU 合成层并请求外部 unsplash 纹理图片 - 影响: 无视觉差异,减少一次外部网络请求 + 减少 GPU 合成
- 文件:
App.tsx - 改动:
setTimeout从180ms→80ms - 原因: 180ms 延迟导致页面切换有明显"卡顿感",80ms 足够触发 CSS 退场动画
- 影响: 页面切换更流畅
- 文件:
pages/Landing.tsx - 改动:
FloatingHearts组件中的hearts数组从每次渲染重新生成 →useMemo(() => ..., []) - 原因: 数组包含随机坐标/大小/延迟值,每次渲染重新随机会导致心形位置跳变
- 影响: 心形位置稳定,避免不必要的重复计算
- 文件:
context.tsx - 改动:
addMemory在有云端会话时先用createLocalMemory立即插入一条临时条目到 UI,后端 API 成功后替换为服务器返回的真实条目;失败时保留本地条目作为兜底 - 原因: 原实现需等待后端响应才能看到新添加的回忆,网络慢时体验差
- 影响: 用户操作后立即看到新条目,感知延迟大幅降低
- 文件:
pages/Anniversary.tsx - 问题: 原始 F14 优化将添加按钮从
absolute改为fixed,导致按钮定位参考变为浏览器视口,在max-w-md容器下按钮跑到屏幕右下角而非 APP 容器右下角 - 修复: 恢复为
absolute right-4 bottom-24 z-20,定位在父容器relative内 - 教训: 在有
max-w-md布局约束的 APP 中,FAB 按钮应使用absolute+ 父容器relative,而非fixed
以下为审查中发现的后端优化项,前端已全部就绪,后端可按需逐步改进。
- 文件:
backend/src/routes/appRoutes.js - 现状:
GET /app/state每次返回全部 memories + events,数据量大时响应慢 - 建议改法:
- memories 改为分页返回,接受
?page=1&limit=50参数 - 首次只返回缩略图 URL(如果用了 Supabase Storage,可生成 thumbnail URL)
- 详情/原图按点击时按需拉取
- memories 改为分页返回,接受
- 前端适配: 前端
context.tsx中syncFromCloud已改为分页拉取并追加(CLOUD_SYNC_PAGE_SIZE = 50,循环调用/memories?page=N&limit=50)
- 文件:
backend/src/routes/appRoutes.js - 现状:
addMemoriesBatch逐条插入,中途失败会留下部分数据 - 建议改法:
- 改用 Supabase 的
.insert(items)一次性批量写入(Supabase JS SDK 支持数组) - 失败时整体回滚,返回错误给前端
- 改用 Supabase 的
- 文件:
backend/src/imageStorage.js+backend/src/index.js - 现状: 无后端尺寸限制,客户端可上传任意大小图片
- 建议改法:
- Express 中间件设置
bodyParser.json({ limit: '10mb' })或更合理的上限 - 可选:引入
sharp库在后端二次压缩(如限制最大边长 2048px)
- Express 中间件设置
- 文件:
backend/src/routes/appRoutes.js - 现状: 后端已计算
yearStats并返回,前端context.tsx已适配—优先使用后端返回的yearStats,仅在后端未返回时本地降级计算 - 建议改法:
- 确保后端
GET /app/state始终返回yearStats字段 - 前端
Timeline.tsx中的年份统计可直接使用context中的yearStats,减少 O(n) 遍历
- 确保后端
- 文件:
backend/src/index.js - 现状: 无请求速率限制,可被恶意刷接口
- 建议改法:
npm install express-rate-limit- 全局限流:如 100 次/分钟
- 登录/注册/重置密码等敏感接口:如 5 次/分钟
const rateLimit = require('express-rate-limit'); app.use('/api/auth', rateLimit({ windowMs: 60000, max: 5 })); app.use('/api', rateLimit({ windowMs: 60000, max: 100 }));
- 文件:
backend/src/routes/authRoutes.js - 现状: 密码重置验证码无尝试次数限制,可被暴力枚举
- 建议改法:
- 数据库层面:验证码记录增加
attempts字段,超过 5 次自动失效 - 接口层面:同一邮箱 5 分钟内只允许请求 1 次验证码
- 可结合 B05 的
express-rate-limit实现
- 数据库层面:验证码记录增加
tsc --noEmit ✅ 通过
npm run build ✅ 通过 (vite v6.4.1, 4.33s)输出:
dist/index.html 2.77 kB │ gzip: 1.19 kB
dist/assets/index-DlnUsBbg.css 21.83 kB │ gzip: 5.38 kB
dist/assets/index-DUGIXGNJ.js 388.54 kB │ gzip: 111.96 kB| 文件 | 改动类型 |
|---|---|
components/CuteLoadingScreen.tsx |
重写(像素 div → SVG 矢量角色) |
index.css |
替换 pixel-runner CSS → runner SVG 动画(行 1199-1330) |
components/Layout.tsx |
删除 paper-texture 叠加层 |
index.html |
删除 .paper-texture CSS 类 |
App.tsx |
页面转场 setTimeout 180ms → 80ms |
pages/Landing.tsx |
hearts 数组 useMemo 包裹 |
context.tsx |
addMemory 乐观更新(用户后续又独立增加了分页/yearStats) |
pages/Anniversary.tsx |
修复 F14 按钮定位(fixed → 恢复 absolute) |