refactor: 重构文件存储路径并清理冗余代码

- 将项目文件存储从 public/uploads 迁移至项目根目录专用目录
- 更新所有前端页面的路径提示文本与API请求路径
- 移除冗余的列车详情展开收起功能及相关代码
- 重构服务器端文件路径处理逻辑,增强路径访问安全性
- 清理旧注释和未使用的变量与函数
This commit is contained in:
2026-07-31 19:41:26 +08:00
parent d6e5ce5a93
commit f530d7e045
7 changed files with 79 additions and 269 deletions
+2 -2
View File
@@ -1251,7 +1251,7 @@
async function loadList() {
try {
const res = await fetch('/api/files/content?path=uploads/list.txt');
const res = await fetch('/api/files/content?path=list.txt');
const data = await res.json();
listData = [];
if (data.success && data.content) {
@@ -1418,7 +1418,7 @@
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
path: 'uploads/list.txt',
path: 'list.txt',
content: content + '\n'
})
});
+4 -100
View File
@@ -857,7 +857,6 @@
// --- State ---
let allFiles = [];
let currentPath = 'root';
let expandedTrains = new Set();
let stationCodeMap = {};
let broadcastingStations = new Set();
@@ -951,101 +950,26 @@
if (!state.activeTrains || state.activeTrains.length === 0) list.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-muted);font-size:0.8rem">No active trains</div>';
state.activeTrains.forEach(tr => {
const isExpanded = expandedTrains.has(tr.trainId);
const div = document.createElement('div');
div.className = 'status-item';
let detailsHtml = '';
if (tr.tripLogs && tr.tripLogs.length > 0) {
detailsHtml = tr.tripLogs.map(log => {
const time = new Date(log.time).toLocaleTimeString();
const stCode = stationCodeMap[log.station] || log.station;
return `
<div class="trip-log">
<span>${time}</span>
<span>${stCode}</span>
<span>${log.action}</span>
</div>
`;
}).join('');
} else {
detailsHtml = '<div style="font-style:italic">No trip history</div>';
}
const currentStCode = stationCodeMap[tr.currentStation] || tr.currentStation || '-';
div.innerHTML = `
<div class="status-main" onclick="toggleTrain('${tr.trainId}')">
div.innerHTML = `
<div class="status-main">
<div class="item-info">
<div class="item-title">${tr.trainId}</div>
<div class="item-sub">${currentStCode}</div>
<div class="item-sub">${currentStCode} <span style="margin-left:8px;color:var(--text-muted)">${tr.status || '-'}</span></div>
</div>
<div style="display:flex;align-items:center;gap:10px">
<div class="status-dot ${tr.status === 'At Station' ? 'dot-online' : 'dot-warn'}"></div>
<div style="color:var(--text-muted)">${isExpanded ? ICONS.chevronUp : ICONS.chevronDown}</div>
</div>
</div>
<div class="status-details ${isExpanded ? 'open' : ''}" id="details-${tr.trainId}">
<div style="margin-bottom:8px;font-weight:600">Trip Logs</div>
${detailsHtml}
<div style="margin-top:10px;text-align:right">
<button class="btn-icon" onclick="event.stopPropagation(); deleteTrain('${tr.trainId}')" style="display:inline-flex">${ICONS.trash}</button>
<button class="btn-icon" onclick="deleteTrain('${tr.trainId}')" style="display:inline-flex">${ICONS.trash}</button>
</div>
</div>
`;
list.appendChild(div);
});
// Network Statistics
const statsDiv = document.createElement('div');
statsDiv.className = 'status-item';
const isStatsExpanded = expandedTrains.has('__STATS__');
let statsHtml = '<div style="padding:10px;text-align:center;color:var(--text-muted)">No data</div>';
if (state.travelTimes) {
const keys = Object.keys(state.travelTimes).sort();
if (keys.length > 0) {
statsHtml = keys.map(key => {
const times = state.travelTimes[key];
const [from, to] = key.split('|');
const fromCode = stationCodeMap[from] || from;
const toCode = stationCodeMap[to] || to;
const avg = Math.ceil(times.reduce((a, b) => a + b, 0) / times.length);
return `
<div class="trip-log">
<span>${fromCode} &rarr; ${toCode}</span>
<span style="text-align:right; color:var(--success)">${avg}s</span>
<span style="text-align:right; font-size:0.75em; opacity:0.7">(${times.length})</span>
</div>
`;
}).join('');
}
}
statsDiv.innerHTML = `
<div class="status-main" onclick="toggleTrain('__STATS__')" style="background:var(--bg-body); border-top:1px solid var(--bg-border)">
<div class="item-info">
<div class="item-title" style="font-size:0.85rem; color:var(--text-muted)">NETWORK STATISTICS</div>
</div>
<div style="display:flex;align-items:center;gap:10px">
<button class="btn-icon" onclick="event.stopPropagation(); deleteStats()" style="display:inline-flex" title="Clear Statistics">${ICONS.trash}</button>
<div style="color:var(--text-muted)">${isStatsExpanded ? ICONS.chevronUp : ICONS.chevronDown}</div>
</div>
</div>
<div class="status-details ${isStatsExpanded ? 'open' : ''}" id="details-__STATS__">
<div style="display:grid; grid-template-columns: 1fr 40px 30px; gap:10px; margin-bottom:5px; font-weight:600; font-size:0.75rem; color:var(--text-muted)">
<span>ROUTE</span>
<span style="text-align:right">AVG</span>
<span style="text-align:right">CNT</span>
</div>
${statsHtml}
</div>
`;
list.appendChild(statsDiv);
}
}
/* Removed separate Travel Times renderer */
function showRadioStream() {
document.getElementById('file-manager-view').style.display = 'none';
@@ -1120,18 +1044,6 @@
});
}
function toggleTrain(id) {
if (expandedTrains.has(id)) expandedTrains.delete(id);
else expandedTrains.add(id);
// We need to re-render to update icons/state, but since state updates often,
// just toggling the DOM element is smoother until next update
const details = document.getElementById(`details-${id}`);
if (details) {
details.classList.toggle('open');
// Force re-render on next update will respect the Set
}
}
function addLog(logText) {
const container = document.getElementById('console-logs');
const div = document.createElement('div');
@@ -1334,14 +1246,6 @@
await fetch(`/api/trains/${encodeURIComponent(id)}`, { method: 'DELETE' });
}
async function deleteStats() {
if (!confirm('Are you sure you want to clear all network travel time statistics?')) return;
const res = await fetch('/api/stats', { method: 'DELETE' });
const data = await res.json();
if (data.success) showToast('Success', 'Network statistics cleared');
else showToast('Error', data.message);
}
function triggerUpload() {
if (currentPath === 'config') document.getElementById('upload-config').click();
else if (currentPath === 'general') document.getElementById('upload-general').click();
+2 -48
View File
@@ -184,19 +184,6 @@
background: var(--bg-hover);
}
.train-details {
display: none;
background: rgba(0, 0, 0, 0.2);
padding: 12px 20px;
font-size: 0.8rem;
color: var(--text-muted);
border-top: 1px solid var(--bg-border);
}
.train-details.open {
display: block;
}
.trip-log {
display: grid;
grid-template-columns: 80px 1fr auto;
@@ -380,7 +367,6 @@
};
// --- State ---
let expandedTrains = new Set();
let stationCodeMap = {};
let broadcastingStations = new Set();
let messageLog = [];
@@ -526,27 +512,8 @@
list.innerHTML = '<div style="padding:40px;text-align:center;color:var(--text-muted);font-size:0.8rem">暂无活跃列车</div>';
} else {
state.activeTrains.forEach(tr => {
const isExpanded = expandedTrains.has(tr.trainId);
const div = document.createElement('div');
div.className = 'status-item train-item';
div.onclick = () => toggleTrain(tr.trainId);
let detailsHtml = '';
if (tr.tripLogs && tr.tripLogs.length > 0) {
detailsHtml = tr.tripLogs.slice().reverse().map(log => {
const time = new Date(log.time).toLocaleTimeString();
const stCode = stationCodeMap[log.station] || log.station;
return `
<div class="trip-log">
<span>${time}</span>
<span style="color:var(--text-primary)">${stCode}</span>
<span style="color:${log.action === 'Arrival' ? 'var(--success)' : 'var(--warning)'}">${log.action === 'Arrival' ? '到达' : '出发'}</span>
</div>
`;
}).join('');
} else {
detailsHtml = '<div style="font-style:italic">暂无运行历史</div>';
}
const currentStCode = stationCodeMap[tr.currentStation] || tr.currentStation || '-';
const statusColor = tr.status === 'At Station' ? 'var(--success)' : 'var(--warning)';
@@ -556,13 +523,9 @@
<div style="display:flex; justify-content:space-between; align-items:center;">
<div class="item-info">
<div class="item-title">${tr.trainId} <span style="font-size:0.7rem; color:var(--text-muted); font-weight:normal; margin-left:8px;">${tr.destination || '未知终点'}</span></div>
<div class="item-sub">${currentStCode} <span style="color:${statusColor}; margin-left:8px;">${tr.status === 'At Station' ? '在站' : '运行中'}</span></div>
<div class="item-sub">${currentStCode} <span style="color:${statusColor}; margin-left:8px;">${tr.status === 'At Station' ? '在站' : (tr.status === 'Departed' ? '已发车' : '运行中')}</span></div>
</div>
<div style="color:var(--text-muted)">${isExpanded ? ICONS.chevronUp : ICONS.chevronDown}</div>
</div>
<div class="train-details ${isExpanded ? 'open' : ''}" id="details-${tr.trainId}">
<div style="margin-bottom:8px;font-weight:600;font-size:0.7rem;text-transform:uppercase;color:var(--text-muted)">运行日志 (Trip Logs)</div>
${detailsHtml}
<div class="status-dot ${tr.status === 'At Station' ? 'dot-online' : 'dot-warn'}"></div>
</div>
</div>
`;
@@ -572,15 +535,6 @@
}
}
function toggleTrain(id) {
if (expandedTrains.has(id)) expandedTrains.delete(id);
else expandedTrains.add(id);
const details = document.getElementById(`details-${id}`);
if (details) {
details.classList.toggle('open');
}
}
function updateStationDots() {
const dots = document.querySelectorAll('[id^="station-dot-"]');
dots.forEach(dot => {
+3 -3
View File
@@ -397,7 +397,7 @@
</div>
<div class="panel">
<div class="panel-subtitle">对 uploads/list.txt 进行增删改;每条记录格式:站号==音频文件1,音频文件2,... (支持多个站号用 / 分隔)</div>
<div class="panel-subtitle">对 /opt/CC-StationBroadcast/list.txt 进行增删改;每条记录格式:站号==音频文件1,音频文件2,... (支持多个站号用 / 分隔)</div>
<div class="toolbar">
<button class="btn" onclick="openAddModal()">添加</button>
<div style="display:flex; gap:10px; flex-wrap:wrap;">
@@ -545,7 +545,7 @@
}
async function loadList() {
const res = await fetch('/api/files/content?path=uploads/list.txt');
const res = await fetch('/api/files/content?path=list.txt');
const data = await res.json();
listData = [];
if (data.success && data.content) {
@@ -704,7 +704,7 @@
const res = await fetch('/api/files/content', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: 'uploads/list.txt', content: content + '\n' })
body: JSON.stringify({ path: 'list.txt', content: content + '\n' })
});
const data = await res.json();
if (!data.success) throw new Error(data.message || 'Save failed');
+2 -3
View File
@@ -180,7 +180,7 @@
<span class="badge" id="track_badge">未选择</span>
</div>
<label for="track_select">选择音乐(来自 uploads/audio/general)</label>
<label for="track_select">选择音乐(来自 /opt/CC-StationBroadcast/Audio/general)</label>
<div class="row" style="margin-top:0">
<select id="track_select" onchange="onTrackChange()" style="flex:1; min-width: 200px;"></select>
<button class="btn" onclick="loadTracks()">刷新</button>
@@ -316,7 +316,7 @@
if (tracks.length === 0) {
sel.innerHTML = '<option value="">(无可用音频)</option>';
onTrackChange();
setStatus("没有可用音频(请上传到 uploads/audio/general)");
setStatus("没有可用音频(请上传到 /opt/CC-StationBroadcast/Audio/general)");
return;
}
sel.appendChild(new Option("请选择…", ""));
@@ -433,4 +433,3 @@
</script>
</body>
</html>
+1 -1
View File
@@ -206,7 +206,7 @@
</div>
<div class="hint" id="hint_area">
需要服务端已存在 lsts.wav(建议放在 uploads/audio/general/lsts.wav)。点击“合成”会自动中文 TTS 并拼接生成临时广播,随后可发布到所选站台。
需要服务端已存在 lsts.wav(建议放在 /opt/CC-StationBroadcast/Audio/general/lsts.wav)。点击“合成”会自动中文 TTS 并拼接生成临时广播,随后可发布到所选站台。
</div>
<div id="preview_area" style="margin-top:12px; display:none;">