feat(web): 优化票务与IC卡查询页面的功能与UI
- 更新静态资源版本以清理浏览器缓存 - 新增查询概览模块与搜索辅助提示文字 - 添加XSS内容转义防护,优化列表项选中样式 - 重构IC卡查询页面布局,拆分详情与事件记录区域 - 优化移动端响应式展示效果
This commit is contained in:
+34
-6
@@ -4,6 +4,10 @@
|
||||
const detailEl = $('#detail');
|
||||
const qEl = $('#q');
|
||||
const btn = $('#searchBtn');
|
||||
const state = {
|
||||
items: [],
|
||||
selectedId: ''
|
||||
};
|
||||
|
||||
const api = {
|
||||
searchTickets: async (q) => {
|
||||
@@ -52,6 +56,13 @@
|
||||
return type;
|
||||
};
|
||||
|
||||
const escapeHtml = (value) => String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
const getTicketId = (obj) => (obj && (obj.ticket_id || obj["车票编号"] || obj.id)) || '';
|
||||
|
||||
const isValidStatus = (status) => {
|
||||
@@ -111,6 +122,7 @@
|
||||
};
|
||||
|
||||
function renderList(items) {
|
||||
state.items = items;
|
||||
listEl.innerHTML = '';
|
||||
if (items.length === 0) {
|
||||
listEl.innerHTML = '<div class="jr-center-empty"><p>未找到匹配结果。</p></div>';
|
||||
@@ -119,22 +131,35 @@
|
||||
items.forEach(it => {
|
||||
const id = it.ticket_id || it["车票编号"] || '';
|
||||
const row = document.createElement('div');
|
||||
row.className = 'jr-ticket-row';
|
||||
|
||||
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 = `
|
||||
<div class="jr-ticket-row-head">
|
||||
<span class="jr-ticket-id mono">${id}</span>
|
||||
<i class="fas fa-chevron-right text-muted"></i>
|
||||
<span class="jr-ticket-id mono">${escapeHtml(id)}</span>
|
||||
<span class="jr-status-pill ${isValidStatus(statusText) ? 'jr-status-valid' : (statusText === '已使用' ? 'jr-status-used' : 'jr-status-expired')}">${escapeHtml(statusText)}</span>
|
||||
</div>
|
||||
<div class="jr-ticket-route">
|
||||
${startName} → ${terminalName}
|
||||
${escapeHtml(startName)} → ${escapeHtml(terminalName)}
|
||||
</div>
|
||||
<div class="jr-list-meta">最近更新 ${escapeHtml(updateTime)}</div>
|
||||
`;
|
||||
row.onclick = () => loadDetail(id);
|
||||
row.onclick = () => {
|
||||
state.selectedId = id;
|
||||
renderList(state.items);
|
||||
loadDetail(id);
|
||||
};
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
@@ -208,6 +233,8 @@
|
||||
}
|
||||
|
||||
async function loadDetail(id) {
|
||||
state.selectedId = id;
|
||||
renderList(state.items);
|
||||
detailEl.innerHTML = '<div class="jr-center-empty"><p>正在加载详情...</p></div>';
|
||||
try {
|
||||
const d = await api.ticketDetail(id);
|
||||
@@ -229,6 +256,7 @@
|
||||
listEl.innerHTML = '<div class="jr-center-empty"><p>正在搜索...</p></div>';
|
||||
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 = '<div class="jr-center-empty"><p>搜索失败。</p></div>';
|
||||
|
||||
Reference in New Issue
Block a user