fix security and import validation
This commit is contained in:
+42
-7
@@ -62,6 +62,39 @@ function headers() {
|
||||
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '').replace(/[&<>"']/g, (ch) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[ch]));
|
||||
}
|
||||
|
||||
function escapeAttr(value) {
|
||||
return escapeHtml(value);
|
||||
}
|
||||
|
||||
function jsString(value) {
|
||||
return escapeAttr(JSON.stringify(String(value ?? '')));
|
||||
}
|
||||
|
||||
function recordArg(record) {
|
||||
return escapeAttr(JSON.stringify(record));
|
||||
}
|
||||
|
||||
// 统一的 API 请求函数,自动处理 401 跳转到登录页
|
||||
async function apiFetch(url, options = {}) {
|
||||
const res = await fetch(url, { ...options, headers: { ...headers(), ...(options.headers || {}) } });
|
||||
if (res.status === 401) {
|
||||
AppState.clearAuth();
|
||||
showPage('loginPage');
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function formatLocalTime(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
const d = new Date(isoStr);
|
||||
@@ -81,7 +114,7 @@ function formatLocalDate(input) {
|
||||
|
||||
async function loadTotalStats() {
|
||||
try {
|
||||
const res = await fetch(API + '/api/stats/total', { headers: headers() });
|
||||
const res = await apiFetch(API + '/api/stats/total');
|
||||
if (!res.ok) return;
|
||||
const s = await res.json();
|
||||
document.getElementById('totalTakeCount').textContent = s.take_count;
|
||||
@@ -99,10 +132,11 @@ function renderHorizontalChart(containerId, rows, clickHandler, fromPage) {
|
||||
const max = Math.max(...rows.map(r => r.count), 1);
|
||||
container.innerHTML = rows.map((r, i) => {
|
||||
const pct = Math.max((r.count / max) * 100, 2);
|
||||
const onclick = clickHandler ? ` onclick="${clickHandler}('${r.name.replace(/'/g, "\\'")}', '${fromPage}')"` : '';
|
||||
const onclick = clickHandler ? ` onclick="${clickHandler}(${jsString(r.name)}, ${jsString(fromPage)})"` : '';
|
||||
const name = escapeHtml(r.name);
|
||||
return `<div class="hbar-row"${onclick}>
|
||||
<div class="hbar-index ${i < 3 ? 'top' : ''}">${i + 1}</div>
|
||||
<div class="hbar-label" title="${r.name}">${r.name}</div>
|
||||
<div class="hbar-label" title="${escapeAttr(r.name)}">${name}</div>
|
||||
<div class="hbar-track"><div class="hbar-fill" style="width:${pct}%"></div></div>
|
||||
<div class="hbar-value">${r.count}单</div>
|
||||
</div>`;
|
||||
@@ -112,7 +146,7 @@ function renderHorizontalChart(containerId, rows, clickHandler, fromPage) {
|
||||
async function openTakeRanking() {
|
||||
showPage('takeRankingPage');
|
||||
try {
|
||||
const res = await fetch(API + '/api/stats/take-ranking', { headers: headers() });
|
||||
const res = await apiFetch(API + '/api/stats/take-ranking');
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const rows = data.rows || [];
|
||||
@@ -141,7 +175,7 @@ async function openTakeRanking() {
|
||||
async function openDispatchRanking() {
|
||||
showPage('dispatchRankingPage');
|
||||
try {
|
||||
const res = await fetch(API + '/api/stats/dispatch-ranking', { headers: headers() });
|
||||
const res = await apiFetch(API + '/api/stats/dispatch-ranking');
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const rows = data.rows || [];
|
||||
@@ -178,9 +212,10 @@ async function openDispatchRanking() {
|
||||
const container = document.getElementById('dispatchRankingChart');
|
||||
container.innerHTML = rows.map((r, i) => {
|
||||
const pct = Math.max(((r.dispatch_count || 0) / max) * 100, 2);
|
||||
return `<div class="hbar-row" onclick="openPartnerDetail('${r.name.replace(/'/g, "\\'")}', 'dispatchRankingPage')">
|
||||
const name = escapeHtml(r.name);
|
||||
return `<div class="hbar-row" onclick="openPartnerDetail(${jsString(r.name)}, 'dispatchRankingPage')">
|
||||
<div class="hbar-index ${i < 3 ? 'top' : ''}">${i + 1}</div>
|
||||
<div class="hbar-label" title="${r.name}">${r.name}</div>
|
||||
<div class="hbar-label" title="${escapeAttr(r.name)}">${name}</div>
|
||||
<div class="hbar-track"><div class="hbar-fill" style="width:${pct}%"></div></div>
|
||||
<div class="hbar-value">${r.dispatch_count}单</div>
|
||||
</div>`;
|
||||
|
||||
Reference in New Issue
Block a user