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.
839 lines
22 KiB
839 lines
22 KiB
|
3 months ago
|
# 客户通知弹窗重新设计方案
|
||
|
|
|
||
|
|
## 一、设计思路
|
||
|
|
|
||
|
|
### 1.1 整体设计理念
|
||
|
|
- **简洁性**:去除冗余元素,突出核心信息
|
||
|
|
- **一致性**:与现有网页风格保持统一,使用相同的配色方案和设计语言
|
||
|
|
- **易用性**:优化交互流程,提升用户体验
|
||
|
|
- **美观性**:采用现代化设计,增强视觉吸引力
|
||
|
|
|
||
|
|
### 1.2 设计目标
|
||
|
|
- 重新设计通知按钮和数量徽章
|
||
|
|
- 优化通知弹窗的布局和样式
|
||
|
|
- 改进通知项的渲染格式
|
||
|
|
- 增强用户交互反馈
|
||
|
|
- 确保响应式设计适配不同屏幕尺寸
|
||
|
|
|
||
|
|
## 二、实现思路
|
||
|
|
|
||
|
|
### 2.1 技术栈
|
||
|
|
- HTML5 + CSS3 + JavaScript
|
||
|
|
- 保持与现有代码结构的兼容性
|
||
|
|
- 利用Font Awesome图标库
|
||
|
|
|
||
|
|
### 2.2 核心功能实现
|
||
|
|
- 通知按钮的视觉增强和动画效果
|
||
|
|
- 通知弹窗的布局优化
|
||
|
|
- 通知项的卡片式设计
|
||
|
|
- 通知状态的交互更新
|
||
|
|
- 空状态的友好提示
|
||
|
|
|
||
|
|
## 三、设计图
|
||
|
|
|
||
|
|
### 3.1 通知按钮设计
|
||
|
|
|
||
|
|
#### 当前设计
|
||
|
|
```html
|
||
|
|
<button class="notification-btn" id="notificationButton">
|
||
|
|
<i class="fas fa-bell"></i>
|
||
|
|
</button>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 新设计
|
||
|
|
```html
|
||
|
|
<button class="notification-btn" id="notificationButton">
|
||
|
|
<i class="fas fa-bell"></i>
|
||
|
|
<span class="notification-count">3</span>
|
||
|
|
</button>
|
||
|
|
```
|
||
|
|
|
||
|
|
**设计说明**:
|
||
|
|
- 保留铃铛图标,但增加悬停效果
|
||
|
|
- 优化数量徽章的位置和样式
|
||
|
|
- 为有新通知的状态添加动画效果
|
||
|
|
|
||
|
|
### 3.2 通知弹窗设计
|
||
|
|
|
||
|
|
#### 当前设计
|
||
|
|
```html
|
||
|
|
<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>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 新设计
|
||
|
|
```html
|
||
|
|
<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 通知项渲染格式
|
||
|
|
|
||
|
|
#### 当前渲染格式
|
||
|
|
```html
|
||
|
|
<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>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 新渲染格式
|
||
|
|
```html
|
||
|
|
<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 空状态设计
|
||
|
|
|
||
|
|
#### 当前设计
|
||
|
|
```html
|
||
|
|
<div class="notification-empty"><p>暂无通知</p></div>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 新设计
|
||
|
|
```html
|
||
|
|
<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样式实现
|
||
|
|
|
||
|
|
```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 = '<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. **性能优化**:保持了与现有代码的兼容性,无需大规模重构
|
||
|
|
|
||
|
|
新设计简洁美观,与整体网页风格契合,同时提升了用户的操作效率和体验。
|