import type { VehicleProfileSyncItem } from '../../api/types'; const headers = ['vin', 'modelName', 'vehicleType', 'companyName', 'operationStatus', 'accessProvider', 'firstAccessAt', 'runtimeSeconds'] as const; const allowedStatuses = new Set(['', 'unknown', 'active', 'inactive', 'maintenance', 'retired']); export const vehicleProfileSyncCSVHeader = headers.join(','); export function parseVehicleProfileSyncCSV(text: string): VehicleProfileSyncItem[] { const rows = parseCSVRows(text.replace(/^\uFEFF/, '')); if (rows.length < 2) throw new Error('CSV 至少需要表头和一行车辆数据'); const actualHeaders = rows[0].map((value) => value.trim()); if (actualHeaders.length !== headers.length || actualHeaders.some((value, index) => value !== headers[index])) { throw new Error(`CSV 表头必须为:${vehicleProfileSyncCSVHeader}`); } const dataRows = rows.slice(1).filter((row) => row.some((value) => value.trim() !== '')); if (dataRows.length === 0 || dataRows.length > 500) throw new Error('单个 CSV 必须包含 1 至 500 辆车'); const seen = new Set(); return dataRows.map((row, index) => { const line = index + 2; if (row.length !== headers.length) throw new Error(`CSV 第 ${line} 行列数不正确`); const [rawVIN, modelName, vehicleType, companyName, rawStatus, accessProvider, firstAccessAt, rawRuntime] = row.map((value) => value.trim()); const vin = rawVIN.toUpperCase(); if (!vin || vin.length > 32) throw new Error(`CSV 第 ${line} 行 VIN 无效`); if (seen.has(vin)) throw new Error(`CSV 第 ${line} 行 VIN 重复:${vin}`); seen.add(vin); const operationStatus = rawStatus.toLowerCase(); if (!allowedStatuses.has(operationStatus)) throw new Error(`CSV 第 ${line} 行运营状态无效`); const runtimeSeconds = rawRuntime === '' ? null : Number(rawRuntime); if (runtimeSeconds !== null && (!Number.isSafeInteger(runtimeSeconds) || runtimeSeconds < 0)) throw new Error(`CSV 第 ${line} 行累计运行秒数无效`); return { vin, modelName, vehicleType, companyName, operationStatus: (operationStatus || 'unknown') as VehicleProfileSyncItem['operationStatus'], accessProvider, firstAccessAt, runtimeSeconds }; }); } function parseCSVRows(text: string): string[][] { const rows: string[][] = []; let row: string[] = []; let field = ''; let quoted = false; let closedQuote = false; for (let index = 0; index < text.length; index += 1) { const char = text[index]; if (quoted) { if (char === '"') { if (text[index + 1] === '"') { field += '"'; index += 1; } else { quoted = false; closedQuote = true; } } else { field += char; } continue; } if (closedQuote && char !== ',' && char !== '\n' && char !== '\r') throw new Error('CSV 引号结束后存在无效字符'); if (char === '"') { if (field !== '') throw new Error('CSV 引号格式无效'); quoted = true; } else if (char === ',') { row.push(field); field = ''; closedQuote = false; } else if (char === '\n' || char === '\r') { if (char === '\r' && text[index + 1] === '\n') index += 1; row.push(field); rows.push(row); row = []; field = ''; closedQuote = false; } else { field += char; } } if (quoted) throw new Error('CSV 存在未闭合的引号'); if (field !== '' || row.length > 0) { row.push(field); rows.push(row); } return rows; }