From 8b101174870170c3c0a4e002c14306539abdafe8 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Fri, 10 Jul 2026 12:02:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=A0=87=E8=AF=86=E8=AF=86=E5=88=AB=E4=B8=8E=E5=8D=95=E7=A8=8B?= =?UTF-8?q?=E7=A5=A8=E6=A3=80=E7=A5=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 升级项目版本至v1.0.16 - 重构多文件的设备ID标准化逻辑,新增空格修剪、大小写不敏感处理,支持更多设备类型 - 基于事件类型智能区分闸机、充值机与售票机 - 新增站点线路校验工具,修复单程票检票的站点合法性校验 - 修正单程票票价计算使用实际进站站点而非初始起点 - 添加事件元数据格式化的空值安全检查 --- package.json | 2 +- server/routes/api.js | 38 +++++++++++++++++++++++++++++++++++--- web/ic-card-admin.js | 21 ++++++++++++++++----- web/ic-card-search.js | 21 ++++++++++++++++----- web/index.js | 40 ++++++++++++++++++++++++++++++---------- web/ticket-board.html | 24 +++++++++++++++++++----- web/ticket-route.js | 19 ++++++++++++++----- web/ticket-search.js | 19 ++++++++++++++----- 8 files changed, 145 insertions(+), 39 deletions(-) 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 === '售票') {