火车纪念票是一个微信小程序,利用它可以快速制作生成电子版火车纪念票。本仓库是票根纪念册小程序火车票模块的历史实现。
- 记录历史出行
在官方的铁路12306购票平台上,系统仅支持查看30天历史内的出行订单记录,当我们后期突然想要回看历史出行信息时,在铁路12306的app上往往就已经找不到了。火车纪念票提供了一个平台可以让用户集中记录其历史出行记录并存储在云端,随时可以回看历史出行数据。
- 收集火车票
生活中有很多人喜欢收集自己出行后的火车票,整理成册,形成自己专属的回忆与仪式感。2024年10月18日,国家税务总局、财政部、国铁集团联合对外发布公告,自2024年11月1日起,在我国铁路客运领域推广使用全面数字化的电子发票——电子发票(铁路电子客票)。至过渡期2025年9月30后,纸质火车票和纸质报销凭证将完全不再使用。火车纪念票可以为用户快速创建一个与原版车票近似的电子图片票根,支持下载。用户可以选择在小程序内收集或打印后自行进行实体收集。
火车纪念票使用大模型视觉模型对火车票或者各种包含出行信息的图片进行识别,并根据提示词让大模型结构化输出想要提取的出行信息,能够以非常低廉的成本和非常快速的技术开发完成对包含火车票出行信息的图片进行内容识别与提取。
- 小程序核心功能已完整实现,针对录入票根这一场景,支持完全自定义的手动输入和使用图片或铁路电子发票pdf文件进行快速的自动化输入,并且在录入页面支持实时预览。
- 小程序支持对录入好的票根进行高清图片下载,方便用户自行打印。
- 小程序云端记录历史数据,支持二次编辑或删除等操作,能够让用户在小程序统一记录历史所有出行数据。
- 火车纪念票使用微信小程序为实现载体,天生跨平台,且无需安装,使用起来方便快捷。
- 应用在用户第一次使用时默认为用户展示一张示例票根,方便用户快速理解小程序的用途与生成的图片的效果。
- 应用默认进入票根集页面并以时间倒序展示用户所有票根,另外一步即可进入创建页面。
- 票根录入过程支持自动识别与手动录入两种形式,自动识别后也支持再手动纠错。编辑页面添加二次返回确认防止误触退出丢失编辑中的数据,编辑页面可实时预览创建效果。
- 在使用大模型自动识别时,受限于大模型的识别与解析速度,需要较长的时间。在该过程中为用户添加一个预估的倒计时展示,给用户提供心理预期,防止误认为程序出现了异常。
- 首页,新用户默认有一张示例票根
![]()
- 编辑添加页面,具有防误触返回二次确认
![]()
- 解析识别预估倒计时
![]()
- 解析后自动填充,支持实时预览
![]()
- 保存后示例票根自动删除,用户票根倒序展示,点击支持高级功能
![]()
应用使用微信小程序云开发作为后端实现,使用云函数承载后端逻辑,使用云数据库持久化用户数据。
前端使用微信小程序原生语法和框架,原生组件和腾讯tdesign组件。
使用视觉大模型进行图片识别与解析。
- 微信扫码访问。票根纪念册线上最新版本,与本仓库实现有出入。
![]()
- 小程序打开后默认展示票根集,新用户内默认有一张示例票根。
- 点击添加按钮进入编辑添加页面,支持手动填写或者从图片识别,也可以从铁路电子发票文件识别。
- 确认填写内容后点击保存,系统会保存数据并返回首页,新创建的票根会展示在列。
- 点击票根可以进行高级操作,包括编辑、复制、删除、下载和转换样式。
- 大模型调用
async parseTrainImage(img) {
const res = await this.generateText({
model: "hunyuan-turbo-vision",
messages: [
{
role: "user",
content: [
{
type: "text",
text:
'图片中可能包含一段铁路出行信息' +
'请你解析文本以json格式返回以下内容:' +
'start(出发站中文车站名,不保留站字),' +
'end(终到站中文车站名,不保留站字),' +
'price(票价,¥后面的数字部分),' +
'date(乘车日期,格式处理为中划线分割),' +
'train(班次号), time(开车时间),' +
'seat(车厢座位,x车x号x铺),' +
'seatKind(座席或卧铺类别,一般为xx座或xx卧,无座或不对号入座也返回),' +
'idNumber(乘客证件号,含有*号的编号部分), name(乘客姓名,一般在证件号后), ' +
'check(检票或候车信息), ' +
'isInvoice(是否为发票,正文包含发票二字返回true,否则返回false),' +
'括号内为对应的中文解释,json键只使用英文部分。' +
'如果不能解析到以上信息,请直接返回无法解析。' +
'除以上信息外,若主体为一张火车票而不是铁路电子客票、行程信息提示单或截图,还可能包含:' +
'leftUpNumber(出发站站名上方编号), downNumber(虚线框下方的单排字符)' +
'marks(购票标识,只能是“网折学惠孩赠”这几个字中的一到两个字,这几个字可能被圆圈包裹), ' +
'以上所有字段,如何未能匹配到某个键值,请保留键但把对应值赋值空字符串。' +
'若图中包含多个出行信息,请只返回第一个。',
},
{
type: "image_url",
image_url: {
url: img,
},
}]
},
],
});
if (res.text.includes('无法解析')) {
throw new Error('无法解析');
}
return res;
}- 截图本地保存纪念票图片
async takeSnapshot(that) {
const saveSnapshot = (snapRes) => {
wx.saveImageToPhotosAlbum({
filePath: snapRes.tempFilePath,
success() {
wx.hideLoading()
wx.showToast({
title: '下载成功'
})
},
fail() {
wx.hideLoading()
wx.showModal({
content: '保存失败,请点击确定前往设置页面,开启添加到相册权限后重试',
success(res) {
if (res.confirm) {
wx.openSetting()
}
}
})
},
})
}
await this.showLoading(Loading.DOWNLOADING)
setTimeout(() => {
that.createSelectorQuery().select("#card")
.node().exec(res => {
const node = res[0].node
node.takeSnapshot({
type: 'file',
format: 'png',
success: (res) => {
saveSnapshot(res)
that.setData({
trainDownloading: false
})
},
fail() {
wx.hideLoading()
wx.showToast({
title: '下载失败,请稍候重试',
})
}
})
})
}, 500)
}




