|
|
|
@ -4763,14 +4763,34 @@ |
|
|
|
// 更新通知铃铛状态 |
|
|
|
updateNotificationStatus(customers) { |
|
|
|
// 统计notice为banold的客户数量 |
|
|
|
let banoldCustomers = []; |
|
|
|
|
|
|
|
// 如果没有提供customers参数,尝试使用缓存数据 |
|
|
|
if (customers) { |
|
|
|
console.log('📊 更新通知状态 - 客户列表:', customers.length, '个客户'); |
|
|
|
const totalNotifications = customers.filter(customer => customer.notice === 'banold').length; |
|
|
|
console.log('🔔 待处理通知数量:', totalNotifications); |
|
|
|
banoldCustomers = customers.filter(customer => customer.notice === 'banold'); |
|
|
|
} else if (this.allPublicSeaCustomers) { |
|
|
|
console.log('📊 更新通知状态 - 使用缓存数据:', this.allPublicSeaCustomers.length, '个客户'); |
|
|
|
banoldCustomers = this.allPublicSeaCustomers.filter(customer => customer.notice === 'banold'); |
|
|
|
} else { |
|
|
|
console.log('📊 更新通知状态 - 无客户数据,直接返回'); |
|
|
|
// 如果没有数据,不更新通知状态 |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 获取已读通知ID |
|
|
|
const readNotifications = JSON.parse(localStorage.getItem('readNotifications') || '[]'); |
|
|
|
// 计算未读通知数量 |
|
|
|
const unreadCount = Math.max(0, totalNotifications - readNotifications.length); |
|
|
|
// 计算实际未读数量(考虑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} 已读`); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
console.log('🔔 待处理通知数量:', unreadCount); |
|
|
|
|
|
|
|
// 更新通知铃铛样式 |
|
|
|
const notificationButton = document.getElementById('notificationButton'); |
|
|
|
@ -4780,7 +4800,16 @@ |
|
|
|
if (bellIcon) { |
|
|
|
if (unreadCount > 0) { |
|
|
|
notificationButton.classList.add('notification-active'); |
|
|
|
// 只在首次激活时添加动画,避免重复闪烁 |
|
|
|
if (!notificationButton.classList.contains('animation-added')) { |
|
|
|
bellIcon.style.animation = 'ring 1s ease-in-out'; |
|
|
|
notificationButton.classList.add('animation-added'); |
|
|
|
// 动画结束后移除标记 |
|
|
|
setTimeout(() => { |
|
|
|
notificationButton.classList.remove('animation-added'); |
|
|
|
bellIcon.style.animation = 'none'; |
|
|
|
}, 1000); |
|
|
|
} |
|
|
|
|
|
|
|
// 添加或更新通知数量显示 |
|
|
|
let countBadge = notificationButton.querySelector('.notification-count'); |
|
|
|
@ -4793,6 +4822,7 @@ |
|
|
|
} else { |
|
|
|
notificationButton.classList.remove('notification-active'); |
|
|
|
bellIcon.style.animation = 'none'; |
|
|
|
notificationButton.classList.remove('animation-added'); |
|
|
|
|
|
|
|
// 移除通知数量显示 |
|
|
|
const countBadge = notificationButton.querySelector('.notification-count'); |
|
|
|
@ -4809,6 +4839,25 @@ |
|
|
|
return unreadCount; |
|
|
|
} |
|
|
|
|
|
|
|
// 标记通知为已读 |
|
|
|
markNotificationAsRead(customerId) { |
|
|
|
// 标记通知为已读 |
|
|
|
localStorage.setItem(`notification_read_${customerId}`, 'true'); |
|
|
|
|
|
|
|
// 更新界面显示 |
|
|
|
const notificationItem = document.querySelector(`[data-customer-id="${customerId}"]`); |
|
|
|
if (notificationItem) { |
|
|
|
notificationItem.classList.remove('unread'); |
|
|
|
const readStatus = notificationItem.querySelector('.read-status'); |
|
|
|
if (readStatus) { |
|
|
|
readStatus.remove(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新通知状态 |
|
|
|
this.updateNotificationStatus(this.allPublicSeaCustomers || []); |
|
|
|
} |
|
|
|
|
|
|
|
// 获取公海池数据的辅助方法 |
|
|
|
async fetchPublicSeaData(loginInfo, level) { |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/all-customers`); |
|
|
|
@ -4891,13 +4940,8 @@ |
|
|
|
notificationModal.style.zIndex = '1000'; // 确保弹窗在最上层 |
|
|
|
|
|
|
|
try { |
|
|
|
// 先尝试从缓存获取数据 |
|
|
|
let allCustomers = []; |
|
|
|
if (this.allPublicSeaCustomers) { |
|
|
|
allCustomers = this.allPublicSeaCustomers; |
|
|
|
} else { |
|
|
|
// 如果缓存中没有数据,直接从API获取 |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/supply/pool/all-customers`); |
|
|
|
// 每次打开通知弹窗时,强制从API获取最新数据,不依赖缓存 |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/all-customers`); |
|
|
|
console.log('🌐 请求API地址:', url); |
|
|
|
const response = await fetch(url); |
|
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); |
|
|
|
@ -4908,15 +4952,15 @@ |
|
|
|
if (!result.success) throw new Error(result.message); |
|
|
|
|
|
|
|
const data = result.data || {}; |
|
|
|
allCustomers = Array.isArray(data) ? data : Object.values(data); |
|
|
|
const allCustomers = Array.isArray(data) ? data : Object.values(data); |
|
|
|
console.log('🔄 转换后客户数组:', allCustomers.length, '条'); |
|
|
|
|
|
|
|
// 更新缓存 |
|
|
|
this.allPublicSeaCustomers = allCustomers; |
|
|
|
} |
|
|
|
|
|
|
|
// 获取notice为banold的客户 |
|
|
|
const banoldCustomers = allCustomers.filter(customer => customer.notice === 'banold'); |
|
|
|
// 获取notice为banold的客户 - 添加更灵活的过滤条件 |
|
|
|
const banoldCustomers = allCustomers.filter(customer => customer.notice && customer.notice === 'banold'); |
|
|
|
console.log('🔍 过滤后banold客户:', banoldCustomers.length, '条'); |
|
|
|
|
|
|
|
if (banoldCustomers.length === 0) { |
|
|
|
// 渲染空状态 |
|
|
|
@ -4928,16 +4972,16 @@ |
|
|
|
</div> |
|
|
|
`; |
|
|
|
} else { |
|
|
|
// 获取已读通知ID |
|
|
|
// 获取已读通知ID - 使用更可靠的存储方式 |
|
|
|
const readNotifications = JSON.parse(localStorage.getItem('readNotifications') || '[]'); |
|
|
|
|
|
|
|
// 渲染通知列表 |
|
|
|
let contentHTML = ''; |
|
|
|
banoldCustomers.forEach(customer => { |
|
|
|
const customerName = customer.company || customer.companyName || '未知客户'; |
|
|
|
const customerName = customer.company || customer.companyName || customer.name || '未知客户'; |
|
|
|
const isRead = readNotifications.includes(customer.id); |
|
|
|
const notificationClass = isRead ? 'notification-item' : 'notification-item unread'; |
|
|
|
const timeStr = customer.created_at ? new Date(customer.created_at).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '未知时间'; |
|
|
|
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)"> |
|
|
|
@ -4959,6 +5003,7 @@ |
|
|
|
`; |
|
|
|
}); |
|
|
|
notificationContent.innerHTML = contentHTML; |
|
|
|
console.log('📋 渲染完成通知列表:', banoldCustomers.length, '条'); |
|
|
|
|
|
|
|
// 为每个通知项添加点击事件(保持原有功能) |
|
|
|
notificationContent.querySelectorAll('.notification-item').forEach(item => { |
|
|
|
@ -4997,7 +5042,7 @@ |
|
|
|
async markNotificationAsRead(customerId, notificationItem) { |
|
|
|
try { |
|
|
|
// 调用API更新通知状态 |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/supply/pool/update-customer-notice`); |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/update-customer-notice`); |
|
|
|
const response = await fetch(url, { |
|
|
|
method: 'POST', |
|
|
|
headers: { |
|
|
|
@ -5041,6 +5086,9 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新控制面板统计卡片 |
|
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
// 显示成功提示 |
|
|
|
this.showToast('通知已标记为已读', 'success'); |
|
|
|
|
|
|
|
@ -5073,7 +5121,7 @@ |
|
|
|
const customerIds = Array.from(unreadNotifications).map(item => item.dataset.customerId); |
|
|
|
|
|
|
|
for (const customerId of customerIds) { |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/supply/pool/update-customer-notice`); |
|
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/update-customer-notice`); |
|
|
|
await fetch(url, { |
|
|
|
method: 'POST', |
|
|
|
headers: { |
|
|
|
@ -5109,6 +5157,9 @@ |
|
|
|
// 更新通知数量 |
|
|
|
this.updateNotificationStatus(this.allPublicSeaCustomers || []); |
|
|
|
|
|
|
|
// 更新控制面板统计卡片 |
|
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
// 恢复按钮状态 |
|
|
|
if (markAllBtn) { |
|
|
|
markAllBtn.disabled = false; |
|
|
|
@ -5376,21 +5427,365 @@ |
|
|
|
console.log('🧹 缓存已清空'); |
|
|
|
} |
|
|
|
} |
|
|
|
// 初始化缓存系统 |
|
|
|
window.customerCache = new CustomerDataCache(); |
|
|
|
// 初始化缓存系统将在后面使用OptimizedCustomerDataCache完成 |
|
|
|
|
|
|
|
// WebSocket客户端实现 |
|
|
|
class WebSocketClient { |
|
|
|
constructor() { |
|
|
|
this.socket = null; |
|
|
|
this.isConnected = false; |
|
|
|
this.reconnectInterval = 5000; |
|
|
|
this.reconnectAttempts = 0; |
|
|
|
this.maxReconnectAttempts = 10; |
|
|
|
this.callbacks = {}; |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化WebSocket连接 |
|
|
|
init() { |
|
|
|
// 添加SockJS和STOMP依赖 |
|
|
|
const script1 = document.createElement('script'); |
|
|
|
script1.src = 'https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js'; |
|
|
|
document.head.appendChild(script1); |
|
|
|
|
|
|
|
const script2 = document.createElement('script'); |
|
|
|
script2.src = 'https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js'; |
|
|
|
script2.onload = () => this.connect(); |
|
|
|
document.head.appendChild(script2); |
|
|
|
} |
|
|
|
|
|
|
|
// 连接WebSocket服务器 |
|
|
|
connect() { |
|
|
|
try { |
|
|
|
const socket = new SockJS('/DL/ws'); |
|
|
|
this.stompClient = Stomp.over(socket); |
|
|
|
|
|
|
|
this.stompClient.connect({}, |
|
|
|
(frame) => { |
|
|
|
console.log('✅ WebSocket连接成功:', frame); |
|
|
|
this.isConnected = true; |
|
|
|
this.reconnectAttempts = 0; |
|
|
|
|
|
|
|
// 订阅公海池数据更新 |
|
|
|
this.stompClient.subscribe('/topic/public-sea', (message) => { |
|
|
|
const data = JSON.parse(message.body); |
|
|
|
this.handlePublicSeaUpdate(data); |
|
|
|
}); |
|
|
|
|
|
|
|
// 订阅所有客户数据更新 |
|
|
|
this.stompClient.subscribe('/topic/all-customers', (message) => { |
|
|
|
const data = JSON.parse(message.body); |
|
|
|
this.handleAllCustomersUpdate(data); |
|
|
|
}); |
|
|
|
|
|
|
|
// 订阅通知 |
|
|
|
this.stompClient.subscribe('/topic/notifications', (message) => { |
|
|
|
const notification = JSON.parse(message.body); |
|
|
|
this.handleNotification(notification); |
|
|
|
}); |
|
|
|
}, |
|
|
|
(error) => { |
|
|
|
console.error('❌ WebSocket连接错误:', error); |
|
|
|
this.isConnected = false; |
|
|
|
this.attemptReconnect(); |
|
|
|
} |
|
|
|
); |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ WebSocket初始化失败:', error); |
|
|
|
this.attemptReconnect(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 尝试重连 |
|
|
|
attemptReconnect() { |
|
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) { |
|
|
|
this.reconnectAttempts++; |
|
|
|
console.log(`⏱️ 尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); |
|
|
|
setTimeout(() => this.connect(), this.reconnectInterval); |
|
|
|
} else { |
|
|
|
console.error('❌ 达到最大重连次数,停止尝试'); |
|
|
|
// 回退到轮询模式 |
|
|
|
console.log('🔄 回退到轮询模式'); |
|
|
|
window.customerCache.startAutoRefresh(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 处理公海池数据更新 |
|
|
|
handlePublicSeaUpdate(data) { |
|
|
|
console.log('🔄 收到公海池数据更新'); |
|
|
|
// 清除现有缓存,强制更新 |
|
|
|
window.customerCache.clear(); |
|
|
|
// 更新缓存 |
|
|
|
window.customerCache.updatePublicSeaCache(data); |
|
|
|
// 重新加载当前视图 |
|
|
|
this.reloadCurrentView(); |
|
|
|
} |
|
|
|
|
|
|
|
// 处理所有客户数据更新 |
|
|
|
handleAllCustomersUpdate(data) { |
|
|
|
console.log('🔄 收到所有客户数据更新'); |
|
|
|
// 清除现有缓存,强制更新 |
|
|
|
window.customerCache.clear(); |
|
|
|
// 更新缓存 |
|
|
|
window.customerCache.updateAllCustomersCache(data); |
|
|
|
// 重新加载当前视图 |
|
|
|
this.reloadCurrentView(); |
|
|
|
} |
|
|
|
|
|
|
|
// 重新加载当前视图 |
|
|
|
reloadCurrentView() { |
|
|
|
const currentActiveLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level'); |
|
|
|
if (currentActiveLevel) { |
|
|
|
console.log(`🔄 重新加载当前视图: ${currentActiveLevel}`); |
|
|
|
// 触发当前视图的数据加载 - 检查loadCustomerData函数是否存在 |
|
|
|
if (typeof loadCustomerData === 'function') { |
|
|
|
loadCustomerData(currentActiveLevel); |
|
|
|
} else { |
|
|
|
console.warn('⚠️ loadCustomerData函数未定义,跳过视图重新加载'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 处理通知 |
|
|
|
handleNotification(notification) { |
|
|
|
console.log('📢 收到通知:', notification); |
|
|
|
// 显示通知 |
|
|
|
this.showNotification(notification); |
|
|
|
|
|
|
|
// 收到通知后,清除缓存并更新通知状态,确保通知列表实时更新 |
|
|
|
this.allPublicSeaCustomers = null; |
|
|
|
// 更新通知状态 |
|
|
|
this.updateNotificationStatus(); |
|
|
|
|
|
|
|
// 调用回调函数 |
|
|
|
if (this.callbacks['notification']) { |
|
|
|
this.callbacks['notification'].forEach(callback => callback(notification)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 显示通知 |
|
|
|
showNotification(notification) { |
|
|
|
const notificationEl = document.createElement('div'); |
|
|
|
notificationEl.className = 'websocket-notification'; |
|
|
|
notificationEl.innerHTML = ` |
|
|
|
<div class="notification-header"> |
|
|
|
<h4>${notification.title}</h4> |
|
|
|
<button class="close-btn">×</button> |
|
|
|
</div> |
|
|
|
<div class="notification-body"> |
|
|
|
<p>${notification.message}</p> |
|
|
|
<small>${new Date(notification.timestamp).toLocaleString()}</small> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
|
|
|
|
// 添加样式 |
|
|
|
notificationEl.style.cssText = ` |
|
|
|
position: fixed; |
|
|
|
top: 20px; |
|
|
|
right: 20px; |
|
|
|
background: white; |
|
|
|
border-radius: 8px; |
|
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
|
|
|
padding: 16px; |
|
|
|
width: 320px; |
|
|
|
z-index: 10000; |
|
|
|
animation: slideIn 0.3s ease-out; |
|
|
|
`; |
|
|
|
|
|
|
|
// 添加关闭事件 |
|
|
|
const closeBtn = notificationEl.querySelector('.close-btn'); |
|
|
|
closeBtn.addEventListener('click', () => notificationEl.remove()); |
|
|
|
|
|
|
|
// 添加到页面 |
|
|
|
document.body.appendChild(notificationEl); |
|
|
|
|
|
|
|
// 3秒后自动关闭 |
|
|
|
setTimeout(() => notificationEl.remove(), 3000); |
|
|
|
} |
|
|
|
|
|
|
|
// 订阅事件 |
|
|
|
on(event, callback) { |
|
|
|
if (!this.callbacks[event]) { |
|
|
|
this.callbacks[event] = []; |
|
|
|
} |
|
|
|
this.callbacks[event].push(callback); |
|
|
|
} |
|
|
|
|
|
|
|
// 取消订阅 |
|
|
|
off(event, callback) { |
|
|
|
if (this.callbacks[event]) { |
|
|
|
this.callbacks[event] = this.callbacks[event].filter(cb => cb !== callback); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 断开连接 |
|
|
|
disconnect() { |
|
|
|
if (this.stompClient) { |
|
|
|
this.stompClient.disconnect(); |
|
|
|
this.isConnected = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化WebSocket客户端 |
|
|
|
window.wsClient = new WebSocketClient(); |
|
|
|
|
|
|
|
// 优化CustomerDataCache,添加WebSocket支持 |
|
|
|
class OptimizedCustomerDataCache extends CustomerDataCache { |
|
|
|
constructor() { |
|
|
|
super(); |
|
|
|
this.isWebSocketEnabled = true; |
|
|
|
} |
|
|
|
|
|
|
|
// 更新公海池缓存 |
|
|
|
updatePublicSeaCache(data) { |
|
|
|
try { |
|
|
|
const loginInfo = getLoginInfo(); |
|
|
|
const levels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools']; |
|
|
|
|
|
|
|
// 保存所有公海客户数据到缓存中,用于通知弹窗 |
|
|
|
this.allPublicSeaCustomers = data; |
|
|
|
|
|
|
|
// 统计notice为banold的客户数量并更新通知铃铛 |
|
|
|
this.updateNotificationStatus(data); |
|
|
|
|
|
|
|
// 对每个等级分别过滤和缓存 |
|
|
|
for (const level of levels) { |
|
|
|
const filteredCustomers = this.filterPublicSeaCustomersByLoginInfo( |
|
|
|
data, loginInfo, level |
|
|
|
).sort((a, b) => { |
|
|
|
const aTime = new Date(a.updated_at || a.created_at); |
|
|
|
const bTime = new Date(b.updated_at || b.created_at); |
|
|
|
return bTime - aTime; |
|
|
|
}); |
|
|
|
|
|
|
|
this.set(level, filteredCustomers); |
|
|
|
this.updateUIIfActive(level, filteredCustomers); |
|
|
|
} |
|
|
|
|
|
|
|
console.log('✅ 公海池缓存已更新'); |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 更新公海池缓存失败:', error); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新所有客户缓存 |
|
|
|
updateAllCustomersCache(data) { |
|
|
|
try { |
|
|
|
const loginInfo = getLoginInfo(); |
|
|
|
const levels = ['important', 'normal', 'low-value', 'logistics', 'unclassified']; |
|
|
|
|
|
|
|
// 根据登录信息过滤一次 |
|
|
|
const filteredByLogin = this.filterCustomersByLoginInfo( |
|
|
|
data, loginInfo, null |
|
|
|
); |
|
|
|
|
|
|
|
// 对每个等级分别过滤和缓存 |
|
|
|
for (const level of levels) { |
|
|
|
const levelMap = { |
|
|
|
'important': 'important', |
|
|
|
'normal': 'normal', |
|
|
|
'low-value': 'low-value', |
|
|
|
'logistics': 'logistics', |
|
|
|
'unclassified': 'unclassified' |
|
|
|
}; |
|
|
|
|
|
|
|
const backendLevel = levelMap[level] || level; |
|
|
|
|
|
|
|
const customersForLevel = filteredByLogin.filter(customer => { |
|
|
|
const customerLevel = standardizeCustomerLevel(customer.level); |
|
|
|
return customerLevel === backendLevel; |
|
|
|
}).sort((a, b) => { |
|
|
|
const aTime = new Date(a.updated_at || a.created_at); |
|
|
|
const bTime = new Date(b.updated_at || b.created_at); |
|
|
|
return bTime - aTime; |
|
|
|
}); |
|
|
|
|
|
|
|
// 页面加载时预加载所有等级数据 |
|
|
|
this.set(level, customersForLevel); |
|
|
|
this.updateUIIfActive(level, customersForLevel); |
|
|
|
} |
|
|
|
|
|
|
|
console.log('✅ 所有客户缓存已更新'); |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 更新所有客户缓存失败:', error); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 重写startAutoRefresh方法,使用WebSocket时不启动轮询 |
|
|
|
startAutoRefresh() { |
|
|
|
if (!window.wsClient || !window.wsClient.isConnected) { |
|
|
|
console.log('🔄 启动自动刷新'); |
|
|
|
super.startAutoRefresh(); |
|
|
|
} else { |
|
|
|
console.log('✅ WebSocket已连接,跳过轮询启动'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 替换原有的缓存系统 |
|
|
|
window.customerCache = new OptimizedCustomerDataCache(); |
|
|
|
|
|
|
|
// 页面加载时预加载所有等级数据并初始化WebSocket |
|
|
|
document.addEventListener('DOMContentLoaded', async () => { |
|
|
|
console.log('🚀 页面加载完成,开始预加载客户数据'); |
|
|
|
console.log('🚀 页面加载完成,开始初始化系统'); |
|
|
|
try { |
|
|
|
// 并行预加载所有等级数据,提升性能 |
|
|
|
// 1. 初始化WebSocket |
|
|
|
window.wsClient.init(); |
|
|
|
|
|
|
|
// 2. 初始化UI组件 |
|
|
|
initUserInfoDropdown(); |
|
|
|
initAllCustomersPagination(); |
|
|
|
initLevelPagination(); |
|
|
|
setupEnhancedEventDelegation(); // 关键:确保事件委托优先设置 |
|
|
|
setupLevelTabs(); |
|
|
|
initAutoRefresh(); |
|
|
|
initTimeFilter(); |
|
|
|
console.log('✅ UI组件初始化完成'); |
|
|
|
|
|
|
|
// 3. 并行预加载所有等级数据,提升性能 |
|
|
|
await window.customerCache.preloadAllLevels(); |
|
|
|
console.log('✅ 所有等级数据预加载完成'); |
|
|
|
|
|
|
|
// 4. 初始渲染当前活跃标签页 |
|
|
|
const activeLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all'; |
|
|
|
refreshCustomerData(activeLevel); |
|
|
|
|
|
|
|
// 5. WebSocket连接成功后停止轮询 |
|
|
|
if (window.wsClient && window.wsClient.isConnected) { |
|
|
|
window.customerCache.stopAutoRefresh(); |
|
|
|
} |
|
|
|
|
|
|
|
// 6. 添加调试信息按钮 |
|
|
|
setTimeout(addRefreshButtons, 2000); |
|
|
|
|
|
|
|
console.log('✅ 系统初始化完成'); |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 预加载数据失败:', error); |
|
|
|
console.error('❌ 初始化失败:', error); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 页面关闭前断开连接 |
|
|
|
window.addEventListener('beforeunload', () => { |
|
|
|
if (window.wsClient) { |
|
|
|
window.wsClient.disconnect(); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 添加动画样式 |
|
|
|
const style = document.createElement('style'); |
|
|
|
style.textContent = ` |
|
|
|
@keyframes slideIn { |
|
|
|
from { |
|
|
|
transform: translateX(100%); |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
to { |
|
|
|
transform: translateX(0); |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
} |
|
|
|
`; |
|
|
|
document.head.appendChild(style); |
|
|
|
|
|
|
|
|
|
|
|
function optimizedProcessFilteredCustomers(customers, level) { |
|
|
|
// 快速检查空数据 |
|
|
|
@ -7520,19 +7915,10 @@ |
|
|
|
this.updateDemandDisplay(demand); |
|
|
|
this.setHiddenCartItemId(this.currentCartItemId); |
|
|
|
|
|
|
|
// 🔥 关键修复:重新调用viewCustomerDetails以刷新数据 |
|
|
|
// 直接更新当前客户数据的选中需求ID,不重新调用viewCustomerDetails |
|
|
|
if (window.currentCustomerData) { |
|
|
|
const customerId = window.currentCustomerData.id; |
|
|
|
const phoneNumber = window.currentCustomerData.phoneNumber; |
|
|
|
|
|
|
|
console.log('🔄 重新加载客户详情以更新选中状态', { |
|
|
|
customerId, |
|
|
|
phoneNumber, |
|
|
|
targetCartItemId: this.currentCartItemId |
|
|
|
}); |
|
|
|
|
|
|
|
// 重新调用viewCustomerDetails,传递选中的购物车项ID |
|
|
|
viewCustomerDetails(customerId, phoneNumber, this.currentCartItemId); |
|
|
|
window.currentCustomerData._currentSelectedDemandId = this.currentCartItemId; |
|
|
|
console.log('🔄 更新当前客户数据的选中需求ID:', this.currentCartItemId); |
|
|
|
} |
|
|
|
|
|
|
|
console.log('✅ 需求选择完成,当前ID:', this.currentCartItemId); |
|
|
|
@ -11612,9 +11998,80 @@ |
|
|
|
|
|
|
|
// 更新控制面板统计卡片 |
|
|
|
function updateDashboardStats() { |
|
|
|
// 这里可以添加统计数据的更新逻辑 |
|
|
|
// 例如从API获取最新的统计数据 |
|
|
|
console.log('更新控制面板统计卡片'); |
|
|
|
|
|
|
|
// 尝试从多种来源获取客户数据 |
|
|
|
let allCustomers = []; |
|
|
|
|
|
|
|
// 1. 尝试直接从customerCache获取(如果存在) |
|
|
|
if (typeof customerCache !== 'undefined' && customerCache) { |
|
|
|
if (customerCache['all'] && customerCache['all'].data && Array.isArray(customerCache['all'].data)) { |
|
|
|
allCustomers = customerCache['all'].data; |
|
|
|
} else if (typeof customerCache.get === 'function') { |
|
|
|
const cachedData = customerCache.get('all'); |
|
|
|
if (cachedData && Array.isArray(cachedData)) { |
|
|
|
allCustomers = cachedData; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 尝试从全局变量获取 |
|
|
|
if (allCustomers.length === 0) { |
|
|
|
if (window.allCustomersData && Array.isArray(window.allCustomersData)) { |
|
|
|
allCustomers = window.allCustomersData; |
|
|
|
} else if (window.customers && Array.isArray(window.customers)) { |
|
|
|
allCustomers = window.customers; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
console.log('用于更新统计的数据数量:', allCustomers.length); |
|
|
|
|
|
|
|
// 更新总客户数 |
|
|
|
const totalCustomers = allCustomers.length; |
|
|
|
console.log('总客户数:', totalCustomers); |
|
|
|
|
|
|
|
// 更新活跃客户数(这里使用一个简单的计算,实际应该根据业务逻辑计算) |
|
|
|
const activeCustomers = Math.floor(totalCustomers * 0.7); // 假设70%是活跃客户 |
|
|
|
console.log('活跃客户数:', activeCustomers); |
|
|
|
|
|
|
|
// 更新客户留存率 |
|
|
|
const retentionRate = totalCustomers > 0 ? '100%' : '0%'; |
|
|
|
console.log('客户留存率:', retentionRate); |
|
|
|
|
|
|
|
// 更新本月销售额(模拟数据) |
|
|
|
const monthlySales = '¥' + (Math.floor(Math.random() * 1000000) + 500000).toLocaleString(); |
|
|
|
console.log('本月销售额:', monthlySales); |
|
|
|
|
|
|
|
// 更新统计卡片 |
|
|
|
try { |
|
|
|
// 更新总客户数卡片 |
|
|
|
const totalCustomersElement = document.querySelector('.stats-cards .stat-card:nth-child(1) .value'); |
|
|
|
if (totalCustomersElement) { |
|
|
|
totalCustomersElement.textContent = totalCustomers; |
|
|
|
} |
|
|
|
|
|
|
|
// 更新活跃客户卡片 |
|
|
|
const activeCustomersElement = document.querySelector('.stats-cards .stat-card:nth-child(2) .value'); |
|
|
|
if (activeCustomersElement) { |
|
|
|
activeCustomersElement.textContent = activeCustomers; |
|
|
|
} |
|
|
|
|
|
|
|
// 更新客户留存率卡片 |
|
|
|
const retentionRateElement = document.querySelector('.stats-cards .stat-card:nth-child(3) .value'); |
|
|
|
if (retentionRateElement) { |
|
|
|
retentionRateElement.textContent = retentionRate; |
|
|
|
} |
|
|
|
|
|
|
|
// 更新本月销售额卡片 |
|
|
|
const monthlySalesElement = document.querySelector('.stats-cards .stat-card:nth-child(4) .value'); |
|
|
|
if (monthlySalesElement) { |
|
|
|
monthlySalesElement.textContent = monthlySales; |
|
|
|
} |
|
|
|
|
|
|
|
console.log('✅ 控制面板统计卡片更新完成'); |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 更新控制面板统计卡片失败:', error); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 获取等级显示名称 |
|
|
|
@ -12295,6 +12752,9 @@ |
|
|
|
currentCustomerId = customerId || phoneNumber; |
|
|
|
resetEditStateToInitial(); |
|
|
|
|
|
|
|
// 初始化模态框事件,确保关闭功能正常 |
|
|
|
initModalEvents(); |
|
|
|
|
|
|
|
try { |
|
|
|
// 尝试按优先级查询客户信息:API查询 -> 本地缓存 |
|
|
|
const customerData = await fetchCustomerData(customerId, phoneNumber, targetCartItemId); |
|
|
|
@ -13059,17 +13519,23 @@ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 移除旧的事件监听器,避免重复绑定 - 只处理关闭按钮,不替换整个模态框 |
|
|
|
const newCloseModal = closeModal.cloneNode(true); |
|
|
|
closeModal.parentNode.replaceChild(newCloseModal, closeModal); |
|
|
|
|
|
|
|
// 重新绑定关闭事件 |
|
|
|
closeModal.addEventListener('click', function (e) { |
|
|
|
newCloseModal.addEventListener('click', function (e) { |
|
|
|
e.stopPropagation(); |
|
|
|
const modal = document.getElementById('customerModal'); |
|
|
|
modal.classList.remove('active'); |
|
|
|
document.body.style.overflow = 'auto'; |
|
|
|
console.log("关闭模态框,重置编辑状态"); |
|
|
|
resetEditStateToInitial(); |
|
|
|
}); |
|
|
|
|
|
|
|
// 点击模态框外部关闭 |
|
|
|
// 只在模态框没有外部点击事件监听器时添加 |
|
|
|
// 避免重复添加导致的多次触发问题 |
|
|
|
if (!modal.dataset.hasOutsideClickListener) { |
|
|
|
// 绑定模态框外部点击事件 |
|
|
|
modal.addEventListener('click', function (e) { |
|
|
|
if (e.target === modal) { |
|
|
|
modal.classList.remove('active'); |
|
|
|
@ -13077,6 +13543,8 @@ |
|
|
|
resetEditStateToInitial(); |
|
|
|
} |
|
|
|
}); |
|
|
|
modal.dataset.hasOutsideClickListener = 'true'; |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化编辑按钮 |
|
|
|
initEditButton(); |
|
|
|
@ -14701,17 +15169,7 @@ |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
closeModal.addEventListener('click', function () { |
|
|
|
modal.classList.remove('active'); |
|
|
|
document.body.style.overflow = 'auto'; |
|
|
|
}); |
|
|
|
|
|
|
|
modal.addEventListener('click', function (e) { |
|
|
|
if (e.target === modal) { |
|
|
|
modal.classList.remove('active'); |
|
|
|
document.body.style.overflow = 'auto'; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 新增详情按钮事件 |
|
|
|
addDetailBtn.addEventListener('click', function () { |
|
|
|
@ -14878,115 +15336,13 @@ |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// 确保数据缓存系统在页面加载时正确初始化 |
|
|
|
document.addEventListener('DOMContentLoaded', async function() { |
|
|
|
console.log('🚀 初始化客户数据系统'); |
|
|
|
|
|
|
|
// 1. 先初始化缓存系统 - 修复:确保即使没有CustomerDataCache类也能正常工作 |
|
|
|
if (!window.customerCache) { |
|
|
|
console.log('创建简单缓存系统'); |
|
|
|
window.customerCache = { |
|
|
|
data: {}, |
|
|
|
set: function(key, value) { |
|
|
|
this.data[key] = value; |
|
|
|
console.log('缓存已更新:', key, '数量:', Array.isArray(value) ? value.length : 1); |
|
|
|
}, |
|
|
|
get: function(key) { |
|
|
|
return this.data[key] || null; |
|
|
|
}, |
|
|
|
clear: function() { |
|
|
|
this.data = {}; |
|
|
|
}, |
|
|
|
init: async function() { |
|
|
|
// 简单初始化 |
|
|
|
console.log('缓存系统初始化完成'); |
|
|
|
}, |
|
|
|
preloadAllLevels: async function() { |
|
|
|
// 尝试预加载数据 |
|
|
|
try { |
|
|
|
const response = await fetch(appendAuthParams(`${API_BASE_URL}/customers/all`)); |
|
|
|
if (response.ok) { |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success && Array.isArray(data.data)) { |
|
|
|
this.set('all', data.data); |
|
|
|
allCustomersData = data.data; |
|
|
|
console.log('预加载客户数据成功,数量:', data.data.length); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('预加载数据失败:', error); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
await window.customerCache.init(); |
|
|
|
} catch (error) { |
|
|
|
console.error('缓存系统初始化失败:', error); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 初始化UI组件 |
|
|
|
try { |
|
|
|
initUserInfoDropdown(); |
|
|
|
initAllCustomersPagination(); |
|
|
|
initLevelPagination(); |
|
|
|
setupEnhancedEventDelegation(); // 关键:确保事件委托优先设置 |
|
|
|
setupLevelTabs(); |
|
|
|
initAutoRefresh(); |
|
|
|
initTimeFilter(); |
|
|
|
console.log('UI组件初始化完成'); |
|
|
|
} catch (error) { |
|
|
|
console.error('UI组件初始化失败:', error); |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 预加载关键数据 - 修复:添加更健壮的数据加载逻辑 |
|
|
|
try { |
|
|
|
await window.customerCache.preloadAllLevels(); |
|
|
|
|
|
|
|
// 获取缓存的数据 |
|
|
|
const allCustomers = window.customerCache.get('all') || []; |
|
|
|
console.log('✅ 所有等级数据预加载完成,数量:', allCustomers.length); |
|
|
|
|
|
|
|
// 更新全局数据 |
|
|
|
if (allCustomers.length > 0) { |
|
|
|
allCustomersData = allCustomers; |
|
|
|
} |
|
|
|
|
|
|
|
// 4. 初始渲染当前活跃标签页 |
|
|
|
const activeLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all'; |
|
|
|
refreshCustomerData(activeLevel); |
|
|
|
|
|
|
|
// 移除直接调用updateRecentCustomers() |
|
|
|
// 因为已经在refreshCustomerData函数中添加了对refreshRecentCustomers()的调用 |
|
|
|
// 这样可以确保最近客户与全部客户、重要客户、普通客户等一起渲染 |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 数据预加载失败:', error); |
|
|
|
|
|
|
|
// 失败时尝试使用备用数据源 |
|
|
|
if (window.customers && Array.isArray(window.customers)) { |
|
|
|
console.log('使用备用数据源,数量:', window.customers.length); |
|
|
|
allCustomersData = window.customers; |
|
|
|
window.customerCache.set('all', window.customers); |
|
|
|
updateAllCustomersPagination(window.customers); |
|
|
|
// 移除直接调用updateRecentCustomers() |
|
|
|
// 依赖refreshCustomerData中的refreshRecentCustomers()调用来同步更新 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 修复:移除重复的bindTableRowClickEvents调用,使用事件委托 |
|
|
|
console.log('初始化完成,依赖事件委托机制处理交互'); |
|
|
|
|
|
|
|
// 添加调试信息按钮 |
|
|
|
setTimeout(addRefreshButtons, 2000); |
|
|
|
}); |
|
|
|
// 第二个DOMContentLoaded事件监听器已删除,避免与第一个冲突 |
|
|
|
|
|
|
|
// 绑定行点击事件的功能已在setupEnhancedEventDelegation中实现 |
|
|
|
|
|
|
|
// 添加通知弹窗样式 |
|
|
|
const style = document.createElement('style'); |
|
|
|
style.textContent = ` |
|
|
|
const notificationStyle = document.createElement('style'); |
|
|
|
notificationStyle.textContent = ` |
|
|
|
/* 通知弹窗内容区域 */ |
|
|
|
#notificationModal .modal-body { |
|
|
|
flex: 1; |
|
|
|
|