/** * YCat Notification Bell Component * 在页面 header 中显示通知铃铛,点击展开通知下拉面板 * 使用方式:在 HTML 中引入 * 自动在 #notificationBell 容器中渲染,或在 body 中自动注入 */ (function() { 'use strict'; // 样式注入 const style = document.createElement('style'); style.textContent = ` .notif-bell-wrapper { position: relative; display: inline-flex; align-items: center; cursor: pointer; padding: 6px 10px; border-radius: 8px; transition: background 0.2s; } .notif-bell-wrapper:hover { background: rgba(0,0,0,0.05); } .notif-bell-icon { font-size: 20px; line-height: 1; } .notif-badge { position: absolute; top: 2px; right: 4px; background: #ef4444; color: #fff; font-size: 11px; font-weight: 700; min-width: 18px; height: 18px; border-radius: 9px; display: flex; align-items: center; justify-content: center; padding: 0 4px; animation: notif-pulse 2s infinite; } .notif-badge.hidden { display: none; } @keyframes notif-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } .notif-dropdown { position: absolute; top: 100%; right: 0; width: 380px; max-height: 500px; background: #fff; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.08); z-index: 10000; overflow: hidden; display: none; flex-direction: column; } .notif-dropdown.open { display: flex; } .notif-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid #f0f0f0; background: #fafafa; } .notif-header h3 { font-size: 15px; font-weight: 600; margin: 0; } .notif-header-actions { display: flex; gap: 8px; } .notif-header-actions button { font-size: 12px; color: #6366f1; background: none; border: none; cursor: pointer; padding: 2px 6px; border-radius: 4px; } .notif-header-actions button:hover { background: #f0f0ff; } .notif-list { flex: 1; overflow-y: auto; max-height: 400px; } .notif-item { display: flex; gap: 10px; padding: 12px 16px; border-bottom: 1px solid #f5f5f5; cursor: pointer; transition: background 0.15s; } .notif-item:hover { background: #f8f9ff; } .notif-item.unread { background: #f0f4ff; } .notif-item.unread:hover { background: #e8edff; } .notif-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; } .notif-icon.publish_success { background: #dcfce7; } .notif-icon.publish_failed { background: #fee2e2; } .notif-icon.team_invite { background: #dbeafe; } .notif-icon.approval_request { background: #fef3c7; } .notif-icon.approval_result { background: #e0e7ff; } .notif-icon.subscription { background: #fce7f3; } .notif-icon.system { background: #f3f4f6; } .notif-icon.comment { background: #ccfbf1; } .notif-icon.version { background: #fde68a; } .notif-body { flex: 1; min-width: 0; } .notif-title { font-size: 13px; font-weight: 600; color: #1a1a2e; margin-bottom: 2px; } .notif-text { font-size: 12px; color: #666; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .notif-time { font-size: 11px; color: #999; margin-top: 3px; } .notif-empty { padding: 40px 16px; text-align: center; color: #999; font-size: 14px; } .notif-empty-icon { font-size: 32px; margin-bottom: 8px; } @media (max-width: 480px) { .notif-dropdown { position: fixed; top: auto; bottom: 0; left: 0; right: 0; width: 100%; max-height: 70vh; border-radius: 16px 16px 0 0; } } `; document.head.appendChild(style); // 类型图标映射 const TYPE_ICONS = { publish_success: '✅', publish_failed: '❌', team_invite: '👥', approval_request: '📝', approval_result: '📋', subscription: '💎', system: '🔔', comment: '💬', version: '📦', }; let isOpen = false; let bellContainer = null; function timeAgo(dateStr) { const now = Date.now(); const then = new Date(dateStr).getTime(); const diff = Math.floor((now - then) / 1000); if (diff < 60) return '刚刚'; if (diff < 3600) return Math.floor(diff / 60) + '分钟前'; if (diff < 86400) return Math.floor(diff / 3600) + '小时前'; if (diff < 604800) return Math.floor(diff / 86400) + '天前'; return new Date(dateStr).toLocaleDateString('zh-CN'); } function renderBadge(count) { const badge = bellContainer?.querySelector('.notif-badge'); if (!badge) return; if (count > 0) { badge.textContent = count > 99 ? '99+' : count; badge.classList.remove('hidden'); } else { badge.classList.add('hidden'); } } function renderItem(n) { const icon = TYPE_ICONS[n.type] || '🔔'; const isUnread = !n.is_read; return `