diff --git a/web/src/components/layout/NotificationBell.vue b/web/src/components/layout/NotificationBell.vue
index c961f0b..12a4a04 100644
--- a/web/src/components/layout/NotificationBell.vue
+++ b/web/src/components/layout/NotificationBell.vue
@@ -1,7 +1,73 @@
- 🔔
+
+
+
+
+ 🔔
+
+
+
+
+
+ 暂无通知
+
+
+
+ {{ n.title }}
+
+
+ {{ n.body }}
+
+
+
+
+ 全部标记为已读
+
+
+
+
diff --git a/web/src/composables/useNotifications.ts b/web/src/composables/useNotifications.ts
new file mode 100644
index 0000000..0418480
--- /dev/null
+++ b/web/src/composables/useNotifications.ts
@@ -0,0 +1,31 @@
+import { onMounted, onUnmounted, ref } from 'vue'
+import { notificationsApi } from '@/api/notifications'
+import { useNotificationsStore } from '@/stores/notifications'
+
+const POLL_INTERVAL_MS = 30_000
+
+export function useUnreadPoller() {
+ const store = useNotificationsStore()
+ let timer: ReturnType | null = null
+ const error = ref(null)
+
+ async function refresh() {
+ try {
+ const { count } = await notificationsApi.unreadCount()
+ store.setUnreadCount(count)
+ } catch (e) {
+ error.value = String(e)
+ }
+ }
+
+ onMounted(() => {
+ refresh()
+ timer = setInterval(refresh, POLL_INTERVAL_MS)
+ })
+
+ onUnmounted(() => {
+ if (timer) clearInterval(timer)
+ })
+
+ return { refresh, error }
+}