Files
FSE-Ticket.sys/IC储蓄卡功能开发.md
T
2026-06-21 10:00:13 +08:00

122 KiB
Raw Blame History

IC储蓄卡功能开发

Workspace: d:\TaoYuan2023_HSTG\HSTG\Lua\TicketMachine


User

现在我们新增了一个IC储蓄卡的功能。MOD那边的API是这样的: IC 卡系统 Lua API

售票机 ticket_vending_machine 新增 方法 | 返回 issueTicket(...) | true, ticketId issueICCard() | true, cardId local ok, cardId = vm.issueICCard(

充值机 ic_refill_machine 方法 | 参数 | 返回 | 说明 getCardInfo() | 无 | table |卡号/持有者/余额 refill(amount) | 金额 | true, 新余额 | 充值 deduct(amount) | 金额 | true, 新余额 | 扣费

操作流程:手持卡右键方块 → 插入 → CC 操作 → ejectCard() 取出


所以要加:

  1. d:\TaoYuan2023_HSTG\HSTG\Lua\TicketMachine\ticketmachine.lua 添加一个“办卡”的页面。点击按钮后开始办卡。首先输入卡的使用者。输入框下面有26键键盘以及符号键盘。确定之后选择首次充值余额。可以自定义,也可以选择预设:5、10、15、20。然后选择之后就像“购票支付” d:\TaoYuan2023_HSTG\HSTG\Lua\TicketMachine\ticketmachine.lua#L902-902 那样付钱充值.完成之后就出卡。把使用者名、余额传入出票机。同时也要向网页端记录该卡。

  2. 检票模块: 卡右键检票机,向卡中传入进站的车站编号。出站时通过识别卡中的进站站台编号和当前出站站台编号,计算路费。计算后扣费后向卡中传入新的余额。

其它“已经进站”/“还未进站”等提示照旧

3.网页控制台端。加上新的记录卡信息的/api 地址。然后后台也能看到卡的所有信息(类似车票)。同时也要在公开的页面显示,加入查询的索引。

  1. 线上购卡 和线上购票类似,通过线上购卡,然后获得凭证,在computercraft中通过凭证兑卡。

请注意,所有网页的css请按照当前的css风格(模仿JR官网 https://www.jreast.co.jp/

Assistant

我先梳理现有代码结构,重点看 ticketmachine.lua 的购票支付流程、检票模块的进出站逻辑,以及网页端现有的票据 API 和展示页,确认新加 IC 储蓄卡最合适的接入点。

我已做了完整检索,覆盖了售票机主程序、线上预定页、凭证详情页,以及闸机核验侧代码;下面按“页面结构、输入交互、支付、出票、接入点”汇总。

主流程

  • 主状态机在 mainPageLoop,页面顺序是 home -> departure -> terminal -> type -> trips -> order -> done,线上兑票走 home -> online -> order -> done
  • 首页模式选择在 showHome,只有两个入口:NEW 新购票、ONLINE 凭证兑票
  • 站点/线路选择页共用 renderLinesSelection,数据来自 CFG.lines / CFG.stations
  • 订单确认与支付页在 showOrderAndAudio,出票完成页在 showDone
  • 线上凭证输入页在 showOnlineVoucher

页面结构

  • home:模式选择页,按钮为 NEW / ONLINE,进入不同购票链路,见 showHome
  • departure:选择出发站,见 showDeparture
  • terminal:选择到达站,禁止同站,见 showTerminal
  • type:选择 Local / Express,见 showType
  • trips:选择乘次数量,+/- 调整,见 showTrips
  • order:显示区间、车型、次数、金额、支付进度条、确认按钮,见 drawOrdershowOrderAndAudio
  • done:真正执行出票/上传后展示票据信息,见 showDone
  • online5 位凭证码输入页,见 showOnlineVoucher

输入交互

支付流程

  • 票价计算核心在 computeCost,基于 CFG.fares 构造普通/特急邻接图后跑最短路
  • 订单页会先重算价格,避免旧数据残留,见 showOrderAndAudio
  • 支付状态 UI 在 drawOrder,显示 Paid / Remaining 与进度条
  • 实际“收款”逻辑很简单:每次 redstone.getInput('right')false -> truestate.paid += 1,见 showOrderAndAudio
  • 付满后自动确认;若金额为 0 也会自动确认,见 confirmActionauto-confirm
  • 当前代码没有接现金/扫码/银行卡 API;支付接入本质上就是把外设回调换成更新 state.paid,或重写确认条件

出票流程

线上预定关联文件

建议接入点

  • 支付外设接入:优先改 showOrderAndAudio,把 redstone right 替换为投币器/纸币器/扫码回调,再统一累加到 state.paid
  • 支付校验扩展:改 confirmAction,可在确认前增加第三方支付结果查询、超时撤单、退款判断
  • 出票设备接入:改 showDone,当前已预留 ticket_vending_machine.issueTicket(...),这是最直接硬件接入点
  • 售票上报接入:改 uploadTicketRecord,适合补充订单号、支付渠道、设备编号、流水号
  • 线上订单联动:改 showOnlineVoucher.submitCodeconsume voucher,适合加预锁定、核销前校验、重复兑票保护
  • 闸机验票联动:下游在 gate.lua,用于票据进出站核验,适合作为出票后全链路闭环入口

结论

  • ticketmachine.lua 已具备完整页面状态机、触摸/键盘交互、基础支付累加、出票外设调用、售票上传与离线补传
  • 当前最薄弱的部分是“支付”,本质仍是右侧红石脉冲计数,不是真正支付网关
  • 最推荐的三个接入点是:showOrderAndAudio 的支付监听、confirmAction 的核销确认、showDoneissueTicket 外设调用

如果你需要,我下一步可以继续把这些流程整理成一张“页面/状态/事件/接口”的接入表。

已做一轮完整检索,覆盖 gate.lua、售票机 Lua、安装脚本,以及与闸机校验直接对应的服务端路由;结论是:当前 Lua 侧已经有比较清晰的“票卡介质适配层”,可直接扩展为 IC 卡接入。

结论

  • 最核心接入点在 gate.lua:闸机不直接操作软盘文件,而是依赖 ticket_inspection_machine 外设的事件和方法,这里最适合替换/扩展为 IC 卡读写器。
  • 售票/发卡接入点在 ticketmachine.lua:售票机通过 ticket_vending_machine.issueTicket(...) 下发票据,可平移为“写 IC 卡初始化数据/发新卡”。
  • 进出站状态维护分两层:Lua 侧只做事件接收、服务器请求、回写介质;真正的状态判定和扣次在服务端 api.js
  • 如果要支持 IC 卡,优先保留现有 HTTP 校验协议不变,只替换 Lua 外设层,改造成本最低。

进出站状态

票据读写

  • 读票不是 Lua 自己读文件,而是等待外设抛出 ticket_scanned 事件,并从事件里拿 scan 表,见 gate.lua:L530-L545
  • 票号读取统一走 getTicketId,兼容 ticketId/ticket_id/id 多种字段。
  • 起终点读取统一走 getStartStation,兼容多种字段名,也支持从站名反推站码。
  • 验票通过后,Lua 侧对介质回写在 gate.lua:L632-L659updateTicket("entered"/"exited"/"rides", ...),以及必要时 destroyTicket()
  • 售票写票在 ticketmachine.lua:L1236-L1249:调用 ticket_vending_machine.issueTicket(...),这是发新票/初始化卡片的现成接入点。

扣费与校验

  • gate.lua 自己不做本地票价计算,也不直接扣费;它只向 /api/tickets/check 提交 ticket_id + action + station_codes + trips_*,见 postCheckdoCheck
  • 服务端才是权威校验点:进站校验起点站、是否已进站;出站校验终点站、是否已进站/已出站,并在出站时 trips_remaining - 1,见 api.js:L650-L697
  • 服务端返回 resp.trips_remainingresp.destroy_ticketLua 再把剩余次数写回票介质,见 gate.lua:L632-L659
  • 售票后会把票据记录上传到后端,见 enqueueTicketUploadticketmachine.lua:L1308-L1310

可扩展为 IC 卡的接入点

  • ticket_inspection_machine 外设接口是第一接入点,见 gate.lua:L479-L493;只要 IC 卡读卡器实现:
  • 抛出 ticket_scanned 事件,并提供一个 scan 表,至少含 ticket_id/id,最好再带 entered/exited/trips_remaining/start_station/terminal_station
  • 提供 updateTicket(key, value),用于进出站后回写卡内状态。
  • 提供 destroyTicket(),用于单次票或次数耗尽后的废票/锁卡处理。
  • ticket_vending_machine.issueTicket(...) 是第二接入点,见 ticketmachine.lua:L1236-L1249;可替换成 IC 发卡器,把新票初始数据写入空卡。
  • 安装配置层已支持闸机模式和站点下发,见 installer.luainstaller_bi.lua;无需改协议,只需新增 IC 设备部署说明。

建议方案

  • 最小改造:保留 gate.lua 与后端 /tickets/check 协议不变,实现一个兼容 ticket_inspection_machine 的 IC 卡外设。
  • 如果 IC 卡要做脱机校验,可把 handleScandoCheck() 前后拆成“本地预校验 + 在线确认 + 回写卡片”三段。
  • 如果要支持储值卡而不是次数票,新增点应放在服务端 /tickets/check 与售票记录模型,不建议把扣费逻辑直接塞进 Lua。
  • 当前代码最像“票卡介质抽象层”的其实不是文件读写,而是这两个外设接口:ticket_inspection_machineticket_vending_machine

如果你要,我下一步可以直接给你整理一份“IC 卡外设最小接口协议”,按这个项目现有 Lua 调用方式列出函数签名和事件格式。

我已做过一轮较完整的代码检查,覆盖了管理控制台、公开查询/预定页、后端路由、MySQL 持久化,以及售票机侧的线上兑票流程;结论如下。

现状总览

当前已存在的票据/兑票结构

IC 卡现状

  • 项目里没有独立的 “IC 卡记录 / IC 卡索引 / IC 卡事件” 后端结构。
  • 现有模型只有“纸票/电子票 ticket”与“线上凭证 order”;也就是线上兑票已经有,但“兑卡”还没有独立数据层。

新增 IC 卡记录与线上兑卡,最少需要的文件

建议新增的数据结构

  • ic_cards
    • card_id
    • physical_uid
    • status (active/lost/disabled)
    • balance
    • deposit
    • holder
    • bind_order_code
    • created_ts
    • updated_ts
  • ic_card_events
    • card_id
    • type (issue/topup/redeem/entry/exit/refund/disable)
    • station_code
    • device
    • amount
    • ticket_id
    • order_code
    • ts
  • ic_order_index 或复用 orders
    • 若复用现有 orders,建议加字段:
    • fulfill_type: 'ticket' | 'ic_card'
    • target_card_id
    • redeem_status
    • redeemed_ts

建议新增的 API 结构

  • 管理端
    • GET /api/ic-cards
    • GET /api/ic-cards/:id
    • POST /api/ic-cards
    • PUT /api/ic-cards/:id
    • GET /api/ic-cards/:id/events
  • 公开端
    • POST /api/public/ic-orders 创建线上兑卡订单
    • GET /api/public/ic-orders/:code 查询兑卡凭证
    • POST /api/public/ic-orders/:code/redeem 售票机/制卡机兑卡
  • 设备端
    • 若沿用现有订单体系,也可直接在 orders 上扩展 redeem_target='ic_card'

落地建议

  • 最稳妥的做法是:不要硬改现有 ticket_index;新增 ic_cardsic_card_events 两套表。
  • 线上兑卡优先复用现有 orders/order_index 流程,只给订单加 fulfill_type='ic_card' 和兑卡结果字段。
  • 控制台可在现有 “凭证管理” 基础上增加 “IC卡记录” 视图,避免再开一套后台框架。
  • 售票机侧已具备线上凭证输入/消费骨架,新增“兑卡”只需要在 ticketmachine.lua:L1338-L1424 的成功分支里把目标从“出票”改为“写卡/绑卡”。

如果你要,我下一步可以直接按这个检查结果,继续给你整理一份“IC 卡功能改造清单(逐文件到字段级)”。

现有结构已经比较清楚了:Lua 侧有成熟的页面状态机、支付与出票上传链路,后端也有票据与线上凭证模型。接下来我会直接读关键文件,把 IC 卡需要复用和新增的字段、页面、API 精确到函数级,再开始落代码。

我改成分段读取,避免一次性拉太多文件导致 IDE 超时。先把将要修改的函数和路由精确定位出来,再逐段展开读取。

我已经锁定了关键函数和路由位置,现在开始逐段读取并准备改动方案。先从 ticketmachine.lua 的页面流和 data.js 的存储接口入手,因为这两处会决定前后端字段是否能对齐。

读取工具对这些大文件持续超时,我改用终端按行读取,并顺手检查工作区当前改动,避免误覆盖你已有的内容。

这里有一个关键不确定点:你给的 MOD API 里只明确了 issueICCard() 和充值机 getCardInfo/refill/deduct,但没有看到“在办卡时写入持有者名/初始余额”以及“检票机读写进站站号”的具体 Lua 接口。
我先继续把现有代码读细,确认哪些地方可以先做;如果最终缺少 MOD 侧读写接口,我会单独把那一处列出来请你补一下函数名。

我继续往前做,不过会把缺失的 MOD 接口影响控制在最小范围内:先把服务端、网页端和售票机办卡流程落好,Lua 检票端则按“卡数据同步到服务端、设备侧尽量兼容现有事件格式”的思路实现。等你补到精确接口名时,只需要替换设备读写那一层。

先梳理 TicketMachine 现有 Lua 工程结构,定位办卡、支付、兑卡、闸机扣费和外设事件相关代码,再基于现有流程做兼容式扩展。

先查看 TicketMachine 项目的结构和现有网页/后端实现方式,确认应该在什么技术栈和目录下扩展 IC 卡相关功能。

已经定位到 serverweb 目录,下一步会读现有数据层、路由和票务页面,沿用当前项目的接口风格与样式结构扩展 IC 卡功能。

已定位到核心文件主要集中在 ticketmachine.luagate.lua,接下来读取主流程并搜索现有卡务、支付、同步、外设事件入口。

主流程里目前已有纸票购票和线上券核销基础,但还没有会员卡/IC 卡独立状态与首充支付流程。我在继续查找是否已有服务端卡同步字段或外设读卡事件约定,避免破坏现有事件模型。

现有项目已经有票务、订单和公共查询的雏形,我在继续确认后台单页应用的导航与渲染方式,再把 IC 卡页面无缝接进去。

我在改动前先补齐后台入口与现有订票页的上下文;刚才直接读大文件超时了,改用检索关键片段定位页面和渲染函数。

还需要对齐服务端已有接口字段,尤其是 /tickets/check 和线上订单相关返回结构,这样 Lua 侧新增卡务字段才能尽量复用当前同步模型。

我已经确认后台是 Vue 单页,接下来直接抓取 index.js 里视图状态和票据模块的关键代码片段,避免盲改导航和渲染逻辑。

我已经确认仓库里还有 Node 服务端代码,接下来直接读服务端入口,核对 Lua 侧要提交的字段名和可复用接口。

当前结构已经够用,我准备开始落地实现:先补数据层和 API,再把后台列表详情与公开查询/购卡页接上现有导航和样式。

先获取后台模板和脚本的精确片段,随后会直接修改导航、数据源与新页面文件。

服务端文件较大,我改用按路由片段抓取,重点看售票上传、检票、订单查询/核销几段实现。

准备按两条线改:

  1. ticketmachine.lua 增加卡务状态、办卡入口、姓名软键盘、首充金额与支付确认、线上兑卡查询与确认,并把写卡/建卡动作做成“优先调用已存在外设能力,缺失时保留同步与日志”的兼容实现。
  2. gate.lua 保持 ticket_scanned 事件不变,新增 IC 卡识别与服务端同步扣费分支,优先走卡专用同步接口,失败时不影响原纸票检票逻辑,同时把返回的余额/进站站点等字段尽量写回现有 updateTicket。开始改 ticketmachine.lua,先补通用请求与卡务辅助函数,再接页面流转和确认逻辑。

我在按最小侵入方案设计 IC 卡数据模型:一张卡对应基础信息、状态、余额和事件流;线上购卡会生成订单号并同步落卡,后台直接基于这套记录做列表和详情。

辅助函数已补进主文件,现在先回读最新片段,再接首页入口、卡务页面和确认提交流程,避免和刚才的改动冲突。

首页和卡务子页面已经接上,下一步改确认页与完成页,让票务和卡务共用支付界面,但按不同产品分支处理。

先改服务端数据层,增加 IC 卡主表与事件流,后面的管理和公开页面都会直接复用这套接口。

现在补管理 API:提供 IC 卡列表、详情、建卡、编辑和充值,前端后台页面就能直接接这几个接口。

再补公开 API:支持线上购卡下单、按卡号或订单号查询,以及给购卡页读取卡种配置。

后端数据和公开接口已经补上,接着把新页面路由挂到 Express,保证 IC 卡查询和购卡页可以直接访问。

售票机主线基本串起来了,开始处理 gate.lua:保持现有 ticket_scanned 事件入口不变,只在识别到 IC 卡字段时切到卡务同步分支。

我改成最小侵入的后台接入方式:在现有控制台侧边栏增加一个独立的 IC 卡管理页入口,这样不用重写原有 Vue 控制台也能保持一致风格。

接下来新增三个页面文件:后台 IC 卡管理页、公开查询页和线上购卡页;页面结构会直接复用现在的深色后台和 JR 风格公开页。

代码已经落到两个 Lua 主文件里,我先跑编辑器诊断看有没有明显语法或引用问题,再决定是否需要补一轮修正。

诊断没报语法问题,但我在回读 gate.lua 时发现一个运行时风险:getCardId() 提前引用了后面才声明的 firstNonEmpty(),这类 Lua 闭包在本文件写法下可能取到全局空值。我先修这个,再做一次最终检查。

语法检查已经过了,环境里没有可直接调用的 lua/luajit 解释器,所以验证会以编辑器诊断和静态流程核对为主。我再取一份变更摘要和关键片段行号,方便你对照 review。

再做一处稳健性修正:writeICCard() 目前把“调用成功但返回 false”也当成写卡成功,我把这个分支收紧,避免外设明确失败时误报完成。

页面文件已经生成,最后补一组很薄的样式扩展,让 IC 卡表单、卡种卡片和后台详情区能贴合现有视觉体系。

我顺手把首页入口补上,让新增的 IC 卡查询和线上购卡页能从门户页直接进入,不会变成隐藏功能。

已修改

  • ticketmachine.lua:首页新增 CARD 入口,加入办卡子菜单、姓名输入软键盘、首充金额选择、线上兑卡页。
  • ticketmachine.lua:确认页兼容票务/卡务双模式,首充走现有右侧红石支付,线上兑卡支持免现场支付确认。
  • ticketmachine.lua:新增卡片完成页与 logs/last_card_issue.json 快照记录,保留原纸票完成页。
  • ticketmachine.lua:补充卡片写卡辅助、卡务同步请求封装、外设方法兼容探测。
  • gate.lua:新增 IC 卡识别字段解析,兼容 card_id、余额、媒体类型等扫描数据。
  • gate.lua:新增 IC 卡进出站同步扣费分支,按服务端返回写回 entered/exited/balance/entry_station/exit_station/last_fare
  • gate.lua:主循环继续只监听 ticket_scanned,检测到卡数据时切到卡务分支,纸票分支保持原逻辑。

实现说明

  • 办卡流程:Home -> CARD -> OPEN CARD -> 姓名输入 -> 首充金额 -> 支付确认 -> 写卡完成
  • 姓名输入:使用屏幕软键盘与 char/key 事件,支持字母、数字、空格,限制 12 字符。
  • 首充支付:复用原 order 页面与右侧红石投币/支付事件,尽量不改既有外设触发方式。
  • 线上兑卡:CARD -> ONLINE REDEEM 输入 5 位码,读取 /public/orders/:code,识别为卡订单后直接进入确认页。
  • 写卡兼容:优先尝试 issueCard / issueICCard / writeCard / writeICCard / issueTicketData / writeTicketData,成功后记录快照;明确返回 false 时判定写卡失败。
  • 闸机扣费:继续走服务端判定模型,只是把纸票 /tickets/check 扩到卡片 /cards/check,并保留双向闸机的自动进出站推断逻辑。

假设

  • 服务端提供 POST /api/cards/open 用于办卡/线上兑卡建卡,返回可包含 card_id / balance / deposit / topup
  • 服务端提供 POST /api/cards/check,返回结构尽量仿照现有 /api/tickets/check,至少包含 result,可选 reason / balance / fare / entry_station / exit_station
  • IC 卡介质仍通过现有 ticket_scanned 事件上报,只是扫描表里会带 card_idbalancemedia 等字段。
  • 售票机外设若没有上述写卡方法,则本次实现会停在“写卡失败”而不会伪装成功;同时保留 logs/last_card_issue.json 便于后续对接外设。
  • 姓名输入按当前 CC:Tweaked 屏幕与键盘能力处理为 ASCII 软键盘,不做中文输入法。

验证结果

  • 诊断检查:ticketmachine.luagate.lua 的 VS Code 诊断均为 0 错误。
  • 运行环境检查:当前环境未发现 lualuajit 可执行文件,无法做本地解释执行验证。
  • 静态流程核对:已逐段检查办卡首页流转、姓名输入、首充确认、线上兑卡确认、卡完成页,以及闸机 ticket_scanned -> isICCardScan() -> handleICCardScan() 分支闭环。
  • 仓库状态:当前目录不是 Git 仓库,无法直接给出 git diff/提交号;如果你需要,我可以下一步按这两份文件再整理一版人工 diff 摘要。

建议下一步

  • 先在服务端补齐或确认 /api/cards/open/api/cards/check 的字段名。
  • 在真实外设上确认 ticket_vending_machine 的写卡方法名;如果你给我外设 API,我可以把兼容探测改成精确调用。
  • 如果你希望,我可以继续补一版服务端 cards 路由,和网页后台里的卡订单管理。

home.html 修改时工具超时了,我改用精确替换的方式更新首页卡片文案和入口,避免影响现有结构。

功能已经接上,下面开始做静态检查和实际启动验证,优先排语法错误和明显的页面脚本问题。

语法检查已通过。现在启动一次本地服务并打几个新接口,确认路由和页面文件都能被服务器正常挂载。

服务正在拉起,我改用更小输出重试状态检查,确认端口和启动日志即可。

新页面请求返回了 404,我在看服务端运行日志和实际加载的 server/index.js,确认是不是需要重启或有路由没生效。

端口 23333 已被现有进程占用,所以我改在 23334 启动修改后的版本做独立验证,这样能确认新增路由确实来自本次代码。

新页面路由已经在 23334 验证为 200。我再补一次接口验证,确认健康检查和新增 IC 卡公开配置接口也可响应。

再做一组端到端验证:调用线上购卡接口生成一张测试卡,再用公开查询接口回查,确认关键流程闭环可用。

我补一个容错修正:让 IC 卡事件流同时写入内存缓存。这样即使本地没起 MySQL,测试环境下也能正常看到详情时间线,不影响正式有库部署。

事件缓存修正已完成,我会重启 23334 的验证实例,再确认查询接口能带出事件记录。

已完成

  • 已在项目中补齐 IC 卡数据存储、管理 API、公开 API、后台列表详情页、公开查询页、线上购卡页,并复用现有深色后台与 JR 风格公开页样式。
  • 后端新增 ic_cards / ic_card_events 数据结构,同时做了内存缓存兜底,便于本地无 MySQL 时继续联调。
  • 后台入口已加到现有控制台侧栏,首页也补了 IC 卡相关服务入口。

修改文件

新增能力

  • 管理 API
    • GET /api/ic-cards
    • GET /api/ic-cards/:id
    • POST /api/ic-cards
    • PUT /api/ic-cards/:id
    • POST /api/ic-cards/:id/topup
  • 公开 API
    • GET /api/public/ic-cards/config
    • GET /api/public/ic-cards/query?q=...
    • GET /api/public/ic-cards/orders/:code
    • POST /api/public/ic-cards/orders
  • 页面路由:
    • /admin/ic-card
    • /ic-card/order
    • /ic-card/search

验证结果

  • 语法检查通过:
    • node --check server/index.js
    • node --check server/routes/api.js
    • node --check server/routes/public.js
    • node --check server/services/data.js
    • node --check web/ic-card-admin.js
    • node --check web/ic-card-order.js
    • node --check web/ic-card-search.js
  • 编辑后诊断结果:
    • GetDiagnostics 返回空,无新增诊断错误。
  • 本地服务验证:
    • 因现有 23333 端口已被占用,验证实例使用 PORT=23334
    • GET http://127.0.0.1:23334/ic-card/order 返回 200
    • GET http://127.0.0.1:23334/ic-card/search 返回 200
    • GET http://127.0.0.1:23334/admin/ic-card 返回 200
    • GET http://127.0.0.1:23334/api/public/health 返回正常
    • GET http://127.0.0.1:23334/api/public/ic-cards/config 返回卡种配置
    • GET http://127.0.0.1:23334/api/ic-cards 返回空列表后可正常写入
    • POST /api/public/ic-cards/orders 已成功生成测试订单与卡号
    • GET /api/public/ic-cards/query?q=卡号/订单号 已成功回查,并能返回事件流
    • POST /api/ic-cardsGET /api/ic-cards/:id 已成功验证后台建卡与详情读取

说明

  • 本机 127.0.0.1:3306 当前未启动 MySQL,启动日志里有 ECONNREFUSED;因此本次运行验证主要依赖新增的内存缓存兜底。
  • 正式环境只要 MySQL 可用,代码已会自动建表并写入 ic_cardsic_card_events

两块改动都已经落下来了。我先在主会话里做一次交叉核对,确认新增的 IC 路由、页面入口和 Lua 页面状态都在项目里,并补跑一次诊断,避免子任务改完后还有漏项。

我做交叉核对时发现一个需要马上确认的点:Lua 侧现在已经调用了 /api/cards/open/api/cards/check,但我还要再检查服务端是否真的暴露了这两个设备接口,避免页面改完但机台联不起来。

我确认到一个真实问题了:目前后端有 ic-cards 的管理和公开页面接口,但还没有机台/闸机直接调用的 /api/cards/open/api/cards/check 设备接口。
这会导致网页能用、Lua 机台却打不到后端。我现在补这一层,让售票机办卡和闸机扣费真正闭环。

我要补两个设备端关键点:一是线上兑卡查询改成优先走新的 IC 卡公开接口,避免机台还去查旧票据凭证;二是后端补出 /api/cards/open/api/cards/check,把办卡、进站、出站扣费都接上。

先查看 api.jsticketmachine.lua 的现状,确认现有卡务接口、扣费流程,以及线上兑卡当前调用链,再进行最小范围修改和验证。

已定位到服务端路由和 Lua 客户端文件,接下来读取相关实现,确认 /public/ic-cards/orders/:code 现有返回结构以及当前线上兑卡分支如何落到本地逻辑。

文件直读超时了,我先用内容检索切出关键片段,定位新增接口应插入的位置,以及 Lua 里当前兑卡查询顺序。

已经锁定到关键行号,但编辑前还需要把这些行附近的代码完整取出来。我改用终端分段读取,避免 IDE 读大文件超时。

关键片段已经拿到一部分。现在继续补齐 tickets/checkcards/open 调用方和 ticketmachine.lua 的兑卡分支,这样就能准确对齐请求/响应字段。

现在已经能看到服务器和终端两端的关键入口。下一步会对齐 gate.lua 期望的 /api/cards/check 响应格式,然后在 api.js 里补足对应卡片状态流转。

准备开始改文件前,我先确认工作区是否已有未提交改动,避免覆盖你现有编辑;然后会按最小补丁方式更新 api.jsticketmachine.lua

准备写入 api.js:会新增卡片下发与检票两个接口,并复用现有 DataService/LogicService,保证返回结构能直接被 ticketmachine.luagate.lua 消费。

接着修改 ticketmachine.lua 的兑卡查询顺序,只动线上兑卡入口,先查卡单接口,再回退旧票券接口,避免影响普通车票兑换流程。

代码已写入。现在回读关键片段确认没有插错位置,再做语法/诊断检查与最小验证。

语法诊断已经通过。为了补一层验证,我再做一次 JS 语法检查,并确认路由已按现有 server/index.js/api 挂载方式生效。

关键改动和验证都完成了,我再补一遍精确位置,方便你直接点开对应代码。

修改结果

  • api.js 新增了 POST /cards/openPOST /cards/check 两个接口,实际挂载路径是 /api/cards/open/api/cards/check,挂载位置见 index.js
  • api.js 补了卡单号、金额、车站解析等辅助方法,供新卡务接口复用。
  • ticketmachine.lua 调整了线上兑卡逻辑,先查 /public/ic-cards/orders/:code,失败后再回退 /public/orders/:code,并对非 pending_pickup 的卡单直接提示 Already Used!

接口行为

  • POST /api/cards/open
    • 支持本地办卡:生成或激活 card_id,落库为 active,记录开卡事件。
    • 支持线上兑卡:按 voucher_code/order_code 查找 pending_pickup 卡单,兑卡成功后改为 active,写入兑卡站点、设备、余额等信息。
    • 已兑过的线上卡单会返回 409Lua 端会显示 Already Used!
  • POST /api/cards/check
    • 支持 IC 卡进站:记录 entry_station、进站时间与当前卡状态。
    • 支持 IC 卡出站:按 entry_station -> 当前站 调用现有票价逻辑扣费,返回 farebalance/remaining_balanceentry_stationexit_station
    • not_foundalready_enterednot_enteredalready_exitedinsufficient_balancefare_not_found 等情况返回 result: 'deny',与 gate.lua 当前处理方式兼容。

验证情况

  • api.js 通过 node --check 语法检查。
  • GetDiagnostics 检查 api.jsticketmachine.lua 均无诊断错误。
  • 已确认新接口会被现有 /api 路由挂载,不需要额外改 server/index.js

补充说明

  • 这次验证是静态语法与编辑器诊断级别,未做带真实卡数据/站点票价数据的联机跑单。
  • 如果你要,我下一步可以继续帮你补一组最小联调用例,直接给出 curlPowerShell Invoke-RestMethod 的办卡、兑卡、进站、出站验证脚本。

已完成

关键说明

  • 机台写卡这一层做了兼容探测,会按顺序尝试 issueCardissueICCardwriteCardwriteICCardissueTicketDatawriteTicketData,实现位置在 ticketmachine.lua
  • 这是为了兼容你目前给出的 MOD 信息不完整的情况;也就是说,服务端与网页闭环已经补齐,但如果 MOD 最终实际写卡函数名不同,还需要把这层适配精确收口一次。
  • 进出站状态提示仍沿用现有逻辑,already_enterednot_enteredalready_exited 这类提示链路没有拆掉,只是新增了卡的余额与扣费分支。
  • 卡数据会同步写入服务端,并记录事件流,后台与公开查询都能查到。

验证

  • 我做了主会话交叉核对,确认新入口和新路由已经落在代码里,关键命中包括 ticketmachine.luaapi.jsapi.js
  • 全局 GetDiagnostics 返回空,没有看到新增诊断错误。
  • 之前子任务里也完成了 Node 语法检查和页面/API 基本联通检查。

残余风险

  • 最大风险仍然是 MOD 侧真实的“写卡/回写卡字段”函数签名未最终确认;现在代码已按兼容方式实现,但如果你那边的真实接口名、参数顺序、返回字段不同,需要再做一次精准对接。
  • 目前售票机姓名输入是 ASCII 软键盘,不支持中文输入法;如果你要中文持有人名,需要 MOD 或 CC 侧另给输入方案。
  • 如果你愿意,下一步我可以直接继续做两件事中的任意一件:
  • 给你一份“MOD 侧最终应提供的精确接口清单”,你发给做 MOD 的人即可。
  • 给你一组联调脚本,按“办卡 -> 兑卡 -> 进站 -> 出站”完整跑通。

User

现在有好多内容都出现了乱码!请修复

Assistant

我先定位哪些文件出现了编码乱码,重点检查刚改过的网页、Lua 和后端文件里的中文文本,再逐个修复成统一编码内容。

我先排除了 node_modules 里的无关命中。现在重点看项目自身文件,尤其是网页页头有没有缺少 UTF-8 声明,以及哪些中文文本已经被写坏。

已经确认至少 web/home.html 的中文文本本身被写坏了,不是浏览器编码声明问题;web/index.html 只有一处注释/注释尾部残缺。
我继续扫描公开页面和脚本里是否还有类似 这类乱码串,然后统一修复。

我已经定位到主要乱码源就在 web/home.html,内容像是被错误编码后又保存了一次。我直接把这页的中文文案全部重写为正常 UTF-8,同时顺手修掉几个被截断的标签文本。

home.html 的主体乱码已经换回正常中文了。我再做一轮扫描,把剩余的残留乱码和注释损坏一并清掉,然后检查有没有因此引入语法问题。

现在只剩 web/index.html 里一处注释残留乱码,虽然不影响显示,但我把它也修掉,保持文件干净。

已修复

  • 主要乱码来源确认是 home.html,这不是浏览器编码声明缺失,而是文件内容本身已经被错误编码后保存了。
  • 我已经把这页的中文文案全部恢复为正常 UTF-8,包括标题、导航、首页说明、服务卡片、提醒文案、票价图/线路图提示和页脚备案号。
  • 同时清理了 index.html 里残留的一处乱码注释。

修改文件

检查结果

  • 再次扫描 web 目录后,项目自身网页文件里已没有同类乱码残留。
  • home.html 的诊断结果为空。
  • index.html 的诊断结果也为空。

说明

  • 这次确认到的乱码集中在首页 home.html,其他我本轮扫描到的网页、Lua、JS 文件没有发现同类坏字串。
  • 如果你现在浏览器里还有页面显示乱码,通常是浏览器缓存或服务器还在提供旧文件;建议先强刷一次页面。

建议

  • 先重点刷新首页、线上购卡页、IC 卡查询页确认显示是否正常。
  • 如果你还看到别的页面有乱码,直接把页面路径发我,我可以继续逐页清。

User

还是有乱码字符 div 线上购卡的css失效了 https://ticket.fse-media.group/ic-card/order

Assistant

我先做两件事并行确认:一是继续扫描 home.html 是否还有漏掉的乱码文本,二是检查 ic-card/order 页面为什么样式没生效,重点看 HTML 引用、CSS 文件、以及线上实际页面加载情况。

我已经看到一个很像根因的问题:ic-card/order 页面里 CSS 用的是相对路径 style.css?v=6,在 /ic-card/order 这个地址下会被浏览器解析成 /ic-card/style.css?v=6,这很可能直接 404。
我再用浏览器和线上请求确认一下,然后一并修复相对路径和剩余乱码。

问题确认了两点:

  1. 线上购卡 样式失效的根因就是 CSS 路径写成了相对路径,线上实际请求了 https://ticket.fse-media.group/ic-card/style.css,而这个地址返回 Cannot GET
  2. home.html 里还残留了一处乱码文本 绾胯矾璧勬簮

我现在直接修这两个点。

我又确认到一个更完整的原因:线上购卡 不只是 CSS,相对路径的 logo.pngic-card-order.jspublic-status.js 也都会在 /ic-card/order 下解析错。
所以这次我会把公开页面里所有静态资源统一改成站点根路径,避免同类问题反复出现。

我已经把公开页面的静态资源路径统一改成绝对路径了,这样 /ic-card/order/ic-card/search 这类带子路径的页面就不会再把 CSS 和脚本请求到错误地址。
接下来我做一轮快速验证,确认没有残留相对路径和新语法问题。

已修

  • 修掉了 home.html 里残留的乱码文案,绾胯矾璧勬簮 已恢复为“线路资源”,位置在 home.html
  • 修掉了 线上购卡 样式失效的根因:/ic-card/order 页面原来用的是相对路径 style.css?v=6,浏览器实际请求成了 https://ticket.fse-media.group/ic-card/style.css,线上返回了 Cannot GET /ic-card/style.css
  • 现在已把公开页面静态资源统一改成根路径,避免带子路径的页面再次把资源请求错位置。

已改文件

修复内容

  • href="style.css?... 改为 href="/style.css?...
  • src="logo.png" 改为 src="/logo.png"
  • public-status.jsic-card-order.jsic-card-search.jsticket-order.jsticket-search.jstoken.js 改为根路径引用
  • 这样 /ic-card/order/ic-card/search 不会再去请求 /ic-card/style.css/ic-card/ic-card-order.js 这类错误地址

验证

  • 我直接验证了线上请求:
  • https://ticket.fse-media.group/ic-card/style.css?v=6 当前确实返回 Cannot GET /ic-card/style.css
  • https://ticket.fse-media.group/style.css?v=6 可以正常返回 CSS
  • 修改后的几个 HTML 文件诊断都为空,没有新增语法错误

说明

  • 你现在浏览器里还能看到那个乱码 div,大概率是页面还没重新加载到最新文件,或者线上站点还没重新部署/重载静态文件。
  • 代码层面这两个问题都已经修好了;如果线上 URL 还是旧表现,需要让当前服务重新读到这些改动。

下一步

  • 先强制刷新 homehttps://ticket.fse-media.group/ic-card/order
  • 如果这是你本机运行的 Node 服务,重启一下站点进程
  • 如果你愿意,我可以继续把其他仍使用相对静态资源路径的页面也一并扫完,例如 blog.htmlticket-route.htmllogin.html,一次性消掉同类隐患

User

购卡界面去掉卡类型,改成首次充值:5、10、15、20、自定义,去掉备注,去掉联系电话,然后持卡人名改成只能英文+符号,去掉押金

Assistant

我先改线上购卡这一套:页面表单、前端校验和后端下单字段一起收口,确保“卡类型/备注/联系电话/押金”都真正去掉,不只是前端隐藏。

我已经确认线上购卡页和公开下单接口目前都还带着“卡类型、押金、备注、联系电话”这套旧逻辑。现在把它们一起改成“首次充值”模式,并把姓名限制改成英文+符号校验。

我会顺手把公开查询页里和这次规则冲突的“卡种、押金、备注”展示也收一下,避免用户购卡后在查询页又看到旧概念。

我把界面结构改成“首次充值按钮 + 自定义金额”,并同步把前后端字段削到只剩 holder_nameinitial_balance,这样规则不会前端后端不一致。

我已经把公开购卡页和后端下单接口都改成“首次充值”模型了。现在做一轮语法和残留字段检查,确认前端没有还在引用被删掉的 phonenotecard_type

已修改

  • 线上购卡页去掉了“卡类型”“备注”“联系电话”,改成“首次充值”模式,支持 5 / 10 / 15 / 20 / 自定义,改动在 ic-card-order.htmlic-card-order.js
  • 持卡人姓名现在限制为“英文 + 常用符号”,不允许中文和数字;前端和后端都做了校验,后端规则在 public.js
  • 押金已从线上购卡逻辑中移除,购卡金额现在等于首次充值金额;公开下单接口也不再使用 phonenotecard_type 作为必需/核心字段,见 public.jspublic.js
  • 公开查询页同步去掉了“押金”和“备注”展示,改为显示“首次充值”,见 ic-card-search.js

现在的购卡规则

  • 卡片固定按 IC 储值卡 处理,不再让用户选卡种。
  • 首次充值可直接点 5 / 10 / 15 / 20,也可切到“自定义”手输金额。
  • 持卡人姓名必须匹配英文和常用符号格式,例如 Alex SmithA.BrownChris-O'Neil
  • 押金固定为 0,实付金额就是首次充值金额。

验证

提醒

  • 线上站点要看到新表单,需要重载当前服务或重新部署静态文件。
  • 如果你还想把后台 IC 卡管理 里的“卡种 / 联系电话 / 押金 / 备注”也一起删掉,我可以下一步继续统一那一页。

User

继续统一

Assistant

我继续把后台 IC 卡管理 一并统一到新规则,重点清掉“卡种、联系电话、押金、备注”相关表单和展示,保留持卡人姓名、状态、余额这几个真正还在使用的字段。

我把后台这边也按同一套规则收口:建卡只保留“持卡人姓名 + 初始余额”,详情只保留“姓名 + 状态 + 余额”,其余旧字段全部从界面上撤掉。必要的话,我也会顺手把后台接口默认值改成和公开购卡一致。

后台页面和后台 API 已经按新规则收口了。现在做最后一轮残留字段扫描和语法检查,确认没有再引用旧的 联系电话 / 卡种 / 押金 / 备注 输入。

已统一

  • 后台 IC 卡管理 现在也按同一规则收口了,不再出现“卡种、联系电话、押金、备注”这套旧字段。
  • 快速建卡只保留 持卡人姓名(仅英文与符号)初始余额,改动在 ic-card-admin.html
  • 后台详情页只保留 持卡人 / 卡片类型(固定显示 IC 储值卡)/ 状态 / 余额,并把摘要区补成“首次充值 + 购卡金额”,改动在 ic-card-admin.js
  • 后台列表里的卡片副标题也统一成 持卡人 · IC 储值卡,不再根据旧 card_type 显示月票卡/纪念卡。

接口同步

  • 后台 API 现在也统一默认按 IC 储值卡 处理,建卡和更新都不再接受前端传入的 phone / note / deposit / card_type 作为主逻辑字段,改动在 api.js
  • 后台建卡和编辑同样加入了“持卡人姓名仅支持英文与常用符号”的校验,和公开购卡页保持一致。
  • 后台卡片列表搜索也去掉了对 phonenote 的依赖。

验证

  • ic-card-admin.htmlic-card-admin.jsapi.js 诊断均为空。
  • node --check 校验 api.js 已通过。
  • 我也额外扫过后台页面代码,已没有 createPhonecreateTypedetailPhonedetailTypedetailDepositdetailNote 这类旧字段残留。

当前统一结果

  • 公开购卡页:只填持卡人姓名,选择首次充值金额。
  • 公开查询页:不再展示押金、备注。
  • 后台管理页:不再编辑卡种、联系电话、押金、备注。
  • 后台和公开端:都使用同一套英文姓名校验规则。

如果继续

  • 现在还可以再统一一层:把设备端 /api/cards/open 里残留的 phone / note / deposit / card_type 兼容字段也进一步收窄成纯“姓名 + 余额”模型。
  • 如果你要,我下一步就继续清这一层。

User

没有显示出充值配置 Uncaught TypeError: Cannot read properties of null (reading 'addEventListener') at ic-card-order.js:167:22 at ic-card-order.js:182:3

Assistant

我先对照 ic-card-order.htmlic-card-order.js 定位是哪一个元素 ID 没对上,再补一层空值保护,避免单个 DOM 缺失就直接把整页脚本打挂。

我确认这更像是“线上实际 HTML 还是旧版、JS 已经是新版”造成的前后不匹配:旧页面没有 #customInitialBalance,新版脚本一绑定事件就炸了。
我现在把 ic-card-order.js 做成向后兼容,能同时兼容新旧 DOM,避免线上缓存/灰度状态下直接白屏。

我已经把线上购卡脚本改成兼容新旧页面结构了:即使线上还在缓存旧 HTML,也不会因为找不到新 ID 直接抛错。再做一轮诊断确认没有手滑引入新问题。

已修

  • 这个报错的根因是线上页面很可能还是旧版 HTML,而浏览器拿到了新版 ic-card-order.js
  • 新版脚本会找 #customInitialBalance#rechargeOptionList,但旧版页面只有 #initialBalance#planList,所以在绑定 addEventListener 时直接对 null 调用了方法。

这次改动

  • 我把 ic-card-order.js 改成了兼容新旧 DOM
  • #rechargeOptionList 不存在时,自动回退到旧的 #planList
  • #customInitialBalance 不存在时,自动回退到旧的 #initialBalance
  • 如果关键节点仍然缺失,会先 console.error 并安全退出,不再整页脚本崩掉
  • 旧页面里残留的 联系电话备注 输入框会被脚本自动禁用并隐藏,避免旧 HTML 和新逻辑混在一起

验证

你现在应该看到的效果

  • 即使线上还是旧 HTML,页面也不该再报 Cannot read properties of null (reading 'addEventListener')
  • 充值配置应该能正常渲染出来
  • 旧的“联系电话/备注”如果还在缓存页面里,也会被隐藏掉

说明

  • 这属于典型的“HTML 和 JS 部署版本不一致”问题。
  • 代码侧我已经做了兼容,但如果你想完全干净,还是建议把线上静态页面一起更新并清缓存。

建议

  • 强刷 https://ticket.fse-media.group/ic-card/order
  • 如果服务端有静态缓存/CDN,也一起清掉
  • 如果刷新后还有问题,我可以继续帮你直接排线上实际返回的 HTML 和 JS 是否版本不一致

User

现在似乎cc端没有正确传给售票方块出卡参数,导致没有正常出卡。

Assistant

我先对照售票机 Lua 里“购票正常出票”的调用方式,检查办卡分支到底传了哪些参数给 ticket_vending_machine,再和你给的 MOD API issueICCard() 对比,找出哪里少传或传错了。

我基本锁定根因了:当前 CC 端 writeICCard() 是按“带参数写卡”去试 issueICCard 的,但你最早给的 MOD API 里 issueICCard()无参
这就很可能导致外设方法被调用时参数不匹配,最终根本没成功出卡。我先把这一层改成优先按真实无参 API 调用,再兼容旧的猜测式写法。

我发现这块不只是“参数不对”,还有更深一层的问题:如果 MOD 的 issueICCard() 真的是无参并返回真实卡号,那现在 CC 端是“先向服务端开卡,再尝试往方块写卡”,顺序也反了。
正确闭环应该更像“先让售票方块实际出卡拿到真实 cardId,再把这个 cardId 连同姓名/余额同步到服务端”。我先确认 firstString() 这类工具函数怎么取返回值,再按这个思路改。

我按“真实无参 issueICCard() 优先”的方式改:先让方块出卡拿到物理 cardId,再把这个 cardId 发给服务端建卡/兑卡。旧的“带参数写卡”逻辑保留作兼容后备。

我已经把办卡/兑卡的出卡顺序改成“优先真实无参 issueICCard() 出物理卡,再把返回的 cardId 同步给服务端”。同时保留了旧的参数写卡逻辑作为后备,避免兼容老实现时直接失效。

已修方向

  • 我已经把 CC 端出卡流程改成更符合你给的 MOD API:
  • 先调用售票方块的无参 issueICCard() 尝试实际出卡
  • 拿到它返回的真实 cardId
  • 再把这个 cardId + 持卡人 + 余额 发给服务端 /cards/open
  • 只有当无参出卡接口不存在或失败时,才回退到之前那套“服务端先建卡,再尝试参数写卡”的兼容逻辑

为什么之前会出问题

  • 你给的 API 是 local ok, cardId = vm.issueICCard()
  • 但原来的 ticketmachine.luaissueICCard 当成“可带参数写卡”的方法来试,这很容易导致外设调用参数不匹配,最终不出卡
  • 更严重的是,原来顺序是“先服务端生成卡号,再让方块出卡”,如果方块自己返回了一个真实 cardId,那物理卡号和服务端卡号还会错位

这次改动

  • 新增了物理出卡 helper,在 ticketmachine.lua
  • 调整了办卡确认分支,优先走“先出卡再同步”,位置在 ticketmachine.lua
  • 旧的 writeICCard() 仍保留为 fallback,并补了一些兼容字段和无参尝试,位置在 ticketmachine.lua

现在行为

  • 如果 ticket_vending_machine.issueICCard() 正常返回 true, cardId
    • 会直接用这个 cardId 去服务端开卡/兑卡
    • 这样服务端和实体卡号一致
  • 如果物理卡已经出了,但服务端同步失败
    • 页面会显示 Issued, sync failed: ...
    • 不会误报“办卡成功”
  • 如果方块出了卡但没返回 cardId
    • 页面会显示 Card issued without ID
    • 方便你继续判断 MOD 侧返回值是否异常

验证

建议你现在测试

  • 在 CC 端实际点一次办卡
  • 看售票机屏幕提示是否从之前的写卡失败,变成正常 Card ready
  • 如果还不行,优先看两件事:
  • issueICCard() 是否真的返回了 true, cardId
  • 物理卡出了以后,服务端 /cards/open 是否返回成功

如果继续排

  • 我下一步可以直接给你加一版更明确的现场调试输出:
  • 在 CC 屏幕或 logs/last_card_issue.json 里把 issueICCard() 的返回值、方法名、最终提交给 /cards/opencardId 都打印出来
    这样就能马上判断是“方块没出卡”,还是“出了卡但没回传卡号”,还是“服务端同步失败”。

User

还是方块没出卡,你先确定这里的代码没有问题

Assistant

我按代码审查方式只看这条链路:ticketmachine.lua 里办卡确认 → 调用售票方块 → 同步 /cards/open。先把相关代码和服务端接口完整读一遍,再给你结论,优先找“必然导致不出卡/错卡”的问题。

我还要核对一件关键事:正常出票 issueTicket(...) 是怎么调用的,以及 ticketVendingMachine 这个外设对象到底是怎么拿到的。这样才能判断现在 issueICCard() 的调用方式是不是和现有外设包装层一致。

我已经抓到至少一个会直接影响“没出卡却被当成成功分支处理”的硬问题,现在做第二遍交叉验证,避免把猜测当结论给你。

先定位 ticketmachine.luaserver/routes/api.js 中办卡、兑卡与 /cards/open 的实现,再沿着调用链核对 card_id 的来源、落库逻辑,以及同步失败后的重试/去重控制。

先查看 ticketmachine.lua 里这两个函数及其调用链,核实返回值约定和失败回退路径,再给出逐条审查结论。

已经定位到关键判断:两处都用 r1 ~= false 作为成功条件,我继续查卡片开户流程里失败后的补偿/回退逻辑,确认是否会出现“没出卡但流程已成功/已扣款”的后果。

回退链路也查过了:当前卡开户分支只有 submitCardOpen 正向调用,没有对应撤销/退款/作废分支;接下来我按“是否会把 nil 当成功、是否会导致不出卡不回退”分别给出结论和等级。

我已经看到终端机分成两条路径:一条先出空白卡再调 /cards/open,另一条先调接口再写卡。接下来补看服务端 upsert 与订单索引逻辑,确认线上兑卡是否真的保留设备上报的 card_id,以及“同步失败后重试”会不会再放一张新卡。

我在补读辅助函数时遇到一次读取超时,改成更小范围继续取证。重点会放在 normalizeIcCardIdfindIcCardByOrderCodeupsertIcCard,这些能直接决定是否覆盖 card_id 和是否允许重复兑卡。

补充看了一下服务端路由,/cards/open 存在,但暂时没看到与这条开卡调用成对的回滚接口;我再取一小段路由确认一下,避免把“前端无回退”误说成“系统绝对无回退”。

发现

  • 严重writeICCard 的成功判断会把 nil 误判为成功;一旦外设方法“正常返回但首返回值是 nil”,代码仍会走成功分支,直接把订单标记为 confirmed = true 并显示 Card ready,即使实际没有写卡/出卡。ticketmachine.lua:L362-L364 ticketmachine.lua:L1611-L1617
  • 结论:这里的 nil 返回会被误判为成功,且会导致“可能没出卡却不回退”。更严重的是,submitCardOpen(payload) 在写卡前已把服务端卡状态创建/激活,当前 ticketmachine.lua 中没有对应撤销/退款/回退逻辑。ticketmachine.lua:L1594-L1617 api.js:L729-L818
  • issueBlankICCard 的成功判断同样会把 nil 误判为成功;但它的后果和 writeICCard 不完全一样。这里不会直接把 UI 标成成功,而是进入 elseif okIssueBlank 分支,报 Card issued without ID,同时跳过后续 submitCardOpen + writeICCard 的后备路径。ticketmachine.lua:L378-L380 ticketmachine.lua:L1568-L1592
  • 结论issueBlankICCardnil 返回也会被误判为成功;如果这个 nil 实际表示“未出空白卡/未拿到有效结果”,当前流程不会回退付款,也不会进入后备写卡路径,结果是订单停在失败态但没有补偿逻辑,属于“支付后可能不出卡且不回退”。ticketmachine.lua:L1568-L1594

流程图

flowchart TD
    A[确认开卡] --> B[issueBlankICCard]
    B -->|pcall成功 且 r1=nil| C[被判定为 okIssueBlank=true]
    C --> D{blankCardId 是否为空}
    D -->|为空| E[显示 Card issued without ID]
    E --> F[不走 submitCardOpen/writeICCard]
    F --> G[无回退/退款逻辑]

    B -->|返回 false| H[submitCardOpen]
    H --> I[writeICCard]
    I -->|pcall成功 且 r1=nil| J[被判定为 okWrite=true]
    J --> K[confirmed=true + Card ready]
    K --> L[即使未实际写卡/出卡也结束交易]

    style C fill:#fff3e0,color:#e65100
    style E fill:#fff3e0,color:#e65100
    style J fill:#ffcdd2,color:#b71c1c
    style K fill:#ffcdd2,color:#b71c1c
    style G fill:#ffcdd2,color:#b71c1c
    style L fill:#ffcdd2,color:#b71c1c

审查表

No. Issue Title Suggestion Code Link
1 严重 writeICCard 经由 callPeripheralMethodsnil 当成功,可能未写卡却显示成功 成功条件改为“显式成功值”或“显式拿到有效卡标识/写卡结果”,不要用 r1 ~= false;同时在 submitCardOpen 成功但写卡失败时增加补偿逻辑 ticketmachine.lua:L362-L364, ticketmachine.lua:L1611-L1617
2 issueBlankICCardnil 当成功,导致跳过后备路径并卡死已支付交易 issueBlankICCard 仅在返回明确成功信号时才置 okIssueBlank=true;若无卡号,应继续尝试后备路径或进入补偿/退款流程,而不是停在“issued without ID” ticketmachine.lua:L378-L380, ticketmachine.lua:L1568-L1592

补充说明

  • nil 会被误判为成功,这一点是代码语义上可以直接确认的,因为 Lua 中 nil ~= false 为真。
  • 两处问题的区别在于:issueBlankICCard 的误判更像“内部成功态误判,阻断后续分支”;writeICCard 的误判则是“最终用户可见成功误判”,风险更高。
  • 就当前代码范围看,确实存在“可能不出卡却不回退”的结果;其中 writeICCard 路径是最明确、最严重的一条。

如果你要,我可以下一步直接把这两处成功判断改成“只接受显式成功返回”,并补一个最小化的失败补偿方案。

关键证据已经比较清楚了:线上卡订单在下单时就预生成了 card_id,而终端兑卡时又会先从设备拿真实空白卡号再上报。如果 /cards/open 不接纳这个真实卡号,就会出现“数据库里是预生成号,实体卡上是设备号”的错位;我再补一眼 /cards/check 的查卡逻辑,确认这个错位会不会直接导致刷卡失败。

结论

flowchart TD
    A[线上下单生成待领卡订单] --> B[public.js 预生成 card_id]
    B --> C[终端兑卡先从设备拿空白卡真实 card_id]
    C --> D[/cards/open 兑卡分支]
    D --> E[沿用订单原 card_id]
    E --> F[返回旧 card_id 给终端]
    F --> G[数据库 card_id 与实体卡真实 card_id 错位]

    H[终端确认办卡/兑卡] --> I[先 issueBlankICCard 出卡]
    I --> J[再调用 /cards/open 同步]
    J --> K{同步失败?}
    K -->|是| L[仅提示 Issued, sync failed]
    L --> M[processing=false 且 confirmed=false]
    M --> N[CONFIRM 再次可点]
    N --> I

    style B fill:#bbdefb,color:#0d47a1
    style E fill:#fff3e0,color:#e65100
    style G fill:#ffcdd2,color:#b71c1c
    style I fill:#f3e5f5,color:#7b1fa2
    style L fill:#fff3e0,color:#e65100
    style N fill:#ffcdd2,color:#b71c1c
  • 1. 严重 线上兑卡会忽略设备返回的 card_id。 结论:。在线订单在创建时就预生成了一个 card_idpublic.js;终端兑卡时先从设备拿到真实空白卡号并放进 payload.card_idticketmachine.lua。但 /cards/open 的兑卡分支只用 findIcCardByOrderCode() 找到原订单后直接 upsert {...current, ...},没有把 body.card_id 写回卡记录,因此仍保留订单原来的 card_idapi.js。随后终端又优先采用服务端返回的 respData.card_id 覆盖本地显示值,ticketmachine.lua。而后续验卡是按上报的 card_id 直接查索引,api.js,所以一旦设备真实卡号与订单预生成号不一致,就会形成“实体卡号和后台卡号错位”。

  • 2. 严重 同步失败后允许重复出卡。 结论:允许。无论办新卡还是兑卡,终端在确认时都会先执行 issueBlankICCard() 出卡,再调用 /cards/open 同步,ticketmachine.lua。如果同步失败,代码只是提示 Issued, sync failed,没有落本地待补偿队列,也没有把本次出卡锁死;随后 processing 被清回 falseconfirmed 仍是 falseCONFIRM 按钮重新可点,ticketmachine.lua。同时当前工程的离线补偿队列只覆盖车票上传,不覆盖卡开卡/兑卡,ticketmachine.luaticketmachine.lua。这意味着操作员重试时会再次先出一张新卡;若第一次其实已被服务端受理但响应丢失,第二次重试在兑卡场景会先出第二张卡再收到 409 already redeemed,在普通开卡场景还可能直接生成第二张有效卡。

补充

  • 未看到能够推翻上述两点的保护逻辑。
  • 这两个问题都不是纯日志/展示问题,都会影响线上实体卡与后台状态的一致性。
  • 如需,我可以下一步直接给出修复方案,或按“先修 card_id 错位 / 先修重复出卡”分别落代码。

我已经确认:这段代码目前还不能说没问题,而且有两处是会直接影响实体卡流程的硬问题。
我先把问题按严重级别列清楚,再由你选是一起修还是先修最影响出卡的那一处。

你这边已经选了“全部修复”,我就直接动手。
这次我会同时修 3 件事:nil 误判成功、线上兑卡忽略真实 card_id、同步失败后重复点确认会再次出卡。

我要顺手确认一下 DataService.upsertIcCard() 在“在线兑卡时替换 card_id”这个场景下是否会清理旧索引;如果不会,我会一起补上,避免后台里残留旧卡号脏数据。

我现在开始改 Lua 主流程,重点是让“重试只重试同步、不再重复出卡”,并把外设成功判断收严成“必须显式成功”。

Lua 侧第一轮已改完。接着补服务端 /cards/open 的兑卡分支,把设备真实 card_id 接住并替换旧预生成卡号,避免实体卡和后台错位。

我补一处刚才顺手写错的服务端语法:把误写成 Lua 的 ~= 改回 JS 比较,并顺便把旧卡号变量提出来,避免重复归一化。

结论

  • 这段代码原来不是没问题,我确认到了 3 个会影响实体卡流程的硬问题,并且已经全部修掉。
  • 现在这条链路从静态代码上看已经顺了:外设成功判断更严格、线上兑卡会接住真实 card_id、同步失败后不会再次重复出卡。
  • 语法和诊断已通过:ticketmachine.luaapi.js 都无诊断错误,node --check 也通过。

本次修复

  • nil 误判成功已修:外设调用现在只接受“显式成功返回”,不再把 nil 当成功,位置在 ticketmachine.lua
  • 同步失败重复出卡已修:如果空白卡已经出过但服务端同步失败,后续重试会复用同一张已出的卡号,只重试同步,不会再调用方块重新出卡,位置在 ticketmachine.lua
  • 线上兑卡卡号错位已修:/cards/open 的兑卡分支现在会采用设备返回的真实 card_id,并清理旧的预生成卡号记录,位置在 api.js

流程图

flowchart TD
    A[CC 确认办卡/兑卡] --> B{已有待同步空白卡号?}
    B -->|否| C[调用 issueICCard 出实体卡]
    B -->|是| D[复用上次 blank card_id]
    C --> E{返回显式成功且有 card_id}
    E -->|是| F[/cards/open 同步服务端]
    E -->|否| G[停在失败态,不误判成功]
    D --> F
    F -->|成功| H[Card ready]
    F -->|失败| I[保留 pendingBlankCardId,仅重试同步]
    style C fill:#bbdefb,color:#0d47a1
    style F fill:#c8e6c9,color:#1a5e20
    style G fill:#ffcdd2,color:#b71c1c
    style I fill:#fff3e0,color:#e65100

我确认过的关键点

  • 成功判断:r1 ~= false 已改成“r1 不能是 nil/false”,避免外设没回值时被当成成功。
  • 重试逻辑:同步失败后会记住 pendingBlankCardId,下一次 CONFIRM 不会再出第二张卡。
  • 在线兑卡:服务端不再死守下单时预生成的卡号,而是跟随设备真实卡号。

现在如果还不出卡,优先怀疑代码外因素

  • ticket_vending_machine 外设没有被 peripheral.find('ticket_vending_machine') 找到。
  • MOD 实际方法名/返回值和你给的文档不一致,比如 issueICCard() 并不返回 true, cardId
  • 售票方块出卡前还有额外前置条件,例如库存、朝向、内部槽位、红石状态或实体卡物品未就绪。

下一步最有效

  • 我建议直接再加一轮运行时日志,把这 4 个值打印出来:
  • ticketVendingMachine 是否存在
  • issueICCard 是否存在
  • pcall(issueICCard) 的原始返回值
  • 最终提交到 /cards/opencard_id
  • 这样就能马上把问题切成“代码流程问题”还是“MOD/外设行为问题”。

状态

  • 这轮代码我已经改完。
  • 如果你愿意,我下一步直接给你把 CC 端调试日志也补上,专门抓“为什么方块没出卡”。

User

IC卡网页控制台端可以正常收到信息,也能看到卡号、持卡人。但是订单号那里显示“---”。而且页面上还是有联系方式,请删除。然后加一个“删除卡”的按钮。然后把这个IC卡管理的页合并到/admin页

Assistant

我先检查后台页面结构和 IC 卡管理页现状,重点看 3 件事:订单号为什么显示 ---、联系方式残留在哪里、以及现在 /admin 和独立 IC 卡页是怎么分开的。

我已经确认独立的 IC 卡页就是现在那套后台界面。接下来我会把它并到现有主控制台页,同时补上删除接口和删除按钮,再顺手查订单号为什么总是空。

我在看主控制台的 Vue 结构,判断是直接并一块原生区域,还是把现有 IC 卡逻辑迁进 Vue。优先会选改动更小但不别扭的方案。

我准备直接把 IC 卡管理迁进主控制台的 Vue 页,不走 iframe。这样 /admin 里能原生切标签、共享现有刷新逻辑,也方便加“删除卡”按钮和统一样式。

我开始改主控制台模板,把独立的 IC 卡区块直接并进去,同时会让旧地址 /admin/ic-card 自动落到这个新视图,不再维持两套后台入口。

主控制台里的 IC 卡视图已经嵌进去了。现在我先重新读一遍刚改过的 index.js,再补最后几处导出和路由改动,避免在同一文件上踩掉刚才的上下文。

我再补两层后端收口:去掉 IC 卡记录里的联系方式字段,并加删除卡接口。顺手把旧 /admin/ic-card 入口指向新的 /admin 视图。

我已经把主界面、后台接口和旧入口路由都串起来了。现在做最后一轮检查,确认 index.html/index.js/api.js/server/index.js 没有新报错,也确认后台页里不再残留“联系方式”。

最后我再抓一下关键改动的定位行,方便你直接点开看:主控制台入口、IC 卡视图、删除接口和旧路由跳转。

已调整

  • IC 卡管理已并入主控制台 /admin,侧边栏不再跳独立页,而是在主控制台内切换到 IC 卡管理 视图,改动在 index.htmlindex.html
  • 订单号显示已改成优先读取 order_code / voucher_code / code,如果本来就是现场办卡没有线上订单,则显示“现场办卡”,不再一律是 ---,逻辑在 index.js
  • 后台页中的“联系方式”已去掉;我也确认新的 /admin 页面代码里已经没有 联系方式 / 联系电话 / phone 残留。
  • 卡片详情区新增了“删除卡”按钮,前端逻辑在 index.htmlindex.js

接口变更

  • 新增后台删除接口 DELETE /api/ic-cards/:id,会删除卡记录并记一条删除事件,位置在 api.js
  • cards/open 这条卡开卡接口里不再继续写 phone,位置在 api.js

路由合并

  • 旧入口 /admin/ic-card 现在会跳转到 /admin?view=iccards,位置在 server/index.js
  • 旧兼容入口 /ic-card-admin 也同样跳转到新的主控制台视图,位置在 server/index.js
  • 主控制台脚本会自动识别 view=iccards 并直接打开 IC 卡管理页,位置在 index.js

验证

说明

  • 独立文件 ic-card-admin.html/js 还保留在仓库里,但运行时入口已经并回 /admin 了,用户访问旧地址会自动跳到新视图。
  • 如果你愿意,我下一步可以继续把这两个旧文件也彻底清理掉,避免以后误维护两套页面。

User

在出卡的时候持卡人name是否正确传给mod

Assistant

我直接核对办卡这条调用链里“持卡人姓名”从页面状态到 ticket_vending_machine 外设调用的传递路径,重点确认有没有真正作为参数传到 MOD。

结论

  • 按你现在给的 MOD API 来看,持卡人 name 在主流程里没有直接传给 MOD 出卡方法
  • 当前代码的“优先路径”是先调无参 issueICCard() 出空白卡,这一步不带姓名参数,位置在 ticketmachine.luaticketmachine.lua
  • holder_name 目前是先组进本地 payload,然后发给你的网站后端 /cards/open,位置在 ticketmachine.luaticketmachine.lua

现在的实际行为

  • 姓名来源:state.holderName 会写进 payload.holder_name,见 ticketmachine.lua
  • 主出卡调用:issueBlankICCard() 只做 pcall(fn),没有任何参数,见 ticketmachine.lua
  • 服务端同步:姓名会跟着 payload 提交给 /cards/open,所以网页控制台能看到持卡人姓名,这和你观察到的一致。

只有哪种情况会传给 MOD

  • 只有走到后备路径 writeICCard(finalCard) 时,代码才会尝试把姓名传给外设。
  • 这里会把 holder_name 填进 payload.ownerpayload.card_holder,并尝试多种参数形式调用外设,见 ticketmachine.lua
  • 例如它会尝试:
{ payload }
{ tostring(payload.holder_name or ''), tonumber(payload.initial_balance) or 0 }
{ tostring(payload.card_id or ''), tostring(payload.holder_name or ''), ... }
  • 但这只是兼容性后备,不是当前你这套 issueICCard() API 的主流程。

所以答案是

  • 如果 MOD 真实接口就是你发的:
local ok, cardId = vm.issueICCard()
  • 那么答案是:没有,姓名没有在“出卡给 MOD”这一步传进去。
  • 目前姓名只进了后端记录,没有确定写进实体 IC 卡的数据区。

建议

  • 如果你希望“出卡时就把持卡人名写进卡里”,MOD 侧还需要提供明确的写卡接口,比如:
  • issueICCard(holderName, balance)
  • issueICCard() 后再 writeCardData(cardId, data)
  • 如果你要,我下一步可以直接把 CC 端改成“只支持你最终确认的 MOD 写卡接口”,把这层彻底收口,不再靠兼容猜测。

User

调用是: vm.issueICCard("")

Assistant

我按你刚确认的真实调用签名把 CC 端收口到 vm.issueICCard("<name>"),这样主出卡路径会把持卡人姓名直接传给 MOD,不再走无参猜测。

我已经把主流程改成按姓名参数出卡了,同时把后备写卡路径也补上了“单参数姓名”这一种调用形式,避免 issueICCard(name) 被别的参数组合打偏。现在做一轮语法检查。

已改

  • 现在主出卡调用已经按你给的真实签名改成:vm.issueICCard("<name>")
  • 也就是说,持卡人姓名现在会在“出卡给 MOD”这一步直接传入,不再是之前的无参调用。

具体改动

  • issueBlankICCard() 改成接收 holderName,位置在 ticketmachine.lua
  • 主办卡确认分支现在会把 payload.holder_name 传给它,位置在 ticketmachine.lua
  • 后备写卡路径也补上了单参数调用形式 { holder_name },这样如果走到兼容分支,也会优先尝试 issueICCard(name),位置在 ticketmachine.lua

现在的实际行为

  • 办卡页输入的姓名先进入 payload.holder_name
  • 然后主流程调用:
ticketVendingMachine.issueICCard(payload.holder_name)
  • 如果这种调用失败,代码才会退回到旧的兼容尝试

验证

结论

  • 现在可以明确说:持卡人 name 已经会正确传给 MOD 出卡方法
  • 如果接下来还是“不出卡”或“卡里名字不对”,问题就更可能在 MOD 侧实现,而不是这里没把参数传过去。

如果你要继续排

  • 我下一步可以再把这次调用的运行时参数直接写到 logs/last_card_issue.json 或屏幕调试输出里,确认现场实际传出去的名字字符串到底是什么。

User

办卡充值时不要计算手续费、押金等内容。就直接是充值多少付多少钱。

然后网页查询那里支持查询凭证码

Assistant

我先收口两处逻辑:CC 办卡支付金额只按首充金额计算,以及网页查询把“凭证码”也作为 IC 卡查询入口。随后会把前后端一起对齐。

我看到查询页目前只认 card_id / order_code,还没把线上购卡返回的“凭证码/兑换码”纳入匹配。我再把购卡下单返回字段和服务端存储字段核对一下,一并修成可按凭证码查。

我已经定位到 CC 端办卡金额仍然沿用了旧的 deposit + topup 计算。我现在把这套金额模型直接改成“首充即实付”,并同步把公开查询文案和后端匹配都加上“凭证码”。

金额逻辑和查询入口都已经改完。我现在做检查,确认 Lua 和网页脚本没有新报错,同时抓一下关键位置方便你直接点开看。

已修改

现在的行为

  • 现场办卡:付 5 就充 5,付 10 就充 10,不再额外加押金。
  • 线上购卡查询:如果你拿到的是 IC 卡订单生成的凭证码,也可以直接在查询页输入。
  • 查询结果里会显示“凭证码”字段;当前若没有单独存 voucher_code,会回退显示已有的 order_code

验证

说明

  • 你现在如果在线上页面还看不到新查询提示,通常是静态文件缓存;强刷 IC 卡查询 页即可。
  • 如果你还想更彻底一点,我下一步可以把后台 /admin 里的 IC 卡搜索框也一起扩成支持“凭证码”检索。

User

把后台 /admin 里的 IC 卡搜索框也一起扩成支持“凭证码”检索。

Assistant

我先把 /admin 的 IC 卡搜索链路看一遍,确认是前端占位文案要改,还是后台 /api/ic-cards?q= 本身还没把“凭证码”纳入检索。

后台接口这边确实还没把 voucher_code/code 纳入过滤;前端占位文案也还是旧的。我直接一起补上。

我再做一轮检查,确认 /api/ic-cards?q=/admin 的 IC 卡页都没有新问题。

已支持

  • /admin 里的 IC 卡搜索框现在也支持按“凭证码”检索。
  • 后台接口 /api/ic-cards?q= 已把 voucher_codecode 纳入搜索字段,改动在 api.js
  • 前端输入框提示文案也同步改成“搜索卡号 / 订单号 / 凭证码 / 姓名”,位置在 index.html

验证

说明

  • 现在后台 IC 卡列表可按 card_id / order_code / voucher_code / code / holder_name / source 命中。
  • 如果线上数据里“凭证码”实际仍是写在 order_code,这次改动也兼容,不会影响原有搜索。

建议

  • 强刷一下 /admin?view=iccards 后测试一张线上购卡记录的凭证码。
  • 如果你愿意,我还可以继续把后台 IC 卡列表里那行“订单 xxx”文案也改成“订单/凭证 xxx”,避免展示上看起来像只能搜订单号。

User

售票机办卡的时候也要传入余额参数: issueICCard(ownerName, initialBalance) — 第二参数可选,默认 0。 vm.issueICCard("张三", 50) -- 开卡,余额 50

Assistant

我把售票机办卡主调用再收口一次,改成按你最新确认的签名传 name + initialBalance,并同步检查后备分支的参数顺序。

我顺手扫到办卡金额选择页里还有一处旧公式残留,仍在用 押金 + 充值。这次会一起修掉,避免界面金额和实际出卡参数不一致。

我开始改 ticketmachine.lua:主出卡改成 issueICCard(name, initialBalance),并把办卡金额页里残留的押金公式和提示一起清干净。

主调用已经改完了。我现在做一轮诊断,确认 Lua 没有语法问题,并把关键位置抓出来给你确认。

已修改

  • 售票机办卡主调用现在会把“持卡人姓名 + 初始余额”一起传给 MOD:
    • vm.issueICCard(ownerName, initialBalance)
  • 对应改动在 ticketmachine.luaticketmachine.lua

现在的实际调用

  • 主流程会调用:
issueBlankICCard(payload.holder_name, payload.balance)
  • issueBlankICCard() 内部优先尝试:
ticketVendingMachine.issueICCard(holderName, initialBalance)

兼容处理

  • 如果 issueICCard(name, balance) 失败,代码还会回退尝试:
    • issueICCard(name)
    • issueICCard()
  • 这样既满足你现在确认的真实签名,也不会把旧兼容路径直接砍掉。

顺手修正

  • 办卡充值页里残留的旧公式也一起修了,不再出现 押金 + 充值 的计算。
  • 现在 Need Pay 直接等于首充金额,位置在 ticketmachine.lua

验证

结论

  • 现在 CC 端已经会按你给的最新接口,把姓名和余额一起传给 MOD 出卡。
  • 如果接下来实体卡余额仍不对,就更像是 MOD 侧没有正确使用第二参数,而不是 CC 端没传。

User

线上购卡这里没有正确出五位凭证码,出了一个ICO-xxxx,请修改。 然后一些订单信息的字颜色要醒目一些。

我希望卡片信息也有一个单独的界面,类似电子客票。 在ticket.fse-media.group/ic/<卡号>

cc的屏幕键盘要加上符号的按钮

Assistant

我先核对 4 条链路:线上购卡凭证码生成规则、购卡结果页样式、独立卡片详情页路由,以及 CC 办卡软键盘的按键布局。

我看到一个关键不一致:CC 线上兑卡当前就只接受 5 位码,但网页购卡后端还在生成 ICO-xxxxxx。我会把后端和结果页一起改成统一的 5 位凭证码,并顺手把乱码 HTML 一并清掉。

我再看一下现有的 5 位凭证码生成器和“电子客票”样式,尽量复用现成的设计语言,不新造一套。

我已经确认这次有三类改动要一起做:凭证码统一为 5 位、独立 IC 卡详情页、CC 符号键盘;同时会把购卡结果卡片的关键信息做成更醒目的高对比展示。

我准备开始改后端与页面文件了:先统一生成 5 位凭证码,再补独立卡片详情页和更醒目的结果展示,最后处理 CC 键盘。

我先把后端码制改掉:线上购卡返回的主标识改成 5 位凭证码,并把卡数据里也显式保留 voucher_code/code 字段,方便展示和查询。

我已经开始做页面层改造了。接下来把新卡片详情页路由接上,并新建 /ic/<卡号> 页面文件。

CC 这边我会把姓名输入键盘做成 ABC / SYM 切换,不只补符号按钮,也顺手把输入约束收口到“英文 + 常用符号 + 空格”。

我在补最后的细节检查前,先把新详情页脚本做一层转义处理,避免事件详情里有特殊字符时把 DOM 撑坏。

我开始做最后一轮检查:后端码制、两个网页脚本、新增详情页和 CC Lua 一起过一遍,避免这波改动互相打架。

诊断已经空了。我再做一层语法校验,并抓关键定位行,方便你直接跳到 5 位凭证码、独立卡片页和符号键盘这几处确认。

我再抓一下几处精确行号:5 位凭证码、独立卡片页入口、购卡结果链接,以及 CC ABC/SYM 键盘按钮,方便你直接点进去看。

新页面的行号我再单独补一下,上一轮通配路径没命中。

已修改

  • 线上购卡凭证码已改成统一 5 位,不再生成 ICO-xxxx;生成逻辑在 public.js,新建卡记录时也会同时写入 voucher_code/code 字段,位置在 public.js
  • 购卡结果区已改成更醒目的高对比展示,重点强化了 凭证码 / 卡号 / 首次充值 / 状态 的颜色和层级,主逻辑在 ic-card-order.js,样式在 style.css
  • ic-card-order.html 这页我也顺手清掉了残留乱码,并把文案统一成“凭证码”口径,文件在 ic-card-order.html

新页面

  • 已新增独立 IC 卡详情页入口 ticket.fse-media.group/ic/<卡号>,服务端路由在 server/index.js
  • 新页面模板在 ic-card-detail.html,走的是类似电子客票的双栏卡片样式。
  • 页面数据通过公开查询接口加载,脚本在 ic-card-detail.js
  • 线上购卡成功后,结果卡片里已新增“卡片详情”按钮,会直接跳到 /ic/<卡号>,位置在 ic-card-order.js

CC 键盘

  • 办卡姓名输入已新增 ABC / SYM 切换按钮,支持常用符号键盘。
  • 现在姓名输入会走“英文 + 常用符号 + 空格”的收口逻辑,相关代码在 ticketmachine.lua
  • SYM 按钮和字符追加逻辑分别在 ticketmachine.luaticketmachine.lua

验证

注意

  • 这次改的是“新生成的线上购卡凭证码”规则;历史上已经生成出来的 ICO-xxxx 老记录不会自动改写,但现有查询仍兼容旧值。
  • 线上要看到新页面和 5 位凭证码,需要重新部署或重载当前服务,并强刷 ic-card/order
  • 如果你要,我下一步可以继续把 IC 卡查询页 也加一个“查看电子卡片页”按钮,直接从查询结果跳到 /ic/<卡号>

User

线上购卡那里 自定义充值的框放到那个按钮下面,就是把按钮的高度换成现在的一半,然后把输入框放到下面。 有个问题:首次充值的卡(未使用),但是卡详情里面显示余额10,首次充值30. 我们直接改成只显示余额就可以了不显示首次充值

Assistant

我先改线上购卡页的自定义充值布局,再把 IC 卡详情页里“首次充值”字段去掉,只保留余额,避免和实际卡内余额混淆。

我把自定义金额输入框从“持卡人信息”区挪到充值选项区下面,并且会把“自定义”按钮做得更紧凑一些。然后把详情页里的“首次充值”整块删掉。

我已经把布局和详情字段都改完了。现在做一轮检查,确认购卡页和详情页没有新增错误。

我再抓一下关键位置的行号:自定义充值框的新位置、按钮高度样式,以及详情页去掉“首次充值”后的字段区。

已调整

  • 线上购卡页的“自定义充值”输入框已经移到充值按钮区下面,位置在 ic-card-order.html
  • “自定义”按钮高度已改成更紧凑的一版,并在选中时显示下面的输入框,逻辑在 ic-card-order.js
  • 对应样式在 style.css

卡详情页

  • /ic/<卡号> 详情页里已经去掉“首次充值”字段,只保留 当前余额 / 凭证码 / 开卡时间,模板在 ic-card-detail.html
  • 详情页脚本也同步去掉了对“首次充值”的赋值,现在只写余额、凭证码和开卡时间,见 ic-card-detail.js

现在效果

  • 充值区:先点 自定义,下面才会出现金额输入框。
  • 输入区:持卡人姓名表单不再混着自定义金额输入框。
  • 卡详情页:不会再出现“余额 10、首次充值 30”这种并排冲突展示,只显示当前余额。

验证

提示

  • 线上页面如果暂时还是旧布局,强刷 ic-card/order/ic/<卡号> 即可。
  • 如果你愿意,我还可以顺手把 IC 卡查询页 里的“首次充值”字段也一起去掉,整站统一成只显示余额。

User

没有看到“自定义充值”输入框

Assistant

我先核对购卡页当前 DOM 和切换逻辑,确认是“按钮没切到显示状态”,还是输入框实际被隐藏在别处。

我确认到问题点了:现在这版是“只有点了自定义才显示输入框”,在缓存或旧静态文件混用时很容易看不到。
我准备改成更稳妥的方案:输入框固定显示在按钮区下面,点“自定义”时启用并聚焦,选预设金额时禁用但仍可见。

我已经把输入框改成固定显示了,现在只是在“自定义”时高亮并可输入。再做一轮检查确认没有新错误。

已修

  • 我把“自定义充值”输入框改成了固定显示在按钮区下面,不再依赖“点了才出现”的隐藏逻辑。
  • 现在的行为是:
    • 5 / 10 / 15 / 20 时:输入框可见,但禁用
    • 自定义 时:输入框可见、启用,并自动聚焦

改动位置

验证

你现在应该看到

  • “自定义”按钮下方始终有一个输入框
  • 点击“自定义”后,这个输入框会变为可输入状态

提醒

  • 如果线上还没看到,先强刷 ic-card/order,因为这次改的是静态页面和前端脚本。

User

卡ID要统一啊,现在各个界面显示的都不一样,有00020,有64AE5246

Assistant

我先把 IC 卡 card_id 的生成和展示链路梳理一下,重点找出哪里在用“预生成卡号”,哪里在用“MOD 实际返回卡号”,再统一成一套口径。

我已经定位到根因了:
现在系统里同时存在两种 ID 口径。

  • 线上购卡下单时,后端先预生成一个占位卡号,比如 IC-000020
  • 真正出卡时,MOD 又返回实体卡号,比如 64AE5246

所以有些页面拿的是“预生成号”,有些页面拿的是“实体卡号”,就分裂了。
我再确认一下检票和查卡接口现在是按哪一个字段找卡,避免直接改显示把进出站链路弄断。

我已经确认到一个关键点:后台和公开页面很多地方都直接渲染 card.card_id,而这个字段在“线上待领卡”和“实体卡已出卡”两个阶段含义不一样。
我再读一下 /api/cards/check,确认它是不是强依赖当前这条主键,避免我统一显示后把检票查卡弄断。

我准备把“卡 ID”的显示口径收成一条规则:
只有 MOD/实体卡真正拿到的那个 ID 才当作对外卡号;线上待领卡阶段不再把 IC-000020 这种内部占位号当成“卡 ID”展示。

具体会做两层:

  • 后端统一给卡对象补一个 display_card_id
  • 前端各页面都改成显示这个字段,不再直接拿 card_id 上屏

这样至少页面上不会再一会儿 00020、一会儿 64AE5246

我开始改后端响应和前端展示,把“显示用卡号”抽成一个统一字段,不碰底层实际查卡主键,这样不会影响检票和数据同步。

后端已经补好了统一展示字段。现在把前端几个页面都改成只显示 display_card_id,不再直接把内部 card_id 上屏。

我顺手把旧的独立 IC 管理页也一并改一下,避免你本地如果还开着旧文件时继续看到两套卡号。

我已经把主后台、公开查询、详情页、购卡结果页和旧后台页的显示都收成统一字段了。现在做一轮检查,确保没有新增报错。

我再把关键定位行抓一下,方便你直接点进去核对“显示用卡号”的规则和几个页面的替换点。

先继续收口“卡 ID 统一显示”这件事,我会先把代码里所有仍然直接显示 card_id 的地方找出来,再核对后端返回的展示字段是否已经统一。


User

IC卡详情页东西不要那么多,就和电子客票上显示的内容差不多就好了。

Assistant

我继续把卡号显示统一收口,同时会顺手把 IC 卡详情页精简成接近电子客票的展示,不再堆太多字段。

现在看到后端已经在返回 display_card_id,但详情页和结果页还保留了不少“真实卡号”直出逻辑;我先把这些展示入口统一掉,再把详情页内容裁成电子客票那种简洁版。

我准备直接改两个点:一是把 ic-card-detail 页面收成单卡片、少字段的电子客票样式;二是把待领卡状态下的跳转和复制逻辑改成优先用凭证码,避免看到占位卡号。

我已经把详情页主体裁掉了“最近记录”等重内容,接下来再扫一遍剩余直接露出原始 card_id 的前端代码,避免还有漏网的显示入口。

细节页和线上购卡页已经没有新的报错,我再把旧的 ic-card-admin 残留提示也顺手统一一下,避免弹窗里还冒出原始占位号。

已调整

  • 已把详情页精简成接近电子客票的单卡片样式,只保留 卡号 / 持卡人 / 余额 / 凭证码 / 开卡时间 / 状态提示 / 操作按钮,去掉了“最近记录”等过重内容,见 ic-card-detail.html
  • 已把详情页逻辑改成统一优先显示 display_card_id,并根据状态自动切换提示文案;待领卡时按钮会优先按“凭证码”处理,避免又露出占位卡号,见 ic-card-detail.js
  • 已把线上购卡结果页的“查询此卡 / 卡片详情”跳转改成待领卡时优先使用凭证码,避免后续实体卡出卡后链接还绑着旧占位号,见 ic-card-order.js
  • 旧后台页里的创建成功提示和充值弹窗也改成走统一展示口径,不再直接弹原始 card_id,见 ic-card-admin.js

当前效果

  • 详情页现在会更像电子客票,不再堆很多字段。
  • 待领卡状态下,页面展示继续是统一口径,不会一会儿是 IC-000020 一会儿又是别的显示。
  • 已启用实体卡时,仍然显示并复制真实卡号,检票主键逻辑没有动。

检查

  • 已检查 ic-card-detail.htmlic-card-detail.jsic-card-order.jsic-card-admin.js 的诊断,当前无新增报错。

建议

  • 如果你要再进一步统一,我下一步可以把公开查询页 ic-card-search 也继续裁成和电子客票同一套信息密度,这样所有对外页面观感会更一致。

User

去掉购卡的“订单号”吧。 然后在兑换卡的时候没有支付就直接出卡了。 卡号改成后面几位随机的。 控制台无法进入“IC卡管理”页 section 这一块直接拉宽到和上面 section 对齐

Assistant

我先分五块排查:购卡页文案、兑卡支付校验、卡号生成规则、后台 /admin 视图切换、以及详情页宽度布局,然后一起改掉。

我已经定位到后台入口和详情页布局点了,接着看卡号生成和 CC 兑卡支付流程,重点查是不是线上兑卡分支直接跳过了付费确认。

问题点找到了:ticketmachine.lua 的线上兑卡分支把 state.cost = 0payment_mode = 'online',所以确认页会自动放行并直接出卡,这里我会改回“到机支付后才能兑卡”。

后台进不去的根因也找到了:顶部导航还写着旧地址 /ic-card-admin.html,而现在真正可用的是 /admin?view=iccards。我会一并改掉。

我会顺手把服务端也加一道“兑卡必须已支付”的校验,不只修前端/CC 流程,这样就算有人直接调接口也不能白拿卡。

我顺手把公开页文案里的“订单号”一起淡出,保留“凭证码”作为统一说法;后台内部检索能力不动。

改动已经落下去了,我先做一轮诊断检查,重点看 api/public 路由、后台页和购卡详情页有没有因为这些改动引入新错误。

已修改

检查

  • 已检查 server/routes/api.jsserver/routes/public.jsweb/index.htmlweb/ic-card-order.jsweb/ic-card-search.jsweb/ic-card-search.htmlweb/ic-card-detail.htmlweb/ticket-route.html,当前无新增诊断错误。
  • 这次没有在实际 ComputerCraft 机器上跑兑卡流程,所以设备侧建议你再现场点一次“线上兑卡”确认红石付费后才会出卡。

建议验证

  • 线上购卡新建一单,确认生成的占位卡号已变成随机 6 位。
  • 在 CC 上输入凭证码后,不投币时应不能确认出卡。
  • 后台顶部导航点击 IC 卡管理,应直接进入 /admin?view=iccards
  • /ic/<id> 详情页中卡片主区域宽度应与上方介绍区对齐。