From a35689cdda973064fde4fbe7c43e5bd1c0a41af4 Mon Sep 17 00:00:00 2001 From: wangxiaogang Date: Sat, 6 Jun 2026 22:02:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=A8=E9=9D=A2=E5=85=AC=E5=91=8A?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=20+=20=E5=85=A8=E7=B3=BB=E7=BB=9F=20Bug=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全面公告系统: - notifications 表新增置顶/强提醒/定时发布/过期/配图/链接字段 - 后端增强:7 个接口(列表/置顶/未读数/已读/发布/编辑/删除) - 首页强提醒公告弹窗 - 通知中心:置顶标记、配图展示、链接跳转、管理员操作 - 管理员发布表单:标题+内容+链接+配图+置顶+强提醒+定时+过期 Bug 修复: - 401 重试队列竞态导致请求永久挂起 - 多分类筛选条件被静默丢弃 - 网络错误导致群组选择丢失 - Numpad 输入 '.' 显示异常 - transaction store 错误时清空数据 - 首页/预算页 Promise.all 部分失败阻塞关键数据 - CSS prefers-reduced-motion 类名不匹配 - 通知 read-all 遗漏群组通知 - auth.ts 错误日志缺少 stack trace - stats 页面 v-for key 使用 index - add 页面重复调用 fetchCategories --- client/src/api/notification.ts | 41 ++- client/src/components/Numpad/Numpad.vue | 2 +- client/src/pages/add/index.vue | 4 +- client/src/pages/admin/index.vue | 355 +++++++++++++++++++++-- client/src/pages/bills/filter-panel.vue | 321 +++++++++++++------- client/src/pages/bills/index.vue | 2 +- client/src/pages/budget/index.vue | 23 +- client/src/pages/index/index.vue | 134 ++++++++- client/src/pages/notifications/index.vue | 126 +++++++- client/src/pages/profile/index.vue | 2 +- client/src/pages/stats/index.vue | 7 +- client/src/stores/group.ts | 18 +- client/src/stores/notification.ts | 12 +- client/src/stores/transaction.ts | 3 +- client/src/utils/request.ts | 11 +- server/src/db/init.ts | 14 + server/src/db/schema.sql | 10 +- server/src/routes/auth.ts | 4 +- server/src/routes/notification.ts | 160 ++++++++-- server/src/routes/transaction.ts | 11 +- 20 files changed, 1058 insertions(+), 202 deletions(-) diff --git a/client/src/api/notification.ts b/client/src/api/notification.ts index d4542f4..f8d9f51 100644 --- a/client/src/api/notification.ts +++ b/client/src/api/notification.ts @@ -9,6 +9,12 @@ export interface Notification { is_read: number created_at: string group_id?: number | null + is_pinned?: number + is_urgent?: number + publish_at?: string | null + expire_at?: string | null + image_url?: string + link_url?: string } /** 通知列表响应 */ @@ -19,11 +25,30 @@ export interface NotificationList { pageSize: number } +/** 发布公告参数 */ +export interface PublishParams { + title: string + content?: string + type?: 'system' | 'group' | 'personal' + group_id?: number + is_pinned?: boolean + is_urgent?: boolean + publish_at?: string + expire_at?: string + image_url?: string + link_url?: string +} + /** 获取通知列表 */ export function getNotifications(params?: { type?: string; page?: number; pageSize?: number }) { return request({ url: '/notifications', data: params }) } +/** 获取置顶/强提醒公告 */ +export function getPinnedNotifications() { + return request({ url: '/notifications/pinned' }) +} + /** 获取未读数量 */ export function getUnreadCount() { return request<{ count: number }>({ url: '/notifications/unread-count' }) @@ -39,7 +64,17 @@ export function markAllRead() { return request({ url: '/notifications/read-all', method: 'PUT' }) } -/** 发布系统公告(管理员) */ -export function publishNotification(data: { title: string; content?: string }) { - return request({ url: '/notifications', method: 'POST', data }) +/** 发布公告 */ +export function publishNotification(data: PublishParams) { + return request<{ id: number }>({ url: '/notifications', method: 'POST', data }) +} + +/** 编辑公告 */ +export function updateNotification(id: number, data: Partial) { + return request({ url: `/notifications/${id}`, method: 'PUT', data }) +} + +/** 删除公告 */ +export function deleteNotification(id: number) { + return request({ url: `/notifications/${id}`, method: 'DELETE' }) } diff --git a/client/src/components/Numpad/Numpad.vue b/client/src/components/Numpad/Numpad.vue index 937cac6..588397d 100644 --- a/client/src/components/Numpad/Numpad.vue +++ b/client/src/components/Numpad/Numpad.vue @@ -52,7 +52,7 @@ function onKeyTap(key: string) { if (key === '.') { if (current.includes('.')) return - if (!current) { emitValue('0.'); return } + if (!current || current === '0') { emitValue('0.'); return } } let next: string diff --git a/client/src/pages/add/index.vue b/client/src/pages/add/index.vue index def4e9d..37aed76 100644 --- a/client/src/pages/add/index.vue +++ b/client/src/pages/add/index.vue @@ -149,11 +149,13 @@ onMounted(async () => { if (currentCategories.value.length > 0 && !selectedCat.value) { selectedCat.value = currentCategories.value[0].id } + initialLoaded.value = true }) // 返回页面时刷新分类(可能新增了分类) +const initialLoaded = ref(false) onShow(() => { - catStore.fetchCategories() + if (initialLoaded.value) catStore.fetchCategories() }) function onDateChange(e: any) { diff --git a/client/src/pages/admin/index.vue b/client/src/pages/admin/index.vue index 8f6c264..6f4a4b9 100644 --- a/client/src/pages/admin/index.vue +++ b/client/src/pages/admin/index.vue @@ -70,6 +70,74 @@ + + + + + + 发布公告 + + × + + + + + 标题 * + + + + 内容 +