feat(web,installer): 更新下载源、升级资源缓存版本、本地化界面并新增管理功能
- 更新 update_machine.lua 和 installer.lua 中的远程资源下载地址,从旧云存储链接切换为 Gitea 仓库提交镜像地址 - 新增双向闸机专用安装脚本 installer_bi.lua - 为所有网页HTML文件更新静态资源的缓存版本号,避免浏览器加载过期的静态文件缓存 - 修复登录页面的乱码文本,替换为标准简体中文内容,修正ICP备案标识文本 - 新增管理后台概览板块、快捷操作按钮,优化IC卡管理界面与响应式布局样式
This commit is contained in:
+108
-89
@@ -4,10 +4,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FSE閾佽矾鐢靛瓙瀹㈢エ</title>
|
||||
<title>FSE铁路电子客票</title>
|
||||
<link rel="icon" type="image/png" href="/FSE-ticket.png">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="/style.css?v=12">
|
||||
<link rel="stylesheet" href="/style.css?v=13">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
@@ -49,12 +49,12 @@
|
||||
<div class="jr-topbar-inner">
|
||||
<a href="javascript:void(0)" @click="goHome" class="jr-top-link">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<span>杩斿洖鏌ヨ</span>
|
||||
<span>返回查询</span>
|
||||
</a>
|
||||
<div class="jr-top-status is-checking" data-server-status-root>
|
||||
<span class="jr-top-status-label">鏈嶅姟鍣ㄧ姸鎬?/span>
|
||||
<span class="jr-top-status-dot"></span>
|
||||
<span class="jr-top-status-value" data-server-status-value>妫€娴嬩腑</span>
|
||||
<span class="jr-top-status-label">服务器状态</span>
|
||||
<span class="jr-top-status-dot"></span>
|
||||
<span class="jr-top-status-value" data-server-status-value>检测中</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -63,28 +63,28 @@
|
||||
<a href="javascript:void(0)" @click="goHome" class="jr-brand">
|
||||
<img src="/FSE-ticket.png" alt="FSE Railway" class="jr-brand-logo" />
|
||||
<div class="jr-brand-copy">
|
||||
<strong>FSE閾佽矾绁ㄥ姟绯荤粺</strong>
|
||||
<span>鐢靛瓙瀹㈢エ淇℃伅</span>
|
||||
<strong>FSE铁路票务系统</strong>
|
||||
<span>电子客票信息</span>
|
||||
</div>
|
||||
</a>
|
||||
<nav class="jr-nav" aria-label="绔欑偣瀵艰埅">
|
||||
<a href="https://ticket.fse-media.group/home.html" data-link="home">棣栭〉</a>
|
||||
<a href="https://ticket.fse-media.group/order" data-link="order">绾夸笂棰勫畾</a>
|
||||
<a href="https://ticket.fse-media.group/search" data-link="search" class="is-active">杞︾エ鏌ヨ</a>
|
||||
<a href="https://ticket.fse-media.group/ic-card/search" data-link="card-search">IC 鍗℃煡璇?/a>
|
||||
<a href="https://ticket.fse-media.group/ic-card/order" data-link="card-order">绾夸笂璐崱</a>
|
||||
<nav class="jr-nav" aria-label="站点导航">
|
||||
<a href="https://ticket.fse-media.group/home.html" data-link="home">首页</a>
|
||||
<a href="https://ticket.fse-media.group/order" data-link="order">线上预定</a>
|
||||
<a href="https://ticket.fse-media.group/search" data-link="search" class="is-active">车票查询</a>
|
||||
<a href="https://ticket.fse-media.group/ic-card/search" data-link="card-search">IC 卡查询</a>
|
||||
<a href="https://ticket.fse-media.group/ic-card/order" data-link="card-order">线上购卡</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<main class="jr-public-main">
|
||||
<section class="jr-page-intro">
|
||||
<span class="jr-kicker">ELECTRONIC TICKET</span>
|
||||
<h1>鏌ョ湅杞︾エ鐘舵€佷笌鏈€杩戞祦杞褰?/h1>
|
||||
<p>鐢ㄤ簬鏌ョ湅鍗曞紶鐢靛瓙瀹㈢エ鐨勪箻杞︿俊鎭€佺姸鎬佷笌杩涘嚭绔欒褰曪紝渚夸簬鏃呭鍜屽伐浣滀汉鍛樺揩閫熺‘璁ょエ鎹姸鎬併€?/p>
|
||||
<h1>查看车票状态与最近流转记录</h1>
|
||||
<p>用于查看单张电子客票的乘车信息、状态与进出站记录,便于旅客和工作人员快速确认票据状态。</p>
|
||||
</section>
|
||||
<div v-if="loading" class="jr-panel-card">
|
||||
<div class="jr-center-empty">
|
||||
<p>姝e湪璇诲彇绁ㄦ嵁鏁版嵁...</p>
|
||||
<p>正在读取车票数据...</p>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="!loading && hasTicket">
|
||||
@@ -92,8 +92,9 @@
|
||||
<article class="jr-board-card">
|
||||
<div class="jr-panel-headline">
|
||||
<h2 class="mono">{{ ticket.ticket_id }}</h2>
|
||||
<span class="jr-status-pill" :class="statusInfo.class.replace('status-', 'jr-status-')">{{
|
||||
statusInfo.text }}</span>
|
||||
<span class="jr-status-pill" :class="statusInfo.class.replace('status-', 'jr-status-')">
|
||||
{{ statusInfo.text }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="jr-route-board">
|
||||
<div class="jr-station-block">
|
||||
@@ -114,46 +115,44 @@
|
||||
</div>
|
||||
<div class="jr-meta-grid">
|
||||
<div class="jr-meta-item">
|
||||
<span>杞﹀瀷</span>
|
||||
<span>车型</span>
|
||||
<strong>{{ formatTrainType(ticket.overview.train_type) }}</strong>
|
||||
</div>
|
||||
<div class="jr-meta-item">
|
||||
<span>绁ㄤ环</span>
|
||||
<strong>楼 {{ ticket.overview.amount || 0 }}</strong>
|
||||
<span>票价</span>
|
||||
<strong>¥ {{ ticket.overview.amount || 0 }}</strong>
|
||||
</div>
|
||||
<div class="jr-meta-item">
|
||||
<span>涔樻</span>
|
||||
<strong>{{ (ticket.overview.trips_remaining == null ? 1 :
|
||||
ticket.overview.trips_remaining) }} / {{ (ticket.overview.trips_total == null ? 1 :
|
||||
ticket.overview.trips_total) }}</strong>
|
||||
<span>乘次</span>
|
||||
<strong>{{ (ticket.overview.trips_remaining == null ? 1 : ticket.overview.trips_remaining) }} / {{ (ticket.overview.trips_total == null ? 1 : ticket.overview.trips_total) }}</strong>
|
||||
</div>
|
||||
<div class="jr-meta-item">
|
||||
<span>鏇存柊鏃堕棿</span>
|
||||
<span>更新时间</span>
|
||||
<strong>{{ formatTime(ticket.overview.last_update_ts) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<aside class="jr-board-card">
|
||||
<div class="jr-panel-headline">
|
||||
<h3>娴佽浆璁板綍</h3>
|
||||
<h3>流转记录</h3>
|
||||
<span class="jr-panel-note">Recent Events</span>
|
||||
</div>
|
||||
<div class="jr-history-list" v-if="ticket.events && ticket.events.length > 0">
|
||||
<div v-for="ev in ticket.events.slice().reverse().slice(0, 5)"
|
||||
:key="ev.ts || ev.鏃堕棿鎴? class="jr-history-item">
|
||||
:key="ev.ts || ev['时间戳'] || Math.random()"
|
||||
class="jr-history-item">
|
||||
<div class="jr-history-row">
|
||||
<span class="jr-history-title">{{ formatEvent(ev) }}</span>
|
||||
<span class="jr-history-time">{{ formatTime(ev.鏃堕棿鎴?|| ev.ts) }}</span>
|
||||
<span class="jr-history-time">{{ formatTime(ev['时间戳'] || ev.ts) }}</span>
|
||||
</div>
|
||||
<div class="jr-history-desc">
|
||||
<div>{{ formatEventLocation(ev) }}</div>
|
||||
<div v-if="formatEventMeta(ev)" style="margin-top:4px;">{{ formatEventMeta(ev) }}
|
||||
</div>
|
||||
<div v-if="formatEventMeta(ev)" style="margin-top:4px;">{{ formatEventMeta(ev) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="jr-center-empty">
|
||||
<p>鏆傛棤娴佽浆璁板綍銆?/p>
|
||||
<p>暂无流转记录。</p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -161,23 +160,23 @@
|
||||
|
||||
<div v-if="!loading && !hasTicket" class="jr-panel-card">
|
||||
<div class="jr-center-empty">
|
||||
<h2 style="margin:0 0 10px;">鏃犳晥杞︾エ</h2>
|
||||
<p>鏈壘鍒拌杞︾エ鐨勮缁嗕俊鎭€?/p>
|
||||
<h2 style="margin:0 0 10px;">无效车票</h2>
|
||||
<p>未找到该车票的详细信息。</p>
|
||||
<div class="jr-action-row">
|
||||
<button @click="goHome" class="btn primary jr-search-button">杩斿洖鏌ヨ</button>
|
||||
<button @click="goHome" class="btn primary jr-search-button">返回查询</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="site-footer jr-footer-space">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">绮CP澶?025450093鍙?/a>
|
||||
<span class="version">v1.0.12</span>
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">粤ICP备2025450093号</a>
|
||||
<span class="version">v1.0.12</span>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<script src="/custom-dialog.js?v=11"></script>
|
||||
<script src="/custom-dialog.js?v=12"></script>
|
||||
<script src="/public-status.js?v=13"></script>
|
||||
<script src="/ai-assistant.js?v=6"></script>
|
||||
<script>
|
||||
@@ -218,60 +217,74 @@
|
||||
const statusInfo = computed(() => {
|
||||
if (!hasTicket.value) return {};
|
||||
let raw = '';
|
||||
if (ticket.value && ticket.value.overview) {
|
||||
if (ticket.value.overview.status != null) raw = ticket.value.overview.status;
|
||||
if (ticket.value && ticket.value.overview && ticket.value.overview.status != null) {
|
||||
raw = ticket.value.overview.status;
|
||||
}
|
||||
if (!raw && ticket.value) {
|
||||
if (ticket.value.status != null) raw = ticket.value.status;
|
||||
if (!raw && ticket.value && ticket.value.status != null) {
|
||||
raw = ticket.value.status;
|
||||
}
|
||||
const status = String(raw).toLowerCase();
|
||||
|
||||
if (
|
||||
status === '鏈夋晥' ||
|
||||
status === '有效' ||
|
||||
status === 'valid' ||
|
||||
status === 'unused' ||
|
||||
status === 'active' ||
|
||||
status.includes('鏈夋晥') ||
|
||||
status.includes('鏈娇鐢?) ||
|
||||
status.includes('有效') ||
|
||||
status.includes('未使用') ||
|
||||
status.includes('unused')
|
||||
) {
|
||||
return { text: '鏈夋晥', class: 'status-valid' };
|
||||
) {
|
||||
return { text: '有效', class: 'status-valid' };
|
||||
}
|
||||
if (status === '宸蹭娇鐢? || status === 'used' || status.includes('宸蹭娇鐢?) || status.includes('used')) {
|
||||
return { text: '宸蹭娇鐢?, class: 'status-used' };
|
||||
if (
|
||||
status === '已使用' ||
|
||||
status === 'used' ||
|
||||
status.includes('已使用') ||
|
||||
status.includes('used')
|
||||
) {
|
||||
return { text: '已使用', class: 'status-used' };
|
||||
}
|
||||
return { text: '澶辨晥', class: 'status-expired' };
|
||||
return { text: '失效', class: 'status-expired' };
|
||||
});
|
||||
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return '---';
|
||||
let ts = Number(timestamp);
|
||||
if (!Number.isFinite(ts)) return String(timestamp);
|
||||
if (ts > 0 && ts < 1000000000000) ts = ts * 1000;
|
||||
if (ts > 0 && ts < 1000000000000) ts *= 1000;
|
||||
const date = new Date(ts);
|
||||
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit'});
|
||||
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
const formatEvent = (event) => {
|
||||
const type = String(event.type || event.绫诲瀷 || '').toLowerCase();
|
||||
const action = String(event.action || event.鍔ㄤ綔 || '').toLowerCase();
|
||||
const type = String(event.type || event['类型'] || '').toLowerCase();
|
||||
const action = String(event.action || event['动作'] || '').toLowerCase();
|
||||
|
||||
if (type === '鐘舵€? || type === 'status') {
|
||||
const actionMap = { 'entry': '杩涚珯鎴愬姛', 'exit': '鍑虹珯鎴愬姛' };
|
||||
return actionMap[action] || '鐘舵€佸彉鏇?;
|
||||
if (type === '状态' || type === 'status') {
|
||||
const actionMap = { entry: '进站成功', exit: '出站成功' };
|
||||
return actionMap[action] || '状态变更';
|
||||
}
|
||||
|
||||
const typeMap = { 'sale': '鍞エ鎴愬姛', '鍞エ': '鍞エ鎴愬姛', 'entry': '杩涚珯鎴愬姛', 'exit': '鍑虹珯鎴愬姛' };
|
||||
return typeMap[type] || event.type || event.绫诲瀷 || '鐘舵€佸彉鏇?;
|
||||
const typeMap = {
|
||||
sale: '售票成功',
|
||||
售票: '售票成功',
|
||||
entry: '进站成功',
|
||||
exit: '出站成功'
|
||||
};
|
||||
return typeMap[type] || event.type || event['类型'] || '状态变更';
|
||||
};
|
||||
|
||||
const formatEventLocation = (event) => {
|
||||
const type = String(event.type || event.绫诲瀷 || '').toLowerCase();
|
||||
const stationName = event.station_name || event.鍞エ绔?|| event.鍙戠敓绔?|| '';
|
||||
const stationCode = event.station_code || event.绔欑偣缂栧彿 || '';
|
||||
const type = String(event.type || event['类型'] || '').toLowerCase();
|
||||
const stationName = event.station_name || event['售票站'] || event['发生站'] || '';
|
||||
const stationCode = event.station_code || event['站点编号'] || '';
|
||||
|
||||
if (type === 'sale' || type === '鍞エ') {
|
||||
return stationName || '绾夸笂鍞エ';
|
||||
if (type === 'sale' || type === '售票') {
|
||||
return stationName || '线上售票';
|
||||
}
|
||||
|
||||
if (!stationName && !stationCode) return '---';
|
||||
@@ -279,31 +292,31 @@
|
||||
};
|
||||
|
||||
const formatEventMeta = (event) => {
|
||||
const type = String(event.type || event.绫诲瀷 || '').toLowerCase();
|
||||
if (type === 'sale' || type === '鍞エ') {
|
||||
const amount = event.amount ?? event.鍞エ棰?
|
||||
if (amount != null && amount !== '') return `绁ㄤ环锛毬?${amount}`;
|
||||
const type = String(event.type || event['类型'] || '').toLowerCase();
|
||||
if (type === 'sale' || type === '售票') {
|
||||
const amount = event.amount ?? event['售票额'];
|
||||
if (amount != null && amount !== '') return `票价:¥ ${amount}`;
|
||||
}
|
||||
|
||||
const stationEn = event.station_en || event.绔欑偣鑻辨枃 || '';
|
||||
const deviceId = event.device_id || event.璁惧缂栧彿 || '';
|
||||
const stationEn = event.station_en || event['站点英文'] || '';
|
||||
const deviceId = event.device_id || event['设备编号'] || '';
|
||||
if (stationEn && deviceId) return `${stationEn} (${deviceId})`;
|
||||
if (deviceId) return `璁惧锛?{deviceId}`;
|
||||
if (deviceId) return `设备:${deviceId}`;
|
||||
return stationEn;
|
||||
};
|
||||
|
||||
const formatTrainType = (type) => {
|
||||
if (!type) return '鏅€?;
|
||||
const t = type.toLowerCase();
|
||||
if (t === 'local') return '鏅€?;
|
||||
if (t === 'ltd.exp' || t === 'express' || t === 'exp' || t === 'express_train') return '鐗规€?;
|
||||
if (t.includes('鐗规€?)) return '鐗规€?;
|
||||
if (!type) return '普通';
|
||||
const t = String(type).toLowerCase();
|
||||
if (t === 'local') return '普通';
|
||||
if (t === 'ltd.exp' || t === 'express' || t === 'exp' || t === 'express_train') return '特急';
|
||||
if (t.includes('特急')) return '特急';
|
||||
return String(type);
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
ticket.value = null;
|
||||
ticket.value = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/public/tickets/${ticketid}`);
|
||||
@@ -311,22 +324,22 @@
|
||||
ticket.value = null;
|
||||
} else {
|
||||
const data = await response.json();
|
||||
const id = (data && (data.ticket_id || data.杞︾エ缂栧彿 || data.id)) || ticketid;
|
||||
const id = (data && (data.ticket_id || data['车票编号'] || data.id)) || ticketid;
|
||||
let overview = null;
|
||||
if (data) {
|
||||
if (data.overview != null) overview = data.overview;
|
||||
else if (data.姒傝 != null) overview = data.姒傝;
|
||||
else if (data['概览'] != null) overview = data['概览'];
|
||||
else if (data.summary != null) overview = data.summary;
|
||||
}
|
||||
let events = [];
|
||||
if (data) {
|
||||
if (Array.isArray(data.events)) events = data.events;
|
||||
else if (data.浜嬩欢 != null) events = data.浜嬩欢;
|
||||
else if (data['事件'] != null) events = data['事件'];
|
||||
}
|
||||
if (id && overview != null) {
|
||||
const out = {};
|
||||
if (data && typeof data === 'object') {
|
||||
for (const k in data) out[k] = data[k];
|
||||
for (const key in data) out[key] = data[key];
|
||||
}
|
||||
out.ticket_id = id;
|
||||
out.overview = overview;
|
||||
@@ -337,7 +350,7 @@
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('鑾峰彇杞︾エ鏁版嵁澶辫触:', e);
|
||||
console.error('获取车票数据失败:', e);
|
||||
ticket.value = null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -356,17 +369,23 @@
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return {
|
||||
loading, ticket, hasTicket, statusInfo,
|
||||
formatTime, formatEvent, formatEventLocation, formatEventMeta, formatTrainType, goHome
|
||||
return {
|
||||
loading,
|
||||
ticket,
|
||||
hasTicket,
|
||||
statusInfo,
|
||||
formatTime,
|
||||
formatEvent,
|
||||
formatEventLocation,
|
||||
formatEventMeta,
|
||||
formatTrainType,
|
||||
goHome
|
||||
};
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user