feat(web): 首页换成和工作区同一套外壳,补上账号设置页
CI / backend (push) Canceled after 0s

首页原来是居中窄栏,点进工作区又变全屏,进出像换了个网站。现在把首页也做成
全宽 + 左侧栏(SideRail 抽出来给首页和账号页共用),工作区卡片改成网格,
新建工作区是网格里的虚线卡片——宽度不再跳。

账号页 /account:基本信息(头像首字母、昵称、只读手机号、加入时间)、
登录密码(设置/修改 + 确认输入)、退出登录。工作区侧栏底部的用户块点进去就是它。

e2e account.mjs 14 项全绿,其中一条断言进工作区后侧栏宽度和首页差值 < 1px。
This commit is contained in:
Steven
2026-10-31 04:05:05 +08:00
parent a5510cdb79
commit 096acb00e7
11 changed files with 799 additions and 122 deletions
+47 -1
View File
@@ -58,7 +58,8 @@
- [x] 手机号 + 验证码注册/登录(dev 固定码 `123456` 并回显;`ENV=prod` 禁止 `SMS_PROVIDER=dev`)
- [x] JWT:**access 30min / refresh 14d**(计划里写的 15min/30d,实际取宽了一点,理由是移动端弱网刷新成本高)
- [x] 发码节流(`SMS_THROTTLE_SECONDS`,默认 60s,测试可置 0)
- [ ] 邮箱 + 密码(argon2)(**未做**,M1 有空再说;手机号登录先行)
- [x] 手机号 + **密码**登录(可选,第二把钥匙:设过密码就能用密码进,没设过的一律回 `2013`;scrypt 哈希,15 分钟连错 10 次 → `2016`)
- [ ] 邮箱 + 密码(**未做**,M1 有空再说;手机号登录先行)
- [ ] refresh 令牌落库可撤销(**未做**,当前是无状态 JWT;被移出团队的成员靠"每次请求查 member 表"兜底,refresh 本身不可撤销)
- **验收**:✅ 注册 → 登录 → 刷新 → `/auth/me` 全链路通;错误码 2010/2011/2012 语义清晰
@@ -450,3 +451,48 @@
- [x] 本地 `GET /api/v1/app/release/latest?version_code=1` → `has_update=true`、`download_url` 可下(200,7 762 912 字节),`version_code=2` → `false`
- [x] 生产(`http://192.168.8.188:8200`)同上,安装包已随部署上传
- [x] 模拟器上走通全流程:装 debug 包 → 登录 → 启动即弹「有新版」→ 下载 7 762 912 字节(与清单 `size` 一致,`.part` 已改名)→ 无安装权限时提示去开 → 拉系统安装器「是否更新此应用」→ 装完 `versionCode=2 / versionName=0.2.0`;截图 `2.Design/screens/android-update-{dialog,permission,installer}.png` 与 `android-home-check-update.png`
---
## 7. 账号与外壳统一(Web,2026-10-31)
### 7.1 两个问题(用户提的)
1. 首页是"居中窄栏",点进工作区却变成全屏——同一套产品两种壳,进出像换了个网站。
2. 用户相关功能缺失:没有基本信息、没有改密码、没有一个统一的"退出登录"入口。
### 7.2 决定
- **外壳统一到全宽 + 左侧栏**(不是把工作区收回窄栏):工作区里本来就要放下看板四列、知识分类树,
收窄是倒退。首页跟着做同样的壳,宽度就不再跳。
- **加密码登录,作为验证码之外的第二条路**。理由:验证码依赖短信通道,通道挂了人就进不来;
密码是自助的第二把钥匙。但**不强制**:没设密码的人照旧只用验证码,少一道门槛。
- 外壳抽成 `web/src/components/SideRail.tsx`,首页与账号页共用;工作区的 `AppShell` 保持自己的侧栏(有工作区切换与页签)。
两处宽度一致(`w-[248px]` 级别的同一份样式),所以进出不跳。
### 7.3 后端
- `app_user.password_hash`(迁移 `0008_user_password`,加列不加表)
- `app/security.py`:scrypt(`n=2^14, r=8, p=1`,标准库 `hashlib`),存 `scrypt$n$r$p$salt$hash`;
`password_problem()` 一处定义强度(8–64 位、字母 + 数字)
- `POST /auth/login/password`、`POST /auth/password`(设/改共用,改要先验旧)、`PATCH /auth/me`(改昵称)
- 用户输出统一走 `_user_out()`:**白名单字段**,`password_hash` 一个字节都不出去;对外只给 `has_password`
- 错误码:`2013` 密码错/没设过、`2014` 未设密码(保留)、`2015` 太弱、`2016` 连错太多
### 7.4 Web
- 首页:左侧栏 + 全宽内容区,工作区从列表改成卡片网格(`sm:2 / xl:3 / 2xl:4` 列),
"新建工作区"是网格里的一张虚线卡片,点开就地展开表单
- 新增 `/account`:基本信息(头像首字母、昵称、只读手机号、加入时间)、登录密码(设置/修改 + 确认输入)、退出登录
- 登录页:顶部「验证码 / 密码」两段切换,同一个表单两种提交
- 工作区 `AppShell` 底部用户块可点进 `/account`,旁边一个齿轮图标
### 7.5 验证
- 后端 `pytest` → **113 passed**(新增 `tests/test_account.py` 10 项);`ruff check` / `ruff format --check` 全绿
- Web `tsc --noEmit` 通过、`vite build` 通过
- 新增 e2e `web/e2e/account.mjs`(**13 项全绿**):登录进首页 → 首页有侧栏 → 进工作区**侧栏宽度不变(差值 < 1px)**
→ 账号页复用同一条侧栏 → 改昵称 → 设/改密码 → 刷新后仍显示"已设置密码" → 退出 →
错密码被拒(`2013`)→ 密码登录成功 → `/auth/me` 里 `has_password=true` 且昵称是刚改的 → 验证码登录仍可用 → 控制台零报错
- 截图:`2.Design/screens/web-home-rail.png`、`web-account-settings.png`
- 脚本里两个坑写进了注释:① 短信 60 秒节流会让"发码→登录"整段失败(先看 429,别怪登录);
② 改密码要先给旧密码,脚本把上一轮的密码记在 `.local/e2e-account-password.txt` 里自己维护
### 7.6 没做的
- 安卓端登录页仍只有验证码,账号页也没有——下一步对齐(密码登录 + 改昵称 + 退出)
- 手机号换绑、头像上传未做(头像现在只有首字母)
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB