const Dashboard = (() => { const truckIcon = `
`; const metricsIcons = { '直通良品率': '', '良品率': '', '发货数量': truckIcon, '不良数量': '' }; 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); // 绘制参数(使用逻辑尺寸而非物理像素) const padding = {left: 40, 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 drawLine = (counts, color) => { ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.beginPath(); counts.forEach((count, i) => { const x = padding.left + (chartWidth / (timePoints.length - 1)) * i; const y = padding.top + chartHeight - (count / maxCount) * chartHeight; if(i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, 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.replace(')', ', 0.2)').replace('rgb', 'rgba').replace('#', ''); 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(); // 显示今日数值标签 ctx.fillStyle = color; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(count.toString(), x, y - 14); } else { // 普通数据点 ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fillStyle = color; ctx.fill(); } }); }; drawLine(pddCounts, '#f59e0b'); drawLine(ytCounts, '#3b82f6'); // 绘制图例 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] = 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:[]}; }) ]); 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.fpyRate || '—', 'success')} ${metricsCard('良品率', data.goodRate, 'success')} ${metricsCard('发货数量', data.shipments, 'warning')} ${metricsCard('不良数量', data.badCount ?? data.defects, 'danger')}
    审计趋势
    审计看板
      ${pddList}
    `; } Router.register('/dashboard', render); })();