fix: 修复3个高优先级Bug + 5个中低优先级问题

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 去掉相对路径允许
This commit is contained in:
2026-06-10 15:59:12 +08:00
parent f586f9e117
commit 9f5802d634
7 changed files with 50 additions and 48 deletions

View File

@@ -242,7 +242,7 @@ async function onDelete(item: any) {
// 触底加载更多 // 触底加载更多
onReachBottom(() => { onReachBottom(() => {
if (noMore.value || loading) return if (noMore.value || loading.value) return
page.value++ page.value++
loadTx(false) loadTx(false)
}) })

View File

@@ -55,7 +55,7 @@ export const useGroupStore = defineStore('group', () => {
if (currentGroupId.value !== null) { if (currentGroupId.value !== null) {
const stillValid = groups.value.some(g => g.id === currentGroupId.value) const stillValid = groups.value.some(g => g.id === currentGroupId.value)
if (!stillValid) { if (!stillValid) {
switchToPersonal() await switchToPersonal()
} }
} }
} catch { } catch {
@@ -83,7 +83,7 @@ export const useGroupStore = defineStore('group', () => {
async function leaveGroup(groupId: number) { async function leaveGroup(groupId: number) {
await api.leaveGroup(groupId) await api.leaveGroup(groupId)
if (currentGroupId.value === groupId) { if (currentGroupId.value === groupId) {
switchToPersonal() await switchToPersonal()
} }
await fetchGroups() await fetchGroups()
} }
@@ -92,7 +92,7 @@ export const useGroupStore = defineStore('group', () => {
async function deleteGroup(groupId: number) { async function deleteGroup(groupId: number) {
await api.deleteGroup(groupId) await api.deleteGroup(groupId)
if (currentGroupId.value === groupId) { if (currentGroupId.value === groupId) {
switchToPersonal() await switchToPersonal()
} }
await fetchGroups() await fetchGroups()
} }

View File

@@ -36,7 +36,11 @@ export function formatDate(date: string | Date): string {
if (diff === 1) return '昨天' if (diff === 1) return '昨天'
if (diff === 2) 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 { export function formatMonth(date: string): string {

View File

@@ -124,9 +124,9 @@ router.post('/:id/migrate', async (req: AuthRequest, res: Response) => {
await conn.beginTransaction() await conn.beginTransaction()
// 验证源分类存在且属于当前用户 // 验证源分类存在且属于当前用户(仅允许迁移自定义分类)
const [sourceRows] = await conn.query( 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] [req.params.id, req.userId]
) )
const sourceCategory = (sourceRows as any[])[0] const sourceCategory = (sourceRows as any[])[0]

View File

@@ -46,35 +46,40 @@ const upload = multer({
/** 公告编辑权限检查(管理员可编辑所有公告,群主可编辑自己群组的公告) */ /** 公告编辑权限检查(管理员可编辑所有公告,群主可编辑自己群组的公告) */
async function requireNotificationEditAuth(req: AuthRequest, res: Response, next: NextFunction) { async function requireNotificationEditAuth(req: AuthRequest, res: Response, next: NextFunction) {
const [rows] = await pool.query( try {
'SELECT type, group_id FROM notifications WHERE id = ?', const [rows] = await pool.query(
[req.params.id] '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]
) )
const group = (groupRows as any[])[0] const notification = (rows as any[])[0]
if (group && group.created_by === req.userId) { 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 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) const parsed = new URL(url)
return ['http:', 'https:'].includes(parsed.protocol) return ['http:', 'https:'].includes(parsed.protocol)
} catch { } catch {
// 相对路径也允许 // 非标准 URL 格式,小程序不支持相对路径,拒绝
return url.startsWith('/') || url.startsWith('./') return false
} }
} }

View File

@@ -177,21 +177,10 @@ router.post('/sync', async (req, res) => {
} }
} }
// 更新 next_date 到下一周期 // 更新 next_date:循环结束后 next 已推进到今天之后的下一周期,直接使用
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())
}
await pool.execute( await pool.execute(
'UPDATE recurring_templates SET next_date = ? WHERE id = ?', 'UPDATE recurring_templates SET next_date = ? WHERE id = ?',
[newNext.toISOString().slice(0, 10), t.id] [next.toISOString().slice(0, 10), t.id]
) )
} }

View File

@@ -132,7 +132,11 @@ router.post('/avatar', (req: AuthRequest, res: Response) => {
if (oldUrl) { if (oldUrl) {
const oldPath = path.join(AVATAR_DIR, oldUrl) const oldPath = path.join(AVATAR_DIR, oldUrl)
if (fs.existsSync(oldPath)) { if (fs.existsSync(oldPath)) {
fs.unlinkSync(oldPath) try {
fs.unlinkSync(oldPath)
} catch (e) {
console.warn('[User] 删除旧头像失败:', e)
}
} }
} }