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

客户通知弹窗重新设计方案

一、设计思路

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">&times;</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">&times;</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 部署步骤

  1. 备份现有代码
  2. 将新的CSS样式添加到样式文件中
  3. 更新JavaScript函数
  4. 修改HTML结构
  5. 测试功能完整性

5.2 测试要点

  • 通知按钮的显示和动画效果
  • 通知弹窗的打开和关闭
  • 通知列表的渲染
  • 通知状态的更新
  • 空状态的显示
  • 响应式设计的适配

六、总结

本方案通过重新设计客户通知弹窗和通知渲染格式,实现了以下改进:

  1. 视觉提升:采用现代化的卡片式设计,增强视觉层次感
  2. 用户体验优化:提供更清晰的操作按钮和状态反馈
  3. 功能增强:增加了批量标记已读和忽略功能
  4. 友好性提升:优化了空状态和错误状态的提示
  5. 性能优化:保持了与现有代码的兼容性,无需大规模重构

新设计简洁美观,与整体网页风格契合,同时提升了用户的操作效率和体验。