fix: 全面修复 22 项问题

严重 Bug:
- NaN 金额校验绕过(add 页面 parseFloat 空字符串)
- 4 个页面补充 waitForReady(add/budget/category-manage/group-manage)
- 导出 Loading 遮罩不消失(profile exportData 缺 hideLoading)
- Store 吞掉请求错误不 re-throw(transaction/stats/budget/user)
- 分类排序 SQL 拼接改参数化查询(category PUT /sort)
- 备份接口添加管理员权限检查(backup requireAdmin)

中等问题:
- 群组加入竞态条件改为 INSERT IGNORE
- 统计页切月/切Tab添加 loadSeq 防竞态
- stats store 补全 loading 状态 + Overview 初始值补全 7 字段
- 保存成功后返回不再弹「放弃修改」确认框
- 群组管理创建/加入添加 saving 双重提交保护
- 预算页添加 onShow 刷新
- 服务端输入校验(name/color/invite_code/微信code)

UI/样式:
- 触摸目标: bell/nav-back/nav-save→88rpx, avatar-badge→64rpx, action-btn→80rpx
- :active 反馈: bell/profile-card/identity-card/id-manage/arrow/action-btn/group-btn/cat-info
- 内联样式改动态绑定(4处 spacer 使用 capsuleRight)
- FAB 文字+改用 Icon 组件
- prefers-reduced-motion 动画降级(Skeleton/profile/budget/add)
- 胶囊适配(add/category-manage/group-manage)
- CategoryIcon !important 改为 iconStyle 计算属性

文档:
- CLAUDE.md 补充 SaveSuccess/ChartWrapper 组件 + category/budget/index API
This commit is contained in:
2026-06-04 17:06:02 +08:00
parent 21f4d81959
commit e110cd5c5d
25 changed files with 314 additions and 88 deletions

View File

@@ -129,8 +129,8 @@ router.get('/:id', async (req: AuthRequest, res: Response) => {
router.post('/join', async (req: AuthRequest, res: Response) => {
try {
const { invite_code } = req.body
if (!invite_code || typeof invite_code !== 'string') {
return res.status(400).json({ code: 40001, message: '邀请码不能为空' })
if (!invite_code || typeof invite_code !== 'string' || invite_code.length > 10) {
return res.status(400).json({ code: 40001, message: '邀请码无效' })
}
const [groupRows] = await pool.query(
@@ -142,19 +142,14 @@ router.post('/join', async (req: AuthRequest, res: Response) => {
return res.status(404).json({ code: 40400, message: '邀请码无效' })
}
// 检查是否已是成员
const [existing] = await pool.query(
'SELECT id FROM group_members WHERE group_id = ? AND user_id = ?',
[group.id, req.userId]
)
if ((existing as any[]).length > 0) {
return res.status(400).json({ code: 40002, message: '你已经是该群组成员' })
}
await pool.query(
'INSERT INTO group_members (group_id, user_id, role) VALUES (?, ?, ?)',
// 使用 INSERT IGNORE 避免并发加入时的竞态条件
const [result] = await pool.query(
'INSERT IGNORE INTO group_members (group_id, user_id, role) VALUES (?, ?, ?)',
[group.id, req.userId, 'member']
)
if ((result as any).affectedRows === 0) {
return res.status(400).json({ code: 40002, message: '你已经是该群组成员' })
}
res.json({ code: 0, data: { group_id: group.id, name: group.name } })
} catch (err) {