|
|
@@ -0,0 +1,290 @@
|
|
|
+// utils/flightParser.js
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析航班代码
|
|
|
+ * @param {string} code - 航班代码字符串
|
|
|
+ * @returns {object} 解析后的航班信息
|
|
|
+ */
|
|
|
+export function parseFlightCode(code) {
|
|
|
+ if (!code || typeof code !== 'string') {
|
|
|
+ return { error: '输入无效,请提供有效的航班代码字符串' }
|
|
|
+ }
|
|
|
+
|
|
|
+ let codeStr = code.trim()
|
|
|
+
|
|
|
+ // 去除开头的序号(如 "1."、"2."、"3."、"4." 等)
|
|
|
+ codeStr = codeStr.replace(/^\d+\./, '')
|
|
|
+
|
|
|
+ if (codeStr.length === 0) {
|
|
|
+ return { error: '航班代码不能为空' }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按空格分割
|
|
|
+ const parts = codeStr.trim().split(/\s+/)
|
|
|
+
|
|
|
+ // 根据字段数量判断格式
|
|
|
+ if (parts.length === 9) {
|
|
|
+ // 格式: 航班号 操作码+日期 航线 起飞时间 到达时间 预订码 航站楼 机型 时长
|
|
|
+ // 例如: CA878 SA31MAY CPHPEK 1905 1000+1 3 T3 359 08H55M
|
|
|
+ return parseFormat9(parts)
|
|
|
+ } else if (parts.length === 10) {
|
|
|
+ // 格式: 航班号 操作码+日期 航线 起飞时间 到达时间 预订码 航站楼 机型 时长(某些格式预订码和航站楼位置不同)
|
|
|
+ return parseFormat10(parts)
|
|
|
+ } else if (parts.length === 11) {
|
|
|
+ // 格式: 航班号 操作码+日期 航线 起飞时间 到达时间 航站楼 预订码 机型 时长
|
|
|
+ // 例如: CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
|
|
|
+ return parseFormat11(parts)
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ error: '航班代码格式错误,字段数量不正确',
|
|
|
+ expected: '9、10或11个字段',
|
|
|
+ received: parts.length + '个字段',
|
|
|
+ parts: parts
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析9字段格式(最常用)
|
|
|
+ * CA878 SA31MAY CPHPEK 1905 1000+1 3 T3 359 08H55M
|
|
|
+ */
|
|
|
+function parseFormat9(parts) {
|
|
|
+ const [flightNumber, actionDate, route, departureTime, arrivalTimeRaw, bookingCode, terminal, aircraft, duration] = parts
|
|
|
+
|
|
|
+ // 提取操作码和日期
|
|
|
+ const actionCode = actionDate.slice(0, 2)
|
|
|
+ const dateStr = actionDate.slice(2)
|
|
|
+
|
|
|
+ const origin = route.slice(0, 3)
|
|
|
+ const destination = route.slice(3)
|
|
|
+
|
|
|
+ // 处理到达时间中的 +1(跨天)
|
|
|
+ let arrivalTime = arrivalTimeRaw
|
|
|
+ let plusDays = 0
|
|
|
+ if (arrivalTimeRaw && arrivalTimeRaw.includes('+')) {
|
|
|
+ const [time, days] = arrivalTimeRaw.split('+')
|
|
|
+ arrivalTime = time
|
|
|
+ plusDays = parseInt(days)
|
|
|
+ }
|
|
|
+
|
|
|
+ return buildFlightInfo({
|
|
|
+ flightNumber, actionCode, dateStr, origin, destination,
|
|
|
+ departureTime, arrivalTime, terminal, bookingCode, aircraft, duration,
|
|
|
+ plusDays
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析10字段格式
|
|
|
+ */
|
|
|
+function parseFormat10(parts) {
|
|
|
+ // 尝试两种可能的顺序
|
|
|
+ // 顺序1: 航班号 操作码+日期 航线 起飞 到达 预订码 航站楼 机型 时长
|
|
|
+ // 顺序2: 航班号 操作码+日期 航线 起飞 到达 航站楼 预订码 机型 时长
|
|
|
+ const [flightNumber, actionDate, route, departureTime, arrivalTimeRaw, code1, code2, aircraft, duration] = parts
|
|
|
+
|
|
|
+ // 判断哪个是航站楼(通常T开头)哪个是预订码(数字)
|
|
|
+ let terminal, bookingCode
|
|
|
+ if (code1.startsWith('T')) {
|
|
|
+ terminal = code1
|
|
|
+ bookingCode = code2
|
|
|
+ } else if (code2.startsWith('T')) {
|
|
|
+ terminal = code2
|
|
|
+ bookingCode = code1
|
|
|
+ } else {
|
|
|
+ // 默认顺序
|
|
|
+ bookingCode = code1
|
|
|
+ terminal = code2
|
|
|
+ }
|
|
|
+
|
|
|
+ const actionCode = actionDate.slice(0, 2)
|
|
|
+ const dateStr = actionDate.slice(2)
|
|
|
+ const origin = route.slice(0, 3)
|
|
|
+ const destination = route.slice(3)
|
|
|
+
|
|
|
+ let arrivalTime = arrivalTimeRaw
|
|
|
+ let plusDays = 0
|
|
|
+ if (arrivalTimeRaw && arrivalTimeRaw.includes('+')) {
|
|
|
+ const [time, days] = arrivalTimeRaw.split('+')
|
|
|
+ arrivalTime = time
|
|
|
+ plusDays = parseInt(days)
|
|
|
+ }
|
|
|
+
|
|
|
+ return buildFlightInfo({
|
|
|
+ flightNumber, actionCode, dateStr, origin, destination,
|
|
|
+ departureTime, arrivalTime, terminal, bookingCode, aircraft, duration,
|
|
|
+ plusDays
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析11字段格式
|
|
|
+ * CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
|
|
|
+ */
|
|
|
+function parseFormat11(parts) {
|
|
|
+ const [flightNumber, actionDate, route, departureTime, arrivalTime, terminal, bookingCode, aircraft, duration] = parts
|
|
|
+
|
|
|
+ const actionCode = actionDate.slice(0, 2)
|
|
|
+ const dateStr = actionDate.slice(2)
|
|
|
+ const origin = route.slice(0, 3)
|
|
|
+ const destination = route.slice(3)
|
|
|
+
|
|
|
+ return buildFlightInfo({
|
|
|
+ flightNumber, actionCode, dateStr, origin, destination,
|
|
|
+ departureTime, arrivalTime, terminal, bookingCode, aircraft, duration
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 构建航班信息对象
|
|
|
+ */
|
|
|
+function buildFlightInfo(params) {
|
|
|
+ const {
|
|
|
+ flightNumber, actionCode, dateStr, origin, destination,
|
|
|
+ departureTime, arrivalTime, terminal, bookingCode, aircraft, duration,
|
|
|
+ plusDays = 0
|
|
|
+ } = params
|
|
|
+
|
|
|
+ // 解析日期
|
|
|
+ const day = parseInt(dateStr.slice(0, 2))
|
|
|
+ const monthStr = dateStr.slice(2)
|
|
|
+ const months = {
|
|
|
+ 'JAN': 0, 'FEB': 1, 'MAR': 2, 'APR': 3, 'MAY': 4, 'JUN': 5,
|
|
|
+ 'JUL': 6, 'AUG': 7, 'SEP': 8, 'OCT': 9, 'NOV': 10, 'DEC': 11
|
|
|
+ }
|
|
|
+ const month = months[monthStr]
|
|
|
+
|
|
|
+ if (month === undefined) {
|
|
|
+ return { error: '无效的月份格式: ' + monthStr }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算年份
|
|
|
+ const now = new Date()
|
|
|
+ let year = now.getFullYear()
|
|
|
+ if (now.getMonth() > month) {
|
|
|
+ year++
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析时间
|
|
|
+ const depHour = parseInt(departureTime.slice(0, 2))
|
|
|
+ const depMinute = parseInt(departureTime.slice(2))
|
|
|
+ const arrHour = parseInt(arrivalTime.slice(0, 2))
|
|
|
+ const arrMinute = parseInt(arrivalTime.slice(2))
|
|
|
+
|
|
|
+ // 创建日期对象
|
|
|
+ const departureDate = new Date(year, month, day, depHour, depMinute)
|
|
|
+ let arrivalDate = new Date(year, month, day, arrHour, arrMinute)
|
|
|
+
|
|
|
+ // 处理跨天
|
|
|
+ let isOvernight = false
|
|
|
+ if (plusDays > 0) {
|
|
|
+ arrivalDate = new Date(year, month, day + plusDays, arrHour, arrMinute)
|
|
|
+ isOvernight = true
|
|
|
+ } else if (arrivalDate < departureDate) {
|
|
|
+ arrivalDate = new Date(year, month, day + 1, arrHour, arrMinute)
|
|
|
+ isOvernight = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析时长
|
|
|
+ const durationHours = parseInt(duration.slice(0, 2))
|
|
|
+ const durationMinutes = parseInt(duration.slice(3, 5))
|
|
|
+
|
|
|
+ // 返回解析结果
|
|
|
+ return {
|
|
|
+ success: true,
|
|
|
+ flightNumber: flightNumber,
|
|
|
+ actionCode: actionCode,
|
|
|
+ date: {
|
|
|
+ original: dateStr,
|
|
|
+ day: day,
|
|
|
+ month: monthStr,
|
|
|
+ monthNumber: month + 1,
|
|
|
+ year: year
|
|
|
+ },
|
|
|
+ origin: origin,
|
|
|
+ originName: getAirportName(origin),
|
|
|
+ destination: destination,
|
|
|
+ destinationName: getAirportName(destination),
|
|
|
+ departureTime: {
|
|
|
+ original: departureTime,
|
|
|
+ hour: depHour,
|
|
|
+ minute: depMinute,
|
|
|
+ formatted: formatTime(depHour, depMinute)
|
|
|
+ },
|
|
|
+ arrivalTime: {
|
|
|
+ original: arrivalTime,
|
|
|
+ hour: arrHour,
|
|
|
+ minute: arrMinute,
|
|
|
+ formatted: formatTime(arrHour, arrMinute)
|
|
|
+ },
|
|
|
+ terminal: terminal,
|
|
|
+ bookingCode: parseInt(bookingCode),
|
|
|
+ aircraft: aircraft,
|
|
|
+ aircraftName: getAircraftName(aircraft),
|
|
|
+ duration: {
|
|
|
+ original: duration,
|
|
|
+ hours: durationHours,
|
|
|
+ minutes: durationMinutes,
|
|
|
+ formatted: durationHours + '小时' + durationMinutes + '分钟',
|
|
|
+ totalMinutes: durationHours * 60 + durationMinutes
|
|
|
+ },
|
|
|
+ isOvernight: isOvernight,
|
|
|
+ plusDays: plusDays,
|
|
|
+ fullDateTime: {
|
|
|
+ departure: departureDate,
|
|
|
+ arrival: arrivalDate
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 格式化时间
|
|
|
+ */
|
|
|
+function formatTime(hour, minute) {
|
|
|
+ return hour.toString().padStart(2, '0') + ':' + minute.toString().padStart(2, '0')
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取机型名称
|
|
|
+ */
|
|
|
+function getAircraftName(code) {
|
|
|
+ var aircraftMap = {
|
|
|
+ '359': '空客A350-900',
|
|
|
+ '321': '空客A321',
|
|
|
+ '32A': '空客A320 (鲨鳍小翼)',
|
|
|
+ '738': '波音737-800',
|
|
|
+ '789': '波音787-9',
|
|
|
+ '77W': '波音777-300ER',
|
|
|
+ '333': '空客A330-300',
|
|
|
+ '332': '空客A330-200',
|
|
|
+ '320': '空客A320'
|
|
|
+ }
|
|
|
+ return aircraftMap[code] || code
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取机场名称
|
|
|
+ */
|
|
|
+function getAirportName(code) {
|
|
|
+ var airportMap = {
|
|
|
+ 'TFU': '成都天府',
|
|
|
+ 'FRA': '法兰克福',
|
|
|
+ 'MUC': '慕尼黑',
|
|
|
+ 'CPH': '哥本哈根',
|
|
|
+ 'PEK': '北京首都',
|
|
|
+ 'PVG': '上海浦东',
|
|
|
+ 'CAN': '广州白云',
|
|
|
+ 'CTU': '成都双流'
|
|
|
+ }
|
|
|
+ return airportMap[code] || code
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 验证航班代码格式
|
|
|
+ */
|
|
|
+export function validateFlightCode(code) {
|
|
|
+ if (!code || typeof code !== 'string') return false
|
|
|
+ var cleanCode = code.trim().replace(/^\d+\./, '')
|
|
|
+ var parts = cleanCode.split(/\s+/)
|
|
|
+ return parts.length === 9 || parts.length === 10 || parts.length === 11
|
|
|
+}
|