feat: 新增客户端问题主动上报与控制台问题汇总功能

1. 客户端 (ticketmachine.lua): 修改 showAlert 支持详细错误并主动上报,使用 pcall 包装主循环捕获 Fatal Error 并上报。
2. 服务端 (data.js, api.js): 增加 issues 数据表,提供 /report_issue 和 /issues 接口。
3. 控制台 (index.html, index.js): 添加“问题汇总”菜单及界面状态管理。
This commit is contained in:
2026-07-24 22:37:26 +08:00
parent be0077743c
commit c840e2abfe
5 changed files with 148 additions and 12 deletions
+39 -10
View File
@@ -327,6 +327,19 @@ local function enqueueTicketUpload(ticketData)
return false
end
local function reportIssue(errMsg, detail)
local url = API_BASE .. '/report_issue'
local payload = {
device = currentDeviceId(),
machine_id = currentDeviceId(),
station_code = state.stationCode or CURRENT_STATION_CODE,
error_msg = tostring(errMsg or ''),
detail = tostring(detail or ''),
ts = (os.epoch and os.epoch('utc')) or (os.time() * 1000)
}
postJSONResult(url, payload)
end
-- ###########################
-- Peripheral discovery
-- ###########################
@@ -1286,14 +1299,22 @@ local function renderConfirmCancel()
addButton(bx+4+bw, by+4, 'NO', bw, 1, {colors.green, colors.white}, function() ui_cancel_confirmed = false; ui_cancel_request = false end)
end
local function showAlert(msg)
local function showAlert(msg, detail)
if detail then
reportIssue(msg, detail)
end
local boxW, boxH = math.max(24, math.min(32, w-4)), 6
local bx, by = math.floor((w-boxW)/2)+1, math.floor((h-boxH)/2)+1
termDev.setBackgroundColor(colors.gray)
for i=0,boxH-1 do termDev.setCursorPos(bx, by+i); termDev.write(string.rep(' ', boxW)) end
msg = tostring(msg or '')
if #msg > boxW-4 then msg = msg:sub(1, boxW-4) end
termDev.setTextColor(colors.white); termDev.setCursorPos(bx+2, by+2); termDev.write(msg)
local displayMsg = tostring(msg or '')
if #displayMsg > boxW-4 then displayMsg = displayMsg:sub(1, boxW-4) end
termDev.setTextColor(colors.white); termDev.setCursorPos(bx+2, by+2); termDev.write(displayMsg)
if detail then
local displayDetail = tostring(detail)
if #displayDetail > boxW-4 then displayDetail = displayDetail:sub(1, boxW-4) end
termDev.setCursorPos(bx+2, by+3); termDev.write(displayDetail)
end
Buttons = {}
addButton(bx+math.floor((boxW-6)/2), by+4, 'OK', 6, 1, {colors.green, colors.white}, function() end)
waitButtons()
@@ -1815,7 +1836,7 @@ local function showTerminal()
invalidUntil = os.epoch('utc') + 1500
sel = nil
state.terminal = nil
showAlert('Same station')
showAlert('Same station', 'Dep and Term are identical')
else
state.terminal = sel
end
@@ -2073,7 +2094,7 @@ local function showOrderAndAudio()
else
local unit = computeCost(state.departure or '', state.terminal or '', state.trainType or 'Local')
if not unit then
showAlert('No route')
showAlert('No route', 'Cannot find fare path')
state.page = 'terminal'
return
end
@@ -2084,7 +2105,7 @@ local function showOrderAndAudio()
end
if state.productMode ~= 'card' and state.trainType ~= 'single' and sameLogicalStation(state.departure, state.terminal) then
showAlert('Same station')
showAlert('Same station', 'Cannot purchase to same station')
state.page = state.voucher_code and 'online' or 'terminal'
return
end
@@ -2097,7 +2118,7 @@ local function showOrderAndAudio()
local function confirmAction()
if processing or confirmed then return end
if state.productMode ~= 'card' and sameLogicalStation(state.departure, state.terminal) then
showAlert('Same station')
showAlert('Same station', 'Dep and Term are identical on confirm')
if render then render() end
return
end
@@ -2411,7 +2432,7 @@ local function showDone()
local issueError = tostring(issueMethod or 'ticket_issue_failed')
print('Ticket issue failed: ' .. issueError)
_G.TICKET_MACHINE_LAST_TICKET.ticket_issue_error = issueError
showAlert('Ticket issue failed')
showAlert('Ticket issue failed', issueError)
resetTicketFlow()
state.page = 'home'
return
@@ -2608,4 +2629,12 @@ local function mainPageLoop()
end
end
parallel.waitForAny(mainPageLoop, backgroundSyncTask, backgroundTicketUploadTask, backgroundPeripheralTask)
local function safeMain()
local ok, err = pcall(parallel.waitForAny, mainPageLoop, backgroundSyncTask, backgroundTicketUploadTask, backgroundPeripheralTask)
if not ok then
reportIssue("Fatal Error", tostring(err))
error(err)
end
end
safeMain()