(() => { const $ = (sel) => document.querySelector(sel); const listEl = $('#list'); const detailEl = $('#detail'); const qEl = $('#q'); const btn = $('#searchBtn'); const state = { items: [], selectedId: '' }; const api = { searchTickets: async (q) => { const r = await fetch(`/api/public/tickets?q=${encodeURIComponent(q || '')}`); return r.json(); }, ticketDetail: async (id) => { const r = await fetch(`/api/public/tickets/${encodeURIComponent(id)}`); return r.json(); }, popular: async () => { const r = await fetch('/api/public/popular'); return r.json(); } }; const formatTime = (value) => { if (value == null || value === '') return '---'; let ts = Number(value); if (Number.isFinite(ts)) { if (ts > 0 && ts < 1000000000000) ts *= 1000; const date = new Date(ts); if (!Number.isNaN(date.getTime())) { return date.toLocaleString('zh-CN', { hour12: false }); } } const date = new Date(value); if (!Number.isNaN(date.getTime())) { return date.toLocaleString('zh-CN', { hour12: false }); } return String(value); }; const formatTrainType = (type) => { if (!type) return '普通'; const t = type.toLowerCase(); if (t === 'local') return '普通'; if (t === 'ltd.exp' || t === 'express') return '特急'; return type; }; const escapeHtml = (value) => String(value == null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); const getTicketId = (obj) => (obj && (obj.ticket_id || obj["车票编号"] || obj.id)) || ''; const isValidStatus = (status) => { const s = String(status || '').toLowerCase(); return s === '有效' || s === 'valid' || s === 'unused' || s === 'active' || s.includes('有效') || s.includes('未使用'); }; const formatStatusText = (status) => { const s = String(status || '').toLowerCase(); if (s === '有效' || s === 'valid' || s === 'unused' || s === 'active' || s.includes('有效') || s.includes('未使用')) return '有效'; if (s === '已使用' || s === 'used') return '已使用'; if (!s) return '未知'; if (s === 'expired') return '失效'; if (s === 'refunded') return '已退票'; return String(status); }; const getEventType = (event) => String(event.type || event["类型"] || '').toLowerCase(); const formatEventTitle = (event) => { const type = getEventType(event); const action = String(event.action || event["动作"] || '').toLowerCase(); if (type === 'sale' || type === '售票') return '售票成功'; if (type === 'entry' || action === 'entry') return '进站成功'; if (type === 'exit' || action === 'exit') return '出站成功'; if (type === 'status' || type === '状态') return '状态变更'; return event.type || event["类型"] || '状态更新'; }; const formatEventLocation = (event) => { const type = getEventType(event); const stationName = event.station_name || event["售票站"] || event["发生站"] || ''; const stationCode = event.station_code || event["站点编号"] || ''; if (type === 'sale' || type === '售票') { return stationName || '线上售票'; } if (!stationName && !stationCode) return '---'; return [stationName, stationName && stationCode ? stationCode : ''].filter(Boolean).join(' '); }; const formatEventExtra = (event) => { const type = getEventType(event); if (type === 'sale' || type === '售票') { const amount = event.amount ?? event["售票额"]; if (amount != null && amount !== '') return `票价:¥ ${amount}`; } const stationEn = event.station_en || event["站点英文"] || ''; const deviceId = event["设备编号"] || event.device_id || ''; if (stationEn && deviceId) return `${stationEn} (${deviceId})`; if (deviceId) return `设备:${deviceId}`; return stationEn; }; function renderList(items) { state.items = items; listEl.innerHTML = ''; if (items.length === 0) { listEl.innerHTML = '

未找到匹配结果。

'; return; } items.forEach(it => { const id = it.ticket_id || it["车票编号"] || ''; const row = document.createElement('div'); const statusText = formatStatusText(it.status || it["状态"] || ''); const isSelected = state.selectedId === id; row.className = `jr-ticket-row${isSelected ? ' is-active' : ''}`; const overview = it.overview || it["概览"] || null; const startName = overview ? (overview.start_name || overview["起点"]) : (it.start_name || it["起点"] || '---'); const terminalName = overview ? (overview.terminal_name || overview["终点"]) : (it.terminal_name || it["终点"] || '---'); const updateTime = formatTime( (overview && (overview.last_update_ts || overview["上次更新时间"])) || it.last_update_ts || it["上次更新时间"] || '' ); row.innerHTML = `
${escapeHtml(id)} ${escapeHtml(statusText)}
${escapeHtml(startName)} → ${escapeHtml(terminalName)}
最近更新 ${escapeHtml(updateTime)}
`; row.onclick = () => { state.selectedId = id; renderList(state.items); loadDetail(id); }; listEl.appendChild(row); }); } function openDetail(id) { if (window.location.hostname.includes('fse-media.group')) { window.open(`https://ticket.fse-media.group/detail/${id}`, '_blank'); } else { window.open(`/${id}`, '_blank'); } } function renderDetail(d) { const ov = d.overview || d["概览"] || {}; const evs = d.events || d["事件"] || []; const id = getTicketId(d) || getTicketId(ov); const stRaw = ov.status || ov["状态"] || d.status || d["状态"] || ''; const statusText = formatStatusText(stRaw); const statusClass = isValidStatus(stRaw) ? 'jr-status-valid' : (statusText === '已使用' ? 'jr-status-used' : 'jr-status-expired'); detailEl.innerHTML = `
${id} ${statusText}
${ov.start_name || ov["起点"] || '---'} ${ov.start_code || ov["起点编号"] || ''}
${ov.start_en || ov["起点英文"] || ''}
${ov.terminal_name || ov["终点"] || '---'} ${ov.terminal_code || ov["终点编号"] || ''}
${ov.terminal_en || ov["终点英文"] || ''}
车型${formatTrainType(ov.train_type || ov["车型"])}
乘次${ov.trips_total ?? ov["总乘次"] ?? 0}
票价${ov.amount ?? ov["金额"] ?? 0}
更新${formatTime(ov.last_update_ts ?? ov["上次更新时间"])}

流转记录

Recent Events
${evs.map(e => `
${formatEventTitle(e)} ${formatTime(e.ts || e["时间戳"])}
${formatEventLocation(e)}
${formatEventExtra(e) || '---'}
`).join('')}
`; } async function loadDetail(id) { state.selectedId = id; renderList(state.items); detailEl.innerHTML = '

正在加载详情...

'; try { const d = await api.ticketDetail(id); if (d && getTicketId(d) && (d.overview || d["概览"])) { renderDetail(d); } else { detailEl.innerHTML = '

未找到车票详情。

'; } // Update URL without reload const newUrl = window.location.origin + window.location.pathname + '?id=' + encodeURIComponent(id); window.history.pushState({ path: newUrl }, '', newUrl); } catch (e) { detailEl.innerHTML = '

加载详情失败。

'; } } async function doSearch() { const q = (qEl.value || '').trim(); listEl.innerHTML = '

正在搜索...

'; try { const d = await api.searchTickets(q); state.selectedId = state.selectedId && d.some((item) => getTicketId(item) === state.selectedId) ? state.selectedId : ''; renderList(d); } catch (e) { listEl.innerHTML = '

搜索失败。

'; } } async function loadPopular() { try { const d = await api.popular(); const ps = $('#popularStations'); const pr = $('#popularRoutes'); ps.innerHTML = d.topStations.map(s => ` `).join(''); pr.innerHTML = d.topRoutes.map(r => ` `).join(''); } catch (_) { } } btn.onclick = doSearch; qEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') doSearch(); }); window.openTicketDetail = openDetail; const sp = new URLSearchParams(location.search); const qid = sp.get('id'); if (qid) { qEl.value = qid; loadDetail(qid); doSearch(); } else { doSearch(); } loadPopular(); })();