feat: 新增客户端问题主动上报与控制台问题汇总功能
1. 客户端 (ticketmachine.lua): 修改 showAlert 支持详细错误并主动上报,使用 pcall 包装主循环捕获 Fatal Error 并上报。 2. 服务端 (data.js, api.js): 增加 issues 数据表,提供 /report_issue 和 /issues 接口。 3. 控制台 (index.html, index.js): 添加“问题汇总”菜单及界面状态管理。
This commit is contained in:
@@ -73,6 +73,9 @@
|
||||
<div class="nav-item" :class="{active: currentView === 'iccards'}" @click="currentView = 'iccards'">
|
||||
<span class="nav-icon"><i class="fas fa-credit-card"></i></span> IC卡管理
|
||||
</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'issues'}" @click="currentView = 'issues'">
|
||||
<span class="nav-icon"><i class="fas fa-exclamation-triangle"></i></span> 问题汇总
|
||||
</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'assets'}" @click="currentView = 'assets'">
|
||||
<span class="nav-icon"><i class="fas fa-route"></i></span> 线路图上传
|
||||
</div>
|
||||
@@ -893,6 +896,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志 -->
|
||||
<div v-if="currentView === 'logs'">
|
||||
<div class="card">
|
||||
<div class="flex between mb-4" style="flex-wrap: wrap; gap: 10px;">
|
||||
@@ -947,6 +951,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 问题汇总 -->
|
||||
<div v-if="currentView === 'issues'">
|
||||
<div class="card">
|
||||
<div class="flex between mb-4" style="flex-wrap: wrap; gap: 10px;">
|
||||
<h4>问题汇总</h4>
|
||||
<div class="flex" style="gap: 8px;">
|
||||
<button @click="fetchIssues" title="刷新"><i class="fas fa-sync-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table v-if="issues && issues.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>设备</th>
|
||||
<th>车站</th>
|
||||
<th>错误信息</th>
|
||||
<th>详细信息</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(issue, idx) in issues" :key="idx">
|
||||
<td style="white-space: nowrap;">{{ formatTime(issue.ts) }}</td>
|
||||
<td class="mono">{{ issue.device || issue.machine_id }}</td>
|
||||
<td>{{ issue.station_code }}</td>
|
||||
<td style="color: var(--danger); font-weight: bold;">{{ issue.error_msg }}</td>
|
||||
<td>
|
||||
<details v-if="issue.detail">
|
||||
<summary class="text-muted" style="cursor: pointer;">查看详情</summary>
|
||||
<pre style="white-space: pre-wrap; margin: 8px 0 0; font-size: 0.85rem; line-height: 1.35; color: #fff;">{{ issue.detail }}</pre>
|
||||
</details>
|
||||
<span v-else class="text-muted">无</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else class="empty-state">
|
||||
<i class="fas fa-check-circle" style="font-size: 48px; margin-bottom: 16px; opacity: 0.3; color: var(--success);"></i>
|
||||
<p>暂无问题上报记录</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="site-footer">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">粤ICP备2025450093号</a>
|
||||
<span class="version">v1.0.12</span>
|
||||
|
||||
+24
-1
@@ -43,6 +43,7 @@ createApp({
|
||||
const lines = ref([]);
|
||||
const fares = ref([]);
|
||||
const tickets = ref([]);
|
||||
const issues = ref([]);
|
||||
const stats = reactive({ sold_tickets: 0, revenue: 0 });
|
||||
const config = reactive({
|
||||
api_base: '',
|
||||
@@ -77,6 +78,7 @@ createApp({
|
||||
let ticketDataLoaded = false;
|
||||
let orderDataLoaded = false;
|
||||
let logDataLoaded = false;
|
||||
let issueDataLoaded = false;
|
||||
let assetsLoaded = false;
|
||||
let fareMapLoaded = false;
|
||||
const loadingState = reactive({
|
||||
@@ -84,7 +86,8 @@ createApp({
|
||||
tickets: false,
|
||||
orders: false,
|
||||
logs: false,
|
||||
iccards: false
|
||||
iccards: false,
|
||||
issues: false
|
||||
});
|
||||
const lastSyncAt = ref(0);
|
||||
|
||||
@@ -861,6 +864,24 @@ createApp({
|
||||
}
|
||||
};
|
||||
|
||||
// --- Issues Management ---
|
||||
const fetchIssues = async () => {
|
||||
if (loadingState.issues) return;
|
||||
loadingState.issues = true;
|
||||
try {
|
||||
const res = await requestJson('/api/issues');
|
||||
if (res && res.ok) {
|
||||
issues.value = res.issues || [];
|
||||
issueDataLoaded = true;
|
||||
markSynced();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
loadingState.issues = false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchIcCards = async (keepSelection = true) => {
|
||||
if (loadingState.iccards) return;
|
||||
loadingState.iccards = true;
|
||||
@@ -1147,6 +1168,7 @@ createApp({
|
||||
if (view === 'tickets' && (force || !ticketDataLoaded)) await fetchTicketData();
|
||||
if (view === 'vouchers' && (force || !orderDataLoaded)) await fetchOrders();
|
||||
if (view === 'logs' && (force || !logDataLoaded)) await fetchLogs();
|
||||
if (view === 'issues' && (force || !issueDataLoaded)) await fetchIssues();
|
||||
if (view === 'iccards') {
|
||||
await fetchIcCards(true);
|
||||
if (icSelectedId.value) {
|
||||
@@ -2013,6 +2035,7 @@ createApp({
|
||||
stations, lines, fares, stats, config, recentLogs, ticketList, ticketStats, orderStats,
|
||||
logs, logCategory, logTypeFilter, logQuery, logMax, logLoading, fetchLogs,
|
||||
orders, orderList, fetchOrders, deleteOrder,
|
||||
issues, fetchIssues,
|
||||
icCards, icCardSearch, icSelectedId, icSelectedCard, icSelectedEvents, icCreateForm, icDetailForm, icCardStats,
|
||||
createIcCard, loadIcCard, saveIcCard, topupIcCard, deleteIcCard, icStatusInfo, icStatusColor, icEventTitle, formatIcEventDetail, cardOrderCode, displayIcCardId, formatMoney,
|
||||
showAddLine, showAddStation, newLine, newStation, fareMapSvg, ticketSearch,
|
||||
|
||||
Reference in New Issue
Block a user