This commit is contained in:
2026-03-21 19:30:13 +08:00
parent 7520b7e2a0
commit 557e25d187
100 changed files with 16990 additions and 18914 deletions

View File

@@ -59,8 +59,8 @@
.brand { font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 10px; }
.brand span { padding: 4px 8px; background: var(--primary); border-radius: 4px; font-size: 14px; }
.nav-top a { padding: 8px 12px; color: #94a3b8; font-size: 13px; border-radius: 4px; }
.nav-top a:hover, .nav-top a.active { color: white; background: rgba(255,255,255,0.1); }
.nav-top button { padding: 8px 12px; color: #94a3b8; font-size: 13px; border-radius: 4px; background: transparent; border: none; cursor: pointer; }
.nav-top button:hover, .nav-top button.active { color: white; background: rgba(255,255,255,0.1); }
.user-area { display: flex; align-items: center; gap: 15px; }
.avatar { width: 32px; height: 32px; background: var(--primary); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 2px solid rgba(255,255,255,0.2); }
@@ -70,7 +70,7 @@
.sidebar { width: var(--sidebar-w); background: var(--bg-sidebar); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding-top: 10px; transition: background 0.3s; }
.menu-group { margin-bottom: 20px; }
.menu-header { font-size: 11px; text-transform: uppercase; color: var(--text-muted); padding: 0 20px; margin-bottom: 8px; font-weight: 700; }
.menu-item { padding: 10px 20px; display: flex; align-items: center; color: var(--text-muted); font-weight: 500; border-left: 3px solid transparent; }
.menu-item { padding: 10px 20px; display: flex; align-items: center; color: var(--text-muted); font-weight: 500; border-left: 3px solid transparent; background: transparent; border: none; width: 100%; cursor: pointer; text-align: left; }
.menu-item:hover { background: var(--bg-body); color: var(--text-main); }
.menu-item.active { background: var(--primary-light); color: var(--primary); border-left-color: var(--primary); }
.badge { margin-left: auto; background: var(--danger); color: white; font-size: 10px; padding: 2px 6px; border-radius: 10px; }
@@ -177,46 +177,46 @@
</svg>
<div class="app-container">
<header class="header">
<header class="header" role="banner">
<div class="brand">
<svg class="icon" style="width:24px; height:24px;"><use xlink:href="#i-check"></use></svg>
<svg class="icon" style="width:24px; height:24px;" aria-hidden="true"><use xlink:href="#i-check"></use></svg>
GlobalGuard <span>Audit V9.4</span>
</div>
<div class="nav-top">
<a onclick="app.nav('dashboard')" class="active">Workspace</a>
<a onclick="app.nav('orders')">Order Queue (200+)</a>
<a onclick="app.nav('settings')">Settings</a>
</div>
<nav class="nav-top" aria-label="Main navigation">
<button onclick="app.nav('dashboard')" class="active" aria-current="page">Workspace</button>
<button onclick="app.nav('orders')">Order Queue (200+)</button>
<button onclick="app.nav('settings')">Settings</button>
</nav>
<div class="user-area">
<div style="text-align: right; font-size:12px;">
<div>Auditor_4421</div>
<div style="color:rgba(255,255,255,0.6)">East China Audit Group</div>
</div>
<div class="avatar">A</div>
<div class="avatar" aria-label="User avatar">A</div>
<button class="btn" style="background:rgba(255,255,255,0.1); padding:5px 10px;" onclick="app.showLogout()">Logout</button>
</div>
</header>
<div class="main-body">
<aside class="sidebar">
<div class="menu-group">
<div class="menu-header">Core Operations</div>
<div class="menu-item active" onclick="app.nav('dashboard')" id="menu-dashboard">
<svg class="icon"><use xlink:href="#i-home"></use></svg> Dashboard Overview
</div>
<div class="menu-item" onclick="app.nav('orders')" id="menu-orders">
<svg class="icon"><use xlink:href="#i-list"></use></svg> Audit Queue
<span class="badge" id="sidebar-count">--</span>
</div>
</div>
<div class="menu-group">
<div class="menu-header">Tools</div>
<div class="menu-item" onclick="app.nav('settings')" id="menu-settings">
<svg class="icon"><use xlink:href="#i-settings"></use></svg> System Settings
</div>
</div>
<aside class="sidebar" role="navigation" aria-label="Sidebar navigation">
<nav class="menu-group">
<h2 class="menu-header">Core Operations</h2>
<button class="menu-item active" onclick="app.nav('dashboard')" id="menu-dashboard" role="menuitem" aria-current="page">
<svg class="icon" aria-hidden="true"><use xlink:href="#i-home"></use></svg> Dashboard Overview
</button>
<button class="menu-item" onclick="app.nav('orders')" id="menu-orders" role="menuitem">
<svg class="icon" aria-hidden="true"><use xlink:href="#i-list"></use></svg> Audit Queue
<span class="badge" id="sidebar-count" aria-label="Pending orders count">--</span>
</button>
</nav>
<nav class="menu-group">
<h2 class="menu-header">Tools</h2>
<button class="menu-item" onclick="app.nav('settings')" id="menu-settings" role="menuitem">
<svg class="icon" aria-hidden="true"><use xlink:href="#i-settings"></use></svg> System Settings
</button>
</nav>
<div style="margin-top:auto; padding:20px;">
<div style="background:var(--bg-body); padding:10px; border-radius:4px; font-size:11px; color:var(--text-muted);">
<div style="background:var(--bg-body); padding:10px; border-radius:4px; font-size:11px; color:var(--text-muted);" role="status" aria-live="polite">
<strong>Server Status:</strong><br>
<span style="color:var(--success)"></span> Live Data Stream<br>
<span style="color:var(--success)"></span> Database (OK)
@@ -229,33 +229,33 @@
<div id="view-dashboard" class="view-panel active">
<h2 style="margin-bottom:20px;">Today's Data Overview (Live)</h2>
<div class="stat-grid">
<div class="stat-box">
<div class="stat-grid" role="region" aria-label="Today's statistics">
<article class="stat-box">
<span class="stat-label">Pending Orders</span>
<span class="stat-num" id="dash-pending">--</span>
<span class="stat-num" id="dash-pending" role="status" aria-live="polite">--</span>
<span class="stat-trend trend-down">Action Required</span>
</div>
<div class="stat-box">
</article>
<article class="stat-box">
<span class="stat-label">High Risk Intercepts</span>
<span class="stat-num" style="color:var(--danger)" id="dash-risk">--</span>
<span class="stat-trend">Ratio <span id="dash-risk-rate">--%</span></span>
</div>
<div class="stat-box">
<span class="stat-num" style="color:var(--danger)" id="dash-risk" role="status" aria-live="polite">--</span>
<span class="stat-trend">Ratio <span id="dash-risk-rate" aria-label="Risk ratio percentage">--%</span></span>
</article>
<article class="stat-box">
<span class="stat-label">Total Amount (CNY)</span>
<span class="stat-num" id="dash-amount">--</span>
<span class="stat-num" id="dash-amount" role="status" aria-live="polite">--</span>
<span class="stat-trend trend-up">vs Yesterday ↑ 23%</span>
</div>
<div class="stat-box">
</article>
<article class="stat-box">
<span class="stat-label">My Performance (Today)</span>
<span class="stat-num" style="color:var(--success)">42</span>
<span class="stat-trend">KPI Achievement 80%</span>
</div>
</article>
</div>
<div class="card">
<section class="card" role="region" aria-labelledby="announcements-title">
<div class="card-header">
<span class="card-title">System Announcements</span>
<span class="tag tag-urgent">New</span>
<h3 id="announcements-title" class="card-title" style="margin:0;">System Announcements</h3>
<span class="tag tag-urgent" aria-label="New announcements">New</span>
</div>
<div style="font-size:13px; color:var(--text-muted); line-height:1.6;">
<p><strong>[Security Alert] Notification on Risk Upgrade for Graphics Card Proxy Buying</strong></p>
@@ -264,20 +264,22 @@
<p><strong>[System Update] V9.4 Patch Notes</strong></p>
<p>Integrated mock data generation engine, demo environment now supports 200+ concurrent order displays.</p>
</div>
</div>
</section>
</div>
<div id="view-orders" class="view-panel">
<h2 style="margin-bottom:20px;">Manual Audit Queue</h2>
<div class="filter-panel">
<fieldset class="filter-panel" aria-label="Order filters">
<legend style="display:none;">Filter orders</legend>
<div class="form-group">
<label>Keywords (Order/User/Product)</label>
<input type="text" class="input-control" id="search-keyword" placeholder="Enter keywords...">
<label for="search-keyword">Keywords (Order/User/Product)</label>
<input type="text" class="input-control" id="search-keyword" placeholder="Enter keywords..." aria-describedby="keyword-help">
<span id="keyword-help" style="display:none;">Search by order ID, user name, or product name</span>
</div>
<div class="form-group">
<label>Risk Level</label>
<select class="input-control" id="search-risk">
<label for="search-risk">Risk Level</label>
<select class="input-control" id="search-risk" aria-label="Filter by risk level">
<option value="all">All Levels</option>
<option value="high">High (High Risk)</option>
<option value="medium">Medium (Medium Risk)</option>
@@ -285,46 +287,46 @@
</select>
</div>
<div class="form-group">
<label>Order Status</label>
<select class="input-control" id="search-status">
<label for="search-status">Order Status</label>
<select class="input-control" id="search-status" aria-label="Filter by order status">
<option value="pending">Pending Audit</option>
<option value="all_history">All History</option>
</select>
</div>
<div style="margin-left:auto; display:flex; gap:10px;">
<button class="btn btn-secondary" onclick="app.resetFilter()">Reset</button>
<button class="btn btn-primary" onclick="app.applyFilter()">
<svg class="icon"><use xlink:href="#i-list"></use></svg> Search
<button class="btn btn-secondary" onclick="app.resetFilter()" aria-label="Reset all filters">Reset</button>
<button class="btn btn-primary" onclick="app.applyFilter()" aria-label="Apply filters and search">
<svg class="icon" aria-hidden="true"><use xlink:href="#i-list"></use></svg> Search
</button>
</div>
</div>
</fieldset>
<div class="order-table-wrapper">
<table class="order-table">
<thead>
<tr>
<th style="width:40px;"><input type="checkbox" id="check-all" onclick="app.toggleSelectAll()"></th>
<th>Order Info</th>
<th>User Profile</th>
<th>Product Overview</th>
<th>Amount</th>
<th>Risk Score</th>
<th>Status</th>
<th style="text-align:right;">Actions</th>
<div class="order-table-wrapper" role="region" aria-label="Orders table">
<table class="order-table" role="table" aria-label="Audit queue orders">
<thead role="rowgroup">
<tr role="row">
<th role="columnheader" style="width:40px;"><input type="checkbox" id="check-all" onclick="app.toggleSelectAll()" aria-label="Select all orders"></th>
<th role="columnheader">Order Info</th>
<th role="columnheader">User Profile</th>
<th role="columnheader">Product Overview</th>
<th role="columnheader">Amount</th>
<th role="columnheader">Risk Score</th>
<th role="columnheader">Status</th>
<th role="columnheader" style="text-align:right;">Actions</th>
</tr>
</thead>
<tbody id="table-body">
<tbody id="table-body" role="rowgroup">
</tbody>
</table>
<div id="empty-state" style="padding:40px; text-align:center; color:var(--text-muted); display:none;">
<div id="empty-state" style="padding:40px; text-align:center; color:var(--text-muted); display:none;" role="status" aria-live="polite">
No orders found matching your criteria
</div>
</div>
<div style="margin-top:15px; display:flex; gap:10px; align-items:center;">
<button class="btn btn-primary" onclick="app.batchAction('approve')">Batch Approve</button>
<button class="btn btn-danger" onclick="app.batchAction('reject')">Batch Reject</button>
<span style="margin-left:auto; font-size:12px; color:var(--text-muted);">
<button class="btn btn-primary" onclick="app.batchAction('approve')" aria-label="Batch approve selected orders">Batch Approve</button>
<button class="btn btn-danger" onclick="app.batchAction('reject')" aria-label="Batch reject selected orders">Batch Reject</button>
<span style="margin-left:auto; font-size:12px; color:var(--text-muted);" role="status" aria-live="polite">
Showing <strong id="current-count">0</strong> results / Total <span id="total-db-count">0</span>
</span>
</div>
@@ -332,15 +334,16 @@
<div id="view-settings" class="view-panel">
<h2>System Preferences</h2>
<div class="card" style="margin-top:20px; max-width:600px;">
<section class="card" style="margin-top:20px; max-width:600px;" role="region" aria-labelledby="settings-title">
<h3 id="settings-title" style="display:none;">Settings</h3>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<div>
<h4 style="margin:0 0 5px 0;">Dark Mode</h4>
<div style="font-size:12px; color:var(--text-muted);">Switch interface to dark theme, suitable for night work.</div>
</div>
<label class="switch">
<input type="checkbox" id="theme-toggle" onchange="app.toggleTheme()">
<span class="slider"></span>
<label class="switch" for="theme-toggle">
<input type="checkbox" id="theme-toggle" onchange="app.toggleTheme()" aria-label="Toggle dark mode">
<span class="slider" aria-hidden="true"></span>
</label>
</div>
<hr style="border:0; border-top:1px solid var(--border); margin:15px 0;">
@@ -349,65 +352,67 @@
<h4 style="margin:0 0 5px 0;">Auto-Refresh List</h4>
<div style="font-size:12px; color:var(--text-muted);">Automatically fetch new orders every 60 seconds.</div>
</div>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
<label class="switch" for="auto-refresh-toggle">
<input type="checkbox" id="auto-refresh-toggle" checked aria-label="Toggle auto-refresh">
<span class="slider" aria-hidden="true"></span>
</label>
</div>
</div>
</section>
</div>
</main>
</div>
<div class="drawer-backdrop" id="drawer-backdrop" onclick="app.closeDrawer()"></div>
<div class="drawer" id="drawer-panel">
<div class="drawer-backdrop" id="drawer-backdrop" onclick="app.closeDrawer()" role="presentation" aria-hidden="true"></div>
<aside class="drawer" id="drawer-panel" role="complementary" aria-label="Order details panel" aria-modal="true">
<div class="drawer-header">
<h3 style="margin:0;">Order Details <span id="d-id" style="font-weight:400; color:var(--text-muted);"></span></h3>
<button class="btn btn-secondary" style="padding:4px 8px;" onclick="app.closeDrawer()">&times;</button>
<button class="btn btn-secondary" style="padding:4px 8px;" onclick="app.closeDrawer()" aria-label="Close order details panel">&times;</button>
</div>
<div class="drawer-body" id="d-content">
<div class="drawer-body" id="d-content" role="region" aria-label="Order information">
</div>
<div class="drawer-footer" id="d-footer">
<button class="btn btn-danger" onclick="app.rejectCurrent()">Reject Order</button>
<button class="btn btn-primary" onclick="app.approveCurrent()">Approve Order</button>
<button class="btn btn-danger" onclick="app.rejectCurrent()" aria-label="Reject this order">Reject Order</button>
<button class="btn btn-primary" onclick="app.approveCurrent()" aria-label="Approve this order">Approve Order</button>
</div>
</div>
</aside>
<div class="modal-overlay" id="modal-reject">
<div class="modal">
<div class="modal-overlay" id="modal-reject" role="presentation" aria-hidden="true">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="reject-title">
<div class="modal-body">
<h3 style="margin-top:0;">Confirm Reject?</h3>
<h3 id="reject-title" style="margin-top:0;">Confirm Reject?</h3>
<p style="color:var(--text-muted); font-size:13px; margin-bottom:15px;">Please select a reason for rejection. The system will notify the user.</p>
<select class="input-control" id="reject-reason" style="width:100%; margin-bottom:10px;">
<label for="reject-reason" style="display:block; margin-bottom:8px; font-weight:600; font-size:13px;">Rejection Reason</label>
<select class="input-control" id="reject-reason" style="width:100%; margin-bottom:10px;" aria-label="Select rejection reason">
<option value="风险拦截-疑似盗刷">Risk Intercept - Suspected Fraud</option>
<option value="信息不全-地址模糊">Incomplete Info - Vague Address</option>
<option value="限购限制-超出数量">Purchase Limit - Exceeded Quantity</option>
<option value="其他原因">Other Reason</option>
</select>
<textarea class="input-control" id="reject-note" style="width:100%; height:80px;" placeholder="Notes (Optional)..."></textarea>
<label for="reject-note" style="display:block; margin-bottom:8px; font-weight:600; font-size:13px;">Additional Notes</label>
<textarea class="input-control" id="reject-note" style="width:100%; height:80px;" placeholder="Notes (Optional)..." aria-label="Additional rejection notes"></textarea>
</div>
<div class="modal-actions">
<button onclick="app.closeModal('modal-reject')">Cancel</button>
<button class="confirm" onclick="app.confirmReject()">Confirm Reject</button>
<button onclick="app.closeModal('modal-reject')" aria-label="Cancel rejection">Cancel</button>
<button class="confirm" onclick="app.confirmReject()" aria-label="Confirm order rejection">Confirm Reject</button>
</div>
</div>
</div>
<div class="modal-overlay" id="modal-logout">
<div class="modal">
<div class="modal-overlay" id="modal-logout" role="presentation" aria-hidden="true">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="logout-title">
<div class="modal-body">
<h3>System Logout</h3>
<h3 id="logout-title">System Logout</h3>
<p style="color:var(--text-muted);">Are you sure you want to log out? Unsaved changes will be lost.</p>
</div>
<div class="modal-actions">
<button onclick="app.closeModal('modal-logout')">Cancel</button>
<button class="confirm" onclick="location.reload()">Confirm Logout</button>
<button onclick="app.closeModal('modal-logout')" aria-label="Cancel logout">Cancel</button>
<button class="confirm" onclick="location.reload()" aria-label="Confirm logout">Confirm Logout</button>
</div>
</div>
</div>
<div class="toast-container" id="toast-root"></div>
<div class="toast-container" id="toast-root" role="region" aria-live="polite" aria-atomic="true" aria-label="Notifications"></div>
</div>
<script>
@@ -506,10 +511,10 @@ const app = {
const menuEl = document.getElementById(menuId);
if(menuEl) menuEl.classList.add('active');
document.querySelectorAll('.nav-top a').forEach(el => el.classList.remove('active'));
if(viewId === 'orders') document.querySelector('.nav-top a:nth-child(2)').classList.add('active');
if(viewId === 'dashboard') document.querySelector('.nav-top a:nth-child(1)').classList.add('active');
if(viewId === 'settings') document.querySelector('.nav-top a:nth-child(3)').classList.add('active');
document.querySelectorAll('.nav-top button').forEach(el => el.classList.remove('active'));
if(viewId === 'orders') document.querySelector('.nav-top button:nth-child(2)').classList.add('active');
if(viewId === 'dashboard') document.querySelector('.nav-top button:nth-child(1)').classList.add('active');
if(viewId === 'settings') document.querySelector('.nav-top button:nth-child(3)').classList.add('active');
},
toggleTheme() {
@@ -596,29 +601,29 @@ const app = {
}).join('');
htmlBuffer += `
<tr>
<td><input type="checkbox" class="row-check" value="${order.id}" onchange="app.toggleSelect('${order.id}')" ${this.data.selectedIds.has(order.id) ? 'checked' : ''} ${order.status !== 'pending' ? 'disabled' : ''}></td>
<td>
<tr role="row">
<td role="cell"><input type="checkbox" class="row-check" value="${order.id}" onchange="app.toggleSelect('${order.id}')" aria-label="Select order ${order.id}" ${this.data.selectedIds.has(order.id) ? 'checked' : ''} ${order.status !== 'pending' ? 'disabled' : ''}></td>
<td role="cell">
<div style="font-weight:600; font-family:'Consolas', monospace;">${order.id}</div>
<div style="font-size:12px; color:var(--text-muted);">${order.date}</div>
<div style="margin-top:4px;">${tagsHtml}</div>
</td>
<td>
<td role="cell">
<div>${order.user}</div>
<div style="font-size:11px; color:var(--text-muted);">Credit Score: ${order.userScore || 'N/A'}</div>
</td>
<td><div title="${itemSummary}">${itemSummary}</div></td>
<td style="font-weight:600; color:var(--text-main);">¥ ${order.total.toLocaleString()}</td>
<td>
<td role="cell"><div title="${itemSummary}">${itemSummary}</div></td>
<td role="cell" style="font-weight:600; color:var(--text-main);">¥ ${order.total.toLocaleString()}</td>
<td role="cell">
<div style="font-weight:bold; color:${riskColor};">${order.score} / 100</div>
<div class="risk-bar-container">
<div class="risk-bar-container" role="progressbar" aria-valuenow="${order.score}" aria-valuemin="0" aria-valuemax="100" aria-label="Risk score progress">
<div class="risk-bar-fill" style="width: ${order.score}%; background: ${riskColor};"></div>
</div>
</td>
<td>${statusBadge}</td>
<td style="text-align:right;">
<button class="btn btn-secondary" style="padding:4px 8px;" onclick="app.openDrawer('${order.id}')">
<svg class="icon" style="margin:0;"><use xlink:href="#i-eye"></use></svg>
<td role="cell">${statusBadge}</td>
<td role="cell" style="text-align:right;">
<button class="btn btn-secondary" style="padding:4px 8px;" onclick="app.openDrawer('${order.id}')" aria-label="View details for order ${order.id}">
<svg class="icon" aria-hidden="true" style="margin:0;"><use xlink:href="#i-eye"></use></svg>
</button>
</td>
</tr>
@@ -650,36 +655,38 @@ const app = {
const content = document.getElementById('d-content');
let itemsHtml = order.items.map(item => `
<div style="display:flex; justify-content:space-between; border-bottom:1px dashed #eee; padding:8px 0;">
<span>${item.name} x ${item.qty}</span>
<span style="font-weight:600;">¥ ${(item.price * item.qty).toLocaleString()}</span>
<div style="display:flex; justify-content:space-between; border-bottom:1px dashed #eee; padding:8px 0;" role="row">
<span role="cell">${item.name} x ${item.qty}</span>
<span role="cell" style="font-weight:600;">¥ ${(item.price * item.qty).toLocaleString()}</span>
</div>
`).join('');
content.innerHTML = `
<div class="detail-section">
<section class="detail-section">
<h4>Risk Control Decision Engine</h4>
<div style="background:${order.risk === 'high' ? '#fff5f5' : '#f0fdf4'}; padding:10px; border-radius:4px; border:1px solid ${order.risk === 'high' ? '#feb2b2' : '#bbf7d0'};">
<div style="background:${order.risk === 'high' ? '#fff5f5' : '#f0fdf4'}; padding:10px; border-radius:4px; border:1px solid ${order.risk === 'high' ? '#feb2b2' : '#bbf7d0'};" role="status" aria-live="polite">
<div style="font-weight:bold; color:${order.risk === 'high' ? '#c53030' : '#22543d'};">
Composite Risk Score: ${order.score} (${order.risk.toUpperCase()})
</div>
<p style="margin:5px 0 0 0; font-size:12px; color:var(--text-muted);">${order.logs}</p>
</div>
</div>
<div class="detail-section">
</section>
<section class="detail-section">
<h4>Shipping Info</h4>
<div class="info-grid">
<span class="info-label">Consignee:</span> <span class="info-val">${order.user}</span>
<span class="info-label">Address:</span> <span class="info-val" style="grid-column:span 2">${order.address}</span>
</div>
</div>
<div class="detail-section">
</section>
<section class="detail-section">
<h4>Item List</h4>
${itemsHtml}
<div role="table" aria-label="Order items">
${itemsHtml}
</div>
<div style="text-align:right; margin-top:10px; font-size:16px; font-weight:700;">
Total: ¥ ${order.total.toLocaleString()}
</div>
</div>
</section>
`;
const footer = document.getElementById('d-footer');
@@ -795,6 +802,9 @@ const app = {
const container = document.getElementById('toast-root');
const el = document.createElement('div');
el.className = `toast ${type}`;
el.setAttribute('role', 'status');
el.setAttribute('aria-live', 'polite');
el.setAttribute('aria-atomic', 'true');
el.innerHTML = `<span>${msg}</span>`;
container.appendChild(el);
setTimeout(() => el.classList.add('show'), 10);