docs(audit): UI 还原 + 性能审计报告;修登录假动画延迟

- 新增 UI 还原对比报告 / 性能审计报告(2026-06-19,14 块页面 + 6 块性能,逐项 file:line)
- auth-screen: 删除登录成功后 ~1.5s 人为进度动画延迟,凭证验过即进

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-19 02:07:44 +08:00
co-authored by Claude Opus 4.8
parent 5cfee715a2
commit 856a93b8a9
3 changed files with 475 additions and 18 deletions
+313
View File
@@ -0,0 +1,313 @@
# AirShelf · UI 还原度对比报告
> **对比对象:** V1 设计稿(`电商AI平台/` 纯 HTML+CSS,UI 完全调好的金标准) ↔ 张业昌 React 实现(`core/frontend/src/`)
> **目的:** 不同技术栈重写后,逐页找出 React 没有还原到位 / 做错 / 缺失的地方
> **日期:** 2026-06-19
> **方法:** 13 路并行审计,每页两边源码完整读一遍(含 inline `<style>`、inline 弹窗 markup),以 V1 HTML 为首要参照、`design.md` 为规范准则
> **严重度:** P0 = 整块功能/区块缺失或完全不还原 · P1 = 一眼可见的视觉/布局/字段偏差 · P2 = 细节(间距/字号/mono/准星/状态)
---
## 一、总览评分表
| 页面 | 还原度 | 一句话结论 |
| ---- | :----: | ---- |
| 生产管线 pipeline | **8.5** | 五阶段结构/编辑器忠实且功能更强,只差 3 处 mono 信息密度 + 额度预检弹窗评估 |
| 消息中心 messages | **8.5** | CSS 几乎逐行照搬,缺「查看日志」整链 + 详情多动作按钮收敛 |
| 设置 settings | **8.5** | 视觉高度还原,缺「保存所有变更」dirty-state 引擎(顶栏保存按钮形同虚设) |
| 全局外壳 shell | **8.5** | 侧栏/搜索/命令面板/顶栏照搬,缺**账户下拉菜单整块** + 命令面板底栏 |
| 项目+新建向导 projects | **7.0** | 列表骨架到位,网格卡缺进度条、失败 pill 失色、**批量管理栏整块缺** |
| 全局浮层 overlays | **6.5** | CSS 对,但**弹窗进场动画/ESC 集体失效** + **Toast 体系被换** |
| 工作台 dashboard | **6.5** | KPI 还原,但右栏三块吃了**废弃暖米主题**(错橙 #E55B26)+ KPI 假数据 |
| 商品库+详情 products | **6.5** | 卡片主体忠实,缺视图切换/多选分类/分页器,详情砍掉「任务记录」Tab |
| 商品创建 drawer | **6.5** | 字段/卖点对,但**多图上传砍成单图**、拖拽缺失、必填校验被吞 |
| 账户 account | **6.0** | banner/充值卡好,但**三大表格砍成裸表 + 充值扫码弹窗整个没做** |
| AI 图片工具 ai-tools | **5.5** | 图片创作骨架在,但**模特库/商品库选择弹窗、gen-card 三接口大面积砍掉** |
| 团队 team | **5.0** | 骨架在,**5 个定制弹窗全降级成通用裸输入框** + 凭据分享缺失 |
| 资产库 library | **4.5** | **批量栏 + 资产详情弹窗 + 上传动态字段三大件全换成简化件** |
| **登录/注册 auth** | **4.0** | **整张注册页 0 实现** + 第三方登录/切换链全删 + 邮箱字段错改用户名 |
---
## 二、系统性问题(跨页面 · 改一处惠及多页 · 最高优先级)
> 这些不是单页 bug,是贯穿全站的根因。优先修这几条,大半页面的「观感不对」会同时被拉回。
### S1 · 双 CSS 主题打架 — 旧暖米调色板污染(根因级)
`main.tsx` 先引 `styles.css`(旧「流·Studio」**暖米主题**:`--orange #E55B26``--bg-soft #F4F2EC``--ink-3 #9C988C``JetBrains Mono`),再引 `design-restraint.css`(**冷灰 restraint 主题**:`--heat #fa5d19`)。但 design-restraint.css **不重定义** `--orange / --bg-soft / --ink* / --card / --border` 等旧 token。
**后果:** 凡是只在 styles.css 里定义、没被 restraint.css 覆盖的类,全部吃到**错误的暖米色 + 错橙**。已确认受害:工作台 `.shortcut/.recent-row/.tip`、登录页品牌区、`.queue-chip`、Toast。
**修复:** 把这些残留类迁入 design-restraint.css 并改用 `--heat / --black-alpha-* / --font-mono`;或在 restraint 主题里把旧 token 重映射到新值。
### S2 · Mono 装饰字号被全局抬到 12px(几乎每一页)
V1 的 mono 装饰是**分级精细**的(10.5 / 11 / 11.5px),这是「图纸感/调试视图感」品牌签名的关键。React 几乎把所有 mono 小字统一抬到 **12px**
**后果:** 全站 mono 装饰偏粗一档、层级感被抹平,「克制」气质下降。已确认:products / projects / account / team / settings / auth / ai-tools 全中。
**修复:** 恢复 V1 的分级 mono 字号(可抽一组 `.mono-10 / .mono-11 / .mono-12` 工具类统一管理)。
### S3 · Toast 体系被替换 + 就地反馈丢失
V1 主反馈是 `.toast`(右下、橙 icon-box、`--shadow-floating`、300ms 弹性进入、2400ms 自动消失)。React 用 legacy `inline-toast`(**右上**、灰重阴影、直角无圆角、走暖米 token)顶替,且 `.toast` 整套在 React 里**零引用(死代码)**。
**后果:** ① 位置/动画/配色全偏且违反「单橙锚点」(toast 随成功/失败变绿/红边);② V1 大量就地轻反馈(已删除/已上传/已达上限/已采用…)被丢失或被升级成模态弹窗。§4.18 明确禁止「用全局 toast 通知已采用」,但 ai-tools 正是这么做的。
**修复:** 弃用 inline-toast,改渲染已存在的 `.toast`;gen-card 采用反馈改回就地 `.gen-image-feedback`
### S4 · 弹窗/抽屉进场动画 + ESC 集体失效
`overlays.tsx``TeamModal / ConfirmModal / SuccessModal / Drawer` 全部 `if(!open) return null` 后直接渲染带 `.show` 的节点 → CSS 过渡两帧无变化,**进入动画完全不触发,瞬现瞬隐**;且除 Lightbox 外**全部不支持 ESC 关闭**。(对照:`ProductCreateDrawer` 常驻挂载 + 切 `.show`,是唯一会动的。)
**修复:** 改成常驻挂载用 `open``.show`(或 mount 后下一帧加 `.show`);4 个组件补 `keydown` Escape→close。
### S5 · 定制弹窗被降级成通用弹窗(功能塌缩)
V1 多个页面有**专属定制弹窗**,React 大量塞进通用 `TeamModal/ConfirmModal` 的裸输入框,丢了结构与交互:
- **团队:** 创建账户 / 编辑成员 / 重置密码 / 月限额 / 全部动态 —— 5 个定制 modal 全降级;角色选择 `.role-choice` 双卡 pill → `<select>`;**账户创建成功的「分享凭据」弹窗整个缺失**。
- **资产库:** 上传 Modal(类型选择 + 按类型动态字段 + 拖拽预览)→ 裸 Drawer 只剩「文件 + 名称」;**资产详情弹窗(立绘+三视图+版本历史+属性)整套缺失**。
- **账户:** **充值扫码 modal 完全没做**(点支付直接提交)。
- **AI 图片工具:** **模特库全屏弹窗、商品库选择器弹窗(`pl-card`)、编辑商品 drawer 全缺**
### S6 · 批量多选 + 吸底操作栏(`.bulk-bar`)整体缺失
V1 的 `products / projects / library` 都有「进编辑态 → 卡片勾选圈 `.card-check` → 选中橙边 → 吸底 `.bulk-bar`(已选 N 项 / 清空 / 删除所选 / 移动到 / 完成)」整套。React 三页**全部只做了 editMode + 单卡删除**,无多选、无勾选圈、无 bulk-bar、无「移动到」。
### S7 · 分页器降级
V1 的 `.pagination` 是富控件:mono「共 N 条」+ **每页条数循环(12/24/48/96)** + 页码窗口 + **「跳至 __ 页」输入框**,吸底。React 用通用 `Pager`(无每页条数切换、无跳页输入、不吸底),且**默认每页 10,V1 是 12**。已确认:products / library。
### S8 · banner/hero 大数字字重 700→600
V1 规范里 banner 大标题/KPI 大数是 **700**(唯一例外重档)。React 多处降到 **600**,大数字偏轻。已确认:account 余额 hero、team banner、auth 品牌 h1。
### S9 · 表格 tbody 画了横向分割线(违反铁律)
design.md §4.7.1 铁律「tbody 行与行之间不画横线」。account-page.css 自加了 `tbody td { border-bottom: 1px solid var(--border-faint) }`
**修复:** 去掉 tbody 行间 border,靠 padding + hover 区分行。
---
## 三、分页面详单
### 1. 全局外壳(shell.js → app-shell.tsx) — 8.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 账户下拉菜单 `shell.js:172-188` | 点侧栏 `.user`/顶栏头像弹账户菜单(头像+店名+邮箱 / 个人设置·消息·团队·消费·退出 6 项) | 完全没做。`.user` 无 onClick;头像改成**双击退出**;`.shell-account-menu` 样式在但无组件渲染 | 新建 AccountMenu 组件接 `.user` 与头像 click |
| P1 | 命令面板底栏 `.shell-command-foot` | 底部 mono 装饰栏:`COMMAND` · `// Enter 打开 · Ctrl K 唤起` · `N 项`计数 | 没渲染;计数被挪到输入框头部 | 补底栏,计数移回底部 |
| P1 | 顶栏任务队列 chip `.queue-chip` | 右侧有 queue-chip(默认 hidden,样式齐全) | 缺(CSS 死代码) | 保留占位以备开启 |
| P2 | 命令面板 ESC 按钮 / placeholder / 图标 | `ESC` 全大写 / `搜索页面、动作、项目入口` / image·plus 图标 | `Esc` / `搜索页面、动作…` / images·clapperboard | 文案与图标对齐 V1 |
**React 自加:** 移动端汉堡键+抽屉(合理增强);头像双击退出;`.user` 走真实数据。
> 注:`route-config.ts` 的 `mainNav/supplementNav` 是**死代码**(外壳实际用 app-shell 内自带的 NAV),易误导,建议清理。
---
### 2. 全局浮层组件(restraint.css → overlays.tsx) — 6.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | `TeamModal/ConfirmModal/SuccessModal/Drawer` | Modal `scale(.96→1)` 250ms 弹性 / Drawer `translateX(100%→0)`,靠常驻+切 `.show` | 条件卸载后带 `.show` 挂载 → **进场动画完全不触发,瞬现瞬隐** | 常驻挂载用 open 切 `.show` |
| P0 | 同上 4 组件 | Modal/Drawer 支持 **ESC 关闭** | 仅 Lightbox 监听 Escape,4 个全无 | 补 keydown Escape→close |
| P0 | `inline-toast` / `ToastLike` | Toast 右下 / translateX 弹性 / `--shadow-floating` / 橙 icon-box 8px 圆角 / 2400ms | 右上 / 直线淡入 / 灰重阴影 / 直角 icon / 走 legacy token | 改用已存在的 `.toast`(见 S3) |
| P1 | `.toast`(design-restraint.css:1695) | 整套类已移植 | **零引用(死代码)**,被 inline-toast 顶替 | 启用 `.toast` 即零成本对齐 |
| P1 | `EmptyPanel` | 空状态含 `.ic-empty` 48×48 灰 icon-box | 只有 h3+p+按钮,**缺灰 icon 占位框** | 补 `.ic-empty` |
**React 自加:** `useBodyScrollLock``createPortal`、Lightbox 扩 video/audio(均合理)。
---
### 3. 工作台(index.html → dashboard.tsx) — 6.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P1 | `.shortcut .ic` 快捷入口图标盒 | 32×32 / `--heat-12` 淡橙底 / `--heat` icon / 8px / 无边框 | 30×30 / **`--orange-tint #FFF4ED` 暖米底 / `--orange #E55B26` 错橙** / 4px / 加了边框 | 迁入 restraint,用 heat token(S1) |
| P1 | KPI delta 文案 | 四格真实 delta + 余额「已用 ¥162.60/¥500」mini progress | **占位假值**(把总数当增量)+ 余额写死 38%「已冻结」 | 接真实统计 |
| P1 | `.recent-meta .sub / .shortcut .d` | mono 用 `--font-mono` | **`JetBrains Mono`(项目未声明,退化系统等宽)** | 改 `var(--font-mono)`(S2) |
| P1 | `.recent-row:hover` | hover 冷灰 `--background-lighter` | **`--bg-soft #F4F2EC` 暖米 hover** | 改 `--black-alpha-4`(S1) |
| P2 | tip 内 `[ 确认通过 ]` | `.tip .mono` 橙字+橙底高亮 chip | React 无该规则,退化纯文字 | 补 `.tip .mono` |
| P2 | 日期 mono / 最近项目镜数 | `// 05.14 · 周三` / 真实镜数 | `2026/6/19` 斜杠 / 写死「4 镜」 | 格式化对齐 + 接真实值 |
---
### 4. 商品库 + 商品详情(products/product-detail → products.tsx) — 6.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 商品库·工具栏 | 网格/列表**视图切换**(`.view-tog` 黑激活)+ 列表态横排卡 | 整块不存在,只有网格态(CSS 已移植但无 JSX) | 加视图切换+list-view |
| P0 | 商品库·分页 | 富分页(每页条数 12/24/48/96 + 跳页输入),默认 12 | 通用 Pager,无条数切换/跳页,默认 10 | 见 S7 |
| P0 | 商品详情·任务记录 Tab | 第三 Tab:4 格 KPI + 8 列任务表格(CSS 全在) | Tab 被 `hidden`,**pane 整个不渲染** | 实现或与产品确认删除 |
| P0 | 商品详情·素材卡状态 | 三态 pill(通过/不通过/归档)可循环切换 | **写死 `"pass"`**,全恒绿不可点 | 接真实 review_status |
| P1 | 商品库·分类筛选 | **多选** chip + 菜单计数 + 多选橙计数徽标 | 退化**单选**,无计数 | 改回多选 Set |
| P1 | 商品库·删除引用拦截 | 被项目引用则禁止强删 / 分组「N 个已跳过」 | 通用确认,固定文案,**无拦截** | 接软删除规则 |
| P1 | 创建主图上传 | **多图(≤5)+ 拖拽 + 缩略网格 + 删除** | **单图,无拖拽,无网格** | 见「商品创建」 |
| P1 | 商品详情·采用版本联动 | 采用后联动素材卡 pass/fail + prepend 新卡 | 只本地标记,**不联动** | 采用后同步素材卡状态 |
| P1 | 商品详情·素材区 | 视图切换 + 状态筛选 chip + 空筛选「重置」入口 | 无视图切换/状态筛选/重置入口 | 补回 |
| P2 | 商品库·素材 stat | hover 切文案 + 点击弹「模特/平台套图」生成选择 modal | 仅视频 stat 可点,无 hover/弹窗 | 还原 |
| P2 | 缺三视图徽标 | 新建商品卡缺三视图角标 + tooltip | 无 | 视需要补 |
---
### 5. 商品创建抽屉(products.html#pc-drawer → product-create-drawer.tsx) — 6.5/10
> V1 真正金标准是 `products.html` 内的 `#pc-drawer`(`product-create.html`/`-upload.html` 已是重定向 stub)。V1 仅「手填」一种模式,不存在「上传识别」向导缺失问题。
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | `pickImage` / 上传区 | 主图**多图(≤5)** + 拖拽 + `.pf-grid` 缩略网格 + 单张删除 + 上限/计数 toast | 只取 `files?.[0]` **单图**;input 无 `multiple`;`.pf-grid` 永远空 | 改 `File[]` + multiple + 缩略网格 |
| P0 | `submit` 校验 | 主图为空/卖点为空两条必填 toast + 自动收编未回车卖点 | 只校验 title;主图/卖点静默放行;**未回车卖点被丢** | 补必填校验 + 收编 bulletDraft |
| P1 | 上传区拖拽 | dragover/drop 边框变橙 | 只 click,文案写「拖拽到此处」却不可拖 | 加拖拽事件 |
| P1 | 卖点序号字重 / 输入字号 | num 700 / input 13.5px / uz-d 11.5px | num 600 / input 14px / uz-d 12px | 对齐 V1(S2) |
| P2 | 删除卖点动画 | 淡出位移 150ms | 瞬时移除 | 补过渡 |
**React 自加:** 「使用指南」改内联展开框(违反 inline style 禁令,建议挪进 css);主按钮 loading;校验红字(应抽 css 类);真实 mock 图替换线框 SVG。
---
### 6. 项目列表 + 新建向导(projects/projects-new → projects.tsx) — 7.0/10
> V1 的 `projects-new.html` 运行时已收敛为 2 步单页(step2/4/preview 是 dead code),React 的 2 步实现与 V1 实际运行态一致,**向导主体不算缺失**。
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 列表·网格卡 | 含 `.prog` 5 段进度条 + `N/5` 计数 | **网格卡完全无进度条** | 补进度条 |
| P0 | 列表+网格·失败态 | 失败用 `.pill.err`(crimson) + 进度 `.fail` 红段 | `projPillClass` 返回 `"fail"` 但**无 `.pill.fail` 定义 → 失败 pill 退化无色** | 改返回 `"err"` |
| P0 | 批量管理 | 编辑态 `.bulk-bar` + `.card-check` 勾选圈 + 选中橙边 + 批量删除 | **全缺**(只有 editMode + 单卡删) | 见 S6 |
| P1 | 列表行·镜数副标题 | `6 镜 · 0-15s` 真实时长 | **硬编码 `0-60s`** | 用真实档 |
| P1 | 列表行·删除入口 | 行末常驻 `.row-more` 气泡删除 | row-more 在,但又加了仅 editMode 的 `.row-del`(重复) | 去掉 row-del |
| P2 | 空状态 / 向导时长档文案 | `.empty-state` 放大镜+mono / 「标准短片」 | 通用 EmptyPanel / 「精准短片」 | 对齐 V1 |
**React 自加:** mock 封面图、Pager 分页、骨架屏(合理增强)。
---
### 7. 生产管线(pipeline.html → pipeline.tsx) — 8.5/10
> 用户已认可脚本编辑块功能 OK,以下偏差以视觉/信息密度为主。技术栈未伤骨架,restraint 共享类全复用。
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P1 | 阶段1·脚本助手头 | mono 模型签名 `· GPT-4o`(品牌签名) | **删掉了模型 mono 签名**(改放输入框下 picker) | 头部补回 `· {模型名}` mono |
| P1 | 阶段4·视频卡 meta | `15s · 1080×1920 · ¥0.45`(三段 mono) | 只剩 `{duration}s`,**丢分辨率+单价** | 补回三段式 meta |
| P1 | 阶段4·queue-bar 文案 | `每场 Seedance 约 15 秒`(带预估) | `生成 · {statusText}`,去掉「约 N 秒」 | 补预估秒数 |
| P2 | 阶段5·时间轴 zoom | `min=50 max=200`(可缩到 50%) | `min=100 max=300` | 统一到 50-200 |
**V1 有 React 没做(需产品决策):** 阶段2「新增人物」轻量 modal(改走演员库工作台);**额度预检 modal(`quota-bg` 四层预检)全流程缺失**;阶段5 BGM 常驻波形装饰轨(无 BGM 时整轨消失)。
**React 自加(大量真功能增强):** 模型 picker / 停止键 / 流式思考时间轴 / 字幕烧入 / TTS 配音 / 转场 / BGM 上传 / 片段拖拽重排 / 双缓冲预览 / 审核绿红盾 —— 均 V1 mock 没有。
---
### 8. 资产库(library.html → library.tsx) — 4.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 批量栏 | `card-check` 选框 + 吸底 bulk-bar(删除/移动到↑/清空/完成) + 拖卡到 Tab | **全无多选**,仅 editMode + 单卡删 | 见 S6 |
| P0 | 资产详情弹窗 | 点卡开 `.asset-modal`(立绘+三视图版本历史+AI生成三视图+简介/标签/属性+商品媒体画廊) | 点卡只开 `MediaLightbox` 纯放大,**整套详情缺失** | 还原 asset-detail modal |
| P0 | 上传入口 | 居中 Modal + 资产类型下拉 + **按类型动态字段** + 拖拽预览 + mono 脚注 | 侧拉 Drawer,只「文件+名称」两栏 | 改 Modal + 动态字段 |
| P0 | 缺三视图徽标 | 人物卡 `tri-missing-badge`「缺三视图」+ hover tooltip | 完全没有 | 补徽标+popover |
| P1 | 排序 / 分页 / 清空筛选 | 3 档(最近使用默认/最近添加/引用次数) / 富分页 / 工具栏清空按钮 | 2 档(最近添加默认/最早添加) / 简化 Pager / 无清空按钮 | 对齐(S7) |
| P1 | 卡片下载按钮 | hover `card-dl-btn` | 无 | 补 |
| P2 | 空状态 / 使用次数 | `.empty-state` 图标+mono / meta 含「用过 N 次」 | 裸一行文字 / 无使用次数 | 补回 |
> V1 library 本身无视图切换(grid/list),React 缺它不算偏离。
---
### 9. AI 图片工具(asset-factory/image-optimize/model-photo/platform-cover → ai-tools.tsx) — 5.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 模特上身图 | 全屏**模特库弹窗**(来源/性别/年龄 chip + 12 卡 + 添加模特画布) | 完全没做(`actor-library.tsx` 存在却未被引入);「全部模特→」只内联展开 | 接入模特库弹窗 |
| P0 | 模特/平台 | 全屏**商品库选择器弹窗**(`pl-card` 多选 + hover 编辑删 + 右上勾 + 已选汇总) | 无 `pl-card`、无弹窗,只在最左列 slice 12 个 | 实现 `pl-modal` |
| P0 | 模特/平台·gen-card | 单图**再生成**按钮 + 中央**就地反馈**遮罩 + 左上**已采用绿角标**(§4.18 三件套) | 只有下载+更多;采用用**全局 SuccessModal**(违反 §4.18) | 补三件套,采用改就地反馈 |
| P0 | 平台套图 | 平台**多选**(出多平台分组结果) | 改成**单选**(功能性退化) | 恢复多选+分组 section |
| P1 | 编辑商品 drawer / 比例默认 | 有编辑 drawer;模特默认 **3:4** | 无编辑 drawer;模特默认 `1:1` | 补 drawer;默认改 3:4 |
| P1 | 顶部 toolbar / 折叠侧栏 | 折叠+搜索+三筛选 chip + 回到底部 + 会话归档 | 整条 toolbar 缺失;无 fold/回到底部/归档 | 补回 |
| P1 | 图片创作输入栏 | 模型胶囊 + 7 风格(含 cyber) + 多张参考图(≤3,64×64) | 无模型胶囊 / 6 风格(缺 cyber) / 单参考图 | 补齐 |
**React 自加:** 左栏塞了提示词 textarea(V1 无);采用用全局弹窗(应禁);平台/模特改单选(退化)。
---
### 10. 消息中心(messages.html → messages.tsx) — 8.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P1 | 详情底栏动作 | 每条消息带 `actions[]`(进入项目/查看成片/重跑/调整权限…首个 primary)+ 固定「归档」「静音同类」 | 只剩 归档 + 标已读 + 单个「进入X」;**静音同类 + 多动作丢失** | 后端下发 actions[] 或前端按 type 推断 |
| P1 | 查看日志 | err/ok 消息带 `log`,详情「查看日志」展开黑底 mono `.msg-log` | **完全没接**(`.msg-log` 死样式) | 加 log 字段 + 展开块 |
| P2 | 关联资源行 / 面板高度 | `<a>{project}→</a>`,无则「无」 / `min-height:640` | 写死目标页 label / `calc(100vh-280px)` | 用项目名;高度确认 |
**React 自加:** 真分页/滚动加载、搜索去抖、「刚刚」时间(均合理)。
---
### 11. 设置页(settings.html → settings.tsx) — 8.5/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 顶栏「保存所有变更」 | **全局 dirty-state 引擎**:无改动 disabled,改动后激活+「· N 项」计数,保存 toast「N 项/M 分区」,离开 beforeunload 提醒 | 取消/保存只管个人信息;偏好/通知/安全是即时单字段 PUT,**与顶栏按钮脱钩**;按钮永远 enabled、无计数、语义骗人 | 实现 dirty 引擎,或明确改即时保存模型并降级顶栏按钮 |
| P1 | nav badge / dirty-dot | badge 动态联动(通知 4/4→3/4、安全 N 设备);改过的区 nav 亮橙点 | **写死字符串**,切开关不变;橙点死代码 | 由 state 计算 |
| P2 | URL hash 同步 / 校验红边 | 切 tab 改 hash + hashchange;邮箱/名称/手机 blur 校验红边 | 仅本地 state;仅原生 email 校验,无 `.invalid` 红边 | 补回 |
**React 自加:** 修改密码 modal(真表单)、真实会话渲染(合理增强)。
---
### 12. 账户页(account.html → account.tsx) — 6.0/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 充值流程 | 微信/支付宝 → **扫码充值 modal**(二维码+金额+赠送+5分钟有效+双按钮)+ toast | **整个充值 modal 没做**,点支付直接提交 | 移植 `.topup-modal` |
| P0 | 三大表格筛选条 | 项目/成员/流水表均有 `.filter-bar`(阶段/成员/范围 select + 清除 + 计数) | **筛选条整条缺失** | 补 filter-bar |
| P0 | 按项目/成员表 | 项目 6 列(商品/成员+角色pill/阶段 progress-mini/状态 pill);成员 5 列(完成数/额度百分比+进度条/最近活跃) | 砍成裸表(项目 4 列、成员缺完成数/活跃/角色 pill/进度条),状态是裸英文 | 按 V1 列重排 + pill/progress |
| P1 | 趋势图 / 金额格式 | 14 柱有 mock 兜底;`fmtMoney` 带千分位 | 接口空则**整图空白**;`money()` **无千分位** | 加空态兜底 + toLocaleString |
| P1 | 流水状态/正负色 | status-tag 三态 + 金额 +/- 三色 | **写死全绿「成功」+ 全 neg**,无 ¥/正负/千分位 | 按真实状态映射 |
| P1 | 表格 tbody 横线 | **无横线**(铁律) | 自加 `border-bottom` | 去掉(S9) |
| P2 | mono 字号 / hero 字重 | 分级 9-11.5px / hero 数 700 | 统一 12px / 600 | 对齐(S2/S8) |
---
### 13. 团队页(team.html → team.tsx) — 5.0/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | 创建账户弹窗 | 专属 modal:用户名/密码+生成按钮、角色 `.role-choice` 双卡、每日/每月/总额三额度 | 通用 TeamModal:角色 `<select>`、只有月度额度一格、无生成按钮 | 重建专属 modal |
| P0 | 分享凭据弹窗 | 创建成功弹绿勾 + cred-card(地址/用户名/密码)+ 单项/一键复制 | **完全缺失**(创建后无凭据回显) | 新建分享凭据 modal |
| P0 | 编辑成员 / 重置密码 / 月限额弹窗 | 各有专属结构(角色卡/警示框/快捷预设 pill + 实时剩余) | 全降级成通用裸输入框 | 重建三个定制 modal |
| P1 | 全部动态弹窗 | `feed-all-modal` + 4 筛选 chip + 计数 | 直接跳 messages 页 | 建动态弹窗或确认产品决策 |
| P1 | 成员表·每日额度列 / 创建者标 / pending 态 | 真实每日额度 / 「· 创建者」mono / pending 行 .65 透明 | 整列硬编码「不限」/ 无创建者标 / 无 pending 态 | 接数据+补态 |
| P1 | 弹窗准星 / 行可点击 | SVG 十字准星 / 整行可点进编辑+hover | TeamModal 用字面 `+` / 行不可点 | 改 SVG 准星 + 行 hover |
| P2 | banner 字重 / mono 字号 | 700 / 10.5px | 600 / 12px | 对齐(S8/S2) |
---
### 14. 登录 + 注册(login/register → auth-screen.tsx) — 4.0/10
| 严重度 | 位置 | V1 设计 | React 现状 | 修复建议 |
|---|---|---|---|---|
| P0 | **整张注册页** | 团队名/超管邮箱/密码/确认密码/邀请码 5 字段 + 一致性校验 + 协议勾选 + 左侧 4 价值点 + 「创建团队」CTA | **0 实现**;`AuthMode` 只有 `"login"`;`/register` 被强制改回 `/login` | 新建注册态/组件,放开 AuthMode |
| P0 | 第三方登录 | OR 分割线 + 微信扫码 / 飞书 SSO 双按钮 | **整块缺失** | 补 `.divider` + `.sso-row` |
| P0 | 切换注册行 | 底部「还没账号? 注册团队 →」 | **完全没有**(无入口去注册) | 补切换行 |
| P1 | 第一字段 | 「**邮箱** *」type=email 信封 icon `name@company.com` | 错改成「**用户名** *」type=text 人形 icon | 改回邮箱(除非后端确认改用户名) |
| P1 | lead 引导 / mono 副标 / 返回链 | 引导文案 / `// /auth/login` / 「← 返回工作台」 | 无 lead / `// LOGIN` / 无返回链 | 补回 |
| P2 | 品牌字号字重 / h-row 布局 | h1 30px/700、h2 22px/600、h-row 横排 baseline | 36px/500、28px/500、改竖排 | 对齐 V1 |
**React 自加:** 三段进度动画 + spinner;字段级红边校验(登录页 V1 本无 error 态参照)。
---
## 四、按优先级的修复清单(建议执行顺序)
### 第一波 · 系统性根因(改一次,全站受益)
1. **S1 双主题** — 把残留 styles.css 类迁入 restraint 体系,消灭暖米错橙(工作台/登录/Toast 一起好)
2. **S3 Toast 体系** — 启用已存在的 `.toast`,弃用 inline-toast,恢复就地反馈
3. **S4 弹窗动画+ESC** — overlays.tsx 4 组件改常驻挂载 + 补 ESC
4. **S2 mono 字号** — 抽工具类统一回 10.5/11/11.5 分级
### 第二波 · 各页 P0 功能补全
5. **登录/注册** — 把注册页做出来(目前 0 实现) + 登录第三方/切换链/邮箱字段
6. **资产库** — 批量栏 + 资产详情弹窗 + 上传动态字段三大件
7. **团队** — 5 个定制弹窗重建 + 分享凭据弹窗
8. **账户** — 充值扫码弹窗 + 三表筛选条 + 表格列补全
9. **AI 图片工具** — 模特库/商品库选择弹窗 + gen-card 三接口 + 平台多选
10. **商品** — 视图切换 + 多选分类 + 任务记录 Tab + 多图上传;**项目** — 网格卡进度条 + 失败 pill + 批量栏
### 第三波 · 细节回补(P1/P2)
11. S6 批量多选(products/projects/library 统一)· S7 分页器升级 · S8 hero 字重 · S9 表格横线
12. 各页字号/文案/mono/准星等逐项对齐(见分页面详单 P2 行)
---
## 五、客观说明
- **不是所有差异都是「做错」:** React 侧有大量 V1 mock 没有的**真功能增强**(管线的 TTS/字幕/转场/拖拽、骨架屏 loading 态、真实数据绑定、修改密码/会话管理),这些是加分项,不应回退。
- **本报告只盘「视觉/交互还原度」**,不评判后端功能正确性。
- **优先级排序的逻辑:** 系统性根因 > 整页缺失(登录/资产/团队)> 单页 P0 > 细节。先打根因,能用最小改动把「整体观感不对」拉回最多。
- **建议:** 这份报告可作为给张业昌的还原 checklist;逐条认领、改完对照 design.md §8 Don't List 自检。
+2 -18
View File
@@ -5,25 +5,13 @@ import { api, getRemember, setRemember as persistRemember } from "../api";
import type { Team, User } from "../types"; import type { Team, User } from "../types";
import type { AuthMode } from "./route-config"; import type { AuthMode } from "./route-config";
const LOGIN_PROGRESS_STAGE_MS = 520; type LoginProgress = "checking" | "entering";
type LoginProgress = "checking" | "verified" | "entering";
const LOGIN_PROGRESS_COPY: Record<LoginProgress, string> = { const LOGIN_PROGRESS_COPY: Record<LoginProgress, string> = {
checking: "正在验证用户名和密码…", checking: "正在验证用户名和密码…",
verified: "验证成功,正在登录…",
entering: "登录成功,正在进入 Airshelf" entering: "登录成功,正在进入 Airshelf"
}; };
function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function completeProgressStage(startedAt: number) {
const remaining = LOGIN_PROGRESS_STAGE_MS - (Date.now() - startedAt);
if (remaining > 0) await wait(remaining);
}
type FieldErrors = { type FieldErrors = {
username?: string; username?: string;
password?: string; password?: string;
@@ -92,15 +80,11 @@ export function AuthScreen({
setError(""); setError("");
setLoginProgress("checking"); setLoginProgress("checking");
try { try {
const checkingStartedAt = Date.now();
const payload = await api.login({ username: username.trim(), password }); const payload = await api.login({ username: username.trim(), password });
// 记住我:勾选则存用户名 + 7 天有效期,token 落 localStorage;否则只存当前会话 // 记住我:勾选则存用户名 + 7 天有效期,token 落 localStorage;否则只存当前会话
persistRemember(username.trim(), remember); persistRemember(username.trim(), remember);
await completeProgressStage(checkingStartedAt); // 凭证验过即进:不再做任何人为延迟动画。"进入"态由真实的 onAuthed(身份+数据水合)驱动。
setLoginProgress("verified");
await wait(LOGIN_PROGRESS_STAGE_MS);
setLoginProgress("entering"); setLoginProgress("entering");
await wait(LOGIN_PROGRESS_STAGE_MS);
await onAuthed({ ...payload, remember }); await onAuthed({ ...payload, remember });
} catch (err) { } catch (err) {
setLoginProgress(null); setLoginProgress(null);
+160
View File
@@ -0,0 +1,160 @@
# AirShelf · 性能审计报告
> **范围:** 全量扫描 —— 前端(数据加载/水合、列表渲染、pipeline 重渲染、全站轮询)+ 后端(ORM N+1、端点耗时/payload、数据库索引)
> **背景:** 用户反馈刷新/进页面拉数据慢、不够丝滑;张业昌已优化过一波,仍有体感卡顿
> **日期:** 2026-06-19
> **方法:** 6 路并行审计,每块两端源码核查到 file:line
> **严重度:** P0 = 明显卡顿/秒级阻塞 · P1 = 可感知延迟 · P2 = 优化空间
---
## 〇、先说结论:"刷新慢/不丝滑"的真正元凶
按"改动成本 vs 体感收益"排序。**前两条是一行/小改就能立竿见影的,强烈建议先做。**
| # | 根因 | 类型 | 现状 | 一句话修复 | 收益 |
|---|---|---|---|---|---|
| 🔴 1 | **线上 DB 连接不复用** | 后端配置 | `production.py``CONN_MAX_AGE`,每请求重连远程 MySQL ~1.6s;并发 N 接口=N×1.6s。development 有这条优化,**线上没搬** | production.py 加 `CONN_MAX_AGE=300`+`CONN_HEALTH_CHECKS` | **刷新整页快几十秒→秒级** |
| 🔴 2 | **登录假动画延迟 1.5s** | 前端 | 凭证验过后还硬演 verified+entering 两段共 ~1.5s | **已修**(见本报告末) | 登录即时进 |
| 🔴 3 | **视频生成期 5s 全量回拉项目详情** | 前后端 | 有视频在跑时每 5s 调 `api.project()`,跑最重的 12-prefetch 序列化器、回 ~26KB,整页每 5 秒抖一下 | 轮询改用轻量 `poll-video-segment` 单段状态 | 视频阶段不再整页抖、payload 砍 99% |
| 🔴 4 | **消息中心首屏同步 N+1 爆炸** | 后端 | 进消息面板时 `list()` 里同步循环 aggregate+get_or_create,单次可达 **500~1000+ 条 SQL** | 批量聚合 + bulk_create + 改 Celery 异步 | 打开消息面板秒回 |
| 🔴 5 | **Asset 大表零索引** | 后端索引 | 全站最大增长表,资产库列表/计数/facets 全表扫 + filesort | 加 `(team,category,-created_at)` 等复合索引 | 资产多时数量级提速 |
| 🔴 6 | **pipeline 3100 行巨页不拆不 memo** | 前端 | 任一 state 变(打字/轮询/hover)reconcile 整棵树 | 5 个 stage 拆 memo 子组件 + 派生数据 useMemo | 输入/拖动恢复跟手 |
| 🟠 7 | **切走再回必重拉、无缓存** | 前端 | 换页即卸载,每次进页面从零拉、白骨架闪烁 | 加内存缓存 + stale-while-revalidate | 二次进页 0 等待 |
| 🟠 8 | **大页一次拉 200 条 + img 漏 lazy** | 前端 | 商品详情/图片工具 `pageSize:200`,任务中心缩图无 `loading=lazy` | 改服务端分页 + 补 lazy | 首屏字节/解码降一个量级 |
---
## 一、前端 · 数据加载与水合(7/10)
> 水合链路已认真优化过:骨架先出、`loadData` 并行 Promise.all、不阻塞首屏、liteRefresh、懒加载——核心卡顿点基本消除。剩下的是缓存与"小操作全量重拉"。
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| P1 | 各路由 + `App.tsx` renderPage switch | **切走再回每次都重拉**:换页即卸载旧组件,`reloadFlag` 归零,资产库/消息/团队/消费/图片工具/商品详情/工作台进去都从零拉,无任何缓存 | 反复横跳每次白骨架闪烁,不丝滑 | 模块级内存缓存或 SWR:先渲染上次结果,再后台 revalidate |
| P1 | `App.tsx:387` `action()` | **几乎所有变更都触发全量 `loadData()`**(并发重拉 products+projects+billing+models+badge 五个);只有 pipeline 改/加/删分镜传了 liteRefresh | 改商品/传图/改资料后顶栏侧栏整体抖一下,且并发拉无关数据 | 给不动列表的操作普遍补 `liteRefresh`,或让调用方只刷相关切片 |
| P1 | `App.tsx:388` `action()` | **每次 action 无条件 `refreshProjectDetail()`**,即便不在 pipeline、操作与项目无关(改头像/充值/建商品都白拉 26KB 项目详情) | 非管线页做操作时白发一个大请求 | 仅 `page==="pipeline"` 或操作确属项目相关时才刷 |
| P2 | `ai-tools.tsx:94`/`products.tsx:551`/`:568` | `pageSize:200` 一次拉大页再前端筛/分页 | 资产多的团队首屏偏慢 | 改服务端分页 + 触底加载 |
| P2 | 站内点进管线 `App.tsx:759-786` | 详情未到时整页 loading,未与导航预取重叠 | 从项目列表点进管线有一次明显等待 | `openPipeline` 时即并行预取 `api.project(id)` |
✅ 已核实无问题:loadData 并行且不阻塞首屏、骨架态完整、搜索去抖到位、防连点到位、**无人为延迟卡流程**(除已修的登录)。
---
## 二、前端 · 列表与图片渲染(6.5/10)
> 分页/懒加载/去抖基础都对,但几处"大页一次拉 + 一次性渲染"和漏 lazy,会在素材多的真实账号上突然变卡。
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| **P0** | 商品详情 `products.tsx:551` | 进详情一次拉该商品**最多 200 条**素材,前端再 filter+slice 只显示 12,200 条全进 state | 素材多的商品点进去明显"转一下" | 服务端按页拉(page+pageSize=12),"加载更多"走下一页 |
| **P0** | 图片工具任务中心 `ai-tools.tsx:314,349` | 结果缩图 `<img>` **漏 `loading="lazy"`**(同文件别处都加了)+ `:94` 一次拉 200 张,首屏并发几十张原图 | 进"图片生成"页一批图齐刷、带宽抖动 | 两处补 `loading="lazy"`,源数据改分页 |
| **P0** | 商品库/项目页 `App.tsx:141-142`+`api.ts:175,204` | `api.products()/projects()` 只取后端**第 1 页 20 条**,却被当"全量"做客户端搜索/筛选/分页 → 第 21 个起根本不在内存 | **搜不到/筛不到第 20 名以后的数据**,用户误判"卡了没加载" | products/projects 改服务端分页(像 library/messages 那样) |
| P1 | 图片工具模特网格 `ai-tools.tsx:568` | 一次拉 200 个模特进 state,点"全部模特"渲染全部缩图 | 模特库大时工作台首开+展开卡 | 默认拉前若干,"全部"走分页 |
| P1 | 商品详情视频 tab `products.tsx:643-646` | `videoProjects` 不分页不截断,一次渲染全部视频卡(assets tab 有 limit,videos 没有) | 项目多的商品切到视频 tab 一次铺满 | videos tab 也加 limit/分页 |
| P1 | 向导商品选择器 `projects.tsx:278-280` | 页码用 `Array.from({length:totalPages})` 全量展开,没用 pageWindow 折叠 | 商品多时页码一长条 | 复用 `pageWindow()` 折叠 |
| P2 | 商品库 `products.tsx:124-134` | 搜索纯客户端但每键触发整列表重算;`ProductCard` 未 memo,map 内联闭包 | 商品多时逐字搜索略顿 | ProductCard 包 memo,filtered 用 useMemo |
| P2 | 列表行未 memo / key 用 index | `projects.tsx:588`/`account.tsx:331,347` 内联闭包无 memo;`products.tsx:435,819` key=index | 当前每页 10 条无碍,数据增长后退化 | 列表项抽组件 memo,key 用稳定 id |
✅ 正向:六个页面 loading 都用骨架不白屏;messages/library/account 已是真服务端分页+去抖,messages 还做了滚动无限加载+防竞态。
---
## 三、前端 · pipeline 重渲染与轮询(5/10)
> 轮询清理/动画做得不错,但**单个 3100 行巨页 + 5s 全量回拉**让整页每 5 秒抖,后台 tab 也照拉不停。这是 pipeline"不丝滑"的核心。
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| **P0** | `App.tsx:320-334`+`api.ts:207` | 视频 5s 轮询每轮 `api.project()` 拉**全量 ~26KB**再 `setProjectDetail`,无脏检查,即便没变也 set → 整页重渲染 | 有视频在跑时整页每 5 秒"刷一下",滚动/输入发顿 | 后端加轻量 `video-status` 端点;或 set 前对比版本号/JSON 跳过 |
| **P0** | `pipeline.tsx:445`(整组件)+`503-585` | 单个 3100 行函数持 60+ useState,5 阶段全在一棵树;`byId/scripts/shots/groups` 每次渲染重算(memo 远不够),组件无 memo 无拆分 | 任一 state 变(打字/轮询/hover)reconcile 整棵巨树,输入延迟 | 5 阶段拆 memo 子组件只渲当前 stage;派生数据包 useMemo |
| P1 | 全站轮询无 `visibilitychange` | 5s/8s/4s 轮询在**隐藏 tab/切后台时照跑**(全局 0 命中 document.hidden) | 切走后仍持续打接口,耗电耗流量,服务端空转 | poll tick 起始加 `if(document.hidden)return` |
| P1 | `pipeline.tsx:2076-2095` | stage2 `pendingAssets` 4s 轮询**空闲也永不停**,`list.length==0` 也照 setTimeout | 停在资产页啥也不干也每 4s 打接口 | 连续 N 轮空就停,有新提交再启 |
| P2 | `pipeline.tsx:84-89` `getAssetBlobUrl` | `createObjectURL` 进 module Map 缓存后**全程不 revoke**,跨项目累积 | 长会话内存缓慢上涨 | 切/卸载项目时 revoke + 清 Map |
✅ 已核实无问题:ProgressTimeline 的 1s interval 作用域小且 done 即停;两处 rAF 正确 gate+cleanup;5s/8s/120ms 轮询都在卸载时清理且按阶段 gate;CSS infinite 动画都是条件出现的 spinner/shimmer;无人为卡流程的 setTimeout。
---
## 四、后端 · ORM N+1 与序列化器(核心路径已治理,集中 2 个热点)
> 详情/列表核心路径已被认真优化(prefetch、defer 大 JSON 列、annotate 计数、轻量列表序列化器)。真正的放大点集中在消息中心首屏 + 写操作回吐全量。
| 严重度 | 位置 | 问题(估算多发 SQL) | 体感影响 | 修复建议 |
|---|---|---|---|---|
| **P0** | `apps/ops/views.py:69-235` `ensure_team_notifications`(在 `list()` 同步触发) | 首屏循环内:5 项目各 `credit_ledgers.aggregate()` + 拉最多 500 条 charge 流水逐条 `get_or_create`。**单次首屏 500~1000+ 条往返**,远程库几秒~几十秒 | 第一次开消息面板/换团队整页长转圈 | 项目花费 `values('project').annotate(Sum)` 批量;charge 通知批量查 dedupe_key + bulk_create;首屏走 Celery 异步 |
| P1 | `apps/ops/views.py:285-292` `type_counts` | **每次**列表请求额外 6 条 `.count()`(all/unread/task/team/billing/system) | 收件箱翻页每次多 6 个往返 | 一次 `values('notification_type').annotate(Count)`+单 unread count = 2 条 |
| P1 | `apps/projects/views.py` 写 action 一律回 `ProjectSerializer(project).data`(`:491/544/681/781` 等) | 每个采用版本/上传/存草稿按钮都回吐**整棵项目树**(几十~上百行),个别 action 未走 prefetch 还会 N+1 | 每次写操作后界面可感卡顿 + 回包巨大 | 只回受影响子对象(已有 VideoSegmentVersionSerializer 等);必须回全量则走带 prefetch 的重取 |
| P2 | `apps/projects/serializers.py:90-100` `BaseAssetGroupSerializer` | 线索核实:列表/详情**不 N+1**(queryset 已 prefetch adopted_asset/candidate/files)。仅 `adopt_base_asset` 单独序列化未预取的 group 时每组 +2~3 条 | 采用基础资产后单次轻微多查 | 单对象序列化前补 select_related/prefetch |
| P2 | `apps/assets/views.py:98-118` summary/facets | 资产库徽标 6 条 count、facets 多条 distinct(固定不放大) | 进资产库一组并发 count | 可合成一条 `aggregate(Count(Case(When)))` |
✅ 已核实无问题:项目列表用 ProjectListSerializer+annotate 无 N+1;详情/商品/AITask/资产列表均已 select_related/prefetch_related + defer 大 JSON;billing/team_members 已批量化。
---
## 五、后端 · 端点耗时与 payload(7/10)
| 严重度 | 位置 | 问题 | 体感影响 | 修复建议 |
|---|---|---|---|---|
| **P0** | `settings/production.py`(缺 `CONN_MAX_AGE`)vs `development.py:10-13` | **线上 MySQL 连接未复用**,每请求新建连接 ~1.6s 握手。开发环境设了 300s 复用并注明"登录并发 12 接口=卡几十秒",**这条优化只在 development 生效** | 线上每接口多 1.6s,一进页面并发 N 请求=N×握手 | production.py 加 `CONN_MAX_AGE=300`+`CONN_HEALTH_CHECKS=True`(提到 base.py 更稳) |
| **P0** | `App.tsx:324,331`+`pipeline.tsx:1876``projects/views.py:118` | 任一视频段 running 时**每 5s 调 `api.project`**,触发 12-prefetch 最重序列化器,把全部版本/分镜/资产/timeline 一次塞响应 | 视频阶段持续拉巨 payload + 整页重渲 | 轮询改用已有轻量 `poll-video-segment`(views.py:546)只回单段状态;或详情加 ETag→304 |
| P1 | `projects/views.py:278`+`assets/review.py:111`,前端 `pipeline.tsx:816`(8s) | `poll-reviews` 在**请求线程里同步循环调火山外部 HTTP**(每个审核中资产一次),前端每 8s 打一次 | N 个审核资产时单次请求阻塞=N×火山往返 | 火山审核轮询挪 Celery 后台,端点只读 DB review_status |
| P1 | `apps/ai/views.py:68` `AITaskViewSet` | 未设显式 `ordering`,分页可能重/漏行 | 任务多时前端反复拉 | 加 `ordering=["-created_at"]` |
| P2 | `apps/ops/views.py:276` list | 每次额外 6 条 count(同上 N+1 P1) | 无高频轮询,影响有限 | 合并 count |
| P2 | `apps/billing/views.py:182` trend | "按阶段分布"用 Python 循环累加本月全部流水,非 DB 聚合 | 流水多时账户页加载慢 | 改 `values('task__task_type').annotate(Sum)` |
✅ 已核实无问题:全局分页健全(PAGE_SIZE=20,无全量返回);assets/AITask/billing 已 defer 3MB+ JSON 大列(注释"40 条要 30s+"已修);poll-storyboard 后台线程不阻塞;通知生成已 Celery+60s 缓存。
---
## 六、后端 · 数据库索引(7/10)
> 基表 `team` FK 自带单列索引,纯 `filter(team=)` 不慢。真正缺的全是"team + 第二过滤列/时间排序"的**复合索引**。远程 MySQL 上数据量上来后,缺索引的过滤/排序会明显变慢。
| 严重度 | 表/字段 | 问题 | 体感影响 | 加什么索引 |
|---|---|---|---|---|
| **P0** | **Asset** `(team,category,-created_at)` 等 | `assets/models.py:6` 整表**零自定义索引**,资产库每 tab `filter(team,category).order_by(-created_at)` + 6 个 count + facets distinct。Asset 是全站最大增长表 | 资产库刷新/切 tab/徽标计数,大表全表扫+filesort 秒级卡 | `Index(["team","category","-created_at"])` + `(team,asset_type)` + 视需要 `(team,source)` |
| **P0** | **Asset** `(team,-created_at)` | 默认无筛选排序也要 team 内取全部再 filesort | 默认视图随资产数线性变慢 | `Index(["team","-created_at"])`(或被上条复合覆盖) |
| P1 | **CreditLedger** `(team,-created_at)` | 流水分页 `filter(team).order_by(-created_at)`,现有索引不覆盖。Ledger 高频写入 | 账单流水翻页随增长变慢 | `Index(["team","-created_at"])` |
| P1 | **CreditLedger** `created_at` 范围 | 消费趋势 `filter(team,ledger_type=CHARGE,created_at__date__gte)`+TruncDate 分组无法走索引 | 账户消费分析月度数据多时慢 | `(team,ledger_type)` 扩成 `(team,ledger_type,created_at)` |
| P1 | **AITask** `(team,-created_at)` | 无显式 ordering + 无时间序索引 | 任务列表分页不稳+filesort | `Index(["team","-created_at"])` + ViewSet `ordering` |
| P1 | **Project** `(team,-updated_at)` | 列表 `filter(team).order_by(-updated_at)`,索引不含 updated_at | 项目列表/侧栏随项目数变慢 | `Index(["team","-updated_at"])` |
| P1 | **Product** `(team,-created_at)` | 列表无显式 ordering + 时间序索引 | 商品列表分页随商品数变慢 | `Index(["team","-created_at"])` + 默认 ordering |
| P2 | **Asset** `metadata__product_id` | 按商品过滤走 JSON 提取 + 三路 Q 并集 distinct,无法走普通索引 | 商品详情"该商品资产"大表下慢 | MySQL 函数/生成列索引,或冗余真实 `product_id` 列 + `(team,product_id)` |
> ⚠️ 远程 MySQL 大表 `ALTER TABLE ADD INDEX` 会锁表,建议低峰执行或用 online DDL。Notification 表(`ops/models.py:52`)索引已做得很好,可作其它表范本。
---
## 七、已修复
- **登录假动画延迟(🔴2)** —— [auth-screen.tsx](core/frontend/src/routes/auth-screen.tsx) 已删除 `api.login()` 成功后的 verified/entering 两段共 ~1.5s 人为 `wait`,以及 checking 阶段的最小垫时。现在凭证验过即进,"进入"态由真实的 `onAuthed`(身份+数据水合)驱动,不再演动画。
---
## 八、建议执行顺序(按 ROI)
### 第一波 · 一行/小改,立竿见影(强烈建议先做)
1. **🔴1 production.py 加 `CONN_MAX_AGE`** —— 消掉线上每请求 1.6s 握手,"刷新慢"最大单点
2. **🔴3 视频 5s 轮询改轻量端点** —— 别再每 5s 拉全量项目详情
3. **🔴8 两处 `<img>``loading="lazy"`** + 商品详情/图片工具改服务端分页
4. **隐藏 tab 暂停轮询**(poll 加 document.hidden 守卫)
### 第二波 · 后端查询治理
5. **🔴4 消息中心首屏 N+1** —— 批量聚合 + bulk_create + Celery 异步
6. **🔴5 Asset/Ledger/Project/AITask 加复合索引**(低峰执行 DDL)
7. **poll-reviews 火山同步 HTTP 挪 Celery**
8. **写 action 不再回吐全量 ProjectSerializer**
### 第三波 · 前端体验
9. **🔴6 pipeline 巨页拆 memo 子组件**(工作量大,但根治 pipeline 不丝滑)
10. **🟠7 切走再回加内存缓存 + SWR**
11. **action() 普遍补 liteRefresh + 条件化 refreshProjectDetail**
---
## 九、客观说明
- **张业昌确实优化过一波且方向对**:骨架先出、并行水合、defer 大 JSON 列、列表轻量序列化器、prefetch、防竞态、去抖——这些都到位,不是没做。
- **剩下的卡顿高度集中**:① 一条线上配置漏搬(CONN_MAX_AGE)② 视频轮询拉全量 ③ 消息中心首屏 N+1 ④ Asset 缺索引 ⑤ pipeline 巨页。**修掉前 4 个(都不算大改),"刷新慢"基本能解决**;pipeline 巨页拆分是更大的工程,可单独排期。
- 本报告每条都带 file:line,可直接当修复 checklist 交给张业昌认领。