// 全局变量 let autoRefreshInterval = null; // 保持兼容性 let onSaleRefreshInterval = null; let soldRefreshInterval = null; let statisticsRefreshInterval = null; // 统计数据刷新定时器 let currentOnSalePage = 1; let currentSoldPage = 1; let pageSize = 20; // default fallback, will be loaded from server config let refreshInProgress = false; let onSaleRefreshInProgress = false; let soldRefreshInProgress = false; let manualRefreshInProgress = false; // 手动刷新进行中标志 let lastRefreshTime = 0; let lastOnSaleRefreshTime = 0; let lastSoldRefreshTime = 0; let refreshErrorCount = 0; let onSaleRefreshErrorCount = 0; let soldRefreshErrorCount = 0; // Token有效性检查配置 let TOKEN_CHECK_INTERVAL = 60000; // 默认60秒 let tokenCheckTimer = null; // 用户权限配置 let currentUserLevel = 'guest'; // 当前用户等级 let currentPermissions = null; // 当前用户权限配置 let permissionsApplied = false; // 权限是否已应用 // 详情轮询相关变量 let onSaleDetailPollingTimer = null; let soldDetailPollingTimer = null; const DETAIL_POLLING_INTERVAL = 5000; // 5秒轮询一次(优化:从3秒调整为5秒,减少服务器压力) // 前端架构:前端只从数据库/缓存读取数据,不直接调用API // - 后端爬虫独立运行,定期从X-Meta API获取数据 // - 前端自动刷新只负责更新显示,从数据库读取最新数据 // - 爬虫状态和间隔完全由后端控制 // 搜索相关变量 let isSearchMode = false; let currentSearchKeyword = ''; let currentSearchStatus = ''; let currentSearchPage = 1; let currentSortBy = 'time'; let currentSortOrder = 'desc'; // 商品缓存 - 用于差异更新,避免图片闪烁 let cachedOnSaleGoods = new Map(); // key: goods_id, value: goods data let cachedSoldGoods = new Map(); let imageCache = new Set(); // 已加载的图片URL缓存 // ========== 优化:智能刷新 - 数据哈希缓存 ========== let onSaleDataHash = null; // 在售商品数据哈希 let soldDataHash = null; // 已售商品数据哈希 let statisticsDataHash = null; // 统计数据哈希 // ========== 增量更新哈希状态(用于服务端哈希对比) ========== let onSaleLastHash = null; // 在售商品上次哈希值 let soldLastHash = null; // 已售商品上次哈希值 // ========== 用户权限配置应用 ========== /** * 应用用户权限配置 * @param {object} permissions - 权限配置对象 * @param {string} userLevel - 用户等级 */ function applyUserPermissions(permissions, userLevel) { currentUserLevel = userLevel; currentPermissions = permissions; console.debug(`应用用户权限配置: 等级=${userLevel}`, permissions); // 根据权限配置设置刷新间隔和最小值限制 if (permissions.on_sale_refresh_interval) { $('#onSaleInterval').val(permissions.on_sale_refresh_interval); $('#onSaleInterval').attr('min', permissions.on_sale_refresh_interval); $('#settingsOnSaleInterval').val(permissions.on_sale_refresh_interval); } if (permissions.sold_refresh_interval) { $('#soldInterval').val(permissions.sold_refresh_interval); $('#soldInterval').attr('min', permissions.sold_refresh_interval); $('#settingsSoldInterval').val(permissions.sold_refresh_interval); } // 根据权限配置设置自动刷新开关 // 注意:登录后不默认勾选自动刷新,用户需要手动开启 // 只在首次应用权限时停止自动刷新,后续调用时保持用户的选择状态 if (permissions.auto_refresh !== undefined && !permissionsApplied) { // 不再根据权限自动勾选,始终默认不勾选 // const autoRefreshEnabled = permissions.auto_refresh; $('#autoRefresh').prop('checked', false); $('#mobileAutoRefresh').prop('checked', false); $('#settingsAutoRefresh').prop('checked', false); // 停止可能正在运行的自动刷新 stopAutoRefresh(); console.debug('用户权限配置已应用,自动刷新默认不启用,需手动开启'); } // 禁用游客的某些功能 if (userLevel === 'guest') { // 游客模式:禁用自动刷新 if (!permissions.auto_refresh) { $('#autoRefresh').prop('disabled', true); $('#mobileAutoRefresh').prop('disabled', true); console.debug('游客模式:自动刷新已禁用'); } // 游客模式:禁用搜索功能 if (!permissions.search_enabled) { $('#searchInput').prop('disabled', true); $('#mobileSearchInput').prop('disabled', true); $('#searchBtn').prop('disabled', true); $('#mobileSearchBtn').prop('disabled', true); console.log('游客模式:搜索功能已禁用'); } } else { // 登录用户:启用所有功能 $('#autoRefresh').prop('disabled', false); $('#mobileAutoRefresh').prop('disabled', false); $('#searchInput').prop('disabled', false); $('#mobileSearchInput').prop('disabled', false); $('#searchBtn').prop('disabled', false); $('#mobileSearchBtn').prop('disabled', false); } permissionsApplied = true; } // 导出到全局,供其他脚本调用 window.applyUserPermissions = applyUserPermissions; // ========== PFP编号提取函数 ========== /** * 从字符串中提取有效的PFP编号 * 有效范围: 2-10000 * @param {string} str - 包含编号的字符串 * @returns {number|null} 提取的编号或null */ function extractPfpNumberFromString(str) { if (!str) return null; // 提取所有数字 const numbers = str.match(/\d+/g); if (!numbers) return null; // 返回第一个在有效范围内的数字 for (const numStr of numbers) { const num = parseInt(numStr, 10); if (num >= 2 && num <= 10000) { return num; } } return null; } // 页面加载完成后初始化 $(document).ready(function() { // 配置全局Ajax设置,自动在所有请求中添加Authorization header $.ajaxSetup({ beforeSend: function(xhr) { const token = webAuth.getToken(); if (token) { xhr.setRequestHeader('Authorization', `Bearer ${token}`); } }, error: function(xhr, status, error) { // 全局401错误处理:Token失效时自动登出 if (xhr.status === 401) { const responseData = xhr.responseJSON; const errorDetail = responseData ? responseData.detail : ''; // 检查是否是密码修改或权限变更导致的Token失效 if (errorDetail && ( errorDetail.includes('密码已修改') || errorDetail.includes('密码修改成功') || errorDetail.includes('Token已失效') || errorDetail.includes('Token无效') )) { console.log('检测到Token失效,自动登出:', errorDetail); // 清除本地Token和用户信息 webAuth.clearToken(); // 更新UI状态为游客模式 if (typeof updateUIState === 'function') { updateUIState(); } // 显示提示信息 if (typeof showToast === 'function') { showToast('您的登录已失效,请重新登录', 'warning'); } // 延迟500ms后弹出登录框 setTimeout(() => { const loginModalElement = document.getElementById('loginModal'); if (loginModalElement) { const loginModal = new bootstrap.Modal(loginModalElement); loginModal.show(); } }, 500); // 刷新页面数据(以游客身份) if (typeof loadGoods === 'function') { setTimeout(() => { loadGoods(); }, 800); } } } } }); // 加载Token检查配置 loadTokenCheckConfig(); initializePage(); setupEventListeners(); loadInitialData(); // 如果用户已登录,启动Token有效性检查 const userInfo = webAuth.getUserInfo(); if (userInfo) { startTokenValidityCheck(); } // 启动健康检查机制 startHealthCheck(); }); // ========== 优化:数据哈希计算函数 ========== function hashData(data) { return JSON.stringify(data); } // 加载分页配置 async function loadPaginationConfig() { try { const response = await fetch('/api/v1/system/config/pagination'); const result = await response.json(); if (result.success && result.data) { pageSize = result.data.page_size; console.log(`分页配置已加载: ${pageSize} 条/页`); } } catch (error) { console.warn('加载分页配置失败,使用默认值:', error); } } /** * 从后端获取Token检查间隔配置 */ async function loadTokenCheckConfig() { try { const response = await fetch('/api/v1/web-auth/config'); if (response.ok) { const config = await response.json(); if (config.token_check_interval) { TOKEN_CHECK_INTERVAL = config.token_check_interval; console.debug('[Token检查] 配置已加载,间隔:', TOKEN_CHECK_INTERVAL / 1000, '秒'); } } } catch (error) { console.error('[Token检查] 配置加载失败,使用默认值:', error); } } /** * 启动Token有效性定期检查 */ function startTokenValidityCheck() { // 清除已存在的定时器 if (tokenCheckTimer) { clearInterval(tokenCheckTimer); } // 只有登录用户才需要检查 const userInfo = webAuth.getUserInfo(); if (!userInfo) { return; } console.debug('[Token检查] 启动定期检查,间隔:', TOKEN_CHECK_INTERVAL / 1000, '秒'); tokenCheckTimer = setInterval(async () => { const isValid = await webAuth.checkTokenValidity(); if (!isValid) { console.log('[Token检查] Token已失效,执行自动登出'); // 停止定期检查 stopTokenValidityCheck(); // 清除本地Token和用户信息 webAuth.clearToken(); // 更新UI状态为游客模式 if (typeof updateUIState === 'function') { updateUIState(); } // 显示提示信息 if (typeof showToast === 'function') { showToast('您的账号已在其他设备登录,请重新登录', 'warning'); } // 延迟500ms后弹出登录框 setTimeout(() => { const loginModalElement = document.getElementById('loginModal'); if (loginModalElement) { const loginModal = new bootstrap.Modal(loginModalElement); loginModal.show(); } }, 500); // 刷新页面数据(以游客身份) if (typeof loadGoods === 'function') { setTimeout(() => { loadGoods(); }, 800); } } }, TOKEN_CHECK_INTERVAL); } /** * 停止Token有效性定期检查 */ function stopTokenValidityCheck() { if (tokenCheckTimer) { clearInterval(tokenCheckTimer); tokenCheckTimer = null; console.log('[Token检查] 已停止定期检查'); } } // 初始化页面 function initializePage() { console.debug('山海·精卫PFP监控系统初始化...'); // 加载分页配置 loadPaginationConfig(); // 智能同步后端间隔设置 syncBackendIntervals(); // 初始化同步信息显示 updateSyncInfo(); // 初始化排序图标 updateSortOrderIcon(); // 初始化搜索类型选项 updateSearchTypeOptions(); } // 更新同步信息显示(优化版:减少闪动) function updateSyncInfo() { const soldInterval = getSoldInterval(); const newText = `间隔: ${soldInterval}秒`; const element = $('#soldSyncInterval'); if (element.text() !== newText) { element.text(newText); } } // 智能同步后端间隔设置 function syncBackendIntervals() { $.get('/api/monitor/status') .done(function(response) { if (response.success && response.data) { const backendOnSaleInterval = response.data.on_sale_interval || 1; const backendSoldInterval = response.data.sold_interval || 5; // 建议前端间隔为后端间隔的2-3倍,确保能及时看到更新 const recommendedOnSaleInterval = Math.max(3, backendOnSaleInterval * 2); const recommendedSoldInterval = Math.max(5, backendSoldInterval * 2); // 如果当前设置的间隔过长,自动调整 const currentOnSaleInterval = getOnSaleInterval(); const currentSoldInterval = getSoldInterval(); if (currentOnSaleInterval > recommendedOnSaleInterval * 3) { $('#onSaleInterval').val(recommendedOnSaleInterval); console.log(`自动优化在售商品刷新间隔: ${currentOnSaleInterval}s -> ${recommendedOnSaleInterval}s`); } if (currentSoldInterval > recommendedSoldInterval * 3) { $('#soldInterval').val(recommendedSoldInterval); console.log(`自动优化已售商品刷新间隔: ${currentSoldInterval}s -> ${recommendedSoldInterval}s`); } } }) .fail(function() { console.warn('无法获取后端间隔设置'); }); } // 更新上次更新时间(优化版:减少闪动) function updateLastUpdateTime(type = 'both') { const now = new Date(); const timeString = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); const newText = `上次更新: ${timeString}`; if (type === 'both' || type === 'onSale') { const element = $('#onSaleLastUpdate'); if (element.text() !== newText) { element.text(newText); } } if (type === 'both' || type === 'sold') { const element = $('#soldLastUpdate'); if (element.text() !== newText) { element.text(newText); } } } // ==================== 详情轮询机制 ==================== /** * 检查是否需要启动详情轮询 * 当API返回的数据包含缺少详情的商品时,启动轮询 * @param {string} type - 'onSale' 或 'sold' * @param {object} response - API响应 */ function checkAndStartDetailPolling(type, response) { const missingDetails = response.data?.missing_details || 0; if (missingDetails > 0) { // 显示详情加载提示 showDetailLoadingTip(type, missingDetails); // 启动轮询(如果未启动) if (type === 'onSale' && !onSaleDetailPollingTimer) { console.log(`📦 在售商品有 ${missingDetails} 个缺少详情,${DETAIL_POLLING_INTERVAL/1000}秒后自动刷新`); onSaleDetailPollingTimer = setTimeout(() => { onSaleDetailPollingTimer = null; loadOnSaleGoods(currentOnSalePage); }, DETAIL_POLLING_INTERVAL); } else if (type === 'sold' && !soldDetailPollingTimer) { console.log(`📦 已售商品有 ${missingDetails} 个缺少详情,${DETAIL_POLLING_INTERVAL/1000}秒后自动刷新`); soldDetailPollingTimer = setTimeout(() => { soldDetailPollingTimer = null; loadSoldGoods(currentSoldPage, true); }, DETAIL_POLLING_INTERVAL); } } else { // 所有商品都有详情,停止轮询 stopDetailPolling(type); hideDetailLoadingTip(type); } } /** * 停止详情轮询 * @param {string} type - 'onSale' 或 'sold',不传则停止全部 */ function stopDetailPolling(type) { if (!type || type === 'onSale') { if (onSaleDetailPollingTimer) { clearTimeout(onSaleDetailPollingTimer); onSaleDetailPollingTimer = null; } } if (!type || type === 'sold') { if (soldDetailPollingTimer) { clearTimeout(soldDetailPollingTimer); soldDetailPollingTimer = null; } } } /** * 显示详情加载提示 * @param {string} type - 'onSale' 或 'sold' * @param {number} count - 缺少详情的商品数量 */ function showDetailLoadingTip(type, count) { const tipId = type === 'onSale' ? 'onSaleDetailTip' : 'soldDetailTip'; let tipElement = $(`#${tipId}`); if (tipElement.length === 0) { // 创建提示元素 const containerSelector = type === 'onSale' ? '#onSaleList' : '#soldList'; const tip = $(`
正在加载 ${count} 个商品的详情...
`); $(containerSelector).before(tip); tipElement = tip; } else { tipElement.find('.tip-text').text(`正在加载 ${count} 个商品的详情...`); } tipElement.show(); } /** * 隐藏详情加载提示 * @param {string} type - 'onSale' 或 'sold' */ function hideDetailLoadingTip(type) { const tipId = type === 'onSale' ? 'onSaleDetailTip' : 'soldDetailTip'; $(`#${tipId}`).hide(); } // ==================== 同步状态管理 ==================== // 更新同步状态显示(优化版:区分正常过程和真正异常,减少闪动) function updateSyncStatus(type, status, message = '') { const statusElement = type === 'onSale' ? $('#onSaleSyncStatus') : $('#soldSyncStatus'); // 获取当前状态 const currentText = statusElement.text().trim(); const currentClasses = statusElement.attr('class') || ''; // 优化状态逻辑:只有真正的错误才显示异常 let newText, newClass; if (status === 'success') { newText = '同步正常'; newClass = 'bg-success'; } else if (status === 'loading') { // loading状态保持为正常状态,只是显示为正常同步过程 newText = '同步正常'; newClass = 'bg-success'; } else if (status === 'error') { // 只有网络失败、接口报错等严重问题才显示异常 newText = '同步异常'; newClass = 'bg-warning'; } else { // warning 等其他状态也归为异常 newText = '同步异常'; newClass = 'bg-warning'; } // 只在状态真正改变时才更新DOM if (currentText !== newText || !currentClasses.includes(newClass)) { statusElement.removeClass('bg-success bg-warning bg-danger bg-secondary bg-info'); statusElement.addClass(newClass).text(newText); } } // 设置事件监听器 function setupEventListeners() { // 自动刷新复选框 $('#autoRefresh').change(function() { if (this.checked) { startAutoRefresh(); } else { stopAutoRefresh(); } }); // 在售商品刷新间隔设置 $('#onSaleInterval').on('input change', function() { let newInterval = parseInt(this.value); // 获取用户等级的最小间隔限制 const minInterval = currentPermissions ? (currentPermissions.on_sale_refresh_interval || 1) : 1; // 验证输入值(不能小于用户等级允许的最小间隔) if (isNaN(newInterval) || newInterval < minInterval) { newInterval = minInterval; this.value = minInterval; // 提示用户最小值限制 if (currentUserLevel !== 'guest') { console.log(`当前等级(${currentUserLevel})的在售商品刷新间隔最小值为 ${minInterval} 秒`); } } // 更新同步间隔显示 updateSyncInfo(); if ($('#autoRefresh').is(':checked')) { // 如果自动刷新正在运行,重新启动以应用新的间隔 stopAutoRefresh(); startAutoRefresh(); console.log(`在售商品刷新间隔已更改为 ${newInterval} 秒`); } }); // 已售商品刷新间隔设置 $('#soldInterval').on('input change', function() { let newInterval = parseInt(this.value); // 获取用户等级的最小间隔限制 const minInterval = currentPermissions ? (currentPermissions.sold_refresh_interval || 1) : 1; // 验证输入值(不能小于用户等级允许的最小间隔) if (isNaN(newInterval) || newInterval < minInterval) { newInterval = minInterval; this.value = minInterval; // 提示用户最小值限制 if (currentUserLevel !== 'guest') { console.log(`当前等级(${currentUserLevel})的已售商品刷新间隔最小值为 ${minInterval} 秒`); } } // 更新同步间隔显示 updateSyncInfo(); if ($('#autoRefresh').is(':checked')) { // 如果自动刷新正在运行,重新启动以应用新的间隔 stopAutoRefresh(); startAutoRefresh(); console.log(`已售商品刷新间隔已更改为 ${newInterval} 秒`); } }); // 立即刷新按钮 $('#refreshBtn').click(function() { // 停止自动刷新并取消勾选 if ($('#autoRefresh').is(':checked')) { $('#autoRefresh').prop('checked', false); $('#mobileAutoRefresh').prop('checked', false); stopAutoRefresh(); } refreshAllData(); }); // 成交分析按钮 $('#analysisBtn').click(function() { showAnalysisModal(); }); // 刷新分析数据按钮 $('#refreshAnalysisBtn').click(function() { const timeRange = $('input[name="timeRange"]:checked').val() || '30d'; loadAnalysisData(timeRange); }); // 时间周期选择器 $(document).on('change', 'input[name="timeRange"]', function() { const timeRange = $(this).val(); loadAnalysisData(timeRange); }); // 设置按钮(已移除) // $('#settingsBtn').click(function() { // loadSettingsModal(); // }); // 保存设置按钮(已移除) // $('#saveSettingsBtn').click(function() { // saveSettings(); // }); // 设置模态框中的间隔输入验证 $('#settingsOnSaleInterval').on('input', function() { let value = parseInt(this.value); if (isNaN(value) || value < 1) { this.value = 1; } }); $('#settingsSoldInterval').on('input', function() { let value = parseInt(this.value); if (isNaN(value) || value < 1) { this.value = 1; } }); // 商品项点击事件(事件委托) $(document).on('click', '.goods-item', function() { const goodsData = $(this).data('goods'); if (goodsData) { showGoodsModal(goodsData); } }); // 搜索相关事件 $('#searchBtn').click(function() { performSearch(); }); $('#clearSearchBtn').click(function() { clearSearch(); }); // 搜索框回车键搜索 $('#searchInput').keypress(function(e) { if (e.which === 13) { // Enter键 performSearch(); } }); // 状态选择改变时自动搜索(如果有关键词) $('#searchStatus').change(function() { if ($('#searchInput').val().trim()) { performSearch(); } }); // 排序字段改变时自动搜索(如果有关键词) $('#searchSortBy').change(function() { currentSortBy = $(this).val(); if ($('#searchInput').val().trim()) { performSearch(); } }); // 排序方向切换 $('#sortOrderBtn').click(function() { currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc'; updateSortOrderIcon(); if ($('#searchInput').val().trim()) { performSearch(); } }); // 设置模态框标签页切换事件 setupSettingsTabSwitching(); // 移动端功能 setupMobileEventListeners(); } // 设置模态框标签页切换事件监听 function setupSettingsTabSwitching() { // 监听主要标签页切换 - 使用Bootstrap原生事件 $('#settingsTabs button[data-bs-toggle="tab"]').on('show.bs.tab', function (e) { const targetId = $(e.target).attr('data-bs-target'); console.log('准备切换到标签页:', targetId); // 手动隐藏所有其他标签页 $('#settingsTabContent .tab-pane').each(function() { if (!$(this).is(targetId)) { $(this).removeClass('show active'); } }); }); $('#settingsTabs button[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) { const targetId = $(e.target).attr('data-bs-target'); const previousId = $(e.relatedTarget) ? $(e.relatedTarget).attr('data-bs-target') : null; console.log('标签页切换完成:', previousId, '->', targetId); // 确保目标标签页显示,其他隐藏 $('#settingsTabContent .tab-pane').removeClass('show active'); $(targetId).addClass('show active'); // 根据标签页执行特殊处理 switch(targetId) { case '#general': console.log('显示基础设置'); // 显示底部的取消和保存按钮(基础设置需要保存) $('#cancelSettingsBtn').show(); $('#saveSettingsBtn').show(); break; case '#traits': console.log('显示特征管理'); // 隐藏底部的取消和保存按钮(特征显示只读,不需要保存) $('#cancelSettingsBtn').hide(); $('#saveSettingsBtn').hide(); handleTraitsTabActivation(); break; } }); // 监听特征管理子标签页切换 $('#traitsSubTabs button[data-bs-toggle="pill"]').on('shown.bs.tab', function (e) { const targetId = $(e.target).attr('data-bs-target'); console.log('特征管理子标签页切换到:', targetId); }); // 设置模态框打开时重置状态 $('#settingsModal').on('show.bs.modal', function() { console.log('设置模态框即将打开'); // 重置所有标签页状态 setTimeout(function() { // 确保基础设置标签页被激活 $('#settingsTabs .nav-link').removeClass('active'); $('#general-tab').addClass('active'); // 确保只有基础设置内容显示 $('#settingsTabContent .tab-pane').removeClass('show active'); $('#general').addClass('show active'); // 显示底部按钮(默认显示基础设置) $('#cancelSettingsBtn').show(); $('#saveSettingsBtn').show(); console.log('标签页状态已重置到基础设置'); }, 50); }); } // 处理特征管理标签页激活 function handleTraitsTabActivation() { // 检查是否已经验证过密码 if (!$('#traitsPasswordPanel').is(':visible')) { // 已验证过密码,确保上传功能已设置 setTimeout(setupTraitsUpload, 100); return; } // 如果密码面板可见,说明需要验证密码 $('#traitsAdminPassword').focus(); } // 加载初始数据 function loadInitialData() { console.log('开始加载初始数据...'); showLoading(true); // 创建一个20秒的超时Promise const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('初始数据加载超时 (20秒)')), 20000); }); // 包装每个数据加载函数,添加5秒超时 function withTimeout(promise, name, timeoutMs = 5000) { const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error(`${name} 加载超时 (${timeoutMs/1000}秒)`)), timeoutMs); }); return Promise.race([promise, timeout]).catch((e) => { console.error(`${name} 加载失败:`, e.message); return null; }); } // 使用Promise.race确保整体超时控制 const dataLoadingPromise = Promise.all([ withTimeout(loadStatistics(), '统计数据'), withTimeout(loadOnSaleGoods(1), '在售商品'), withTimeout(loadSoldGoods(1), '已售商品'), withTimeout(loadMarketData(), '行情数据'), withTimeout(checkMonitorStatus(), '监控状态') ]).then(() => { console.log('所有数据加载完成'); return true; }).catch((e) => { console.error('数据加载失败:', e); showError('数据加载失败,请刷新页面重试'); return false; }); // 使用超时控制 Promise.race([dataLoadingPromise, timeoutPromise]) .then((result) => { if (result === true) { console.log('数据加载成功完成'); } }) .catch((e) => { console.error('加载超时或失败:', e.message); showError('数据加载超时,请检查网络连接并刷新页面'); }) .finally(() => { console.log('隐藏加载界面'); showLoading(false); }); } // 显示/隐藏加载状态 function showLoading(show) { console.debug('showLoading called:', show); const overlay = document.getElementById('loadingOverlay'); if (!overlay) { console.error('loadingOverlay元素未找到!'); return; } if (show) { overlay.style.display = 'block'; // 添加10秒强制超时,防止永久卡住 setTimeout(() => { if (overlay.style.display === 'block') { console.warn('强制超时:隐藏loading overlay'); showError('加载超时,如果页面未正常显示,请刷新页面'); overlay.style.display = 'none'; } }, 10000); } else { overlay.style.display = 'none'; } } // ========== Phase 2优化:启用API缓存 ========== // 加载统计信息 function loadStatistics() { return $.ajax({ url: '/api/stats', method: 'GET', timeout: 10000, // 10秒超时 cache: true, // ✓ 启用浏览器缓存(配合后端Cache-Control: 30秒) headers: { 'Cache-Control': 'max-age=30' // 客户端建议缓存时间 } }).done(function(response) { if (response.success) { updateStatistics(response.data); } else { console.error('统计信息API错误:', response.error); showError('加载统计信息失败: ' + response.error); } }).fail(function(xhr, status, error) { console.error('统计信息请求失败:', status, error); if (status === 'timeout') { showError('加载统计信息超时,请稍后重试'); } else { showError('网络连接失败,请稍后重试'); } }); } // 更新统计信息 function updateStatistics(stats) { // ========== 优化:智能刷新 - 数据未变化时跳过更新 ========== const currentHash = hashData(stats); if (statisticsDataHash === currentHash) { console.log('⏭️ 统计数据未变化,跳过更新'); return; } statisticsDataHash = currentHash; console.log('✅ 统计数据已更新'); const onSaleStats = stats.on_sale || {}; const soldStats = stats.sold || {}; $('#onSaleCount').text(onSaleStats.count || 0); $('#soldCount').text(soldStats.count || 0); $('#avgPrice').text('¥' + (onSaleStats.avg_price ? Math.round(onSaleStats.avg_price) : 0)); $('#todayNew').text(stats.today_new || 0); // 更新最后更新时间 if (stats.last_update_time) { $('#lastUpdateTime').html('最后更新: ' + stats.last_update_time); } } // 加载在售商品 function loadOnSaleGoods(page) { // 翻页时清空哈希,确保获取完整数据 if (page !== currentOnSalePage) { onSaleLastHash = null; } currentOnSalePage = page; // 更新同步状态为加载中 updateSyncStatus('onSale', 'loading', '正在加载在售数据...'); return $.ajax({ url: '/api/v1/goods/list', method: 'GET', data: { page: page, limit: pageSize, status: 1, sort_by: 'publish_time', sort_order: 'desc', fetch_details: true, incremental: true, // 启用增量更新 last_hash: onSaleLastHash // 上次哈希值 }, timeout: 10000, // 10秒超时 cache: false // 禁用浏览器缓存,始终从服务器获取 }) .done(function(response) { if (response.success) { const data = response.data; // ========== 增量更新:检查服务端数据是否变化 ========== if (data.changed === false) { // 数据未变化,跳过更新 console.debug('⏭️ 在售商品数据未变化(服务端确认),跳过更新'); updateSyncStatus('onSale', 'success', '数据无变化'); // 始终更新分页(即使数据未变化) updatePagination('onSale', data); return; } // 数据已变化,更新哈希值 onSaleLastHash = data.hash; console.debug(`✅ 在售商品数据已更新`); // 处理用户权限配置 if (data.permissions) { applyUserPermissions(data.permissions, data.user_level); } // ========== 优化:display函数内部已集成数据哈希检测 ========== // 数据未变化时,displayOnSaleGoods会提前返回,下面的代码不会执行 const dataChanged = displayOnSaleGoods(data); // 始终更新分页(即使数据未变化,也要确保分页显示) updatePagination('onSale', data); // 仅在数据变化时执行以下操作 if (dataChanged !== false) { // 更新最后更新时间和同步状态 updateLastUpdateTime('onSale'); updateSyncStatus('onSale', 'success', '在售数据同步正常'); // 检查是否需要轮询获取详情 checkAndStartDetailPolling('onSale', response); // 同步到移动端(仅在数据变化时) syncMobileDesktopData(); syncMobileOnSaleList(); } else { // 数据未变化,仅更新同步状态 updateSyncStatus('onSale', 'success', '在售数据同步正常'); } } else { showError('加载在售商品失败: ' + response.error); updateSyncStatus('onSale', 'error', '加载失败: ' + response.error); } }) .fail(function() { // 失败时清空哈希,下次全量刷新 onSaleLastHash = null; showError('网络连接失败,请稍后重试'); updateSyncStatus('onSale', 'error', '网络连接失败'); }); } // 加载已售商品 function loadSoldGoods(page, isAutoRefresh = false) { // 翻页时清空哈希,确保获取完整数据 if (!isAutoRefresh && page !== currentSoldPage) { soldLastHash = null; } // 只在手动翻页时更新当前页码 if (!isAutoRefresh) { currentSoldPage = page; } // 更新同步状态为加载中 const loadingMessage = isAutoRefresh ? '正在刷新最新数据...' : '正在加载已售数据...'; updateSyncStatus('sold', 'loading', loadingMessage); return $.ajax({ url: '/api/v1/goods/list', method: 'GET', data: { page: page, limit: pageSize, status: 2, sort_by: 'sell_time', sort_order: 'desc', fetch_details: true, incremental: true, // 启用增量更新 last_hash: soldLastHash // 上次哈希值 }, timeout: 10000, // 10秒超时 cache: false // 禁用浏览器缓存,始终从服务器获取 }) .done(function(response) { if (response.success) { const data = response.data; // ========== 增量更新:检查服务端数据是否变化 ========== if (data.changed === false) { // 数据未变化,跳过更新 console.debug('⏭️ 已售商品数据未变化(服务端确认),跳过更新'); updateSyncStatus('sold', 'success', '数据无变化'); // 始终更新分页(即使数据未变化) updatePagination('sold', data); return; } // 数据已变化,更新哈希值 soldLastHash = data.hash; console.debug(`✅ 已售商品数据已更新`); // 处理用户权限配置 if (data.permissions) { applyUserPermissions(data.permissions, data.user_level); } // ========== 优化:display函数内部已集成数据哈希检测 ========== // 数据未变化时,displaySoldGoods会返回false const dataChanged = displaySoldGoods(data); // 始终更新分页(即使数据未变化,也要确保分页显示) updatePagination('sold', data); // 仅在数据变化时执行以下操作 if (dataChanged !== false) { // 更新最后更新时间和同步状态 updateLastUpdateTime('sold'); updateSyncStatus('sold', 'success', '已售数据同步正常'); // 检查是否需要轮询获取详情 checkAndStartDetailPolling('sold', response); // 在控制台显示数据更新信息 const refreshType = isAutoRefresh ? '自动刷新' : '手动加载'; console.log(`✅ 已售商品列表已更新 (${refreshType}) - 第${page}页, ${data.goods?.length || 0}个商品, 总共${data.total || 0}个`); // 同步到移动端(仅在数据变化时) syncMobileDesktopData(); syncMobileSoldList(); } else { // 数据未变化,仅更新同步状态 updateSyncStatus('sold', 'success', '已售数据同步正常'); } } else { showError('加载已售商品失败: ' + response.error); updateSyncStatus('sold', 'error', '加载失败: ' + response.error); } }) .fail(function() { // 失败时清空哈希,下次全量刷新 soldLastHash = null; showError('网络连接失败,请稍后重试'); updateSyncStatus('sold', 'error', '网络连接失败'); }); } // 显示在售商品列表(优化版:差异更新,避免图片闪烁) // 返回值:true表示数据已更新,false表示数据未变化 function displayOnSaleGoods(data) { const goods = data.goods || []; const container = $('#onSaleList'); // ========== 优化:智能刷新 - 数据未变化时跳过更新 ========== const currentHash = hashData(goods); if (onSaleDataHash === currentHash) { console.debug('⏭️ 在售商品数据未变化,跳过更新'); return false; // 返回false表示数据未变化 } onSaleDataHash = currentHash; console.debug('✅ 在售商品数据已更新'); $('#onSaleTotal').text('总计: ' + (data.total || 0)); if (goods.length === 0) { container.html('

暂无在售商品

'); cachedOnSaleGoods.clear(); return; } // 获取新数据的ID集合 const newGoodsMap = new Map(); goods.forEach(item => { const id = item.goods_id || item.id; newGoodsMap.set(id, item); }); // 检查是否需要完全重建(页码变化或首次加载) const existingItems = container.find('.goods-item'); const needFullRebuild = existingItems.length === 0 || cachedOnSaleGoods.size === 0 || goods.length !== existingItems.length; if (needFullRebuild) { // 保存现有的溢价率值 const existingPremiumRates = {}; $('#onSaleList .premium-rate').each(function() { const $this = $(this); const priceText = $this.closest('.goods-price').text(); const priceMatch = priceText.match(/¥(\d+(?:\.\d+)?)/); if (priceMatch) { const price = parseFloat(priceMatch[1]); existingPremiumRates[price] = { text: $this.text().trim(), classes: $this.attr('class') }; } }); let html = ''; goods.forEach(function(item) { html += createGoodsItemHtml(item, 'on-sale'); }); container.html(html).addClass('fade-in'); // 恢复已有的溢价率值 $('#onSaleList .premium-rate').each(function() { const $this = $(this); const priceText = $this.closest('.goods-price').text(); const priceMatch = priceText.match(/¥(\d+(?:\.\d+)?)/); if (priceMatch) { const price = parseFloat(priceMatch[1]); const existing = existingPremiumRates[price]; if (existing && existing.text !== '--') { $this.text(existing.text); $this.attr('class', existing.classes); } } }); } else { // 差异更新:只更新变化的商品 let hasChanges = false; existingItems.each(function(index) { const $item = $(this); const oldData = $item.data('goods'); const oldId = oldData ? (oldData.goods_id || oldData.id) : null; const newItem = goods[index]; const newId = newItem ? (newItem.goods_id || newItem.id) : null; // 如果ID不同或数据变化,更新该项 if (oldId !== newId || (newItem && hasGoodsChanged(oldData, newItem))) { const newHtml = createGoodsItemHtml(newItem, 'on-sale'); $item.replaceWith(newHtml); hasChanges = true; } }); if (hasChanges) { console.log('差异更新:部分商品已更新'); } } // 更新缓存 cachedOnSaleGoods.clear(); goods.forEach(item => { const id = item.goods_id || item.id; cachedOnSaleGoods.set(id, item); }); // 仅对在售商品计算溢价率 if (data.goods && data.goods.length > 0) { // console.debug(`准备计算溢价率,商品数量: ${data.goods.length}`); setTimeout(() => { calculateAndDisplayPremiumRates(data.goods); }, 100); } // ========== Phase 2优化:触发图片懒加载 ========== triggerLazyLoad(); // 更新分页导航 updatePagination('onSale', data); return true; // 返回true表示数据已更新 } // 检查商品数据是否变化 function hasGoodsChanged(oldData, newData) { if (!oldData || !newData) return true; return oldData.price !== newData.price || oldData.sell_status !== newData.sell_status || oldData.seller_name !== newData.seller_name; } // 显示已售商品列表(优化版:差异更新) // 返回值:true表示数据已更新,false表示数据未变化 function displaySoldGoods(data) { const goods = data.goods || []; const container = $('#soldList'); // ========== 优化:智能刷新 - 数据未变化时跳过更新 ========== const currentHash = hashData(goods); if (soldDataHash === currentHash) { console.debug('⏭️ 已售商品数据未变化,跳过更新'); return false; // 返回false表示数据未变化 } soldDataHash = currentHash; console.debug('✅ 已售商品数据已更新'); $('#soldTotal').text('总计: ' + (data.total || 0)); if (goods.length === 0) { container.html('

暂无已售商品

'); cachedSoldGoods.clear(); return; } // 检查是否需要完全重建 const existingItems = container.find('.goods-item'); const needFullRebuild = existingItems.length === 0 || cachedSoldGoods.size === 0 || goods.length !== existingItems.length; if (needFullRebuild) { let html = ''; goods.forEach(function(item) { html += createGoodsItemHtml(item, 'sold'); }); container.html(html).addClass('fade-in'); } else { // 差异更新:只更新变化的商品 let hasChanges = false; existingItems.each(function(index) { const $item = $(this); const oldData = $item.data('goods'); const oldId = oldData ? (oldData.goods_id || oldData.id) : null; const newItem = goods[index]; const newId = newItem ? (newItem.goods_id || newItem.id) : null; if (oldId !== newId || (newItem && hasGoodsChanged(oldData, newItem))) { const newHtml = createGoodsItemHtml(newItem, 'sold'); $item.replaceWith(newHtml); hasChanges = true; } }); if (hasChanges) { console.log('已售列表差异更新:部分商品已更新'); } } // 更新缓存 cachedSoldGoods.clear(); goods.forEach(item => { const id = item.goods_id || item.id; cachedSoldGoods.set(id, item); }); // ========== Phase 2优化:触发图片懒加载 ========== triggerLazyLoad(); // 更新分页导航 updatePagination('sold', data); return true; // 返回true表示数据已更新 } // 创建商品项HTML function createGoodsItemHtml(item, type) { const priceClass = type === 'sold' ? 'sold' : ''; const statusBadge = type === 'sold' ? '已售' : (item.is_presale ? '预售' : '在售'); // 特征和OCR状态指示器 let indicators = ''; if (item.traits_info) { try { let traits = typeof item.traits_info === 'string' ? JSON.parse(item.traits_info) : item.traits_info; if (traits && Object.keys(traits).length > 0) { indicators += ' '; } } catch (e) {} } if (item.ocr_status === 2) { indicators += ' '; } else if (item.ocr_status === 1) { indicators += ' '; } const publishTime = type === 'sold' ? (item.sell_time ? '售出时间: ' + item.sell_time : '售出时间: 未知') : (item.publish_time ? '上架时间: ' + item.publish_time : '上架时间: 未知'); const description = item.description || item.introduce || ''; // 处理商品编号和设定集标记 let collectionNumber = ''; let shedingJiBadge = ''; let nameClass = ''; let pfpNumber = item.pfp_number; let isInferredNumber = false; // 如果没有pfp_number,尝试从collection_number提取 if (!pfpNumber && item.collection_number) { const extracted = extractPfpNumberFromString(item.collection_number); if (extracted) { pfpNumber = extracted; } } // 如果没有pfp_number但有traits_info,尝试反推编号 if (!pfpNumber && item.traits_info && pfpFeatureModal && pfpFeatureModal.allPFPData) { try { let traitsObj = item.traits_info; if (typeof traitsObj === 'string') { traitsObj = JSON.parse(traitsObj); } const matchResult = pfpFeatureModal.matchPfpNumberByTraits(traitsObj); if (matchResult && matchResult.pfpNumber) { pfpNumber = matchResult.pfpNumber; isInferredNumber = true; item._inferredPfpNumber = true; item._matchResult = matchResult; } } catch (e) { console.warn('特征反推编号失败:', e); } } if (pfpNumber) { // 验证编号在有效范围内 [2, 10000] if (pfpNumber >= 2 && pfpNumber <= 10000) { // 检查是否为设定集 const isShedingJi = pfpFeatureModal && pfpFeatureModal.isShedingJi(pfpNumber); if (isShedingJi) { nameClass = 'text-danger fw-bold'; shedingJiBadge = '设定集'; } // 创建可点击的编号链接 const ocrResultParam = item.ocr_result ? JSON.stringify(item.ocr_result) : 'null'; const inferredBadge = isInferredNumber ? '推测' : ''; collectionNumber = ` #${pfpNumber} ${shedingJiBadge} ${inferredBadge} `; } } else if (item.collection_number) { collectionNumber = `#${item.collection_number}`; } const goodsName = item.name || '未知商品'; // 生成deal_link(如果数据库中没有,则使用模板生成) const goodsId = item.goods_id || item.id; const dealLink = item.deal_link && item.deal_link.trim() !== '' ? item.deal_link : `https://xmeta.x-metash.cn/prod/xmeta_mall/index.html#/pages/goodsDetail/index?id=${goodsId}`; // 存储生成的deal_link到item对象,供模态框使用 item.deal_link = dealLink; // 检查是否有多张图片,准备显示最多2张 let allImages = []; let imageCount = 1; let imageIndicator = ''; let imagesToShow = [item.image_url]; // 默认只有主图 if (item.all_images) { try { // 支持数组或字符串格式 if (Array.isArray(item.all_images)) { allImages = item.all_images; } else if (typeof item.all_images === 'string') { allImages = JSON.parse(item.all_images); } if (allImages && Array.isArray(allImages) && allImages.length > 0) { imageCount = allImages.length; imagesToShow = allImages.slice(0, 2); // 最多显示2张图 if (imageCount > 2) { imageIndicator = `${imageCount}图`; } } } catch (e) { // JSON解析失败,使用主图 imagesToShow = [item.image_url]; } } // ========== Phase 2优化:图片懒加载 + 省流缓存 ========== // 生成图片HTML let imagesHtml = ''; imagesToShow.forEach((imageUrl, index) => { const imageClass = imagesToShow.length === 1 ? 'goods-image single-image lazyload' : 'goods-image dual-image lazyload'; // 如果图片已在缓存中,直接使用src;否则使用data-src懒加载 const isCached = imageCache.has(imageUrl); imagesHtml += ` ${goodsName} `; }); return `
${imagesHtml} ${imageIndicator}
${goodsName}
${indicators} ${statusBadge} ${type === 'sold' ? ` ` : (currentUserLevel === 'vip' || currentUserLevel === 'pro' ? ` 购买 ` : ` 查看 `) }
¥${item.price} ${type !== 'sold' ? `--` : ''}
卖家: ${item.seller_name || '未知'} | ${publishTime}
${description ? `
${description}
` : ''}
`; } // 更新分页导航 function updatePagination(type, data) { const paginationId = type === 'onSale' ? 'onSalePagination' : 'soldPagination'; const currentPage = data.page || 1; const limit = data.limit || 20; const total = data.total || 0; // 优先使用后端返回的pages,如果没有则自己计算 const totalPages = data.pages || Math.ceil(total / limit) || 1; const hasPrev = data.has_prev !== undefined ? data.has_prev : (currentPage > 1); const hasNext = data.has_next !== undefined ? data.has_next : (currentPage < totalPages); console.log(`📄 分页: type=${type}, page=${currentPage}/${totalPages}, total=${total}, limit=${limit}`); if (totalPages <= 1) { console.log(`📄 totalPages <= 1, 清空分页`); $('#' + paginationId).html(''); return; } let html = ''; // 上一页 if (hasPrev) { html += `
  • 上一页
  • `; } // 页码 const startPage = Math.max(1, currentPage - 2); const endPage = Math.min(totalPages, currentPage + 2); if (startPage > 1) { html += '
  • 1
  • '; if (startPage > 2) { html += '
  • ...
  • '; } } for (let i = startPage; i <= endPage; i++) { const activeClass = i === currentPage ? ' active' : ''; html += `
  • ${i}
  • `; } if (endPage < totalPages) { if (endPage < totalPages - 1) { html += '
  • ...
  • '; } html += `
  • ${totalPages}
  • `; } // 下一页 if (hasNext) { html += `
  • 下一页
  • `; } $('#' + paginationId).html(html); } // 显示商品详情模态框 function showGoodsModal(goods) { const modal = $('#goodsModal'); const modalTitle = $('#goodsModalTitle'); const modalBody = $('#goodsModalBody'); const modalLink = $('#goodsModalLink'); const modalViewLink = $('#goodsModalViewLink'); modalTitle.text(goods.name); // 设置"查看"按钮链接(始终显示原始商品详情链接) modalViewLink.attr('href', goods.deal_link); // 根据商品状态设置主按钮文字和链接 const isOnSale = goods.sell_status !== 2; // 不是已售状态就是在售状态 if (isOnSale) { // 在售商品:根据用户权限显示不同按钮 if (currentUserLevel === 'vip' || currentUserLevel === 'pro') { // VIP和PRO用户:显示"购买"按钮,使用购买链接 const purchaseLink = `https://xmeta.x-metash.cn/prod/xmeta_mall/index.html#/pages/confirmOrder/index?goodsId=${goods.goods_id}&platformId=${goods.platform_id}&amount=${goods.price}`; modalLink.attr('href', purchaseLink); modalLink.html('购买'); modalLink.removeClass('btn-outline-primary').addClass('btn-primary'); } else { // 其他用户:显示"查看"按钮,使用原始链接 modalLink.attr('href', goods.deal_link); modalLink.html('查看'); modalLink.removeClass('btn-primary').addClass('btn-outline-primary'); } } else { // 已售商品:显示"查看原链接"按钮,使用原始链接 modalLink.attr('href', goods.deal_link); modalLink.html('查看原链接'); modalLink.removeClass('btn-primary').addClass('btn-outline-primary'); } const statusText = goods.sell_status === 2 ? '已售' : (goods.is_presale ? '预售' : '在售'); const statusClass = goods.sell_status === 2 ? 'success' : (goods.is_presale ? 'warning' : 'primary'); // 处理多张图片 let imagesHtml = ''; if (goods.all_images) { try { // 支持数组或字符串格式 let allImages; if (Array.isArray(goods.all_images)) { allImages = goods.all_images; } else if (typeof goods.all_images === 'string') { allImages = JSON.parse(goods.all_images); } if (allImages && allImages.length > 0) { imagesHtml = '
    '; allImages.forEach((imageUrl, index) => { imagesHtml += `
    ${goods.name} - 图${index + 1}
    `; }); imagesHtml += '
    '; } else { imagesHtml = `${goods.name}`; } } catch (e) { imagesHtml = `${goods.name}`; } } else { imagesHtml = `${goods.name}`; } // 处理特征信息显示 let traitsHtml = ''; // 提取PFP编号 let modalPfpNumber = goods.pfp_number; if (!modalPfpNumber && goods.collection_number) { modalPfpNumber = extractPfpNumberFromString(goods.collection_number); } // 检查是否为设定集 const modalIsShedingJi = modalPfpNumber && pfpFeatureModal && pfpFeatureModal.isShedingJi(modalPfpNumber); if (goods.traits_info) { try { let traits = goods.traits_info; if (typeof traits === 'string') { traits = JSON.parse(traits); } if (traits && Object.keys(traits).length > 0) { traitsHtml = '
    特征属性
    '; for (const [category, value] of Object.entries(traits)) { traitsHtml += `
    ${category} ${value}
    `; } traitsHtml += '
    '; } } catch (e) { console.error('解析特征信息失败:', e); } } // 如果没有traits_info但有有效编号,尝试从官方数据获取特征 if (!traitsHtml && modalPfpNumber && modalPfpNumber >= 2 && modalPfpNumber <= 10000) { if (pfpFeatureModal && pfpFeatureModal.allPFPData) { const pfpData = pfpFeatureModal.allPFPData.data || pfpFeatureModal.allPFPData; const officialData = pfpData[modalPfpNumber]; if (officialData && officialData.features) { const features = pfpFeatureModal.formatFeatures(officialData.features); if (features && features.length > 0) { traitsHtml = '
    特征属性 官方数据
    '; for (const feature of features) { traitsHtml += `
    ${feature.category} ${feature.name} ${feature.rarity ? `${feature.rarity}` : ''}
    `; } traitsHtml += '
    '; } } } } // 官网不显示OCR识别结果,只显示最终的特征属性 // OCR识别结果仅在后台管理系统中显示 let ocrHtml = ''; // 生成编号显示HTML let collectionNumberHtml = goods.collection_number || '无'; if (modalPfpNumber && modalPfpNumber >= 2 && modalPfpNumber <= 10000) { const shedingJiBadge = modalIsShedingJi ? '设定集' : ''; collectionNumberHtml = ` #${modalPfpNumber} ${shedingJiBadge} `; } /* 已移除OCR识别结果显示 - 官网只显示特征属性 if (goods.ocr_status === 2 && goods.ocr_result) { try { let ocrData = goods.ocr_result; if (typeof ocrData === 'string') { ocrData = JSON.parse(ocrData); } if (ocrData.text) { ocrHtml = `
    OCR识别结果
    ${ocrData.text} ${ocrData.processed_at ? `
    识别时间: ${ocrData.processed_at}
    ` : ''}
    `; } } catch (e) { console.error('解析OCR结果失败:', e); } } else if (goods.ocr_status === 1) { ocrHtml = `
    OCR识别
    正在识别中...
    `; } else if (goods.ocr_status === 3) { ocrHtml = `
    OCR识别
    识别失败
    `; } */ modalBody.html(`
    ${imagesHtml}
    商品信息
    ${goods.sell_status === 2 ? `` : ''}
    名称:${goods.name}
    价格:¥${goods.price}
    状态:${statusText}
    卖家:${goods.seller_name || '未知'}
    编号:${collectionNumberHtml}
    平台:${goods.platform_name || '未知'}
    上架时间:${goods.publish_time || '未知'}
    售出时间:${goods.sell_time || '未知'}
    ${traitsHtml} ${ocrHtml} ${goods.description || goods.introduce ? `
    商品描述

    ${goods.description || goods.introduce}

    ` : ''}
    `); modal.modal('show'); } // 显示图片预览 function showImagePreview(imageUrl, title) { const previewModal = ` `; // 移除已存在的预览模态框 $('#imagePreviewModal').remove(); // 添加新的预览模态框 $('body').append(previewModal); // 显示预览模态框 $('#imagePreviewModal').modal('show'); // 在模态框关闭后移除DOM元素 $('#imagePreviewModal').on('hidden.bs.modal', function () { $(this).remove(); }); } // 获取在售商品刷新间隔(秒,最少1秒) function getOnSaleInterval() { const intervalInput = $('#onSaleInterval'); let interval = parseInt(intervalInput.val()) || 3; // 确保间隔至少为1秒 if (interval < 1) { interval = 1; intervalInput.val(1); } return interval; } // 获取已售商品刷新间隔(秒,最少1秒) function getSoldInterval() { const intervalInput = $('#soldInterval'); let interval = parseInt(intervalInput.val()) || 30; // 确保间隔至少为1秒 if (interval < 1) { interval = 1; intervalInput.val(1); } return interval; } // 获取刷新间隔(秒转毫秒,最少1秒) - 保持向后兼容 function getRefreshInterval() { // 为了向后兼容,返回在售商品的间隔 return getOnSaleInterval() * 1000; // 转换为毫秒 } // 开始自动刷新 function startAutoRefresh() { // 先停止所有现有的刷新(不显示消息,避免alert重复移除错误) stopAutoRefresh(false); const onSaleInterval = getOnSaleInterval() * 1000; // 转换为毫秒 const soldInterval = getSoldInterval() * 1000; // 转换为毫秒 console.debug(`启动自动刷新 - 在售商品:${onSaleInterval/1000}秒, 已售商品:${soldInterval/1000}秒, 统计数据:60秒`); // 启动在售商品自动刷新 startOnSaleAutoRefresh(onSaleInterval); // 启动已售商品自动刷新 startSoldAutoRefresh(soldInterval); // ========== 优化:启动统计数据定期刷新 ========== startStatisticsRefresh(); // 保持兼容性设置 autoRefreshInterval = onSaleRefreshInterval; updateMonitorStatus(`监控状态: 自动刷新中 (在售:${onSaleInterval/1000}s, 已售:${soldInterval/1000}s)`, 'success'); showMessage(`自动刷新已启动 - 在售:${onSaleInterval/1000}秒, 已售:${soldInterval/1000}秒`, 'success'); } // 启动在售商品自动刷新(仅从数据库读取数据) function startOnSaleAutoRefresh(interval) { if (onSaleRefreshInterval) { clearInterval(onSaleRefreshInterval); } onSaleRefreshInterval = setInterval(function() { try { // 检查开关状态 if (!$('#autoRefresh').prop('checked')) { console.log('自动刷新开关已关闭,停止在售商品刷新'); stopOnSaleAutoRefresh(); return; } // 检查页面可见性 if (document.hidden) { console.log('页面隐藏,跳过在售商品刷新'); return; } // 防重叠检查 if (onSaleRefreshInProgress || manualRefreshInProgress) { console.log('在售商品刷新仍在进行中或手动刷新中,跳过本次刷新'); return; } const now = Date.now(); // 执行数据库数据刷新 console.log(`执行在售商品数据刷新 (间隔${interval/1000}秒)`); onSaleRefreshInProgress = true; lastOnSaleRefreshTime = now; lastRefreshTime = now; // 保持兼容性 updateSyncStatus('onSale', 'loading', '正在刷新数据...'); loadOnSaleGoods(currentOnSalePage) .then(() => { // 刷新成功,重置错误计数 onSaleRefreshErrorCount = 0; }) .catch((error) => { console.error('在售商品刷新出错:', error); onSaleRefreshErrorCount++; updateSyncStatus('onSale', 'error', '刷新失败: ' + (error.message || '未知错误')); if (onSaleRefreshErrorCount >= 5) { console.error('在售商品连续刷新失败过多,停止刷新'); stopOnSaleAutoRefresh(); showError('在售商品自动刷新连续失败,已停止'); } }) .always(() => { onSaleRefreshInProgress = false; }); } catch (error) { console.error('在售商品自动刷新机制出错:', error); onSaleRefreshInProgress = false; } }, interval); } // 启动已售商品自动刷新(仅从数据库读取数据) function startSoldAutoRefresh(interval) { if (soldRefreshInterval) { clearInterval(soldRefreshInterval); } soldRefreshInterval = setInterval(function() { try { // 检查开关状态 if (!$('#autoRefresh').prop('checked')) { console.log('自动刷新开关已关闭,停止已售商品刷新'); stopSoldAutoRefresh(); return; } // 检查页面可见性 if (document.hidden) { console.log('页面隐藏,跳过已售商品刷新'); return; } // 防重叠检查 if (soldRefreshInProgress || manualRefreshInProgress) { console.log('已售商品刷新仍在进行中或手动刷新中,跳过本次刷新'); return; } const now = Date.now(); // 执行数据库数据刷新 - 只刷新第一页最新数据 console.log(`执行已售商品数据刷新 (间隔${interval/1000}秒) - 仅更新第一页数据`); soldRefreshInProgress = true; lastSoldRefreshTime = now; updateSyncStatus('sold', 'loading', '正在刷新最新数据...'); // 自动刷新策略:只刷新第一页最新数据,减少网络负载和后端耦合 const targetPage = 1; // 自动刷新始终只获取第一页最新数据 loadSoldGoods(targetPage, true) .then(() => { // 刷新成功,重置错误计数 soldRefreshErrorCount = 0; }) .catch((error) => { console.error('已售商品刷新出错:', error); soldRefreshErrorCount++; updateSyncStatus('sold', 'error', '刷新失败: ' + (error.message || '未知错误')); if (soldRefreshErrorCount >= 5) { console.error('已售商品连续刷新失败过多,停止刷新'); stopSoldAutoRefresh(); showError('已售商品自动刷新连续失败,已停止'); } }) .always(() => { soldRefreshInProgress = false; }); } catch (error) { console.error('已售商品自动刷新机制出错:', error); soldRefreshInProgress = false; } }, interval); } // 停止自动刷新 function stopAutoRefresh(showMsg = true) { stopOnSaleAutoRefresh(); stopSoldAutoRefresh(); stopStatisticsRefresh(); // 停止统计数据刷新 // 保持兼容性 if (autoRefreshInterval) { clearInterval(autoRefreshInterval); autoRefreshInterval = null; } // 只在真正停止时才显示消息,避免在启动时显示重复消息 if (showMsg) { updateMonitorStatus('监控状态: 已停止', 'secondary'); showMessage('自动刷新已停止', 'info'); } console.log('自动刷新已停止'); } // 停止在售商品自动刷新 function stopOnSaleAutoRefresh() { if (onSaleRefreshInterval) { clearInterval(onSaleRefreshInterval); onSaleRefreshInterval = null; console.log('在售商品自动刷新已停止'); } onSaleRefreshInProgress = false; onSaleRefreshErrorCount = 0; } // 停止已售商品自动刷新 function stopSoldAutoRefresh() { if (soldRefreshInterval) { clearInterval(soldRefreshInterval); soldRefreshInterval = null; console.log('已售商品自动刷新已停止'); } soldRefreshInProgress = false; soldRefreshErrorCount = 0; } // ========== 优化:统计数据定期刷新 ========== // 启动统计数据定期刷新(每60秒) function startStatisticsRefresh() { if (statisticsRefreshInterval) { clearInterval(statisticsRefreshInterval); } statisticsRefreshInterval = setInterval(() => { // 检查页面可见性 if (document.hidden) { console.log('页面隐藏,跳过统计数据刷新'); return; } console.log('执行统计数据定期刷新'); loadStatistics().catch(error => { console.error('统计数据刷新失败:', error); }); }, 60000); // 每60秒刷新一次 console.log('✅ 统计数据定期刷新已启动(60秒间隔)'); } // 停止统计数据定期刷新 function stopStatisticsRefresh() { if (statisticsRefreshInterval) { clearInterval(statisticsRefreshInterval); statisticsRefreshInterval = null; console.log('统计数据定期刷新已停止'); } } // 刷新显示数据(轻量级,仅更新前端显示) function refreshDisplayData(silent = false) { if (!silent) { console.log('刷新显示数据...'); manualRefreshInProgress = true; } // 使用jQuery.when确保返回jQuery Deferred对象,保证.always()兼容性 return $.when( loadStatistics(), loadOnSaleGoods(currentOnSalePage), loadSoldGoods(currentSoldPage), loadMarketData(), checkMonitorStatus() ).then(() => { if (!silent) { console.log('显示数据刷新完成'); // 更新刷新时间戳,防止健康检查误判 const now = Date.now(); lastOnSaleRefreshTime = now; lastSoldRefreshTime = now; lastRefreshTime = now; } }).fail((error) => { if (!silent) { console.error('显示数据刷新出错:', error); } }).always(() => { if (!silent) { manualRefreshInProgress = false; } }); } // 刷新显示数据(前端只从数据库读取最新数据) function forceRefreshAllData() { const refreshBtn = $('#refreshBtn'); const refreshStatus = $('#refreshStatus'); refreshBtn.addClass('refreshing').prop('disabled', true); refreshStatus.html('正在刷新显示数据...'); console.log('刷新显示数据...'); return refreshDisplayData(false) .then(() => { console.log('显示数据刷新成功'); showMessage('显示数据刷新成功', 'success'); refreshStatus.html('刷新成功'); }) .catch((error) => { console.error('显示数据刷新失败:', error); showError('显示数据刷新失败,请稍后重试'); refreshStatus.html('刷新失败'); }) .always(() => { refreshBtn.removeClass('refreshing').prop('disabled', false); setTimeout(function() { refreshStatus.html(''); }, 3000); }); } // 保持向后兼容的函数名 function refreshAllData(silent = false) { if (silent) { // 自动刷新时使用轻量级刷新 return refreshDisplayData(true); } else { // 手动刷新时使用强制刷新 return forceRefreshAllData(); } } // 检查监控状态 function checkMonitorStatus() { return $.ajax({ url: '/api/monitor/status', method: 'GET', timeout: 10000, // 10秒超时 cache: false }) .done(function(response) { if (response.success) { const data = response.data; const isMonitoring = data.is_monitoring; const crawlerStatus = data.crawler_status; const intervals = { on_sale_interval: data.on_sale_interval, sold_interval: data.sold_interval, crawler_interval: data.crawler_interval // 向后兼容 }; const syncTimes = { lastOnSale: data.last_on_sale_update, lastSold: data.last_sold_update }; if (isMonitoring) { updateMonitorStatus('监控状态: 运行中', 'success'); } else { updateMonitorStatus('监控状态: 已停止', 'secondary'); } // 更新爬虫状态显示 updateCrawlerStatus(crawlerStatus, intervals, syncTimes); } }) .fail(function() { updateMonitorStatus('监控状态: 连接失败', 'danger'); updateCrawlerStatus('unknown', null, {lastOnSale: null, lastSold: null}); }); } // 更新监控状态显示(优化版:减少闪动) function updateMonitorStatus(text, type) { const statusElement = $('#monitorStatus'); const currentText = statusElement.text().trim(); const currentClasses = statusElement.attr('class') || ''; const newClass = `bg-${type}`; // 只在内容或样式真正改变时才更新DOM if (currentText !== text || !currentClasses.includes(newClass)) { statusElement.removeClass('bg-success bg-warning bg-danger bg-secondary') .addClass(newClass) .text(text); } } // 更新爬虫状态显示 function updateCrawlerStatus(status, intervals, syncTimes) { const crawlerElement = $('#crawlerStatus'); // 不显示任何后端相关信息 crawlerElement.html(''); } // 显示错误消息 function showError(message) { showMessage(message, 'error'); } // 显示消息 function showMessage(message, type = 'info') { const alertClass = type === 'error' ? 'alert-danger' : type === 'success' ? 'alert-success' : type === 'warning' ? 'alert-warning' : 'alert-info'; const alertHtml = ` `; $('body').append(alertHtml); // 自动消失 setTimeout(function() { $('.alert').alert('close'); }, 5000); } // 格式化时间 function formatTime(timeString) { if (!timeString || timeString === '1970-01-01 08:00:00') { return '未知'; } const date = new Date(timeString); const now = new Date(); const diff = now - date; // 如果是今天,显示具体时间 if (diff < 24 * 60 * 60 * 1000) { return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } // 否则显示日期 return date.toLocaleDateString('zh-CN'); } // 格式化完整时间(用于价格排行榜等需要显示完整时间的场景) function formatFullTime(timeString) { if (!timeString || timeString === '1970-01-01 08:00:00') { return '未知'; } const date = new Date(timeString); // 显示完整的日期和时间 return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } // 页面可见性变化处理 document.addEventListener('visibilitychange', function() { if (document.hidden) { // 页面隐藏时暂停自动刷新以节省资源 if ((onSaleRefreshInterval || soldRefreshInterval) && $('#autoRefresh').prop('checked')) { console.log('页面隐藏,暂停自动刷新'); stopAutoRefresh(); } } else { // 页面显示时恢复自动刷新 if ($('#autoRefresh').prop('checked') && !onSaleRefreshInterval && !soldRefreshInterval) { console.log('页面显示,恢复自动刷新'); // 重置时间戳,避免触发"长时间无响应"误判 lastOnSaleRefreshTime = Date.now(); lastSoldRefreshTime = Date.now(); startAutoRefresh(); } } }); // 搜索功能相关函数 // 验证搜索输入(SQL注入防护) function validateSearchInput(keyword) { // 检查是否为空 if (!keyword || keyword.trim() === '') { return { valid: false, message: '请输入搜索关键词' }; } // 检查长度限制(最大100字符) if (keyword.length > 100) { return { valid: false, message: '搜索关键词过长,最多支持100个字符' }; } // SQL注入可疑模式检测(与后端保持一致) const suspiciousPatterns = [ /;\s*DROP/i, /;\s*DELETE/i, /;\s*UPDATE/i, /UNION\s+SELECT/i, /--/, /\/\*.*\*\// ]; // 检测每个可疑模式 for (const pattern of suspiciousPatterns) { if (pattern.test(keyword)) { return { valid: false, message: '输入内容包含非法字符' }; } } // 验证通过 return { valid: true, message: '' }; } // 执行搜索 function performSearch() { const keyword = $('#searchInput').val().trim(); const status = $('#searchStatus').val(); // 验证搜索输入(SQL注入防护) const validationResult = validateSearchInput(keyword); if (!validationResult.valid) { showError(validationResult.message); return; } currentSearchKeyword = keyword; currentSearchStatus = status; currentSearchPage = 1; currentSortBy = $('#searchSortBy').val(); console.log(`搜索: 关键词="${keyword}", 状态="${status}", 排序="${currentSortBy}", 方向="${currentSortOrder}"`); // 显示搜索结果区域,隐藏默认列表 switchToSearchMode(); // 执行搜索 loadSearchResults(currentSearchPage); } // 清空搜索 function clearSearch() { $('#searchInput').val(''); $('#searchStatus').val(''); $('#searchSortBy').val('time'); currentSearchKeyword = ''; currentSearchStatus = ''; currentSearchPage = 1; currentSortBy = 'time'; currentSortOrder = 'desc'; // 重置排序UI updateSortOrderIcon(); // 隐藏搜索结果区域,显示默认列表 switchToDefaultMode(); console.log('搜索已清空,返回默认视图'); } // 更新搜索类型选项(根据用户权限) function updateSearchTypeOptions() { const userInfo = webAuth.getUserInfo(); const userLevel = userInfo ? (userInfo.user_level || 'guest') : 'guest'; const searchTypeSelect = $('#searchTypeSelect'); // 定义搜索类型选项 const allOptions = { 'cumulative': { label: '智能搜索', levels: ['guest', 'normal', 'vip', 'pro'] }, 'name': { label: '名称/编号', levels: ['guest', 'normal', 'vip', 'pro'] }, 'description': { label: '商品描述', levels: ['normal', 'vip', 'pro'] }, 'trait': { label: '特征属性', levels: ['vip', 'pro'] }, 'seller': { label: '卖家昵称', levels: ['pro'] } }; // 清空现有选项 searchTypeSelect.empty(); // 添加用户可用的选项 for (const [value, config] of Object.entries(allOptions)) { if (config.levels.includes(userLevel)) { const option = $('') .attr('value', value) .text(config.label); searchTypeSelect.append(option); } } // 默认选择cumulative searchTypeSelect.val('cumulative'); console.debug('搜索类型选项已更新'); } // 更新排序方向图标 function updateSortOrderIcon() { const icon = $('#sortOrderIcon'); if (currentSortOrder === 'desc') { icon.removeClass('fa-sort-amount-up').addClass('fa-sort-amount-down'); $('#sortOrderBtn').attr('title', '降序排列 (点击切换为升序)'); } else { icon.removeClass('fa-sort-amount-down').addClass('fa-sort-amount-up'); $('#sortOrderBtn').attr('title', '升序排列 (点击切换为降序)'); } } // 获取排序显示文本 function getSortDisplayText(sortBy, sortOrder) { const sortName = sortBy === 'price' ? '价格' : '时间'; const orderName = sortOrder === 'desc' ? '降序' : '升序'; return `按${sortName}${orderName}排列`; } // 切换到搜索模式 function switchToSearchMode() { if (!isSearchMode) { isSearchMode = true; $('#searchResultsSection').show(); $('#defaultListsSection').hide(); } } // 切换到默认模式 function switchToDefaultMode() { if (isSearchMode) { isSearchMode = false; $('#searchResultsSection').hide(); $('#defaultListsSection').show(); // 刷新默认列表 refreshDisplayData(false); } } // 加载搜索结果 function loadSearchResults(page = 1) { currentSearchPage = page; // 从下拉菜单获取用户选择的搜索类型 const searchType = $('#searchTypeSelect').val() || 'cumulative'; const params = { keyword: currentSearchKeyword, search_type: searchType, // 使用用户选择的搜索类型 page: page, limit: pageSize, sort_by: currentSortBy, sort_order: currentSortOrder }; if (currentSearchStatus) { params.status = currentSearchStatus; } console.log('加载搜索结果:', params); // 使用 $.ajax 并禁用缓存,避免搜索结果被缓存 $.ajax({ url: '/api/v1/goods/search', type: 'GET', data: params, cache: false, // 禁用缓存 success: function(response) { if (response.success) { displaySearchResults(response.data); } else { showError('搜索失败: ' + response.error); } }, error: function(xhr, status, error) { console.error('搜索API调用失败:', error); showError('搜索请求失败,请稍后重试'); } }); } // 显示搜索结果 function displaySearchResults(data) { const { goods, total, page, pages, keyword, status, sort_by, sort_order } = data; // 更新搜索信息显示 let statusText = ''; if (status == 1) statusText = ' (在售)'; else if (status == 2) statusText = ' (已售)'; $('#searchResultInfo').text(`搜索 "${keyword}"${statusText}: 找到 ${total} 件商品`); // 更新排序信息显示 const sortText = getSortDisplayText(sort_by, sort_order); $('#searchSortInfo').text(sortText); // 显示商品列表 const listContainer = $('#searchResultsList'); listContainer.empty(); if (goods.length === 0) { listContainer.html(`

    未找到匹配的商品

    `); } else { goods.forEach(item => { // 根据商品的sell_status确定类型 const type = item.sell_status === 2 ? 'sold' : 'on-sale'; const goodsHtml = createGoodsItemHtml(item, type); listContainer.append(goodsHtml); }); } // 更新分页 updateSearchPagination(data); // ========== Phase 2优化:触发图片懒加载 ========== triggerLazyLoad(); console.log(`显示搜索结果: ${goods.length}/${total} 件商品`); } // 更新搜索分页 function updateSearchPagination(data) { const pagination = $('#searchPagination'); pagination.empty(); if (data.pages <= 1) { return; } const { page, pages } = data; // 上一页 if (data.has_prev) { pagination.append(`
  • 上一页
  • `); } else { pagination.append('
  • 上一页
  • '); } // 页码 const startPage = Math.max(1, page - 2); const endPage = Math.min(pages, page + 2); if (startPage > 1) { pagination.append('
  • 1
  • '); if (startPage > 2) { pagination.append('
  • ...
  • '); } } for (let i = startPage; i <= endPage; i++) { const activeClass = i === page ? ' active' : ''; pagination.append(`
  • ${i}
  • `); } if (endPage < pages) { if (endPage < pages - 1) { pagination.append('
  • ...
  • '); } pagination.append(`
  • ${pages}
  • `); } // 下一页 if (data.has_next) { pagination.append(`
  • 下一页
  • `); } else { pagination.append('
  • 下一页
  • '); } } // 健康检查机制 function startHealthCheck() { // 每20秒检查一次自动刷新状态 setInterval(function() { try { const shouldBeRunning = $('#autoRefresh').prop('checked'); const onSaleRunning = onSaleRefreshInterval !== null; const soldRunning = soldRefreshInterval !== null; const timeSinceLastOnSaleRefresh = Date.now() - lastOnSaleRefreshTime; const timeSinceLastSoldRefresh = Date.now() - lastSoldRefreshTime; console.log(`健康检查 - 开关:${shouldBeRunning}, 在售定时器:${onSaleRunning}, 已售定时器:${soldRunning}`); console.log(`距上次刷新 - 在售:${Math.round(timeSinceLastOnSaleRefresh/1000)}s, 已售:${Math.round(timeSinceLastSoldRefresh/1000)}s`); // 检查定时器是否意外停止 if (shouldBeRunning && (!onSaleRunning || !soldRunning)) { console.warn('检测到自动刷新定时器意外停止,尝试重启'); startAutoRefresh(); return; } // 检查在售商品刷新是否长时间没有响应 const onSaleInterval = getOnSaleInterval() * 1000; if (shouldBeRunning && onSaleRunning && timeSinceLastOnSaleRefresh > onSaleInterval * 10) { console.warn('检测到在售商品刷新长时间无响应,尝试重启'); stopOnSaleAutoRefresh(); onSaleRefreshInProgress = false; onSaleRefreshErrorCount = 0; setTimeout(() => startOnSaleAutoRefresh(onSaleInterval), 1000); } // 检查已售商品刷新是否长时间没有响应 const soldInterval = getSoldInterval() * 1000; if (shouldBeRunning && soldRunning && timeSinceLastSoldRefresh > soldInterval * 10) { console.warn('检测到已售商品刷新长时间无响应,尝试重启'); stopSoldAutoRefresh(); soldRefreshInProgress = false; soldRefreshErrorCount = 0; setTimeout(() => startSoldAutoRefresh(soldInterval), 1000); } // 检查是否卡在刷新状态 if (onSaleRefreshInProgress && timeSinceLastOnSaleRefresh > 30000) { console.warn('检测到在售商品刷新进程可能卡死,重置状态'); onSaleRefreshInProgress = false; } if (soldRefreshInProgress && timeSinceLastSoldRefresh > 30000) { console.warn('检测到已售商品刷新进程可能卡死,重置状态'); soldRefreshInProgress = false; } // 保持向后兼容性 refreshInProgress = onSaleRefreshInProgress || soldRefreshInProgress; lastRefreshTime = Math.max(lastOnSaleRefreshTime, lastSoldRefreshTime); autoRefreshInterval = onSaleRefreshInterval; } catch (error) { console.error('健康检查出错:', error); } }, 20000); console.log('自动刷新健康检查机制已启动'); } // 显示成交分析模态框 function showAnalysisModal() { const modal = $('#analysisModal'); modal.modal('show'); // 加载分析数据 loadAnalysisData(); } // 加载成交分析数据 function loadAnalysisData(timeRange) { // 如果没有传入timeRange,使用当前选中的值,默认为30d if (!timeRange) { timeRange = $('input[name="timeRange"]:checked').val() || '30d'; } const modalBody = $('#analysisModalBody'); // 显示加载状态 modalBody.html(`
    正在加载分析数据...
    `); // 使用time_range参数调用API $.get(`/api/v1/analytics/transactions?time_range=${timeRange}&limit=200`) .done(function(response) { if (response.success) { displayAnalysisData(response.data); } else { modalBody.html(`
    加载分析数据失败: ${response.error || '未知错误'}
    `); } }) .fail(function(xhr, status, error) { console.error('加载分析数据失败:', error); let errorMsg = '网络连接失败,请稍后重试'; if (xhr.responseJSON && xhr.responseJSON.detail) { errorMsg = xhr.responseJSON.detail; } modalBody.html(`
    ${errorMsg}
    `); }); } // 全局变量存储价格排行榜数据 let globalPriceRankingData = []; // 显示分析数据 function displayAnalysisData(data) { const modalBody = $('#analysisModalBody'); // 存储价格排行榜数据到全局变量 globalPriceRankingData = data.price_ranking || []; // 获取时间范围标签 const timeRangeLabels = { '7d': '近7天', '30d': '近30天', '90d': '近90天', 'all': '全部历史' }; const timeRangeLabel = timeRangeLabels[data.time_range] || '近30天'; let html = '
    '; // 1. 价格排行榜(支持点击查看详情) html += `
    价格排行榜(前200名) ${timeRangeLabel}
    点击商品名称查看详情
    `; data.price_ranking.forEach((item, index) => { const rankClass = index < 3 ? 'text-warning fw-bold' : ''; const rankIcon = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : ''; html += ` `; }); html += `
    排名 商品名称 价格 卖家 售出时间
    ${rankIcon}${index + 1} ${item.name} ¥${item.price} ${item.seller_name || '未知'} ${formatFullTime(item.sell_time)}
    `; // 2. 卖家排行榜 html += `
    卖家排行榜(成交次数) ${timeRangeLabel}
    `; data.seller_ranking.slice(0, 15).forEach((item, index) => { html += ` `; }); html += `
    排名 卖家 成交数 总收入 均价
    ${index + 1} ${item.seller_name} ${item.sold_count} ¥${Math.round(item.total_revenue)} ¥${Math.round(item.avg_price)}
    `; // 3. 价格分布 html += `
    价格区间分布 ${timeRangeLabel}
    `; data.price_distribution.forEach(item => { const percentage = (item.count / data.price_ranking.length * 100).toFixed(1); html += `
    ${item.price_range} ${item.count}件 (${percentage}%)
    平均价格: ¥${Math.round(item.avg_price)}
    `; }); html += `
    `; // 4. 每日成交统计 html += `
    每日成交情况 ${timeRangeLabel}
    `; data.daily_transactions.forEach(item => { html += ` `; }); html += `
    日期 成交数量 平均价格 总成交额
    ${item.sell_date} ${item.daily_count} ¥${Math.round(item.daily_avg_price || 0)} ¥${Math.round(item.daily_total_value || 0)}
    `; // 5. 稀有度统计 if (data.rarity_stats && data.rarity_stats.length > 0) { html += `
    稀有度/类型统计
    `; data.rarity_stats.forEach(item => { html += ` `; }); html += `
    类型/稀有度 成交数 均价 价格区间
    ${item.collection_number} ${item.count} ¥${Math.round(item.avg_price)} ¥${Math.round(item.min_price)} - ¥${Math.round(item.max_price)}
    `; } // 6. 成交时间分布 if (data.time_distribution && data.time_distribution.length > 0) { html += `
    成交时间分布
    `; data.time_distribution.forEach(item => { const maxCount = Math.max(...data.time_distribution.map(d => d.count)); const percentage = (item.count / maxCount * 100).toFixed(1); html += `
    ${item.time_period} ${item.count}件 | 均价¥${Math.round(item.avg_price)}
    `; }); html += `
    `; } html += '
    '; modalBody.html(html); } // 显示价格排行榜商品详情 function showPriceRankingDetails(index) { if (!globalPriceRankingData || index >= globalPriceRankingData.length) { console.error('价格排行榜数据不存在或索引超出范围'); return; } const item = globalPriceRankingData[index]; // 转换数据格式以匹配 showGoodsModal 的期望 const goodsId = item.goods_id || item.id; // 生成正确的详情链接,优先使用数据库中的链接,如果为空则使用模板生成 let dealLink = item.deal_link; if (!dealLink || dealLink.trim() === '') { dealLink = `https://xmeta.x-metash.cn/prod/xmeta_mall/index.html#/pages/goodsDetail/index?id=${goodsId}`; } const goodsData = { id: goodsId, // 数据库返回的是 goods_id name: item.name, description: item.description || item.introduce || '', // 支持两个字段 introduce: item.introduce || item.description || '', price: item.price, original_price: item.original_price || item.price, seller_name: item.seller_name, sell_status: 2, // 价格排行榜都是已售商品 sell_time: item.sell_time, image_url: item.image_url, all_images: item.all_images || '[]', // 数据库直接返回的字段名 deal_link: dealLink, // 使用生成或修正后的链接 collection_number: item.collection_number, // 编号 platform_name: item.platform_name || '未知', // 平台名称 publish_time: item.publish_time || '未知', // 上架时间 is_presale: false // 已售商品不是预售 }; // 使用现有的商品详情模态框 showGoodsModal(goodsData); } // 执行重新获取已售数据(需要密码) function executeRefreshSoldData(password) { const statusDiv = $('#refreshSoldStatus'); const btn = $('#refreshSoldDataBtn'); // 显示执行状态 statusDiv.show().html(`
    正在验证管理员密码...
    `); // 禁用按钮防止重复点击 btn.prop('disabled', true).html('执行中...'); // 发送密码和执行请求 $.ajax({ url: '/api/refresh/sold', type: 'POST', contentType: 'application/json', data: JSON.stringify({ password: password }), success: function(response) { if (response.success) { statusDiv.html(`
    执行成功!
    ${response.message || '已售数据重新获取完成'} ${response.processed_pages ? `
    处理页数: ${response.processed_pages}` : ''} ${response.total_goods ? `
    总商品数: ${response.total_goods}` : ''}
    `); // 清空密码输入框并重置按钮状态 $('#adminPassword').val(''); $('#refreshSoldDataBtn').prop('disabled', true).removeClass('btn-warning').addClass('btn-secondary'); // 刷新已售商品列表 setTimeout(() => { loadSoldGoods(currentSoldPage); loadStatistics(); }, 1000); showMessage('已售数据重新获取成功', 'success'); } else { throw new Error(response.error || '操作失败'); } }, error: function(xhr, status, error) { let errorMessage = '操作失败'; if (xhr.status === 401) { errorMessage = '密码错误,无权执行此操作'; statusDiv.html(`
    密码错误
    请检查管理员密码是否正确
    `); // 清空错误的密码 $('#adminPassword').val().select(); } else { try { const response = JSON.parse(xhr.responseText); errorMessage = response.error || errorMessage; } catch (e) { errorMessage = `网络错误 (${xhr.status})`; } statusDiv.html(`
    执行失败
    ${errorMessage}
    `); } showMessage(errorMessage, 'error'); }, complete: function() { // 重新启用按钮 btn.prop('disabled', $('#adminPassword').val().trim().length === 0) .html('执行重新获取'); } }); } // 设置模态框相关功能 function loadSettingsModal() { // 加载当前设置到模态框 const currentOnSaleInterval = $('#onSaleInterval').val() || 3; const currentSoldInterval = $('#soldInterval').val() || 30; const isAutoRefreshEnabled = $('#autoRefresh').is(':checked'); $('#settingsOnSaleInterval').val(currentOnSaleInterval); $('#settingsSoldInterval').val(currentSoldInterval); $('#settingsAutoRefresh').prop('checked', isAutoRefreshEnabled); // 重置管理员功能区域 $('#adminPassword').val(''); $('#refreshSoldDataBtn').prop('disabled', true).removeClass('btn-warning').addClass('btn-secondary') .html('执行重新获取'); $('#refreshSoldStatus').hide(); } function saveSettings() { let newOnSaleInterval = parseInt($('#settingsOnSaleInterval').val()); let newSoldInterval = parseInt($('#settingsSoldInterval').val()); const enableAutoRefresh = $('#settingsAutoRefresh').is(':checked'); // 验证输入值 if (isNaN(newOnSaleInterval) || newOnSaleInterval < 1) { newOnSaleInterval = 1; $('#settingsOnSaleInterval').val(1); showMessage('在售商品刷新间隔已自动修正为最小值1秒', 'warning'); } if (isNaN(newSoldInterval) || newSoldInterval < 1) { newSoldInterval = 1; $('#settingsSoldInterval').val(1); showMessage('已售商品刷新间隔已自动修正为最小值1秒', 'warning'); } // 应用设置到主界面 $('#onSaleInterval').val(newOnSaleInterval); $('#soldInterval').val(newSoldInterval); $('#autoRefresh').prop('checked', enableAutoRefresh); // 更新同步间隔显示 updateSyncInfo(); // 如果自动刷新状态发生变化,相应地启动或停止 if (enableAutoRefresh) { if (autoRefreshInterval) { stopAutoRefresh(); // 先停止旧的 } startAutoRefresh(); // 启动新的 } else { stopAutoRefresh(); } // 关闭模态框 $('#settingsModal').modal('hide'); // 显示保存成功消息 showMessage(`设置已保存:在售商品${newOnSaleInterval}秒,已售商品${newSoldInterval}秒,自动刷新${enableAutoRefresh ? '已启用' : '已禁用'}`, 'success'); console.log(`设置已保存:在售商品${newOnSaleInterval}秒,已售商品${newSoldInterval}秒,自动刷新${enableAutoRefresh ? '已启用' : '已禁用'}`); } // 检查已售数据同步状态 function checkSoldDataSyncStatus() { console.log('检查已售数据同步状态...'); return $.get('/api/sold/sync-status') .done(function(response) { if (response.success) { const data = response; const syncRate = data.sync_rate; console.log(`已售数据同步状态: ${data.message}`); if (syncRate < 90) { showMessage(`已售数据同步异常: ${data.message},正在自动修复...`, 'warning'); // 自动尝试修复同步问题 autoFixSoldDataSync(); } else { console.log(`已售数据同步正常: ${data.message}`); } return data; } else { console.error('检查同步状态失败:', response.error); return null; } }) .fail(function(xhr, status, error) { console.error('检查已售数据同步状态失败:', error); return null; }); } // 窗口关闭前清理 window.addEventListener('beforeunload', function() { stopAutoRefresh(); }); // ========== 优化:禁用定期同步检查机制 ========== // 原因:当前修复任务(fetch_sold_goods_details)不存在,检查机制无效 // 每次检查都会触发警告,但无法真正修复,浪费资源且影响用户体验 // 如需启用,请先实现真正的修复任务 /* let syncCheckInterval = setInterval(function() { checkSoldDataSyncStatus().then(data => { if (data && data.sync_rate < 90) { // 如果同步率过低,增加检查频率 clearInterval(syncCheckInterval); syncCheckInterval = setInterval(() => checkSoldDataSyncStatus(), 1 * 60 * 1000); // 每分钟检查 logger.warning('已售数据同步异常,增加检查频率'); } }); }, 3 * 60 * 1000); */ // 自动修复已售数据同步问题 function autoFixSoldDataSync() { console.log('尝试自动修复已售数据同步问题...'); return $.post('/api/sold/force-sync') .done(function(response) { if (response.success) { if (response.action === 'forced_refresh') { showMessage(`同步修复成功,同步率从${response.sync_before}%提升`, 'success'); // 刷新已售商品列表 loadSoldGoods(1); } else { showMessage('已售数据同步正常,无需修复', 'info'); } } else { showMessage('自动修复失败: ' + response.error, 'error'); } }) .fail(function(xhr, status, error) { console.error('自动修复已售数据同步失败:', error); showMessage('自动修复网络请求失败,请手动检查', 'error'); }); } // ========== 优化:禁用页面加载时的同步检查 ========== // 原因:当前修复任务(fetch_sold_goods_details)不存在,检查机制无效 // 每次打开页面都会显示警告,但无法真正修复,影响用户体验和首页加载速度 // 如需启用,请先实现真正的修复任务 /* $(document).ready(function() { setTimeout(() => { checkSoldDataSyncStatus(); }, 5000); // 页面加载5秒后检查 }); */ // ==================== 管理员功能 ==================== // 管理员功能变量 let currentAdminAction = ''; let batchUpdateInProgress = false; // 设置事件监听器 - 在 setupEventListeners 函数中添加 function setupAdminEventListeners() { // 管理员密码输入监听 - 控制设置界面中的按钮启用状态 $('#adminPassword').on('input', function() { const password = $(this).val().trim(); const isPasswordValid = password.length > 0; // 注意:原有的重新获取数据和批量更新功能已集成到数据校验功能中 // 这些按钮已从界面移除,相关功能通过数据校验模块实现 // $('#refreshOnSaleDataBtn').prop('disabled', !isPasswordValid); // $('#refreshSoldDataBtn').prop('disabled', !isPasswordValid); // $('#batchUpdateBtn').prop('disabled', !isPasswordValid); }); // 注意:以下功能已集成到数据校验模块中,相关按钮已从界面移除 /* // 重新获取在售数据按钮点击 $('#refreshOnSaleDataBtn').click(function(e) { e.preventDefault(); const password = $('#adminPassword').val().trim(); if (!password) { showMessage('请先输入管理员密码', 'warning'); return; } if (confirm('确认要重新获取所有在售数据吗?这个操作可能需要较长时间。')) { executeRefreshOnSaleData(password); } }); // 批量更新按钮点击(设置界面中的) $('#batchUpdateBtn').click(function(e) { e.preventDefault(); const password = $('#adminPassword').val().trim(); if (!password) { showMessage('请先输入管理员密码', 'warning'); return; } showAdminConfirmModal('批量更新商品详情信息', 'batchUpdate'); }); */ // 管理员密码确认输入监听 $('#adminConfirmPassword').on('input', function() { const password = $(this).val().trim(); $('#confirmAdminAction').prop('disabled', password.length === 0); }); // 密码显示/隐藏切换 $('#toggleConfirmPassword').click(function() { const input = $('#adminConfirmPassword'); const icon = $(this).find('i'); if (input.attr('type') === 'password') { input.attr('type', 'text'); icon.removeClass('fa-eye').addClass('fa-eye-slash'); } else { input.attr('type', 'password'); icon.removeClass('fa-eye-slash').addClass('fa-eye'); } }); // 设置界面密码显示/隐藏切换 $('#toggleAdminPassword').click(function() { const input = $('#adminPassword'); const icon = $(this).find('i'); if (input.attr('type') === 'password') { input.attr('type', 'text'); icon.removeClass('fa-eye').addClass('fa-eye-slash'); } else { input.attr('type', 'password'); icon.removeClass('fa-eye-slash').addClass('fa-eye'); } }); // 确认执行按钮 $('#confirmAdminAction').click(function() { executeAdminAction(); }); // 模态框关闭时重置状态 $('#adminConfirmModal').on('hidden.bs.modal', function() { resetAdminModal(); }); } // 显示管理员确认模态框 function showAdminConfirmModal(actionName, actionType) { currentAdminAction = actionType; $('#adminActionName').text(actionName); $('#adminConfirmPassword').val(''); $('#confirmAdminAction').prop('disabled', true); // 根据操作类型显示/隐藏相关选项 if (actionType === 'batchUpdate') { $('#batchUpdateOptions').show(); } else { $('#batchUpdateOptions').hide(); } // 重置进度显示 $('#batchUpdateProgress').hide(); resetProgressDisplay(); // 显示模态框 const modal = new bootstrap.Modal($('#adminConfirmModal')[0]); modal.show(); } // 执行管理员操作 function executeAdminAction() { const password = $('#adminConfirmPassword').val().trim(); if (!password) { showMessage('请输入管理员密码', 'warning'); return; } if (batchUpdateInProgress) { showMessage('批量更新正在进行中,请等待完成', 'warning'); return; } // 显示进度区域 $('#batchUpdateProgress').show(); $('#confirmAdminAction').prop('disabled', true); batchUpdateInProgress = true; updateProgressDisplay(0, '正在验证密码...'); if (currentAdminAction === 'batchUpdate') { executeBatchUpdate(password); } } // 执行批量更新 function executeBatchUpdate(password) { const statusFilter = $('input[name="updateScope"]:checked').val(); const limit = parseInt($('#batchLimit').val()); const statusText = statusFilter === '1' ? '在售' : '所有'; updateProgressDisplay(0, `开始批量更新${statusText}商品信息...`); // 构建请求数据 const requestData = { password: password, limit: limit, status_filter: statusFilter || null }; // 发送批量更新请求 $.ajax({ url: '/api/admin/batch_fetch_details', method: 'POST', contentType: 'application/json', data: JSON.stringify(requestData), timeout: 300000, // 5分钟超时 xhr: function() { const xhr = new window.XMLHttpRequest(); // 监听进度(如果服务器支持的话) xhr.addEventListener('progress', function(evt) { if (evt.lengthComputable) { const percentComplete = evt.loaded / evt.total; updateProgressDisplay(percentComplete * 100, '正在处理...'); } }, false); return xhr; } }) .done(function(response) { if (response.success) { const processed = response.processed || 0; const updated = response.updated || 0; const errors = response.errors || 0; updateProgressDisplay(100, `批量更新完成!处理 ${processed} 个商品,成功更新 ${updated} 个,失败 ${errors} 个`); // 显示详细结果 const resultHtml = `
    批量更新完成
    `; $('#batchUpdateStatus').html(resultHtml).show(); // 同时在设置界面显示结果 const settingsResultHtml = `
    批量更新完成

    处理 ${processed} 个商品,成功更新 ${updated} 个,失败 ${errors} 个

    `; // 在设置界面显示结果 const settingsStatus = $('#batchUpdateStatus'); if (settingsStatus.length) { settingsStatus.html(settingsResultHtml).show(); } // 刷新商品列表 setTimeout(function() { if (statusFilter === '1') { loadOnSaleGoods(1); } else { loadOnSaleGoods(1); loadSoldGoods(1); } loadStats(); // 刷新统计信息 }, 1000); showMessage(`批量更新完成:成功更新 ${updated} 个商品`, 'success'); // 3秒后自动关闭模态框 setTimeout(function() { if (!batchUpdateInProgress) return; $('#adminConfirmModal').modal('hide'); }, 3000); } else { updateProgressDisplay(0, '批量更新失败:' + (response.error || '未知错误')); showMessage('批量更新失败:' + (response.error || '未知错误'), 'error'); const errorHtml = `
    更新失败

    ${response.error || '未知错误'}

    `; $('#batchUpdateStatus').html(errorHtml); } }) .fail(function(xhr, status, error) { let errorMessage = '网络请求失败'; if (xhr.status === 403) { errorMessage = '管理员密码错误'; } else if (xhr.status === 408 || status === 'timeout') { errorMessage = '请求超时,操作可能仍在后台进行'; } else if (xhr.responseJSON && xhr.responseJSON.error) { errorMessage = xhr.responseJSON.error; } updateProgressDisplay(0, '批量更新失败:' + errorMessage); showMessage('批量更新失败:' + errorMessage, 'error'); const errorHtml = `
    更新失败

    ${errorMessage}

    `; $('#batchUpdateStatus').html(errorHtml); }) .finally(function() { batchUpdateInProgress = false; $('#confirmAdminAction').prop('disabled', false); }); } // 更新进度显示 function updateProgressDisplay(percentage, statusText) { $('#progressBar').css('width', percentage + '%') .attr('aria-valuenow', percentage); $('#progressText').text(Math.round(percentage) + '%'); if (statusText) { $('#batchUpdateStatus').html(`${statusText}`); } } // 重置进度显示 function resetProgressDisplay() { updateProgressDisplay(0, ''); $('#batchUpdateStatus').html(''); } // 重置管理员模态框 function resetAdminModal() { currentAdminAction = ''; batchUpdateInProgress = false; $('#adminConfirmPassword').val(''); $('#confirmAdminAction').prop('disabled', true); $('#batchUpdateProgress').hide(); resetProgressDisplay(); // 重置密码输入框类型 $('#adminConfirmPassword').attr('type', 'password'); $('#toggleConfirmPassword').find('i').removeClass('fa-eye-slash').addClass('fa-eye'); } // 在页面初始化时设置管理员事件监听器 $(document).ready(function() { setupAdminEventListeners(); setupTraitsManagement(); }); // =================== 特征管理功能 =================== let traitsData = []; // 初始化为数组,支持虚拟滚动 let currentEditingTrait = null; // 初始化特征管理 function setupTraitsManagement() { // 设置事件监听器 $('#loadTraitsBtn').on('click', function() { loadTraitsData(false); }); $('#traitsStatsBtn').on('click', toggleTraitsStats); $('#traitCategoryFilter').on('change', filterTraitsByCategory); $('#saveTraitEditBtn').on('click', saveTraitEdit); $('#removeTraitOverrideBtn').on('click', removeTraitOverride); // 模态框重置 $('#traitEditModal').on('hidden.bs.modal', function() { resetTraitEditModal(); }); } // 加载特征数据 - 支持虚拟滚动 // 全局变量:分页状态 let traitsCurrentPage = 1; let traitsPageSize = 30; let traitsTotalCount = 0; let traitsIsLoading = false; let traitsHasMore = true; let traitsCurrentCategory = ''; // 加载特征数据(只读模式,支持分页和虚拟滚动) function loadTraitsData(isAppend = false) { // 防止重复加载 if (traitsIsLoading) return; // 如果已经没有更多数据,不再加载 if (isAppend && !traitsHasMore) { console.log('已加载全部数据'); return; } traitsIsLoading = true; if (!isAppend) { // 初始加载 $('#loadTraitsBtn').prop('disabled', true).html('加载中...'); traitsCurrentPage = 1; traitsHasMore = true; traitsData = []; } else { // 追加加载 - 显示底部加载指示器 showLoadingIndicator(); } // 构建API URL let url = `/api/v1/traits/list?page=${traitsCurrentPage}&limit=${traitsPageSize}`; if (traitsCurrentCategory) { url += `&category=${encodeURIComponent(traitsCurrentCategory)}`; } $.get(url) .done(function(response) { try { if (response.success) { // 确保获取到的是数组 let newTraits = response.data.traits || response.data; // 如果不是数组,尝试转换或使用空数组 if (!Array.isArray(newTraits)) { console.warn('API返回的数据不是数组格式:', newTraits); newTraits = []; } traitsTotalCount = response.data.total || 0; // 验证数据格式 if (!newTraits || newTraits.length === 0) { if (!isAppend) { showMessage('暂无特征数据', 'warning'); } traitsHasMore = false; hideLoadingIndicator(); return; } // 确保 traitsData 是数组 if (!Array.isArray(traitsData)) { console.warn('traitsData不是数组,重置为空数组'); traitsData = []; } // 合并或替换数据 if (isAppend) { traitsData = traitsData.concat(newTraits); } else { traitsData = newTraits; } // 检查是否还有更多数据 traitsHasMore = traitsData.length < traitsTotalCount; // 渲染表格 renderTraitsTable(traitsData, isAppend); if (!isAppend) { showMessage(`特征数据加载成功,共${traitsTotalCount}条,已显示${traitsData.length}条`, 'success'); } // 准备加载下一页 traitsCurrentPage++; } else { const errorMsg = response.error || response.message || '未知错误'; showMessage('加载失败:' + String(errorMsg), 'error'); traitsHasMore = false; } } catch (e) { console.error('处理特征数据失败:', e); showMessage('处理数据失败:' + e.message, 'error'); traitsHasMore = false; } }) .fail(function(xhr, status, error) { let errorMessage = '加载失败'; try { if (xhr.responseJSON) { if (xhr.responseJSON.error) { errorMessage = String(xhr.responseJSON.error); } else if (xhr.responseJSON.detail) { // 处理Pydantic验证错误(数组格式) if (Array.isArray(xhr.responseJSON.detail)) { const firstError = xhr.responseJSON.detail[0]; if (firstError && firstError.msg) { errorMessage = String(firstError.msg); } else { errorMessage = '参数验证失败'; } } else { errorMessage = String(xhr.responseJSON.detail); } } else if (xhr.responseJSON.message) { errorMessage = String(xhr.responseJSON.message); } } else if (xhr.responseText) { errorMessage = '请求失败: ' + xhr.status + ' ' + xhr.statusText; } else { errorMessage = '网络错误: ' + String(error || status); } } catch (e) { console.error('解析错误信息失败:', e); errorMessage = '请求失败,状态码: ' + xhr.status; } console.error('加载特征数据失败:', {xhr, status, error, responseJSON: xhr.responseJSON}); showMessage(errorMessage, 'error'); traitsHasMore = false; }) .finally(function() { traitsIsLoading = false; hideLoadingIndicator(); $('#loadTraitsBtn').prop('disabled', false).html('加载特征数据'); }); } // 显示底部加载指示器 function showLoadingIndicator() { if ($('#traitsLoadingIndicator').length === 0) { $('#traitsTableContainer').after(`
    加载更多数据...
    `); } $('#traitsLoadingIndicator').show(); } // 隐藏底部加载指示器 function hideLoadingIndicator() { $('#traitsLoadingIndicator').hide(); } // 渲染特征表格(只读模式,支持追加模式) function renderTraitsTable(data, isAppend = false) { // 追加模式:只添加新行,不重建整个表格 if (isAppend && $('#traitsTableContainer table tbody').length > 0) { let rowsHtml = ''; if (Array.isArray(data)) { // 计算需要追加的新数据(当前页的数据) const existingRowCount = $('#traitsTableContainer table tbody tr').length; const newData = data.slice(existingRowCount); newData.forEach(trait => { const rarityBadge = getRarityBadge(trait.rarity_level); const rarityPercent = (trait.rarity_rate * 100).toFixed(2); rowsHtml += ` ${trait.category} ${trait.trait_name} ${rarityPercent}% ${rarityBadge} `; }); } if (rowsHtml) { $('#traitsTableContainer table tbody').append(rowsHtml); } return; } // 初始加载:重建整个表格 let html = `
    `; // 兼容两种数据格式:数组格式和对象格式 if (Array.isArray(data)) { // 数组格式:API返回的traits数组 data.forEach(trait => { const rarityBadge = getRarityBadge(trait.rarity_level); // rarity_rate是小数格式(0-1),需要转换为百分比 const rarityPercent = (trait.rarity_rate * 100).toFixed(2); html += ` `; }); } else { // 对象格式:按类别分组的对象(向后兼容) for (const [category, traits] of Object.entries(data)) { traits.forEach(trait => { const rarityBadge = getRarityBadge(trait.rarity_level); const rarityPercent = trait.rarity ? trait.rarity.toFixed(2) : (trait.rarity_rate * 100).toFixed(2); html += ` `; }); } } html += `
    类别 特征名称 稀有度 等级
    ${trait.category} ${trait.trait_name} ${rarityPercent}% ${rarityBadge}
    ${category} ${trait.name || trait.trait_name} ${rarityPercent}% ${rarityBadge}
    `; $('#traitsTableContainer').html(html); // 绑定滚动事件监听 setupTraitsScrollListener(); } // 设置滚动事件监听(无限滚动) function setupTraitsScrollListener() { const scrollContainer = $('#traitsTableScroll'); if (scrollContainer.length === 0) return; // 移除旧的监听器,避免重复绑定 scrollContainer.off('scroll'); // 添加滚动监听 scrollContainer.on('scroll', function() { const container = $(this); const scrollTop = container.scrollTop(); const scrollHeight = container[0].scrollHeight; const clientHeight = container.innerHeight(); // 当滚动到距离底部100px时,加载更多数据 if (scrollTop + clientHeight >= scrollHeight - 100) { if (!traitsIsLoading && traitsHasMore) { console.log('触发加载更多数据'); loadTraitsData(true); } } }); } // 根据稀有度分数获取等级信息 function getRarityLevel(score) { if (score >= 90) { return { level: 'legendary', label: '传说级', class: 'primary' }; } else if (score >= 75) { return { level: 'super_rare', label: '超稀有', class: 'danger' }; } else if (score >= 60) { return { level: 'rare', label: '稀有', class: 'warning' }; } else { return { level: 'common', label: '普通', class: 'secondary' }; } } // 获取稀有度徽章 function getRarityBadge(level) { const badges = { 'common': '普通', 'rare': '稀有', 'super_rare': '超稀有', 'legendary': '传说' }; return badges[level] || '未知'; } // 按类别筛选特征 function filterTraitsByCategory() { const selectedCategory = $('#traitCategoryFilter').val(); // 更新当前筛选的类别 traitsCurrentCategory = selectedCategory; // 重新加载数据(从第一页开始) loadTraitsData(false); // 如果统计面板正在显示,重新加载统计数据(带上类别筛选) const statsPanel = $('#traitsStatsPanel'); if (statsPanel.is(':visible')) { loadTraitsStats(selectedCategory); } } // 编辑特征 function editTrait(category, name, rarity, currentLevel, hasOverride, manualLevel) { currentEditingTrait = { category, name, rarity }; // 填充模态框信息 $('#traitEditCategory').text(category); $('#traitEditName').text(name); $('#traitEditOriginalRarity').text(rarity.toFixed(2)); $('#traitEditLevel').val(manualLevel || currentLevel); $('#traitEditReason').val(''); $('#traitEditPassword').val(''); // 显示/隐藏恢复按钮 if (hasOverride) { $('#removeTraitOverrideBtn').show(); } else { $('#removeTraitOverrideBtn').hide(); } // 显示模态框 $('#traitEditModal').modal('show'); } // 快速移除覆盖 function quickRemoveOverride(category, name) { if (!confirm(`确认要恢复 "${category} - ${name}" 为自动计算吗?`)) { return; } const password = $('#adminPassword').val(); if (!password) { showMessage('请先在管理员功能页面输入密码', 'warning'); return; } removeTraitOverrideAPI(category, name, password); } // 保存特征编辑 function saveTraitEdit() { if (!currentEditingTrait) return; const level = $('#traitEditLevel').val(); const reason = $('#traitEditReason').val(); const password = $('#traitEditPassword').val(); if (!level || !password) { showMessage('请填写完整信息', 'warning'); return; } const data = { category: currentEditingTrait.category, name: currentEditingTrait.name, manual_level: level, reason: reason, password: password }; $('#saveTraitEditBtn').prop('disabled', true).html('保存中...'); $.ajax({ url: '/api/admin/traits/override', method: 'POST', contentType: 'application/json', data: JSON.stringify(data) }) .done(function(response) { if (response.success) { showMessage(response.message, 'success'); $('#traitEditModal').modal('hide'); loadTraitsData(); // 重新加载数据 } else { showMessage('保存失败:' + response.error, 'error'); } }) .fail(function(xhr) { let errorMessage = '保存失败'; if (xhr.status === 401) { errorMessage = '管理员密码错误'; } else if (xhr.responseJSON && xhr.responseJSON.error) { errorMessage = xhr.responseJSON.error; } showMessage(errorMessage, 'error'); }) .finally(function() { $('#saveTraitEditBtn').prop('disabled', false).html('保存'); }); } // 移除特征覆盖 function removeTraitOverride() { if (!currentEditingTrait) return; const password = $('#traitEditPassword').val(); if (!password) { showMessage('请输入管理员密码', 'warning'); return; } removeTraitOverrideAPI(currentEditingTrait.category, currentEditingTrait.name, password); } // 移除特征覆盖API调用 function removeTraitOverrideAPI(category, name, password) { const data = { category: category, name: name, password: password }; $.ajax({ url: '/api/admin/traits/override', method: 'DELETE', contentType: 'application/json', data: JSON.stringify(data) }) .done(function(response) { if (response.success) { showMessage(response.message, 'success'); if ($('#traitEditModal').hasClass('show')) { $('#traitEditModal').modal('hide'); } loadTraitsData(); // 重新加载数据 } else { showMessage('操作失败:' + response.error, 'error'); } }) .fail(function(xhr) { let errorMessage = '操作失败'; if (xhr.status === 401) { errorMessage = '管理员密码错误'; } else if (xhr.responseJSON && xhr.responseJSON.error) { errorMessage = xhr.responseJSON.error; } showMessage(errorMessage, 'error'); }); } // 加载特征统计数据(支持按类别筛选) function loadTraitsStats(category) { const panel = $('#traitsStatsPanel'); // 禁用按钮并显示加载状态 $('#traitsStatsBtn').prop('disabled', true).html('加载中...'); // 构建API URL(如果有类别筛选则添加参数) let apiUrl = '/api/v1/traits/stats'; if (category) { apiUrl += '?category=' + encodeURIComponent(category); } $.get(apiUrl) .done(function(response) { if (response.success && response.data) { // 使用API返回的统计数据 const stats = response.data; const totalTraits = stats.total || 0; const categoriesCount = Object.keys(stats.by_category || {}).length; // 计算稀有特征数量(稀有+超稀有+传说级) const rarityStats = stats.by_rarity_level || {}; const rareTraitsCount = (rarityStats.rare || 0) + (rarityStats.super_rare || 0) + (rarityStats.legendary || 0); $('#totalTraitsCount').text(totalTraits); $('#categoriesCount').text(categoriesCount); $('#rareTraitsCount').text(rareTraitsCount); // 显示各个稀有度等级的详细数量 console.log('稀有度统计数据:', rarityStats, '筛选类别:', category || '全部'); $('#legendaryCount').text(rarityStats.legendary || 0); $('#superRareCount').text(rarityStats.super_rare || 0); $('#rareCount').text(rarityStats.rare || 0); $('#commonCount').text(rarityStats.common || 0); console.log('已更新稀有度数量显示'); panel.show(); $('#traitsStatsBtn').html('隐藏统计'); } else { showMessage('加载统计失败:' + (response.error || '未知错误'), 'error'); } }) .fail(function(xhr, status, error) { let errorMessage = '加载统计失败'; if (xhr.responseJSON && xhr.responseJSON.error) { errorMessage = xhr.responseJSON.error; } else if (xhr.responseJSON && xhr.responseJSON.detail) { errorMessage = String(xhr.responseJSON.detail); } else if (error) { errorMessage = '网络错误: ' + error; } console.error('加载统计失败:', {xhr, status, error}); showMessage(errorMessage, 'error'); }) .finally(function() { $('#traitsStatsBtn').prop('disabled', false); }); } // 切换特征统计面板(只读模式) function toggleTraitsStats() { const panel = $('#traitsStatsPanel'); if (panel.is(':visible')) { panel.hide(); $('#traitsStatsBtn').html('查看统计'); return; } // 获取当前选择的类别 const selectedCategory = $('#traitCategoryFilter').val(); // 加载统计数据(带上类别筛选) loadTraitsStats(selectedCategory); } // 重置特征编辑模态框 function resetTraitEditModal() { currentEditingTrait = null; $('#traitEditForm')[0].reset(); $('#removeTraitOverrideBtn').hide(); } // =================== 在售数据重新获取功能 =================== // 执行重新获取在售数据 function executeRefreshOnSaleData(password) { const btn = $('#refreshOnSaleDataBtn'); const statusDiv = $('#refreshOnSaleStatus'); // 禁用按钮 btn.prop('disabled', true).html('获取中...'); statusDiv.show().html('
    正在重新获取在售数据,请稍候...
    '); $.ajax({ url: '/api/refresh/on_sale', method: 'POST', contentType: 'application/json', data: JSON.stringify({ password: password }), timeout: 600000 // 10分钟超时 }) .done(function(response) { if (response.success) { const stats = response.stats; const successHtml = `
    在售数据获取完成
    `; statusDiv.html(successHtml); showMessage('在售数据获取完成', 'success'); // 刷新页面数据 setTimeout(() => { if (!isSearchMode) { loadOnSaleData(currentOnSalePage); } else { performSearch(); } }, 1000); } else { statusDiv.html(`
    获取失败

    ${response.error}

    `); showMessage('在售数据获取失败:' + response.error, 'error'); } }) .fail(function(xhr, status, error) { let errorMessage = '网络请求失败'; if (xhr.status === 401) { errorMessage = '管理员密码错误'; } else if (xhr.status === 408 || status === 'timeout') { errorMessage = '请求超时,操作可能仍在后台进行'; } else if (xhr.responseJSON && xhr.responseJSON.error) { errorMessage = xhr.responseJSON.error; } const errorHtml = `
    获取失败

    ${errorMessage}

    `; statusDiv.html(errorHtml); showMessage('在售数据获取失败:' + errorMessage, 'error'); }) .finally(function() { // 恢复按钮状态 btn.prop('disabled', false).html('重新获取在售数据'); }); } // ================== 特征管理功能 ================== let traitsAdminPassword = ''; let isTraitsPasswordVerified = false; let traitsVersions = []; // 特征管理密码验证 function verifyTraitsPassword() { const password = $('#traitsAdminPassword').val(); const statusSpan = $('#traitsPasswordStatus'); if (!password) { showTraitsPasswordStatus('请输入密码', 'danger'); return; } traitsAdminPassword = password; // 通过尝试获取版本列表来验证密码 $.ajax({ url: '/admin/traits/versions', method: 'POST', data: { admin_password: password }, timeout: 10000 }) .done(function(response) { if (response.success) { isTraitsPasswordVerified = true; showTraitsPasswordStatus('密码验证成功', 'success'); $('#traitsPasswordPanel').hide(); $('#traitsAdminPanel').show(); logTraitsMessage('管理员密码验证成功,特征管理面板已激活'); setupTraitsUpload(); loadVersions(); } else { showTraitsPasswordStatus('密码验证失败', 'danger'); } }) .fail(function() { showTraitsPasswordStatus('验证请求失败', 'danger'); }); } function showTraitsPasswordStatus(message, type) { const statusSpan = $('#traitsPasswordStatus'); const className = type === 'success' ? 'text-success' : 'text-danger'; statusSpan.html(`${message}`) .removeClass('text-success text-danger') .addClass(className); } // 设置文件上传 function setupTraitsUpload() { const uploadArea = $('#uploadArea'); const fileInput = $('#excelFile'); // 检查元素是否存在 if (uploadArea.length === 0) { console.error('找不到uploadArea元素'); return; } if (fileInput.length === 0) { console.error('找不到excelFile元素'); return; } uploadArea.off('click').on('click', function(e) { e.preventDefault(); e.stopPropagation(); fileInput.trigger('click'); }); uploadArea.off('dragover drop dragleave').on({ 'dragover': function(e) { e.preventDefault(); $(this).addClass('border-primary bg-primary bg-opacity-10'); }, 'drop': function(e) { e.preventDefault(); $(this).removeClass('border-primary bg-primary bg-opacity-10'); const files = e.originalEvent.dataTransfer.files; if (files.length > 0 && files[0].name.match(/\.(xlsx?|xls)$/i)) { fileInput[0].files = files; updateImportButton(); const fileName = files[0].name; logTraitsMessage(`选择了文件: ${fileName}`); autoFillVersionInfo(fileName); } else { alert('请选择Excel文件(.xlsx或.xls格式)'); } }, 'dragleave': function() { $(this).removeClass('border-primary bg-primary bg-opacity-10'); } }); fileInput.off('change').on('change', function() { updateImportButton(); if (this.files.length > 0) { const fileName = this.files[0].name; logTraitsMessage(`选择了文件: ${fileName}`); autoFillVersionInfo(fileName); } }); } // 自动填充版本信息 function autoFillVersionInfo(fileName) { // 自动填充版本名称为文件名(去除扩展名) const versionName = fileName.replace(/\.(xlsx?|xls)$/i, ''); $('#versionName').val(versionName); // 自动填充版本描述 const currentDate = new Date().toLocaleDateString('zh-CN'); $('#versionDescription').val(`从文件 ${fileName} 导入 (${currentDate})`); } function updateImportButton() { const fileInput = $('#excelFile')[0]; const importBtn = $('#importBtn'); if (fileInput.files.length > 0 && isTraitsPasswordVerified) { importBtn.prop('disabled', false); } else { importBtn.prop('disabled', true); } } // 导入Excel文件 function importExcel() { const fileInput = $('#excelFile')[0]; const versionName = $('#versionName').val(); const versionDescription = $('#versionDescription').val(); if (!fileInput.files.length) { alert('请选择Excel文件'); return; } if (!versionName.trim()) { alert('请输入版本名称'); return; } const formData = new FormData(); formData.append('file', fileInput.files[0]); formData.append('version_name', versionName); formData.append('version_description', versionDescription); formData.append('admin_password', traitsAdminPassword); const importBtn = $('#importBtn'); const originalText = importBtn.html(); importBtn.prop('disabled', true).html('导入中...'); logTraitsMessage(`开始导入: ${fileInput.files[0].name}`); $.ajax({ url: '/admin/traits/import', method: 'POST', data: formData, processData: false, contentType: false, timeout: 60000 }) .done(function(response) { if (response.success) { logTraitsMessage(`导入成功: 版本"${versionName}"已创建,包含${response.data_count}条数据`); $('#versionName').val(''); $('#versionDescription').val(''); $('#excelFile').val(''); updateImportButton(); loadVersions(); alert('Excel文件导入成功!'); } else { logTraitsMessage(`导入失败: ${response.error}`); alert('导入失败:' + response.error); } }) .fail(function(xhr) { const errorMessage = xhr.responseJSON ? xhr.responseJSON.error : '网络错误'; logTraitsMessage(`导入失败: ${errorMessage}`); alert('导入失败:' + errorMessage); }) .finally(function() { importBtn.prop('disabled', false).html(originalText); }); } // 导出Excel文件 function exportExcel() { const versionId = $('#exportVersionSelect').val(); const exportData = { admin_password: traitsAdminPassword }; if (versionId) { exportData.version_id = versionId; } logTraitsMessage('开始导出数据...'); $.ajax({ url: '/admin/traits/export', method: 'POST', data: JSON.stringify(exportData), contentType: 'application/json', timeout: 30000 }) .done(function(response) { if (response.success) { logTraitsMessage(`导出成功: ${response.filename || '特征数据.xlsx'}`); alert('导出成功!文件已保存到服务器doc目录'); } else { logTraitsMessage(`导出失败: ${response.error}`); alert('导出失败:' + response.error); } }) .fail(function() { logTraitsMessage('导出失败: 网络错误'); alert('导出失败:网络错误'); }); } // 加载版本列表 function loadVersions() { $.ajax({ url: '/admin/traits/versions', method: 'POST', data: { admin_password: traitsAdminPassword }, timeout: 10000 }) .done(function(response) { if (response.success) { traitsVersions = response.data; renderVersionsList(); updateExportVersionOptions(); } }); } function renderVersionsList() { const container = $('#versionsList'); if (!traitsVersions.length) { container.html('
    暂无版本数据
    '); return; } let html = ''; traitsVersions.forEach(version => { const isActive = version.is_active; const activeClass = isActive ? ' bg-light border-primary' : ''; const activeTag = isActive ? '活跃' : ''; html += `
    ${version.version_name}${activeTag}

    ${version.description || '无描述'}

    ${version.created_at} ${version.data_count}条数据
    ${!isActive ? `` : ''} ${!isActive ? `` : ''}
    `; }); container.html(html); } function updateExportVersionOptions() { const select = $('#exportVersionSelect'); select.empty().append(''); traitsVersions.forEach(version => { const activeText = version.is_active ? ' (当前活跃)' : ''; select.append(``); }); } // 切换版本 function switchVersion(versionId) { if (!confirm('确定要切换到此版本吗?这将影响特征识别功能。')) { return; } $.ajax({ url: '/admin/traits/switch_version', method: 'POST', data: { version_id: versionId, admin_password: traitsAdminPassword }, timeout: 10000 }) .done(function(response) { if (response.success) { logTraitsMessage(`版本切换成功: ${response.message}`); loadVersions(); alert('版本切换成功!'); } else { alert('切换失败:' + response.error); } }) .fail(function() { alert('切换失败:网络错误'); }); } // 删除版本 function deleteVersion(versionId) { if (!confirm('确定要删除此版本吗?此操作不可恢复!')) { return; } $.ajax({ url: '/admin/traits/delete_version', method: 'POST', data: { version_id: versionId, admin_password: traitsAdminPassword }, timeout: 10000 }) .done(function(response) { if (response.success) { logTraitsMessage(`版本删除成功: ${response.message}`); loadVersions(); alert('版本删除成功!'); } else { alert('删除失败:' + response.error); } }) .fail(function() { alert('删除失败:网络错误'); }); } // 备份当前版本 function backupCurrentVersion() { const backupName = prompt('请输入备份名称:'); if (!backupName) return; $.ajax({ url: '/admin/traits/backup', method: 'POST', data: { backup_name: backupName, admin_password: traitsAdminPassword }, timeout: 30000 }) .done(function(response) { if (response.success) { logTraitsMessage(`备份成功: ${response.backup_name}`); loadVersions(); alert('备份成功!'); } else { alert('备份失败:' + response.error); } }) .fail(function() { alert('备份失败:网络错误'); }); } // 刷新版本列表 function refreshVersions() { loadVersions(); logTraitsMessage('版本列表已刷新'); } // 清空日志 function clearTraitsLog() { $('#traitsLogContainer').empty(); } // 记录日志消息 function logTraitsMessage(message) { const logContainer = $('#traitsLogContainer'); const timestamp = new Date().toLocaleTimeString(); const logEntry = `
    [${timestamp}] ${message}
    `; logContainer.append(logEntry); logContainer.scrollTop(logContainer[0].scrollHeight); } // ================================ 移动端功能 ================================ // 设置移动端事件监听器 function setupMobileEventListeners() { console.log('设置移动端事件监听器...'); // 移动端菜单切换 $('#mobileMenuBtn').click(function() { $('#mobileSearchCollapse').collapse('toggle'); }); // 移动端自动刷新同步 $('#mobileAutoRefresh').change(function() { const isChecked = $(this).is(':checked'); $('#autoRefresh').prop('checked', isChecked); $('#autoRefresh').trigger('change'); }); // 移动端刷新按钮 $('#mobileRefreshBtn').click(function() { // 停止自动刷新并取消勾选 if ($('#autoRefresh').is(':checked')) { $('#autoRefresh').prop('checked', false); $('#mobileAutoRefresh').prop('checked', false); stopAutoRefresh(); } refreshAllData(); }); // 移动端分析按钮 $('#mobileAnalysisBtn').click(function() { showAnalysisModal(); }); // 移动端设置按钮(已移除) // $('#mobileSettingsBtn').click(function() { // $('#settingsModal').modal('show'); // $('#mobileSearchCollapse').collapse('hide'); // }); // 移动端搜索功能 $('#mobileSearchBtn').click(function() { performMobileSearch(); }); $('#mobileClearSearchBtn').click(function() { clearMobileSearch(); }); // 移动端搜索输入框回车事件 $('#mobileSearchInput').keypress(function(e) { if (e.which === 13) { performMobileSearch(); } }); // 移动端排序方向切换 $('#mobileSortOrderBtn').click(function() { toggleMobileSortOrder(); }); // 移动端搜索状态和排序改变时 $('#mobileSearchStatus, #mobileSearchSortBy').change(function() { if (isSearchMode) { performMobileSearch(); } }); // 移动端标签页切换事件 $('#mobileGoodsTabs button').on('shown.bs.tab', function (e) { const targetTab = $(this).data('bs-target'); console.log('移动端标签页切换:', targetTab); // 根据切换的标签页更新对应的商品列表 if (targetTab === '#mobile-on-sale') { syncMobileOnSaleList(); } else if (targetTab === '#mobile-sold') { syncMobileSoldList(); } }); // 窗口大小变化时同步数据 $(window).resize(debounce(function() { syncMobileDesktopData(); }, 300)); console.log('移动端事件监听器设置完成'); } // 移动端搜索功能 function performMobileSearch() { const keyword = $('#mobileSearchInput').val().trim(); const status = $('#mobileSearchStatus').val(); const sortBy = $('#mobileSearchSortBy').val(); // 验证搜索输入(SQL注入防护) const validationResult = validateSearchInput(keyword); if (!validationResult.valid) { showError(validationResult.message); return; } // 同步到桌面端搜索 $('#searchInput').val(keyword); $('#searchStatus').val(status); $('#searchSortBy').val(sortBy); // 执行搜索 performSearch(); } // 清空移动端搜索 function clearMobileSearch() { $('#mobileSearchInput').val(''); $('#mobileSearchStatus').val(''); $('#mobileSearchSortBy').val('time'); $('#mobileSortOrderIcon').removeClass('fa-sort-amount-up').addClass('fa-sort-amount-down'); // 同步到桌面端并清空搜索 clearSearch(); } // 切换移动端排序方向 function toggleMobileSortOrder() { currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc'; updateMobileSortOrderIcon(); // 同步到桌面端 updateSortOrderIcon(); if ($('#mobileSearchInput').val().trim()) { performMobileSearch(); } } // 更新移动端排序图标 function updateMobileSortOrderIcon() { const icon = $('#mobileSortOrderIcon'); icon.removeClass('fa-sort-amount-up fa-sort-amount-down'); icon.addClass(currentSortOrder === 'desc' ? 'fa-sort-amount-down' : 'fa-sort-amount-up'); } // 同步移动端在售商品列表 function syncMobileOnSaleList() { const desktopList = $('#onSaleList'); const mobileList = $('#mobileOnSaleList'); // 复制桌面端内容到移动端 mobileList.html(desktopList.html()); // 同步分页 const desktopPagination = $('#onSalePagination'); const mobilePagination = $('#mobileOnSalePagination'); mobilePagination.html(desktopPagination.html()); // 更新计数 const total = $('#onSaleTotal').text(); $('#mobileOnSaleCount').text(total.replace('总计: ', '')); } // 同步移动端已售商品列表 function syncMobileSoldList() { const desktopList = $('#soldList'); const mobileList = $('#mobileSoldList'); // 复制桌面端内容到移动端 mobileList.html(desktopList.html()); // 同步分页 const desktopPagination = $('#soldPagination'); const mobilePagination = $('#mobileSoldPagination'); mobilePagination.html(desktopPagination.html()); // 更新计数 const total = $('#soldTotal').text(); $('#mobileSoldCount').text(total.replace('总计: ', '')); } // 同步移动端和桌面端数据 function syncMobileDesktopData() { // 同步统计数据 $('#mobileOnSaleCount').text($('#onSaleCount').text()); $('#mobileSoldCount').text($('#soldCount').text()); // 同步自动刷新状态 const autoRefreshChecked = $('#autoRefresh').is(':checked'); $('#mobileAutoRefresh').prop('checked', autoRefreshChecked); // 同步监控状态 const monitorStatus = $('#monitorStatus').text(); $('#mobileMonitorStatus').text(monitorStatus.replace('监控状态: ', '')); // 同步上次更新时间 const onSaleUpdate = $('#onSaleLastUpdate').text(); $('#mobileOnSaleLastUpdate').text(onSaleUpdate.replace('上次更新: ', '')); const soldUpdate = $('#soldLastUpdate').text(); $('#mobileSoldLastUpdate').text(soldUpdate.replace('上次更新: ', '')); // 同步同步状态 const onSaleSync = $('#onSaleSyncStatus').text(); $('#mobileOnSaleSyncStatus').text(onSaleSync); const soldSync = $('#soldSyncStatus').text(); $('#mobileSoldSyncStatus').text(soldSync); // 同步地板价、涨跌幅和在售总数(防止遗漏) const floorPrice = $('#floorPrice').text(); if (floorPrice && floorPrice !== '¥--') { $('#mobileFloorPrice').text(floorPrice); } const trendHtml = $('#trendPercentage').html(); if (trendHtml) { $('#mobileTrendPercentage').html(trendHtml); } const onSaleTotal = $('#onSaleTotal').text(); if (onSaleTotal) { $('#mobileOnSaleTotal').text(onSaleTotal); } } // 防抖函数 function debounce(func, wait, immediate) { let timeout; return function executedFunction() { const context = this; const args = arguments; const later = function() { timeout = null; if (!immediate) func.apply(context, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // ====== 数据校验功能 ====== // 校验状态变量 let validationInProgress = false; let validationStatusInterval = null; let marketDataInterval = null; // 初始化校验功能 // 更新市场数据 function updateMarketData() { // 检查管理员密码 const password = $('#adminPassword').val(); if (!password) { showError('请先在管理员功能页面输入密码'); return; } const btn = $('#updateMarketDataBtn'); const originalText = btn.text(); btn.prop('disabled', true).text('更新中...'); $.ajax({ url: '/api/market/update', method: 'POST', data: JSON.stringify({ admin_password: password }), contentType: 'application/json', success: function(response) { if (response.success) { showMessage('市场数据已更新', 'success'); loadMarketData(); // 重新加载市场数据 } else { showError('市场数据更新失败: ' + (response.error || '未知错误')); } }, error: function(xhr, status, error) { console.error('市场数据更新失败:', error); showError('市场数据更新失败: ' + error); }, complete: function() { btn.prop('disabled', false).text(originalText); } }); } // 获取校验类型的中文显示名称 function getValidationTypeText(type) { const typeMap = { 'manual': '手动数据校验', 'auto': '自动数据校验', 'active_consistency': '在售商品一致性校验', 'sold_integrity': '已售数据完整性校验' }; return typeMap[type] || type; } // 加载市场数据 function loadMarketData() { return $.ajax({ url: '/api/market/data', method: 'GET', timeout: 10000, // 10秒超时 cache: true, // ✓ 启用缓存 headers: { 'Cache-Control': 'max-age=60' // 市场数据缓存60秒 } }).done(function(response) { if (response.success && response.data) { displayMarketData(response.data); } else { console.error('行情数据API错误:', response.error); } }).fail(function(xhr, status, error) { console.error('加载行情数据失败:', status, error); if (status === 'timeout') { console.warn('行情数据加载超时,将显示默认值'); } }); } // 全局变量存储当前地板价 let currentFloorPrice = 0; // 显示市场数据 function displayMarketData(data) { console.log('更新行情数据:', data); // 更新地板价(PC端和手机端同步) if (data.floor_price !== undefined && data.floor_price > 0) { currentFloorPrice = data.floor_price; $('#floorPrice').text(`¥${data.floor_price.toFixed(0)}`); $('#mobileFloorPrice').text(`¥${data.floor_price.toFixed(0)}`); } else { currentFloorPrice = 0; $('#floorPrice').text('¥--'); $('#mobileFloorPrice').text('¥--'); } // 更新涨跌幅(PC端和手机端同步) if (data.trend_percentage !== undefined) { const trend = data.trend_percentage; const trendElement = $('#trendPercentage'); const mobileTrendElement = $('#mobileTrendPercentage'); // 清除之前的样式 trendElement.find('span').removeClass('trend-up trend-down trend-neutral'); mobileTrendElement.find('span').removeClass('trend-up trend-down trend-neutral'); let trendHtml; if (trend > 0) { trendHtml = `+${trend.toFixed(2)}%`; } else if (trend < 0) { trendHtml = `${trend.toFixed(2)}%`; } else { trendHtml = `0.00%`; } trendElement.html(trendHtml); mobileTrendElement.html(trendHtml); } else { $('#trendPercentage').html(`--`); $('#mobileTrendPercentage').html(`--`); } // 更新在售总数(PC端和手机端同步,使用API数据) if (data.total_listings !== undefined && data.total_listings > 0) { $('#onSaleTotal').text(`总计: ${data.total_listings}`); $('#mobileOnSaleTotal').text(`总计: ${data.total_listings}`); } // 当地板价更新时,重新计算所有在售商品的溢价率 updateAllPremiumRates(); } // 计算并显示指定商品列表的溢价率 function calculateAndDisplayPremiumRates(goods) { // console.debug(`溢价率计算开始`); if (!goods || goods.length === 0) { // console.debug('商品列表为空,跳过溢价率计算'); return; } // 检查地板价是否有效,如果无效则先加载地板价再计算 if (!currentFloorPrice || currentFloorPrice <= 0) { console.log('地板价无效,先加载地板价数据'); loadMarketData().then(() => { // console.debug(`地板价加载完成,重新计算溢价率`); // 地板价加载完成后重新计算 calculateAndDisplayPremiumRatesSync(goods); }).catch((error) => { console.error('加载地板价失败,使用默认显示:', error); calculateAndDisplayPremiumRatesSync(goods); }); return; } // 直接计算溢价率 // console.debug('地板价有效,直接计算溢价率'); calculateAndDisplayPremiumRatesSync(goods); } // 同步计算并显示溢价率(内部函数) function calculateAndDisplayPremiumRatesSync(goods) { if (!goods || goods.length === 0) { // console.debug('同步计算溢价率: 商品列表为空'); return; } // console.debug(`开始同步计算溢价率`); let calculatedCount = 0; let foundElementCount = 0; // 同时更新PC端和移动端的溢价率 $('#onSaleList .premium-rate, #mobileOnSaleList .premium-rate').each(function(index) { const $premiumElement = $(this); const $priceElement = $premiumElement.closest('.goods-price'); const priceText = $priceElement.text(); const priceMatch = priceText.match(/¥(\d+(?:\.\d+)?)/); if (priceMatch) { const price = parseFloat(priceMatch[1]); // 在商品列表中查找匹配的价格 const matchingItem = goods.find(item => parseFloat(item.price) === price ); if (matchingItem) { foundElementCount++; const premiumRate = calculatePremiumRate(price, currentFloorPrice); updatePremiumRateDisplay($premiumElement, premiumRate); calculatedCount++; // 只在值发生变化时输出日志,并限制日志数量 if (calculatedCount <= 3) { // console.debug(`溢价率元素${index + 1} 溢价率: ${premiumRate !== null ? premiumRate.toFixed(1) + '%' : '--'}`); } } } }); if (calculatedCount > 0) { // console.debug(`溢价率计算完成`); } } // 更新所有在售商品的溢价率(当地板价变化时调用) function updateAllPremiumRates() { // 确保地板价有效 if (!currentFloorPrice || currentFloorPrice <= 0) { // console.debug('地板价无效,跳过溢价率更新'); return; } // 同时更新PC端和移动端的溢价率 $('#onSaleList .premium-rate, #mobileOnSaleList .premium-rate').each(function() { const $this = $(this); const priceText = $this.closest('.goods-price').text(); const priceMatch = priceText.match(/¥(\d+(?:\.\d+)?)/); if (priceMatch) { const price = parseFloat(priceMatch[1]); const premiumRate = calculatePremiumRate(price, currentFloorPrice); updatePremiumRateDisplay($this, premiumRate); } }); } // 计算溢价率:(当前价格 / 地板价 - 1) * 100 function calculatePremiumRate(currentPrice, floorPrice) { if (!floorPrice || floorPrice <= 0 || !currentPrice || currentPrice <= 0) { return null; } return ((currentPrice / floorPrice - 1) * 100); } // 更新溢价率显示(增量更新,避免不必要的闪动) function updatePremiumRateDisplay(element, premiumRate) { // 获取当前显示的内容 const currentText = element.text().trim(); const currentClasses = element.attr('class') || ''; let newText, newClass; if (premiumRate === null) { newText = '--'; newClass = 'premium-rate premium-unknown'; } else { newText = premiumRate >= 0 ? `+${premiumRate.toFixed(1)}%` : `${premiumRate.toFixed(1)}%`; // 根据溢价率确定样式类 let styleClass; if (premiumRate <= 5) { styleClass = 'premium-floor'; // 接近地板价 (≤5%) } else if (premiumRate <= 20) { styleClass = 'premium-low'; // 低溢价 (5-20%) } else if (premiumRate <= 50) { styleClass = 'premium-medium'; // 中溢价 (20-50%) } else { styleClass = 'premium-high'; // 高溢价 (>50%) } newClass = `premium-rate ${styleClass}`; } // 只在内容或样式真正改变时才更新DOM if (currentText !== newText || !currentClasses.includes(newClass.split(' ')[1])) { element.text(newText); element.removeClass('premium-high premium-medium premium-low premium-floor premium-unknown'); // 添加新样式 if (premiumRate === null) { element.addClass('premium-unknown'); } else if (premiumRate <= 5) { element.addClass('premium-floor'); } else if (premiumRate <= 20) { element.addClass('premium-low'); } else if (premiumRate <= 50) { element.addClass('premium-medium'); } else { element.addClass('premium-high'); } } } // 显示校验结果 function displayValidationResult(result) { if (!result) return; let resultHtml = '
    '; resultHtml += '
    校验结果:
    '; if (result.checked_items !== undefined) { resultHtml += `

    检查项目: ${result.checked_items}

    `; } if (result.updated_items !== undefined) { resultHtml += `

    更新项目: ${result.updated_items}

    `; } if (result.errors !== undefined && result.errors > 0) { resultHtml += `

    错误: ${result.errors}

    `; } if (result.details) { resultHtml += `
    ${result.details}
    `; } resultHtml += '
    '; // 显示结果(可以添加到消息区域或专门的结果区域) showMessage(resultHtml, 'info', 10000); // 显示10秒 } // 设置校验相关的定期更新 // 停止校验相关的定期更新 function stopValidationUpdates() { if (validationStatusInterval) { clearInterval(validationStatusInterval); validationStatusInterval = null; } if (marketDataInterval) { clearInterval(marketDataInterval); marketDataInterval = null; } console.log('校验定期更新已停止'); } // =========================================== // 管理员功能模块 // =========================================== // 管理员状态管理 let isAdminAuthenticated = false; // 初始化管理员功能 function initAdminFunctions() { console.log('初始化管理员功能...'); // 绑定密码验证按钮事件 $('#verifyAdminPasswordBtn').on('click', function() { verifyAdminPassword(); }); // 绑定管理员密码输入框回车事件 $('#adminPassword').on('keypress', function(e) { if (e.which === 13) { // 回车键 verifyAdminPassword(); } }); // 绑定密码显示/隐藏按钮 $('#toggleAdminPassword').on('click', function() { togglePasswordVisibility('adminPassword', 'toggleAdminPassword'); }); // 绑定退出登录按钮 $('#logoutAdminBtn').on('click', function() { logoutAdmin(); }); console.log('管理员功能初始化完成'); } // 验证管理员密码 function verifyAdminPassword() { const password = $('#adminPassword').val(); if (!password) { showAdminPasswordStatus('请输入管理员密码', 'danger'); return; } console.log('正在验证管理员密码...'); showAdminPasswordStatus('正在验证密码...', 'info'); // 禁用验证按钮防止重复提交 $('#verifyAdminPasswordBtn').prop('disabled', true); // 通过校验API测试密码 $.ajax({ url: '/api/validation/manual', method: 'POST', data: JSON.stringify({ admin_password: password, test_auth: true }), contentType: 'application/json', success: function(response) { if (response.success || response.error === '无效的请求参数') { loginAdmin(); showAdminPasswordStatus('密码验证成功!', 'success'); } else { showAdminPasswordStatus('密码验证失败', 'danger'); $('#adminPassword').val('').focus(); } $('#verifyAdminPasswordBtn').prop('disabled', false); }, error: function(xhr) { if (xhr.status === 403) { showAdminPasswordStatus('管理员密码错误', 'danger'); $('#adminPassword').val('').focus(); } else { showAdminPasswordStatus('网络错误,请稍后重试', 'danger'); } $('#verifyAdminPasswordBtn').prop('disabled', false); } }); } // 管理员登录成功 function loginAdmin() { isAdminAuthenticated = true; // 隐藏密码验证面板 $('#adminPasswordPanel').fadeOut(300); // 显示管理员功能面板 setTimeout(() => { $('#adminFunctionsPanel').fadeIn(300); }, 300); // 清空密码字段 $('#adminPassword').val(''); console.log('管理员登录成功'); } // 管理员退出登录 function logoutAdmin() { isAdminAuthenticated = false; // 隐藏管理员功能面板 $('#adminFunctionsPanel').fadeOut(300); // 显示密码验证面板 setTimeout(() => { $('#adminPasswordPanel').fadeIn(300); $('#adminPasswordStatus').hide(); }, 300); console.log('管理员已退出登录'); } // 显示管理员密码验证状态 function showAdminPasswordStatus(message, type) { const statusDiv = $('#adminPasswordStatus'); const alertClass = { 'success': 'alert-success', 'danger': 'alert-danger', 'warning': 'alert-warning', 'info': 'alert-info' }[type] || 'alert-info'; statusDiv.html(` `).show(); // 自动隐藏成功和错误消息 if (type === 'success' || type === 'danger') { setTimeout(() => { statusDiv.fadeOut(); }, 3000); } } // 切换密码显示/隐藏 function togglePasswordVisibility(passwordFieldId, toggleButtonId) { const passwordField = $('#' + passwordFieldId); const toggleButton = $('#' + toggleButtonId); const icon = toggleButton.find('i'); if (passwordField.attr('type') === 'password') { passwordField.attr('type', 'text'); icon.removeClass('fa-eye').addClass('fa-eye-slash'); } else { passwordField.attr('type', 'password'); icon.removeClass('fa-eye-slash').addClass('fa-eye'); } } // ========== Phase 2优化:图片懒加载实现 ========== /** * 图片懒加载功能 * 使用Intersection Observer API实现 * 只加载可视区域内的图片,节省流量和提高加载速度 */ // 全局懒加载观察器 let lazyloadObserver = null; // 初始化懒加载观察器 function initLazyLoad() { // 检查浏览器是否支持Intersection Observer if ('IntersectionObserver' in window) { // 创建观察器实例 lazyloadObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { // 当图片进入可视区域时加载 if (entry.isIntersecting) { const img = entry.target; const src = img.getAttribute('data-src'); if (src) { // 检查图片是否已在缓存中 if (imageCache.has(src)) { // 已缓存,直接设置src(浏览器会从缓存加载) img.src = src; img.classList.remove('lazyload'); observer.unobserve(img); } else { // 未缓存,加载并添加到缓存 img.onload = function() { imageCache.add(src); }; img.src = src; img.classList.remove('lazyload'); observer.unobserve(img); } } } }); }, { // 配置选项 rootMargin: '100px', // 提前100px开始加载(优化体验) threshold: 0.01 // 至少1%可见时触发 }); console.log('✓ 图片懒加载已启用(带缓存优化)'); } else { // 不支持Intersection Observer时的降级方案 console.warn('浏览器不支持Intersection Observer,使用降级方案'); // 直接加载所有图片 loadAllImagesImmediately(); } } // 观察所有懒加载图片 function observeLazyImages() { if (!lazyloadObserver) { initLazyLoad(); } // 查找所有带lazyload类的图片 const lazyImages = document.querySelectorAll('img.lazyload'); if (lazyloadObserver) { lazyImages.forEach(function(img) { lazyloadObserver.observe(img); }); if (lazyImages.length > 0) { console.log(`正在观察 ${lazyImages.length} 张图片的懒加载`); } } } // 降级方案:直接加载所有图片 function loadAllImagesImmediately() { const lazyImages = document.querySelectorAll('img.lazyload'); lazyImages.forEach(function(img) { const src = img.getAttribute('data-src'); if (src) { img.src = src; img.classList.remove('lazyload'); } }); } // 在商品列表更新后调用懒加载 function triggerLazyLoad() { // 使用setTimeout确保DOM已更新 setTimeout(function() { observeLazyImages(); }, 100); } // 页面加载时初始化懒加载 $(document).ready(function() { initLazyLoad(); }); console.debug('✓ Phase 2优化已加载:API缓存 + 图片懒加载');