mobile chat themes

This commit is contained in:
2026-03-10 13:19:48 +08:00
parent 6ccfe9b107
commit 751cc8dc24

View File

@@ -375,7 +375,7 @@ function switchTab(tab) {
notifPanel.classList.remove('hidden');
tabChat.className = 'flex-1 px-3 py-2.5 text-sm font-medium text-slate-400 border-b-2 border-transparent hover:text-slate-600';
tabNotif.className = 'flex-1 px-3 py-2.5 text-sm font-medium text-primary border-b-2 border-primary relative';
loadNotifications();
loadChatNotifications();
// Mark all notifications as read when switching to the notification tab
fetch('/api/notifications/read-all', { method: 'POST' }).then(() => {
@@ -388,7 +388,7 @@ function switchTab(tab) {
}
}
async function loadNotifications() {
async function loadChatNotifications() {
const res = await fetch('/api/notifications');
const data = await res.json();
if (data.success) {
@@ -428,20 +428,20 @@ function renderNotifications() {
let actionsHtml = '';
if (isPendingContest && currentUser.role === 'admin') {
actionsHtml = `<div class="flex gap-2 mt-2">
<button onclick="event.stopPropagation();approveContest(${n.post_id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">批准</button>
<button onclick="event.stopPropagation();rejectContest(${n.post_id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
<button onclick="event.stopPropagation();chatApproveContest(${n.post_id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">批准</button>
<button onclick="event.stopPropagation();chatRejectContest(${n.post_id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
</div>`;
}
if (isPendingTeacher && currentUser.role === 'admin' && n.application_id) {
actionsHtml = `<div class="flex gap-2 mt-2">
<button onclick="event.stopPropagation();approveTeacher(${n.application_id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">批准</button>
<button onclick="event.stopPropagation();rejectTeacher(${n.application_id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
<button onclick="event.stopPropagation();chatApproveTeacher(${n.application_id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">批准</button>
<button onclick="event.stopPropagation();chatRejectTeacher(${n.application_id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
</div>`;
}
if (isPendingFriend && n.friend_request_id) {
actionsHtml = `<div class="flex gap-2 mt-2">
<button onclick="event.stopPropagation();acceptFriendReq(${n.friend_request_id},${n.id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">同意</button>
<button onclick="event.stopPropagation();rejectFriendReq(${n.friend_request_id},${n.id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
<button onclick="event.stopPropagation();chatAcceptFriendReq(${n.friend_request_id},${n.id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">同意</button>
<button onclick="event.stopPropagation();chatRejectFriendReq(${n.friend_request_id},${n.id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
</div>`;
}
return `<div class="px-3 py-3 ${n.read ? '' : 'bg-blue-50'} hover:bg-slate-50 border-b border-slate-100 transition cursor-pointer" onclick="${clickAction}">
@@ -470,65 +470,65 @@ async function markNotifRead(nid, el) {
}
}
async function approveContest(appId) {
async function chatApproveContest(appId) {
if (!confirm('确定批准该杯赛申请?')) return;
const res = await fetch(`/api/contest-applications/${appId}/approve`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadNotifications();
loadChatNotifications();
} else {
alert(data.message || '操作失败');
}
}
async function rejectContest(appId) {
async function chatRejectContest(appId) {
if (!confirm('确定拒绝该杯赛申请?')) return;
const res = await fetch(`/api/contest-applications/${appId}/reject`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadNotifications();
loadChatNotifications();
} else {
alert(data.message || '操作失败');
}
}
async function approveTeacher(appId) {
async function chatApproveTeacher(appId) {
if (!confirm('确定批准该教师申请?')) return;
const res = await fetch(`/api/teacher-applications/${appId}/approve`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadNotifications();
loadChatNotifications();
} else {
alert(data.message || '操作失败');
}
}
async function rejectTeacher(appId) {
async function chatRejectTeacher(appId) {
if (!confirm('确定拒绝该教师申请?')) return;
const res = await fetch(`/api/teacher-applications/${appId}/reject`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadNotifications();
loadChatNotifications();
} else {
alert(data.message || '操作失败');
}
}
async function acceptFriendReq(reqId, notifId) {
async function chatAcceptFriendReq(reqId, notifId) {
const res = await fetch(`/api/friend/accept/${reqId}`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadNotifications();
loadChatNotifications();
} else {
alert(data.message || '操作失败');
}
}
async function rejectFriendReq(reqId, notifId) {
async function chatRejectFriendReq(reqId, notifId) {
const res = await fetch(`/api/friend/reject/${reqId}`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadNotifications();
loadChatNotifications();
} else {
alert(data.message || '操作失败');
}
@@ -606,13 +606,13 @@ function showNotifDetail(nid) {
let actHtml = '';
if (n.type === 'contest_application' && n.application_status === 'pending' && n.post_id && currentUser.role === 'admin') {
actHtml = `<div class="flex gap-3">
<button onclick="approveContest(${n.post_id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">批准</button>
<button onclick="rejectContest(${n.post_id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
<button onclick="chatApproveContest(${n.post_id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">批准</button>
<button onclick="chatRejectContest(${n.post_id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
</div>`;
} else if (n.type === 'teacher_application' && n.application_status === 'pending' && n.application_id && currentUser.role === 'admin') {
actHtml = `<div class="flex gap-3">
<button onclick="approveTeacher(${n.application_id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">批准</button>
<button onclick="rejectTeacher(${n.application_id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
<button onclick="chatApproveTeacher(${n.application_id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">批准</button>
<button onclick="chatRejectTeacher(${n.application_id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
</div>`;
} else if (n.type === 'exam_submission' && n.exam_id && n.submission_id) {
actHtml = `<a href="/exams/${n.exam_id}/grade/${n.submission_id}" class="btn-futuristic inline-flex items-center gap-2 px-5 py-2.5 text-sm font-medium">
@@ -623,8 +623,8 @@ function showNotifDetail(nid) {
actHtml = `<a href="/exams/${n.post_id}" class="btn-futuristic inline-flex items-center gap-2 px-5 py-2.5 text-sm font-medium">查看考试</a>`;
} else if (n.type === 'friend_request' && n.application_status === 'pending' && n.friend_request_id) {
actHtml = `<div class="flex gap-3">
<button onclick="acceptFriendReq(${n.friend_request_id},${n.id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">同意</button>
<button onclick="rejectFriendReq(${n.friend_request_id},${n.id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
<button onclick="chatAcceptFriendReq(${n.friend_request_id},${n.id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">同意</button>
<button onclick="chatRejectFriendReq(${n.friend_request_id},${n.id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
</div>`;
} else if (n.type === 'friend_request' && n.application_status === 'accepted') {
actHtml = '<span class="text-sm text-green-600 font-medium">✅ 已同意</span>';
@@ -674,14 +674,14 @@ function renderRooms() {
list.innerHTML = filtered.map(r => {
const peerInExam = r.type === 'private' && r.peer_id && peerExamStatus[r.peer_id]?.in_exam;
return `
<div class="flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-slate-50 transition ${currentRoomId === r.id ? 'bg-blue-50 border-r-2 border-primary' : ''}" onclick="selectRoom(${r.id})">
<div class="w-10 h-10 rounded-full bg-slate-200 flex items-center justify-center flex-shrink-0 overflow-hidden">
<div class="flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-white/10 transition rounded-xl ${currentRoomId === r.id ? 'bg-cyan-500/15 border-r-2 border-cyan-400' : ''}" onclick="selectRoom(${r.id})">
<div class="w-10 h-10 rounded-full bg-slate-700/50 flex items-center justify-center flex-shrink-0 overflow-hidden border border-white/10">
${r.avatar ? `<img src="${r.avatar}" class="w-full h-full object-cover">` :
`<span class="text-sm font-medium text-slate-500">${(r.name || '?')[0]}</span>`}
`<span class="text-sm font-medium text-cyan-400">${(r.name || '?')[0]}</span>`}
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-center">
<span class="text-sm font-medium text-slate-800 truncate">${escHtml(r.name || '未命名')}</span>
<span class="text-sm font-medium text-white truncate">${escHtml(r.name || '未命名')}</span>
${peerInExam ? '<span class="text-[10px] text-amber-400 bg-amber-500/30 px-1.5 py-0.5 rounded flex-shrink-0">考试中</span>' : ''}
<span class="text-xs text-slate-400 flex-shrink-0">${r.last_message ? formatTime(r.last_message.created_at) : ''}</span>
</div>
@@ -710,7 +710,7 @@ function filterRooms() { renderRooms(); }
async function selectRoom(roomId) {
currentRoomId = roomId;
oldestMsgId = null;
markRead(roomId); // 立即标记已读,使聊天气泡消失
markChatRead(roomId); // 立即标记已读,使聊天气泡消失
document.getElementById('emptyState').classList.add('hidden');
// 隐藏通知详情
const nd = document.getElementById('notifDetailView');
@@ -988,7 +988,7 @@ function previewImage(url) {
}
// ========== 标记已读 ==========
function markRead(roomId) {
function markChatRead(roomId) {
fetch(`/api/chat/rooms/${roomId}/read`, { method: 'POST' });
socket.emit('mark_read', { room_id: roomId });
const room = rooms.find(r => r.id === roomId);
@@ -1425,7 +1425,7 @@ socket.on('new_message', (msg) => {
area.scrollTop = area.scrollHeight;
}, 50);
}
markRead(currentRoomId);
markChatRead(currentRoomId);
}
// 更新会话列表
const room = rooms.find(r => r.id === msg.room_id);