chore(backend,frontend): 移除设备位置管理全链路功能与相关代码

- 删除后端所有机器位置相关的API接口、缓存逻辑与后台同步任务
- 移除前端的机器位置导航入口、地图页面与管理代码
- 将原机器地图页面重构为车票查询页面
- 调整Lua程序的默认版本号
- 删除无用的.gitignore文件
- 优化前端页面的代码格式与导航结构
This commit is contained in:
2026-07-11 16:05:20 +08:00
parent 4fe3a24eaf
commit 1d9f08d5c6
14 changed files with 112 additions and 448 deletions
+89 -106
View File
@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<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=14" />
@@ -38,130 +38,113 @@
<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">车票查询</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>
<a href="https://ticket.fse-media.group/machine-map.html" data-link="machine-map" class="is-active">机器位置</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">MACHINE MAP</span>
<h1>设备分布图</h1>
<p>实时查看所有售票机和检票机的位置及状态。</p>
<span class="jr-kicker">TICKET SEARCH</span>
<h1>按票号、站点或日期快速查询票据</h1>
<p>输入完整票号、起终点、日期或关键词,左侧浏览结果,右侧查看票据详情与最近流转记录。</p>
</section>
<section class="jr-panel-card" style="margin-bottom:24px;">
<div class="jr-panel-headline">
<h2>机器位置</h2>
<span class="jr-panel-note">Live Map</span>
<h2>检索条件</h2>
<span class="jr-panel-note">Ticket ID / Station / Date</span>
</div>
<div id="map-container" style="position: relative; width: 100%; height: 600px; background: #1a1a1a; border-radius: 8px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1);">
<div id="map-loading" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666;">
加载中...
</div>
<div class="jr-search-form">
<input id="q" class="jr-search-input" type="text"
placeholder="输入完整票号 / 起点 / 终点 / 日期 (YYYY-MM-DD)" />
<button id="searchBtn" class="btn primary jr-search-button"><i class="fas fa-search"></i>
立即搜索</button>
</div>
<p class="jr-search-helper">可直接输入完整票号,也可输入起点、终点或日期关键字进行模糊检索。</p>
</section>
<section class="jr-search-results">
<article class="jr-panel-card">
<div class="jr-panel-headline">
<h3>结果列表</h3>
<span class="jr-panel-note">Search Results</span>
</div>
<div id="list" class="jr-scroll-box">
<div class="jr-center-empty">
<p>请输入关键词开始查询。</p>
</div>
</div>
</article>
<section id="detail-section" class="jr-detail-stack">
<article class="jr-panel-card" style="margin-bottom:20px;">
<div class="jr-panel-headline">
<h3>车票详情</h3>
<span class="jr-panel-note">Ticket Overview</span>
</div>
<div id="detail">
<div class="jr-center-empty">
<p>从左侧选择一张车票以查看详情。</p>
</div>
</div>
</article>
<div class="jr-grid-two">
<article class="jr-panel-card">
<div class="jr-panel-headline">
<h3>热门站点</h3>
<span class="jr-panel-note">Popular Stations</span>
</div>
<div id="popularStations" class="jr-popular-list"></div>
</article>
<article class="jr-panel-card">
<div class="jr-panel-headline">
<h3>热门路线</h3>
<span class="jr-panel-note">Popular Routes</span>
</div>
<div id="popularRoutes" class="jr-popular-list"></div>
</article>
</div>
</section>
</section>
<footer class="site-footer jr-footer-space">
<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="/public-status.js"></script>
<script src="/custom-dialog.js?v=12"></script>
<script src="/ticket-search.js?v=12"></script>
<script src="/public-status.js?v=13"></script>
<script>
let machineLocations = {};
let selectedMachineId = null;
function getMapX(x) {
if (typeof x !== 'number') return 50;
let min = 0, max = 0;
const vals = Object.values(machineLocations).map(l => l.x).filter(v => typeof v === 'number');
if (vals.length > 0) {
min = Math.min(...vals) - 50;
max = Math.max(...vals) + 50;
} else {
return 50;
}
if (max === min) return 50;
return ((x - min) / (max - min)) * 90 + 5;
}
function getMapY(z) {
if (typeof z !== 'number') return 50;
let min = 0, max = 0;
const vals = Object.values(machineLocations).map(l => l.z).filter(v => typeof v === 'number');
if (vals.length > 0) {
min = Math.min(...vals) - 50;
max = Math.max(...vals) + 50;
} else {
return 50;
}
if (max === min) return 50;
return ((z - min) / (max - min)) * 90 + 5;
}
function renderMap() {
const container = document.getElementById('map-container');
if (Object.keys(machineLocations).length === 0) {
container.innerHTML = `<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666;">暂无机器坐标数据</div>`;
return;
}
let html = '';
for (const [id, loc] of Object.entries(machineLocations)) {
const x = getMapX(loc.x);
const y = getMapY(loc.z);
const isSelected = selectedMachineId === id;
const isGate = loc.type === 'Gate';
const color = isGate ? '#ff9800' : '#4ade80';
html += `
<div class="map-marker" style="position: absolute; left: ${x}%; top: ${y}%; transform: translate(-50%, -50%); cursor: pointer; z-index: ${isSelected ? 10 : 1};" onclick="selectMachine('${id}')">
<div style="width: 20px; height: 20px; border-radius: 50%; background: ${color}; border: 2px solid #fff; box-shadow: 0 0 10px rgba(0,0,0,0.5);"></div>
`;
if (isSelected) {
html += `
<div class="marker-popup" style="position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%); background: #333; padding: 10px; border-radius: 6px; width: max-content; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 20; color: white; text-align: center;">
<div style="font-weight: bold; margin-bottom: 4px;">${loc.device_id || id}</div>
<div style="font-size: 0.85em; color: #aaa;">类型: ${isGate ? '检票机' : '售票机'}</div>
`;
if (isGate) {
const mode = loc.gate_mode === 'entry' ? '进站' : (loc.gate_mode === 'exit' ? '出站' : '双向');
html += `<div style="font-size: 0.85em; color: #aaa;">模式: ${mode}</div>`;
}
html += `
<div style="font-size: 0.85em; color: #aaa;">站点: ${loc.station || loc.station_code || '未知'}</div>
<div style="font-size: 0.8em; color: #888; margin-top: 4px;">坐标: ${loc.x}, ${loc.y}, ${loc.z}</div>
</div>
`;
}
html += `</div>`;
}
container.innerHTML = html;
}
window.selectMachine = function(id) {
selectedMachineId = id;
renderMap();
};
async function fetchLocations() {
try {
const res = await fetch('/api/public/machine-locations');
const data = await res.json();
if (data && data.ok) {
machineLocations = data.data || {};
renderMap();
}
} catch (e) {
console.error(e);
}
}
document.addEventListener('DOMContentLoaded', () => {
fetchLocations();
setInterval(fetchLocations, 60000);
const isDomain = location.hostname.includes('fse-media.group');
const links = {
home: isDomain ? 'https://ticket.fse-media.group' : '/home.html',
order: isDomain ? 'https://ticket.fse-media.group/order' : '/ticket-order.html',
search: isDomain ? 'https://ticket.fse-media.group/search' : '/ticket-search.html',
'card-search': isDomain ? 'https://ticket.fse-media.group/ic-card/search' : '/ic-card-search.html',
'card-order': isDomain ? 'https://ticket.fse-media.group/ic-card/order' : '/ic-card-order.html'
};
const homeLink = document.getElementById('homeLink');
const brandLink = document.getElementById('brandLink');
if (homeLink) homeLink.href = links.home;
if (brandLink) brandLink.href = links.home;
document.querySelectorAll('[data-link]').forEach((el) => {
const key = el.getAttribute('data-link');
if (links[key]) el.href = links[key];
});
});
</script>
</body>
</html>