fix security and import validation

This commit is contained in:
lizhilun
2026-07-01 16:13:47 +08:00
parent 6467048f38
commit e69930799c
14 changed files with 498 additions and 163 deletions
+42 -7
View File
@@ -62,6 +62,39 @@ function headers() {
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, (ch) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[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>`;