feat: 优化自动更新逻辑,移除单程票购票并升级版本至1.0.18
升级package.json版本至1.0.18;重构update_gate.lua和update_machine.lua,支持--silent静默运行参数,统一日志输出逻辑;优化ticketmachine.lua和gate.lua的自动更新触发逻辑,新增设备空闲检测,避免打断用户操作,仅在空闲时执行静默更新后重启设备;修复ticketmachine.lua中state变量重复声明的问题;移除Web端ticket-order相关的单程票购票UI与业务逻辑,简化购票流程;将gate.lua中的版本检查间隔从5秒调整为60秒,优化设备ID显示格式。
This commit is contained in:
@@ -2,7 +2,7 @@ local DEFAULT_SERVER_BASE = "http://ticket.fse-media.group"
|
|||||||
local DEFAULT_SERVER_PATH = "/api/tickets/check"
|
local DEFAULT_SERVER_PATH = "/api/tickets/check"
|
||||||
local GATE_OPEN_SECONDS = 2
|
local GATE_OPEN_SECONDS = 2
|
||||||
local VERSION = "v1.5.10"
|
local VERSION = "v1.5.10"
|
||||||
local VERSION_CHECK_INTERVAL = 5
|
local VERSION_CHECK_INTERVAL = 60
|
||||||
|
|
||||||
local CONFIG_PATH = "gate_config.json"
|
local CONFIG_PATH = "gate_config.json"
|
||||||
|
|
||||||
@@ -92,6 +92,30 @@ local serverConnected = nil
|
|||||||
local serverLastChangeTs = 0
|
local serverLastChangeTs = 0
|
||||||
local expectedGateVersion = nil
|
local expectedGateVersion = nil
|
||||||
local versionMismatch = nil
|
local versionMismatch = nil
|
||||||
|
local pendingGateUpdate = false
|
||||||
|
local gateBusyUntilTs = 0
|
||||||
|
|
||||||
|
local function nowMs()
|
||||||
|
return (os.epoch and os.epoch("utc")) or (os.time() * 1000)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function markGateBusy(seconds)
|
||||||
|
local secondsNum = tonumber(seconds) or 0
|
||||||
|
local untilTs = nowMs() + math.max(1000, math.floor(secondsNum * 1000))
|
||||||
|
if untilTs > gateBusyUntilTs then
|
||||||
|
gateBusyUntilTs = untilTs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isGateIdleForUpdate()
|
||||||
|
return nowMs() >= gateBusyUntilTs
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runSilentGateUpdate()
|
||||||
|
local baseEnv = (getfenv and getfenv()) or _ENV or _G
|
||||||
|
local env = setmetatable({ AUTO_UPDATE_SILENT = true }, { __index = baseEnv })
|
||||||
|
return os.run(env, "update_gate.lua", "--silent")
|
||||||
|
end
|
||||||
|
|
||||||
local function setServerConnected(ok)
|
local function setServerConnected(ok)
|
||||||
if serverConnected == ok then return end
|
if serverConnected == ok then return end
|
||||||
@@ -997,7 +1021,7 @@ local function currentDeviceId()
|
|||||||
if label and label ~= "" then
|
if label and label ~= "" then
|
||||||
return label
|
return label
|
||||||
end
|
end
|
||||||
return "闸机#" .. tostring(os.getComputerID())
|
return "#" .. tostring(os.getComputerID())
|
||||||
end
|
end
|
||||||
|
|
||||||
local function syncICCardState(cardId, payload)
|
local function syncICCardState(cardId, payload)
|
||||||
@@ -1353,6 +1377,7 @@ local versionTimer = os.startTimer(VERSION_CHECK_INTERVAL)
|
|||||||
while true do
|
while true do
|
||||||
local ev = pack(os.pullEvent())
|
local ev = pack(os.pullEvent())
|
||||||
if ev[1] == "ticket_scanned" or ev[1] == "ic_card_scanned" then
|
if ev[1] == "ticket_scanned" or ev[1] == "ic_card_scanned" then
|
||||||
|
markGateBusy(GATE_OPEN_SECONDS + 2)
|
||||||
processInspectionEvent(ev[1], ev)
|
processInspectionEvent(ev[1], ev)
|
||||||
os.sleep(0.35)
|
os.sleep(0.35)
|
||||||
drawReadyScreen()
|
drawReadyScreen()
|
||||||
@@ -1361,19 +1386,16 @@ while true do
|
|||||||
refreshRemoteLuaVersion(guessBaseFromStatusURL(serverURL))
|
refreshRemoteLuaVersion(guessBaseFromStatusURL(serverURL))
|
||||||
end)
|
end)
|
||||||
if versionMismatch == true then
|
if versionMismatch == true then
|
||||||
pcall(function()
|
pendingGateUpdate = true
|
||||||
clear()
|
elseif versionMismatch == false then
|
||||||
local w, h = termDev.getSize()
|
pendingGateUpdate = false
|
||||||
centerText(math.floor(h / 2), "Auto Updating...", colors.yellow)
|
end
|
||||||
end)
|
if pendingGateUpdate and isGateIdleForUpdate() then
|
||||||
pcall(function()
|
local ok, updated = pcall(runSilentGateUpdate)
|
||||||
if shell and type(shell.run) == "function" then
|
if ok and updated then
|
||||||
shell.run("update_gate.lua")
|
os.reboot()
|
||||||
else
|
return
|
||||||
os.run(getfenv and getfenv() or _ENV, "update_gate.lua")
|
end
|
||||||
end
|
|
||||||
end)
|
|
||||||
os.reboot()
|
|
||||||
end
|
end
|
||||||
drawReadyScreen()
|
drawReadyScreen()
|
||||||
versionTimer = os.startTimer(VERSION_CHECK_INTERVAL)
|
versionTimer = os.startTimer(VERSION_CHECK_INTERVAL)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ticketmachine",
|
"name": "ticketmachine",
|
||||||
"version": "1.0.17",
|
"version": "1.0.18",
|
||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server/index.js",
|
"start": "node server/index.js",
|
||||||
|
|||||||
+21
-10
@@ -1,6 +1,7 @@
|
|||||||
local CURRENT_STATION_CODE = 'Ticket-Machine'
|
local CURRENT_STATION_CODE = 'Ticket-Machine'
|
||||||
local API_BASE = 'http://ticket.fse-media.group/api'
|
local API_BASE = 'http://ticket.fse-media.group/api'
|
||||||
local VERSION = 'v1.5.12'
|
local VERSION = 'v1.5.12'
|
||||||
|
local UPDATE_CHECK_INTERVAL = 60
|
||||||
|
|
||||||
-- ###########################
|
-- ###########################
|
||||||
-- Core HTTP & JSON Utilities
|
-- Core HTTP & JSON Utilities
|
||||||
@@ -13,6 +14,8 @@ end
|
|||||||
|
|
||||||
local serverConnected = nil
|
local serverConnected = nil
|
||||||
local versionMismatch = nil
|
local versionMismatch = nil
|
||||||
|
local pendingMachineUpdate = false
|
||||||
|
local state
|
||||||
|
|
||||||
local function normalizeVersionTag(v)
|
local function normalizeVersionTag(v)
|
||||||
local s = tostring(v or ''):gsub('^%s+', ''):gsub('%s+$', '')
|
local s = tostring(v or ''):gsub('^%s+', ''):gsub('%s+$', '')
|
||||||
@@ -652,19 +655,27 @@ local function clickSound() end
|
|||||||
-- ###########################
|
-- ###########################
|
||||||
-- Background Tasks
|
-- Background Tasks
|
||||||
-- ###########################
|
-- ###########################
|
||||||
|
local function runSilentMachineUpdate()
|
||||||
|
local baseEnv = (getfenv and getfenv()) or _ENV or _G
|
||||||
|
local env = setmetatable({ AUTO_UPDATE_SILENT = true }, { __index = baseEnv })
|
||||||
|
return os.run(env, 'update_machine.lua', '--silent')
|
||||||
|
end
|
||||||
|
|
||||||
local function backgroundSyncTask()
|
local function backgroundSyncTask()
|
||||||
while true do
|
while true do
|
||||||
sleep(5)
|
sleep(UPDATE_CHECK_INTERVAL)
|
||||||
pcall(refreshConfigOnce)
|
pcall(refreshConfigOnce)
|
||||||
if versionMismatch == true then
|
if versionMismatch == true then
|
||||||
pcall(function()
|
pendingMachineUpdate = true
|
||||||
if shell and type(shell.run) == "function" then
|
elseif versionMismatch == false then
|
||||||
shell.run("update_machine.lua")
|
pendingMachineUpdate = false
|
||||||
else
|
end
|
||||||
os.run(getfenv and getfenv() or _ENV, "update_machine.lua")
|
if pendingMachineUpdate and state and tostring(state.page or 'home') == 'home' then
|
||||||
end
|
local ok, updated = pcall(runSilentMachineUpdate)
|
||||||
end)
|
if ok and updated then
|
||||||
-- Removed os.reboot() to prevent interrupting user unexpectedly
|
os.reboot()
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -966,7 +977,7 @@ end
|
|||||||
-- ###########################
|
-- ###########################
|
||||||
-- Pages & Navigation
|
-- Pages & Navigation
|
||||||
-- ###########################
|
-- ###########################
|
||||||
local state = {
|
state = {
|
||||||
page = 'home',
|
page = 'home',
|
||||||
stationName = (CFG.current_station and (CFG.current_station.en_name or CFG.current_station.name)) or 'Station',
|
stationName = (CFG.current_station and (CFG.current_station.en_name or CFG.current_station.name)) or 'Station',
|
||||||
stationCode = (CFG.current_station and CFG.current_station.code) or CURRENT_STATION_CODE,
|
stationCode = (CFG.current_station and CFG.current_station.code) or CURRENT_STATION_CODE,
|
||||||
|
|||||||
+24
-13
@@ -1,5 +1,12 @@
|
|||||||
|
|
||||||
local URL_GATE = "http://gitea.fse-media.group/Henry_Du/FSE-Ticket.sys/raw/branch/main/gate.lua"
|
local URL_GATE = "http://gitea.fse-media.group/Henry_Du/FSE-Ticket.sys/raw/branch/main/gate.lua"
|
||||||
|
local args = { ... }
|
||||||
|
local silent = AUTO_UPDATE_SILENT == true or args[1] == "--silent"
|
||||||
|
|
||||||
|
local function log(msg)
|
||||||
|
if not silent then
|
||||||
|
print(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function writeFile(path, content, binary)
|
local function writeFile(path, content, binary)
|
||||||
local mode = binary and "wb" or "w"
|
local mode = binary and "wb" or "w"
|
||||||
@@ -54,25 +61,29 @@ local function httpGet(url)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
term.clear()
|
if not silent then
|
||||||
term.setCursorPos(1, 1)
|
term.clear()
|
||||||
print("Ticket Gate Updater")
|
term.setCursorPos(1, 1)
|
||||||
print("")
|
log("Ticket Gate Updater")
|
||||||
print("Downloading gate program...")
|
log("")
|
||||||
|
end
|
||||||
|
log("Downloading gate program...")
|
||||||
|
|
||||||
local ok, code = httpGet(URL_GATE)
|
local ok, code = httpGet(URL_GATE)
|
||||||
if not ok or type(code) ~= "string" or #code == 0 then
|
if not ok or type(code) ~= "string" or #code == 0 then
|
||||||
print("Download failed: " .. tostring(code or ""))
|
error("Download failed: " .. tostring(code or ""))
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if not atomicWrite("startup.lua", code, false) then
|
if not atomicWrite("startup.lua", code, false) then
|
||||||
print("Write failed: startup.lua")
|
error("Write failed: startup.lua")
|
||||||
return
|
|
||||||
end
|
end
|
||||||
atomicWrite("startup", code, false)
|
atomicWrite("startup", code, false)
|
||||||
if fs.exists("gate.lua") then atomicWrite("gate.lua", code, false) end
|
if fs.exists("gate.lua") then atomicWrite("gate.lua", code, false) end
|
||||||
|
|
||||||
print("")
|
if not silent then
|
||||||
print("Done.")
|
log("")
|
||||||
print("Reboot the computer to apply the update.")
|
log("Done.")
|
||||||
|
log("Reboot the computer to apply the update.")
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
|||||||
+24
-13
@@ -1,5 +1,12 @@
|
|||||||
|
|
||||||
local URL_MACHINE = "http://gitea.fse-media.group/Henry_Du/FSE-Ticket.sys/raw/branch/main/ticketmachine.lua"
|
local URL_MACHINE = "http://gitea.fse-media.group/Henry_Du/FSE-Ticket.sys/raw/branch/main/ticketmachine.lua"
|
||||||
|
local args = { ... }
|
||||||
|
local silent = AUTO_UPDATE_SILENT == true or args[1] == "--silent"
|
||||||
|
|
||||||
|
local function log(msg)
|
||||||
|
if not silent then
|
||||||
|
print(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function writeFile(path, content, binary)
|
local function writeFile(path, content, binary)
|
||||||
local mode = binary and "wb" or "w"
|
local mode = binary and "wb" or "w"
|
||||||
@@ -54,25 +61,29 @@ local function httpGet(url)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
term.clear()
|
if not silent then
|
||||||
term.setCursorPos(1, 1)
|
term.clear()
|
||||||
print("Ticket Machine Updater")
|
term.setCursorPos(1, 1)
|
||||||
print("")
|
log("Ticket Machine Updater")
|
||||||
print("Downloading ticket machine program...")
|
log("")
|
||||||
|
end
|
||||||
|
log("Downloading ticket machine program...")
|
||||||
|
|
||||||
local ok, code = httpGet(URL_MACHINE)
|
local ok, code = httpGet(URL_MACHINE)
|
||||||
if not ok or type(code) ~= "string" or #code == 0 then
|
if not ok or type(code) ~= "string" or #code == 0 then
|
||||||
print("Download failed: " .. tostring(code or ""))
|
error("Download failed: " .. tostring(code or ""))
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if not atomicWrite("startup.lua", code, false) then
|
if not atomicWrite("startup.lua", code, false) then
|
||||||
print("Write failed: startup.lua")
|
error("Write failed: startup.lua")
|
||||||
return
|
|
||||||
end
|
end
|
||||||
atomicWrite("startup", code, false)
|
atomicWrite("startup", code, false)
|
||||||
if fs.exists("ticketmachine.lua") then atomicWrite("ticketmachine.lua", code, false) end
|
if fs.exists("ticketmachine.lua") then atomicWrite("ticketmachine.lua", code, false) end
|
||||||
|
|
||||||
print("")
|
if not silent then
|
||||||
print("Done.")
|
log("")
|
||||||
print("Reboot the computer to apply the update.")
|
log("Done.")
|
||||||
|
log("Reboot the computer to apply the update.")
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
|||||||
@@ -47,29 +47,6 @@
|
|||||||
<section class="jr-content-grid">
|
<section class="jr-content-grid">
|
||||||
<div class="jr-column-main">
|
<div class="jr-column-main">
|
||||||
<article class="jr-section-card" id="reservationPanel">
|
<article class="jr-section-card" id="reservationPanel">
|
||||||
<div class="jr-form-block" style="margin-bottom: 24px;">
|
|
||||||
<div class="jr-field-head">
|
|
||||||
<span class="jr-field-label">票种</span>
|
|
||||||
<span class="jr-field-note">请选择购买的车票类型</span>
|
|
||||||
</div>
|
|
||||||
<div class="train-type-group jr-train-type-group">
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="ticketClass" value="normal" checked>
|
|
||||||
<div class="type-card jr-type-card">
|
|
||||||
<div class="type-title">普通票</div>
|
|
||||||
<div class="type-desc">按起终点购票</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="ticketClass" value="single">
|
|
||||||
<div class="type-card jr-type-card">
|
|
||||||
<div class="type-title">单程票</div>
|
|
||||||
<div class="type-desc">限乘一个区间</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="normalTicketView">
|
<div id="normalTicketView">
|
||||||
<div class="jr-section-head">
|
<div class="jr-section-head">
|
||||||
<div>
|
<div>
|
||||||
@@ -98,21 +75,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="singleTicketView" style="display: none;">
|
|
||||||
<div class="jr-section-head">
|
|
||||||
<div>
|
|
||||||
<p class="jr-section-label">Single Trip</p>
|
|
||||||
<h2>选择单程区间</h2>
|
|
||||||
</div>
|
|
||||||
<p class="jr-section-note">根据线路和票价分类,购买后可乘坐该线路任意单一区间。</p>
|
|
||||||
</div>
|
|
||||||
<div id="singleSegmentList" class="list jr-price-list" style="max-height: 400px; overflow-y: auto;">
|
|
||||||
<div class="loading">加载区间数据...</div>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" id="singleSegmentSelection" value="" />
|
|
||||||
<input type="hidden" id="singleSegmentPrice" value="" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="jr-form-grid" style="margin-top: 24px;">
|
<div class="jr-form-grid" style="margin-top: 24px;">
|
||||||
<div class="jr-form-block">
|
<div class="jr-form-block">
|
||||||
<div class="jr-field-head">
|
<div class="jr-field-head">
|
||||||
|
|||||||
+6
-132
@@ -565,43 +565,9 @@
|
|||||||
|
|
||||||
async function previewPrice(force = false){
|
async function previewPrice(force = false){
|
||||||
const seq = ++previewSeq;
|
const seq = ++previewSeq;
|
||||||
const ticketClass = document.querySelector('input[name="ticketClass"]:checked').value;
|
|
||||||
const type = document.querySelector('input[name="trainType"]:checked').value;
|
const type = document.querySelector('input[name="trainType"]:checked').value;
|
||||||
const trips = normalizeTrips();
|
const trips = normalizeTrips();
|
||||||
|
|
||||||
if (ticketClass === 'single') {
|
|
||||||
const lineName = document.getElementById('singleSegmentSelection').value;
|
|
||||||
const priceStr = document.getElementById('singleSegmentPrice').value;
|
|
||||||
if (!lineName || !priceStr) {
|
|
||||||
priceBox.innerHTML = '<div class="empty-state jr-empty-state"><p>请选择单程区间</p></div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cost = Number(priceStr) * trips;
|
|
||||||
priceBox.innerHTML = `
|
|
||||||
<div class="list-item jr-result-row">
|
|
||||||
<span class="k">线路</span>
|
|
||||||
<span class="v" style="font-weight:700; color:#0ea5e9;">${lineName}</span>
|
|
||||||
</div>
|
|
||||||
<div class="list-item jr-result-row">
|
|
||||||
<span class="k">区间</span>
|
|
||||||
<span class="v">任意单区间</span>
|
|
||||||
</div>
|
|
||||||
<div class="list-item jr-result-row">
|
|
||||||
<span class="k">车型</span>
|
|
||||||
<span class="v">${type}</span>
|
|
||||||
</div>
|
|
||||||
<div class="list-item jr-result-row">
|
|
||||||
<span class="k">单价</span>
|
|
||||||
<span class="v">$${priceStr}</span>
|
|
||||||
</div>
|
|
||||||
<div class="list-item jr-result-row">
|
|
||||||
<span class="k">总价</span>
|
|
||||||
<span class="v" style="font-size:18px; font-weight:800; color:#0ea5e9;">$${cost}</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const from = (fromEl.value||'').trim();
|
const from = (fromEl.value||'').trim();
|
||||||
const to = (toEl.value||'').trim();
|
const to = (toEl.value||'').trim();
|
||||||
if(!from || !to){ priceBox.innerHTML = '<div class="empty-state jr-empty-state"><p>请选择起点与终点</p></div>'; return; }
|
if(!from || !to){ priceBox.innerHTML = '<div class="empty-state jr-empty-state"><p>请选择起点与终点</p></div>'; return; }
|
||||||
@@ -679,29 +645,18 @@
|
|||||||
|
|
||||||
// Create Order with auto-preview
|
// Create Order with auto-preview
|
||||||
async function createOrder(){
|
async function createOrder(){
|
||||||
const ticketClass = document.querySelector('input[name="ticketClass"]:checked').value;
|
|
||||||
const type = document.querySelector('input[name="trainType"]:checked').value;
|
const type = document.querySelector('input[name="trainType"]:checked').value;
|
||||||
const trips = normalizeTrips();
|
const trips = normalizeTrips();
|
||||||
const ride_date = new Date().toISOString().slice(0, 10);
|
const ride_date = new Date().toISOString().slice(0, 10);
|
||||||
|
|
||||||
let payload = null;
|
let payload = null;
|
||||||
if (ticketClass === 'single') {
|
const from = (fromEl.value||'').trim();
|
||||||
const lineName = document.getElementById('singleSegmentSelection').value;
|
const to = (toEl.value||'').trim();
|
||||||
const priceStr = document.getElementById('singleSegmentPrice').value;
|
if(!from || !to){ alert('请完整填写信息'); return; }
|
||||||
if (!lineName || !priceStr) {
|
|
||||||
alert('请选择单程区间');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
payload = { start: lineName, terminal: 'SINGLE_SEGMENT', train_type: 'single', price: Number(priceStr), trips, ride_date };
|
|
||||||
} else {
|
|
||||||
const from = (fromEl.value||'').trim();
|
|
||||||
const to = (toEl.value||'').trim();
|
|
||||||
if(!from || !to){ alert('请完整填写信息'); return; }
|
|
||||||
|
|
||||||
// Auto-fetch price before creating
|
// Auto-fetch price before creating
|
||||||
await previewPrice(true);
|
await previewPrice(true);
|
||||||
payload = { start: from, terminal: to, train_type: type, trips, ride_date };
|
payload = { start: from, terminal: to, train_type: type, trips, ride_date };
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
const res = await fetch('/api/public/orders', { method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(payload) });
|
const res = await fetch('/api/public/orders', { method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify(payload) });
|
||||||
@@ -740,89 +695,8 @@
|
|||||||
document.querySelectorAll('input[name="trainType"]').forEach(el => {
|
document.querySelectorAll('input[name="trainType"]').forEach(el => {
|
||||||
el.onchange = () => { if(fromEl.value && toEl.value) previewPrice(); };
|
el.onchange = () => { if(fromEl.value && toEl.value) previewPrice(); };
|
||||||
});
|
});
|
||||||
document.querySelectorAll('input[name="ticketClass"]').forEach(r => r.addEventListener('change', toggleTicketClass));
|
|
||||||
if (tripsEl) {
|
if (tripsEl) {
|
||||||
tripsEl.oninput = () => { if(fromEl.value && toEl.value) previewPrice(); };
|
tripsEl.oninput = () => { if(fromEl.value && toEl.value) previewPrice(); };
|
||||||
tripsEl.onchange = () => { normalizeTrips(); if(fromEl.value && toEl.value) previewPrice(); };
|
tripsEl.onchange = () => { normalizeTrips(); if(fromEl.value && toEl.value) previewPrice(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTicketClass() {
|
|
||||||
const isSingle = document.querySelector('input[name="ticketClass"]:checked').value === 'single';
|
|
||||||
if (isSingle) {
|
|
||||||
document.getElementById('normalTicketView').style.display = 'none';
|
|
||||||
document.getElementById('singleTicketView').style.display = 'block';
|
|
||||||
document.getElementById('tripsBlock').style.display = 'none';
|
|
||||||
if (tripsEl) tripsEl.value = 1;
|
|
||||||
renderSingleSegments();
|
|
||||||
} else {
|
|
||||||
document.getElementById('normalTicketView').style.display = 'block';
|
|
||||||
document.getElementById('singleTicketView').style.display = 'none';
|
|
||||||
document.getElementById('tripsBlock').style.display = 'block';
|
|
||||||
}
|
|
||||||
previewPrice(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renderSingleSegments() {
|
|
||||||
const listEl = document.getElementById('singleSegmentList');
|
|
||||||
if (listEl.innerHTML.includes('jr-result-row')) return; // Already rendered
|
|
||||||
listEl.innerHTML = '<div class="loading">加载区间数据...</div>';
|
|
||||||
try {
|
|
||||||
const [lines, fares] = await Promise.all([
|
|
||||||
fetch('/api/public/lines').then(r=>r.json()),
|
|
||||||
fetch('/api/public/fares').then(r=>r.json())
|
|
||||||
]);
|
|
||||||
|
|
||||||
const type = document.querySelector('input[name="trainType"]:checked').value;
|
|
||||||
const segments = [];
|
|
||||||
|
|
||||||
lines.forEach(line => {
|
|
||||||
const stops = Array.isArray(line.stops) ? line.stops : (Array.isArray(line.站点列表) ? line.站点列表 : []);
|
|
||||||
const lineFares = new Set();
|
|
||||||
for (let i = 0; i < stops.length - 1; i++) {
|
|
||||||
const a = stops[i], b = stops[i+1];
|
|
||||||
const f = fares.find(x => (x.from === a && x.to === b) || (x.from === b && x.to === a));
|
|
||||||
if (f) {
|
|
||||||
const cost = type === 'Express' ? (f.cost_express ?? f.cost ?? 0) : (f.cost_regular ?? f.cost ?? 0);
|
|
||||||
lineFares.add(Number(cost));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lineFares.forEach(cost => {
|
|
||||||
segments.push({
|
|
||||||
lineName: line.name || line.线路名称 || '未知线路',
|
|
||||||
color: line.color || line.颜色 || '#93a2b7',
|
|
||||||
cost
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (segments.length === 0) {
|
|
||||||
listEl.innerHTML = '<div class="empty-state jr-empty-state"><p>暂无单程区间数据</p></div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = '';
|
|
||||||
segments.forEach((seg, idx) => {
|
|
||||||
html += `
|
|
||||||
<label class="list-item jr-result-row" style="cursor: pointer; display: flex; align-items: center; gap: 12px; padding: 12px;">
|
|
||||||
<input type="radio" name="singleSegmentOption" value="${idx}" onchange="selectSingleSegment('${seg.lineName}', ${seg.cost})" style="margin: 0;">
|
|
||||||
<div style="width: 12px; height: 12px; border-radius: 50%; background-color: ${seg.color};"></div>
|
|
||||||
<div style="flex: 1;">
|
|
||||||
<div style="font-weight: bold; color: #1e293b;">${seg.lineName}</div>
|
|
||||||
<div style="font-size: 12px; color: #64748b;">单区间票</div>
|
|
||||||
</div>
|
|
||||||
<div style="font-weight: bold; color: #0ea5e9;">$${seg.cost}</div>
|
|
||||||
</label>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
listEl.innerHTML = html;
|
|
||||||
} catch (e) {
|
|
||||||
listEl.innerHTML = '<div class="error">加载区间数据失败</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.selectSingleSegment = (lineName, cost) => {
|
|
||||||
document.getElementById('singleSegmentSelection').value = lineName;
|
|
||||||
document.getElementById('singleSegmentPrice').value = cost;
|
|
||||||
previewPrice(true);
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user