From 9f5802d63407dfcff0a95d5b690e13f3dbba03d7 Mon Sep 17 00:00:00 2001 From: wangxiaogang <1433729587@qq.com> Date: Wed, 10 Jun 2026 15:59:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D3=E4=B8=AA=E9=AB=98?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7Bug=20+=205=E4=B8=AA=E4=B8=AD?= =?UTF-8?q?=E4=BD=8E=E4=BC=98=E5=85=88=E7=BA=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BUG-01: recurring /sync next_date 更新逻辑错误(应用循环后的 next) BUG-02: bills onReachBottom loading 缺 .value BUG-03: group store switchToPersonal 缺少 await (3处) OPT-04: notification requireNotificationEditAuth 加 try-catch OPT-05: category 迁移源分类条件改为 is_custom=1 AND user_id=? OPT-06: user 旧头像删除加 try-catch + 日志 CODE-02: format.ts 跨年日期显示年份 CODE-05: notification isValidUrl 去掉相对路径允许 --- client/src/pages/bills/index.vue | 2 +- client/src/stores/group.ts | 6 ++-- client/src/utils/format.ts | 6 +++- server/src/routes/category.ts | 4 +-- server/src/routes/notification.ts | 59 +++++++++++++++++-------------- server/src/routes/recurring.ts | 15 ++------ server/src/routes/user.ts | 6 +++- 7 files changed, 50 insertions(+), 48 deletions(-) diff --git a/client/src/pages/bills/index.vue b/client/src/pages/bills/index.vue index e833eeb..9e53571 100644 --- a/client/src/pages/bills/index.vue +++ b/client/src/pages/bills/index.vue @@ -242,7 +242,7 @@ async function onDelete(item: any) { // 触底加载更多 onReachBottom(() => { - if (noMore.value || loading) return + if (noMore.value || loading.value) return page.value++ loadTx(false) }) diff --git a/client/src/stores/group.ts b/client/src/stores/group.ts index 4b9e54d..3bb761c 100644 --- a/client/src/stores/group.ts +++ b/client/src/stores/group.ts @@ -55,7 +55,7 @@ export const useGroupStore = defineStore('group', () => { if (currentGroupId.value !== null) { const stillValid = groups.value.some(g => g.id === currentGroupId.value) if (!stillValid) { - switchToPersonal() + await switchToPersonal() } } } catch { @@ -83,7 +83,7 @@ export const useGroupStore = defineStore('group', () => { async function leaveGroup(groupId: number) { await api.leaveGroup(groupId) if (currentGroupId.value === groupId) { - switchToPersonal() + await switchToPersonal() } await fetchGroups() } @@ -92,7 +92,7 @@ export const useGroupStore = defineStore('group', () => { async function deleteGroup(groupId: number) { await api.deleteGroup(groupId) if (currentGroupId.value === groupId) { - switchToPersonal() + await switchToPersonal() } await fetchGroups() } diff --git a/client/src/utils/format.ts b/client/src/utils/format.ts index e505f7d..c3e1a6e 100644 --- a/client/src/utils/format.ts +++ b/client/src/utils/format.ts @@ -36,7 +36,11 @@ export function formatDate(date: string | Date): string { if (diff === 1) return '昨天' if (diff === 2) return '前天' - return `${d.getMonth() + 1}月${d.getDate()}日` + // 跨年显示年份 + const isCurrentYear = d.getFullYear() === now.getFullYear() + return isCurrentYear + ? `${d.getMonth() + 1}月${d.getDate()}日` + : `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日` } export function formatMonth(date: string): string { diff --git a/server/src/routes/category.ts b/server/src/routes/category.ts index 95656a9..28a5fd4 100644 --- a/server/src/routes/category.ts +++ b/server/src/routes/category.ts @@ -124,9 +124,9 @@ router.post('/:id/migrate', async (req: AuthRequest, res: Response) => { await conn.beginTransaction() - // 验证源分类存在且属于当前用户 + // 验证源分类存在且属于当前用户(仅允许迁移自定义分类) const [sourceRows] = await conn.query( - 'SELECT id, type FROM categories WHERE id = ? AND (user_id = 0 OR user_id = ?)', + 'SELECT id, type FROM categories WHERE id = ? AND is_custom = 1 AND user_id = ?', [req.params.id, req.userId] ) const sourceCategory = (sourceRows as any[])[0] diff --git a/server/src/routes/notification.ts b/server/src/routes/notification.ts index 1da188e..d31d848 100644 --- a/server/src/routes/notification.ts +++ b/server/src/routes/notification.ts @@ -46,35 +46,40 @@ const upload = multer({ /** 公告编辑权限检查(管理员可编辑所有公告,群主可编辑自己群组的公告) */ async function requireNotificationEditAuth(req: AuthRequest, res: Response, next: NextFunction) { - const [rows] = await pool.query( - 'SELECT type, group_id FROM notifications WHERE id = ?', - [req.params.id] - ) - const notification = (rows as any[])[0] - if (!notification) { - return res.status(404).json({ code: 40400, message: '公告不存在' }) - } - - // 检查是否是管理员 - const [userRows] = await pool.query('SELECT role FROM users WHERE id = ?', [req.userId]) - const user = (userRows as any[])[0] - if (user && user.role === 'admin') { - return next() - } - - // 群组公告:检查是否是群主 - if (notification.type === 'group' && notification.group_id) { - const [groupRows] = await pool.query( - 'SELECT created_by FROM `groups` WHERE id = ?', - [notification.group_id] + try { + const [rows] = await pool.query( + 'SELECT type, group_id FROM notifications WHERE id = ?', + [req.params.id] ) - const group = (groupRows as any[])[0] - if (group && group.created_by === req.userId) { + const notification = (rows as any[])[0] + if (!notification) { + return res.status(404).json({ code: 40400, message: '公告不存在' }) + } + + // 检查是否是管理员 + const [userRows] = await pool.query('SELECT role FROM users WHERE id = ?', [req.userId]) + const user = (userRows as any[])[0] + if (user && user.role === 'admin') { return next() } - } - return res.status(403).json({ code: 40300, message: '需要管理员或群主权限' }) + // 群组公告:检查是否是群主 + if (notification.type === 'group' && notification.group_id) { + const [groupRows] = await pool.query( + 'SELECT created_by FROM `groups` WHERE id = ?', + [notification.group_id] + ) + const group = (groupRows as any[])[0] + if (group && group.created_by === req.userId) { + return next() + } + } + + return res.status(403).json({ code: 40300, message: '需要管理员或群主权限' }) + } catch (err) { + console.error('[Notification] editAuth error:', err) + return res.status(500).json({ code: 50000, message: '服务器错误' }) + } } /** 获取通知列表 */ @@ -283,8 +288,8 @@ function isValidUrl(url: string): boolean { const parsed = new URL(url) return ['http:', 'https:'].includes(parsed.protocol) } catch { - // 相对路径也允许 - return url.startsWith('/') || url.startsWith('./') + // 非标准 URL 格式,小程序不支持相对路径,拒绝 + return false } } diff --git a/server/src/routes/recurring.ts b/server/src/routes/recurring.ts index e9d51f1..562574c 100644 --- a/server/src/routes/recurring.ts +++ b/server/src/routes/recurring.ts @@ -177,21 +177,10 @@ router.post('/sync', async (req, res) => { } } - // 更新 next_date 到下一周期 - let newNext: Date - if (t.frequency === 'weekly') { - newNext = new Date(new Date(t.next_date).getTime() + 7 * 86400000) - } else if (t.frequency === 'monthly') { - const d = new Date(t.next_date) - newNext = new Date(d.getFullYear(), d.getMonth() + 1, d.getDate()) - } else { - const d = new Date(t.next_date) - newNext = new Date(d.getFullYear() + 1, d.getMonth(), d.getDate()) - } - + // 更新 next_date:循环结束后 next 已推进到今天之后的下一周期,直接使用 await pool.execute( 'UPDATE recurring_templates SET next_date = ? WHERE id = ?', - [newNext.toISOString().slice(0, 10), t.id] + [next.toISOString().slice(0, 10), t.id] ) } diff --git a/server/src/routes/user.ts b/server/src/routes/user.ts index 75dab80..1a4c117 100644 --- a/server/src/routes/user.ts +++ b/server/src/routes/user.ts @@ -132,7 +132,11 @@ router.post('/avatar', (req: AuthRequest, res: Response) => { if (oldUrl) { const oldPath = path.join(AVATAR_DIR, oldUrl) if (fs.existsSync(oldPath)) { - fs.unlinkSync(oldPath) + try { + fs.unlinkSync(oldPath) + } catch (e) { + console.warn('[User] 删除旧头像失败:', e) + } } }