/** React 主應用程式 */ const { useState, useEffect } = React; // 工具函數:格式化日期 const formatDate = (date) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}/${month}/${day}`; }; // localStorage 工具函數 const storageUtils = { // 保存台鐵訂票資訊 saveTaiwanRailwayBooking(data) { try { const bookingData = { startStation: data.startStation, endStation: data.endStation, puyumaOnly: data.puyumaOnly, window: data.window, swapSeat: data.swapSeat, t3k: data.t3k }; localStorage.setItem('taiwan_railway_booking', JSON.stringify(bookingData)); } catch (error) { console.error('保存台鐵訂票資訊失敗:', error); } }, // 讀取台鐵訂票資訊 loadTaiwanRailwayBooking() { try { const data = localStorage.getItem('taiwan_railway_booking'); if (data) { const parsed = JSON.parse(data); // 驗證資料格式 if (parsed && typeof parsed === 'object') { return parsed; } } } catch (error) { console.error('讀取台鐵訂票資訊失敗:', error); } return null; }, // 保存高鐵訂票資訊 saveHSRBooking(data) { try { const bookingData = { nationalId: data.nationalId, departureStation: data.departureStation, destinationStation: data.destinationStation }; localStorage.setItem('hsr_booking', JSON.stringify(bookingData)); } catch (error) { console.error('保存高鐵訂票資訊失敗:', error); } }, // 讀取高鐵訂票資訊 loadHSRBooking() { try { const data = localStorage.getItem('hsr_booking'); if (data) { const parsed = JSON.parse(data); // 驗證資料格式 if (parsed && typeof parsed === 'object') { return parsed; } } } catch (error) { console.error('讀取高鐵訂票資訊失敗:', error); } return null; } }; // API 客戶端 const apiClient = { // 獲取 Telegram initData getInitData() { if (window.Telegram && window.Telegram.WebApp) { return window.Telegram.WebApp.initData; } return null; }, // 統一的 API 請求方法 async request(url, options = {}) { const initData = this.getInitData(); const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', 'X-Telegram-Init-Data': initData || '', ...options.headers } }); if (!response.ok) { const error = await response.json().catch(() => ({ message: '請求失敗' })); throw new Error(error.message || '請求失敗'); } return response.json(); }, // GET 請求方法(用於高鐵訂票) async get(url, params = {}) { const initData = this.getInitData(); const queryString = new URLSearchParams(params).toString(); const fullUrl = queryString ? `${url}?${queryString}` : url; const response = await fetch(fullUrl, { method: 'GET', headers: { 'X-Telegram-Init-Data': initData || '' } }); if (!response.ok) { const error = await response.json().catch(() => ({ message: '請求失敗' })); throw new Error(error.message || '請求失敗'); } return response.json(); } }; const App = () => { const [tasks, setTasks] = useState([]); const [currentTaskId, setCurrentTaskId] = useState(null); const [loading, setLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); // Tab 狀態 const [activeTab, setActiveTab] = useState('taiwan_railway'); // 'taiwan_railway' 或 'hsr' // 台鐵表單狀態 const [date, setDate] = useState(''); const [startTime, setStartTime] = useState('16:30'); const [endTime, setEndTime] = useState('17:00'); const [startStation, setStartStation] = useState(''); const [endStation, setEndStation] = useState(''); const [puyumaOnly, setPuyumaOnly] = useState(true); const [window, setWindow] = useState(false); const [swapSeat, setSwapSeat] = useState(false); const [t3k, setT3k] = useState(false); const [note, setNote] = useState(''); // 高鐵表單狀態 const [hsrNationalId, setHsrNationalId] = useState(''); const [hsrDepartureStation, setHsrDepartureStation] = useState(''); const [hsrDestinationStation, setHsrDestinationStation] = useState(''); const [hsrDepartureDate, setHsrDepartureDate] = useState(''); const [hsrDepartureTime, setHsrDepartureTime] = useState('14:30'); const [hsrLatestArrival, setHsrLatestArrival] = useState(''); const [hsrNote, setHsrNote] = useState(''); const [hsrIsSubmitting, setHsrIsSubmitting] = useState(false); const [hsrMessage, setHsrMessage] = useState(null); // 獲取任務列表 const fetchTasks = async () => { try { const data = await apiClient.request('/api/tasks', { method: 'GET' }); if (data.success) { setTasks(data.tasks || []); // 查找當前執行中的任務 const runningTask = data.tasks.find(t => t.status === 'running'); setCurrentTaskId(runningTask ? runningTask.task_id : null); } } catch (error) { console.error('獲取任務列表失敗:', error); } finally { setLoading(false); } }; // 獲取當前執行中的任務 const fetchCurrentTask = async () => { try { const data = await apiClient.request('/api/task/current', { method: 'GET' }); if (data.has_task && data.task) { setCurrentTaskId(data.task.task_id); } else { setCurrentTaskId(null); } } catch (error) { console.error('獲取當前任務失敗:', error); } }; // 取消任務 const handleCancelTask = async (taskId) => { if (!confirm('確定要取消這個任務嗎?')) { return; } try { const data = await apiClient.request('/api/task/cancel', { method: 'POST', body: JSON.stringify({ task_id: taskId }) }); if (data.success) { await fetchTasks(); // 刷新任務列表 alert('任務已取消'); } else { alert(data.message || '取消任務失敗'); } } catch (error) { console.error('取消任務失敗:', error); alert(error.message || '取消任務時發生錯誤'); } }; // 移除任務 const handleRemoveTask = async (taskId) => { if (!confirm('確定要移除這個任務嗎?')) { return; } try { const data = await apiClient.request(`/api/task/${taskId}/remove`, { method: 'POST' }); if (data.success) { await fetchTasks(); // 刷新任務列表 alert('任務已移除'); } else { alert(data.message || '移除任務失敗'); } } catch (error) { console.error('移除任務失敗:', error); alert(error.message || '移除任務時發生錯誤'); } }; // 提交台鐵訂票 const handleSubmit = async () => { if (!startStation || !endStation) { alert('請選擇出發站和終點站'); return; } setIsSubmitting(true); try { const requestData = { date: date || formatDate(new Date()), start_station: startStation, end_station: endStation, start_time: startTime, end_time: endTime, puyuma_only: puyumaOnly, window: window, swap_seat: swapSeat, t3k: t3k }; // 添加備註(如果有) if (note && note.trim()) { requestData.note = note.trim(); } const data = await apiClient.request('/api/book', { method: 'POST', body: JSON.stringify(requestData) }); if (data.success) { alert('訂票任務已加入隊列'); // 保存訂票資訊到 localStorage(不包含日期時間和備註) storageUtils.saveTaiwanRailwayBooking({ startStation, endStation, puyumaOnly, window, swapSeat, t3k }); // 清空備註 setNote(''); // 重新獲取任務列表 await fetchTasks(); } else { alert(data.message || '提交訂票失敗'); } } catch (error) { console.error('提交訂票失敗:', error); alert(error.message || '提交訂票時發生錯誤'); } finally { setIsSubmitting(false); } }; // 提交高鐵訂票 const handleHsrSubmit = async () => { // 表單驗證 if (!hsrNationalId || !hsrNationalId.trim()) { setHsrMessage({ type: 'error', text: '請輸入身分證字號' }); return; } if (!hsrDepartureStation) { setHsrMessage({ type: 'error', text: '請選擇出發站' }); return; } if (!hsrDestinationStation) { setHsrMessage({ type: 'error', text: '請選擇目的地站' }); return; } setHsrIsSubmitting(true); setHsrMessage(null); try { // 轉換日期格式從 YYYY/MM/DD 到 YYYY-MM-DD const formattedDate = (hsrDepartureDate || formatDate(new Date())).replace(/\//g, '-'); const params = { national_id: hsrNationalId.trim(), start_position: hsrDepartureStation, end_position: hsrDestinationStation, departure_date: formattedDate, departure_time: hsrDepartureTime }; // 添加最晚抵達時間(如果有) if (hsrLatestArrival && hsrLatestArrival.trim()) { params.latest_arrival = hsrLatestArrival.trim(); } // 添加備註(如果有) if (hsrNote && hsrNote.trim()) { params.note = hsrNote.trim(); } const data = await apiClient.request('/api/book_taiwan_hsr', { method: 'POST', body: JSON.stringify(params) }); if (data.success !== false) { setHsrMessage({ type: 'success', text: '訂票任務已加入隊列' }); // 保存訂票資訊到 localStorage(不包含日期時間和備註) storageUtils.saveHSRBooking({ nationalId: hsrNationalId.trim(), departureStation: hsrDepartureStation, destinationStation: hsrDestinationStation }); // 清空備註 setHsrNote(''); // 重新獲取任務列表 await fetchTasks(); } else { setHsrMessage({ type: 'error', text: '訂票失敗!' }); } } catch (error) { console.error('提交高鐵訂票失敗:', error); setHsrMessage({ type: 'error', text: '訂票失敗!' }); } finally { setHsrIsSubmitting(false); } }; // 初始化日期和從 localStorage 恢復資料 useEffect(() => { const today = formatDate(new Date()); setDate(today); setHsrDepartureDate(today); // 從 localStorage 恢復台鐵訂票資訊 const taiwanBooking = storageUtils.loadTaiwanRailwayBooking(); if (taiwanBooking) { if (taiwanBooking.startStation) setStartStation(taiwanBooking.startStation); if (taiwanBooking.endStation) setEndStation(taiwanBooking.endStation); if (typeof taiwanBooking.puyumaOnly === 'boolean') setPuyumaOnly(taiwanBooking.puyumaOnly); if (typeof taiwanBooking.window === 'boolean') setWindow(taiwanBooking.window); if (typeof taiwanBooking.swapSeat === 'boolean') setSwapSeat(taiwanBooking.swapSeat); if (typeof taiwanBooking.t3k === 'boolean') setT3k(taiwanBooking.t3k); } // 從 localStorage 恢復高鐵訂票資訊 const hsrBooking = storageUtils.loadHSRBooking(); if (hsrBooking) { if (hsrBooking.nationalId) setHsrNationalId(hsrBooking.nationalId); if (hsrBooking.departureStation) setHsrDepartureStation(hsrBooking.departureStation); if (hsrBooking.destinationStation) setHsrDestinationStation(hsrBooking.destinationStation); } }, []); // 頁面載入時獲取任務列表 useEffect(() => { fetchTasks(); }, []); // 定時輪詢更新任務列表(每 5 秒) useEffect(() => { const interval = setInterval(() => { fetchTasks(); }, 5000); return () => clearInterval(interval); }, []); // 初始化 Telegram Web App useEffect(() => { if (window.Telegram && window.Telegram.WebApp) { window.Telegram.WebApp.ready(); window.Telegram.WebApp.expand(); } }, []); if (loading) { return (