function asArray(value) { if (Array.isArray(value)) return value; if (value == null || value === '') return []; return [value]; } function asNumber(value, fallback = 0) { const number = Number(value); return Number.isFinite(number) ? number : fallback; } function clampPercent(value) { return Math.max(0, Math.min(100, asNumber(value))); } function formatDateTime(value) { if (!value) return '—'; const date = new Date(value); if (Number.isNaN(date.getTime())) return String(value); const pad = (number) => String(number).padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; } function containsAbnormalText(value) { return /(异常|失败|取消|作废|拒绝|未通过|超时)/.test(String(value || '')); } function normalizeStage(stage, index = 0) { const source = stage && typeof stage === 'object' ? stage : {}; const stageStatus = asNumber(source.stageStatus, -1); const totalUnits = Math.max(0, asNumber(source.totalUnits)); const checkedUnits = Math.max(0, asNumber(source.checkedUnits)); const rawPercent = asNumber(source.progressPercent); const isAbnormal = ![1, 2, 3, 4].includes(stageStatus) || checkedUnits > totalUnits || rawPercent < 0 || rawPercent > 100 || containsAbnormalText(source.stageStatusText); const athleteNames = asArray(source.athleteNames); return Object.assign({}, source, { stageKey: source.stageKey || `${source.eventProjectId || 'project'}-${source.groupId || 'group'}-${index}`, stageStatus, totalUnits, checkedUnits, progressPercent: clampPercent(rawPercent), displayName: source.displayName || [source.projectName, source.groupName].filter(Boolean).join(' - ') || '未命名项目', stageStatusText: source.stageStatusText || (isAbnormal ? '状态异常' : '等待中'), athleteNames, athleteNamesText: athleteNames.join('、') || '—', isAbnormal, statusClass: isAbnormal ? 'abnormal' : `status-${stageStatus}` }); } function normalizeRound(round) { const source = round && typeof round === 'object' ? round : {}; const passed = source.passed === true; const isAbnormal = containsAbnormalText(source.statusText || source.checkinStatusText); return Object.assign({}, source, { checkinNo: asNumber(source.checkinNo), checkinName: source.checkinName || `${asNumber(source.checkinNo)}检`, passed, checkedTimeText: passed ? formatDateTime(source.checkedTime) : '待完成', checkerNameText: passed ? (source.checkerName || '—') : '—', isAbnormal, statusClass: isAbnormal ? 'abnormal' : (passed ? 'passed' : 'pending') }); } function normalizeMine(item) { const source = item && typeof item === 'object' ? item : {}; const checkinStatus = asNumber(source.checkinStatus, -1); const isAbnormal = ![0, 1, 2, 3].includes(checkinStatus) || containsAbnormalText(source.checkinStatusText) || containsAbnormalText(source.notice); const athleteNames = asArray(source.athleteNames); return Object.assign({}, source, { checkinStatus, displayName: source.displayName || [source.projectName, source.groupName].filter(Boolean).join(' - ') || '未命名项目', athleteNames, athleteNamesText: athleteNames.join('、') || '—', rounds: asArray(source.rounds).map(normalizeRound), checkinStatusText: source.checkinStatusText || (isAbnormal ? '状态异常' : '等待中'), isAbnormal, statusClass: isAbnormal ? 'abnormal' : `status-${checkinStatus}` }); } function normalizeCheckinProgress(payload) { const source = payload && typeof payload === 'object' ? payload : {}; const stages = asArray(source.stages).map((stage, index) => normalizeStage(stage, index)); const mine = asArray(source.mine).map(normalizeMine); const currentCheckin = source.currentCheckin ? normalizeStage(source.currentCheckin) : null; const currentCompeting = source.currentCompeting ? normalizeStage(source.currentCompeting) : null; const competingMine = currentCompeting && mine.find((item) => String(item.eventProjectId) === String(currentCompeting.eventProjectId) && String(item.groupId || '') === String(currentCompeting.groupId || '') ); const currentCompetingDetail = currentCompeting ? Object.assign({}, currentCompeting, { unitNameText: currentCompeting.unitName || (competingMine && competingMine.unitName) || '—', teamNameText: currentCompeting.teamName || (competingMine && competingMine.teamName) || '—', athleteNamesText: currentCompeting.athleteNamesText !== '—' ? currentCompeting.athleteNamesText : ((competingMine && competingMine.athleteNamesText) || '—'), registrationTypeText: currentCompeting.registrationTypeText || '个人报名' }) : null; return Object.assign({}, source, { eventName: source.eventName || '检录进度', serverTimeText: source.serverTimeText || formatDateTime(source.serverTime), weekDayText: source.weekDayText || '', currentCheckin, currentCompeting, currentCompetingDetail, stages, mine, hasData: !!currentCheckin || !!currentCompeting || stages.length > 0 || mine.length > 0 }); } module.exports = { normalizeCheckinProgress };