feat(ui): 新增购票兑换菜单,优化输入交互与界面布局
新增new_menu中间页面,拆分首页NEW按钮为购票和兑换码两个入口 抽离通用输入渲染函数,重构线上券码和IC卡兑换页面的代码逻辑 优化输入交互,按键、退格、清空时实时更新输入显示与提示 完善键盘快捷键支持,调整部分按钮布局与显示文本 统一音频播放逻辑,仅购票时播放欢迎音效
This commit is contained in:
+142
-50
@@ -1440,8 +1440,8 @@ local function showHome()
|
|||||||
local cx = math.floor((w - btnW) / 2) + 1
|
local cx = math.floor((w - btnW) / 2) + 1
|
||||||
addButton(cx, y-4, 'NEW', btnW, btnH, {colors.green, colors.white}, function()
|
addButton(cx, y-4, 'NEW', btnW, btnH, {colors.green, colors.white}, function()
|
||||||
resetTicketFlow()
|
resetTicketFlow()
|
||||||
stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete()
|
stopAudio()
|
||||||
state.page = 'departure'
|
state.page = 'new_menu'
|
||||||
end)
|
end)
|
||||||
addButton(cx, y, 'SINGLE', btnW, btnH, {colors.blue, colors.white}, function()
|
addButton(cx, y, 'SINGLE', btnW, btnH, {colors.blue, colors.white}, function()
|
||||||
resetTicketFlow()
|
resetTicketFlow()
|
||||||
@@ -1452,34 +1452,48 @@ local function showHome()
|
|||||||
resetCardFlow('open')
|
resetCardFlow('open')
|
||||||
stopAudio(); state.page = 'card_home'
|
stopAudio(); state.page = 'card_home'
|
||||||
end)
|
end)
|
||||||
addButton(cx, y+8, 'ONLINE', btnW, btnH, {colors.cyan, colors.white}, function()
|
|
||||||
resetTicketFlow()
|
|
||||||
stopAudio(); state.page = 'online'
|
|
||||||
end)
|
|
||||||
else
|
else
|
||||||
local cx = math.floor((w - btnW) / 2) + 1
|
local cx = math.floor((w - btnW) / 2) + 1
|
||||||
addButton(x1, y-2, 'NEW', btnW, btnH, {colors.green, colors.white}, function()
|
addButton(x1, y-2, 'NEW', btnW, btnH, {colors.green, colors.white}, function()
|
||||||
resetTicketFlow()
|
resetTicketFlow()
|
||||||
stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete()
|
stopAudio()
|
||||||
state.page = 'departure'
|
state.page = 'new_menu'
|
||||||
end)
|
end)
|
||||||
addButton(x2, y-2, 'SINGLE', btnW, btnH, {colors.blue, colors.white}, function()
|
addButton(x2, y-2, 'SINGLE', btnW, btnH, {colors.blue, colors.white}, function()
|
||||||
resetTicketFlow()
|
resetTicketFlow()
|
||||||
stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete()
|
stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete()
|
||||||
state.page = 'single_segment'
|
state.page = 'single_segment'
|
||||||
end)
|
end)
|
||||||
addButton(x1, y+2, 'CARD', btnW, btnH, {colors.orange, colors.white}, function()
|
addButton(cx, y+2, 'CARD', btnW, btnH, {colors.orange, colors.white}, function()
|
||||||
resetCardFlow('open')
|
resetCardFlow('open')
|
||||||
stopAudio(); state.page = 'card_home'
|
stopAudio(); state.page = 'card_home'
|
||||||
end)
|
end)
|
||||||
addButton(x2, y+2, 'ONLINE', btnW, btnH, {colors.cyan, colors.white}, function()
|
|
||||||
resetTicketFlow()
|
|
||||||
stopAudio(); state.page = 'online'
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
waitButtons()
|
waitButtons()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function showNewMenu()
|
||||||
|
while state.page == 'new_menu' do
|
||||||
|
drawHeader('Ticket Service', 'Buy ticket or redeem voucher')
|
||||||
|
Buttons = {}
|
||||||
|
local btnW, btnH = 16, 3
|
||||||
|
local cx = math.floor((w - btnW) / 2) + 1
|
||||||
|
local y = math.floor(h / 2) - 3
|
||||||
|
addButton(cx, y, 'BUY TICKET', btnW, btnH, {colors.green, colors.white}, function()
|
||||||
|
resetTicketFlow()
|
||||||
|
stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete()
|
||||||
|
state.page = 'departure'
|
||||||
|
end)
|
||||||
|
addButton(cx, y + 4, 'REDEEM CODE', btnW, btnH, {colors.cyan, colors.white}, function()
|
||||||
|
resetTicketFlow()
|
||||||
|
stopAudio()
|
||||||
|
state.page = 'online'
|
||||||
|
end)
|
||||||
|
addButton(2, h-3, '<-back', 8, 3, {colors.black, colors.red}, function() state.page = 'home' end)
|
||||||
|
waitButtons()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function showCardHome()
|
local function showCardHome()
|
||||||
while state.page == 'card_home' do
|
while state.page == 'card_home' do
|
||||||
drawHeader('IC Card Service', 'Open card or redeem online order')
|
drawHeader('IC Card Service', 'Open card or redeem online order')
|
||||||
@@ -1643,9 +1657,24 @@ local function showCardOnlineRedeem()
|
|||||||
{'Z','X','C','V','B','N','M'}
|
{'Z','X','C','V','B','N','M'}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function clearLine(y)
|
||||||
|
termDev.setBackgroundColor(colors.black)
|
||||||
|
termDev.setCursorPos(1, y)
|
||||||
|
termDev.write(string.rep(' ', w))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function renderInput()
|
||||||
|
local placeholder = code .. string.rep('_', math.max(0, 5-#code))
|
||||||
|
clearLine(4)
|
||||||
|
clearLine(5)
|
||||||
|
centerText(4, '[' .. placeholder .. ']', colors.yellow)
|
||||||
|
if msg and #msg > 0 then centerText(5, msg, msgCol) end
|
||||||
|
end
|
||||||
|
|
||||||
local function submitCode()
|
local function submitCode()
|
||||||
if #code ~= 5 then
|
if #code ~= 5 then
|
||||||
msg, msgCol = 'Need 5 chars', colors.red
|
msg, msgCol = 'Need 5 chars', colors.red
|
||||||
|
renderInput()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local res = fetchJSON(API_BASE .. '/public/ic-cards/orders/' .. code)
|
local res = fetchJSON(API_BASE .. '/public/ic-cards/orders/' .. code)
|
||||||
@@ -1654,17 +1683,20 @@ local function showCardOnlineRedeem()
|
|||||||
end
|
end
|
||||||
if not (res and res.ok) then
|
if not (res and res.ok) then
|
||||||
msg, msgCol = 'Voucher Invalid', colors.red
|
msg, msgCol = 'Voucher Invalid', colors.red
|
||||||
|
renderInput()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local d = res.data or res
|
local d = res.data or res
|
||||||
local cardStatus = tostring(d.status or ''):lower()
|
local cardStatus = tostring(d.status or ''):lower()
|
||||||
if d.consumed or (cardStatus ~= '' and cardStatus ~= 'pending_pickup' and isCardOrderLike(d)) then
|
if d.consumed or (cardStatus ~= '' and cardStatus ~= 'pending_pickup' and isCardOrderLike(d)) then
|
||||||
msg, msgCol = 'Already Used!', colors.red
|
msg, msgCol = 'Already Used!', colors.red
|
||||||
|
renderInput()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local cardOrder = buildCardOrderState(d, code)
|
local cardOrder = buildCardOrderState(d, code)
|
||||||
if not cardOrder then
|
if not cardOrder then
|
||||||
msg, msgCol = 'Not a card order', colors.red
|
msg, msgCol = 'Not a card order', colors.red
|
||||||
|
renderInput()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
resetCardFlow('redeem')
|
resetCardFlow('redeem')
|
||||||
@@ -1682,10 +1714,7 @@ local function showCardOnlineRedeem()
|
|||||||
end
|
end
|
||||||
|
|
||||||
while state.page == 'card_online' do
|
while state.page == 'card_online' do
|
||||||
local placeholder = code .. string.rep('_', math.max(0, 5-#code))
|
|
||||||
drawHeader('Redeem IC Card', 'Type 5 chars then OK')
|
drawHeader('Redeem IC Card', 'Type 5 chars then OK')
|
||||||
centerText(4, '[' .. placeholder .. ']', colors.yellow)
|
|
||||||
if msg and #msg > 0 then centerText(5, msg, msgCol) end
|
|
||||||
Buttons = {}
|
Buttons = {}
|
||||||
local keyW, keyH = (w < 44 and 2 or 3), 2
|
local keyW, keyH = (w < 44 and 2 or 3), 2
|
||||||
local kbW = 10 * (keyW + 1)
|
local kbW = 10 * (keyW + 1)
|
||||||
@@ -1694,26 +1723,46 @@ local function showCardOnlineRedeem()
|
|||||||
local y, x = sY + (rIdx-1) * (keyH + 1), sX + (rIdx-1)
|
local y, x = sY + (rIdx-1) * (keyH + 1), sX + (rIdx-1)
|
||||||
for _, ch in ipairs(row) do
|
for _, ch in ipairs(row) do
|
||||||
addButton(x, y, ch, keyW, keyH, {colors.black, colors.white}, function()
|
addButton(x, y, ch, keyW, keyH, {colors.black, colors.white}, function()
|
||||||
if #code < 5 then code = code .. ch end
|
if #code < 5 then
|
||||||
|
code = code .. ch
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
x = x + keyW + 1
|
x = x + keyW + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
addButton(2, h-3, '<-back', 8, 3, {colors.black, colors.red}, function() state.page = 'card_home' end)
|
addButton(2, h-3, '<-back', 8, 3, {colors.black, colors.red}, function() state.page = 'card_home' end)
|
||||||
addButton(w-31, h-3, 'BKSP', 8, 3, {colors.black, colors.red}, function() code = code:sub(1, -2) end)
|
addButton(w-31, h-3, 'BKSP', 8, 3, {colors.black, colors.red}, function()
|
||||||
addButton(w-21, h-3, 'CLEAR', 8, 3, {colors.black, colors.red}, function() code = '' end)
|
|
||||||
addButton(w-11, h-3, 'OK', 10, 3, {colors.black, colors.green}, submitCode)
|
|
||||||
local ev, p1, p2, p3 = os.pullEvent()
|
|
||||||
if ev == 'mouse_click' or ev == 'monitor_touch' then
|
|
||||||
for _, b in ipairs(Buttons) do
|
|
||||||
if inRect(b, p2, p3) then clickSound(); if b.onClick then b.onClick() end; break end
|
|
||||||
end
|
|
||||||
elseif ev == 'char' and #code < 5 then
|
|
||||||
code = code .. tostring(p1 or ''):upper()
|
|
||||||
elseif ev == 'key' and p1 == keys.backspace then
|
|
||||||
code = code:sub(1, -2)
|
code = code:sub(1, -2)
|
||||||
elseif ev == 'key' and (p1 == keys.enter or p1 == keys.numPadEnter) then
|
msg = ''
|
||||||
submitCode()
|
renderInput()
|
||||||
|
end)
|
||||||
|
addButton(w-21, h-3, 'CLEAR', 8, 3, {colors.black, colors.red}, function()
|
||||||
|
code = ''
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
end)
|
||||||
|
addButton(w-11, h-3, 'OK', 10, 3, {colors.black, colors.green}, submitCode)
|
||||||
|
renderInput()
|
||||||
|
while state.page == 'card_online' do
|
||||||
|
local ev, p1, p2, p3 = os.pullEvent()
|
||||||
|
if ev == 'mouse_click' or ev == 'monitor_touch' then
|
||||||
|
for _, b in ipairs(Buttons) do
|
||||||
|
if inRect(b, p2, p3) then clickSound(); if b.onClick then b.onClick() end; break end
|
||||||
|
end
|
||||||
|
elseif ev == 'char' and #code < 5 then
|
||||||
|
code = code .. tostring(p1 or ''):upper()
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
elseif ev == 'key' and p1 == keys.backspace then
|
||||||
|
code = code:sub(1, -2)
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
elseif ev == 'key' and (p1 == keys.enter or p1 == keys.numPadEnter) then
|
||||||
|
submitCode()
|
||||||
|
elseif ev == 'config_updated' then
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2531,6 +2580,20 @@ local function showOnlineVoucher()
|
|||||||
{'Z','X','C','V','B','N','M'}
|
{'Z','X','C','V','B','N','M'}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function clearLine(y)
|
||||||
|
termDev.setBackgroundColor(colors.black)
|
||||||
|
termDev.setCursorPos(1, y)
|
||||||
|
termDev.write(string.rep(' ', w))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function renderInput()
|
||||||
|
local placeholder = code .. string.rep('_', math.max(0, 5-#code))
|
||||||
|
clearLine(4)
|
||||||
|
clearLine(5)
|
||||||
|
centerText(4, '[' .. placeholder .. ']', colors.yellow)
|
||||||
|
if msg and #msg > 0 then centerText(5, msg, msgCol) end
|
||||||
|
end
|
||||||
|
|
||||||
local function submitCode()
|
local function submitCode()
|
||||||
if #code == 5 then
|
if #code == 5 then
|
||||||
local res = fetchJSON(API_BASE .. '/public/orders/' .. code)
|
local res = fetchJSON(API_BASE .. '/public/orders/' .. code)
|
||||||
@@ -2540,6 +2603,7 @@ local function showOnlineVoucher()
|
|||||||
-- Check if already consumed
|
-- Check if already consumed
|
||||||
if d.consumed then
|
if d.consumed then
|
||||||
msg, msgCol = 'Already Used!', colors.red
|
msg, msgCol = 'Already Used!', colors.red
|
||||||
|
renderInput()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2553,15 +2617,16 @@ local function showOnlineVoucher()
|
|||||||
state.page = 'order'
|
state.page = 'order'
|
||||||
else
|
else
|
||||||
msg, msgCol = 'Voucher Invalid', colors.red
|
msg, msgCol = 'Voucher Invalid', colors.red
|
||||||
|
renderInput()
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
msg, msgCol = 'Need 5 chars', colors.red
|
||||||
|
renderInput()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while state.page == 'online' do
|
while state.page == 'online' do
|
||||||
local placeholder = code .. string.rep('_', math.max(0, 5-#code))
|
|
||||||
drawHeader('Enter Voucher', 'Type 5 chars then OK')
|
drawHeader('Enter Voucher', 'Type 5 chars then OK')
|
||||||
centerText(4, '[' .. placeholder .. ']', colors.yellow)
|
|
||||||
if msg and #msg > 0 then centerText(5, msg, msgCol) end
|
|
||||||
Buttons = {}
|
Buttons = {}
|
||||||
|
|
||||||
local keyW, keyH = (w < 44 and 2 or 3), 2
|
local keyW, keyH = (w < 44 and 2 or 3), 2
|
||||||
@@ -2571,7 +2636,13 @@ local function showOnlineVoucher()
|
|||||||
for rIdx, row in ipairs(rows) do
|
for rIdx, row in ipairs(rows) do
|
||||||
local y, x = sY + (rIdx-1) * (keyH + 1), sX + (rIdx-1)
|
local y, x = sY + (rIdx-1) * (keyH + 1), sX + (rIdx-1)
|
||||||
for _, ch in ipairs(row) do
|
for _, ch in ipairs(row) do
|
||||||
addButton(x, y, ch, keyW, keyH, {colors.black, colors.white}, function() if #code < 5 then code = code .. ch end end)
|
addButton(x, y, ch, keyW, keyH, {colors.black, colors.white}, function()
|
||||||
|
if #code < 5 then
|
||||||
|
code = code .. ch
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
end
|
||||||
|
end)
|
||||||
x = x + keyW + 1
|
x = x + keyW + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2581,27 +2652,47 @@ local function showOnlineVoucher()
|
|||||||
local gap = 2
|
local gap = 2
|
||||||
local total = bw + gap + cw + gap + ow
|
local total = bw + gap + cw + gap + ow
|
||||||
local ax = math.floor((w - total) / 2) + 1
|
local ax = math.floor((w - total) / 2) + 1
|
||||||
|
local smallMode = false
|
||||||
if ax < 1 then
|
if ax < 1 then
|
||||||
|
smallMode = true
|
||||||
bw, cw, ow = 6, 6, 6
|
bw, cw, ow = 6, 6, 6
|
||||||
total = bw + 1 + cw + 1 + ow
|
gap = 1
|
||||||
|
total = bw + gap + cw + gap + ow
|
||||||
ax = math.floor((w - total) / 2) + 1
|
ax = math.floor((w - total) / 2) + 1
|
||||||
addButton(math.max(1, ax), actY, 'BKSP', bw, 3, {colors.black, colors.red}, function() code = code:sub(1, -2) end)
|
|
||||||
addButton(math.max(1, ax) + bw + 1, actY, 'CLEAR', cw, 3, {colors.black, colors.red}, function() code = '' end)
|
|
||||||
addButton(math.max(1, ax) + bw + 1 + cw + 1, actY, 'OK', ow, 3, {colors.black, colors.green}, submitCode)
|
|
||||||
else
|
|
||||||
addButton(ax, actY, 'Backspace', bw, 3, {colors.black, colors.red}, function() code = code:sub(1, -2) end)
|
|
||||||
addButton(ax + bw + gap, actY, 'Clear', cw, 3, {colors.black, colors.red}, function() code = '' end)
|
|
||||||
addButton(ax + bw + gap + cw + gap, actY, 'OK', ow, 3, {colors.black, colors.green}, submitCode)
|
|
||||||
end
|
end
|
||||||
|
local bx = math.max(1, ax)
|
||||||
|
addButton(bx, actY, smallMode and 'BKSP' or 'Backspace', bw, 3, {colors.black, colors.red}, function()
|
||||||
|
code = code:sub(1, -2)
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
end)
|
||||||
|
addButton(bx + bw + gap, actY, smallMode and 'CLEAR' or 'Clear', cw, 3, {colors.black, colors.red}, function()
|
||||||
|
code = ''
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
end)
|
||||||
|
addButton(bx + bw + gap + cw + gap, actY, 'OK', ow, 3, {colors.black, colors.green}, submitCode)
|
||||||
addButton(2, h-3, '<-back', 8, 3, {colors.black, colors.red}, function() state.page = 'home' end)
|
addButton(2, h-3, '<-back', 8, 3, {colors.black, colors.red}, function() state.page = 'home' end)
|
||||||
|
|
||||||
local ev, p1, p2, p3 = os.pullEvent()
|
renderInput()
|
||||||
if ev == 'mouse_click' or ev == 'monitor_touch' then
|
while state.page == 'online' do
|
||||||
for _, b in ipairs(Buttons) do if inRect(b, p2, p3) then clickSound(); if b.onClick then b.onClick() end; break end end
|
local ev, p1, p2, p3 = os.pullEvent()
|
||||||
elseif ev == 'char' and #code < 5 then code = code .. p1:upper()
|
if ev == 'mouse_click' or ev == 'monitor_touch' then
|
||||||
elseif ev == 'key' and p1 == keys.backspace then code = code:sub(1, -2)
|
for _, b in ipairs(Buttons) do
|
||||||
elseif ev == 'key' and (p1 == keys.enter or p1 == keys.numPadEnter) then submitCode()
|
if inRect(b, p2, p3) then clickSound(); if b.onClick then b.onClick() end; break end
|
||||||
elseif ev == 'config_updated' then
|
end
|
||||||
|
elseif ev == 'char' and #code < 5 then
|
||||||
|
code = code .. tostring(p1 or ''):upper()
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
elseif ev == 'key' and p1 == keys.backspace then
|
||||||
|
code = code:sub(1, -2)
|
||||||
|
msg = ''
|
||||||
|
renderInput()
|
||||||
|
elseif ev == 'key' and (p1 == keys.enter or p1 == keys.numPadEnter) then
|
||||||
|
submitCode()
|
||||||
|
elseif ev == 'config_updated' then
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2612,6 +2703,7 @@ end
|
|||||||
local function mainPageLoop()
|
local function mainPageLoop()
|
||||||
while true do
|
while true do
|
||||||
if state.page == 'home' then showHome()
|
if state.page == 'home' then showHome()
|
||||||
|
elseif state.page == 'new_menu' then showNewMenu()
|
||||||
elseif state.page == 'single_segment' then showSingleSegmentSelection()
|
elseif state.page == 'single_segment' then showSingleSegmentSelection()
|
||||||
elseif state.page == 'card_home' then showCardHome()
|
elseif state.page == 'card_home' then showCardHome()
|
||||||
elseif state.page == 'card_name' then showCardNameInput()
|
elseif state.page == 'card_name' then showCardNameInput()
|
||||||
|
|||||||
Reference in New Issue
Block a user