Browse Source

优化Management页面稳定性和用户体验

Boss3
Default User 2 months ago
parent
commit
ca1f5a7a68
  1. 368
      Management.html
  2. 16
      Reject.js

368
Management.html

@ -432,6 +432,50 @@
.supply-card video {
cursor: pointer;
}
/* 回到顶部按钮样式 */
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgba(153, 153, 153, 0.7); /* 浅灰色透明背景 */
color: #333;
border: none;
cursor: pointer;
z-index: 1000;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.back-to-top-content {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.back-to-top-text {
font-size: 12px;
font-weight: bold;
line-height: 1;
}
.back-to-top:hover {
background-color: rgba(102, 102, 102, 0.9); /* 加深颜色 */
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.back-to-top:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
@ -443,6 +487,13 @@
</div>
</div>
<!-- 回到顶部按钮 -->
<button id="backToTop" class="back-to-top" onclick="scrollToSuppliesSection()" style="display: none;">
<div class="back-to-top-content">
<span class="back-to-top-text">Top</span>
</div>
</button>
<div class="container">
<div class="title-bar">
<div style="display: flex; align-items: center; gap: 15px;">
@ -489,7 +540,7 @@
</div>
</div>
<div class="supplies-section" id="suppliesSection" style="display: none;">
<div class="supplies-section" id="suppliesSection" style="display: block;">
<h3 id="suppliesTitle">用户货源详情</h3>
<div class="supplies-grid" id="suppliesGrid">
<!-- 货源卡片将动态生成 -->
@ -511,9 +562,10 @@
// WebSocket相关变量
let ws = null;
let wsReconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 3000;
const maxReconnectAttempts = 10; // 增加最大重连次数
const reconnectDelay = 5000; // 增加重连延迟,避免频繁重连
let wsUrl = '';
let wsConnecting = false; // 防止重复连接
// 缓存相关变量
const CACHE_KEY = 'management_stats_cache';
@ -581,7 +633,20 @@
// 初始化WebSocket连接
function initWebSocket() {
// 防止重复连接
if (wsConnecting) {
console.log('WebSocket连接正在建立中,请勿重复调用');
return;
}
// 检查是否已经有活跃连接
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
console.log('WebSocket连接已存在或正在建立中,无需重新连接');
return;
}
try {
wsConnecting = true;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
wsUrl = `${protocol}//${window.location.host}`;
console.log('正在尝试连接WebSocket服务器:', wsUrl);
@ -592,6 +657,8 @@
ws.close(1000, '重新连接');
} catch (error) {
console.error('关闭旧WebSocket连接失败:', error);
} finally {
ws = null;
}
}
@ -602,6 +669,7 @@
ws.onopen = function(event) {
console.log('WebSocket连接已打开');
wsReconnectAttempts = 0; // 重置重连计数器
wsConnecting = false;
};
ws.onmessage = function(event) {
@ -615,6 +683,8 @@
ws.onclose = function(event) {
console.log('WebSocket连接已关闭:', event.code, event.reason);
wsConnecting = false;
ws = null;
// 尝试重新连接,最多重试maxReconnectAttempts次
if (wsReconnectAttempts < maxReconnectAttempts) {
@ -629,9 +699,12 @@
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
console.error('WebSocket错误详情:', error.message);
wsConnecting = false;
};
} catch (error) {
console.error('WebSocket连接失败:', error);
wsConnecting = false;
ws = null;
// 尝试重新连接
if (wsReconnectAttempts < maxReconnectAttempts) {
@ -650,28 +723,34 @@
const data = JSON.parse(message);
console.log('处理WebSocket消息:', data);
// 对于任何类型的WebSocket消息,都重新加载数据,确保实时更新
if (data.type) {
console.log('收到WebSocket消息,刷新数据:', data.type);
// 重新加载统计数据
if (currentFilter === 'custom') {
// 如果是自定义筛选,获取当前日期输入值
const startDate = startDateInput.value;
const endDate = endDateInput.value;
loadStats('custom', startDate, endDate);
// 只有当消息类型与货源相关时,才重新加载数据
if (data.type && (data.type === 'supply_created' || data.type === 'supply_updated' || data.type === 'supply_deleted' || data.type === 'supply_status_change')) {
console.log('收到货源相关WebSocket消息,刷新数据:', data.type);
// 保存当前滚动位置
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// 重新加载统计数据
if (currentFilter === 'custom') {
// 如果是自定义筛选,获取当前日期输入值
const startDate = startDateInput.value;
const endDate = endDateInput.value;
loadStats('custom', startDate, endDate);
} else {
loadStats(currentFilter);
}
// 如果正在显示货源详情,重新加载货源数据
if (currentSellerId) {
console.log('正在显示货源详情,重新加载数据');
showSuppliesBySeller(currentSellerId);
}
// 恢复滚动位置
window.scrollTo(0, scrollPosition);
} else {
loadStats(currentFilter);
}
// 如果正在显示货源详情,重新加载货源数据
if (currentSellerId) {
console.log('正在显示货源详情,重新加载数据');
showSuppliesBySeller(currentSellerId);
console.warn('收到未知格式的WebSocket消息,不刷新数据:', data.type);
}
} else {
console.warn('收到未知格式的WebSocket消息');
}
} catch (error) {
console.error('解析WebSocket消息失败:', error);
}
@ -800,6 +879,23 @@
closePreviewModal();
}
});
// 滚动事件,用于显示/隐藏回到顶部按钮
window.addEventListener('scroll', () => {
const backToTopBtn = document.getElementById('backToTop');
// 当滚动距离超过300px时显示按钮
if (window.pageYOffset > 300) {
backToTopBtn.style.display = 'flex';
} else {
backToTopBtn.style.display = 'none';
}
});
}
// 回到supplies-section顶部
function scrollToSuppliesSection() {
const suppliesSection = document.getElementById('suppliesSection');
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 设置筛选条件
@ -814,9 +910,6 @@
startDateInput.value = '';
endDateInput.value = '';
// 隐藏货源详情
suppliesSection.style.display = 'none';
// 重新加载数据
loadStats(filter);
}
@ -838,9 +931,6 @@
// 清除预设筛选的active状态
[todayBtn, yesterdayBtn, beforeYesterdayBtn, weekBtn, monthBtn, allBtn].forEach(btn => btn.classList.remove('active'));
// 隐藏货源详情
suppliesSection.style.display = 'none';
// 重新加载数据,传递自定义日期范围
loadStats('custom', startDate, endDate);
}
@ -906,6 +996,11 @@
// 保存到缓存,自定义日期使用特殊的缓存键
const cacheKey = filter === 'custom' ? `custom_${startDate}_${endDate}` : filter;
saveCachedData(cacheKey, data.data);
// 只有在当前没有显示特定卖家的货源时才显示当天所有货源
if (!currentSellerId) {
showAllSupplies();
}
} else {
console.error('加载统计数据失败:', data.message);
@ -918,6 +1013,11 @@
chartData = cachedData.chartData;
suppliesData = cachedData.suppliesData;
usersData = cachedData.usersData;
// 只有在当前没有显示特定卖家的货源时才显示当天所有货源
if (!currentSellerId && suppliesSection.style.display === 'block') {
showAllSupplies();
}
}
}
} catch (error) {
@ -932,6 +1032,11 @@
chartData = cachedData.chartData;
suppliesData = cachedData.suppliesData;
usersData = cachedData.usersData;
// 只有在当前没有显示特定卖家的货源时才显示当天所有货源
if (!currentSellerId) {
showAllSupplies();
}
}
}
}
@ -1045,6 +1150,118 @@
}
});
// 显示当天所有货源
async function showAllSupplies() {
// 保存当前滚动位置和货源列表高度
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
const originalHeight = suppliesGrid.clientHeight;
// 清空当前正在显示的卖家ID
currentSellerId = null;
// 构建标题
let title = '当天所有货源';
// 根据当前筛选条件更新标题
switch (currentFilter) {
case 'today':
title = '今日所有货源';
break;
case 'yesterday':
title = '昨日所有货源';
break;
case 'beforeYesterday':
title = '前日所有货源';
break;
case 'week':
title = '本周所有货源';
break;
case 'month':
title = '本月所有货源';
break;
case 'all':
title = '全部货源';
break;
case 'custom':
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (startDate && endDate) {
title = `${startDate} 至 ${endDate} 所有货源`;
} else if (startDate) {
title = `${startDate} 至今所有货源`;
} else if (endDate) {
title = `截至 ${endDate} 所有货源`;
}
break;
}
// 只有在标题变化时才更新,减少不必要的DOM操作
if (suppliesTitle.textContent !== title) {
suppliesTitle.textContent = title;
}
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
}
try {
// 直接从API获取所有货源数据
let url = `/api/admin/supplies?filter=${currentFilter}`;
if (currentFilter === 'custom') {
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (startDate) url += `&startDate=${startDate}`;
if (endDate) url += `&endDate=${endDate}`;
}
console.log('showAllSupplies - 正在请求API:', url);
const response = await fetch(url, {
method: 'GET',
timeout: 10000, // 添加10秒超时设置
headers: {
'Content-Type': 'application/json'
}
});
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
const data = await response.json();
console.log('showAllSupplies - API返回数据:', data);
// 仅在数据成功返回且有变化时才更新DOM,减少视觉闪烁
if (data.success) {
console.log('showAllSupplies - 数据请求成功,supplies数量:', data.data.supplies ? data.data.supplies.length : 0);
if (data.data.supplies && data.data.supplies.length > 0) {
renderSupplies(data.data.supplies);
} else {
// 显示空状态
console.log('showAllSupplies - 没有找到货源数据');
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">暂无货源数据</p>';
}
} else {
console.error('加载货源数据失败:', data.message);
// 只在当前不是错误状态时才显示错误信息
if (!suppliesGrid.innerHTML.includes('加载失败') && !suppliesGrid.innerHTML.includes('网络异常')) {
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载货源数据失败,请稍后重试</p>';
}
}
} catch (error) {
console.error('加载货源数据出错:', error);
// 只在当前不是错误状态时才显示错误信息
if (!suppliesGrid.innerHTML.includes('加载失败') && !suppliesGrid.innerHTML.includes('网络异常')) {
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">网络异常,请检查网络连接后重试</p>';
}
} finally {
// 恢复滚动位置
window.scrollTo(0, scrollPosition);
}
}
// 显示指定卖家的货源
async function showSuppliesBySeller(sellerId) {
// 保存当前正在显示的卖家ID
@ -1068,8 +1285,23 @@
if (endDate) url += `&endDate=${endDate}`;
}
// 显示加载状态
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载中...</p>';
try {
const response = await fetch(url);
const response = await fetch(url, {
method: 'GET',
timeout: 10000, // 添加10秒超时设置
headers: {
'Content-Type': 'application/json'
}
});
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
const data = await response.json();
if (data.success) {
@ -1094,43 +1326,59 @@
suppliesTitle.textContent = title;
renderSupplies(data.data.supplies);
suppliesSection.style.display = 'block';
// 滚动到货源详情区域
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
// 只有在第一次显示suppliesSection时才滚动到该区域
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} else {
console.error('加载货源数据失败:', data.message);
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载货源数据失败,请刷新页面重试</p>';
suppliesSection.style.display = 'block';
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载货源数据失败,请稍后重试</p>';
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
}
}
} catch (error) {
console.error('加载货源数据出错:', error);
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载货源数据失败,请刷新页面重试</p>';
suppliesSection.style.display = 'block';
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">网络异常,请检查网络连接后重试</p>';
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
}
}
}
// 渲染货源卡片
function renderSupplies(supplies) {
console.log('renderSupplies函数被调用,supplies数量:', supplies.length);
console.log('完整supplies数据:', supplies);
suppliesGrid.innerHTML = '';
// 过滤掉hidden状态的货源
const filteredSupplies = supplies.filter(supply => supply.status !== 'hidden');
console.log('过滤后的supplies数量:', filteredSupplies.length);
console.log('过滤后的supplies数据:', filteredSupplies);
// 不过滤任何状态的货源,显示所有货源
const filteredSupplies = supplies;
if (filteredSupplies.length === 0) {
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">暂无货源数据</p>';
return;
}
// 保存当前滚动位置和容器高度
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
const containerHeight = suppliesGrid.clientHeight;
// 设置容器固定高度,防止清空内容时页面高度变化
suppliesGrid.style.height = `${containerHeight}px`;
suppliesGrid.style.overflow = 'hidden';
// 创建DocumentFragment,用于构建DOM结构,减少页面重排
const fragment = document.createDocumentFragment();
filteredSupplies.forEach((supply, index) => {
console.log(`渲染第${index + 1}个货源,ID:`, supply.productId);
console.log(`该货源的product_log字段:`, supply.product_log);
console.log(`该货源的product_log类型:`, typeof supply.product_log);
const card = document.createElement('div');
card.className = 'supply-card';
card.style.cssText = 'border: 1px solid #e8e8e8; border-radius: 8px; padding: 15px; transition: all 0.3s;';
@ -1304,11 +1552,6 @@
</div>`;
}
// 产品日志展示 - 添加调试信息
console.log('当前货源的product_log字段:', supply.product_log);
console.log('product_log类型:', typeof supply.product_log);
console.log('product_log值是否为null:', supply.product_log === null);
// 无论product_log是什么类型,都显示日志区域
detailsHTML += `<div style="margin: 10px 0;">
<h4 style="margin: 0 0 10px 0; font-size: 14px; font-weight: bold; color: #333; padding-bottom: 5px; border-bottom: 2px solid #ff4d4f;">价格变更日志</h4>`;
@ -1317,43 +1560,34 @@
let logs = [];
let hasLogs = false;
console.log('开始处理product_log:', supply.product_log);
// 检查product_log是否有值
if (supply.product_log !== null && supply.product_log !== undefined && supply.product_log !== '') {
// 有值,处理日志
if (typeof supply.product_log === 'string') {
// 清理JSON字符串中的特殊字符(换行符、制表符等)
const cleanLogStr = supply.product_log.replace(/\s+/g, ' ').trim();
console.log('清理后的product_log:', cleanLogStr);
// 检查是否为JSON格式
if (cleanLogStr.startsWith('[') && cleanLogStr.endsWith(']')) {
try {
logs = JSON.parse(cleanLogStr);
console.log('解析成功,logs:', logs);
} catch (parseError) {
console.error('JSON解析失败:', parseError);
// 解析失败,作为单个日志处理
logs = [supply.product_log];
console.log('作为单个日志处理,logs:', logs);
}
} else {
// 不是JSON数组,作为单个日志处理
logs = [supply.product_log];
console.log('不是JSON数组,作为单个日志处理,logs:', logs);
}
} else if (Array.isArray(supply.product_log)) {
logs = supply.product_log;
console.log('是数组,直接使用,logs:', logs);
} else {
// 其他类型,转换为字符串数组
logs = [String(supply.product_log)];
console.log('转换为字符串数组,logs:', logs);
}
hasLogs = logs.length > 0;
console.log('hasLogs:', hasLogs);
}
// 显示日志
@ -1419,8 +1653,18 @@
card.style.transform = 'translateY(0)';
});
suppliesGrid.appendChild(card);
// 添加到DocumentFragment
fragment.appendChild(card);
});
// 一次性替换旧内容,减少页面重排
suppliesGrid.innerHTML = '';
suppliesGrid.appendChild(fragment);
// 恢复容器高度和滚动位置
suppliesGrid.style.height = '';
suppliesGrid.style.overflow = '';
window.scrollTo(0, scrollPosition);
}
</script>
</body>

16
Reject.js

@ -1882,16 +1882,26 @@ app.get('/api/admin/supplies', async (req, res) => {
}
}
// 获取指定卖家的货源列表,关联users表获取创建人姓名,过滤掉hidden状态的货源
// 获取货源列表,关联users表获取创建人姓名,过滤掉hidden状态的货源
// 替换created_at为p.created_at,避免歧义
const suppliesTimeCondition = timeCondition.replace(/created_at/g, 'p.created_at');
// 构建查询条件,支持带sellerId或不带sellerId的情况
let whereCondition = 'p.status != "hidden"';
let queryParams = [];
if (sellerId) {
whereCondition += ' AND p.sellerId = ?';
queryParams.push(sellerId);
}
const [supplies] = await connection.query(`
SELECT p.*, u.nickName
FROM products p
LEFT JOIN users u ON p.sellerId = u.userId
WHERE p.sellerId = ? AND p.status != 'hidden' ${suppliesTimeCondition}
WHERE ${whereCondition} ${suppliesTimeCondition}
ORDER BY p.created_at DESC
`, [sellerId]);
`, queryParams);
connection.release();

Loading…
Cancel
Save