新增暗黑风格
This commit is contained in:
1 parent
009c86e089
commit
d06ba0f438
7 files changed
+378
-16
No files matched your search
@@ -24,6 +24,27 @@
|
||||
--transition: 180ms ease;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg-page: #0b1220;
|
||||
--surface: #111b2e;
|
||||
--surface-soft: #0f1728;
|
||||
--surface-hover: #17233a;
|
||||
--text-primary: #e6edf7;
|
||||
--text-secondary: #a5b4cc;
|
||||
--text-muted: #93a4c2;
|
||||
--line: #24324c;
|
||||
--line-strong: #314665;
|
||||
--primary: #78a7d8;
|
||||
--primary-hover: #8ab4de;
|
||||
--primary-active: #6798cb;
|
||||
--danger: #e0828f;
|
||||
--danger-hover: #d46e7c;
|
||||
--success: #6ab79f;
|
||||
--focus: rgba(120, 167, 216, 0.3);
|
||||
--shadow-soft: 0 10px 28px rgba(0, 0, 0, 0.38);
|
||||
--shadow-card: 0 4px 14px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -66,6 +87,10 @@ body.app-page {
|
||||
background: linear-gradient(180deg, #fbfdff 0%, #f5f9ff 100%);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .header {
|
||||
background: linear-gradient(180deg, #121d31 0%, #0f1728 100%);
|
||||
}
|
||||
|
||||
.app-page .header-title h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
@@ -102,6 +127,10 @@ body.app-page {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .header-user {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .header-nav {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -112,6 +141,10 @@ body.app-page {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .header-nav {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .header-nav-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -145,6 +178,11 @@ body.app-page {
|
||||
background: #fef5f6;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .header-nav-link.header-nav-link-logout {
|
||||
background: #321b24;
|
||||
border-color: #5c2d3b;
|
||||
}
|
||||
|
||||
.app-page .header-nav-link.header-nav-link-logout:hover {
|
||||
color: #fff;
|
||||
border-color: var(--danger-hover);
|
||||
@@ -219,6 +257,14 @@ body.app-page {
|
||||
transition: border-color var(--transition), box-shadow var(--transition), background-color var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .form-group input,
|
||||
:root[data-theme="dark"] .app-page .form-group textarea,
|
||||
:root[data-theme="dark"] .app-page .form-control,
|
||||
:root[data-theme="dark"] .app-page .quick-jump input,
|
||||
:root[data-theme="dark"] .app-page .page-size-controls select {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .form-group textarea {
|
||||
min-height: 82px;
|
||||
padding-top: 10px;
|
||||
@@ -281,6 +327,10 @@ body.app-page {
|
||||
transition: border-color var(--transition), box-shadow var(--transition), background-color var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .custom-select-trigger {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .custom-select-trigger::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -327,6 +377,10 @@ body.app-page {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .custom-select-menu {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .custom-select.open .custom-select-menu {
|
||||
display: block;
|
||||
}
|
||||
@@ -368,6 +422,10 @@ body.app-page {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .table-footer {
|
||||
background: #111a2c;
|
||||
}
|
||||
|
||||
/* Shared column semantics: numeric columns stay on one line */
|
||||
.app-page th.num-col,
|
||||
.app-page td.num-col {
|
||||
@@ -412,6 +470,10 @@ body.app-page {
|
||||
transition: background-color var(--transition), border-color var(--transition), color var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .page-nav-text {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .page-nav-text:hover:not(.disabled) {
|
||||
background: var(--surface-hover);
|
||||
border-color: #b5c9de;
|
||||
@@ -438,6 +500,60 @@ body.app-page {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .page-info,
|
||||
:root[data-theme="dark"] .app-page .total-count {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.app-page .theme-toggle {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 9px;
|
||||
background: #fff;
|
||||
color: var(--text-secondary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
|
||||
.app-page .theme-toggle:hover {
|
||||
background: var(--surface-hover);
|
||||
border-color: #b5c9de;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.app-page .theme-toggle:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px var(--focus);
|
||||
}
|
||||
|
||||
.app-page .theme-toggle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.app-page .theme-icon-moon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .theme-toggle {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .theme-icon-sun {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .app-page .theme-icon-moon {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.app-page .quick-jump,
|
||||
.app-page .page-size-controls {
|
||||
display: flex;
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .filters-section {
|
||||
background: #0f1728;
|
||||
}
|
||||
|
||||
.dashboard-page .filters {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
@@ -65,6 +69,26 @@
|
||||
transition: border-color var(--transition), box-shadow var(--transition), background-color var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .filter-group input,
|
||||
:root[data-theme="dark"] .dashboard-page .filter-group select {
|
||||
background: #0d1627;
|
||||
border-color: var(--line-strong);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .filter-group input::placeholder {
|
||||
color: #8ea0be;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .filter-group input[type="date"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .filter-group input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
filter: invert(85%) sepia(7%) saturate(445%) hue-rotate(185deg) brightness(96%) contrast(92%);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.dashboard-page .filter-group input:hover,
|
||||
.dashboard-page .filter-group select:hover {
|
||||
border-color: #b9cbde;
|
||||
@@ -86,6 +110,11 @@
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .stats-container,
|
||||
:root[data-theme="dark"] .dashboard-page .table-container {
|
||||
background: #111b2e;
|
||||
}
|
||||
|
||||
.dashboard-page .stats-table,
|
||||
.dashboard-page table {
|
||||
width: 100%;
|
||||
@@ -109,6 +138,11 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .stats-table th,
|
||||
:root[data-theme="dark"] .dashboard-page th {
|
||||
background: #15233b;
|
||||
}
|
||||
|
||||
.dashboard-page .stats-table td,
|
||||
.dashboard-page td {
|
||||
padding: 13px 14px;
|
||||
@@ -120,22 +154,63 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .stats-table td,
|
||||
:root[data-theme="dark"] .dashboard-page td {
|
||||
background: #111b2e;
|
||||
}
|
||||
|
||||
.dashboard-page .stats-table tbody tr:nth-child(even) td,
|
||||
.dashboard-page tbody tr:nth-child(even) td {
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .stats-table tbody tr:nth-child(even) td,
|
||||
:root[data-theme="dark"] .dashboard-page tbody tr:nth-child(even) td {
|
||||
background: #122038;
|
||||
}
|
||||
|
||||
.dashboard-page .stats-table tbody tr:hover td,
|
||||
.dashboard-page tbody tr:hover td {
|
||||
background: #f2f7fd;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .stats-table tbody tr:hover td,
|
||||
:root[data-theme="dark"] .dashboard-page tbody tr:hover td {
|
||||
background: #1a2b45;
|
||||
}
|
||||
|
||||
.dashboard-page .stats-total-row td {
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
background: #eff5fc;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .stats-total-row td {
|
||||
background: #1a2a44;
|
||||
}
|
||||
|
||||
.dashboard-page .ip-address {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 9px;
|
||||
border: 1px solid #c8d8ea;
|
||||
background: linear-gradient(180deg, #f9fcff 0%, #f1f6fc 100%);
|
||||
color: #22456f;
|
||||
padding: 4px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
font-family: "Cascadia Mono", "Consolas", monospace;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .ip-address {
|
||||
border-color: #2b4469;
|
||||
background: linear-gradient(180deg, #112038 0%, #0d1a30 100%);
|
||||
color: #a7c8f0;
|
||||
}
|
||||
|
||||
.dashboard-page .loading,
|
||||
.dashboard-page .empty-state,
|
||||
.dashboard-page .error {
|
||||
@@ -150,6 +225,11 @@
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .dashboard-page .error {
|
||||
color: #ffb4bf;
|
||||
background: #3a1f2b;
|
||||
}
|
||||
|
||||
.dashboard-page .table-footer {
|
||||
padding: 14px 20px;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,13 @@ body.login-page {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] body.login-page {
|
||||
background:
|
||||
radial-gradient(1100px 620px at -10% -15%, #111d34 0%, transparent 60%),
|
||||
radial-gradient(980px 640px at 100% 0%, #15223b 0%, transparent 62%),
|
||||
var(--bg-page);
|
||||
}
|
||||
|
||||
.login-page .login-shell {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
@@ -23,12 +30,20 @@ body.login-page {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .login-page .login-shell {
|
||||
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.login-page .login-head {
|
||||
padding: 22px 24px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: linear-gradient(180deg, #fbfdff 0%, #f5f9ff 100%);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .login-page .login-head {
|
||||
background: linear-gradient(180deg, #121d31 0%, #0f1728 100%);
|
||||
}
|
||||
|
||||
.login-page .login-head h1 {
|
||||
margin: 0;
|
||||
font-size: 1.38rem;
|
||||
@@ -65,6 +80,10 @@ body.login-page {
|
||||
transition: border-color var(--transition), box-shadow var(--transition), background-color var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .login-page .form-control {
|
||||
background: #0d1627;
|
||||
}
|
||||
|
||||
.login-page .form-control:hover {
|
||||
border-color: #b9cbde;
|
||||
background: #fcfeff;
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .users-section {
|
||||
background: #111b2e;
|
||||
}
|
||||
|
||||
.users-page .users-section h2 {
|
||||
margin: 0 0 14px;
|
||||
font-size: 18px;
|
||||
@@ -95,6 +99,10 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .users-table th {
|
||||
background: #15233b;
|
||||
}
|
||||
|
||||
.users-page .users-table th.sortable-th {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -134,26 +142,46 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .users-table td {
|
||||
background: #111b2e;
|
||||
}
|
||||
|
||||
.users-page .users-table tbody tr:nth-child(even) td {
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .users-table tbody tr:nth-child(even) td {
|
||||
background: #122038;
|
||||
}
|
||||
|
||||
.users-page .users-table tbody tr:hover td {
|
||||
background: #f2f7fd;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .users-table tbody tr:hover td {
|
||||
background: #1a2b45;
|
||||
}
|
||||
|
||||
.users-page .ip-address {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--line);
|
||||
background: #f8fbff;
|
||||
color: var(--text-secondary);
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 9px;
|
||||
border: 1px solid #c8d8ea;
|
||||
background: linear-gradient(180deg, #f9fcff 0%, #f1f6fc 100%);
|
||||
color: #22456f;
|
||||
padding: 4px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
font-family: "Cascadia Mono", "Consolas", monospace;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .ip-address {
|
||||
border-color: #2b4469;
|
||||
background: linear-gradient(180deg, #112038 0%, #0d1a30 100%);
|
||||
color: #a7c8f0;
|
||||
}
|
||||
|
||||
.users-page .action-buttons {
|
||||
@@ -179,6 +207,11 @@
|
||||
border-color: #d6e4f1;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .btn-edit {
|
||||
background: #1a2d49;
|
||||
border-color: #28466d;
|
||||
}
|
||||
|
||||
.users-page .btn-edit:hover {
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
@@ -191,6 +224,11 @@
|
||||
border-color: #f3d7dc;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .btn-delete {
|
||||
background: #3a1f2b;
|
||||
border-color: #5c2d3b;
|
||||
}
|
||||
|
||||
.users-page .btn-delete:hover {
|
||||
color: #fff;
|
||||
background: var(--danger-hover);
|
||||
@@ -221,6 +259,11 @@
|
||||
transition: background-color var(--transition), border-color var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .slider {
|
||||
background: #1a2b45;
|
||||
border-color: #2d4163;
|
||||
}
|
||||
|
||||
.users-page .slider::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -234,6 +277,10 @@
|
||||
transition: transform var(--transition);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .slider::before {
|
||||
background: #e8eef8;
|
||||
}
|
||||
|
||||
.users-page .switch input:checked + .slider {
|
||||
background: #e7f5ef;
|
||||
border-color: #b7dccf;
|
||||
@@ -261,18 +308,35 @@
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .loading,
|
||||
:root[data-theme="dark"] .users-page .empty-state {
|
||||
background: #0f1728;
|
||||
}
|
||||
|
||||
.users-page .error {
|
||||
color: #9b2c2c;
|
||||
border: 1px solid #f1d4d8;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .error {
|
||||
color: #ffb4bf;
|
||||
border-color: #5c2d3b;
|
||||
background: #3a1f2b;
|
||||
}
|
||||
|
||||
.users-page .success {
|
||||
color: #24604d;
|
||||
border: 1px solid #cce4dc;
|
||||
background: #edf9f4;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .users-page .success {
|
||||
background: #193229;
|
||||
border-color: #2f5e4e;
|
||||
color: #a9e2cb;
|
||||
}
|
||||
|
||||
.users-page .table-footer {
|
||||
padding: 14px 4px 0;
|
||||
margin-top: 14px;
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script>
|
||||
(function() {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const theme = savedTheme === 'dark' ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
})();
|
||||
</script>
|
||||
<title>LLM使用统计</title>
|
||||
<link rel="stylesheet" href="/static/vendor/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/admin-base.css">
|
||||
@@ -17,6 +24,15 @@
|
||||
<p>实时监控 API 请求和 Token 消耗</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button type="button" class="theme-toggle" id="themeToggle" aria-label="Toggle theme" title="Toggle theme">
|
||||
<svg class="theme-icon-sun" viewBox="0 0 24 24" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.93 4.93l2.12 2.12M16.95 16.95l2.12 2.12M19.07 4.93l-2.12 2.12M7.05 16.95l-2.12 2.12"></path>
|
||||
</svg>
|
||||
<svg class="theme-icon-moon" viewBox="0 0 24 24" stroke-width="1.8">
|
||||
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 1 0 9.8 9.8z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<nav class="header-nav" aria-label="Main navigation">
|
||||
<a class="header-nav-link active" href="/">统计概览</a>
|
||||
<a class="header-nav-link" href="/users">用户管理</a>
|
||||
@@ -146,6 +162,27 @@
|
||||
activeCustomSelect = null;
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
if (themeToggle) {
|
||||
themeToggle.setAttribute('aria-label', theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme');
|
||||
themeToggle.setAttribute('title', theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme');
|
||||
}
|
||||
}
|
||||
|
||||
function initThemeToggle() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
if (!themeToggle) return;
|
||||
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
|
||||
applyTheme(currentTheme);
|
||||
themeToggle.addEventListener('click', function() {
|
||||
const nextTheme = (document.documentElement.getAttribute('data-theme') || 'light') === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(nextTheme);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeNumber(value) {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -258,8 +295,9 @@
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setupCustomSelect('username');
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initThemeToggle();
|
||||
setupCustomSelect('username');
|
||||
setupCustomSelect('requestModel');
|
||||
setupCustomSelect('pageSizeSelect');
|
||||
setDefaultDateRange();
|
||||
@@ -329,7 +367,7 @@
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td>${stat.ip_address || '-'}</td>
|
||||
<td>${stat.ip_address ? `<span class="ip-address">${stat.ip_address}</span>` : '-'}</td>
|
||||
<td>${stat.username || '-'}</td>
|
||||
<td>${stat.request_model || '-'}</td>
|
||||
<td>${stat.response_model || '-'}</td>
|
||||
@@ -448,7 +486,7 @@
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td>${log.ip_address || '-'}</td>
|
||||
<td>${log.ip_address ? `<span class="ip-address">${log.ip_address}</span>` : '-'}</td>
|
||||
<td>${log.username || '-'}</td>
|
||||
<td>${log.request_model || '-'}</td>
|
||||
<td>${log.response_model || '-'}</td>
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script>
|
||||
(function() {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const theme = savedTheme === 'dark' ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
})();
|
||||
</script>
|
||||
<title>管理员登录 - LLM Proxy</title>
|
||||
<link rel="stylesheet" href="/static/vendor/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/admin-base.css">
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script>
|
||||
(function() {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const theme = savedTheme === 'dark' ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
})();
|
||||
</script>
|
||||
<title>用户管理</title>
|
||||
<link rel="stylesheet" href="/static/vendor/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/admin-base.css">
|
||||
@@ -16,6 +23,15 @@
|
||||
<p>统一管理白名单用户与 IP</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button type="button" class="theme-toggle" id="themeToggle" aria-label="Toggle theme" title="Toggle theme">
|
||||
<svg class="theme-icon-sun" viewBox="0 0 24 24" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.93 4.93l2.12 2.12M16.95 16.95l2.12 2.12M19.07 4.93l-2.12 2.12M7.05 16.95l-2.12 2.12"></path>
|
||||
</svg>
|
||||
<svg class="theme-icon-moon" viewBox="0 0 24 24" stroke-width="1.8">
|
||||
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 1 0 9.8 9.8z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<nav class="header-nav" aria-label="Main navigation">
|
||||
<a class="header-nav-link" href="/">统计概览</a>
|
||||
<a class="header-nav-link active" href="/users">用户管理</a>
|
||||
@@ -124,6 +140,27 @@
|
||||
activeCustomSelect = null;
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
if (themeToggle) {
|
||||
themeToggle.setAttribute('aria-label', theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme');
|
||||
themeToggle.setAttribute('title', theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme');
|
||||
}
|
||||
}
|
||||
|
||||
function initThemeToggle() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
if (!themeToggle) return;
|
||||
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
|
||||
applyTheme(currentTheme);
|
||||
themeToggle.addEventListener('click', function() {
|
||||
const nextTheme = (document.documentElement.getAttribute('data-theme') || 'light') === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(nextTheme);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeNumber(value) {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -236,8 +273,9 @@
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setupCustomSelect('pageSizeSelect');
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initThemeToggle();
|
||||
setupCustomSelect('pageSizeSelect');
|
||||
loadUsers();
|
||||
document.getElementById('pageSizeSelect').value = pageSize;
|
||||
syncCustomSelectValue('pageSizeSelect');
|
||||
|
||||
Reference in new issue
Block a user