mobile app chat notifications theme
This commit is contained in:
15
app.py
15
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': '好友已添加'})
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<button onclick="switchTab('system')" id="tab-system" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">📢 系统公告</button>
|
||||
<button onclick="switchTab('teacher')" id="tab-teacher" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">👨🏫 教师相关</button>
|
||||
<button onclick="switchTab('contest')" id="tab-contest" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">🏆 杯赛相关</button>
|
||||
<button onclick="switchTab('friend')" id="tab-friend" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">👤 好友</button>
|
||||
<button onclick="switchTab('exam')" id="tab-exam" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">📝 考试相关</button>
|
||||
</div>
|
||||
|
||||
@@ -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 `<div class="bg-white border ${n.read ? 'border-slate-100' : 'border-indigo-200 shadow-md shadow-indigo-100/50 relative'} rounded-2xl p-4 sm:p-5 hover:border-indigo-300 hover:shadow-lg transition-all duration-300 cursor-pointer group" onclick="markSingleRead(${n.id}, this)">
|
||||
@@ -221,6 +223,15 @@ function buildActions(n) {
|
||||
if (n.type === 'contest_application' && n.application_status === 'approved') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-emerald-50 border border-emerald-100 text-emerald-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> 已同意</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
||||
if (n.type === 'contest_application' && n.application_status === 'rejected') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-rose-50 border border-rose-100 text-rose-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg> 已拒绝</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
||||
|
||||
// 好友申请处理
|
||||
if (n.type === 'friend_request' && n.application_status === 'pending' && n.friend_request_id) {
|
||||
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-100">
|
||||
<button onclick="event.stopPropagation();acceptFriendN(${n.friend_request_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意</button>
|
||||
<button onclick="event.stopPropagation();rejectFriendN(${n.friend_request_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝</button>
|
||||
</div>`;
|
||||
}
|
||||
if (n.type === 'friend_request' && n.application_status === 'accepted') return '<div class="mt-3 inline-flex items-center gap-1.5 px-3 py-1 bg-emerald-50 border border-emerald-100 text-emerald-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> 已同意</div>';
|
||||
|
||||
// 如果有考试关联的通知,添加快捷入口
|
||||
if (n.type === 'contest_new_exam' || n.type === 'exam_graded') {
|
||||
if(n.post_id) {
|
||||
@@ -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'});
|
||||
// 移除未读的特定样式
|
||||
|
||||
Reference in New Issue
Block a user