You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
22 KiB
22 KiB
客户通知弹窗重新设计方案
一、设计思路
1.1 整体设计理念
- 简洁性:去除冗余元素,突出核心信息
- 一致性:与现有网页风格保持统一,使用相同的配色方案和设计语言
- 易用性:优化交互流程,提升用户体验
- 美观性:采用现代化设计,增强视觉吸引力
1.2 设计目标
- 重新设计通知按钮和数量徽章
- 优化通知弹窗的布局和样式
- 改进通知项的渲染格式
- 增强用户交互反馈
- 确保响应式设计适配不同屏幕尺寸
二、实现思路
2.1 技术栈
- HTML5 + CSS3 + JavaScript
- 保持与现有代码结构的兼容性
- 利用Font Awesome图标库
2.2 核心功能实现
- 通知按钮的视觉增强和动画效果
- 通知弹窗的布局优化
- 通知项的卡片式设计
- 通知状态的交互更新
- 空状态的友好提示
三、设计图
3.1 通知按钮设计
当前设计
<button class="notification-btn" id="notificationButton">
<i class="fas fa-bell"></i>
</button>
新设计
<button class="notification-btn" id="notificationButton">
<i class="fas fa-bell"></i>
<span class="notification-count">3</span>
</button>
设计说明:
- 保留铃铛图标,但增加悬停效果
- 优化数量徽章的位置和样式
- 为有新通知的状态添加动画效果
3.2 通知弹窗设计
当前设计
<div class="modal" id="notificationModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">客户通知</h2>
<button class="close-modal" id="closeNotificationModal">×</button>
</div>
<div class="modal-body">
<div id="notificationContent">
<!-- 通知内容将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
新设计
<div class="modal" id="notificationModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-header-left">
<i class="fas fa-bell modal-header-icon"></i>
<h2 class="modal-title">客户通知</h2>
</div>
<div class="modal-header-right">
<button class="secondary-btn" id="markAllAsRead">全部已读</button>
<button class="close-modal" id="closeNotificationModal">×</button>
</div>
</div>
<div class="modal-body">
<div id="notificationContent">
<!-- 通知内容将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
设计说明:
- 优化头部布局,将标题和图标左对齐
- 添加"全部已读"功能按钮
- 改进关闭按钮的样式
- 增加弹窗的阴影和圆角效果
3.3 通知项渲染格式
当前渲染格式
<div class="notification-item new" onclick="viewCustomerDetails('${customer.id}', '${customer.phoneNumber || ''}', null, true)">
<div class="notification-header">
<div class="notification-icon">
<i class="fas fa-user-clock" style="color: #ff9800;"></i>
</div>
<div class="notification-content">
<div class="notification-title">
${customerName}
</div>
<div class="notification-meta">
<span class="customer-id">客户ID: ${customer.id}</span>
<span class="customer-phone">${customer.phoneNumber || '无电话'}</span>
</div>
<div class="notification-footer">
<div class="notification-time">${new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })}</div>
<div class="notification-status">未读</div>
</div>
</div>
</div>
</div>
新渲染格式
<div class="notification-item new" data-customer-id="${customer.id}">
<div class="notification-avatar">
<i class="fas fa-user-tie"></i>
</div>
<div class="notification-main">
<div class="notification-header">
<h3 class="notification-title">${customerName}</h3>
<span class="notification-badge">新客户</span>
</div>
<div class="notification-info">
<p class="notification-description">有新的客户信息需要您查看</p>
<div class="notification-meta">
<span class="meta-item"><i class="fas fa-id-card"></i> ${customer.id}</span>
<span class="meta-item"><i class="fas fa-phone"></i> ${customer.phoneNumber || '无电话'}</span>
<span class="meta-item"><i class="fas fa-clock"></i> ${new Date(customer.created_at || Date.now()).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}</span>
</div>
</div>
<div class="notification-actions">
<button class="action-btn view-btn" onclick="viewCustomerDetails('${customer.id}', '${customer.phoneNumber || ''}', null, true)">查看详情</button>
<button class="action-btn ignore-btn" onclick="markNotificationAsRead('${customer.id}', this)">忽略</button>
</div>
</div>
</div>
设计说明:
- 采用卡片式设计,增强视觉层次感
- 添加客户头像区域(使用Font Awesome图标)
- 优化信息布局,突出客户名称
- 增加通知类型徽章
- 添加详细的元信息
- 提供明确的操作按钮(查看详情/忽略)
- 为新通知添加视觉标识
3.4 空状态设计
当前设计
<div class="notification-empty"><p>暂无通知</p></div>
新设计
<div class="notification-empty">
<div class="empty-icon">
<i class="fas fa-inbox"></i>
</div>
<h3 class="empty-title">暂无通知</h3>
<p class="empty-description">当有新的客户信息时,会在这里显示通知</p>
</div>
设计说明:
- 增加友好的空状态图标
- 添加标题和描述文本
- 提升视觉体验
四、代码实现
4.1 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实现
// 显示通知弹窗
async function showNotificationModal() {
console.log('🎯 显示通知弹窗');
const notificationModal = document.getElementById('notificationModal');
const notificationContent = document.getElementById('notificationContent');
// 显示加载状态
notificationContent.innerHTML = '<div class="notification-loading"><div class="loading-spinner"></div><p>加载中...</p></div>';
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 = `
<div class="notification-empty">
<div class="empty-icon">
<i class="fas fa-inbox"></i>
</div>
<h3 class="empty-title">暂无通知</h3>
<p class="empty-description">当有新的客户信息时,会在这里显示通知</p>
</div>
`;
} else {
// 渲染通知列表
let contentHTML = '<div class="notification-list">';
banoldCustomers.forEach(customer => {
const customerName = customer.company || customer.companyName || '未知客户';
const notificationClass = customer.notice === 'banold' ? 'notification-item new' : 'notification-item';
contentHTML += `
<div class="${notificationClass}" data-customer-id="${customer.id}">
<div class="notification-avatar">
<i class="fas fa-user-tie"></i>
</div>
<div class="notification-main">
<div class="notification-header">
<h3 class="notification-title">${customerName}</h3>
<span class="notification-badge">新客户</span>
</div>
<div class="notification-info">
<p class="notification-description">有新的客户信息需要您查看</p>
<div class="notification-meta">
<span class="meta-item"><i class="fas fa-id-card"></i> ${customer.id}</span>
<span class="meta-item"><i class="fas fa-phone"></i> ${customer.phoneNumber || '无电话'}</span>
<span class="meta-item"><i class="fas fa-clock"></i> ${new Date(customer.created_at || Date.now()).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}</span>
</div>
</div>
<div class="notification-actions">
<button class="action-btn view-btn" onclick="viewCustomerDetails('${customer.id}', '${customer.phoneNumber || ''}', null, true)">查看详情</button>
<button class="action-btn ignore-btn" onclick="markNotificationAsRead('${customer.id}', this)">忽略</button>
</div>
</div>
</div>
`;
});
contentHTML += '</div>';
notificationContent.innerHTML = contentHTML;
}
} catch (error) {
console.error('❌ 加载通知失败:', error);
notificationContent.innerHTML = `
<div class="notification-error">
<div class="error-icon">
<i class="fas fa-exclamation-circle"></i>
</div>
<h3 class="error-title">加载失败</h3>
<p class="error-description">加载通知时发生错误,请稍后重试</p>
<button class="retry-btn" onclick="showNotificationModal()">重试</button>
</div>
`;
}
}
// 标记通知为已读
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 = `
<div class="notification-empty">
<div class="empty-icon">
<i class="fas fa-inbox"></i>
</div>
<h3 class="empty-title">暂无通知</h3>
<p class="empty-description">当有新的客户信息时,会在这里显示通知</p>
</div>
`;
}
// 更新通知按钮状态
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 = `
<div class="notification-empty">
<div class="empty-icon">
<i class="fas fa-inbox"></i>
</div>
<h3 class="empty-title">暂无通知</h3>
<p class="empty-description">当有新的客户信息时,会在这里显示通知</p>
</div>
`;
// 更新通知按钮状态
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 部署步骤
- 备份现有代码
- 将新的CSS样式添加到样式文件中
- 更新JavaScript函数
- 修改HTML结构
- 测试功能完整性
5.2 测试要点
- 通知按钮的显示和动画效果
- 通知弹窗的打开和关闭
- 通知列表的渲染
- 通知状态的更新
- 空状态的显示
- 响应式设计的适配
六、总结
本方案通过重新设计客户通知弹窗和通知渲染格式,实现了以下改进:
- 视觉提升:采用现代化的卡片式设计,增强视觉层次感
- 用户体验优化:提供更清晰的操作按钮和状态反馈
- 功能增强:增加了批量标记已读和忽略功能
- 友好性提升:优化了空状态和错误状态的提示
- 性能优化:保持了与现有代码的兼容性,无需大规模重构
新设计简洁美观,与整体网页风格契合,同时提升了用户的操作效率和体验。