# 客户通知弹窗重新设计方案 ## 一、设计思路 ### 1.1 整体设计理念 - **简洁性**:去除冗余元素,突出核心信息 - **一致性**:与现有网页风格保持统一,使用相同的配色方案和设计语言 - **易用性**:优化交互流程,提升用户体验 - **美观性**:采用现代化设计,增强视觉吸引力 ### 1.2 设计目标 - 重新设计通知按钮和数量徽章 - 优化通知弹窗的布局和样式 - 改进通知项的渲染格式 - 增强用户交互反馈 - 确保响应式设计适配不同屏幕尺寸 ## 二、实现思路 ### 2.1 技术栈 - HTML5 + CSS3 + JavaScript - 保持与现有代码结构的兼容性 - 利用Font Awesome图标库 ### 2.2 核心功能实现 - 通知按钮的视觉增强和动画效果 - 通知弹窗的布局优化 - 通知项的卡片式设计 - 通知状态的交互更新 - 空状态的友好提示 ## 三、设计图 ### 3.1 通知按钮设计 #### 当前设计 ```html ``` #### 新设计 ```html ``` **设计说明**: - 保留铃铛图标,但增加悬停效果 - 优化数量徽章的位置和样式 - 为有新通知的状态添加动画效果 ### 3.2 通知弹窗设计 #### 当前设计 ```html ``` #### 新设计 ```html ``` **设计说明**: - 优化头部布局,将标题和图标左对齐 - 添加"全部已读"功能按钮 - 改进关闭按钮的样式 - 增加弹窗的阴影和圆角效果 ### 3.3 通知项渲染格式 #### 当前渲染格式 ```html
${customerName}
客户ID: ${customer.id} ${customer.phoneNumber || '无电话'}
``` #### 新渲染格式 ```html

${customerName}

新客户

有新的客户信息需要您查看

${customer.id} ${customer.phoneNumber || '无电话'} ${new Date(customer.created_at || Date.now()).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
``` **设计说明**: - 采用卡片式设计,增强视觉层次感 - 添加客户头像区域(使用Font Awesome图标) - 优化信息布局,突出客户名称 - 增加通知类型徽章 - 添加详细的元信息 - 提供明确的操作按钮(查看详情/忽略) - 为新通知添加视觉标识 ### 3.4 空状态设计 #### 当前设计 ```html

暂无通知

``` #### 新设计 ```html

暂无通知

当有新的客户信息时,会在这里显示通知

``` **设计说明**: - 增加友好的空状态图标 - 添加标题和描述文本 - 提升视觉体验 ## 四、代码实现 ### 4.1 CSS样式实现 ```css /* 通知铃铛按钮样式 */ .notification-btn { background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 50%; width: 44px; height: 44px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; margin-right: 10px; color: #666; position: relative; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .notification-btn:hover { background-color: #f8f9fa; border-color: #adb5bd; color: #495057; transform: scale(1.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); } .notification-btn i { font-size: 20px; transition: all 0.3s ease; } /* 通知铃铛激活状态样式 */ .notification-btn.notification-active { background-color: #ffebee; border-color: #ffcdd2; color: #d32f2f; animation: ring 0.5s ease-in-out 3; } /* 通知数量徽章样式 */ .notification-count { position: absolute; top: -6px; right: -6px; background: linear-gradient(135deg, #d32f2f, #e53935); color: white; font-size: 11px; font-weight: 600; border-radius: 10px; min-width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; padding: 0 6px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); animation: pulse 1.5s infinite; } /* 铃铛摇晃动画 */ @keyframes ring { 0% { transform: rotate(0deg); } 10% { transform: rotate(15deg); } 20% { transform: rotate(-15deg); } 30% { transform: rotate(15deg); } 40% { transform: rotate(-15deg); } 50% { transform: rotate(0deg); } 100% { transform: rotate(0deg); } } /* 脉冲动画 */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } /* 通知弹窗样式 */ #notificationModal .modal-content { width: 420px; max-width: 90vw; max-height: 80vh; border-radius: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); overflow: hidden; } #notificationModal .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; background-color: #f8f9fa; border-bottom: 1px solid #e0e0e0; } #notificationModal .modal-header-left { display: flex; align-items: center; gap: 10px; } #notificationModal .modal-header-icon { color: #d32f2f; font-size: 22px; } #notificationModal .modal-title { margin: 0; font-size: 18px; font-weight: 600; color: #333; } #notificationModal .modal-header-right { display: flex; align-items: center; gap: 10px; } #notificationModal .secondary-btn { background-color: #f0f0f0; color: #666; border: none; padding: 6px 12px; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.3s ease; } #notificationModal .secondary-btn:hover { background-color: #e0e0e0; } #notificationModal .modal-body { padding: 0; max-height: 60vh; overflow-y: auto; } /* 通知列表样式 */ .notification-list { display: flex; flex-direction: column; gap: 8px; padding: 12px; } /* 通知项样式 */ .notification-item { background-color: #fff; border: 1px solid #e0e0e0; border-radius: 10px; padding: 16px; cursor: pointer; transition: all 0.3s ease; display: flex; gap: 12px; } .notification-item:hover { border-color: #d32f2f; box-shadow: 0 4px 12px rgba(211, 47, 47, 0.1); transform: translateY(-2px); } .notification-item.new { border-left: 4px solid #d32f2f; background-color: #fff8f8; } /* 通知头像 */ .notification-avatar { width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #f5f7fa, #c3cfe2); display: flex; justify-content: center; align-items: center; flex-shrink: 0; } .notification-avatar i { font-size: 24px; color: #d32f2f; } /* 通知主内容 */ .notification-main { flex: 1; min-width: 0; } .notification-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px; } .notification-title { margin: 0; font-size: 16px; font-weight: 600; color: #333; word-break: break-word; } .notification-badge { background-color: #ffe0b2; color: #f57c00; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 500; text-transform: uppercase; } .notification-description { margin: 0 0 10px 0; font-size: 14px; color: #666; line-height: 1.4; } .notification-meta { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 12px; } .meta-item { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #999; } .meta-item i { font-size: 12px; } /* 通知操作 */ .notification-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; border-top: 1px solid #f0f0f0; } .action-btn { padding: 6px 12px; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.3s ease; } .view-btn { background-color: #d32f2f; color: white; } .view-btn:hover { background-color: #b71c1c; } .ignore-btn { background-color: #f0f0f0; color: #666; } .ignore-btn:hover { background-color: #e0e0e0; } /* 空状态样式 */ .notification-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; text-align: center; } .empty-icon { width: 80px; height: 80px; border-radius: 50%; background-color: #f5f5f5; display: flex; justify-content: center; align-items: center; margin-bottom: 16px; } .empty-icon i { font-size: 40px; color: #ccc; } .empty-title { margin: 0 0 8px 0; font-size: 18px; font-weight: 600; color: #333; } .empty-description { margin: 0; font-size: 14px; color: #999; max-width: 300px; } /* 滚动条样式 */ #notificationModal .modal-body::-webkit-scrollbar { width: 6px; } #notificationModal .modal-body::-webkit-scrollbar-track { background-color: #f5f5f5; border-radius: 3px; } #notificationModal .modal-body::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 3px; } #notificationModal .modal-body::-webkit-scrollbar-thumb:hover { background-color: #999; } ``` ### 4.2 JavaScript实现 ```javascript // 显示通知弹窗 async function showNotificationModal() { console.log('🎯 显示通知弹窗'); const notificationModal = document.getElementById('notificationModal'); const notificationContent = document.getElementById('notificationContent'); // 显示加载状态 notificationContent.innerHTML = '

加载中...

'; notificationModal.classList.add('active'); // 阻止背景滚动 document.body.style.overflow = 'hidden'; try { // 获取客户数据(保持原有的数据获取逻辑) let allCustomers = []; if (this.allPublicSeaCustomers) { allCustomers = this.allPublicSeaCustomers; } else { const url = appendAuthParams(`${API_BASE_URL}/pool/all-customers`); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); const result = await response.json(); if (!result.success) throw new Error(result.message); const data = result.data || {}; allCustomers = Array.isArray(data) ? data : Object.values(data); this.allPublicSeaCustomers = allCustomers; } // 获取notice为banold的客户 const banoldCustomers = allCustomers.filter(customer => customer.notice === 'banold'); if (banoldCustomers.length === 0) { // 渲染空状态 notificationContent.innerHTML = `

暂无通知

当有新的客户信息时,会在这里显示通知

`; } else { // 渲染通知列表 let contentHTML = '
'; banoldCustomers.forEach(customer => { const customerName = customer.company || customer.companyName || '未知客户'; const notificationClass = customer.notice === 'banold' ? 'notification-item new' : 'notification-item'; contentHTML += `

${customerName}

新客户

有新的客户信息需要您查看

${customer.id} ${customer.phoneNumber || '无电话'} ${new Date(customer.created_at || Date.now()).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
`; }); contentHTML += '
'; notificationContent.innerHTML = contentHTML; } } catch (error) { console.error('❌ 加载通知失败:', error); notificationContent.innerHTML = `

加载失败

加载通知时发生错误,请稍后重试

`; } } // 标记通知为已读 async function markNotificationAsRead(customerId, element) { console.log('📌 标记通知为已读,客户ID:', customerId); try { // 发送请求更新通知状态 const response = await fetch(`/DL/pool/customers/${customerId}/notice`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ notice: 'old' }) }); const data = await response.json(); if (data.success) { console.log('✅ 标记通知为已读成功'); // 移除通知项 const notificationItem = element.closest('.notification-item'); if (notificationItem) { notificationItem.remove(); } // 检查是否还有通知 const notificationContent = document.getElementById('notificationContent'); const remainingNotifications = notificationContent.querySelectorAll('.notification-item').length; if (remainingNotifications === 0) { notificationContent.innerHTML = `

暂无通知

当有新的客户信息时,会在这里显示通知

`; } // 更新通知按钮状态 updateNotificationStatus(this.allPublicSeaCustomers); } else { throw new Error('标记通知失败'); } } catch (error) { console.error('❌ 标记通知失败:', error); showToast('标记通知失败,请稍后重试', 'error'); } } // 全部标记为已读 async function markAllAsRead() { console.log('📌 全部标记为已读'); try { // 获取所有未读通知 const notificationItems = document.querySelectorAll('.notification-item.new'); if (notificationItems.length === 0) { showToast('没有未读通知', 'info'); return; } // 批量更新通知状态 const updatePromises = Array.from(notificationItems).map(item => { const customerId = item.dataset.customerId; return fetch(`/DL/pool/customers/${customerId}/notice`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ notice: 'old' }) }).then(response => response.json()); }); const results = await Promise.all(updatePromises); const successCount = results.filter(r => r.success).length; if (successCount > 0) { console.log('✅ 全部标记为已读成功'); // 清空通知列表 const notificationContent = document.getElementById('notificationContent'); notificationContent.innerHTML = `

暂无通知

当有新的客户信息时,会在这里显示通知

`; // 更新通知按钮状态 updateNotificationStatus(this.allPublicSeaCustomers); showToast(`已标记 ${successCount} 条通知为已读`, 'success'); } } catch (error) { console.error('❌ 全部标记为已读失败:', error); showToast('批量标记失败,请稍后重试', 'error'); } } // 隐藏通知弹窗 function hideNotificationModal() { const notificationModal = document.getElementById('notificationModal'); notificationModal.classList.remove('active'); document.body.style.overflow = ''; } // 更新通知状态 function updateNotificationStatus(customers) { // 统计notice为banold的客户数量 const banoldCount = customers.filter(customer => customer.notice === 'banold').length; const notificationButton = document.getElementById('notificationButton'); if (notificationButton) { if (banoldCount > 0) { // 有新通知 notificationButton.classList.add('notification-active'); let countBadge = notificationButton.querySelector('.notification-count'); if (!countBadge) { countBadge = document.createElement('span'); countBadge.className = 'notification-count'; notificationButton.appendChild(countBadge); } countBadge.textContent = banoldCount; } else { // 无新通知 notificationButton.classList.remove('notification-active'); const countBadge = notificationButton.querySelector('.notification-count'); if (countBadge) { countBadge.remove(); } } } } // 绑定通知事件 function bindNotificationEvents() { const notificationButton = document.getElementById('notificationButton'); const notificationModal = document.getElementById('notificationModal'); const closeNotificationModal = document.getElementById('closeNotificationModal'); const markAllAsReadBtn = document.getElementById('markAllAsRead'); if (notificationButton) { notificationButton.addEventListener('click', showNotificationModal); } if (closeNotificationModal) { closeNotificationModal.addEventListener('click', hideNotificationModal); } if (markAllAsReadBtn) { markAllAsReadBtn.addEventListener('click', markAllAsRead); } if (notificationModal) { notificationModal.addEventListener('click', (e) => { if (e.target === notificationModal) { hideNotificationModal(); } }); } } // 初始化通知功能 document.addEventListener('DOMContentLoaded', function() { bindNotificationEvents(); }); ``` ## 五、部署与测试 ### 5.1 部署步骤 1. 备份现有代码 2. 将新的CSS样式添加到样式文件中 3. 更新JavaScript函数 4. 修改HTML结构 5. 测试功能完整性 ### 5.2 测试要点 - 通知按钮的显示和动画效果 - 通知弹窗的打开和关闭 - 通知列表的渲染 - 通知状态的更新 - 空状态的显示 - 响应式设计的适配 ## 六、总结 本方案通过重新设计客户通知弹窗和通知渲染格式,实现了以下改进: 1. **视觉提升**:采用现代化的卡片式设计,增强视觉层次感 2. **用户体验优化**:提供更清晰的操作按钮和状态反馈 3. **功能增强**:增加了批量标记已读和忽略功能 4. **友好性提升**:优化了空状态和错误状态的提示 5. **性能优化**:保持了与现有代码的兼容性,无需大规模重构 新设计简洁美观,与整体网页风格契合,同时提升了用户的操作效率和体验。