256 lines
8.0 KiB
JavaScript
256 lines
8.0 KiB
JavaScript
|
|
(() => {
|
|||
|
|
let notificationInterval = null;
|
|||
|
|
let isOpen = false;
|
|||
|
|
let isInitialized = false;
|
|||
|
|
|
|||
|
|
// 格式化时间
|
|||
|
|
function formatTime(ts) {
|
|||
|
|
if (!ts) return '';
|
|||
|
|
try {
|
|||
|
|
// 解析ISO格式的时间字符串(已经是北京时间)
|
|||
|
|
const date = new Date(ts);
|
|||
|
|
const now = new Date();
|
|||
|
|
|
|||
|
|
// 计算时间差(毫秒)
|
|||
|
|
const diff = now - date;
|
|||
|
|
const minutes = Math.floor(diff / 60000);
|
|||
|
|
const hours = Math.floor(diff / 3600000);
|
|||
|
|
const days = Math.floor(diff / 86400000);
|
|||
|
|
|
|||
|
|
if (minutes < 1) return '刚刚';
|
|||
|
|
if (minutes < 60) return `${minutes}分钟前`;
|
|||
|
|
if (hours < 24) return `${hours}小时前`;
|
|||
|
|
if (days < 7) return `${days}天前`;
|
|||
|
|
|
|||
|
|
// 超过7天显示完整日期
|
|||
|
|
return date.toLocaleString('zh-CN', {
|
|||
|
|
year: 'numeric',
|
|||
|
|
month: '2-digit',
|
|||
|
|
day: '2-digit',
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('时间格式化错误:', e, ts);
|
|||
|
|
return ts;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取操作的中文描述
|
|||
|
|
function getActionText(action) {
|
|||
|
|
const actionMap = {
|
|||
|
|
'批量上传MAC文件': '批量上传了MAC文件',
|
|||
|
|
'批量上传发货记录文件': '批量上传了发货记录',
|
|||
|
|
'添加人员信息': '添加了人员信息',
|
|||
|
|
'上传发货记录': '上传了发货记录',
|
|||
|
|
'上传MAC与批次': '上传了MAC与批次',
|
|||
|
|
'批量上传不良明细文件': '批量上传了不良明细',
|
|||
|
|
'上传返修记录': '上传了返修记录',
|
|||
|
|
'上传良/不良统计': '上传了良/不良统计',
|
|||
|
|
'上传不良明细': '上传了不良明细'
|
|||
|
|
};
|
|||
|
|
return actionMap[action] || action;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新未读数量
|
|||
|
|
async function updateUnreadCount() {
|
|||
|
|
try {
|
|||
|
|
const data = await API.getUnreadCount();
|
|||
|
|
const badge = document.getElementById('notification-badge');
|
|||
|
|
const count = data.count || 0;
|
|||
|
|
|
|||
|
|
if (count > 0) {
|
|||
|
|
badge.textContent = count > 99 ? '99+' : count;
|
|||
|
|
badge.style.display = 'block';
|
|||
|
|
} else {
|
|||
|
|
badge.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('获取未读数量失败:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载通知列表
|
|||
|
|
async function loadNotifications() {
|
|||
|
|
try {
|
|||
|
|
const data = await API.getNotifications();
|
|||
|
|
const list = data.list || [];
|
|||
|
|
const container = document.getElementById('notification-list');
|
|||
|
|
|
|||
|
|
if (list.length === 0) {
|
|||
|
|
container.innerHTML = '<div class="notification-empty">暂无消息通知</div>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
container.innerHTML = list.map(item => `
|
|||
|
|
<div class="notification-item ${item.read ? '' : 'unread'}" data-id="${item.id}">
|
|||
|
|
<div class="notification-username">${item.username || '用户'}</div>
|
|||
|
|
<div class="notification-action">${getActionText(item.action)}</div>
|
|||
|
|
${item.detail ? `<div class="notification-detail">${item.detail}</div>` : ''}
|
|||
|
|
<div class="notification-time">${formatTime(item.ts)}</div>
|
|||
|
|
</div>
|
|||
|
|
`).join('');
|
|||
|
|
|
|||
|
|
// 为每个通知项添加点击事件
|
|||
|
|
container.querySelectorAll('.notification-item').forEach(item => {
|
|||
|
|
item.addEventListener('click', async () => {
|
|||
|
|
const id = item.dataset.id;
|
|||
|
|
if (item.classList.contains('unread')) {
|
|||
|
|
try {
|
|||
|
|
await API.markNotificationRead(id);
|
|||
|
|
item.classList.remove('unread');
|
|||
|
|
await updateUnreadCount();
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('标记已读失败:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('加载通知失败:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 切换通知面板
|
|||
|
|
function togglePanel() {
|
|||
|
|
const panel = document.getElementById('notification-panel');
|
|||
|
|
if (!panel) {
|
|||
|
|
console.error('[Notifications] 找不到面板元素');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
isOpen = !isOpen;
|
|||
|
|
console.log('[Notifications] 面板状态:', isOpen ? '打开' : '关闭');
|
|||
|
|
|
|||
|
|
if (isOpen) {
|
|||
|
|
panel.style.display = 'flex';
|
|||
|
|
loadNotifications();
|
|||
|
|
} else {
|
|||
|
|
panel.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化通知系统
|
|||
|
|
async function initNotifications() {
|
|||
|
|
// 防止重复初始化
|
|||
|
|
if (isInitialized) {
|
|||
|
|
console.log('[Notifications] 已经初始化,跳过');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 检查用户角色
|
|||
|
|
const user = await API.me();
|
|||
|
|
if (user.role !== 'superadmin') {
|
|||
|
|
console.log('[Notifications] 非超级管理员,不显示通知');
|
|||
|
|
return; // 只有超级管理员才显示通知铃铛
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示通知铃铛
|
|||
|
|
const bell = document.getElementById('notification-bell');
|
|||
|
|
if (!bell) {
|
|||
|
|
console.error('[Notifications] 找不到铃铛元素');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bell.style.display = 'inline-flex';
|
|||
|
|
console.log('[Notifications] 铃铛已显示');
|
|||
|
|
|
|||
|
|
// 移除旧的事件监听器(如果有)
|
|||
|
|
const newBell = bell.cloneNode(true);
|
|||
|
|
bell.parentNode.replaceChild(newBell, bell);
|
|||
|
|
|
|||
|
|
// 绑定铃铛点击事件
|
|||
|
|
newBell.addEventListener('click', (e) => {
|
|||
|
|
console.log('[Notifications] 铃铛被点击');
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
togglePanel();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 绑定全部标记为已读按钮(移除旧的事件监听器)
|
|||
|
|
const markAllBtn = document.getElementById('mark-all-read');
|
|||
|
|
if (markAllBtn) {
|
|||
|
|
const newMarkAllBtn = markAllBtn.cloneNode(true);
|
|||
|
|
markAllBtn.parentNode.replaceChild(newMarkAllBtn, markAllBtn);
|
|||
|
|
|
|||
|
|
newMarkAllBtn.addEventListener('click', async (e) => {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
try {
|
|||
|
|
await API.markAllNotificationsRead();
|
|||
|
|
await loadNotifications();
|
|||
|
|
await updateUnreadCount();
|
|||
|
|
API.toast('已全部标记为已读');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('标记全部已读失败:', e);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 绑定删除已读消息按钮(移除旧的事件监听器)
|
|||
|
|
const deleteBtn = document.getElementById('delete-read');
|
|||
|
|
if (deleteBtn) {
|
|||
|
|
const newDeleteBtn = deleteBtn.cloneNode(true);
|
|||
|
|
deleteBtn.parentNode.replaceChild(newDeleteBtn, deleteBtn);
|
|||
|
|
|
|||
|
|
newDeleteBtn.addEventListener('click', async (e) => {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
if (!confirm('确定要删除所有已读消息吗?')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const result = await API.deleteReadNotifications();
|
|||
|
|
await loadNotifications();
|
|||
|
|
await updateUnreadCount();
|
|||
|
|
API.toast(`已删除 ${result.count || 0} 条已读消息`);
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('删除已读消息失败:', e);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 点击其他地方关闭面板
|
|||
|
|
document.addEventListener('click', (e) => {
|
|||
|
|
const panel = document.getElementById('notification-panel');
|
|||
|
|
const currentBell = document.getElementById('notification-bell');
|
|||
|
|
if (isOpen && panel && currentBell && !panel.contains(e.target) && !currentBell.contains(e.target)) {
|
|||
|
|
isOpen = false;
|
|||
|
|
panel.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 初始加载未读数量
|
|||
|
|
await updateUnreadCount();
|
|||
|
|
|
|||
|
|
// 每30秒更新一次未读数量
|
|||
|
|
if (notificationInterval) {
|
|||
|
|
clearInterval(notificationInterval);
|
|||
|
|
}
|
|||
|
|
notificationInterval = setInterval(updateUnreadCount, 30000);
|
|||
|
|
|
|||
|
|
isInitialized = true;
|
|||
|
|
console.log('[Notifications] 初始化完成');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('初始化通知系统失败:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理定时器
|
|||
|
|
function cleanupNotifications() {
|
|||
|
|
if (notificationInterval) {
|
|||
|
|
clearInterval(notificationInterval);
|
|||
|
|
notificationInterval = null;
|
|||
|
|
}
|
|||
|
|
isInitialized = false;
|
|||
|
|
isOpen = false;
|
|||
|
|
console.log('[Notifications] 已清理');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出到全局
|
|||
|
|
window.NotificationSystem = {
|
|||
|
|
init: initNotifications,
|
|||
|
|
cleanup: cleanupNotifications,
|
|||
|
|
refresh: updateUnreadCount
|
|||
|
|
};
|
|||
|
|
})();
|