const Dashboard = (() => { const truckIcon = `
`; const metricsIcons = { '直通良品数': '', '良品率': '', '发货数量': truckIcon, '不良数量': '', '良品/不良率': '', '今日产量': '' }; // 合并良品率/不良率卡片 function rateCard(goodRate, badRate) { const icon = metricsIcons['良品/不良率']; const goodNum = parseFloat(String(goodRate).replace('%', '')) || 0; const badNum = parseFloat(String(badRate).replace('%', '')) || 0; return `
${icon}

良品/不良率

${goodRate}/${badRate}

`; } // 今日产量卡片(拼多多/圆通自动切换) function todayProductionCard(todayPdd, todayYt, activePlatform) { const isPdd = activePlatform === 'pdd'; const platformName = isPdd ? '拼多多' : '圆通'; const platformIcon = isPdd ? '' : ''; const value = isPdd ? todayPdd : todayYt; const color = isPdd ? { bg: '#e02e24', text: '#e02e24' } : { bg: '#1a6dd6', text: '#1a6dd6' }; const fillWidth = Math.min((value / 10000) * 100, 100); return `
${platformIcon}

今日${platformName}

${value}

`; } function metricsCard(title, value, badgeClass) { const colors = { success: { bg: '#10B981', text: '#02972f' }, warning: { bg: '#F59E0B', text: '#B45309' }, danger: { bg: '#EF4444', text: '#DC2626' } }; const color = colors[badgeClass] || colors.success; const isPercent = String(value).includes('%'); const numValue = parseFloat(String(value).replace('%', '')) || 0; const fillWidth = isPercent ? Math.min(numValue, 100) : Math.min((numValue / 100000) * 100, 100); const icon = metricsIcons[title] || metricsIcons['良品率']; const isTruck = title === '发货数量'; const iconBg = isTruck ? '#f5f5f5' : color.bg; return `
${icon}

${title}

${value}

`; } // 清理函数 const cleanup = () => { if(window.__auditTimer){ clearInterval(window.__auditTimer); window.__auditTimer = null; } // 取消未完成的请求 if(window.__auditAbortController){ window.__auditAbortController.abort(); window.__auditAbortController = null; } // 清理Canvas事件监听器 const canvas = document.getElementById('trend-chart'); if(canvas){ canvas.onmousemove = null; canvas.onmouseleave = null; } // 清理日期选择器事件 const auditDateEl = document.getElementById('audit-date'); const platformSelect = document.getElementById('audit-platform-select'); const pddDateEl = document.getElementById('audit-date-pdd'); const ytDateEl = document.getElementById('audit-date-yt'); if(auditDateEl) auditDateEl.onchange = null; if(platformSelect) platformSelect.onchange = null; if(pddDateEl) pddDateEl.onchange = null; if(ytDateEl) ytDateEl.onchange = null; // 清理resize监听器 if(window.__dashboardResizeHandler){ window.removeEventListener('resize', window.__dashboardResizeHandler); window.__dashboardResizeHandler = null; } // 清理主题切换监听器 if(window.__dashboardThemeHandler){ window.removeEventListener('themeChanged', window.__dashboardThemeHandler); window.__dashboardThemeHandler = null; } // 清理重绘函数引用 window.__redrawTrendChart = null; // 清理全局变量 window.__auditBusy = false; window.__pddParams = null; window.__ytParams = null; }; Router.onBeforeEach((path)=>{ if(path !== '/dashboard'){ cleanup(); } }); async function render() { const [dRes,pRes,yRes] = await Promise.allSettled([ API.dashboard(), API.auditPddQuiet(), API.auditYtQuiet() ]); const data = dRes.status==='fulfilled' ? dRes.value : { goodRate: '—', shipments: '—', defects: '—', badCount: '—' }; const pdd = pRes.status==='fulfilled' ? pRes.value : { list: [] }; const yt = yRes.status==='fulfilled' ? yRes.value : { list: [] }; const pddList = (pdd.list||[]).map(r=>`
  • ${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • `).join('')||'
  • 暂无数据
  • '; const ytList = (yt.list||[]).map(r=>`
  • ${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • `).join('')||'
  • 暂无数据
  • '; setTimeout(()=>{ // 清理旧的定时器和事件 if(window.__auditTimer){ clearInterval(window.__auditTimer); window.__auditTimer = null; } // 清理旧的事件监听器 const oldCanvas = document.getElementById('trend-chart'); if(oldCanvas){ oldCanvas.onmousemove = null; oldCanvas.onmouseleave = null; } // 动态计算审计框高度 const calculateAuditHeight = () => { const viewEl = document.getElementById('view'); if(!viewEl) return 460; const viewHeight = viewEl.clientHeight; const viewPadding = 40; // view的padding (20px * 2) // 获取其他元素的高度 const metricsEl = document.querySelector('.dashboard-metrics-4col'); const trendEl = document.querySelector('.card'); const metricsHeight = metricsEl ? metricsEl.offsetHeight : 60; const trendHeight = trendEl ? trendEl.offsetHeight : 260; const gaps = 24; // margin-top间距 (12px * 2) // 计算剩余高度 const remainingHeight = viewHeight - viewPadding - metricsHeight - trendHeight - gaps; // 最小高度300px,最大不超过剩余空间 return Math.max(300, Math.min(remainingHeight, 460)); }; // 设置审计框高度 const setAuditHeight = () => { const height = calculateAuditHeight(); const auditCard = document.getElementById('audit-card'); const pddCard = document.getElementById('audit-pdd-card'); const ytCard = document.getElementById('audit-yt-card'); if(auditCard) auditCard.style.height = height + 'px'; if(pddCard) pddCard.style.height = height + 'px'; if(ytCard) ytCard.style.height = height + 'px'; }; // 初始设置高度 setAuditHeight(); // 监听窗口大小变化 if(window.__dashboardResizeHandler){ window.removeEventListener('resize', window.__dashboardResizeHandler); } window.__dashboardResizeHandler = () => { setAuditHeight(); // 重绘趋势图 if(window.__redrawTrendChart) window.__redrawTrendChart(); }; window.addEventListener('resize', window.__dashboardResizeHandler); window.__pddParams = window.__pddParams || {}; window.__ytParams = window.__ytParams || {}; window.__auditBusy=false; const toEpoch=(s)=>{try{if(!s)return null;let t=s.trim(); if(/Z$/.test(t) || /[\+\-]\d{2}:\d{2}$/.test(t)) return Date.parse(t); t=t.replace(/\//g,'-'); if(t.includes('T')){ if(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(t)) t = t + ':00'; t = t + (function(){const offMin=-new Date().getTimezoneOffset();const sign=offMin>=0?'+':'-';const hh=String(Math.floor(Math.abs(offMin)/60)).padStart(2,'0');const mm=String(Math.abs(offMin)%60).padStart(2,'0');return sign+hh+':'+mm;})() }else{ if(/^\d{4}-\d{2}-\d{2}$/.test(t)) t = t + ' 00:00:00'; if(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(t)) t = t + ':00'; t = t.replace(' ', 'T') + (function(){const offMin=-new Date().getTimezoneOffset();const sign=offMin>=0?'+':'-';const hh=String(Math.floor(Math.abs(offMin)/60)).padStart(2,'0');const mm=String(Math.abs(offMin)%60).padStart(2,'0');return sign+hh+':'+mm;})() } return Date.parse(t); }catch(e){return null}}; // 获取当前主题的颜色 const getThemeColors = () => { const isLight = document.documentElement.getAttribute('data-theme') === 'light'; return { bg: isLight ? '#f8f9fb' : '#0c0f14', grid: isLight ? '#e5e7eb' : '#1a1f28', text: isLight ? '#6b7280' : '#6b7280', textLabel: isLight ? '#1a1d23' : '#e5e7eb' }; }; // 当前时间维度:day, week, month window.__trendTimeRange = window.__trendTimeRange || 'day'; // 绘制趋势图 let chartData = null; const drawTrendChart = (pddData, ytData) => { const canvas = document.getElementById('trend-chart'); if(!canvas) return; const ctx = canvas.getContext('2d'); const rect = canvas.parentElement.getBoundingClientRect(); const colors = getThemeColors(); const timeRange = window.__trendTimeRange || 'day'; // 处理高分辨率屏幕,避免字体模糊 const dpr = window.devicePixelRatio || 1; canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; canvas.style.width = rect.width + 'px'; canvas.style.height = rect.height + 'px'; ctx.scale(dpr, dpr); // 根据时间维度生成时间点 const getTimePoints = () => { const points = []; const now = new Date(); if(timeRange === 'day') { // 最近30天 for(let i = 29; i >= 0; i--){ const d = new Date(now); d.setDate(d.getDate() - i); points.push(d.toISOString().split('T')[0]); } } else if(timeRange === 'week') { // 最近12周 for(let i = 11; i >= 0; i--){ const d = new Date(now); d.setDate(d.getDate() - i * 7); const weekStart = new Date(d); weekStart.setDate(d.getDate() - d.getDay() + 1); // 周一 points.push(weekStart.toISOString().split('T')[0]); } } else { // 最近12个月 for(let i = 11; i >= 0; i--){ const d = new Date(now); d.setMonth(d.getMonth() - i); points.push(d.toISOString().slice(0, 7)); // YYYY-MM } } return points; }; const timePoints = getTimePoints(); // 统计数据(去重MAC地址) const countByTime = (list) => { const uniqueMacs = {}; timePoints.forEach(t => uniqueMacs[t] = new Set()); (list||[]).forEach(r => { if(r.ts_cn && r.mac){ const date = r.ts_cn.split(' ')[0]; if(timeRange === 'day') { if(uniqueMacs[date] !== undefined) { uniqueMacs[date].add(r.mac); } } else if(timeRange === 'week') { // 找到对应的周 const recordDate = new Date(date); for(let i = 0; i < timePoints.length; i++){ const weekStart = new Date(timePoints[i]); const weekEnd = new Date(weekStart); weekEnd.setDate(weekEnd.getDate() + 6); if(recordDate >= weekStart && recordDate <= weekEnd){ uniqueMacs[timePoints[i]].add(r.mac); break; } } } else { // 月份 const month = date.slice(0, 7); if(uniqueMacs[month] !== undefined) { uniqueMacs[month].add(r.mac); } } } }); return timePoints.map(t => uniqueMacs[t].size); }; const pddCounts = countByTime(pddData); const ytCounts = countByTime(ytData); const maxCount = Math.max(...pddCounts, ...ytCounts, 1); // 绘制参数(使用逻辑尺寸而非物理像素) // 根据最大值动态调整左边距,确保Y轴标签不被截断 const maxDigits = Math.max(maxCount.toString().length, 2); const dynamicLeftPadding = Math.max(50, 25 + maxDigits * 10); const padding = {left: dynamicLeftPadding, right: 20, top: 20, bottom: 30}; const chartWidth = rect.width - padding.left - padding.right; const chartHeight = rect.height - padding.top - padding.bottom; // 保存图表数据供鼠标事件使用 chartData = {timePoints, pddCounts, ytCounts, maxCount, padding, chartWidth, chartHeight, timeRange}; // 清空画布 ctx.fillStyle = colors.bg; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制网格线 ctx.strokeStyle = colors.grid; ctx.lineWidth = 1; for(let i = 0; i <= 4; i++){ const y = padding.top + (chartHeight / 4) * i; ctx.beginPath(); ctx.moveTo(padding.left, y); ctx.lineTo(rect.width - padding.right, y); ctx.stroke(); } // 绘制Y轴刻度 ctx.fillStyle = colors.text; ctx.font = '11px sans-serif'; ctx.textAlign = 'right'; for(let i = 0; i <= 4; i++){ const value = Math.round(maxCount * (4 - i) / 4); const y = padding.top + (chartHeight / 4) * i; ctx.fillText(value.toString(), padding.left - 8, y + 4); } // 绘制X轴标签 ctx.textAlign = 'center'; const labelInterval = timeRange === 'day' ? 5 : (timeRange === 'week' ? 2 : 2); timePoints.forEach((point, i) => { if(i % labelInterval === 0 || i === timePoints.length - 1){ const x = padding.left + (chartWidth / (timePoints.length - 1)) * i; let label; if(timeRange === 'day') { label = point.slice(5); // MM-DD } else if(timeRange === 'week') { label = point.slice(5); // MM-DD (周起始) } else { label = point.slice(2); // YY-MM } ctx.fillText(label, x, rect.height - 8); } }); // 绘制柔和曲线(智能避免标签重叠) const today = new Date().toISOString().split('T')[0]; // 收集今日标签位置 const todayLabels = []; const drawLineWithLabels = (counts, color, labelKey) => { ctx.strokeStyle = color; ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); const points = counts.map((count, i) => ({ x: padding.left + (chartWidth / (timePoints.length - 1)) * i, y: padding.top + chartHeight - (count / maxCount) * chartHeight })); ctx.moveTo(points[0].x, points[0].y); for(let i = 1; i < points.length; i++) { const prev = points[i - 1]; const curr = points[i]; const next = points[i + 1] || curr; const prevPrev = points[i - 2] || prev; const tension = 0.3; const cp1x = prev.x + (curr.x - prevPrev.x) * tension; const cp1y = prev.y + (curr.y - prevPrev.y) * tension; const cp2x = curr.x - (next.x - prev.x) * tension; const cp2y = curr.y - (next.y - prev.y) * tension; ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, curr.x, curr.y); } ctx.stroke(); // 绘制数据点并收集今日标签 counts.forEach((count, i) => { const x = padding.left + (chartWidth / (timePoints.length - 1)) * i; const y = padding.top + chartHeight - (count / maxCount) * chartHeight; const isToday = timeRange === 'day' && timePoints[i] === today; if(isToday) { ctx.beginPath(); ctx.arc(x, y, 10, 0, Math.PI * 2); ctx.fillStyle = color + '33'; ctx.fill(); ctx.beginPath(); ctx.arc(x, y, 6, 0, Math.PI * 2); ctx.fillStyle = color; ctx.fill(); ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); todayLabels.push({x, y, count, color, key: labelKey}); } else { ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fillStyle = color; ctx.fill(); } }); }; drawLineWithLabels(pddCounts, '#f59e0b', 'pdd'); drawLineWithLabels(ytCounts, '#3b82f6', 'yt'); // 智能绘制今日标签,显示在高亮点的左边(数字为0时不显示) const nonZeroLabels = todayLabels.filter(label => label.count > 0); if(nonZeroLabels.length === 2) { const [a, b] = nonZeroLabels; const yDiff = Math.abs(a.y - b.y); if(yDiff < 20) { // 两个标签太近,垂直错开显示在左边 const upper = a.y < b.y ? a : b; const lower = a.y < b.y ? b : a; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'right'; ctx.fillStyle = upper.color; ctx.fillText(upper.count.toString(), upper.x - 14, upper.y - 8); ctx.fillStyle = lower.color; ctx.fillText(lower.count.toString(), lower.x - 14, lower.y + 8); } else { // 正常显示在左边 nonZeroLabels.forEach(label => { ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'right'; ctx.fillStyle = label.color; ctx.fillText(label.count.toString(), label.x - 14, label.y + 4); }); } } else { nonZeroLabels.forEach(label => { ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'right'; ctx.fillStyle = label.color; ctx.fillText(label.count.toString(), label.x - 14, label.y + 4); }); } // 绘制图例 ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillStyle = '#f59e0b'; ctx.fillRect(padding.left, 5, 12, 12); ctx.fillStyle = colors.textLabel; ctx.fillText('拼多多', padding.left + 18, 15); ctx.fillStyle = '#3b82f6'; ctx.fillRect(padding.left + 80, 5, 12, 12); ctx.fillStyle = colors.textLabel; ctx.fillText('圆通', padding.left + 98, 15); }; // 初始化缓存数据 window.__auditCache = window.__auditCache || {pdd: [], yt: []}; if(pdd.list && pdd.list.length > 0) window.__auditCache.pdd = pdd.list; if(yt.list && yt.list.length > 0) window.__auditCache.yt = yt.list; // 初始绘制 drawTrendChart(pdd.list, yt.list); // 监听主题切换事件,立即重绘图表 const themeChangeHandler = () => { // 使用缓存数据或初始数据 const pddData = (window.__auditCache && window.__auditCache.pdd && window.__auditCache.pdd.length > 0) ? window.__auditCache.pdd : pdd.list; const ytData = (window.__auditCache && window.__auditCache.yt && window.__auditCache.yt.length > 0) ? window.__auditCache.yt : yt.list; drawTrendChart(pddData, ytData); }; window.addEventListener('themeChanged', themeChangeHandler); // 保存监听器引用以便清理 window.__dashboardThemeHandler = themeChangeHandler; // 保存重绘函数引用,供 resize 时调用 window.__redrawTrendChart = themeChangeHandler; // 添加鼠标悬停事件 const canvas = document.getElementById('trend-chart'); const tooltip = document.getElementById('chart-tooltip'); if(canvas && tooltip){ canvas.onmousemove = (e) => { if(!chartData) return; const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const mouseY = e.clientY - rect.top; const {timePoints, pddCounts, ytCounts, maxCount, padding, chartWidth, chartHeight, timeRange} = chartData; // 查找最近的数据点 let nearestIndex = -1; let minDist = 15; for(let i = 0; i < timePoints.length; i++){ const x = padding.left + (chartWidth / (timePoints.length - 1)) * i; const pddY = padding.top + chartHeight - (pddCounts[i] / maxCount) * chartHeight; const ytY = padding.top + chartHeight - (ytCounts[i] / maxCount) * chartHeight; const distPdd = Math.sqrt((mouseX - x) ** 2 + (mouseY - pddY) ** 2); const distYt = Math.sqrt((mouseX - x) ** 2 + (mouseY - ytY) ** 2); const dist = Math.min(distPdd, distYt); if(dist < minDist){ minDist = dist; nearestIndex = i; } } if(nearestIndex >= 0){ const point = timePoints[nearestIndex]; const pddCount = pddCounts[nearestIndex]; const ytCount = ytCounts[nearestIndex]; let label = point; if(timeRange === 'week') label = point + ' 周'; else if(timeRange === 'month') label = point + ' 月'; tooltip.innerHTML = `
    ${label}
    拼多多: ${pddCount}
    圆通: ${ytCount}
    `; tooltip.style.display = 'block'; // 获取提示框宽度以便放在鼠标左侧 const tooltipWidth = tooltip.offsetWidth || 100; tooltip.style.left = (e.clientX - rect.left - tooltipWidth - 10) + 'px'; tooltip.style.top = (e.clientY - rect.top - 10) + 'px'; } else { tooltip.style.display = 'none'; } }; canvas.onmouseleave = () => { tooltip.style.display = 'none'; }; } // 时间维度切换事件 const trendRangeSelect = document.getElementById('trend-range-select'); if(trendRangeSelect){ trendRangeSelect.onchange = () => { window.__trendTimeRange = trendRangeSelect.value; const pddData = window.__auditCache?.pdd || pdd.list; const ytData = window.__auditCache?.yt || yt.list; drawTrendChart(pddData, ytData); }; } // 刷新审计列表的函数 const refreshAuditLists = async() => { if(window.__auditBusy) return; window.__auditBusy=true; const pdd2=await API.auditPddQuiet(window.__pddParams).catch(()=>({list:[]})); const yt2=await API.auditYtQuiet(window.__ytParams).catch(()=>({list:[]})); const sP=toEpoch(window.__pddParams.start), eP=toEpoch(window.__pddParams.end); const sY=toEpoch(window.__ytParams.start), eY=toEpoch(window.__ytParams.end); const pddView=(pdd2.list||[]).filter(r=>{const t=toEpoch(r.ts_cn); if(t==null) return false; return (sP==null||t>=sP)&&(eP==null||t<=eP);}); const ytView=(yt2.list||[]).filter(r=>{const t=toEpoch(r.ts_cn); if(t==null) return false; return (sY==null||t>=sY)&&(eY==null||t<=eY);}); const pddEls=pddView.map(r=>`
  • ${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • `).join('')||'
  • 暂无数据
  • '; const ytEls=ytView.map(r=>`
  • ${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • `).join('')||'
  • 暂无数据
  • '; const p=document.getElementById('audit-pdd'); if(p) p.innerHTML=pddEls; const y=document.getElementById('audit-yt'); if(y) y.innerHTML=ytEls; window.__auditBusy=false; }; // 设置日期筛选事件 const q = id => document.getElementById(id); const dateToRange = d => { if(!d) return {}; return { start: d + ' 00:00:00', end: d + ' 23:59:59' }; }; // 整合的审计看板切换逻辑 const platformSelect = q('audit-platform-select'); const auditDateEl = q('audit-date'); const auditListEl = q('audit-list'); // 当前选中的平台 window.__currentAuditPlatform = 'pdd'; // 更新整合看板显示 const updateAuditDisplay = () => { const platform = window.__currentAuditPlatform; const params = platform === 'pdd' ? window.__pddParams : window.__ytParams; const sTime = toEpoch(params.start); const eTime = toEpoch(params.end); const cacheData = platform === 'pdd' ? window.__auditCache.pdd : window.__auditCache.yt; const filteredData = (cacheData || []).filter(r => { const t = toEpoch(r.ts_cn); if (t == null) return false; return (sTime == null || t >= sTime) && (eTime == null || t <= eTime); }); const platformIcon = platform === 'pdd' ? '' : ''; const listHtml = filteredData.slice(0, 100).map(r => `
  • ${platformIcon}${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • ` ).join('') || '
  • 暂无数据
  • '; if (auditListEl) auditListEl.innerHTML = listHtml; }; // 平台切换事件 if (platformSelect) { platformSelect.onchange = () => { window.__currentAuditPlatform = platformSelect.value; // 同步日期选择器 const otherDateEl = platformSelect.value === 'pdd' ? q('audit-date-pdd') : q('audit-date-yt'); if (auditDateEl && otherDateEl) { auditDateEl.value = otherDateEl.value; } updateAuditDisplay(); }; } // 整合看板日期筛选 if (auditDateEl) { auditDateEl.onchange = () => { const d = auditDateEl.value; const platform = window.__currentAuditPlatform; if (platform === 'pdd') { window.__pddParams = dateToRange(d); const pddDateEl = q('audit-date-pdd'); if (pddDateEl) pddDateEl.value = d; } else { window.__ytParams = dateToRange(d); const ytDateEl = q('audit-date-yt'); if (ytDateEl) ytDateEl.value = d; } refreshAuditLists(); updateAuditDisplay(); }; } const pddDateEl=q('audit-date-pdd'); const ytDateEl=q('audit-date-yt'); if(pddDateEl){ pddDateEl.onchange=()=>{ const d=pddDateEl.value; window.__pddParams = dateToRange(d); refreshAuditLists(); // 立即刷新 }; } if(ytDateEl){ ytDateEl.onchange=()=>{ const d=ytDateEl.value; window.__ytParams = dateToRange(d); refreshAuditLists(); // 立即刷新 }; } // 资源管理:缓存数据(已在初始化时创建) // 优化的刷新函数:一次请求同时更新趋势图和列表 const refreshAll = async() => { if(window.__auditBusy) { console.log('[Dashboard] 上次请求还在进行中,跳过本次刷新'); return; } // 检查是否还在dashboard页面 const currentPath = location.hash.replace('#', '') || '/dashboard'; if(currentPath !== '/dashboard'){ if(window.__auditTimer){ clearInterval(window.__auditTimer); window.__auditTimer = null; } return; } // 取消之前的请求 if(window.__auditAbortController){ window.__auditAbortController.abort(); } window.__auditAbortController = new AbortController(); window.__auditBusy=true; const startTime = Date.now(); // 设置超时保护:15秒后强制重置busy状态 const timeoutId = setTimeout(() => { console.warn('[Dashboard] 请求超时,强制重置状态'); window.__auditBusy = false; if(window.__auditAbortController){ window.__auditAbortController.abort(); window.__auditAbortController = null; } }, 15000); try { // 请求全部数据(不限制),传递AbortController信号 const signal = window.__auditAbortController.signal; const [pddRes, ytRes, dashRes] = await Promise.all([ fetch('/api/audit/pdd', { headers: { 'Content-Type': 'application/json' }, credentials: 'include', signal }).then(r => r.ok ? r.json() : {list:[]}).catch((e)=>{ if(e.name === 'AbortError') console.log('[Dashboard] PDD请求被取消'); return {list:[]}; }), fetch('/api/audit/yt', { headers: { 'Content-Type': 'application/json' }, credentials: 'include', signal }).then(r => r.ok ? r.json() : {list:[]}).catch((e)=>{ if(e.name === 'AbortError') console.log('[Dashboard] YT请求被取消'); return {list:[]}; }), fetch('/api/dashboard', { headers: { 'Content-Type': 'application/json' }, credentials: 'include', signal }).then(r => r.ok ? r.json() : null).catch((e)=>{ if(e.name === 'AbortError') console.log('[Dashboard] Dashboard请求被取消'); return null; }) ]); // 实时更新良品/不良率卡片和今日产量卡片 if(dashRes) { // 更新良品/不良率 const goodRateEl = document.getElementById('good-rate-value'); const badRateEl = document.getElementById('bad-rate-value'); if(goodRateEl) goodRateEl.textContent = dashRes.goodRate || '—'; if(badRateEl) badRateEl.textContent = dashRes.badRate || '—'; // 更新今日产量卡片 const productionCard = document.getElementById('today-production-card'); const platformNameEl = document.getElementById('today-platform-name'); const productionValueEl = document.getElementById('today-production-value'); const productionFillEl = document.getElementById('today-production-fill'); if(productionCard && platformNameEl && productionValueEl && productionFillEl) { const currentPlatform = productionCard.dataset.platform; const newPlatform = dashRes.activePlatform || 'pdd'; const isPdd = newPlatform === 'pdd'; const value = isPdd ? (dashRes.todayPdd || 0) : (dashRes.todayYt || 0); const platformName = isPdd ? '拼多多' : '圆通'; const color = isPdd ? '#e02e24' : '#1a6dd6'; const fillWidth = Math.min((value / 10000) * 100, 100); // 如果平台切换了,更新图标 if(currentPlatform !== newPlatform) { productionCard.dataset.platform = newPlatform; const iconEl = productionCard.querySelector('.metrics-icon'); if(iconEl) { iconEl.innerHTML = isPdd ? '' : ''; } const percentEl = productionCard.querySelector('.metrics-percent'); if(percentEl) percentEl.style.color = color; } platformNameEl.textContent = '今日' + platformName; productionValueEl.textContent = value; productionFillEl.style.width = fillWidth + '%'; productionFillEl.style.backgroundColor = color; } } clearTimeout(timeoutId); const duration = Date.now() - startTime; if(duration > 3000){ console.warn('[Dashboard] 请求耗时过长:', duration, 'ms'); } // 再次检查页面,避免切换后更新 if(location.hash.replace('#', '') !== '/dashboard'){ return; } // 缓存数据(只保留最近30天的数据以节省内存) const filterRecent30Days = (list) => { const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000; return (list || []).filter(r => { if(!r.ts_cn) return false; const t = toEpoch(r.ts_cn); return t && t >= cutoff; }); }; window.__auditCache.pdd = filterRecent30Days(pddRes.list); window.__auditCache.yt = filterRecent30Days(ytRes.list); // 更新趋势图 drawTrendChart(window.__auditCache.pdd, window.__auditCache.yt); // 更新列表(应用筛选,只显示前100条) const sP=toEpoch(window.__pddParams.start), eP=toEpoch(window.__pddParams.end); const sY=toEpoch(window.__ytParams.start), eY=toEpoch(window.__ytParams.end); const pddView=(window.__auditCache.pdd||[]).filter(r=>{const t=toEpoch(r.ts_cn); if(t==null) return false; return (sP==null||t>=sP)&&(eP==null||t<=eP);}); const ytView=(window.__auditCache.yt||[]).filter(r=>{const t=toEpoch(r.ts_cn); if(t==null) return false; return (sY==null||t>=sY)&&(eY==null||t<=eY);}); const pddEls=pddView.slice(0, 100).map(r=>`
  • ${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • `).join('')||'
  • 暂无数据
  • '; const ytEls=ytView.slice(0, 100).map(r=>`
  • ${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • `).join('')||'
  • 暂无数据
  • '; const p=document.getElementById('audit-pdd'); if(p) p.innerHTML=pddEls; const y=document.getElementById('audit-yt'); if(y) y.innerHTML=ytEls; // 更新整合看板 const updateAuditDisplay = () => { const platform = window.__currentAuditPlatform || 'pdd'; const params = platform === 'pdd' ? window.__pddParams : window.__ytParams; const sTime = toEpoch(params.start); const eTime = toEpoch(params.end); const cacheData = platform === 'pdd' ? window.__auditCache.pdd : window.__auditCache.yt; const filteredData = (cacheData || []).filter(r => { const t = toEpoch(r.ts_cn); if (t == null) return false; return (sTime == null || t >= sTime) && (eTime == null || t <= eTime); }); const platformIcon = platform === 'pdd' ? '' : ''; const listHtml = filteredData.slice(0, 100).map(r => `
  • ${platformIcon}${r.ts_cn||'—'}${r.batch||''}${r.mac||''}${r.note||''}
  • ` ).join('') || '
  • 暂无数据
  • '; const auditListEl = document.getElementById('audit-list'); if (auditListEl) auditListEl.innerHTML = listHtml; }; updateAuditDisplay(); } catch(e) { clearTimeout(timeoutId); if(e.name !== 'AbortError'){ console.error('更新审计数据失败:', e); } } finally { window.__auditBusy=false; window.__auditAbortController = null; } }; // 刷新间隔10秒 window.__auditTimer=setInterval(refreshAll, 10000); },0); return `
    ${metricsCard('直通良品数', data.fpyCount || 0, 'success')} ${rateCard(data.goodRate || '—', data.badRate || '—')} ${metricsCard('发货数量', data.shipments, 'warning')} ${todayProductionCard(data.todayPdd || 0, data.todayYt || 0, data.activePlatform || 'pdd')}
    审计趋势
    审计看板
      ${pddList}
    `; } Router.register('/dashboard', render); })();