Browse Source

优化缓存机制和WebSocket通信,修复图片懒加载问题

Boss
Default User 4 weeks ago
parent
commit
229f7faefe
  1. 501
      supply.html

501
supply.html

@ -1671,9 +1671,6 @@
</div>
<!-- 右侧:联系客服、刷新和用户信息 -->
<div class="title-bar-right">
<button onclick="refreshSupplies()" style="background-color: rgba(255, 255, 255, 0.2); color: white; border: 1px solid rgba(255, 255, 255, 0.3); padding: 6px 12px; border-radius: 15px; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(10px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); flex-shrink: 0;">
刷新数据
</button>
<button onclick="contactCustomerService()" style="background-color: rgba(255, 255, 255, 0.2); color: white; border: 1px solid rgba(255, 255, 255, 0.3); padding: 6px 12px; border-radius: 15px; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(10px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); flex-shrink: 0;">
联系客服
</button>
@ -2696,16 +2693,61 @@
constructor() {
this.cache = new Map();
this.defaultTTL = 5 * 60 * 1000; // 默认缓存5分钟
this.maxLocalStorageSize = 4 * 1024 * 1024; // 本地存储最大4MB
this.cacheVersion = 'v2'; // 缓存版本号
this.initialize();
}
// 初始化
initialize() {
this.migrateCache();
}
// 缓存迁移
migrateCache() {
const oldVersion = localStorage.getItem('cache_version');
if (oldVersion !== this.cacheVersion) {
// 清理旧版本缓存
for (let key in localStorage) {
if (key.startsWith('cache_') && !key.startsWith(`cache_${this.cacheVersion}_`)) {
localStorage.removeItem(key);
}
}
localStorage.setItem('cache_version', this.cacheVersion);
}
}
// 获取带版本的缓存键
getVersionedKey(key) {
return `${this.cacheVersion}_${key}`;
}
// 设置缓存
set(key, value, ttl = this.defaultTTL) {
const versionedKey = this.getVersionedKey(key);
const item = {
value,
expiry: Date.now() + ttl
expiry: Date.now() + ttl,
_timestamp: Date.now() // 添加时间戳用于排序
};
// 更新内存缓存
this.cache.set(key, item);
localStorage.setItem(`cache_${key}`, JSON.stringify(item));
// 更新本地存储缓存
try {
this.ensureStorageSpace();
localStorage.setItem(`cache_${versionedKey}`, JSON.stringify(item));
} catch (error) {
console.error('存储缓存数据失败:', error);
// 存储失败时清理部分缓存
this.clearOldCache();
try {
localStorage.setItem(`cache_${versionedKey}`, JSON.stringify(item));
} catch (e) {
console.error('清理缓存后仍无法存储:', e);
}
}
}
// 获取缓存
@ -2715,13 +2757,15 @@
// 如果内存缓存不存在,从localStorage获取
if (!item) {
const stored = localStorage.getItem(`cache_${key}`);
const versionedKey = this.getVersionedKey(key);
const stored = localStorage.getItem(`cache_${versionedKey}`);
if (stored) {
try {
item = JSON.parse(stored);
this.cache.set(key, item);
} catch (e) {
console.error('解析缓存失败:', e);
localStorage.removeItem(`cache_${versionedKey}`);
}
}
}
@ -2739,7 +2783,8 @@
// 删除缓存
delete(key) {
this.cache.delete(key);
localStorage.removeItem(`cache_${key}`);
const versionedKey = this.getVersionedKey(key);
localStorage.removeItem(`cache_${versionedKey}`);
}
// 清除所有缓存
@ -2760,12 +2805,64 @@
this.cache.delete(key);
}
}
const versionedPrefix = this.getVersionedKey(prefix);
for (let key in localStorage) {
if (key.startsWith(`cache_${prefix}`)) {
if (key.startsWith(`cache_${versionedPrefix}`)) {
localStorage.removeItem(key);
}
}
}
// 确保存储空间
ensureStorageSpace() {
if (this.getLocalStorageSize() > this.maxLocalStorageSize) {
this.clearOldCache();
}
}
// 获取本地存储大小
getLocalStorageSize() {
let size = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
size += localStorage[key].length;
}
}
return size;
}
// 清理旧缓存
clearOldCache() {
const cacheKeys = [];
// 收集所有缓存键和时间戳
for (let key in localStorage) {
if (key.startsWith('cache_')) {
try {
const stored = localStorage.getItem(key);
const item = JSON.parse(stored);
cacheKeys.push({
key,
timestamp: item._timestamp || 0
});
} catch (error) {
// 解析失败的缓存直接删除
localStorage.removeItem(key);
}
}
}
// 按时间戳排序,删除最旧的缓存
cacheKeys.sort((a, b) => a.timestamp - b.timestamp);
// 删除一半最旧的缓存
const deleteCount = Math.ceil(cacheKeys.length / 2);
for (let i = 0; i < deleteCount; i++) {
if (cacheKeys[i]) {
localStorage.removeItem(cacheKeys[i].key);
}
}
}
}
// 实例化缓存管理器
@ -2982,15 +3079,23 @@
let ws = null;
let wsReconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 3000; // 3秒
const initialReconnectDelay = 1000; // 初始重连延迟1秒
const maxReconnectDelay = 30000; // 最大重连延迟30秒
// 保存定时器引用,方便后续清理
let timers = {
loadSupplies: null,
updateCountdowns: null,
checkAutoOffline: null
checkAutoOffline: null,
heartbeat: null,
heartbeatTimeout: null
};
// WebSocket心跳配置
const heartbeatInterval = 30000; // 心跳间隔30秒
const heartbeatTimeout = 15000; // 心跳超时时间15秒
let lastHeartbeatTime = 0; // 最后一次心跳时间
// 防止loadSupplies并发执行的标志
let isLoadingSupplies = false;
@ -3113,6 +3218,8 @@
userId: userInfo.userId || userInfo.id
}));
}
// 启动心跳机制
startHeartbeat();
};
// 消息接收事件
@ -3128,6 +3235,8 @@
// 连接关闭事件
ws.onclose = function(event) {
console.log('WebSocket连接已关闭:', event.code, event.reason);
// 停止心跳机制
stopHeartbeat();
// 只有在非正常关闭时才重连
// 正常关闭码1000表示主动关闭,不需要重连
if (event.code !== 1000) {
@ -3151,13 +3260,126 @@
function attemptReconnect() {
if (wsReconnectAttempts < maxReconnectAttempts) {
wsReconnectAttempts++;
console.log(`尝试重新连接WebSocket (${wsReconnectAttempts}/${maxReconnectAttempts})...`);
setTimeout(initWebSocket, reconnectDelay);
// 计算指数退避延迟:初始延迟 * 2^(重连次数-1),但不超过最大延迟
const delay = Math.min(initialReconnectDelay * Math.pow(2, wsReconnectAttempts - 1), maxReconnectDelay);
console.log(`尝试重新连接WebSocket (${wsReconnectAttempts}/${maxReconnectAttempts}),延迟 ${delay}ms...`);
setTimeout(initWebSocket, delay);
} else {
console.error('WebSocket重连失败,已达到最大重试次数');
// 30秒后重置重连计数,允许再次尝试
setTimeout(() => {
wsReconnectAttempts = 0;
console.log('WebSocket重连计数已重置,可再次尝试连接');
}, 30000);
}
}
// 获取当前用户的基础缓存键
function getBaseCacheKey() {
const userInfo = checkLogin();
if (!userInfo) return null;
return `supplies_${userInfo.projectName === '管理员' ? 'admin' : userInfo.userId}`;
}
// 获取完整货源列表的缓存键
function getSuppliesListCacheKey() {
const baseKey = getBaseCacheKey();
return baseKey ? `${baseKey}_list` : null;
}
// 获取单个货源的缓存键
function getSupplyCacheKey(supplyId) {
const baseKey = getBaseCacheKey();
return baseKey ? `${baseKey}_supply_${supplyId}` : null;
}
// 获取特定状态货源的缓存键
function getSuppliesByStatusCacheKey(status) {
const baseKey = getBaseCacheKey();
return baseKey ? `${baseKey}_status_${status}` : null;
}
// 获取搜索结果的缓存键
function getSearchCacheKey(keyword) {
const baseKey = getBaseCacheKey();
return baseKey ? `${baseKey}_search_${encodeURIComponent(keyword)}` : null;
}
// 更新缓存中的单个货源数据
function updateSupplyInCache(supplyId, updatedData) {
try {
// 获取完整列表缓存键
const listCacheKey = getSuppliesListCacheKey();
if (!listCacheKey) {
console.error('无法获取缓存键,用户未登录');
return null;
}
// 获取单个货源缓存键
const supplyCacheKey = getSupplyCacheKey(supplyId);
// 1. 更新完整列表缓存
const cachedSupplies = cacheManager.get(listCacheKey);
if (cachedSupplies && Array.isArray(cachedSupplies)) {
const supplyIndex = cachedSupplies.findIndex(supply => String(supply.id) === String(supplyId));
if (supplyIndex !== -1) {
// 创建更新后的货源数据
const updatedSupply = { ...cachedSupplies[supplyIndex], ...updatedData };
// 创建新的货源数组,避免直接修改原数组
const updatedSupplies = [...cachedSupplies];
updatedSupplies[supplyIndex] = updatedSupply;
// 更新完整列表缓存
cacheManager.set(listCacheKey, updatedSupplies, 60 * 60 * 1000);
// 2. 更新单个货源缓存
if (supplyCacheKey) {
cacheManager.set(supplyCacheKey, updatedSupply, 60 * 60 * 1000);
}
// 3. 如果状态发生变化,清除状态相关的缓存
if (updatedData.status) {
const oldStatus = cachedSupplies[supplyIndex].status;
if (oldStatus !== updatedData.status) {
// 清除旧状态和新状态的缓存
const oldStatusCacheKey = getSuppliesByStatusCacheKey(oldStatus);
const newStatusCacheKey = getSuppliesByStatusCacheKey(updatedData.status);
if (oldStatusCacheKey) cacheManager.delete(oldStatusCacheKey);
if (newStatusCacheKey) cacheManager.delete(newStatusCacheKey);
// 清除搜索结果缓存,因为状态变化可能影响搜索
clearSearchCache();
}
}
console.log('缓存中的货源数据已更新:', supplyId, '更新字段:', Object.keys(updatedData));
return updatedSupplies;
} else {
console.warn('在缓存中未找到货源:', supplyId);
return null;
}
} else {
console.warn('缓存中无货源数据或数据格式不正确');
return null;
}
} catch (error) {
console.error('更新缓存中的货源数据失败:', error);
return null;
}
}
// 清除搜索结果缓存
function clearSearchCache() {
const baseKey = getBaseCacheKey();
if (!baseKey) return;
// 清除所有搜索相关的缓存
const searchPrefix = `${baseKey}_search_`;
cacheManager.clearByPrefix(searchPrefix);
console.log('搜索结果缓存已清除');
}
// 处理WebSocket消息
function handleWebSocketMessage(message) {
try {
@ -3167,24 +3389,101 @@
switch (data.type) {
case 'supply_update':
// 货源更新通知,重新加载数据
console.log('收到货源更新通知,重新加载数据');
loadSupplies(true); // 强制刷新,跳过缓存
// 货源更新通知
if (data.supplyId && data.updatedData) {
// 增量更新缓存
console.log('收到货源更新通知,执行增量更新:', data.supplyId);
try {
const updatedSupplies = updateSupplyInCache(data.supplyId, data.updatedData);
if (updatedSupplies) {
// 更新内存中的数据
supplyData.supplies = updatedSupplies;
processSupplyData(updatedSupplies);
renderSupplyLists();
} else {
// 缓存更新失败,重新加载数据
console.warn('缓存更新失败,执行全量加载');
loadSupplies(true);
}
} catch (error) {
console.error('处理货源更新通知失败:', error);
// 发生错误时,重新加载数据
loadSupplies(true);
}
} else {
// 没有具体的更新数据,重新加载
console.log('收到货源更新通知,重新加载数据');
loadSupplies(true);
}
break;
case 'supply_lock':
// 货源锁定状态更新
console.log('收到货源锁定状态更新,重新加载数据');
loadSupplies(true); // 强制刷新,跳过缓存
if (data.supplyId && data.locked !== undefined) {
console.log('收到货源锁定状态更新,执行增量更新:', data.supplyId, '锁定状态:', data.locked);
try {
const updatedSupplies = updateSupplyInCache(data.supplyId, { locked: data.locked });
if (updatedSupplies) {
supplyData.supplies = updatedSupplies;
processSupplyData(updatedSupplies);
renderSupplyLists();
} else {
console.warn('缓存更新失败,执行全量加载');
loadSupplies(true);
}
} catch (error) {
console.error('处理货源锁定状态更新失败:', error);
loadSupplies(true);
}
} else {
console.log('收到货源锁定状态更新,重新加载数据');
loadSupplies(true);
}
break;
case 'supply_status_change':
// 货源状态变更
console.log('收到货源状态变更,重新加载数据');
loadSupplies(true); // 强制刷新,跳过缓存
if (data.supplyId && data.status) {
console.log('收到货源状态变更,执行增量更新:', data.supplyId, '新状态:', data.status);
try {
const updatedSupplies = updateSupplyInCache(data.supplyId, { status: data.status });
if (updatedSupplies) {
supplyData.supplies = updatedSupplies;
processSupplyData(updatedSupplies);
renderSupplyLists();
} else {
console.warn('缓存更新失败,执行全量加载');
loadSupplies(true);
}
} catch (error) {
console.error('处理货源状态变更失败:', error);
loadSupplies(true);
}
} else {
console.log('收到货源状态变更,重新加载数据');
loadSupplies(true);
}
break;
case 'auto_offline':
// 自动下架通知
console.log('收到自动下架通知,重新加载数据');
loadSupplies(true); // 强制刷新,跳过缓存
if (data.supplyId) {
console.log('收到自动下架通知,执行增量更新:', data.supplyId);
try {
const updatedSupplies = updateSupplyInCache(data.supplyId, { status: 'draft' });
if (updatedSupplies) {
supplyData.supplies = updatedSupplies;
processSupplyData(updatedSupplies);
renderSupplyLists();
} else {
console.warn('缓存更新失败,执行全量加载');
loadSupplies(true);
}
} catch (error) {
console.error('处理自动下架通知失败:', error);
loadSupplies(true);
}
} else {
console.log('收到自动下架通知,重新加载数据');
loadSupplies(true);
}
break;
case 'ping':
// 心跳响应
@ -3192,6 +3491,15 @@
ws.send(JSON.stringify({ type: 'pong' }));
}
break;
case 'pong':
// 收到心跳响应
console.log('收到WebSocket心跳响应');
// 清除心跳超时定时器
if (timers.heartbeatTimeout) {
clearTimeout(timers.heartbeatTimeout);
timers.heartbeatTimeout = null;
}
break;
default:
console.log('未知的WebSocket消息类型:', data.type);
}
@ -3209,6 +3517,53 @@
}
}
// 发送心跳
function sendHeartbeat() {
if (ws && ws.readyState === WebSocket.OPEN) {
console.log('发送WebSocket心跳');
sendWebSocketMessage({ type: 'ping' });
lastHeartbeatTime = Date.now();
// 设置心跳超时定时器
if (timers.heartbeatTimeout) {
clearTimeout(timers.heartbeatTimeout);
}
timers.heartbeatTimeout = setTimeout(() => {
console.error('WebSocket心跳超时,连接可能已断开');
// 心跳超时,主动关闭连接并尝试重连
if (ws) {
ws.close(1006, 'Heartbeat timeout');
}
}, heartbeatTimeout);
}
}
// 启动心跳
function startHeartbeat() {
// 停止已有的心跳
stopHeartbeat();
// 立即发送一次心跳
sendHeartbeat();
// 设置心跳间隔
timers.heartbeat = setInterval(sendHeartbeat, heartbeatInterval);
console.log('WebSocket心跳机制已启动,间隔', heartbeatInterval, 'ms');
}
// 停止心跳
function stopHeartbeat() {
if (timers.heartbeat) {
clearInterval(timers.heartbeat);
timers.heartbeat = null;
}
if (timers.heartbeatTimeout) {
clearTimeout(timers.heartbeatTimeout);
timers.heartbeatTimeout = null;
}
console.log('WebSocket心跳机制已停止');
}
// 移除复杂的触摸事件处理,使用浏览器默认滚动行为
function preventIOSDrag() {
// 不再阻止任何默认行为,让浏览器处理滚动
@ -5205,6 +5560,16 @@
try {
console.log('开始加载联系人数据...');
// 尝试从缓存加载
const cachedContacts = cacheManager.get('contacts');
if (cachedContacts) {
contacts = cachedContacts;
console.log('从缓存加载联系人数据,共', contacts.length, '个联系人');
updateContactSelects();
return;
}
const response = await fetch('/api/contacts');
if (!response.ok) {
throw new Error(`服务器响应异常: ${response.status} ${response.statusText}`);
@ -5215,36 +5580,20 @@
contacts = result.data || [];
console.log('联系人数据加载成功,共', contacts.length, '个联系人:', contacts);
// 保存到本地缓存,添加时间戳和版本号
const contactsCache = {
data: contacts,
version: '1.0',
timestamp: Date.now()
};
localStorage.setItem('contactsCache', JSON.stringify(contactsCache));
// 保存到缓存,设置7天过期时间
cacheManager.set('contacts', contacts, 7 * 24 * 60 * 60 * 1000);
updateContactSelects();
} catch (error) {
console.error('加载联系人数据失败:', error);
// 尝试从本地缓存加载
try {
const cachedContacts = localStorage.getItem('contactsCache');
if (cachedContacts) {
const contactsCache = JSON.parse(cachedContacts);
// 检查缓存是否有效(7天内)
const cacheExpiry = 7 * 24 * 60 * 60 * 1000;
if (Date.now() - contactsCache.timestamp < cacheExpiry) {
contacts = contactsCache.data || [];
console.log('从本地缓存加载联系人数据,共', contacts.length, '个联系人:', contacts);
updateContactSelects();
return;
} else {
console.log('联系人缓存已过期');
}
}
} catch (cacheError) {
console.error('加载联系人缓存失败:', cacheError);
// 尝试从缓存加载
const cachedContacts = cacheManager.get('contacts');
if (cachedContacts) {
contacts = cachedContacts;
console.log('API失败,从缓存加载联系人数据,共', contacts.length, '个联系人');
updateContactSelects();
return;
}
// 出错且无有效缓存时,将contacts设为空数组
@ -5303,7 +5652,8 @@
if (!userInfo) return;
// 生成缓存键
const cacheKey = `supplies_cache_${userInfo.projectName === '管理员' ? 'admin' : userInfo.userId}`;
const cacheKey = getSuppliesListCacheKey();
if (!cacheKey) return;
// 重置所有状态的分页数据
supplyData.pagination = {
@ -5321,20 +5671,14 @@
draft: []
};
// 尝试从本地缓存获取数据
// 尝试从缓存获取数据
if (!forceRefresh) {
const cachedData = localStorage.getItem(cacheKey);
const cachedData = cacheManager.get(cacheKey);
if (cachedData) {
try {
const supplies = JSON.parse(cachedData);
console.log('从本地缓存加载货源数据:', supplies.length);
processSupplyData(supplies);
renderSupplyLists();
return;
} catch (error) {
console.error('解析缓存数据失败:', error);
localStorage.removeItem(cacheKey);
}
console.log('从缓存加载货源数据:', cachedData.length);
processSupplyData(cachedData);
renderSupplyLists();
return;
}
}
@ -5357,9 +5701,18 @@
if (result.success) {
console.log('加载到的货源数量:', result.data.list.length);
// 存储到本地缓存
localStorage.setItem(cacheKey, JSON.stringify(result.data.list));
console.log('货源数据已存储到本地缓存');
// 存储到缓存,设置1小时过期时间
cacheManager.set(cacheKey, result.data.list, 60 * 60 * 1000);
// 同时为每个货源创建单独的缓存
result.data.list.forEach(supply => {
const supplyCacheKey = getSupplyCacheKey(supply.id);
if (supplyCacheKey) {
cacheManager.set(supplyCacheKey, supply, 60 * 60 * 1000);
}
});
console.log('货源数据已存储到缓存');
processSupplyData(result.data.list);
renderSupplyLists();
@ -5368,20 +5721,13 @@
console.error('加载货源失败:', error);
// 尝试从缓存加载(如果有)
const userInfo = checkLogin();
if (userInfo) {
const cacheKey = `supplies_cache_${userInfo.projectName === '管理员' ? 'admin' : userInfo.userId}`;
const cachedData = localStorage.getItem(cacheKey);
const cacheKey = getSuppliesListCacheKey();
if (cacheKey) {
const cachedData = cacheManager.get(cacheKey);
if (cachedData) {
try {
const supplies = JSON.parse(cachedData);
console.log('API失败,从本地缓存加载货源数据:', supplies.length);
processSupplyData(supplies);
renderSupplyLists();
} catch (cacheError) {
console.error('解析缓存数据失败:', cacheError);
localStorage.removeItem(cacheKey);
}
console.log('API失败,从缓存加载货源数据:', cachedData.length);
processSupplyData(cachedData);
renderSupplyLists();
}
}
} finally {
@ -5474,6 +5820,11 @@
renderSupplyList('pending', supplyData.pendingSupplies);
renderSupplyList('rejected', supplyData.rejectedSupplies);
renderSupplyList('draft', supplyData.draftSupplies);
// 渲染完成后检查并加载可见的懒加载图片
setTimeout(() => {
checkLazyLoadImages();
}, 100);
}
// 渲染单个货源列表

Loading…
Cancel
Save