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