Kaynağa Gözat

feat: 数字票计费与查询页面(fee计算/列表筛选)+ 多语言及测试完善

Co-Authored-By: Claude <noreply@anthropic.com>
lq 3 hafta önce
ebeveyn
işleme
622ca64afd

+ 3 - 0
.gitignore

@@ -42,6 +42,9 @@ web/pnpm-lock.yaml
 /server/server.exe
 /server/server.exe~
 
+# Go build/test cache redirected to the workspace
+/.gocache/
+
 # Runtime
 /log/
 server_backup.tar.gz

+ 29 - 0
frontend/src/api/digitalTicket.js

@@ -0,0 +1,29 @@
+import service from '@/utils/request'
+
+export const getDigitalTicketList = (params) => service({
+  url: '/digital-ticket/list',
+  method: 'get',
+  params
+})
+
+export const getDigitalTicketSummary = (params) => service({
+  url: '/digital-ticket/summary',
+  method: 'get',
+  params
+})
+
+export const getDigitalTicket = (ticketNo) => service({
+  url: `/digital-ticket/${encodeURIComponent(ticketNo)}`,
+  method: 'get'
+})
+
+export const payDigitalTicket = (ticketNo, data) => service({
+  url: `/digital-ticket/${encodeURIComponent(ticketNo)}/pay`,
+  method: 'post',
+  data
+})
+
+export const exitDigitalTicket = (ticketNo) => service({
+  url: `/digital-ticket/${encodeURIComponent(ticketNo)}/exit`,
+  method: 'post'
+})

+ 78 - 0
frontend/src/lang/en.js

@@ -228,4 +228,82 @@ export default {
 	revenueReport: 'Revenue Report',
 	paymentMethodConfig: 'Payment Methods',
 	paymentEntryConfig: 'Payment Entries',
+	digitalTicket: 'Digital Ticket',
+	digitalTicketEyebrow: 'DIGITAL PASS DESK',
+	digitalTicketSubtitle: 'Review entry credentials, payment status, and exit progress in one place',
+	digitalTicketSyncing: 'Syncing',
+	digitalTicketUpdatedAt: 'Updated',
+	digitalTicketRefresh: 'Refresh digital tickets',
+	digitalTicketSummaryAria: 'Digital ticket status overview',
+	digitalTicketCurrentResult: 'Current result',
+	digitalTicketCountUnit: 'tickets',
+	digitalTicketPending: 'Pending payment',
+	digitalTicketPendingNote: 'Awaiting settlement',
+	digitalTicketPaid: 'Paid',
+	digitalTicketPaidNote: 'Ready to exit',
+	digitalTicketExited: 'Exited',
+	digitalTicketExpired: 'Expired',
+	digitalTicketExitedNote: 'Completed today',
+	digitalTicketQueueKicker: 'TICKET QUEUE',
+	digitalTicketQueue: 'Digital ticket queue',
+	digitalTicketPlateFilter: 'Filter by license plate',
+	digitalTicketFilter: 'Filter',
+	digitalTicketStateAria: 'Ticket status',
+	digitalTicketAll: 'All',
+	digitalTicketEmpty: 'No digital tickets',
+	digitalTicketPreviewKicker: 'DIGITAL TICKET',
+	digitalTicketPreview: 'Ticket preview',
+	digitalTicketCopyNo: 'Copy ticket number',
+	digitalTicketNo: 'Ticket number',
+	digitalTicketPlateNo: 'License plate',
+	digitalTicketNoPlate: 'Unregistered vehicle',
+	digitalTicketEntryTime: 'Entry time',
+	digitalTicketTrigger: 'Trigger',
+	digitalTicketCurrentFee: 'Current fee',
+	digitalTicketVehicleRecord: 'Vehicle record',
+	digitalTicketPaymentEntry: 'Payment entry',
+	digitalTicketPaymentMethod: 'Payment method',
+	digitalTicketTimeline: 'Status timeline',
+	digitalTicketEventCount: '{count} events',
+	digitalTicketNoEvents: 'No status events',
+	digitalTicketSettlement: 'Settlement',
+	digitalTicketSettled: 'Settled',
+	digitalTicketUnsettled: 'Unsettled',
+	digitalTicketPayable: 'Amount due',
+	digitalTicketPaidAmount: 'Amount paid',
+	digitalTicketOrderNo: 'Order number',
+	digitalTicketNextAction: 'NEXT ACTION',
+	digitalTicketCompleteSettlement: 'Complete settlement',
+	digitalTicketConfirmExit: 'Confirm vehicle exit',
+	digitalTicketEntryPlaceholder: 'Payment entry',
+	digitalTicketMethodPlaceholder: 'Payment method',
+	digitalTicketReceivedPlaceholder: 'Amount received',
+	digitalTicketOrderPlaceholder: 'Payment order number (optional)',
+	digitalTicketChange: 'Change',
+	digitalTicketSelectHint: 'Select an entry and method to continue',
+	digitalTicketConfirmPayment: 'Confirm payment',
+	digitalTicketExitHint: 'This ticket is paid. Confirm to record the exit time.',
+	digitalTicketExitButton: 'Confirm exit',
+	digitalTicketEmptyTitle: 'Select a digital ticket',
+	digitalTicketEmptySubtitle: 'Ticket details will appear here',
+	digitalTicketTriggerRfid: 'RFID recognition',
+	digitalTicketTriggerManual: 'Manual entry',
+	digitalTicketTriggerCamera: 'License plate recognition',
+	digitalTicketEntryCounter: 'Payment booth',
+	digitalTicketEntryTicket: 'Parking ticket',
+	digitalTicketEntryCentral: 'Central payment machine',
+	digitalTicketMethodCash: 'Manual payment',
+	digitalTicketMethodPos: 'POS payment',
+	digitalTicketMethodQr: 'Ticket QR payment',
+	digitalTicketCopied: 'Ticket number copied',
+	digitalTicketClipboardUnsupported: 'Copy is not supported in this environment',
+	digitalTicketLoadFailed: 'Failed to load digital tickets',
+	digitalTicketSelectPayment: 'Select a payment entry and method',
+	digitalTicketInsufficient: 'Received amount cannot be lower than the amount due',
+	digitalTicketPaySuccess: 'Digital ticket payment completed',
+	digitalTicketPayFailed: 'Payment confirmation failed',
+	digitalTicketExitConfirmMessage: 'Record this vehicle as exited?',
+	digitalTicketExitConfirmTitle: 'Confirm exit',
+	digitalTicketExitSuccess: 'Exit recorded',
+	digitalTicketExitFailed: 'Exit confirmation failed',
 }

+ 78 - 0
frontend/src/lang/zh-CN.js

@@ -228,4 +228,82 @@ export default {
 	revenueReport: '收入报表',
 	paymentMethodConfig: '支付方式',
 	paymentEntryConfig: '支付入口',
+	digitalTicket: '数字票',
+	digitalTicketEyebrow: 'DIGITAL PASS DESK',
+	digitalTicketSubtitle: '统一查看入场凭证、支付状态与离场进度',
+	digitalTicketSyncing: '正在同步',
+	digitalTicketUpdatedAt: '更新于',
+	digitalTicketRefresh: '刷新数字票',
+	digitalTicketSummaryAria: '数字票状态概览',
+	digitalTicketCurrentResult: '当前结果',
+	digitalTicketCountUnit: '张票据',
+	digitalTicketPending: '待支付',
+	digitalTicketPendingNote: '等待结算',
+	digitalTicketPaid: '已支付',
+	digitalTicketPaidNote: '可确认离场',
+	digitalTicketExited: '已离场',
+	digitalTicketExpired: '已过期',
+	digitalTicketExitedNote: '今日已完成',
+	digitalTicketQueueKicker: 'TICKET QUEUE',
+	digitalTicketQueue: '数字票队列',
+	digitalTicketPlateFilter: '按车牌号筛选',
+	digitalTicketFilter: '筛选',
+	digitalTicketStateAria: '票据状态',
+	digitalTicketAll: '全部',
+	digitalTicketEmpty: '暂无数字票',
+	digitalTicketPreviewKicker: 'DIGITAL TICKET',
+	digitalTicketPreview: '票据预览',
+	digitalTicketCopyNo: '复制票号',
+	digitalTicketNo: '票据编号',
+	digitalTicketPlateNo: '车牌号',
+	digitalTicketNoPlate: '无牌车',
+	digitalTicketEntryTime: '入场时间',
+	digitalTicketTrigger: '触发方式',
+	digitalTicketCurrentFee: '当前费用',
+	digitalTicketVehicleRecord: '车辆记录',
+	digitalTicketPaymentEntry: '支付入口',
+	digitalTicketPaymentMethod: '支付方式',
+	digitalTicketTimeline: '状态轨迹',
+	digitalTicketEventCount: '{count} 条记录',
+	digitalTicketNoEvents: '暂无状态记录',
+	digitalTicketSettlement: '结算信息',
+	digitalTicketSettled: '已结算',
+	digitalTicketUnsettled: '未结算',
+	digitalTicketPayable: '应付金额',
+	digitalTicketPaidAmount: '实付金额',
+	digitalTicketOrderNo: '订单号',
+	digitalTicketNextAction: 'NEXT ACTION',
+	digitalTicketCompleteSettlement: '完成本次结算',
+	digitalTicketConfirmExit: '确认车辆离场',
+	digitalTicketEntryPlaceholder: '支付入口',
+	digitalTicketMethodPlaceholder: '支付方式',
+	digitalTicketReceivedPlaceholder: '实收金额',
+	digitalTicketOrderPlaceholder: '支付订单号(可选)',
+	digitalTicketChange: '找零',
+	digitalTicketSelectHint: '选择入口和支付方式后确认',
+	digitalTicketConfirmPayment: '确认收款',
+	digitalTicketExitHint: '该票据已支付,确认后将记录离场时间。',
+	digitalTicketExitButton: '确认离场',
+	digitalTicketEmptyTitle: '选择一张数字票',
+	digitalTicketEmptySubtitle: '票据详情会在这里展开',
+	digitalTicketTriggerRfid: 'RFID 识别',
+	digitalTicketTriggerManual: '人工录入',
+	digitalTicketTriggerCamera: '车牌识别',
+	digitalTicketEntryCounter: '收费岗亭',
+	digitalTicketEntryTicket: '停车小票',
+	digitalTicketEntryCentral: '中央缴费机',
+	digitalTicketMethodCash: '人工支付',
+	digitalTicketMethodPos: 'POS机支付',
+	digitalTicketMethodQr: '小票扫码支付',
+	digitalTicketCopied: '票号已复制',
+	digitalTicketClipboardUnsupported: '当前环境不支持复制',
+	digitalTicketLoadFailed: '数字票加载失败',
+	digitalTicketSelectPayment: '请选择支付入口和支付方式',
+	digitalTicketInsufficient: '实收金额不能低于应付金额',
+	digitalTicketPaySuccess: '数字票已完成支付',
+	digitalTicketPayFailed: '支付确认失败',
+	digitalTicketExitConfirmMessage: '确认记录该车辆离场吗?',
+	digitalTicketExitConfirmTitle: '确认离场',
+	digitalTicketExitSuccess: '已记录离场',
+	digitalTicketExitFailed: '离场确认失败',
 }

+ 79 - 1
frontend/src/lang/zh-HK.js

@@ -217,5 +217,83 @@ export default {
   paymentRecord: '收費記錄',
   monthlyCard: '包月車管理',
   shiftRecord: '交接班管理',
-  revenueReport: '收入報表',
+	revenueReport: '收入報表',
+	digitalTicket: '數碼票',
+	digitalTicketEyebrow: 'DIGITAL PASS DESK',
+	digitalTicketSubtitle: '統一查看入場憑證、付款狀態及離場進度',
+	digitalTicketSyncing: '同步中',
+	digitalTicketUpdatedAt: '更新於',
+	digitalTicketRefresh: '重新整理數碼票',
+	digitalTicketSummaryAria: '數碼票狀態概覽',
+	digitalTicketCurrentResult: '目前結果',
+	digitalTicketCountUnit: '張票據',
+	digitalTicketPending: '待付款',
+	digitalTicketPendingNote: '等待結算',
+	digitalTicketPaid: '已付款',
+	digitalTicketPaidNote: '可確認離場',
+	digitalTicketExited: '已離場',
+	digitalTicketExpired: '已過期',
+	digitalTicketExitedNote: '今日已完成',
+	digitalTicketQueueKicker: 'TICKET QUEUE',
+	digitalTicketQueue: '數碼票佇列',
+	digitalTicketPlateFilter: '按車牌號碼篩選',
+	digitalTicketFilter: '篩選',
+	digitalTicketStateAria: '票據狀態',
+	digitalTicketAll: '全部',
+	digitalTicketEmpty: '暫無數碼票',
+	digitalTicketPreviewKicker: 'DIGITAL TICKET',
+	digitalTicketPreview: '票據預覽',
+	digitalTicketCopyNo: '複製票號',
+	digitalTicketNo: '票據編號',
+	digitalTicketPlateNo: '車牌號碼',
+	digitalTicketNoPlate: '無牌車',
+	digitalTicketEntryTime: '入場時間',
+	digitalTicketTrigger: '觸發方式',
+	digitalTicketCurrentFee: '目前費用',
+	digitalTicketVehicleRecord: '車輛記錄',
+	digitalTicketPaymentEntry: '付款入口',
+	digitalTicketPaymentMethod: '付款方式',
+	digitalTicketTimeline: '狀態軌跡',
+	digitalTicketEventCount: '{count} 條記錄',
+	digitalTicketNoEvents: '暫無狀態記錄',
+	digitalTicketSettlement: '結算資訊',
+	digitalTicketSettled: '已結算',
+	digitalTicketUnsettled: '未結算',
+	digitalTicketPayable: '應付金額',
+	digitalTicketPaidAmount: '實付金額',
+	digitalTicketOrderNo: '訂單號碼',
+	digitalTicketNextAction: 'NEXT ACTION',
+	digitalTicketCompleteSettlement: '完成今次結算',
+	digitalTicketConfirmExit: '確認車輛離場',
+	digitalTicketEntryPlaceholder: '付款入口',
+	digitalTicketMethodPlaceholder: '付款方式',
+	digitalTicketReceivedPlaceholder: '實收金額',
+	digitalTicketOrderPlaceholder: '付款訂單號碼(可選)',
+	digitalTicketChange: '找零',
+	digitalTicketSelectHint: '選擇入口及付款方式後確認',
+	digitalTicketConfirmPayment: '確認收款',
+	digitalTicketExitHint: '該票據已付款,確認後將記錄離場時間。',
+	digitalTicketExitButton: '確認離場',
+	digitalTicketEmptyTitle: '選擇一張數碼票',
+	digitalTicketEmptySubtitle: '票據詳情會在這裡展開',
+	digitalTicketTriggerRfid: 'RFID 識別',
+	digitalTicketTriggerManual: '人手輸入',
+	digitalTicketTriggerCamera: '車牌識別',
+	digitalTicketEntryCounter: '收費崗亭',
+	digitalTicketEntryTicket: '停車小票',
+	digitalTicketEntryCentral: '中央繳費機',
+	digitalTicketMethodCash: '人手付款',
+	digitalTicketMethodPos: 'POS機付款',
+	digitalTicketMethodQr: '小票掃碼付款',
+	digitalTicketCopied: '票號已複製',
+	digitalTicketClipboardUnsupported: '目前環境不支援複製',
+	digitalTicketLoadFailed: '數碼票載入失敗',
+	digitalTicketSelectPayment: '請選擇付款入口及付款方式',
+	digitalTicketInsufficient: '實收金額不能低於應付金額',
+	digitalTicketPaySuccess: '數碼票已完成付款',
+	digitalTicketPayFailed: '付款確認失敗',
+	digitalTicketExitConfirmMessage: '確認記錄該車輛離場嗎?',
+	digitalTicketExitConfirmTitle: '確認離場',
+	digitalTicketExitSuccess: '已記錄離場',
+	digitalTicketExitFailed: '離場確認失敗',
 }

+ 79 - 1
frontend/src/lang/zh-TW.js

@@ -217,5 +217,83 @@ export default {
   paymentRecord: '收費記錄',
   monthlyCard: '包月車管理',
   shiftRecord: '交接班管理',
-  revenueReport: '收入報表',
+	revenueReport: '收入報表',
+	digitalTicket: '數位票',
+	digitalTicketEyebrow: 'DIGITAL PASS DESK',
+	digitalTicketSubtitle: '統一查看入場憑證、付款狀態與離場進度',
+	digitalTicketSyncing: '同步中',
+	digitalTicketUpdatedAt: '更新於',
+	digitalTicketRefresh: '重新整理數位票',
+	digitalTicketSummaryAria: '數位票狀態概覽',
+	digitalTicketCurrentResult: '目前結果',
+	digitalTicketCountUnit: '張票據',
+	digitalTicketPending: '待付款',
+	digitalTicketPendingNote: '等待結算',
+	digitalTicketPaid: '已付款',
+	digitalTicketPaidNote: '可確認離場',
+	digitalTicketExited: '已離場',
+	digitalTicketExpired: '已過期',
+	digitalTicketExitedNote: '今日已完成',
+	digitalTicketQueueKicker: 'TICKET QUEUE',
+	digitalTicketQueue: '數位票佇列',
+	digitalTicketPlateFilter: '按車牌號碼篩選',
+	digitalTicketFilter: '篩選',
+	digitalTicketStateAria: '票據狀態',
+	digitalTicketAll: '全部',
+	digitalTicketEmpty: '暫無數位票',
+	digitalTicketPreviewKicker: 'DIGITAL TICKET',
+	digitalTicketPreview: '票據預覽',
+	digitalTicketCopyNo: '複製票號',
+	digitalTicketNo: '票據編號',
+	digitalTicketPlateNo: '車牌號碼',
+	digitalTicketNoPlate: '無牌車',
+	digitalTicketEntryTime: '入場時間',
+	digitalTicketTrigger: '觸發方式',
+	digitalTicketCurrentFee: '目前費用',
+	digitalTicketVehicleRecord: '車輛記錄',
+	digitalTicketPaymentEntry: '付款入口',
+	digitalTicketPaymentMethod: '付款方式',
+	digitalTicketTimeline: '狀態軌跡',
+	digitalTicketEventCount: '{count} 筆記錄',
+	digitalTicketNoEvents: '暫無狀態記錄',
+	digitalTicketSettlement: '結算資訊',
+	digitalTicketSettled: '已結算',
+	digitalTicketUnsettled: '未結算',
+	digitalTicketPayable: '應付金額',
+	digitalTicketPaidAmount: '實付金額',
+	digitalTicketOrderNo: '訂單號碼',
+	digitalTicketNextAction: 'NEXT ACTION',
+	digitalTicketCompleteSettlement: '完成本次結算',
+	digitalTicketConfirmExit: '確認車輛離場',
+	digitalTicketEntryPlaceholder: '付款入口',
+	digitalTicketMethodPlaceholder: '付款方式',
+	digitalTicketReceivedPlaceholder: '實收金額',
+	digitalTicketOrderPlaceholder: '付款訂單號碼(可選)',
+	digitalTicketChange: '找零',
+	digitalTicketSelectHint: '選擇入口和付款方式後確認',
+	digitalTicketConfirmPayment: '確認收款',
+	digitalTicketExitHint: '該票據已付款,確認後將記錄離場時間。',
+	digitalTicketExitButton: '確認離場',
+	digitalTicketEmptyTitle: '選擇一張數位票',
+	digitalTicketEmptySubtitle: '票據詳情會在這裡展開',
+	digitalTicketTriggerRfid: 'RFID 識別',
+	digitalTicketTriggerManual: '人工輸入',
+	digitalTicketTriggerCamera: '車牌識別',
+	digitalTicketEntryCounter: '收費崗亭',
+	digitalTicketEntryTicket: '停車小票',
+	digitalTicketEntryCentral: '中央繳費機',
+	digitalTicketMethodCash: '人工付款',
+	digitalTicketMethodPos: 'POS機付款',
+	digitalTicketMethodQr: '小票掃碼付款',
+	digitalTicketCopied: '票號已複製',
+	digitalTicketClipboardUnsupported: '目前環境不支援複製',
+	digitalTicketLoadFailed: '數位票載入失敗',
+	digitalTicketSelectPayment: '請選擇付款入口和付款方式',
+	digitalTicketInsufficient: '實收金額不能低於應付金額',
+	digitalTicketPaySuccess: '數位票已完成付款',
+	digitalTicketPayFailed: '付款確認失敗',
+	digitalTicketExitConfirmMessage: '確認記錄該車輛離場嗎?',
+	digitalTicketExitConfirmTitle: '確認離場',
+	digitalTicketExitSuccess: '已記錄離場',
+	digitalTicketExitFailed: '離場確認失敗',
 }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 585 - 0
frontend/src/view/parking/digitalTicket.vue


+ 5 - 5
frontend/src/view/parking/parkingInfo/lotDetail.vue

@@ -1,11 +1,11 @@
 <template>
   <div v-loading="loading">
     <el-descriptions
-      v-if="detail.id"
+      v-if="detail.ID"
       :column="2"
       border
     >
-      <el-descriptions-item label="ID" :span="2">{{ detail.id }}</el-descriptions-item>
+      <el-descriptions-item label="ID" :span="2">{{ detail.ID }}</el-descriptions-item>
       <el-descriptions-item label="区域编码">{{ detail.lot_code }}</el-descriptions-item>
       <el-descriptions-item label="区域名称">{{ detail.lot_name }}</el-descriptions-item>
       <el-descriptions-item label="容量">{{ detail.capacity }}</el-descriptions-item>
@@ -14,11 +14,11 @@
       <el-descriptions-item label="更新时间" :span="2">{{ detail.UpdatedAt || '-' }}</el-descriptions-item>
     </el-descriptions>
 
-    <div v-if="!detail.id && !loading" style="text-align: center; color: #999; margin-top: 40px;">
+    <div v-if="!detail.ID && !loading" style="text-align: center; color: #999; margin-top: 40px;">
       请从左侧选择一个停车场
     </div>
 
-    <div v-if="detail.id" class="action-bar">
+    <div v-if="detail.ID" class="action-bar">
       <el-button type="primary" icon="Edit" @click="handleEdit">编辑</el-button>
       <el-button type="danger" icon="Delete" @click="handleDelete">删除</el-button>
     </div>
@@ -62,7 +62,7 @@ const handleEdit = () => {
 }
 
 const handleDelete = () => {
-  emit('delete', detail.value.id || detail.value.ID)
+  emit('delete', detail.value.ID)
 }
 
 watch(

+ 1 - 0
internal/dao/digital_ticket.go

@@ -15,6 +15,7 @@ type DigitalTicket struct {
 	EntryTime       time.Time  `json:"entry_time"`
 	ExitTime        *time.Time `json:"exit_time"`
 	Fee             float64    `json:"fee"`
+	PaidAmount      float64    `json:"paid_amount"`
 	PaymentEntry    string     `gorm:"size:32" json:"payment_entry"`
 	PaymentMethod   string     `gorm:"size:20" json:"payment_method"`
 	PaymentOrderNo  string     `gorm:"size:64" json:"payment_order_no"`

+ 35 - 0
internal/initialize/payment_seed.go

@@ -136,6 +136,10 @@ func EnsurePaymentConfiguration() error {
 		if err != nil {
 			return err
 		}
+		ticketMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "digitalTicket", "digitalTicket", "view/parking/digitalTicket.vue", "数字票", "tickets", 10)
+		if err != nil {
+			return err
+		}
 		for _, menu := range []*dao.SysBaseMenu{methodMenu, entryMenu} {
 			for _, authorityID := range []string{"888", "9527"} {
 				var count int64
@@ -151,6 +155,19 @@ func EnsurePaymentConfiguration() error {
 				}
 			}
 		}
+		for _, authorityID := range []string{"618", "888", "9527"} {
+			var count int64
+			if err := db.Model(&dao.SysAuthorityMenu{}).
+				Where("sys_base_menu_id = ? AND sys_authority_authority_id = ?", ticketMenu.ID, authorityID).Count(&count).Error; err != nil {
+				return fmt.Errorf("初始化数字票菜单权限失败: %w", err)
+			}
+			if count == 0 {
+				link := dao.SysAuthorityMenu{MenuId: fmt.Sprint(ticketMenu.ID), AuthorityId: authorityID}
+				if err := db.Create(&link).Error; err != nil {
+					return fmt.Errorf("初始化数字票菜单权限失败: %w", err)
+				}
+			}
+		}
 	}
 
 	apis := []dao.SysApi{
@@ -168,6 +185,11 @@ func EnsurePaymentConfiguration() error {
 		{Path: "/payment/entries/:id", Description: "更新支付入口", ApiGroup: "支付入口", Method: "PUT"},
 		{Path: "/payment/entries/:id/status", Description: "启停支付入口", ApiGroup: "支付入口", Method: "PATCH"},
 		{Path: "/payment/entries/:id", Description: "删除支付入口", ApiGroup: "支付入口", Method: "DELETE"},
+		{Path: "/digital-ticket/summary", Description: "查询数字票汇总", ApiGroup: "数字票", Method: "GET"},
+		{Path: "/digital-ticket/list", Description: "查询数字票列表", ApiGroup: "数字票", Method: "GET"},
+		{Path: "/digital-ticket/:ticketNo", Description: "查询数字票详情", ApiGroup: "数字票", Method: "GET"},
+		{Path: "/digital-ticket/:ticketNo/pay", Description: "确认数字票支付", ApiGroup: "数字票", Method: "POST"},
+		{Path: "/digital-ticket/:ticketNo/exit", Description: "确认数字票离场", ApiGroup: "数字票", Method: "POST"},
 	}
 	for _, api := range apis {
 		var existing dao.SysApi
@@ -211,6 +233,19 @@ func EnsurePaymentConfiguration() error {
 			}
 		}
 	}
+	for _, role := range []string{"618", "888", "9527"} {
+		for _, rule := range [][2]string{
+			{"/digital-ticket/summary", "GET"},
+			{"/digital-ticket/list", "GET"},
+			{"/digital-ticket/:ticketNo", "GET"},
+			{"/digital-ticket/:ticketNo/pay", "POST"},
+			{"/digital-ticket/:ticketNo/exit", "POST"},
+		} {
+			if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil {
+				return err
+			}
+		}
+	}
 	return nil
 }
 

+ 2 - 2
internal/initialize/payment_seed_test.go

@@ -44,9 +44,9 @@ func TestEnsurePaymentConfigurationIsIdempotent(t *testing.T) {
 
 	var permissionCount int64
 	require.NoError(t, db.Model(&dao.SysAuthorityMenu{}).Count(&permissionCount).Error)
-	require.EqualValues(t, 4, permissionCount)
+	require.EqualValues(t, 7, permissionCount)
 
 	var ruleCount int64
 	require.NoError(t, db.Table("casbin_rule").Count(&ruleCount).Error)
-	require.EqualValues(t, 32, ruleCount)
+	require.EqualValues(t, 47, ruleCount)
 }

+ 16 - 4
internal/modules/digital-ticket/api.go

@@ -89,15 +89,27 @@ func ListTickets(c *gin.Context) {
 	response.OkWithDetailed(response.PageResult{List: list, Total: total, Page: q.Page, PageSize: q.PageSize}, "查询成功", c)
 }
 
+func SummaryTickets(c *gin.Context) {
+	var q repository.TicketListQuery
+	if err := c.ShouldBindQuery(&q); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	summary, err := ticketSvc.Summary(q)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithData(summary, c)
+}
+
 func SetupDigitalTicketRouter(router *gin.RouterGroup) {
 	dt := router.Group("/digital-ticket")
 	{
+		dt.GET("/summary", SummaryTickets)
+		dt.GET("/list", ListTickets)
 		dt.GET("/:ticketNo", GetTicket)
 		dt.POST("/:ticketNo/pay", PayTicket)
 		dt.POST("/:ticketNo/exit", ExitTicket)
 	}
-	dt2 := router.Group("/digital-ticket")
-	{
-		dt2.GET("/list", ListTickets)
-	}
 }

+ 61 - 8
internal/modules/digital-ticket/repository/repo.go

@@ -67,14 +67,16 @@ type TicketListQuery struct {
 	PageSize    int    `form:"page_size"`
 }
 
+type TicketSummary struct {
+	Total   int64 `json:"total"`
+	Pending int64 `json:"pending"`
+	Paid    int64 `json:"paid"`
+	Exited  int64 `json:"exited"`
+	Expired int64 `json:"expired"`
+}
+
 func (r *TicketRepository) List(q TicketListQuery) ([]dao.DigitalTicket, int64, error) {
-	db := global.GVA_DB.Model(&dao.DigitalTicket{})
-	if q.PlateNumber != "" {
-		db = db.Where("plate_number LIKE ?", "%"+q.PlateNumber+"%")
-	}
-	if q.State != "" {
-		db = db.Where("state = ?", q.State)
-	}
+	db := ticketQuery(q, true)
 	var total int64
 	db.Count(&total)
 	if q.Page <= 0 {
@@ -84,6 +86,57 @@ func (r *TicketRepository) List(q TicketListQuery) ([]dao.DigitalTicket, int64,
 		q.PageSize = 10
 	}
 	var list []dao.DigitalTicket
-	err := db.Order("id DESC").Offset((q.Page - 1) * q.PageSize).Limit(q.PageSize).Find(&list).Error
+	err := db.Order("digital_ticket.id DESC").Offset((q.Page - 1) * q.PageSize).Limit(q.PageSize).Find(&list).Error
 	return list, total, err
 }
+
+// Summary returns counts for the complete filtered result set. Pagination and
+// the selected state tab are intentionally ignored so the dashboard totals do
+// not change when the user moves between pages.
+func (r *TicketRepository) Summary(q TicketListQuery) (TicketSummary, error) {
+	db := ticketQuery(q, false)
+
+	var rows []struct {
+		State string
+		Count int64
+	}
+	if err := db.Select("digital_ticket.state, COUNT(*) AS count").Group("digital_ticket.state").Scan(&rows).Error; err != nil {
+		return TicketSummary{}, err
+	}
+
+	var summary TicketSummary
+	for _, row := range rows {
+		summary.Total += row.Count
+		switch row.State {
+		case "created", "pending_payment":
+			summary.Pending += row.Count
+		case "paid":
+			summary.Paid += row.Count
+		case "exited":
+			summary.Exited += row.Count
+		case "expired":
+			summary.Expired += row.Count
+		}
+	}
+	return summary, nil
+}
+
+// ticketQuery applies filters shared by the paged list and the unpaged
+// summary. A left join keeps old tickets searchable even when their copied
+// plate_number column is empty and the vehicle record still has the plate.
+func ticketQuery(q TicketListQuery, includeState bool) *gorm.DB {
+	db := global.GVA_DB.Model(&dao.DigitalTicket{})
+	if q.PlateNumber != "" {
+		like := "%" + q.PlateNumber + "%"
+		db = db.Joins("LEFT JOIN vehicle_record ON vehicle_record.id = digital_ticket.vehicle_record_id").
+			Where("(digital_ticket.plate_number LIKE ? OR vehicle_record.plate_number LIKE ?)", like, like)
+	}
+	if includeState && q.State != "" {
+		if q.State == "pending_payment" {
+			db = db.Where("digital_ticket.state IN ?", []string{"created", "pending_payment"})
+		} else {
+			db = db.Where("digital_ticket.state = ?", q.State)
+		}
+	}
+	return db
+}

+ 55 - 0
internal/modules/digital-ticket/service/fee.go

@@ -0,0 +1,55 @@
+package service
+
+import (
+	"time"
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+)
+
+// CalculateFee is the shared parking-fee calculation used by vehicle exit
+// flows and the digital-ticket live preview.
+func CalculateFee(vehicle *dao.Vehicle, stayTime int64) float64 {
+	if vehicle == nil || global.GVA_DB == nil {
+		return 0
+	}
+
+	var feeConfig dao.FeeConfig
+	if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil {
+		return float64(stayTime) * 0.1
+	}
+	if feeConfig.UnitFee == 0 && feeConfig.StartFee == 0 && feeConfig.DailyMaxFee == 0 {
+		return float64(stayTime) * 0.1
+	}
+	if feeConfig.UnitTime <= 0 {
+		return feeConfig.StartFee
+	}
+
+	isVIP := false
+	if vehicle.OwnerId != nil {
+		var owner dao.Owner
+		if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil {
+			now := time.Now()
+			isVIP = owner.IsVip && !owner.VipExpireTime.IsZero() && owner.VipExpireTime.After(now)
+		}
+	}
+	if isVIP && feeConfig.IsVIPFree {
+		return 0
+	}
+	if stayTime <= int64(feeConfig.StartTime) {
+		return 0
+	}
+
+	extraTime := stayTime - int64(feeConfig.StartTime)
+	extraUnits := extraTime / int64(feeConfig.UnitTime)
+	if extraTime%int64(feeConfig.UnitTime) > 0 {
+		extraUnits++
+	}
+	fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee
+	if feeConfig.DailyMaxFee > 0 && fee > feeConfig.DailyMaxFee {
+		fee = feeConfig.DailyMaxFee
+	}
+	if isVIP {
+		fee *= feeConfig.VIPDiscount
+	}
+	return fee
+}

+ 69 - 3
internal/modules/digital-ticket/service/service.go

@@ -99,7 +99,7 @@ func (s *TicketService) TransitionTx(db *gorm.DB, id uint, toState string, extra
 		updates["exit_time"] = now
 	}
 	// 只允许表中存在的字段写入数据库,其余仅记录到事件日志
-	dbFields := map[string]bool{"payment_entry": true, "payment_method": true, "payment_order_no": true, "fee": true}
+	dbFields := map[string]bool{"payment_entry": true, "payment_method": true, "payment_order_no": true, "paid_amount": true, "fee": true}
 	for k, v := range extra {
 		if dbFields[k] {
 			updates[k] = v
@@ -148,9 +148,75 @@ func (s *TicketService) MarkPaidAndExitedTx(db *gorm.DB, recordID uint, paymentE
 }
 
 func (s *TicketService) GetByTicketNo(no string) (*dao.DigitalTicket, error) {
-	return s.repo.GetByTicketNo(no)
+	ticket, err := s.repo.GetByTicketNo(no)
+	if err != nil {
+		return nil, err
+	}
+	s.enrichTicket(ticket)
+	return ticket, nil
 }
 
 func (s *TicketService) List(q repository.TicketListQuery) ([]dao.DigitalTicket, int64, error) {
-	return s.repo.List(q)
+	list, total, err := s.repo.List(q)
+	if err != nil {
+		return nil, 0, err
+	}
+	for i := range list {
+		s.enrichTicket(&list[i])
+	}
+	return list, total, nil
+}
+
+func (s *TicketService) Summary(q repository.TicketListQuery) (repository.TicketSummary, error) {
+	return s.repo.Summary(q)
+}
+
+// enrichTicket keeps the ticket view useful before payment. A ticket is
+// created at vehicle entry, while the definitive fee is calculated at payment
+// or exit time. Pending tickets therefore need a live fee preview derived from
+// their vehicle record and current parking duration.
+func (s *TicketService) enrichTicket(ticket *dao.DigitalTicket) {
+	if ticket == nil || ticket.VehicleRecordID == 0 || global.GVA_DB == nil {
+		return
+	}
+
+	var record dao.VehicleRecord
+	if err := global.GVA_DB.First(&record, ticket.VehicleRecordID).Error; err != nil {
+		return
+	}
+	if record.PlateNumber != "" {
+		ticket.PlateNumber = record.PlateNumber
+	}
+
+	var vehicle dao.Vehicle
+	vehicleQuery := global.GVA_DB
+	vehicleFound := false
+	if record.PlateNumber != "" {
+		vehicleQuery = vehicleQuery.Where("plate_number = ?", record.PlateNumber)
+		vehicleFound = vehicleQuery.First(&vehicle).Error == nil
+	} else if record.RFIDTag != "" {
+		vehicleQuery = vehicleQuery.Where("rfid_tag = ?", record.RFIDTag)
+		vehicleFound = vehicleQuery.First(&vehicle).Error == nil
+	}
+	if ticket.PlateNumber == "" && vehicleFound {
+		ticket.PlateNumber = vehicle.PlateNumber
+	}
+
+	if ticket.State == StateCreated || ticket.State == StatePendingPayment {
+		if !vehicleFound {
+			return
+		}
+		stayTime := int64(time.Since(record.EntryTime).Minutes())
+		if stayTime < 0 {
+			stayTime = 0
+		}
+		ticket.Fee = CalculateFee(&vehicle, stayTime)
+		return
+	}
+
+	// Older rows may have a zero digital-ticket fee even though the vehicle
+	// record was already settled. Keep the historical record's fee visible.
+	if ticket.Fee == 0 && record.Fee != 0 {
+		ticket.Fee = record.Fee
+	}
 }

+ 71 - 0
internal/modules/digital-ticket/service/service_test.go

@@ -1,13 +1,16 @@
 package service
 
 import (
+	"fmt"
 	"testing"
+	"time"
 
 	"github.com/glebarez/sqlite"
 	"github.com/stretchr/testify/require"
 	"gorm.io/gorm"
 	"wails-app/internal/dao"
 	"wails-app/internal/global"
+	"wails-app/internal/modules/digital-ticket/repository"
 )
 
 func setupTicketServiceTest(t *testing.T) *TicketService {
@@ -46,3 +49,71 @@ func TestMarkPaidAndExitedRequiresATicket(t *testing.T) {
 		svc.MarkPaidAndExitedTx(global.GVA_DB, record.ID, "counter", "cash", "", 0, 0),
 		"车辆对应的数字票不存在")
 }
+
+func TestSummaryCountsTheCompleteFilteredSet(t *testing.T) {
+	svc := setupTicketServiceTest(t)
+	states := []string{StateCreated, StatePendingPayment, StatePendingPayment, StatePaid, StateExited, StateExpired}
+	for i, state := range states {
+		record := &dao.VehicleRecord{PlateNumber: "SUMMARY"}
+		require.NoError(t, global.GVA_DB.Create(record).Error)
+		ticket := &dao.DigitalTicket{
+			TicketNo:        fmt.Sprintf("SUMMARY-%d", i),
+			PlateNumber:     "SUMMARY",
+			State:           state,
+			VehicleRecordID: record.ID,
+			EntryTime:       time.Now(),
+		}
+		require.NoError(t, global.GVA_DB.Create(ticket).Error)
+	}
+
+	summary, err := svc.Summary(repository.TicketListQuery{PlateNumber: "SUMMARY", State: StatePaid, Page: 2, PageSize: 1})
+	require.NoError(t, err)
+	require.EqualValues(t, 6, summary.Total)
+	require.EqualValues(t, 3, summary.Pending)
+	require.EqualValues(t, 1, summary.Paid)
+	require.EqualValues(t, 1, summary.Exited)
+	require.EqualValues(t, 1, summary.Expired)
+
+	list, total, err := svc.List(repository.TicketListQuery{PlateNumber: "SUMMARY", State: StatePendingPayment})
+	require.NoError(t, err)
+	require.EqualValues(t, 3, total)
+	require.Len(t, list, 3)
+
+	legacyRecord := &dao.VehicleRecord{PlateNumber: "SUMMARY-LEGACY"}
+	require.NoError(t, global.GVA_DB.Create(legacyRecord).Error)
+	require.NoError(t, global.GVA_DB.Create(&dao.DigitalTicket{
+		TicketNo: "SUMMARY-LEGACY-001", State: StatePendingPayment,
+		VehicleRecordID: legacyRecord.ID, EntryTime: time.Now(),
+	}).Error)
+	legacySummary, err := svc.Summary(repository.TicketListQuery{PlateNumber: "SUMMARY-LEGACY"})
+	require.NoError(t, err)
+	require.EqualValues(t, 1, legacySummary.Total)
+	require.EqualValues(t, 1, legacySummary.Pending)
+}
+
+func TestPendingTicketUsesCurrentFeeAndRecordPlate(t *testing.T) {
+	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
+	require.NoError(t, err)
+	global.GVA_DB = db
+	require.NoError(t, db.AutoMigrate(
+		&dao.VehicleRecord{}, &dao.DigitalTicket{}, &dao.Vehicle{}, &dao.VehicleType{}, &dao.FeeConfig{},
+	))
+
+	vehicleType := &dao.VehicleType{Name: "临时车", IsSystem: true}
+	require.NoError(t, db.Create(vehicleType).Error)
+	require.NoError(t, db.Create(&dao.FeeConfig{
+		VehicleTypeID: vehicleType.ID, StartTime: 0, StartFee: 0, UnitTime: 60, UnitFee: 5, DailyMaxFee: 50,
+	}).Error)
+	require.NoError(t, db.Create(&dao.Vehicle{PlateNumber: "粤A12345", VehicleTypeID: vehicleType.ID}).Error)
+	record := &dao.VehicleRecord{PlateNumber: "粤A12345", EntryTime: time.Now().Add(-61 * time.Minute)}
+	require.NoError(t, db.Create(record).Error)
+	require.NoError(t, db.Create(&dao.DigitalTicket{
+		TicketNo: "FEE-001", PlateNumber: "", State: StatePendingPayment,
+		VehicleRecordID: record.ID, EntryTime: record.EntryTime,
+	}).Error)
+
+	ticket, err := NewTicketService().GetByTicketNo("FEE-001")
+	require.NoError(t, err)
+	require.Equal(t, "粤A12345", ticket.PlateNumber)
+	require.InDelta(t, 10, ticket.Fee, 0.001)
+}

+ 8 - 56
internal/service/vehicle/vehicle.go

@@ -210,8 +210,12 @@ func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.Vehicle
 	// Create the vehicle record and its digital ticket atomically. A vehicle
 	// entry without a ticket would make every later payment flow ambiguous.
 	entryTime := time.Now()
+	plateNumber := vehicle.PlateNumber
+	if plateNumber == "" {
+		plateNumber = req.PlateNumber
+	}
 	record := dao.VehicleRecord{
-		PlateNumber:    req.PlateNumber,
+		PlateNumber:    plateNumber,
 		RFIDTag:        req.RFIDTag,
 		EntryTime:      entryTime,
 		ParkingLotID:   req.ParkingLotID,
@@ -234,7 +238,7 @@ func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.Vehicle
 		if err := tx.Create(&record).Error; err != nil {
 			return err
 		}
-		if _, err := ticketSvc.CreateTx(tx, req.PlateNumber, triggerMode, record.ID); err != nil {
+		if _, err := ticketSvc.CreateTx(tx, plateNumber, triggerMode, record.ID); err != nil {
 			return fmt.Errorf("创建数字票失败: %w", err)
 		}
 		return tx.Model(&dao.Vehicle{}).Where("id = ?", vehicle.ID).
@@ -248,7 +252,7 @@ func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.Vehicle
 		Success: true,
 		Message: "车辆入场成功",
 	}
-	response.Data.PlateNumber = req.PlateNumber
+	response.Data.PlateNumber = plateNumber
 	response.Data.EntryTime = entryTime.Unix()
 	response.Data.ParkingLotID = req.ParkingLotID
 	response.Data.ParkingSpaceID = req.ParkingSpaceID
@@ -434,59 +438,7 @@ func (s *VehicleService) ExitConfirm(req request.ExitConfirmRequest, operatorID
 
 // calculateFee 计算停车费用
 func (s *VehicleService) calculateFee(vehicle *dao.Vehicle, stayTime int64) float64 {
-	// 1. 根据车辆类型ID获取收费配置(唯一)
-	var feeConfig dao.FeeConfig
-	if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil {
-		// 无配置 → 默认计费:0.1元/分钟
-		return float64(stayTime) * 0.1
-	}
-	// 配置存在但全为 0 → 同样回退默认费率
-	if feeConfig.UnitFee == 0 && feeConfig.StartFee == 0 && feeConfig.DailyMaxFee == 0 {
-		return float64(stayTime) * 0.1
-	}
-
-	// 2. 判断VIP有效性(必须IsVip=true、过期时间未到、且过期时间非零值)
-	isVIP := false
-	if vehicle.OwnerId != nil {
-		var owner dao.Owner
-		if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil {
-			now := time.Now()
-			isVIP = owner.IsVip && owner.VipExpireTime.After(now) && !owner.VipExpireTime.IsZero()
-		}
-	}
-
-	// 3. VIP 免费
-	if isVIP && feeConfig.IsVIPFree {
-		return 0
-	}
-
-	// ===================== 修复这里 =====================
-	// 4. 在免费时长内 → 直接 0 元
-	if stayTime <= int64(feeConfig.StartTime) {
-		return 0
-	}
-
-	// 5. 超过免费时长 → 开始计费
-	extraTime := stayTime - int64(feeConfig.StartTime)
-	extraUnits := extraTime / int64(feeConfig.UnitTime)
-	if extraTime%int64(feeConfig.UnitTime) > 0 {
-		extraUnits++
-	}
-
-	// 起步费 + 超时费用
-	fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee
-
-	// 6. 每日封顶
-	if feeConfig.DailyMaxFee > 0 && fee > feeConfig.DailyMaxFee {
-		fee = feeConfig.DailyMaxFee
-	}
-
-	// 7. VIP 折扣
-	if isVIP {
-		fee *= feeConfig.VIPDiscount
-	}
-
-	return fee
+	return ticketService.CalculateFee(vehicle, stayTime)
 }
 
 // InParkVehicle 在场车辆信息

+ 5 - 7
test/testutil/e2e_edge_cases_test.go

@@ -660,13 +660,12 @@ func TestDigitalTicketDetail(t *testing.T) {
 }
 
 // TestDigitalTicketPay DT02: 支付数字票
-// BUG: paid_amount 字段在 digital_ticket 表中不存在,PayTicket API 会报 SQL 错误
-// 预期: 该测试应失败,暴露后端缺陷
+// payment entry is explicit because entry and method are independent settings.
 func TestDigitalTicketPay(t *testing.T) {
 	env := InitTestEnv(t)
 	ticketNo, _ := createTicket(t, env, "DT002", "plate_recognition")
 	resp, body := env.DoPost("/digital-ticket/"+ticketNo+"/pay", map[string]interface{}{
-		"payment_method": "cash", "payment_order_no": "PO002", "paid_amount": 20.0,
+		"payment_entry": "ticket", "payment_method": "cash", "payment_order_no": "PO002", "paid_amount": 20.0,
 	})
 	AssertResponse(t, resp, body, http.StatusOK, response.SUCCESS)
 	fmt.Println("PASS: Pay API works correctly")
@@ -678,7 +677,7 @@ func TestDigitalTicketExit(t *testing.T) {
 	ticketNo, _ := createTicket(t, env, "DT003", "rfid")
 	// 支付
 	_, _ = env.DoPost("/digital-ticket/"+ticketNo+"/pay", map[string]interface{}{
-		"payment_method": "cash", "payment_order_no": "PO003", "paid_amount": 15.0,
+		"payment_entry": "ticket", "payment_method": "cash", "payment_order_no": "PO003", "paid_amount": 15.0,
 	})
 	// 支付成功后离场
 	resp, body := env.DoPost("/digital-ticket/"+ticketNo+"/exit", nil)
@@ -700,14 +699,13 @@ func TestDigitalTicketList(t *testing.T) {
 }
 
 // TestDigitalTicketFullFlow DT05: 完整流程
-// BUG: Pay API 因 paid_amount 缺失而失败,完整流程无法通过 pay API 完成
-// 测试验证: exit API 本身在状态正确时可正常工作
+// payment and exit should advance the digital-ticket lifecycle independently.
 func TestDigitalTicketFullFlow(t *testing.T) {
 	env := InitTestEnv(t)
 	ticketNo, _ := createTicket(t, env, "DT005", "manual")
 	// Pay
 	resp1, body1 := env.DoPost("/digital-ticket/"+ticketNo+"/pay", map[string]interface{}{
-		"payment_method": "cash", "payment_order_no": "PO005", "paid_amount": 25.0,
+		"payment_entry": "ticket", "payment_method": "cash", "payment_order_no": "PO005", "paid_amount": 25.0,
 	})
 	AssertResponse(t, resp1, body1, http.StatusOK, response.SUCCESS)
 	// Exit

+ 1 - 0
test/testutil/testenv.go

@@ -265,6 +265,7 @@ func addTestCasbinRules(t *testing.T, db *gorm.DB) {
 		{"888", "/monthly-card/refund", "DELETE"},
 		{"888", "/monthly-card/list", "GET"},
 		// 数字票
+		{"888", "/digital-ticket/summary", "GET"},
 		{"888", "/digital-ticket/list", "GET"},
 		{"888", "/digital-ticket/*", "GET"},
 		{"888", "/digital-ticket/*/pay", "POST"},