Skip to content

Commit 8d98216

Browse files
committed
feat(website):新增车票自动填充功能及识别界面
1 parent cbd09bf commit 8d98216

File tree

3 files changed

+102
-10
lines changed

3 files changed

+102
-10
lines changed

package/website/src/api/ticketService.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,45 @@ const api = axios.create({
1212
export const ticketService = {
1313
// 获取列表
1414
async getTickets(params: TicketQueryParams) {
15-
const { data } = await api.get<{ items: TicketBackend[], total: number }>('/api/train-ticket', { params });
15+
const { data } = await api.get<{ items: TicketBackend[], total: number }>('/train-ticket', { params });
1616
return data;
1717
},
1818

1919
// 创建
2020
async createTicket(data: Partial<TicketBackend>) {
21-
const { data: res } = await api.post<TicketBackend>('/api/train-ticket', data);
21+
const { data: res } = await api.post<TicketBackend>('/train-ticket', data);
2222
return res;
2323
},
2424

2525
// 更新
2626
async updateTicket(id: number, data: Partial<TicketBackend>) {
27-
const { data: res } = await api.put<TicketBackend>(`/api/train-ticket/${id}`, data);
27+
const { data: res } = await api.put<TicketBackend>(`/train-ticket/${id}`, data);
2828
return res;
2929
},
3030

3131
// 删除
3232
async deleteTicket(id: number) {
33-
await api.delete(`/api/train-ticket/${id}`);
33+
await api.delete(`/train-ticket/${id}`);
3434
return true;
3535
},
3636

3737
// 批量删除 (前端循环调用或后端支持批量接口)
3838
async batchDeleteTickets(ids: number[]) {
3939
// 假设后端没有批量接口,使用 Promise.all
40-
const promises = ids.map(id => api.delete(`/api/train-ticket/${id}`));
40+
const promises = ids.map(id => api.delete(`/train-ticket/${id}`));
4141
await Promise.all(promises);
4242
return true;
43+
},
44+
45+
// 识别车票
46+
async recognizeTicket(file: File) {
47+
const formData = new FormData();
48+
formData.append('file', file);
49+
const { data } = await api.post('/train-ticket/recognize', formData, {
50+
headers: {
51+
'Content-Type': 'multipart/form-data'
52+
}
53+
});
54+
return data;
4355
}
4456
};

package/website/src/components/TicketFormModal.vue

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,28 @@
66
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl w-full max-w-2xl relative z-10 overflow-hidden flex flex-col max-h-[90vh]">
77
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center bg-slate-50 dark:bg-slate-800">
88
<h2 class="text-lg font-bold text-slate-800 dark:text-white">{{ isEditing ? '编辑车票' : '新增车票' }}</h2>
9-
<button @click="handleCancel" class="text-slate-400 hover:text-red-500 transition-colors">
10-
<X class="w-6 h-6" />
11-
</button>
9+
<div class="flex items-center gap-2">
10+
<button
11+
@click="triggerFileInput"
12+
:disabled="recognizing"
13+
class="flex items-center gap-1.5 px-3 py-1.5 text-sm bg-indigo-50 text-indigo-600 dark:bg-indigo-900/30 dark:text-indigo-400 rounded-md hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-colors"
14+
title="上传车票图片自动识别"
15+
>
16+
<Sparkles v-if="!recognizing" class="w-4 h-4" />
17+
<span v-else class="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin"></span>
18+
{{ recognizing ? '识别中...' : '智能填充' }}
19+
</button>
20+
<input
21+
ref="fileInput"
22+
type="file"
23+
accept="image/*"
24+
class="hidden"
25+
@change="handleFileChange"
26+
/>
27+
<button @click="handleCancel" class="text-slate-400 hover:text-red-500 transition-colors ml-2">
28+
<X class="w-6 h-6" />
29+
</button>
30+
</div>
1231
</div>
1332
<div class="p-6 overflow-y-auto dark:text-slate-200">
1433
<form @submit.prevent="handleSubmit" class="grid grid-cols-1 md:grid-cols-2 gap-2">
@@ -241,6 +260,67 @@ const axiosInstance = axios.create({
241260
},
242261
});
243262
263+
// 识别相关
264+
const fileInput = ref(null);
265+
const recognizing = ref(false);
266+
267+
const triggerFileInput = () => {
268+
fileInput.value.click();
269+
};
270+
271+
const handleFileChange = async (event) => {
272+
const file = event.target.files[0];
273+
if (!file) return;
274+
275+
// Reset input
276+
event.target.value = '';
277+
278+
recognizing.value = true;
279+
try {
280+
const formData = new FormData();
281+
formData.append('file', file);
282+
283+
// 直接使用 axiosInstance 调用后端接口
284+
// 注意:这里需要根据实际 API 路径调整,假设 apiBaseUrl 是后端根地址
285+
// 如果 ticketService 已经封装好了,最好通过 props 传入 ticketService 或者 emit 事件让父组件处理
286+
// 但为了保持组件独立性,这里我们直接调用 /api/train-ticket/recognize
287+
288+
// 由于 props.apiBaseUrl 可能包含 /api 也可能不包含,这里假设它指向后端根目录
289+
// 检查 apiBaseUrl 是否以 /api 结尾
290+
let url = '/api/train-ticket/recognize';
291+
if (!props.apiBaseUrl.includes('/api')) {
292+
// 如果 base url 没有 /api,保持原样
293+
}
294+
295+
const response = await axiosInstance.post('/train-ticket/recognize', formData, {
296+
headers: {
297+
'Content-Type': 'multipart/form-data'
298+
}
299+
});
300+
301+
const data = response.data;
302+
303+
// 填充表单
304+
if (data.train_code) form.value.train_code = data.train_code;
305+
if (data.departure_station) form.value.from = data.departure_station;
306+
if (data.arrival_station) form.value.to = data.arrival_station;
307+
if (data.datetime) form.value.dateTime = data.datetime;
308+
if (data.carriage) form.value.carriage = data.carriage;
309+
if (data.seat_num) form.value.seatNumber = data.seat_num;
310+
if (data.price) form.value.price = data.price;
311+
if (data.seat_type) form.value.seatType = data.seat_type;
312+
if (data.name) form.value.name = data.name;
313+
314+
ElMessage.success('车票识别成功,已自动填充');
315+
316+
} catch (error) {
317+
console.error('Recognition failed:', error);
318+
ElMessage.error(error.response?.data?.detail || '识别失败,请重试');
319+
} finally {
320+
recognizing.value = false;
321+
}
322+
};
323+
244324
// 表单模型
245325
const form = ref({
246326
id: null,
@@ -319,7 +399,7 @@ async function fetchSchedules() {
319399
scheduleError.value = '';
320400
try {
321401
// 只传递train_code,其他参数留空
322-
const response = await axiosInstance.get('/api/railway/train-schedules', {
402+
const response = await axiosInstance.get('/railway/train-schedules', {
323403
params: {
324404
train_code: trainCode,
325405
page: 1,

package/website/src/views/TicketPage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@
267267
import { ref, computed, onMounted, onUnmounted } from 'vue';
268268
import {
269269
TrainFront, Search, Plus, MapPin, Clock, Route,
270-
ChevronDown, Trash2, X, User, RefreshCw
270+
ChevronDown, Trash2, X, User, RefreshCw, Database
271271
} from 'lucide-vue-next';
272272
import { ElMessage } from 'element-plus';
273273
import { ListTree, LayoutGrid } from 'lucide-vue-next'; // 引入图标

0 commit comments

Comments
 (0)