Files
tps-dashboard/logs/phase2_log.md
T

539 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 2 开发日志 — 仪表盘真数据 + 用户管理 + Excel 批量导入
> 《军事科技》栏目内部工作台 · Phase 2
> 撰写日期:2026-05-15
> 状态:**Task 1 后端 CRUD 完成,冒烟实测 6/6 通过;Task 2 进行中**
---
## 一、本 Phase 完成时间
- **开始日期**:2026-05-15
- **Task 1 后端 CRUD**:2026-05-15 完成,冒烟 6/6 全绿
- **Task 2 Excel 批量导入**:进行中
- **Task 3 前端**:待开始
- **预计 Phase 2 整体收官**:待定
---
## 二、本 Phase 做了什么
### Task 1 — 后端 CRUD2026-05-15
1. **models**`episode.py`(软引用+快照)、`yearly_target.py`year UNIQUE
2. **schemas**`episode.py``yearly_target.py``user_admin.py``auth.py` 顺手改 ConfigDict
3. **API**`users.py`(管理员 CRUD)、`episodes.py`(增删改查)、`yearly_targets.py`(增删改查,新增 DELETE
4. **路由注册**`main.py` 注册新路由
---
## 三、Task 1 冒烟实测记录(2026-05-15 Cline 自跑)
使用 `backend/scripts/smoke_test.py`(幂等版可重复跑),结果:
| 步骤 | 接口 | 预期 | 实际 | 结果 |
|------|------|------|------|------|
| 1 | POST /api/users 新建 zebian | 201 | 201 | ✅ ID=4 |
| 2 | POST /api/yearly_targets 新建 2026 | 201 | 201 | ✅ ID=2 |
| 3 | POST /api/yearly_targets 重复 2026 | 409 | 409 | ✅ 正确拦截 |
| 4 | POST /api/episodes editor_id=null | 201 | 201 | ✅ ID=2 |
| 5 | GET /api/episodes 列表 | 200 | 200 | ✅ |
| 6 | PATCH /api/yearly_targets 尝试改 year | 200year 不变) | 200 | ✅ year 仍为 2026 |
**6/6 全部通过。**
---
## 四、业务红线验证记录
| 红线 | 代码位置 | 验证结果 |
|------|----------|----------|
| 收视颜色只对 audience_share | `episode.py` 注释 | ✅ 未实现颜色逻辑,仅存字段 |
| 年度目标只增不改 year UNIQUE | `yearly_targets.py` create_target + `yearly_target.py` UNIQUE | ✅ 冲突报 409 |
| year 字段禁止 UPDATE 修改 | `yearly_targets.py` update_target pop("year") | ✅ 冒烟 Step 6 验证 |
| editor_id 可空+editor_name_snapshot 必填 | `episode.py` Field 设计 | ✅ 冒烟 Step 4 editor_id=null 成功 |
| 角色枚举全拼音 | `UserRole` Enum + CHECK 约束 | ✅ 三角色全是拼音 |
| ON CONFLICT DO NOTHING(非 UPDATE | `yearly_targets.py` 注释 | ✅ 只增不改 |
| password_hash 明文传输开发配置 | `security.py` + `main.py` https_only=False | ⚠️ Phase 5 部署前必改 |
---
## 五、踩过的坑(Phase 2 内部教训)
### 坑 1:API 文件漏了模型类导入(一类 bug)
**现象**Swagger UI 实测 `POST /api/yearly_targets` 报 500`NameError: name 'YearlyTarget' is not defined`
**原因**`api/yearly_targets.py``api/episodes.py` 用了 `Episode` / `YearlyTarget` 类但顶部漏了 import 语句
**排查**:同步检查 `api/users.py``api/episodes.py``api/yearly_targets.py` 三文件,发现 episodes.py 也漏了
**修复**
- `episodes.py``from app.models.episode import Episode`
- `yearly_targets.py``from app.models.yearly_target import YearlyTarget`
- `users.py` 删掉未使用的 `verify_password` 导入
**教训**:新建 API 文件后,**第一件事检查所有模型类是否已 import**,与 Schema import 同等优先级。Task 1 完工报告前未做最小冒烟测试,导致这类问题漏到实测阶段才发现。以后每个 Task Act 完成后必须自跑冒烟,不依赖制片人发现。
### 坑 2:冒烟脚本 emoji + 中文导致 Windows GBK 崩溃
**现象**`UnicodeEncodeError: 'gbk' codec can't encode character '\u274c'`
**修复**:将 `smoke_test.py` 全部改英文 + ASCII 字符(`✅``[OK]``❌``[FAIL]`
### 坑 3:数据不幂等导致重跑失败
**现象**:第二次跑冒烟,Step 1/2/4 全部 409(数据已存在)
**修复**:改为幂等版脚本,先清理同名数据再创建
### 坑 4pip 默认路由到系统 Python 3.14,绕过项目锁定的 3.12
**现象**`pip install pandas openpyxl` 后报错,或运行时报 `ModuleNotFoundError: No module named 'pandas'`(已确认装过)
**原因**:系统 PATH 中 python 命令指向 Microsoft Store 的 Python 3.14,而项目要求 Python 3.12,导致装到了错误的解释器。`pip show pandas` 发现路径在 AppData/Local/Microsoft/WindowsApps 而非项目 venv。
**修复**
- 确认项目使用 `python -m pip install ...`(明确调项目锁定的 3.12
- 或激活 venv 后 `& .\venv\Scripts\Activate.ps1` 再 pip install
- 装完 `pip show pandas` 核对路径包含 `venv` 或项目目录
**教训****任何 pip install 必须验证装到了哪个 Python 解释器**。先用 `where python` 确认 PATH 优先级,用 `python -c "import sys; print(sys.executable)"` 确认实际调用的解释器,再执行安装。
---
### 坑 5002b SQL 表达式索引使用 EXTRACT(...)::int 语法不被 PostgreSQL 接受
**现象**`CREATE INDEX idx_episodes_broadcast_date ON episodes (EXTRACT(year FROM broadcast_date)::int);` 在 pgAdmin / psql 中执行报错:`ERROR: syntax error at or near "::"`
**原因**PostgreSQL 的 `EXTRACT()` 返回 `double precision` 类型,`::int` 是标准 cast 语法,但某些 PostgreSQL 版本或客户端对表达式索引内 cast 有限制。SQL 在 commit 前未本机干跑直接进库,翻车后才发现。
**修复**:改为 `date_part('year', broadcast_date)` 语法,等效且兼容性更好:
```sql
CREATE INDEX idx_episodes_broadcast_date ON episodes (date_part('year', broadcast_date));
```
**教训****SQL 文件 commit 前必须在本机干跑**。即使是看似简单的 DDL,本机跑一遍能提前发现语法兼容性问题,避免入库后才发现翻车。Task 1 NameError(坑 1)与本坑同源,都是"没自测就 commit"。
---
### 坑 6Gitea OAuth refresh token 过期导致 git push 反复 401
**现象**`git push` 多次返回 401 Unauthorized,Git 凭证管理器反复弹框,输入正确账密后仍然失败。
**原因**Gitea OAuth access token 有效期到期,refresh token 也已失效,导致 Git 凭证无法续期。多次 401 后需重新完成 OAuth 认证流程刷新 token。
**修复**
- 删掉旧 tokenGit Bash 执行 `git credential reject` + 重置 credential helper
- 重新触发 GitHub/Gitea OAuth 流程获取新 access token
- 验证:`git push` 成功后确认 remote URL 使用新的 token
**教训**:**push 不成功不算完工**。本次 Task 2 期间 push 已全部恢复正常,之前的 git push 条目从"认证失败,需制片人手动"已转为✅完成。
---
## 六、纪律警告(Phase 2 Task 2 期间立)
### 纪律 1"push 待制片人手动处理"不算完工
**背景**Task 1 期间 MiniMax 多次以"push 待手动处理"作为完工结尾。本 chat 期间明确规矩。
**教训**:push 是 Git 工作流最后一环,未 push 就不是真正的完工。Git commit 只代表本地变更保存,push 成功才代表远程仓库同步完成。在任务报告中以"push 待手动处理"结尾会误导协作者认为工作已完成。
**落地规矩**:任何任务的完工报告必须包含"git push 成功"状态才算闭环。若 push 遇到认证问题:
- 优先排查 token 状态(坑 6 经验)
- 不得以"待手动处理"为由跳过 push 环节
- push 失败时用红字标注"⚠️ push 待解决",不写入"完工"状态
---
### 纪律 2SQL / 代码 commit 前必须本机干跑
**背景**Task 1 NameError(坑 1+ Task 2 002b SQL 语法错(坑 5)同源,都是 commit 前没自测直接进库。
**教训**:任何变更(SQL 文件、后端代码、前端代码)在 commit 前必须经过本机验证。对于 SQL 文件,本机用 `psql -U postgres -d milsci_dev -f backend/sql/00X_xxx.sql` 干跑;对于 Python 代码,用 `python -m pytest` 或冒烟脚本验证。发现错误当场修,不把问题带进 commit。
**落地规矩**
- SQL 文件:commit 前用 psql 在本机 dev 数据库干跑,确认无语法错误
- Python 代码:至少跑一遍冒烟测试或单元测试,确认基本功能正常
- 禁止用"这只是小改/语法简单/理论上没问题"为由跳过自测
- 养成习惯:**commit 前自测,发现问题当场修,比入库后发现 cost 低一个数量级**
---
## 七、遗留事项
### 7.1 Phase 2 内部待完成
- [x] **Task 1 后端 CRUD**:✅ 冒烟 6/6 全绿
- [x] **git push**:✅ 已恢复正常(坑 6 已解决)
- [ ] **Task 2**:Excel 批量导入(收视数据 + 排期模板)进行中
- [ ] **Task 3**:前端仪表盘 + 录入界面 + 用户管理界面
### 7.2 跨 Phase 遗留
#### Phase 4 或后续 Task 处理
- **PATCH /api/users 接口 password 相关权限**:当前 `UserUpdate` Schema 允许改 `is_active``role``display_name``profile_text`,但:
- `password_hash` **不允许**通过 API 修改(当前设计)
- 能否改自己密码?不能(当前设计)
- 改别人密码是否强制对方下次登录改密?未实现
- 建议 Phase 4+ 新增独立 `/api/users/change-password` 接口,设计时拉 Op
#### Phase 5 部署时处理(与 SECRET_KEY 并列)
- **传输安全**:当前 `password` 在请求体明文传输,仅 HTTPS 环境下可接受。Phase 5 部署到腾讯云后:
-`same_site="strict"` + `https_only=True`
- 确认 HTTPS 开启,强制全站 TLS
- 考虑 API 请求体密码加密或单独 `/api/auth/change-password` 接口
- **SECRET_KEY 占位符**`.env` 当前是 `change-me-to-a-random-string-in-production`**Phase 5 部署前必须改**为真实随机值。生成命令:
```python
python -c "import secrets; print(secrets.token_urlsafe(32))"
```
---
## 八、Git commit 记录
| commit | 内容 |
|--------|------|
| 143601c | docs: 合入第十三章 Opus task brief 节制原则,版本升至 v5 |
| 33bee0c | feat: 后端 CRUD 完成(episodes + yearly_targets + users 管理 API |
| 63c031a | docs: Task 1 完工记录,Swagger UI 验收清单,跨 Phase 遗留事项 |
| 4f7c92d | fix: 修复 episodes.py + yearly_targets.py 模型导入缺失,删 users.py 残留 verify_password,增 DELETE 接口(幂等冒烟后确认 6/6 全绿) |
| f82a71b | docs: phase2_log 追加 Task 2 进行中坑与纪律警告 |
---
---
## 九、Task 2 完工记录(2026-05-19 commit 442d52f
### 9.1 冒烟结果(8 步全绿)
| Step | 描述 | 预期 | 实际 | 结果 |
|------|------|------|------|------|
| 1 | GET /api/imports/template | 200 xlsx | 200 xlsx (5595 bytes) | ✅ |
| 2 | POST /api/imports/episodes 合法行 | 200 success=1 | 200 success=1 failed=0 | ✅ |
| 3 | POST 部分失败(1ok+2bad | 200 errors=2 | 200 errors=2 row_number=[3,4] | ✅ |
| 4 | GET /api/imports/errors/{batch_id} | 200 xlsx | 200 xlsx (5181 bytes) | ✅ |
| 5 | POST 2024 无年度目标 | 400 | 400 含"年度目标"+"2024" | ✅ |
| 6 | POST 重复期次(库内重复) | 数据库约束触发 | failed_count=1 UniqueViolation | ✅ |
| 7 | POST 未知编导"李不存在"软落地 | 200 GET id 确认 null | 200 id=22 editor_id=null | ✅ |
| 8 | GET /api/episodes/{真实id} 验收 | 200 editor_id=null | 200 ✅ | ✅ |
**smoke 脚本:`backend/scripts/import_smoke.py`(幂等版,可重复跑)**
---
### 9.2 坑 7pandas 2.x 不接受 raw bytesBytesIO 包装解决)
**发现时间**2026-05-19 第一轮冒烟
**现象**`pd.read_excel(file_content)` 传入 bytespandas 2.x 报错或读不出数据。
**原因**pandas 2.x 要求 file-like object,不再直接接受 bytes。
**修复**`excel_service.py` 中用 `io.BytesIO(file_content)` 包装后传给 `pd.read_excel`。
**commit**442d52f
---
### 坑 8openpyxl add_named_style 误用(4ca24dc 原代码遗留)
**发现时间**2026-05-19 冒烟 Step 4 下载失败行 Excel
**现象**`generate_error_excel()` 调用 `wb.add_named_style("error_header")` 触发 `TypeError: Only NamedStyle instances can be added`,导致 GET /api/imports/errors/{batch_id} 500。
**来源**:经 git show 4ca24dc 确认,该 bug **在 4ca24dc 原代码中已存在**,非修坑引入。4ca24dc 自检只发现了 pandas bytes 问题,未发现此第二个 bug。
**修复**:删除 `wb.add_named_style("error_header")` 这一行(该样式未实际使用)。
**教训**commit 前自测要覆盖**所有**接口路径,不能只测主要流程。Step 1-3 通过不等于 Step 4 也通。
---
### 9.3 业务红线澄清:Step 6 重复期次拦截的两套机制
**两套拦截路径,各管各的事,不能混为一谈:**
1. **文件内重复 → check_duplicates() 整体 409**
- 检测 Excel 内部是否有重复的 (year, episode_number)
- 由 `ExcelService.check_duplicates()` 在入库前调用
- 有重复则整体拒绝,整文件不入库,返回 HTTP 409
2. **库内重复 → 数据库 UNIQUE 约束 单行失败,其他行入库**
- 检测 Excel 中某行与数据库已有记录重复
- 由 PostgreSQL `idx_episodes_year_number` 唯一约束触发
- 重复行报错进入 `errors[]`,其他行继续入库,返回 HTTP 200
**Step 6 实测的是第二套**(库内重复):Excel 写入 episode_number=99(与 Step 2 已入库的重复),数据库约束抛出 `UniqueViolation`,整行失败,success=0 failed=1。这两套机制分管文件内重复和库内重复,职责清晰。
---
### 9.4 纪律 3:启动期三件套不能证明接口业务路径通
**背景**:Task 2 开发期间,启动期(imports.py + excel_service.py + schemas/imports.py)写完后只跑了 GET 接口自测,POST/PATCH/DELETE 接口靠理论推导认为没问题就直接 commit。实际冒烟时发现 Step 2/3/4/5/6/7 全是 POST,全部出问题。
**教训**:启动期三件套(models/schemas/API 文件 + 路由注册 + 依赖注入)只能证明**文件层面**没有导入错误、路由能匹配、类型能解析。**不能**证明接口的业务路径(读写数据库、事务提交、错误处理)正常。任何 POST/PUT/PATCH/DELETE 接口 commit 前必须发真请求验证业务路径。
**落地规矩**
- 所有变更(SQL 文件、后端代码、前端代码)在 commit 前必须经过本机验证
- POST/PUT/PATCH/DELETE 接口必须用真请求实测,不能靠代码逻辑推导
- 发现错误当场修,不把问题带进 commit
---
### 9.5 遗留 bugsmoke 脚本 Step 7 写错(已改正)
**发现时间**2026-05-19 冒烟 Step 7 第一版
**现象**GET /api/episodes/300 返回 404"在库中不存在"。
**原因**smoke 脚本**把 episode_number 当 id 传**给 GET /api/episodes/{id}。后端 `GET /api/episodes/{id}` 接收的是数据库自增主键 id,不是业务期号 episode_number。episode_number=300 的记录入库后真实 id 是 14(或列表查询返回的其他值),不是 300。
**改正**:先从 `GET /api/episodes?limit=100` 列表中筛选 episode_number=301 的记录,取其真实 id,再用该真实 id 调用 `GET /api/episodes/{real_id}`。确保 GET /api/episodes/{id} 路径被实际验收。
**教训**:smoke 测试中查到新记录后,应先从查询结果中提取真实 id,再进行后续验证,不能假设 episode_number 等于数据库 id。
---
### 9.6 Task 2 收尾清单(还未做的四件小尾巴)
- [ ] **补录 2024 真值年度目标**Step 5 冒烟用到 2024 年,当前库中无 2024 目标,补录后 Step 5 才能真正通过
- [ ] **Windows python 默认 PATH 改指向 3.12**:坑 4 未完全解决,`py` 命令仍可能路由到系统 Python 3.14,需在系统 PATH 中显式优先 3.12
- [ ] **backups/ 进 .gitignore**:虽然 .gitignore 有 `*.log` 等模式,但 `backups/` 目录显式加入更稳,避免样本数据误传
- [ ] **MiniMax 自检报告里 schema 类名对照 Plan 自查**`ImportError`/`ImportResult` vs `UploadTemplateResponse`/`ImportResultResponse` 等命名差异对着 Plan 确认是否一致
---
## 十、Git commit 记录(2026-05-19 追加)
| commit | 内容 |
|--------|------|
| 442d52f | feat: 完善批量导入逻辑,8步冒烟全绿 |
*本日志由 MiniMax M2.7(Cline Act 模式)于 2026-05-15 持续追加,2026-05-19 追加 Task 2 完工坑与红线澄清,2026-05-20 追加 Task 2 收尾记录。*
---
## 十一、Task 2 收尾完工记录(2026-05-20)
### 11.1 Git 安全卫生:token 从 URL 剥离
**背景**:`.git/config` 里 origin 远程 URL 含明文 token(`cline-tps-dashboard`),
存在被 Cline 误贴日志、截图泄露、备份带出等风险。
**改动**:
- `git remote set-url origin` 改成干净 URL(去掉 `user:token@`)
- token 改由 Windows Git Credential Manager (manager) 加密存储
- 两次 fetch 静默通过,push 走 GCM 透明工作
- 旧 token `cline-tps-dashboard` 继续使用,不吊销
**纪律备注**:以后任何凭证(密码、API key、token)都不许写进 URL、不许写进 .py 文件、
不许写进任何会被 commit 的文件。
### 11.2 小尾巴 1:补录 2024 真值
**操作**:制片人手动 psql 直接 INSERT(绕过 API,因 API 权限收紧待办未做)。
**当前 yearly_targets 真值**:
- 2024: base=0.6448 / stretch=0.8290 (issued_date=2024-01-01, 补录说明在 notes)
- 2025: base=0.6448 / stretch=0.8989
- 2026: base=0.6448 / stretch=0.8989
### 11.3 小尾巴 3:backups/ 进 .gitignore
**commit**:20c0a1d(chore: backups/ 进 .gitignore)
**push**:已成功
### 11.4 小尾巴 4:schema 类名自查通过
类名与 Task 2 Plan 一致,无漂移。`ImportError` / `ImportResult` 命名正确。
发现 2 个隐患,**暂不处理,记为待办**:
- 隐患 A:`ImportError` 与 Python 内置同名,当前未撞,未来若在导入逻辑里 raise 才会冲突
- 隐患 B:`ExcelService.import_episodes()` 返回 dict 而非 Pydantic 对象,
API 层手动构造 `ImportResult`,缺一次 Pydantic 校验层。8 步冒烟已验证业务正确,
仅代码洁癖问题
### 11.5 小尾巴 2:Python PATH 根治坑 4
**改动**:从系统 PATH 删除 `C:\Python314\Scripts\` 和 `C:\Python314\` 两行。
用户 PATH 中 Python 3.12 自动顶上来。
**验证**:
- `where.exe python` 第一行变成 3.12 路径
- `python --version` = Python 3.12.10
- `sys.executable` 指向 3.12
**遗留**:E:\tps-dashboard\backend\ 下没有 venv,Python 依赖装在系统 3.12 全局
site-packages 里。**记入待办**,Phase 4 部署前或 Task 3 启动前评估是否补 venv。
### 11.6 待办清单(Task 3 启动前或后续 Phase 处理)
| 待办 | 触发时机 | 优先级 |
|---|---|---|
| `yearly_targets` 写权限收紧到 `zhipianren` + `zebian`,`biandao` 只读 | Task 3 启动前 | 高(业务红线) |
| backend/ 补 venv,把全局依赖搬进去 | Task 3 启动前评估 | 中 |
| 隐患 A:`ImportError` 同名冲突预防 | 真要 raise 时再说 | 低 |
| 隐患 B:`ExcelService` 返回 Pydantic 化 | 代码洁癖,有空时做 | 低 |
### 11.7 Task 2 完整 commit 链(2026-05-19 → 2026-05-20)
| commit | 内容 |
|---|---|
| 442d52f | feat: 完善批量导入逻辑,8步冒烟全绿(2026-05-19) |
| 6de8111 | docs: Task 2 完工记录,坑7/8/纪律3/Q1-Q4 写入(2026-05-19) |
| 20c0a1d | chore: backups/ 进 .gitignore(Task 2 收尾)(2026-05-20) |
**Task 2 完整收尾,Phase 2 Task 2 状态:✅ 完工。**
---
## 第十二章 yearly_targets 写权限收紧(2026-05-20
**状态:✅ 完工。冒烟 12/12 全绿。**
### 12.1 任务来源
task brief 明确要求收紧 yearly_targets 表的写权限,是业务红线。
### 12.2 改动文件
| 文件 | 改动内容 |
|------|----------|
| `backend/app/api/yearly_targets.py` | 5处 `require_role` 参数修正 |
| `backend/app/schemas/yearly_target.py` | `YearlyTargetUpdate` 改为部分字段可选(支持 PATCH 部分更新) |
| `backend/scripts/smoke_yearly_targets_auth.py` | 新建,12步幂等冒烟脚本 |
| `README.md` | 新增"角色权限表"节 |
| `.clinerules` 5.5 | 新增 yearly_targets 写权限收紧说明 |
| `logs/phase2_log.md` | 本章记录 |
### 12.3 权限矩阵最终状态
| 接口 | zhipianren | zebian | biandao |
|------|-----------|--------|---------|
| GET 列表/详情 | ✅ 200 | ✅ 200 | ✅ 200 |
| POST 新增 | ✅ 201 | ✅ 201 | ❌ 403 |
| PATCH 修改 | ✅ 200 | ✅ 200 | ❌ 403 |
| DELETE 删除 | ✅ 204 | ❌ 403 | ❌ 403 |
### 12.4 GET 权限修改说明
GET 权限修改原本不在 task brief 范围,是 Plan 阶段自查发现并扩大的范围。
Opus 审 Plan 时同意纳入,理由:权限矩阵整体校正一次做完更合理。
此条在 Opus 审方案 v2 中明确记录,phase2_log 本章也写入此说明,不得糊涂带过。
### 12.5 冒烟实测记录
`backend/scripts/smoke_yearly_targets_auth.py`12步结果:
| 步骤 | 场景 | 预期 | 结果 |
|------|------|------|------|
| 1 | zhipianren POST | 201 | ✅ |
| 2 | zebian POST | 201 | ✅ |
| 3 | biandao POST | 403 | ✅ |
| 4 | 未登录 POST | 401 | ✅ |
| 5 | zhipianren PATCH | 200 | ✅ |
| 6 | zebian PATCH | 200 | ✅ |
| 7 | biandao PATCH | 403 | ✅ |
| 8 | zhipianren DELETE | 204 | ✅ |
| 9 | zebian DELETE | 403 | ✅ |
| 10 | biandao DELETE | 403 | ✅ |
| 11 | biandao GET 列表 | 200 | ✅ |
| 12 | zhipianren GET 列表 | 200 | ✅ |
**12/12 全部通过。**
### 12.6 测试账号保留
冒烟脚本末尾显式打印保留账号(供后续手动清理):
- `test_zebian_smoke` (zebian)
- `test_biandao_smoke` (biandao)
---
## 第十三章 Task 3 前端三界面收官(2026-05-22
**状态:✅ 完工。六条尾巴全闭环,Phase 2 整体收官。**
Task 3 = 仪表盘真数据 + 用户管理 + 责编录入页。本次开局承接 2026-05-21 的"部分完成、未收官"状态(HEAD `1ebb8a6`),清完六条尾巴正式收官。
### 13.1 今日 commit 链
| commit | 内容 |
|--------|------|
| `4946ab2` | fix: AppLayout 未挂载——路由改两层 Outlet 嵌套,AuthGuard 不再包裹 AppLayout |
| `ebcb530` | fix: episodes GET 放开三角色,写权限仍仅 zhipianren/zebian |
| `c7296a5` | style: 柱图三色换降饱和柔和版(柔蓝/柔绿/柔红),图例同步 |
| `5839fc6` | chore: .gitignore 新增 cookies*.txt 忽略规则 |
(昨日另有 `bfcaddf` CSS 改动,非侧边栏病根但保留无害。)
### 13.2 六条尾巴收官情况
1. **侧边栏死穴(最硬)**simonkoson 登录后整个侧边栏不渲染。连追五轮才定位真因——**根因不是 CSS(前两轮误判 fixed/flex),是 AuthGuard 被当包裹组件用却内部只渲染 `<Outlet/>`、丢弃了 children `<AppLayout/>`**。改为路由两层 Outlet 嵌套(AuthGuard 独立守卫层 + AppLayout 独立 layout route)。三账号实测侧边栏 + 角色过滤全对(zhipianren 见用户管理、zebian 见责编录入不见用户管理、biandao 两者都不见)。
2. **episodes GET 放开三角色**:biandao 仪表盘从空变有数据;反向验证 biandao 发 POST/PATCH/DELETE 全 403。
3. **柱图配色**:见 13.4 色板固化。
4. **EditorDesk 责编录入**:**实测发现三界面本就完整**(三 Tab + 列表 CRUD + 表单字段齐全,含 audience_share 输入框),昨日"表单不全 / 找不到 audience_share"系侧边栏不通、进不去页面时凭印象误记。四步实测全过,无需改码:
- 新增期次能存(期次 8888 / 份额 0.95 / 编导张颖)
- 编导姓名快照必填、留空不能存(符合软引用红线:editor_id 可空、editor_name_snapshot 必填)
- 年度目标只增不改:2028 能存、2026 重复被拒(year UNIQUE 生效,旧值不被覆盖)
- 三色极端数据实测通过
5. **cookies 进 .gitignore**:5 个测试 cookie 文件忽略,工作区干净。
6. **极端数据三色实测**:造 0.95→柔红、0.5→柔绿、0.71/0.72→柔蓝,三色同框,颜色逻辑 + 配色双验。
### 13.3 侧边栏一役复盘(重点教训)
| 轮次 | Cline 判定的"根因" | 实际 | 结果 |
|------|-------------------|------|------|
| 1 | AuthGuard user===null 返回 null,整页空白 | 与现象矛盾(仪表盘明明渲染了) | 打回 |
| 2 | CSS `.app-sider` fixed 塌成 0 宽 | 猜测,未看真实 CSS | 打回 |
| 3 | 改 fixed 为 flex | 改了但浏览器仍不显示 | 非病根 |
| 4 | 制片人 F12 看真实 DOM | `#root` 下直接是 `.dashboard`,无 AppLayout 层 | 锁定方向 |
| 5 | AuthGuard 不渲染 children 却被当包裹组件用 | 真因,两段代码 + DOM 三者咬合 | 修复成功 |
教训沉淀为下方纪律 4。
### 13.4 柱图三色色板固化(制片人拍板)
**降饱和柔和版**:达标 = 柔蓝 `#5b8db8` / 待提升 = 柔绿 `#7aa874` / 优秀 = 柔红 `#c0584f`
原则:①降饱和度,贴合奶油底 `#fbf9f1` + 深绿主色 `#6b8e6b` 整体柔和基调,不用高饱和纯色;②待提升的绿须与主色深绿拉开(更浅更嫩),且与达标蓝有明显色相区分,杜绝"达标待提升一个色系"。
语义映射仍按 `.clinerules` 5.1:红 = 优秀 / 蓝 = 达标 / 绿 = 待提升。
**教训**:此色板此前仅口头拍板未入档,导致 Cline 重新生成时配错(达标套了主色深绿、待提升用土黄凑数)。**凡视觉拍板必入 log 固化**。
### 13.5 纪律 4:根因未经代码 / DOM 实证,不许动手修
侧边栏一役 Cline 连续三轮在猜测的根因上改代码(loading 态 → CSS fixed → CSS flex),均非病根。最终靠制片人 F12 看真实 DOM + 逼 Cline 贴真实代码才定位。
**落地规矩**Cline 报告"根因是 X"时,必须要求它贴出支撑该结论的真实代码 / DOM 证据;只要它在用"可能 / 也许 / 应该",就是在猜,打回。
---
## 第十四章 Phase 2 整体收官(2026-05-22
- Task 1 后端 CRUD:✅(冒烟 6/6
- Task 2 Excel 批量导入:✅(冒烟 8/8)
- Task 2 收尾 + yearly_targets 写权限收紧:✅(冒烟 12/12)
- Task 3 前端三界面:✅(六尾巴全闭环)
- **Phase 2 整体收官**,打 tag `phase2-dashboard-done`
- 收官 commit`5839fc6`working tree clean
### 14.1 跨 Phase 遗留(带入后续)
- **题图上传 Task(独立小 Task,下一个起手)**:①题图 banner 化;②仪表盘布局重排——题图做全宽 banner、近 9 期收视紧贴其下做全宽横条(不再挤在中间小格)。**走 Claude Design 流程**Design 出可交互原型 → 制片人定稿 → Opus 翻译成色值 + antd 组件名规格 → 喂 MiniMax**不直接把图甩给 Cline**(见 design_环节备忘.md)。
- **Phase 5 上线前数据清理**:清掉今日测试数据(期次 8888/8889 等、年度目标 2027/2028 等占位),录入真数据。删除走 UI 逐条删或 Cline 写"清业务表保真数据"SQL 经制片人确认后跑。
- backend/ 补 venv;隐患 AImportError 同名)、隐患 BExcelService 返回 Pydantic 化)低优;SECRET_KEY + same_site/https_onlyPhase 5 部署)。
---
*第十三~十四章由 Claude Opus 4.7 协助、MiniMax M2.7 落地,于 2026-05-22 Task 3 收官后追加。*