feat: 全面系统检查与修复 — 51项问题修复

## 重大修复

### 安全性修复
- 修复 notification read-all SQL 运算符优先级 bug
- 修复 link_url XSS 漏洞(验证 URL 协议)
- 修复 LIKE 通配符注入(转义 % 和 _)
- 修复 uploadImageIfNeeded 运算符优先级 bug
- 401 时清理所有本地存储(token/nickname/avatar/role/group)

### 数据完整性修复
- 修复公告已读状态共享问题(新建 notification_reads 表)
- 修复群组账单数据缺失(退出群组时保留 group_id)
- 修复群组解散后邀请码仍可加入
- 修复分类迁移未校验目标类型
- 修复群组公告缺少 group_id 必填校验

### 功能修复
- 修复 category PUT /sort 路由冲突
- 修复 GROUP BY 不完整问题
- 修复 budget API 类型不匹配
- 修复 categoryStore.migrateCategory 不刷新本地数据
- 修复 groupStore 并发请求问题
- 修复账单页覆盖 store 数据
- 修复群组预算查询返回 0 而非 null
- 修复通知页面 onShow 不刷新列表
- 修复统计页面不必要的重复请求

### 用户体验优化
- 添加通知详情查看功能(弹窗)
- 添加通知图片服务器上传
- 添加 Markdown 富文本工具栏
- 添加管理页面客户端认证检查
- 添加管理员公告页面下拉刷新
- 添加数据导出进度反馈
- 添加账单删除后筛选金额更新
- Numpad 添加安全区域 padding

### 代码质量提升
- 提取 requireAdmin 为共享中间件
- filter-panel 使用设计 token
- 修复 getCurrentMonth 时区不一致
- 备份功能使用分页查询避免内存问题
- 管理后台仪表盘添加缓存
- 邀请码碰撞重试后报错

## 新增文件
- server/src/middleware/requireAdmin.ts — 共享管理员权限中间件
- client/src/pages/admin/notifications/index.vue — 公告管理页面

## 数据库变更
- 新增 notification_reads 表(公告已读记录)
- 群组解散时保留 groups 记录和 transactions.group_id
This commit is contained in:
wangxiaogang
2026-06-07 15:35:37 +08:00
parent 4f42f2c342
commit bd7af8e512
35 changed files with 1683 additions and 759 deletions

View File

@@ -90,40 +90,42 @@ function onConfirm() {
</script>
<style lang="scss" scoped>
@import '@/styles/mixins.scss';
.numpad {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
padding: 0 32rpx;
gap: $space-sm;
padding: 0 $space-xl;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.key {
height: 96rpx;
border-radius: 24rpx;
background: #FFF8F0;
display: flex;
align-items: center;
justify-content: center;
border-radius: $radius-xl;
background: $bg;
@include flex-center;
&:active { background: #F0E0D6; }
&:active { background: $border; }
}
.key-text {
font-family: 'Fredoka', sans-serif;
font-size: 44rpx;
font-weight: 500;
color: #2D1B1B;
color: $text;
}
.key.fn {
background: #FFE8E0;
background: $primary-light;
&:active { background: #FFD0C0; }
}
.key.eq {
background: linear-gradient(135deg, #FF8C69, #E67355);
background: linear-gradient(135deg, $primary, $primary-dark);
box-shadow: 0 4rpx 16rpx rgba(255, 140, 105, 0.3);
&:active { background: #E67355; }
&:active { background: $primary-dark; }
}
// 自绘 backspace 图标
@@ -139,8 +141,8 @@ function onConfirm() {
top: 50%;
width: 16rpx;
height: 16rpx;
border-left: 4rpx solid #FF8C69;
border-bottom: 4rpx solid #FF8C69;
border-left: 4rpx solid $primary;
border-bottom: 4rpx solid $primary;
transform: translateY(-50%) rotate(45deg);
}
@@ -150,7 +152,7 @@ function onConfirm() {
top: 50%;
width: 26rpx;
height: 4rpx;
background: #FF8C69;
background: $primary;
transform: translateY(-50%);
border-radius: 2rpx;
}

View File

@@ -73,7 +73,7 @@ defineEmits(['item-tap', 'delete'])
const creatorAvatarUrl = computed(() => {
if (!props.item.creator_avatar) return ''
if (props.item.creator_avatar.startsWith('http')) return props.item.creator_avatar
if (props.item.creator_avatar.startsWith('http') || props.item.creator_avatar.startsWith('blob:')) return props.item.creator_avatar
// 截取服务端根地址(去掉 /api
const base = API_BASE.replace(/\/api$/, '')
return base + '/api/user/avatar/' + props.item.creator_avatar