diff --git a/package.json b/package.json index 55db365..c98a36c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ticketmachine", - "version": "1.0.15", + "version": "1.0.16", "main": "server/index.js", "scripts": { "start": "node server/index.js", diff --git a/server/routes/api.js b/server/routes/api.js index 3a08349..1277381 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -1338,8 +1338,35 @@ router.post('/tickets/check', async (req, res) => { const isSingleTicket = String(cur.train_type || '').toLowerCase() === 'single'; + const checkStationOnLine = (lineName, codeRaw, codesSet, codeNorm) => { + if (!lineName) return false; + const target = String(lineName).trim().toLowerCase(); + const lines = DataService.getLines() || []; + const line = lines.find(l => { + const ln = String(l.name || l['线路名称'] || '').trim().toLowerCase(); + const le = String(l.en_name || l.en || l.enName || '').trim().toLowerCase(); + return ln === target || le === target; + }); + if (!line) return false; // Line not found + const stops = Array.isArray(line.stations) ? line.stations : (Array.isArray(line.stops) ? line.stops : (line['站点列表'] || line['站点'] || [])); + const stopSet = new Set(stops.map(normStationKey).filter(Boolean)); + if (codeNorm && stopSet.has(codeNorm)) return true; + if (codesSet.size > 0) { + for (const sc of codesSet) { + if (stopSet.has(sc)) return true; + } + } + return false; + }; + if (action === 'entry') { - if (!isSingleTicket && !matchesStation(start)) return deny('wrong_station'); + if (isSingleTicket) { + if (!checkStationOnLine(cur.start, station_code_raw, station_codes_norm, station_code_norm)) { + return deny('wrong_station'); + } + } else { + if (!matchesStation(start)) return deny('wrong_station'); + } if (entered && !exited) return deny('already_entered'); const entryCode = isSingleTicket ? (station_code_raw || start) : start; @@ -1352,7 +1379,8 @@ router.post('/tickets/check', async (req, res) => { exited: false, last_action: 'entry', last_station_code: entryCode, - start: isSingleTicket ? entryCode : cur.start, + start: isSingleTicket ? cur.start : cur.start, + entered_station: entryCode, last_result: 'pass', last_reason: '', last_event: 'check', @@ -1367,7 +1395,11 @@ router.post('/tickets/check', async (req, res) => { const exitCode = station_code_raw || terminal; if (isSingleTicket) { - const fareInfo = LogicService.computeFareBoth(cur.start, exitCode); + if (!checkStationOnLine(cur.start, station_code_raw, station_codes_norm, station_code_norm)) { + return deny('wrong_station'); + } + const entryCode = cur.entered_station || cur.last_station_code; + const fareInfo = LogicService.computeFareBoth(entryCode, exitCode); if (!fareInfo || fareInfo.paid_segments !== 1) { return deny('not_single_segment'); } diff --git a/web/ic-card-admin.js b/web/ic-card-admin.js index 835971e..4a1ae37 100644 --- a/web/ic-card-admin.js +++ b/web/ic-card-admin.js @@ -191,11 +191,22 @@ const formatIcCardEventUnified = (ev) => { const type = String(ev?.type || '').toLowerCase(); const action = String(ev?.action || '').toLowerCase(); - let deviceId = ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---'; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else if (type === 'topup' || type === 'refill' || action === 'topup' || type === '充值') { + deviceId = '充值机'; + } else { + deviceId = '售票机'; + } + } const timeStr = formatTime(ev?.ts); const balance = ev?.balance ?? ev?.["余额"] ?? 0; diff --git a/web/ic-card-search.js b/web/ic-card-search.js index 6c5e5ca..a9058c9 100644 --- a/web/ic-card-search.js +++ b/web/ic-card-search.js @@ -90,11 +90,22 @@ const formatIcCardEventUnified = (ev) => { const type = String(ev?.type || '').toLowerCase(); const action = String(ev?.action || '').toLowerCase(); - let deviceId = ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---'; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else if (type === 'topup' || type === 'refill' || action === 'topup' || type === '充值') { + deviceId = '充值机'; + } else { + deviceId = '售票机'; + } + } const timeStr = formatTime(ev?.ts); const balance = ev?.balance ?? ev?.["余额"] ?? 0; diff --git a/web/index.js b/web/index.js index 1a531fd..197b458 100644 --- a/web/index.js +++ b/web/index.js @@ -348,11 +348,20 @@ createApp({ const formatTicketEventUnified = (ev) => { const type = getTicketEventType(ev); const action = getTicketEventAction(ev); - let deviceId = ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---'; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else { + deviceId = '售票机'; + } + } const timeStr = formatTime(ev?.ts || ev?.["时间戳"]); if (type === 'sale' || type === '售票') { @@ -455,11 +464,22 @@ createApp({ const formatIcEventDetail = (ev) => { const type = String(ev?.type || '').toLowerCase(); const action = String(ev?.action || '').toLowerCase(); - let deviceId = ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---'; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else if (type === 'topup' || type === 'refill' || action === 'topup' || type === '充值') { + deviceId = '充值机'; + } else { + deviceId = '售票机'; + } + } const timeStr = formatTime(ev?.ts); const balance = ev?.balance ?? ev?.["余额"] ?? 0; diff --git a/web/ticket-board.html b/web/ticket-board.html index eb40c12..7abf260 100644 --- a/web/ticket-board.html +++ b/web/ticket-board.html @@ -310,18 +310,32 @@ }; const formatEventMeta = (event) => { + if (!event) return '---'; const type = String(event.type || event['类型'] || '').toLowerCase(); + const action = String(event.action || event['动作'] || '').toLowerCase(); + if (type === 'sale' || type === '售票') { const amount = event.amount ?? event['售票额']; if (amount != null && amount !== '') return `票价:¥ ${amount}`; } const stationEn = event.station_en || event['站点英文'] || ''; - let deviceId = event.device || event.device_id || event['设备编号'] || event['机器ID'] || ''; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(event.device || event.device_id || event['设备编号'] || event['机器ID'] || '').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else if (type === 'topup' || type === 'refill' || action === 'topup' || type === '充值') { + deviceId = '充值机'; + } else { + deviceId = '售票机'; + } + } if (stationEn && deviceId) return `${stationEn} (${deviceId})`; if (deviceId) return `设备:${deviceId}`; diff --git a/web/ticket-route.js b/web/ticket-route.js index 562bc25..314aadd 100644 --- a/web/ticket-route.js +++ b/web/ticket-route.js @@ -228,11 +228,20 @@ createApp({ const formatTicketEventUnified = (ev) => { const type = getTicketEventType(ev); const action = getTicketEventAction(ev); - let deviceId = ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---'; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(ev?.device || ev?.device_id || ev?.["设备编号"] || ev?.["机器ID"] || '---').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else { + deviceId = '售票机'; + } + } const timeStr = formatTime(ev?.ts || ev?.["时间戳"]); if (type === 'sale' || type === '售票') { diff --git a/web/ticket-search.js b/web/ticket-search.js index 599343c..6e75866 100644 --- a/web/ticket-search.js +++ b/web/ticket-search.js @@ -110,11 +110,20 @@ const formatTicketEventUnified = (ev) => { const type = getEventType(ev); const action = String(ev.action || ev["动作"] || '').toLowerCase(); - let deviceId = ev.device || ev.device_id || ev["设备编号"] || ev["机器ID"] || '---'; - if (/^\d+$/.test(deviceId)) deviceId = 'ticket_machine'; - if (deviceId === 'ticket_machine') deviceId = '售票机'; - if (deviceId === 'gate') deviceId = '闸机'; - if (deviceId === 'refill_machine') deviceId = '充值机'; + let deviceId = String(ev.device || ev.device_id || ev["设备编号"] || ev["机器ID"] || '---').trim(); + const dStr = deviceId.toLowerCase(); + + if (dStr === 'gate') { + deviceId = '闸机'; + } else if (dStr === 'refill_machine') { + deviceId = '充值机'; + } else if (dStr === 'ticket_machine' || dStr === 'ticket_vending_machine' || /^\d+$/.test(dStr) || dStr.includes('computer') || dStr === 'unknown') { + if (type === 'entry' || action === 'entry' || type === 'exit' || action === 'exit' || type === 'check') { + deviceId = '闸机'; + } else { + deviceId = '售票机'; + } + } const timeStr = formatTime(ev.ts || ev["时间戳"]); if (type === 'sale' || type === '售票') {