|
|
@ -747,6 +747,23 @@ |
|
|
let suppliesData = []; |
|
|
let suppliesData = []; |
|
|
let usersData = []; |
|
|
let usersData = []; |
|
|
let chartData = []; |
|
|
let chartData = []; |
|
|
|
|
|
let isLoading = false; // 防重复点击标志 |
|
|
|
|
|
|
|
|
|
|
|
// 缓存相关变量 |
|
|
|
|
|
let suppliesCache = {}; // 货源数据缓存 |
|
|
|
|
|
let cacheExpiry = 5 * 60 * 1000; // 缓存过期时间(5分钟) |
|
|
|
|
|
|
|
|
|
|
|
// 分页相关变量 |
|
|
|
|
|
let currentPage = 1; // 当前页码 |
|
|
|
|
|
let pageSize = 20; // 每页显示数量 |
|
|
|
|
|
let totalItems = 0; // 总数据量 |
|
|
|
|
|
let totalPages = 0; // 总页数 |
|
|
|
|
|
|
|
|
|
|
|
// 虚拟滚动相关变量 |
|
|
|
|
|
let virtualScrollEnabled = false; // 是否启用虚拟滚动 |
|
|
|
|
|
let itemHeight = 600; // 估计的卡片高度 |
|
|
|
|
|
let visibleCount = 5; // 可见区域显示的卡片数量 |
|
|
|
|
|
let bufferCount = 2; // 缓冲区卡片数量 |
|
|
|
|
|
|
|
|
// 货源详情相关变量 |
|
|
// 货源详情相关变量 |
|
|
let currentSellerId = null; // 当前正在显示的卖家ID |
|
|
let currentSellerId = null; // 当前正在显示的卖家ID |
|
|
@ -795,6 +812,7 @@ |
|
|
checkLoginAndRole(); |
|
|
checkLoginAndRole(); |
|
|
|
|
|
|
|
|
initEventListeners(); |
|
|
initEventListeners(); |
|
|
|
|
|
optimizeEventListeners(); // 优化事件监听器 |
|
|
initWebSocket(); |
|
|
initWebSocket(); |
|
|
loadStats(currentFilter); |
|
|
loadStats(currentFilter); |
|
|
|
|
|
|
|
|
@ -1253,6 +1271,12 @@ |
|
|
|
|
|
|
|
|
// 设置筛选条件 |
|
|
// 设置筛选条件 |
|
|
function setFilter(filter) { |
|
|
function setFilter(filter) { |
|
|
|
|
|
// 防重复点击检查 |
|
|
|
|
|
if (isLoading) { |
|
|
|
|
|
console.log('数据正在加载中,请勿重复点击'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
currentFilter = filter; |
|
|
currentFilter = filter; |
|
|
|
|
|
|
|
|
// 重置当前卖家ID,确保切换时间筛选时显示所有货源 |
|
|
// 重置当前卖家ID,确保切换时间筛选时显示所有货源 |
|
|
@ -1272,6 +1296,12 @@ |
|
|
|
|
|
|
|
|
// 应用自定义时间筛选 |
|
|
// 应用自定义时间筛选 |
|
|
function applyCustomFilter() { |
|
|
function applyCustomFilter() { |
|
|
|
|
|
// 防重复点击检查 |
|
|
|
|
|
if (isLoading) { |
|
|
|
|
|
console.log('数据正在加载中,请勿重复点击'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const startDate = startDateInput.value; |
|
|
const startDate = startDateInput.value; |
|
|
const endDate = endDateInput.value; |
|
|
const endDate = endDateInput.value; |
|
|
|
|
|
|
|
|
@ -1327,9 +1357,72 @@ |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 封装API调用函数 |
|
|
|
|
|
async function fetchSuppliesData(filter, startDate = '', endDate = '', sellerId = null) { |
|
|
|
|
|
// 构建缓存键 |
|
|
|
|
|
const cacheKey = sellerId ? |
|
|
|
|
|
`seller_${sellerId}_${filter}_${startDate}_${endDate}` : |
|
|
|
|
|
`${filter}_${startDate}_${endDate}`; |
|
|
|
|
|
|
|
|
|
|
|
// 检查缓存 |
|
|
|
|
|
if (suppliesCache[cacheKey] && (Date.now() - suppliesCache[cacheKey].timestamp < cacheExpiry)) { |
|
|
|
|
|
console.log('使用内存缓存数据:', cacheKey); |
|
|
|
|
|
return suppliesCache[cacheKey].data; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 构建API请求URL |
|
|
|
|
|
let url = `/api/admin/supplies?filter=${filter}`; |
|
|
|
|
|
if (sellerId) url += `&sellerId=${sellerId}`; |
|
|
|
|
|
if (filter === 'custom') { |
|
|
|
|
|
if (startDate) url += `&startDate=${startDate}`; |
|
|
|
|
|
if (endDate) url += `&endDate=${endDate}`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log('fetchSuppliesData - 正在请求API:', url); |
|
|
|
|
|
const response = await fetch(url, { |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
timeout: 10000, |
|
|
|
|
|
headers: { |
|
|
|
|
|
'Content-Type': 'application/json' |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) { |
|
|
|
|
|
throw new Error(`HTTP错误! 状态: ${response.status}`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
|
|
|
|
|
|
if (data.success) { |
|
|
|
|
|
// 保存到内存缓存 |
|
|
|
|
|
suppliesCache[cacheKey] = { |
|
|
|
|
|
data: data.data.supplies || [], |
|
|
|
|
|
timestamp: Date.now() |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return suppliesCache[cacheKey].data; |
|
|
|
|
|
} else { |
|
|
|
|
|
console.error('加载货源数据失败:', data.message); |
|
|
|
|
|
return []; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('fetchSuppliesData - 加载货源数据出错:', error); |
|
|
|
|
|
return []; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 加载统计数据 |
|
|
// 加载统计数据 |
|
|
async function loadStats(filter, startDate = '', endDate = '') { |
|
|
async function loadStats(filter, startDate = '', endDate = '') { |
|
|
|
|
|
// 防重复点击检查 |
|
|
|
|
|
if (isLoading) { |
|
|
|
|
|
console.log('数据正在加载中,请勿重复点击'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
|
|
|
isLoading = true; |
|
|
|
|
|
|
|
|
// 构建API请求URL,包含自定义日期参数 |
|
|
// 构建API请求URL,包含自定义日期参数 |
|
|
let url = `/api/admin/stats/supplies?filter=${filter}`; |
|
|
let url = `/api/admin/stats/supplies?filter=${filter}`; |
|
|
if (filter === 'custom') { |
|
|
if (filter === 'custom') { |
|
|
@ -1397,6 +1490,8 @@ |
|
|
showAllSupplies(); |
|
|
showAllSupplies(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} finally { |
|
|
|
|
|
isLoading = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1518,6 +1613,9 @@ |
|
|
// 清空当前正在显示的卖家ID |
|
|
// 清空当前正在显示的卖家ID |
|
|
currentSellerId = null; |
|
|
currentSellerId = null; |
|
|
|
|
|
|
|
|
|
|
|
// 重置页码 |
|
|
|
|
|
currentPage = 1; |
|
|
|
|
|
|
|
|
// 构建标题 |
|
|
// 构建标题 |
|
|
let title = '当天所有货源'; |
|
|
let title = '当天所有货源'; |
|
|
|
|
|
|
|
|
@ -1565,50 +1663,23 @@ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 直接从API获取所有货源数据 |
|
|
// 使用封装的API调用函数获取数据 |
|
|
let url = `/api/admin/supplies?filter=${currentFilter}`; |
|
|
const startDate = currentFilter === 'custom' ? startDateInput.value : ''; |
|
|
if (currentFilter === 'custom') { |
|
|
const endDate = currentFilter === 'custom' ? endDateInput.value : ''; |
|
|
const startDate = startDateInput.value; |
|
|
|
|
|
const endDate = endDateInput.value; |
|
|
|
|
|
if (startDate) url += `&startDate=${startDate}`; |
|
|
|
|
|
if (endDate) url += `&endDate=${endDate}`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log('showAllSupplies - 正在请求API:', url); |
|
|
const supplies = await fetchSuppliesData(currentFilter, startDate, endDate); |
|
|
const response = await fetch(url, { |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
timeout: 10000, // 添加10秒超时设置 |
|
|
|
|
|
headers: { |
|
|
|
|
|
'Content-Type': 'application/json' |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 检查响应状态 |
|
|
// 更新总数据量和总页数 |
|
|
if (!response.ok) { |
|
|
totalItems = supplies.length; |
|
|
throw new Error(`HTTP错误! 状态: ${response.status}`); |
|
|
totalPages = Math.ceil(totalItems / pageSize); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json(); |
|
|
console.log('showAllSupplies - 数据请求成功,总数量:', totalItems, '总页数:', totalPages); |
|
|
|
|
|
|
|
|
console.log('showAllSupplies - API返回数据:', data); |
|
|
// 渲染当前页数据 |
|
|
|
|
|
renderSuppliesWithPagination(supplies); |
|
|
|
|
|
|
|
|
// 仅在数据成功返回且有变化时才更新DOM,减少视觉闪烁 |
|
|
// 添加分页控件 |
|
|
if (data.success) { |
|
|
addPaginationControls(); |
|
|
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) { |
|
|
} catch (error) { |
|
|
console.error('加载货源数据出错:', error); |
|
|
console.error('加载货源数据出错:', error); |
|
|
// 只在当前不是错误状态时才显示错误信息 |
|
|
// 只在当前不是错误状态时才显示错误信息 |
|
|
@ -1626,6 +1697,9 @@ |
|
|
// 保存当前正在显示的卖家ID |
|
|
// 保存当前正在显示的卖家ID |
|
|
currentSellerId = sellerId; |
|
|
currentSellerId = sellerId; |
|
|
|
|
|
|
|
|
|
|
|
// 重置页码 |
|
|
|
|
|
currentPage = 1; |
|
|
|
|
|
|
|
|
// 获取当前筛选条件和日期范围 |
|
|
// 获取当前筛选条件和日期范围 |
|
|
let startDate = ''; |
|
|
let startDate = ''; |
|
|
let endDate = ''; |
|
|
let endDate = ''; |
|
|
@ -1637,87 +1711,68 @@ |
|
|
endDate = endDateInput.value; |
|
|
endDate = endDateInput.value; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 构建API请求URL,包含自定义日期参数 |
|
|
|
|
|
let url = `/api/admin/supplies?sellerId=${sellerId}&filter=${filter}`; |
|
|
|
|
|
if (filter === 'custom') { |
|
|
|
|
|
if (startDate) url += `&startDate=${startDate}`; |
|
|
|
|
|
if (endDate) url += `&endDate=${endDate}`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 显示加载状态 |
|
|
// 显示加载状态 |
|
|
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载中...</p>'; |
|
|
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载中...</p>'; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
const response = await fetch(url, { |
|
|
// 使用封装的API调用函数获取数据 |
|
|
method: 'GET', |
|
|
const supplies = await fetchSuppliesData(filter, startDate, endDate, sellerId); |
|
|
timeout: 10000, // 添加10秒超时设置 |
|
|
|
|
|
headers: { |
|
|
|
|
|
'Content-Type': 'application/json' |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 检查响应状态 |
|
|
// 更新总数据量和总页数 |
|
|
if (!response.ok) { |
|
|
totalItems = supplies.length; |
|
|
throw new Error(`HTTP错误! 状态: ${response.status}`); |
|
|
totalPages = Math.ceil(totalItems / pageSize); |
|
|
|
|
|
|
|
|
|
|
|
console.log('showSuppliesBySeller - 数据请求成功,总数量:', totalItems, '总页数:', totalPages); |
|
|
|
|
|
|
|
|
|
|
|
// 获取创建人的姓名 |
|
|
|
|
|
let creatorName = ''; |
|
|
|
|
|
|
|
|
|
|
|
// 从chartData中查找对应的nickName |
|
|
|
|
|
const chartItem = chartData.find(item => item.sellerId === sellerId); |
|
|
|
|
|
if (chartItem && chartItem.nickName) { |
|
|
|
|
|
creatorName = chartItem.nickName; |
|
|
|
|
|
} |
|
|
|
|
|
// 如果chartData中没有,从返回的supplies中获取 |
|
|
|
|
|
else if (supplies.length > 0 && supplies[0].nickName) { |
|
|
|
|
|
creatorName = supplies[0].nickName; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const data = await response.json(); |
|
|
// 构建标题 |
|
|
|
|
|
let title = `${sellerId} 创建的货源`; |
|
|
|
|
|
if (creatorName) { |
|
|
|
|
|
title = `${creatorName} (${sellerId}) 创建的货源`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (data.success) { |
|
|
// 添加"查看全部货源"按钮 |
|
|
// 获取创建人的姓名 |
|
|
suppliesTitle.innerHTML = ` |
|
|
let creatorName = ''; |
|
|
${title} |
|
|
|
|
|
<button id="showAllBtn" style=" |
|
|
// 从chartData中查找对应的nickName |
|
|
margin-left: 10px; |
|
|
const chartItem = chartData.find(item => item.sellerId === sellerId); |
|
|
padding: 4px 12px; |
|
|
if (chartItem && chartItem.nickName) { |
|
|
background-color: #1677ff; |
|
|
creatorName = chartItem.nickName; |
|
|
color: white; |
|
|
} |
|
|
border: none; |
|
|
// 如果chartData中没有,从返回的supplies中获取 |
|
|
border-radius: 4px; |
|
|
else if (data.data.supplies && data.data.supplies.length > 0 && data.data.supplies[0].nickName) { |
|
|
cursor: pointer; |
|
|
creatorName = data.data.supplies[0].nickName; |
|
|
font-size: 12px; |
|
|
} |
|
|
font-weight: normal; |
|
|
|
|
|
">查看全部货源</button> |
|
|
// 构建标题 |
|
|
`; |
|
|
let title = `${sellerId} 创建的货源`; |
|
|
|
|
|
if (creatorName) { |
|
|
// 为"查看全部货源"按钮添加点击事件 |
|
|
title = `${creatorName} (${sellerId}) 创建的货源`; |
|
|
document.getElementById('showAllBtn').addEventListener('click', showAllSupplies); |
|
|
} |
|
|
|
|
|
|
|
|
// 渲染当前页数据 |
|
|
// 添加"查看全部货源"按钮 |
|
|
renderSuppliesWithPagination(supplies); |
|
|
suppliesTitle.innerHTML = ` |
|
|
|
|
|
${title} |
|
|
// 添加分页控件 |
|
|
<button id="showAllBtn" style=" |
|
|
addPaginationControls(); |
|
|
margin-left: 10px; |
|
|
|
|
|
padding: 4px 12px; |
|
|
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排 |
|
|
background-color: #1677ff; |
|
|
if (suppliesSection.style.display !== 'block') { |
|
|
color: white; |
|
|
suppliesSection.style.display = 'block'; |
|
|
border: none; |
|
|
// 只有在第一次显示suppliesSection时才滚动到该区域 |
|
|
border-radius: 4px; |
|
|
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
|
cursor: pointer; |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
font-weight: normal; |
|
|
|
|
|
">查看全部货源</button> |
|
|
|
|
|
`; |
|
|
|
|
|
|
|
|
|
|
|
// 为"查看全部货源"按钮添加点击事件 |
|
|
|
|
|
document.getElementById('showAllBtn').addEventListener('click', showAllSupplies); |
|
|
|
|
|
|
|
|
|
|
|
renderSupplies(data.data.supplies); |
|
|
|
|
|
|
|
|
|
|
|
// 只有在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的display属性不是block时才设置,避免不必要的布局重排 |
|
|
|
|
|
if (suppliesSection.style.display !== 'block') { |
|
|
|
|
|
suppliesSection.style.display = 'block'; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error('加载货源数据出错:', error); |
|
|
console.error('加载货源数据出错:', error); |
|
|
@ -1787,7 +1842,7 @@ |
|
|
mediaElement = `<video src="${mediaUrl}" alt="${supply.productName}" controls class="supply-media"></video>`; |
|
|
mediaElement = `<video src="${mediaUrl}" alt="${supply.productName}" controls class="supply-media"></video>`; |
|
|
} else { |
|
|
} else { |
|
|
// 图片文件 |
|
|
// 图片文件 |
|
|
mediaElement = `<img src="${mediaUrl}" alt="${supply.productName}" class="supply-media" onclick="showPreview('${mediaUrl}')">`; |
|
|
mediaElement = `<img src="${mediaUrl}" alt="${supply.productName}" class="supply-media">`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1982,6 +2037,608 @@ |
|
|
suppliesGrid.style.overflow = ''; |
|
|
suppliesGrid.style.overflow = ''; |
|
|
window.scrollTo(0, scrollPosition); |
|
|
window.scrollTo(0, scrollPosition); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建货源卡片 |
|
|
|
|
|
function createSupplyCard(supply) { |
|
|
|
|
|
const card = document.createElement('div'); |
|
|
|
|
|
card.className = 'supply-card'; |
|
|
|
|
|
card.dataset.productId = supply.productId; // 添加产品ID作为数据属性 |
|
|
|
|
|
|
|
|
|
|
|
// 解析媒体URL |
|
|
|
|
|
let mediaUrl = ''; |
|
|
|
|
|
try { |
|
|
|
|
|
// 使用更快的JSON.parse替代复杂的字符串处理 |
|
|
|
|
|
const imageUrls = JSON.parse(supply.imageUrls || '[]'); |
|
|
|
|
|
if (imageUrls && Array.isArray(imageUrls) && imageUrls.length > 0) { |
|
|
|
|
|
mediaUrl = imageUrls[0]; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// 解析失败时不打印错误,避免性能影响 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 状态文本映射,避免switch语句 |
|
|
|
|
|
const statusMap = { |
|
|
|
|
|
'published': '已发布', |
|
|
|
|
|
'sold_out': '已下架', |
|
|
|
|
|
'hidden': '已隐藏' |
|
|
|
|
|
}; |
|
|
|
|
|
const statusClass = `status-${supply.status}`; |
|
|
|
|
|
const statusText = statusMap[supply.status] || '待审核'; |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为视频文件(使用更简单的正则表达式) |
|
|
|
|
|
let mediaElement = ''; |
|
|
|
|
|
if (mediaUrl) { |
|
|
|
|
|
const isVideo = /\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i.test(mediaUrl) || mediaUrl.startsWith('data:video/'); |
|
|
|
|
|
if (isVideo) { |
|
|
|
|
|
// 视频文件 |
|
|
|
|
|
mediaElement = `<video src="${mediaUrl}" alt="${supply.productName}" controls class="supply-media"></video>`; |
|
|
|
|
|
} else { |
|
|
|
|
|
// 图片文件 |
|
|
|
|
|
mediaElement = `<img src="${mediaUrl}" alt="${supply.productName}" class="supply-media" onclick="showPreview('${mediaUrl}')">`; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 第一行展示:种类|蛋黄|货源类型|产品包装|新鲜程度 |
|
|
|
|
|
const firstLineParts = []; |
|
|
|
|
|
if (supply.category) firstLineParts.push(supply.category); |
|
|
|
|
|
if (supply.yolk) firstLineParts.push(supply.yolk); |
|
|
|
|
|
if (supply.sourceType) firstLineParts.push(supply.sourceType); |
|
|
|
|
|
if (supply.producting) firstLineParts.push(supply.producting); |
|
|
|
|
|
if (supply.freshness) firstLineParts.push(supply.freshness); |
|
|
|
|
|
const firstLineHTML = firstLineParts.length > 0 ? |
|
|
|
|
|
`<p class="supply-meta">${firstLineParts.join(' | ')}</p>` : ''; |
|
|
|
|
|
|
|
|
|
|
|
// 构建详细信息HTML |
|
|
|
|
|
const detailsParts = []; |
|
|
|
|
|
|
|
|
|
|
|
// 产品ID |
|
|
|
|
|
if (supply.productId) { |
|
|
|
|
|
detailsParts.push(`<p><strong>产品ID:</strong> ${supply.productId}</p>`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 规格+件数+采购价+销售价组合展示 |
|
|
|
|
|
const specifications = []; |
|
|
|
|
|
if (supply.specification) { |
|
|
|
|
|
if (typeof supply.specification === 'string') { |
|
|
|
|
|
// 规格可能用逗号或中文逗号分隔 |
|
|
|
|
|
specifications.push(...supply.specification.split(/[,,]/).filter(spec => spec.trim())); |
|
|
|
|
|
} else if (Array.isArray(supply.specification)) { |
|
|
|
|
|
specifications.push(...supply.specification); |
|
|
|
|
|
} else { |
|
|
|
|
|
specifications.push(String(supply.specification)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 处理数量、采购价、销售价 |
|
|
|
|
|
const quantities = Array.isArray(supply.quantity) ? supply.quantity : |
|
|
|
|
|
(typeof supply.quantity === 'string' ? supply.quantity.split(',').filter(qty => qty.trim()) : |
|
|
|
|
|
(supply.quantity ? [String(supply.quantity)] : [])); |
|
|
|
|
|
|
|
|
|
|
|
const costprices = Array.isArray(supply.costprice) ? supply.costprice : |
|
|
|
|
|
(typeof supply.costprice === 'string' ? supply.costprice.split(',').filter(cp => cp.trim()) : |
|
|
|
|
|
(supply.costprice ? [String(supply.costprice)] : [])); |
|
|
|
|
|
|
|
|
|
|
|
const prices = Array.isArray(supply.price) ? supply.price : |
|
|
|
|
|
(typeof supply.price === 'string' ? supply.price.split(',').filter(p => p.trim()) : |
|
|
|
|
|
(supply.price ? [String(supply.price)] : [])); |
|
|
|
|
|
|
|
|
|
|
|
// 计算最大长度,确保每个规格都有对应的数据 |
|
|
|
|
|
const maxLength = Math.max(specifications.length, quantities.length, costprices.length, prices.length); |
|
|
|
|
|
|
|
|
|
|
|
// 生成组合展示HTML |
|
|
|
|
|
if (maxLength > 0) { |
|
|
|
|
|
const specDetails = []; |
|
|
|
|
|
for (let i = 0; i < maxLength; i++) { |
|
|
|
|
|
const spec = specifications[i] || ''; |
|
|
|
|
|
const quantity = quantities[i] || ''; |
|
|
|
|
|
const costprice = costprices[i] || ''; |
|
|
|
|
|
const price = prices[i] || ''; |
|
|
|
|
|
|
|
|
|
|
|
// 只显示有数据的行 |
|
|
|
|
|
if (spec || quantity || costprice || price) { |
|
|
|
|
|
const parts = []; |
|
|
|
|
|
if (spec) parts.push(`规格${i+1}: ${spec}`); |
|
|
|
|
|
if (quantity) parts.push(`件数: ${quantity}件`); |
|
|
|
|
|
if (costprice) parts.push(`采购价: ¥${costprice}`); |
|
|
|
|
|
if (price) parts.push(`销售价: ¥${price}`); |
|
|
|
|
|
|
|
|
|
|
|
if (parts.length > 0) { |
|
|
|
|
|
specDetails.push(`<div class="spec-item">${parts.join(' - ')}</div>`); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (specDetails.length > 0) { |
|
|
|
|
|
detailsParts.push(`<div class="spec-section">${specDetails.join('')}</div>`); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 商品地区 |
|
|
|
|
|
if (supply.region) { |
|
|
|
|
|
detailsParts.push(`<p><strong>商品地区:</strong> ${supply.region}</p>`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 商品联系人和联系电话 |
|
|
|
|
|
if (supply.product_contact || supply.contact_phone) { |
|
|
|
|
|
let contactText = '<strong>商品联系人:</strong> '; |
|
|
|
|
|
if (supply.product_contact) { |
|
|
|
|
|
contactText += supply.product_contact; |
|
|
|
|
|
} |
|
|
|
|
|
if (supply.contact_phone) { |
|
|
|
|
|
if (supply.product_contact) { |
|
|
|
|
|
contactText += ` ${supply.contact_phone}`; |
|
|
|
|
|
} else { |
|
|
|
|
|
contactText += supply.contact_phone; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
detailsParts.push(`<p>${contactText}</p>`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建时间 |
|
|
|
|
|
detailsParts.push(`<p><strong>创建时间:</strong> ${new Date(supply.created_at).toLocaleString()}</p>`); |
|
|
|
|
|
|
|
|
|
|
|
// 修改时间 |
|
|
|
|
|
if (supply.updated_at) { |
|
|
|
|
|
detailsParts.push(`<p><strong>修改时间:</strong> ${new Date(supply.updated_at).toLocaleString()}</p>`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 货源描述(限制显示长度) |
|
|
|
|
|
if (supply.description) { |
|
|
|
|
|
const shortDesc = supply.description.length > 50 ? `${supply.description.substring(0, 50)}...` : supply.description; |
|
|
|
|
|
detailsParts.push(`<div class="description-section"><strong>货源描述:</strong><div>${shortDesc}</div></div>`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 价格变更日志 |
|
|
|
|
|
let logHTML = `<div class="log-section"><h4>价格变更日志</h4>`; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
let logs = []; |
|
|
|
|
|
let hasLogs = false; |
|
|
|
|
|
|
|
|
|
|
|
// 检查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(); |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为JSON格式 |
|
|
|
|
|
if (cleanLogStr.startsWith('[') && cleanLogStr.endsWith(']')) { |
|
|
|
|
|
try { |
|
|
|
|
|
logs = JSON.parse(cleanLogStr); |
|
|
|
|
|
} catch (parseError) { |
|
|
|
|
|
// 解析失败,作为单个日志处理 |
|
|
|
|
|
logs = [supply.product_log]; |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// 不是JSON数组,作为单个日志处理 |
|
|
|
|
|
logs = [supply.product_log]; |
|
|
|
|
|
} |
|
|
|
|
|
} else if (Array.isArray(supply.product_log)) { |
|
|
|
|
|
logs = supply.product_log; |
|
|
|
|
|
} else { |
|
|
|
|
|
// 其他类型,转换为字符串数组 |
|
|
|
|
|
logs = [String(supply.product_log)]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
hasLogs = logs.length > 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 显示日志 |
|
|
|
|
|
if (hasLogs) { |
|
|
|
|
|
logs.forEach((log, index) => { |
|
|
|
|
|
logHTML += `<div class="log-item"><span class="log-badge">日志${index + 1}</span><span>${log}</span></div>`; |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 没有日志,显示提示 |
|
|
|
|
|
logHTML += `<div class="log-item"><span>暂无价格变更日志</span></div>`; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
// 如果解析失败,直接显示日志内容 |
|
|
|
|
|
logHTML += `<div class="log-item"><span>${supply.product_log || '暂无价格变更日志'}</span></div>`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
logHTML += `</div>`; |
|
|
|
|
|
detailsParts.push(logHTML); |
|
|
|
|
|
|
|
|
|
|
|
// 状态 |
|
|
|
|
|
detailsParts.push(`<span class="supply-status ${statusClass}">${statusText}</span>`); |
|
|
|
|
|
|
|
|
|
|
|
// 构建完整卡片HTML |
|
|
|
|
|
card.innerHTML = ` |
|
|
|
|
|
<div class="supply-header"> |
|
|
|
|
|
${mediaElement} |
|
|
|
|
|
<div class="supply-title">${supply.productName}</div> |
|
|
|
|
|
${firstLineHTML} |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="supply-info"> |
|
|
|
|
|
${detailsParts.join('')} |
|
|
|
|
|
</div> |
|
|
|
|
|
`; |
|
|
|
|
|
|
|
|
|
|
|
return card; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 渲染带分页的货源卡片 |
|
|
|
|
|
function renderSuppliesWithPagination(supplies) { |
|
|
|
|
|
// 计算当前页的数据范围 |
|
|
|
|
|
const startIndex = (currentPage - 1) * pageSize; |
|
|
|
|
|
const endIndex = startIndex + pageSize; |
|
|
|
|
|
const currentPageSupplies = supplies.slice(startIndex, endIndex); |
|
|
|
|
|
|
|
|
|
|
|
if (currentPageSupplies.length === 0) { |
|
|
|
|
|
suppliesGrid.innerHTML = '<p class="empty-state">暂无货源数据</p>'; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存当前滚动位置 |
|
|
|
|
|
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop; |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否启用虚拟滚动 |
|
|
|
|
|
if (virtualScrollEnabled && currentPageSupplies.length > visibleCount + bufferCount * 2) { |
|
|
|
|
|
// 使用虚拟滚动 |
|
|
|
|
|
renderWithVirtualScroll(currentPageSupplies); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 使用增量渲染 |
|
|
|
|
|
const containerHeight = suppliesGrid.clientHeight; |
|
|
|
|
|
suppliesGrid.style.height = `${containerHeight}px`; |
|
|
|
|
|
suppliesGrid.style.overflow = 'hidden'; |
|
|
|
|
|
incrementalRender(currentPageSupplies); |
|
|
|
|
|
suppliesGrid.style.height = ''; |
|
|
|
|
|
suppliesGrid.style.overflow = ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 恢复滚动位置 |
|
|
|
|
|
window.scrollTo(0, scrollPosition); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 增量渲染函数 |
|
|
|
|
|
function incrementalRender(supplies) { |
|
|
|
|
|
// 创建一个Map来存储当前页的货源 |
|
|
|
|
|
const currentSuppliesMap = new Map(); |
|
|
|
|
|
supplies.forEach(supply => { |
|
|
|
|
|
currentSuppliesMap.set(supply.productId, supply); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 存储已存在的卡片 |
|
|
|
|
|
const existingCards = new Map(); |
|
|
|
|
|
Array.from(suppliesGrid.children).forEach(child => { |
|
|
|
|
|
if (child.classList.contains('supply-card')) { |
|
|
|
|
|
const productId = child.dataset.productId; |
|
|
|
|
|
if (productId) { |
|
|
|
|
|
existingCards.set(productId, child); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 创建DocumentFragment,用于构建DOM结构,减少页面重排 |
|
|
|
|
|
const fragment = document.createDocumentFragment(); |
|
|
|
|
|
|
|
|
|
|
|
// 遍历当前页的货源 |
|
|
|
|
|
supplies.forEach(supply => { |
|
|
|
|
|
const productId = supply.productId; |
|
|
|
|
|
|
|
|
|
|
|
if (existingCards.has(productId)) { |
|
|
|
|
|
// 卡片已存在,检查是否需要更新 |
|
|
|
|
|
const existingCard = existingCards.get(productId); |
|
|
|
|
|
existingCards.delete(productId); // 标记为已处理 |
|
|
|
|
|
|
|
|
|
|
|
// 简单的更新检查:比较创建时间和修改时间 |
|
|
|
|
|
const existingCreatedAt = existingCard.querySelector('.supply-info p:nth-child(6)')?.textContent; |
|
|
|
|
|
const existingUpdatedAt = existingCard.querySelector('.supply-info p:nth-child(7)')?.textContent; |
|
|
|
|
|
|
|
|
|
|
|
const newCreatedAt = `创建时间: ${new Date(supply.created_at).toLocaleString()}`; |
|
|
|
|
|
const newUpdatedAt = supply.updated_at ? `修改时间: ${new Date(supply.updated_at).toLocaleString()}` : ''; |
|
|
|
|
|
|
|
|
|
|
|
if (existingCreatedAt !== newCreatedAt || |
|
|
|
|
|
(newUpdatedAt && existingUpdatedAt !== newUpdatedAt)) { |
|
|
|
|
|
// 需要更新,替换为新卡片 |
|
|
|
|
|
const newCard = createSupplyCard(supply); |
|
|
|
|
|
fragment.appendChild(newCard); |
|
|
|
|
|
existingCard.remove(); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 不需要更新,保留原卡片 |
|
|
|
|
|
fragment.appendChild(existingCard); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// 卡片不存在,创建新卡片 |
|
|
|
|
|
const newCard = createSupplyCard(supply); |
|
|
|
|
|
fragment.appendChild(newCard); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 移除不再需要的卡片 |
|
|
|
|
|
existingCards.forEach(card => { |
|
|
|
|
|
card.remove(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 清空容器并添加新内容 |
|
|
|
|
|
suppliesGrid.innerHTML = ''; |
|
|
|
|
|
suppliesGrid.appendChild(fragment); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 添加分页控件 |
|
|
|
|
|
function addPaginationControls() { |
|
|
|
|
|
// 检查是否已经存在分页控件 |
|
|
|
|
|
const existingPagination = document.querySelector('.pagination'); |
|
|
|
|
|
if (existingPagination) { |
|
|
|
|
|
existingPagination.remove(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果只有一页,不需要分页控件 |
|
|
|
|
|
if (totalPages <= 1) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建分页控件容器 |
|
|
|
|
|
const pagination = document.createElement('div'); |
|
|
|
|
|
pagination.className = 'pagination'; |
|
|
|
|
|
pagination.style.cssText = ` |
|
|
|
|
|
display: flex; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
gap: 8px; |
|
|
|
|
|
margin-top: 20px; |
|
|
|
|
|
padding: 10px; |
|
|
|
|
|
background-color: #f5f5f5; |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
`; |
|
|
|
|
|
|
|
|
|
|
|
// 添加首页按钮 |
|
|
|
|
|
const firstBtn = document.createElement('button'); |
|
|
|
|
|
firstBtn.textContent = '首页'; |
|
|
|
|
|
firstBtn.style.cssText = ` |
|
|
|
|
|
padding: 6px 12px; |
|
|
|
|
|
background-color: #1677ff; |
|
|
|
|
|
color: white; |
|
|
|
|
|
border: none; |
|
|
|
|
|
border-radius: 4px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
`; |
|
|
|
|
|
firstBtn.disabled = currentPage === 1; |
|
|
|
|
|
firstBtn.addEventListener('click', () => { |
|
|
|
|
|
if (currentPage > 1) { |
|
|
|
|
|
currentPage = 1; |
|
|
|
|
|
showCurrentPageData(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
pagination.appendChild(firstBtn); |
|
|
|
|
|
|
|
|
|
|
|
// 添加上一页按钮 |
|
|
|
|
|
const prevBtn = document.createElement('button'); |
|
|
|
|
|
prevBtn.textContent = '上一页'; |
|
|
|
|
|
prevBtn.style.cssText = ` |
|
|
|
|
|
padding: 6px 12px; |
|
|
|
|
|
background-color: #1677ff; |
|
|
|
|
|
color: white; |
|
|
|
|
|
border: none; |
|
|
|
|
|
border-radius: 4px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
`; |
|
|
|
|
|
prevBtn.disabled = currentPage === 1; |
|
|
|
|
|
prevBtn.addEventListener('click', () => { |
|
|
|
|
|
if (currentPage > 1) { |
|
|
|
|
|
currentPage--; |
|
|
|
|
|
showCurrentPageData(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
pagination.appendChild(prevBtn); |
|
|
|
|
|
|
|
|
|
|
|
// 添加页码按钮 |
|
|
|
|
|
const maxVisiblePages = 5; |
|
|
|
|
|
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); |
|
|
|
|
|
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); |
|
|
|
|
|
|
|
|
|
|
|
// 调整起始页码,确保显示足够的页码 |
|
|
|
|
|
if (endPage - startPage + 1 < maxVisiblePages) { |
|
|
|
|
|
startPage = Math.max(1, endPage - maxVisiblePages + 1); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (let i = startPage; i <= endPage; i++) { |
|
|
|
|
|
const pageBtn = document.createElement('button'); |
|
|
|
|
|
pageBtn.textContent = i; |
|
|
|
|
|
pageBtn.style.cssText = ` |
|
|
|
|
|
padding: 6px 12px; |
|
|
|
|
|
background-color: ${i === currentPage ? '#0958d9' : '#1677ff'}; |
|
|
|
|
|
color: white; |
|
|
|
|
|
border: none; |
|
|
|
|
|
border-radius: 4px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
`; |
|
|
|
|
|
pageBtn.addEventListener('click', () => { |
|
|
|
|
|
if (i !== currentPage) { |
|
|
|
|
|
currentPage = i; |
|
|
|
|
|
showCurrentPageData(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
pagination.appendChild(pageBtn); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 添加下一页按钮 |
|
|
|
|
|
const nextBtn = document.createElement('button'); |
|
|
|
|
|
nextBtn.textContent = '下一页'; |
|
|
|
|
|
nextBtn.style.cssText = ` |
|
|
|
|
|
padding: 6px 12px; |
|
|
|
|
|
background-color: #1677ff; |
|
|
|
|
|
color: white; |
|
|
|
|
|
border: none; |
|
|
|
|
|
border-radius: 4px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
`; |
|
|
|
|
|
nextBtn.disabled = currentPage === totalPages; |
|
|
|
|
|
nextBtn.addEventListener('click', () => { |
|
|
|
|
|
if (currentPage < totalPages) { |
|
|
|
|
|
currentPage++; |
|
|
|
|
|
showCurrentPageData(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
pagination.appendChild(nextBtn); |
|
|
|
|
|
|
|
|
|
|
|
// 添加末页按钮 |
|
|
|
|
|
const lastBtn = document.createElement('button'); |
|
|
|
|
|
lastBtn.textContent = '末页'; |
|
|
|
|
|
lastBtn.style.cssText = ` |
|
|
|
|
|
padding: 6px 12px; |
|
|
|
|
|
background-color: #1677ff; |
|
|
|
|
|
color: white; |
|
|
|
|
|
border: none; |
|
|
|
|
|
border-radius: 4px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
`; |
|
|
|
|
|
lastBtn.disabled = currentPage === totalPages; |
|
|
|
|
|
lastBtn.addEventListener('click', () => { |
|
|
|
|
|
if (currentPage < totalPages) { |
|
|
|
|
|
currentPage = totalPages; |
|
|
|
|
|
showCurrentPageData(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
pagination.appendChild(lastBtn); |
|
|
|
|
|
|
|
|
|
|
|
// 添加页码信息 |
|
|
|
|
|
const pageInfo = document.createElement('span'); |
|
|
|
|
|
pageInfo.textContent = `第 ${currentPage} / ${totalPages} 页,共 ${totalItems} 条`; |
|
|
|
|
|
pageInfo.style.cssText = ` |
|
|
|
|
|
margin-left: 15px; |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
color: #666; |
|
|
|
|
|
`; |
|
|
|
|
|
pagination.appendChild(pageInfo); |
|
|
|
|
|
|
|
|
|
|
|
// 添加到页面 |
|
|
|
|
|
suppliesSection.appendChild(pagination); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 显示当前页数据 |
|
|
|
|
|
async function showCurrentPageData() { |
|
|
|
|
|
try { |
|
|
|
|
|
const startDate = currentFilter === 'custom' ? startDateInput.value : ''; |
|
|
|
|
|
const endDate = currentFilter === 'custom' ? endDateInput.value : ''; |
|
|
|
|
|
|
|
|
|
|
|
const supplies = await fetchSuppliesData(currentFilter, startDate, endDate, currentSellerId); |
|
|
|
|
|
|
|
|
|
|
|
// 预加载图片 |
|
|
|
|
|
preloadImages(supplies); |
|
|
|
|
|
|
|
|
|
|
|
// 渲染当前页数据 |
|
|
|
|
|
renderSuppliesWithPagination(supplies); |
|
|
|
|
|
|
|
|
|
|
|
// 更新分页控件 |
|
|
|
|
|
addPaginationControls(); |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('加载当前页数据出错:', error); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 优化事件委托,减少事件监听器数量 |
|
|
|
|
|
function optimizeEventListeners() { |
|
|
|
|
|
// 使用事件委托处理图片点击事件 |
|
|
|
|
|
suppliesGrid.addEventListener('click', function(e) { |
|
|
|
|
|
const mediaElement = e.target.closest('.supply-media'); |
|
|
|
|
|
if (mediaElement) { |
|
|
|
|
|
if (mediaElement.tagName === 'IMG') { |
|
|
|
|
|
const mediaUrl = mediaElement.src; |
|
|
|
|
|
showPreview(mediaUrl); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 预加载图片,提高用户体验 |
|
|
|
|
|
function preloadImages(supplies) { |
|
|
|
|
|
supplies.forEach(supply => { |
|
|
|
|
|
try { |
|
|
|
|
|
const imageUrls = JSON.parse(supply.imageUrls || '[]'); |
|
|
|
|
|
if (imageUrls && Array.isArray(imageUrls) && imageUrls.length > 0) { |
|
|
|
|
|
const img = new Image(); |
|
|
|
|
|
img.src = imageUrls[0]; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// 解析失败,忽略 |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 虚拟滚动渲染函数 |
|
|
|
|
|
function renderWithVirtualScroll(supplies) { |
|
|
|
|
|
// 清空容器 |
|
|
|
|
|
suppliesGrid.innerHTML = ''; |
|
|
|
|
|
|
|
|
|
|
|
// 设置容器样式 |
|
|
|
|
|
suppliesGrid.style.cssText = ` |
|
|
|
|
|
position: relative; |
|
|
|
|
|
overflow-y: auto; |
|
|
|
|
|
max-height: 800px; |
|
|
|
|
|
`; |
|
|
|
|
|
|
|
|
|
|
|
// 创建一个容器来存放虚拟元素 |
|
|
|
|
|
const virtualContainer = document.createElement('div'); |
|
|
|
|
|
virtualContainer.style.cssText = ` |
|
|
|
|
|
position: absolute; |
|
|
|
|
|
top: 0; |
|
|
|
|
|
left: 0; |
|
|
|
|
|
width: 100%; |
|
|
|
|
|
`; |
|
|
|
|
|
suppliesGrid.appendChild(virtualContainer); |
|
|
|
|
|
|
|
|
|
|
|
// 创建占位元素,模拟整个列表的高度 |
|
|
|
|
|
const placeholder = document.createElement('div'); |
|
|
|
|
|
placeholder.style.cssText = ` |
|
|
|
|
|
height: ${supplies.length * itemHeight}px; |
|
|
|
|
|
width: 100%; |
|
|
|
|
|
`; |
|
|
|
|
|
virtualContainer.appendChild(placeholder); |
|
|
|
|
|
|
|
|
|
|
|
// 存储当前渲染的元素 |
|
|
|
|
|
const renderedItems = new Map(); |
|
|
|
|
|
|
|
|
|
|
|
// 渲染可视区域内的元素 |
|
|
|
|
|
function renderVisibleItems() { |
|
|
|
|
|
// 获取容器的滚动位置和可视区域高度 |
|
|
|
|
|
const scrollTop = suppliesGrid.scrollTop; |
|
|
|
|
|
const containerHeight = suppliesGrid.clientHeight; |
|
|
|
|
|
|
|
|
|
|
|
// 计算应该渲染的元素范围 |
|
|
|
|
|
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - bufferCount); |
|
|
|
|
|
const endIndex = Math.min(supplies.length, Math.ceil((scrollTop + containerHeight) / itemHeight) + bufferCount); |
|
|
|
|
|
|
|
|
|
|
|
// 移除不在可视区域内的元素 |
|
|
|
|
|
renderedItems.forEach((item, index) => { |
|
|
|
|
|
if (index < startIndex || index >= endIndex) { |
|
|
|
|
|
item.remove(); |
|
|
|
|
|
renderedItems.delete(index); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 渲染可视区域内的元素 |
|
|
|
|
|
for (let i = startIndex; i < endIndex; i++) { |
|
|
|
|
|
if (!renderedItems.has(i)) { |
|
|
|
|
|
const supply = supplies[i]; |
|
|
|
|
|
const item = createSupplyCard(supply); |
|
|
|
|
|
item.style.cssText = ` |
|
|
|
|
|
position: absolute; |
|
|
|
|
|
top: ${i * itemHeight}px; |
|
|
|
|
|
left: 0; |
|
|
|
|
|
width: 100%; |
|
|
|
|
|
height: ${itemHeight}px; |
|
|
|
|
|
box-sizing: border-box; |
|
|
|
|
|
`; |
|
|
|
|
|
virtualContainer.appendChild(item); |
|
|
|
|
|
renderedItems.set(i, item); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 初始渲染 |
|
|
|
|
|
renderVisibleItems(); |
|
|
|
|
|
|
|
|
|
|
|
// 监听滚动事件,动态渲染元素 |
|
|
|
|
|
suppliesGrid.addEventListener('scroll', renderVisibleItems); |
|
|
|
|
|
} |
|
|
</script> |
|
|
</script> |
|
|
</body> |
|
|
</body> |
|
|
</html> |
|
|
</html> |