diff --git a/app.py b/app.py index b2e640d..4913765 100644 --- a/app.py +++ b/app.py @@ -720,6 +720,21 @@ def api_accept_friend(request_id): if not req or req.friend_id != user_id or req.status != 'pending': return jsonify({'success': False, 'message': '请求不存在或无权操作'}), 404 req.status = 'accepted' + # 自动创建私聊室,让好友立即出现在聊天列表 + friend_user_id = req.user_id + my_rooms = db.session.query(ChatRoomMember.room_id).filter_by(user_id=user_id).subquery() + target_rooms = db.session.query(ChatRoomMember.room_id).filter_by(user_id=friend_user_id).subquery() + existing_room = ChatRoom.query.filter( + ChatRoom.type == 'private', + ChatRoom.id.in_(db.session.query(my_rooms.c.room_id)), + ChatRoom.id.in_(db.session.query(target_rooms.c.room_id)) + ).first() + if not existing_room: + room = ChatRoom(type='private', creator_id=user_id) + db.session.add(room) + db.session.flush() + db.session.add(ChatRoomMember(room_id=room.id, user_id=user_id, role='member')) + db.session.add(ChatRoomMember(room_id=room.id, user_id=friend_user_id, role='member')) db.session.commit() return jsonify({'success': True, 'message': '好友已添加'}) diff --git a/templates/chat.html b/templates/chat.html index e4f3e0d..4b0f2c1 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -332,6 +332,19 @@ document.addEventListener('DOMContentLoaded', () => { if (params.get('room_id')) { setTimeout(() => selectRoom(parseInt(params.get('room_id'))), 500); } + if (params.get('dm')) { + const dmUserId = parseInt(params.get('dm')); + setTimeout(async () => { + try { + const res = await fetch(`/api/chat/private/${dmUserId}`, { method: 'POST' }); + const data = await res.json(); + if (data.success) { + await loadRooms(); + selectRoom(data.room_id); + } + } catch(e) { console.error('打开私聊失败', e); } + }, 500); + } if (params.get('tab') === 'notif') { switchTab('notif'); } diff --git a/templates/notifications.html b/templates/notifications.html index 533dd48..7abd71a 100644 --- a/templates/notifications.html +++ b/templates/notifications.html @@ -27,6 +27,7 @@ + @@ -81,6 +82,7 @@ function getTypeFilter(tab) { if (tab === 'system') return ['system_announcement']; if (tab === 'teacher') return ['teacher_application', 'teacher_result']; if (tab === 'contest') return ['contest_application', 'contest_result', 'contest_new_exam']; + if (tab === 'friend') return ['friend_request']; if (tab === 'exam') return ['exam_graded', 'contest_new_exam']; return null; } @@ -90,7 +92,7 @@ function getTypeIcon(type) { 'teacher_application': '👨‍🏫', 'teacher_result': '🎓', 'contest_application': '🏆', 'contest_result': '🏅', 'contest_new_exam': '📝', 'exam_graded': '✅', - 'system_announcement': '📢' + 'system_announcement': '📢', 'friend_request': '👤' }; return icons[type] || '🔔'; } @@ -100,7 +102,7 @@ function getTypeLabel(type) { 'teacher_application': '教师申请', 'teacher_result': '教师审核结果', 'contest_application': '杯赛申请', 'contest_result': '杯赛通知', 'contest_new_exam': '新考试', 'exam_graded': '成绩通知', - 'system_announcement': '系统公告' + 'system_announcement': '系统公告', 'friend_request': '好友申请' }; return labels[type] || '通知'; } @@ -176,7 +178,7 @@ function renderNotifications() { 'teacher_application': 'bg-purple-100 text-purple-600', 'teacher_result': 'bg-fuchsia-100 text-fuchsia-600', 'contest_application': 'bg-orange-100 text-orange-600', 'contest_result': 'bg-amber-100 text-amber-600', 'contest_new_exam': 'bg-indigo-100 text-indigo-600', 'exam_graded': 'bg-emerald-100 text-emerald-600', - 'system_announcement': 'bg-blue-100 text-blue-600' + 'system_announcement': 'bg-blue-100 text-blue-600', 'friend_request': 'bg-cyan-100 text-cyan-600' }[n.type] || 'bg-slate-100 text-slate-600'; return `
@@ -220,6 +222,15 @@ function buildActions(n) { } if (n.type === 'contest_application' && n.application_status === 'approved') return '
已同意' + (currentUser.role === 'admin' ? `` : '') + '
'; if (n.type === 'contest_application' && n.application_status === 'rejected') return '
已拒绝' + (currentUser.role === 'admin' ? `` : '') + '
'; + + // 好友申请处理 + if (n.type === 'friend_request' && n.application_status === 'pending' && n.friend_request_id) { + return `
+ + +
`; + } + if (n.type === 'friend_request' && n.application_status === 'accepted') return '
已同意
'; // 如果有考试关联的通知,添加快捷入口 if (n.type === 'contest_new_exam' || n.type === 'exam_graded') { @@ -265,6 +276,24 @@ async function rejectContestN(appId) { } catch(e) { alert('操作失败'); } } +async function acceptFriendN(reqId) { + try { + const res = await fetch(`/api/friend/accept/${reqId}`, {method:'POST'}); + const d = await res.json(); + if (d.success) { loadAll(); } + else { alert(d.message || '操作失败'); } + } catch(e) { alert('操作失败'); } +} +async function rejectFriendN(reqId) { + if (!confirm('确定拒绝该好友申请?')) return; + try { + const res = await fetch(`/api/friend/reject/${reqId}`, {method:'POST'}); + const d = await res.json(); + if (d.success) { loadAll(); } + else { alert(d.message || '操作失败'); } + } catch(e) { alert('操作失败'); } +} + function markSingleRead(nid, el) { fetch(`/api/notifications/${nid}/read`, {method:'POST'}); // 移除未读的特定样式