diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx
index deb9995..bebc481 100644
--- a/apps/admin/src/App.tsx
+++ b/apps/admin/src/App.tsx
@@ -232,13 +232,7 @@ const App: React.FC = () => {
}
/>
-
-
-
- }>
- } />
-
+ } />
diff --git a/apps/admin/src/components/NotificationBell.tsx b/apps/admin/src/components/NotificationBell.tsx
index 87d7e69..8ca53e3 100644
--- a/apps/admin/src/components/NotificationBell.tsx
+++ b/apps/admin/src/components/NotificationBell.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import { Badge, Popover, Button, List, Typography, Empty } from 'antd';
import { BellOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
@@ -56,7 +56,10 @@ const NotificationBell: React.FC = () => {
setUnreadCount(data.count);
} catch { /* ignore */ }
};
+ const openRef = useRef(open);
+ openRef.current = open;
+ // SSE connection — decoupled from popover open state
useEffect(() => {
fetchUnread();
const token = localStorage.getItem('token');
@@ -66,16 +69,15 @@ const NotificationBell: React.FC = () => {
try {
JSON.parse(event.data);
setUnreadCount((c) => c + 1);
- if (open) fetchNotifications();
+ if (openRef.current) fetchNotifications();
} catch { /* ignore */ }
};
es.onerror = () => {
es.close();
- const interval = setInterval(fetchUnread, 60_000);
- return () => clearInterval(interval);
+ setInterval(fetchUnread, 60_000);
};
return () => es.close();
- }, [open]);
+ }, []);
const handleOpen = (visible: boolean) => {
setOpen(visible);
diff --git a/apps/admin/src/hooks/useNotifications.ts b/apps/admin/src/hooks/useNotifications.ts
index deb3e85..cc2dd7c 100644
--- a/apps/admin/src/hooks/useNotifications.ts
+++ b/apps/admin/src/hooks/useNotifications.ts
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback } from 'react';
+import { useState, useEffect, useCallback, useRef } from 'react';
import api from '../api';
interface Notification {
@@ -41,16 +41,18 @@ export function useNotifications() {
}
};
+ const pollRef = { current: undefined as number | undefined };
+
es.onerror = () => {
es.close();
- const interval = setInterval(() => {
+ pollRef.current = setInterval(() => {
fetchUnreadCount();
}, 60_000);
- return () => clearInterval(interval);
};
return () => {
es.close();
+ if (pollRef.current !== undefined) clearInterval(pollRef.current);
};
}, [fetchUnreadCount]);
diff --git a/apps/server/src/notifications/notifications.controller.ts b/apps/server/src/notifications/notifications.controller.ts
index 82213c4..c866b80 100644
--- a/apps/server/src/notifications/notifications.controller.ts
+++ b/apps/server/src/notifications/notifications.controller.ts
@@ -48,6 +48,9 @@ export class NotificationsController {
@Sse('stream')
stream(@Req() req: AuthenticatedRequest): Observable {
const userId = req.user.id;
+ req.on('close', () => {
+ this.service.unsubscribe(userId);
+ });
return this.service.subscribe(userId).pipe(
map((notification) => ({
data: JSON.stringify({
diff --git a/apps/server/src/schedules/schedules.controller.ts b/apps/server/src/schedules/schedules.controller.ts
index 3922305..a96cede 100644
--- a/apps/server/src/schedules/schedules.controller.ts
+++ b/apps/server/src/schedules/schedules.controller.ts
@@ -108,6 +108,7 @@ export class SchedulesController {
@Request() req: { user?: { id: number; username: string }; headers?: Record },
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
+ const existing = await this.service.findOne(+id);
try {
const result = await this.service.update(+id, dto);
await this.logService.log({
@@ -126,7 +127,7 @@ export class SchedulesController {
if (error instanceof ConflictException) {
try {
const conflicts = await this.service.checkConflict(
- dto.classroomId ?? 0, dto.weekDay ?? 0, dto.startTime ?? '', dto.endTime ?? '', dto.startDate ?? '', dto.endDate ?? '',
+ existing.classroomId, existing.weekDay, existing.startTime, existing.endTime, existing.startDate, existing.endDate,
);
const teacherIds = [...new Set(conflicts.map(c => c.teacherId).filter(Boolean))];
if (teacherIds.length > 0) {
@@ -134,7 +135,7 @@ export class SchedulesController {
recipientIds: teacherIds,
type: NotificationType.SCHEDULE_CONFLICT,
title: '排课冲突',
- content: `教室${dto.classroomId} 周${dto.weekDay} ${dto.startTime}-${dto.endTime} (更新) 与已有排课冲突`,
+ content: `教室${existing.classroomId} 周${existing.weekDay} ${existing.startTime}-${existing.endTime} (更新) 与已有排课冲突`,
});
}
} catch {}