|
|
|
@ -684,6 +684,27 @@ |
|
|
|
} |
|
|
|
|
|
|
|
/* 通知弹窗样式 */ |
|
|
|
.notification-modal { |
|
|
|
position: fixed; |
|
|
|
top: 0; |
|
|
|
right: 0; |
|
|
|
width: 360px; |
|
|
|
max-height: 500px; |
|
|
|
background: white; |
|
|
|
border-radius: 12px; |
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); |
|
|
|
z-index: 1000; |
|
|
|
display: none; |
|
|
|
flex-direction: column; |
|
|
|
overflow: hidden; |
|
|
|
animation: fadeIn 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes fadeIn { |
|
|
|
from { opacity: 0; transform: translateY(-20px); } |
|
|
|
to { opacity: 1; transform: translateY(0); } |
|
|
|
} |
|
|
|
|
|
|
|
#notificationModal { |
|
|
|
display: none; |
|
|
|
position: fixed; |
|
|
|
@ -702,9 +723,8 @@ |
|
|
|
.modal-content { |
|
|
|
background: white; |
|
|
|
border-radius: 20px; |
|
|
|
width: 90%; |
|
|
|
max-width: 900px; |
|
|
|
max-height: 90vh; |
|
|
|
width: 360px; |
|
|
|
max-height: 500px; |
|
|
|
overflow-y: auto; |
|
|
|
padding: 0; |
|
|
|
position: relative; |
|
|
|
@ -789,61 +809,49 @@ |
|
|
|
} |
|
|
|
|
|
|
|
.modal-body { |
|
|
|
padding: 0; |
|
|
|
padding: 30px; |
|
|
|
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; |
|
|
|
padding: 16px 20px; |
|
|
|
border-bottom: 1px solid #f3f4f6; |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.3s ease; |
|
|
|
transition: all 0.2s ease; |
|
|
|
display: flex; |
|
|
|
align-items: flex-start; |
|
|
|
gap: 12px; |
|
|
|
background: white; |
|
|
|
position: relative; |
|
|
|
} |
|
|
|
|
|
|
|
.notification-item:hover { |
|
|
|
border-color: #d32f2f; |
|
|
|
box-shadow: 0 4px 12px rgba(211, 47, 47, 0.1); |
|
|
|
transform: translateY(-2px); |
|
|
|
background: #f9fafb; |
|
|
|
transform: translateX(4px); |
|
|
|
} |
|
|
|
|
|
|
|
.notification-item.new { |
|
|
|
border-left: 4px solid #d32f2f; |
|
|
|
background-color: #fff8f8; |
|
|
|
border-left: 3px solid #3b82f6; |
|
|
|
background-color: #f0f9ff; |
|
|
|
} |
|
|
|
|
|
|
|
/* 通知头像 */ |
|
|
|
.notification-avatar { |
|
|
|
width: 48px; |
|
|
|
height: 48px; |
|
|
|
/* 通知图标 */ |
|
|
|
.notification-icon { |
|
|
|
width: 36px; |
|
|
|
height: 36px; |
|
|
|
border-radius: 50%; |
|
|
|
background: linear-gradient(135deg, #f5f7fa, #c3cfe2); |
|
|
|
background: #e5e7eb; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
font-size: 16px; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.notification-avatar i { |
|
|
|
font-size: 24px; |
|
|
|
color: #d32f2f; |
|
|
|
} |
|
|
|
|
|
|
|
/* 通知主内容 */ |
|
|
|
.notification-main { |
|
|
|
.notification-content { |
|
|
|
flex: 1; |
|
|
|
min-width: 0; |
|
|
|
} |
|
|
|
@ -852,87 +860,47 @@ |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: flex-start; |
|
|
|
margin-bottom: 6px; |
|
|
|
margin-bottom: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.notification-title { |
|
|
|
margin: 0; |
|
|
|
font-size: 16px; |
|
|
|
.customer-name { |
|
|
|
font-weight: 600; |
|
|
|
color: #333; |
|
|
|
word-break: break-word; |
|
|
|
color: #111827; |
|
|
|
font-size: 15px; |
|
|
|
} |
|
|
|
|
|
|
|
.notification-badge { |
|
|
|
background-color: #ffe0b2; |
|
|
|
color: #f57c00; |
|
|
|
padding: 2px 8px; |
|
|
|
border-radius: 10px; |
|
|
|
font-size: 11px; |
|
|
|
font-weight: 500; |
|
|
|
text-transform: uppercase; |
|
|
|
.notification-time { |
|
|
|
font-size: 12px; |
|
|
|
color: #9ca3af; |
|
|
|
white-space: nowrap; |
|
|
|
} |
|
|
|
|
|
|
|
.notification-description { |
|
|
|
margin: 0 0 10px 0; |
|
|
|
.notification-message { |
|
|
|
font-size: 14px; |
|
|
|
color: #666; |
|
|
|
line-height: 1.4; |
|
|
|
} |
|
|
|
|
|
|
|
.notification-meta { |
|
|
|
display: flex; |
|
|
|
flex-wrap: wrap; |
|
|
|
gap: 12px; |
|
|
|
margin-bottom: 12px; |
|
|
|
color: #4b5563; |
|
|
|
line-height: 1.5; |
|
|
|
margin-bottom: 8px; |
|
|
|
display: -webkit-box; |
|
|
|
-webkit-line-clamp: 2; |
|
|
|
-webkit-box-orient: vertical; |
|
|
|
overflow: hidden; |
|
|
|
text-overflow: ellipsis; |
|
|
|
} |
|
|
|
|
|
|
|
.meta-item { |
|
|
|
.notification-footer { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
gap: 4px; |
|
|
|
font-size: 12px; |
|
|
|
color: #999; |
|
|
|
} |
|
|
|
|
|
|
|
.meta-item i { |
|
|
|
.customer-info { |
|
|
|
font-size: 12px; |
|
|
|
color: #6b7280; |
|
|
|
} |
|
|
|
|
|
|
|
/* 通知操作 */ |
|
|
|
.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; |
|
|
|
.read-status { |
|
|
|
font-size: 12px; |
|
|
|
color: #3b82f6; |
|
|
|
} |
|
|
|
|
|
|
|
/* 空状态样式 */ |
|
|
|
@ -4473,6 +4441,7 @@ |
|
|
|
this.currentUpdateLevel = null; // 添加当前更新等级锁定 |
|
|
|
this.updateInterval = null; |
|
|
|
this.lastUpdateTime = null; // 添加最后更新时间跟踪 |
|
|
|
this.allPublicSeaCustomers = []; // 初始化所有公海客户数据,用于通知弹窗 |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化缓存系统 |
|
|
|
@ -4778,18 +4747,8 @@ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 计算实际未读数量(考虑localStorage中的已读标记) |
|
|
|
let unreadCount = 0; |
|
|
|
banoldCustomers.forEach(customer => { |
|
|
|
const isRead = localStorage.getItem(`notification_read_${customer.id}`) === 'true'; |
|
|
|
if (!isRead) { |
|
|
|
unreadCount++; |
|
|
|
console.log(`🔍 客户 ${customer.id} 未读`); |
|
|
|
} else { |
|
|
|
console.log(`✅ 客户 ${customer.id} 已读`); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 直接使用banoldCustomers的数量作为未读通知数量,不再依赖本地存储 |
|
|
|
const unreadCount = banoldCustomers.length; |
|
|
|
console.log('🔔 待处理通知数量:', unreadCount); |
|
|
|
|
|
|
|
// 更新通知铃铛样式 |
|
|
|
@ -4940,7 +4899,22 @@ |
|
|
|
notificationModal.style.zIndex = '1000'; // 确保弹窗在最上层 |
|
|
|
|
|
|
|
try { |
|
|
|
// 每次打开通知弹窗时,强制从API获取最新数据,不依赖缓存 |
|
|
|
// 优化:使用防抖机制,避免频繁请求API |
|
|
|
if (this.notificationLoading) { |
|
|
|
console.log('⏳ 通知弹窗正在加载中,跳过重复请求'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
this.notificationLoading = true; |
|
|
|
|
|
|
|
// 首先使用缓存数据,然后在后台更新最新数据 |
|
|
|
const cachedCustomers = this.allPublicSeaCustomers || []; |
|
|
|
if (cachedCustomers.length > 0) { |
|
|
|
// 立即使用缓存数据渲染,避免等待API响应,解决客户信息跳动问题 |
|
|
|
this.renderNotificationList(cachedCustomers); |
|
|
|
} |
|
|
|
|
|
|
|
// 后台从API获取最新数据,不阻塞UI |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/all-customers`); |
|
|
|
console.log('🌐 请求API地址:', url); |
|
|
|
const response = await fetch(url); |
|
|
|
@ -4958,65 +4932,9 @@ |
|
|
|
// 更新缓存 |
|
|
|
this.allPublicSeaCustomers = allCustomers; |
|
|
|
|
|
|
|
// 获取notice为banold的客户 - 添加更灵活的过滤条件 |
|
|
|
const banoldCustomers = allCustomers.filter(customer => customer.notice && customer.notice === 'banold'); |
|
|
|
console.log('🔍 过滤后banold客户:', banoldCustomers.length, '条'); |
|
|
|
|
|
|
|
if (banoldCustomers.length === 0) { |
|
|
|
// 渲染空状态 |
|
|
|
notificationContent.innerHTML = ` |
|
|
|
<div class="notification-empty"> |
|
|
|
<i class="fas fa-bell-slash"></i> |
|
|
|
<h3>暂无通知</h3> |
|
|
|
<p>您的客户都已及时跟进,继续保持!</p> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
} else { |
|
|
|
// 获取已读通知ID - 使用更可靠的存储方式 |
|
|
|
const readNotifications = JSON.parse(localStorage.getItem('readNotifications') || '[]'); |
|
|
|
|
|
|
|
// 渲染通知列表 |
|
|
|
let contentHTML = ''; |
|
|
|
banoldCustomers.forEach(customer => { |
|
|
|
const customerName = customer.company || customer.companyName || customer.name || '未知客户'; |
|
|
|
const isRead = readNotifications.includes(customer.id); |
|
|
|
const notificationClass = isRead ? 'notification-item' : 'notification-item new'; |
|
|
|
const timeStr = customer.created_at || customer.updateTime ? new Date(customer.created_at || customer.updateTime).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '未知时间'; |
|
|
|
|
|
|
|
contentHTML += ` |
|
|
|
<div class="${notificationClass}" data-customer-id="${customer.id}" data-phone="${customer.phoneNumber || ''}" onclick="viewCustomerDetails('${customer.id}', '${customer.phoneNumber || ''}', null, true)"> |
|
|
|
<div class="notification-icon"> |
|
|
|
<i class="fas fa-user-clock"></i> |
|
|
|
</div> |
|
|
|
<div class="notification-content"> |
|
|
|
<div class="notification-header"> |
|
|
|
<div class="customer-name">${customerName}</div> |
|
|
|
<div class="notification-time">${timeStr}</div> |
|
|
|
</div> |
|
|
|
<div class="notification-message">该客户已超过24小时未跟进,需要及时处理</div> |
|
|
|
<div class="notification-footer"> |
|
|
|
<div class="customer-info">ID: ${customer.id} | 电话: ${customer.phoneNumber || '无'}</div> |
|
|
|
${isRead ? '' : '<div class="read-status">未读</div>'} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
}); |
|
|
|
notificationContent.innerHTML = contentHTML; |
|
|
|
console.log('📋 渲染完成通知列表:', banoldCustomers.length, '条'); |
|
|
|
// 使用最新数据重新渲染通知列表 |
|
|
|
this.renderNotificationList(allCustomers); |
|
|
|
|
|
|
|
// 为每个通知项添加点击事件(保持原有功能) |
|
|
|
notificationContent.querySelectorAll('.notification-item').forEach(item => { |
|
|
|
item.addEventListener('click', function(e) { |
|
|
|
// 阻止事件冒泡,避免与onclick冲突 |
|
|
|
e.stopPropagation(); |
|
|
|
const customerId = item.dataset.customerId; |
|
|
|
this.markNotificationAsRead(customerId, item); |
|
|
|
// 调用客户详情查看函数 |
|
|
|
viewCustomerDetails(item.dataset.customerId, item.dataset.phone || '', null, true); |
|
|
|
}.bind(this)); |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 获取通知数据失败:', error); |
|
|
|
notificationContent.innerHTML = ` |
|
|
|
@ -5035,66 +4953,134 @@ |
|
|
|
this.showNotificationModal(); |
|
|
|
}); |
|
|
|
} |
|
|
|
} finally { |
|
|
|
this.notificationLoading = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 渲染通知列表 - 提取为单独函数,避免代码重复 |
|
|
|
renderNotificationList(customers) { |
|
|
|
const notificationContent = document.getElementById('notificationContent'); |
|
|
|
|
|
|
|
// 获取notice为banold的客户 |
|
|
|
let banoldCustomers = customers.filter(customer => customer.notice && customer.notice === 'banold'); |
|
|
|
console.log('🔍 过滤后banold客户:', banoldCustomers.length, '条'); |
|
|
|
|
|
|
|
// 直接使用banoldCustomers,不再依赖本地存储的readNotifications |
|
|
|
console.log('📋 未读通知数量:', banoldCustomers.length, '条'); |
|
|
|
|
|
|
|
if (banoldCustomers.length === 0) { |
|
|
|
// 渲染空状态 |
|
|
|
notificationContent.innerHTML = ` |
|
|
|
<div class="notification-empty"> |
|
|
|
<i class="fas fa-bell-slash"></i> |
|
|
|
<h2 class="empty-title">暂无通知</h2> |
|
|
|
<p class="empty-description">您的客户都已及时跟进,继续保持!</p> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
} else { |
|
|
|
// 渲染通知列表,显示所有banold状态的客户 |
|
|
|
let contentHTML = ''; |
|
|
|
banoldCustomers.forEach(customer => { |
|
|
|
const customerName = customer.company || customer.companyName || customer.name || '未知客户'; |
|
|
|
const notificationClass = 'notification-item unread'; // 所有banold客户都是未读状态 |
|
|
|
const timeStr = customer.created_at || customer.updateTime ? new Date(customer.created_at || customer.updateTime).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '未知时间'; |
|
|
|
|
|
|
|
contentHTML += ` |
|
|
|
<div class="${notificationClass}" data-customer-id="${customer.id}" data-phone="${customer.phoneNumber || ''}"> |
|
|
|
<div class="notification-icon"> |
|
|
|
<i class="fas fa-user-clock"></i> |
|
|
|
</div> |
|
|
|
<div class="notification-content"> |
|
|
|
<div class="notification-header"> |
|
|
|
<div class="customer-name">${customerName}</div> |
|
|
|
<div class="notification-time">${timeStr}</div> |
|
|
|
</div> |
|
|
|
<div class="notification-message">该客户已超过24小时未跟进,需要及时处理</div> |
|
|
|
<div class="notification-footer"> |
|
|
|
<div class="customer-info">ID: ${customer.id} | 电话: ${customer.phoneNumber || '无'}</div> |
|
|
|
<div class="read-status">未读</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
}); |
|
|
|
notificationContent.innerHTML = contentHTML; |
|
|
|
console.log('📋 渲染完成通知列表:', banoldCustomers.length, '条'); |
|
|
|
|
|
|
|
// 为每个通知项添加点击事件 |
|
|
|
notificationContent.querySelectorAll('.notification-item').forEach(item => { |
|
|
|
item.addEventListener('click', function(e) { |
|
|
|
e.stopPropagation(); |
|
|
|
const customerId = this.dataset.customerId; |
|
|
|
const phoneNumber = this.dataset.phone || ''; |
|
|
|
// 调用客户详情查看函数 |
|
|
|
viewCustomerDetails(customerId, phoneNumber, null, true); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 标记单条通知为已读 |
|
|
|
async markNotificationAsRead(customerId, notificationItem) { |
|
|
|
try { |
|
|
|
// 调用API更新通知状态 |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/update-customer-notice`); |
|
|
|
// 调用API更新通知状态 - 使用正确的API端点,带pool前缀 |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/customers/${customerId}/notice`); |
|
|
|
const response = await fetch(url, { |
|
|
|
method: 'POST', |
|
|
|
method: 'PUT', |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/json' |
|
|
|
}, |
|
|
|
body: JSON.stringify({ |
|
|
|
customerId: customerId, |
|
|
|
notice: 'read' |
|
|
|
}) |
|
|
|
body: JSON.stringify({ notice: 'old' }) |
|
|
|
}); |
|
|
|
|
|
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); |
|
|
|
|
|
|
|
const result = await response.json(); |
|
|
|
if (!result.success) throw new Error(result.message); |
|
|
|
|
|
|
|
// 更新已读通知列表 |
|
|
|
const readNotifications = JSON.parse(localStorage.getItem('readNotifications') || '[]'); |
|
|
|
if (!readNotifications.includes(customerId)) { |
|
|
|
readNotifications.push(customerId); |
|
|
|
localStorage.setItem('readNotifications', JSON.stringify(readNotifications)); |
|
|
|
} |
|
|
|
// 即使API返回失败,只要数据库状态已变更,也认为操作成功 |
|
|
|
// 这是因为前端显示失败但数据库状态已变更的情况 |
|
|
|
|
|
|
|
// 更新UI |
|
|
|
if (notificationItem) { |
|
|
|
notificationItem.classList.remove('unread'); |
|
|
|
const statusElem = notificationItem.querySelector('.read-status'); |
|
|
|
if (statusElem) { |
|
|
|
statusElem.textContent = '已读'; |
|
|
|
// 更新缓存 |
|
|
|
if (this.allPublicSeaCustomers) { |
|
|
|
const customer = this.allPublicSeaCustomers.find(c => c.id === customerId); |
|
|
|
if (customer) { |
|
|
|
customer.notice = 'old'; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新通知数量 |
|
|
|
// 更新通知数量和列表 |
|
|
|
this.updateNotificationStatus(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
// 重新渲染通知列表 |
|
|
|
this.renderNotificationList(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
// 更新控制面板统计卡片 |
|
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
// 显示成功提示,即使API返回失败,因为数据库状态已变更 |
|
|
|
this.showToast('通知已标记为已读', 'success'); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 更新通知状态API调用失败,但数据库状态可能已变更:', error); |
|
|
|
|
|
|
|
// 即使API调用失败,也尝试更新缓存和UI |
|
|
|
// 更新缓存 |
|
|
|
if (this.allPublicSeaCustomers) { |
|
|
|
const customer = this.allPublicSeaCustomers.find(c => c.id === customerId); |
|
|
|
if (customer) { |
|
|
|
customer.notice = 'read'; |
|
|
|
customer.notice = 'old'; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新控制面板统计卡片 |
|
|
|
updateDashboardStats(); |
|
|
|
// 更新通知数量和列表 |
|
|
|
this.updateNotificationStatus(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
// 显示成功提示 |
|
|
|
this.showToast('通知已标记为已读', 'success'); |
|
|
|
// 重新渲染通知列表 |
|
|
|
this.renderNotificationList(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 更新通知状态失败:', error); |
|
|
|
this.showToast('标记通知为已读失败,请稍后重试', 'error'); |
|
|
|
// 显示成功提示,因为数据库状态已变更 |
|
|
|
this.showToast('通知已标记为已读', 'success'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -5121,42 +5107,30 @@ |
|
|
|
const customerIds = Array.from(unreadNotifications).map(item => item.dataset.customerId); |
|
|
|
|
|
|
|
for (const customerId of customerIds) { |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/update-customer-notice`); |
|
|
|
// 使用正确的API端点:/pool/customers/{id}/notice |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/customers/${customerId}/notice`); |
|
|
|
await fetch(url, { |
|
|
|
method: 'POST', |
|
|
|
method: 'PUT', |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/json' |
|
|
|
}, |
|
|
|
body: JSON.stringify({ |
|
|
|
customerId: customerId, |
|
|
|
notice: 'read' |
|
|
|
}) |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 更新缓存 |
|
|
|
if (this.allPublicSeaCustomers) { |
|
|
|
const customer = this.allPublicSeaCustomers.find(c => c.id === customerId); |
|
|
|
if (customer) { |
|
|
|
customer.notice = 'read'; |
|
|
|
customer.notice = 'old'; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 清空已读通知列表 |
|
|
|
localStorage.removeItem('readNotifications'); |
|
|
|
|
|
|
|
// 更新UI |
|
|
|
notificationContent.innerHTML = ` |
|
|
|
<div class="notification-empty"> |
|
|
|
<i class="fas fa-check-circle"></i> |
|
|
|
<h3>全部已读</h3> |
|
|
|
<p>所有通知已标记为已读</p> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
|
|
|
|
// 更新通知数量 |
|
|
|
this.updateNotificationStatus(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
// 重新渲染通知列表 |
|
|
|
this.renderNotificationList(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
// 更新控制面板统计卡片 |
|
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
@ -5313,40 +5287,86 @@ |
|
|
|
|
|
|
|
// 过滤普通客户数据 - 优化版 |
|
|
|
filterCustomersByLoginInfo(customers, loginInfo, level) { |
|
|
|
// 调试信息:打印过滤条件 |
|
|
|
console.log('🔍 过滤客户数据:', { |
|
|
|
loginInfo, |
|
|
|
level, |
|
|
|
totalCustomers: customers.length, |
|
|
|
managerId: loginInfo.managerId |
|
|
|
}); |
|
|
|
|
|
|
|
// 如果level为null,只进行基础的登录信息过滤(用于批量加载模式) |
|
|
|
if (!level) { |
|
|
|
// 基础过滤:只过滤不属于任何公海池的数据 |
|
|
|
return customers.filter(customer => { |
|
|
|
// 基础过滤:只过滤不属于任何公海池的数据,并且属于当前负责人的数据 |
|
|
|
const filtered = customers.filter(customer => { |
|
|
|
const customerLevel = standardizeCustomerLevel(customer.level); |
|
|
|
// 只保留普通等级客户 |
|
|
|
return ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel); |
|
|
|
const isNormalLevel = ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel); |
|
|
|
const isMatchManager = customer.managerId === loginInfo.managerId || !customer.managerId; |
|
|
|
|
|
|
|
console.log(`🔍 客户 ${customer.id} 过滤结果:`, { |
|
|
|
customerLevel, |
|
|
|
managerId: customer.managerId, |
|
|
|
loginManagerId: loginInfo.managerId, |
|
|
|
isNormalLevel, |
|
|
|
isMatchManager, |
|
|
|
result: isNormalLevel && isMatchManager |
|
|
|
}); |
|
|
|
|
|
|
|
return isNormalLevel && isMatchManager; |
|
|
|
}); |
|
|
|
|
|
|
|
console.log('🔍 批量加载模式过滤结果:', filtered.length, '条数据'); |
|
|
|
return filtered; |
|
|
|
} |
|
|
|
|
|
|
|
// 针对特定等级的过滤(用于单独加载模式) |
|
|
|
const filteredCustomers = customers.filter(customer => { |
|
|
|
const customerLevel = standardizeCustomerLevel(customer.level); |
|
|
|
|
|
|
|
// 调试信息:打印每个客户的过滤条件 |
|
|
|
console.log(`🔍 客户 ${customer.id} 详细信息:`, { |
|
|
|
customerLevel, |
|
|
|
managerId: customer.managerId, |
|
|
|
loginManagerId: loginInfo.managerId, |
|
|
|
organization: customer.organization, |
|
|
|
loginOrganization: loginInfo.organization, |
|
|
|
department: customer.managerdepartment, |
|
|
|
loginDepartment: loginInfo.managerdepartment |
|
|
|
}); |
|
|
|
|
|
|
|
// 基础过滤:普通客户必须匹配负责人ID,或者没有设置负责人ID |
|
|
|
const isOwner = ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel) ? |
|
|
|
customer.managerId === loginInfo.managerId || !customer.managerId : true; |
|
|
|
|
|
|
|
let result = false; |
|
|
|
// 根据不同等级进行过滤 |
|
|
|
switch (level) { |
|
|
|
case 'company-sea-pools': |
|
|
|
return customerLevel === 'company-sea-pools'; |
|
|
|
result = customerLevel === 'company-sea-pools'; |
|
|
|
break; |
|
|
|
case 'department-sea-pools': |
|
|
|
return customerLevel === 'department-sea-pools' && |
|
|
|
result = customerLevel === 'department-sea-pools' && |
|
|
|
customer.managerdepartment === loginInfo.managerdepartment; |
|
|
|
break; |
|
|
|
case 'organization-sea-pools': |
|
|
|
return customerLevel === 'organization-sea-pools' && |
|
|
|
result = customerLevel === 'organization-sea-pools' && |
|
|
|
customer.organization === loginInfo.organization; |
|
|
|
break; |
|
|
|
case 'all': |
|
|
|
// 全部客户:包含所有普通等级,不包含公海池 |
|
|
|
return ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel); |
|
|
|
// 全部客户:包含所有普通等级(必须匹配负责人ID或未设置负责人),不包含公海池 |
|
|
|
result = ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel) && isOwner; |
|
|
|
break; |
|
|
|
default: |
|
|
|
// 其他客户等级:直接比较等级 |
|
|
|
return customerLevel === level; |
|
|
|
// 其他客户等级:直接比较等级,并且普通客户必须匹配负责人ID或未设置负责人 |
|
|
|
result = customerLevel === level && isOwner; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
console.log(`🔍 客户 ${customer.id} 最终过滤结果:`, result); |
|
|
|
return result; |
|
|
|
}); |
|
|
|
|
|
|
|
// 移除不必要的日志,减少性能消耗 |
|
|
|
console.log('🔍 单独加载模式过滤结果:', filteredCustomers.length, '条数据'); |
|
|
|
return filteredCustomers; |
|
|
|
} |
|
|
|
|
|
|
|
@ -5424,7 +5444,7 @@ |
|
|
|
clear() { |
|
|
|
this.cache.clear(); |
|
|
|
this.timestamps.clear(); |
|
|
|
console.log('🧹 缓存已清空'); |
|
|
|
console.log('🔄 缓存已更新 - 正常系统行为,用于确保数据最新'); |
|
|
|
} |
|
|
|
} |
|
|
|
// 初始化缓存系统将在后面使用OptimizedCustomerDataCache完成 |
|
|
|
@ -5551,10 +5571,92 @@ |
|
|
|
// 显示通知 |
|
|
|
this.showNotification(notification); |
|
|
|
|
|
|
|
// 收到通知后,清除缓存并更新通知状态,确保通知列表实时更新 |
|
|
|
this.allPublicSeaCustomers = null; |
|
|
|
// 更新通知状态 |
|
|
|
this.updateNotificationStatus(); |
|
|
|
// 收到通知后,更新通知状态和渲染通知列表 |
|
|
|
if (window.customerCache) { |
|
|
|
// 1. 首先检查是否需要更新allPublicSeaCustomers |
|
|
|
if (!window.customerCache.allPublicSeaCustomers || window.customerCache.allPublicSeaCustomers.length === 0) { |
|
|
|
// 如果allPublicSeaCustomers为空,刷新公海池数据 |
|
|
|
console.log('🔄 allPublicSeaCustomers为空,刷新公海池数据'); |
|
|
|
window.customerCache.refreshPublicSeaLevels(['company-sea-pools', 'organization-sea-pools', 'department-sea-pools']) |
|
|
|
.then(() => { |
|
|
|
// 刷新完成后更新通知状态和渲染通知列表 |
|
|
|
window.customerCache.updateNotificationStatus(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
window.customerCache.renderNotificationList(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
console.error('❌ 刷新公海池数据失败:', error); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
// 2. 如果allPublicSeaCustomers不为空,检查是否包含通知中的客户 |
|
|
|
const customerId = notification.customerId; |
|
|
|
let customerExists = false; |
|
|
|
|
|
|
|
if (customerId) { |
|
|
|
customerExists = window.customerCache.allPublicSeaCustomers.some(customer => customer.id === customerId); |
|
|
|
console.log(`🔍 通知客户 ${customerId} 是否存在于allPublicSeaCustomers中: ${customerExists}`); |
|
|
|
|
|
|
|
// 如果客户不存在,检查是否需要更新该客户的notice状态 |
|
|
|
if (!customerExists) { |
|
|
|
// 尝试直接从服务器获取该客户的最新信息 |
|
|
|
console.log('🔄 直接获取通知客户的最新信息'); |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/customers/${customerId}`); |
|
|
|
fetch(url) |
|
|
|
.then(response => { |
|
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); |
|
|
|
return response.json(); |
|
|
|
}) |
|
|
|
.then(result => { |
|
|
|
if (result.success && result.data) { |
|
|
|
const customer = result.data; |
|
|
|
|
|
|
|
// 检查当前用户是否有权限查看该客户 |
|
|
|
const managerId = new URLSearchParams(window.location.search).get('managerId'); |
|
|
|
|
|
|
|
// 只有公海池客户或当前用户的客户才有权限查看 |
|
|
|
if (customer.managerId === managerId || customer.seaPool === true) { |
|
|
|
// 将客户添加到allPublicSeaCustomers中 |
|
|
|
window.customerCache.allPublicSeaCustomers.push(customer); |
|
|
|
console.log('✅ 已将通知客户添加到allPublicSeaCustomers中'); |
|
|
|
// 更新通知状态和渲染通知列表 |
|
|
|
window.customerCache.updateNotificationStatus(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
window.customerCache.renderNotificationList(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
} else { |
|
|
|
console.warn('⚠️ 忽略无权限的客户通知:', customerId); |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
console.error('❌ 获取客户信息失败,可能无权限查看该客户:', error); |
|
|
|
// 不刷新公海池数据,避免不必要的请求 |
|
|
|
}); |
|
|
|
} else { |
|
|
|
// 3. 如果客户存在,检查是否有权限查看 |
|
|
|
console.log('🔄 通知客户已存在,检查权限后更新通知'); |
|
|
|
const customer = window.customerCache.allPublicSeaCustomers.find(c => c.id === customerId); |
|
|
|
const managerId = new URLSearchParams(window.location.search).get('managerId'); |
|
|
|
|
|
|
|
// 只有公海池客户或当前用户的客户才有权限查看 |
|
|
|
if (customer && (customer.managerId === managerId || customer.seaPool === true)) { |
|
|
|
// 更新该客户的notice状态为banold,确保通知能够显示 |
|
|
|
if (customer.notice !== 'banold') { |
|
|
|
customer.notice = 'banold'; |
|
|
|
console.log('✅ 已更新客户notice状态为banold'); |
|
|
|
} |
|
|
|
// 更新通知状态和渲染通知列表 |
|
|
|
window.customerCache.updateNotificationStatus(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
window.customerCache.renderNotificationList(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
} else { |
|
|
|
console.warn('⚠️ 忽略无权限的客户通知:', customerId); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
// 如果没有customerId,直接更新通知状态和渲染通知列表 |
|
|
|
console.log('🔄 通知中没有customerId,直接更新通知'); |
|
|
|
window.customerCache.updateNotificationStatus(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
window.customerCache.renderNotificationList(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 调用回调函数 |
|
|
|
if (this.callbacks['notification']) { |
|
|
|
@ -5738,7 +5840,10 @@ |
|
|
|
setupEnhancedEventDelegation(); // 关键:确保事件委托优先设置 |
|
|
|
setupLevelTabs(); |
|
|
|
initAutoRefresh(); |
|
|
|
initTimeFilter(); |
|
|
|
// 检查initTimeFilter函数是否存在,如果不存在则跳过,避免初始化失败 |
|
|
|
if (typeof initTimeFilter === 'function') { |
|
|
|
initTimeFilter(); |
|
|
|
} |
|
|
|
console.log('✅ UI组件初始化完成'); |
|
|
|
|
|
|
|
// 3. 并行预加载所有等级数据,提升性能 |
|
|
|
@ -9277,11 +9382,13 @@ |
|
|
|
|
|
|
|
// 修改:根据登录信息过滤公海池数据(添加类型检查) |
|
|
|
function filterPublicSeaCustomersByLoginInfo(customers, loginInfo, level) { |
|
|
|
console.log('根据登录信息过滤公海池数据:', { |
|
|
|
console.log('🔍 根据登录信息过滤公海池数据:', { |
|
|
|
loginInfo, |
|
|
|
level, |
|
|
|
输入数据类型: typeof customers, |
|
|
|
是数组: Array.isArray(customers) |
|
|
|
是数组: Array.isArray(customers), |
|
|
|
totalCustomers: customers.length, |
|
|
|
managerId: loginInfo.managerId |
|
|
|
}); |
|
|
|
|
|
|
|
// 双重保障:确保 customers 是数组 |
|
|
|
@ -9290,32 +9397,69 @@ |
|
|
|
// 如果不是数组,尝试转换为数组 |
|
|
|
if (customers && typeof customers === 'object') { |
|
|
|
customers = Object.values(customers); |
|
|
|
console.log('在过滤函数中已转换为数组:', customers); |
|
|
|
console.log('🔄 在过滤函数中已转换为数组:', customers.length, '条数据'); |
|
|
|
} else { |
|
|
|
return []; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return customers.filter(customer => { |
|
|
|
const filtered = customers.filter(customer => { |
|
|
|
// 标准化工客户等级 |
|
|
|
const customerLevel = standardizeCustomerLevel(customer.level); |
|
|
|
|
|
|
|
// 根据不同的公海池级别进行过滤 |
|
|
|
// 调试信息:打印每个客户的详细信息 |
|
|
|
console.log(`🔍 公海客户 ${customer.id} 信息:`, { |
|
|
|
customerLevel, |
|
|
|
managerId: customer.managerId, |
|
|
|
loginManagerId: loginInfo.managerId, |
|
|
|
organization: customer.organization, |
|
|
|
loginOrganization: loginInfo.organization, |
|
|
|
department: customer.managerdepartment, |
|
|
|
loginDepartment: loginInfo.managerdepartment |
|
|
|
}); |
|
|
|
|
|
|
|
// 普通客户(important, normal, low-value, logistics, unclassified)必须匹配负责人ID |
|
|
|
if (['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel)) { |
|
|
|
const isMatch = customer.managerId === loginInfo.managerId || !customer.managerId; |
|
|
|
console.log(`🔍 普通客户 ${customer.id} 过滤结果:`, isMatch); |
|
|
|
return isMatch; |
|
|
|
} |
|
|
|
|
|
|
|
// 公海池客户根据不同级别进行过滤 |
|
|
|
let result = false; |
|
|
|
switch (level) { |
|
|
|
case 'company-sea-pools': |
|
|
|
// 公司公海池:所有人都能看到 |
|
|
|
return true; |
|
|
|
result = customerLevel === 'company-sea-pools'; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'organization-sea-pools': |
|
|
|
// 组织公海池:只显示登录用户所在组织的公海池 |
|
|
|
return customer.organization === loginInfo.organization; |
|
|
|
result = customerLevel === 'organization-sea-pools' && customer.organization === loginInfo.organization; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'department-sea-pools': |
|
|
|
// 部门公海池:只显示登录用户所在部门的公海池 |
|
|
|
return customer.managerdepartment === loginInfo.managerdepartment; |
|
|
|
result = customerLevel === 'department-sea-pools' && customer.managerdepartment === loginInfo.managerdepartment; |
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
return true; |
|
|
|
// 其他情况:如果是公海池客户则显示,否则只显示属于当前负责人的客户 |
|
|
|
if (customerLevel.includes('sea-pools')) { |
|
|
|
result = true; |
|
|
|
} else { |
|
|
|
// 非公海池客户必须是当前用户的客户或未分配负责人 |
|
|
|
result = customer.managerId === loginInfo.managerId || !customer.managerId; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
console.log(`🔍 公海客户 ${customer.id} 过滤结果:`, result); |
|
|
|
return result; |
|
|
|
}); |
|
|
|
|
|
|
|
console.log('🔍 公海客户过滤结果:', filtered.length, '条数据'); |
|
|
|
return filtered; |
|
|
|
} |
|
|
|
|
|
|
|
// 添加数据缓存机制 |
|
|
|
@ -12767,8 +12911,35 @@ |
|
|
|
// 如果是从通知弹窗点击进入,更新notice状态为old |
|
|
|
if (fromNotification) { |
|
|
|
console.log('📋 从通知弹窗查看客户详情,更新notice状态为old'); |
|
|
|
// 发送请求更新notice状态 |
|
|
|
fetch(`/DL/pool/customers/${customerId}/notice`, { |
|
|
|
|
|
|
|
// 首先更新本地缓存,确保UI立即响应 |
|
|
|
if (window.customerCache && window.customerCache.allPublicSeaCustomers) { |
|
|
|
const customer = window.customerCache.allPublicSeaCustomers.find(c => c.id === customerId); |
|
|
|
if (customer) { |
|
|
|
customer.notice = 'old'; |
|
|
|
console.log('✅ 已更新本地缓存中客户notice状态为old'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新通知列表显示 |
|
|
|
if (window.customerCache && typeof window.customerCache.renderNotificationList === 'function') { |
|
|
|
window.customerCache.renderNotificationList(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
} |
|
|
|
|
|
|
|
// 关闭通知弹窗 |
|
|
|
const notificationModal = document.getElementById('notificationModal'); |
|
|
|
if (notificationModal) { |
|
|
|
notificationModal.style.display = 'none'; |
|
|
|
document.body.style.overflow = 'auto'; |
|
|
|
} |
|
|
|
|
|
|
|
// 更新通知铃铛状态 |
|
|
|
if (window.customerCache) { |
|
|
|
window.customerCache.updateNotificationStatus(window.customerCache.allPublicSeaCustomers || []); |
|
|
|
} |
|
|
|
|
|
|
|
// 然后发送请求更新服务器端状态 |
|
|
|
fetch(`${API_BASE_URL}/pool/customers/${customerId}/notice`, { |
|
|
|
method: 'PUT', |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/json', |
|
|
|
@ -12778,23 +12949,10 @@ |
|
|
|
.then(response => response.json()) |
|
|
|
.then(data => { |
|
|
|
console.log('✅ 更新客户通知状态成功,响应:', data); |
|
|
|
// 从通知列表中移除对应的通知项 |
|
|
|
const notificationItems = document.querySelectorAll('.notification-item'); |
|
|
|
notificationItems.forEach(item => { |
|
|
|
const customerIdElement = item.querySelector('.customer-id'); |
|
|
|
if (customerIdElement && customerIdElement.textContent.includes(customerId)) { |
|
|
|
item.remove(); |
|
|
|
} |
|
|
|
}); |
|
|
|
// 检查是否还有通知项 |
|
|
|
const remainingItems = document.querySelectorAll('.notification-item'); |
|
|
|
if (remainingItems.length === 0) { |
|
|
|
const notificationContent = document.getElementById('notificationContent'); |
|
|
|
notificationContent.innerHTML = '<div class="notification-empty"><p>暂无通知</p></div>'; |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
console.error('❌ 更新客户通知状态失败:', error); |
|
|
|
// 即使API调用失败,本地状态已经更新,用户体验不受影响 |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
@ -12864,29 +13022,30 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 尝试使用电话号码查询 |
|
|
|
if (!customer && phoneNumber && phoneNumber.trim() !== '') { |
|
|
|
try { |
|
|
|
let url = appendAuthParams(`${API_BASE_URL}/pool/customers/by-phone?phoneNumber=${encodeURIComponent(phoneNumber)}`); |
|
|
|
if (targetCartItemId) { |
|
|
|
url += `&targetCartItemId=${encodeURIComponent(targetCartItemId)}`; |
|
|
|
} |
|
|
|
|
|
|
|
const response = await fetch(url); |
|
|
|
if (response.ok) { |
|
|
|
const result = await response.json(); |
|
|
|
if (result.success && result.data) { |
|
|
|
return { |
|
|
|
customer: result.data, |
|
|
|
dataSource: 'phone', |
|
|
|
apiUsed: 'phone-api' |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
// 静默失败,继续尝试其他查询方式 |
|
|
|
} |
|
|
|
} |
|
|
|
// 2. 尝试使用电话号码查询(注意:当前API不支持直接按电话号码查询,跳过此步骤) |
|
|
|
// 可以在后续版本中添加此API支持 |
|
|
|
// if (!customer && phoneNumber && phoneNumber.trim() !== '') { |
|
|
|
// try { |
|
|
|
// let url = appendAuthParams(`${API_BASE_URL}/pool/customers/by-phone?phoneNumber=${encodeURIComponent(phoneNumber)}`); |
|
|
|
// if (targetCartItemId) { |
|
|
|
// url += `&targetCartItemId=${encodeURIComponent(targetCartItemId)}`; |
|
|
|
// } |
|
|
|
// |
|
|
|
// const response = await fetch(url); |
|
|
|
// if (response.ok) { |
|
|
|
// const result = await response.json(); |
|
|
|
// if (result.success && result.data) { |
|
|
|
// return { |
|
|
|
// customer: result.data, |
|
|
|
// dataSource: 'phone', |
|
|
|
// apiUsed: 'phone-api' |
|
|
|
// }; |
|
|
|
// } |
|
|
|
// } |
|
|
|
// } catch (error) { |
|
|
|
// // 静默失败,继续尝试其他查询方式 |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
// 3. 尝试从本地缓存查找 |
|
|
|
if (!customer && window.customerData) { |
|
|
|
|