Beautiful admin panel (#35)

* Fixed some issues

Solve the problem of repeated addition of user objects in db.json after logging into the control panel

* 更新

* 更新

* 修复问题

* Update

* Add missing files
This commit is contained in:
阁主
2025-05-25 21:32:34 +08:00
committed by GitHub
parent 03aca327b0
commit 059821deb7
29 changed files with 4332 additions and 242 deletions

View File

@@ -0,0 +1,376 @@
/* NIKKE 仪表盘专用样式 */
/* 仪表板容器 */
.nikke-dashboard {
padding: var(--spacing-lg);
min-height: calc(100vh - 120px);
}
/* 统计卡片 */
.nikke-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.stats-card {
background-color: var(--nikke-gray-dark);
border-radius: var(--radius-md);
padding: var(--spacing-lg);
position: relative;
overflow: hidden;
box-shadow: var(--shadow-md);
display: flex;
flex-direction: column;
transition: all var(--transition-normal);
border: 1px solid var(--nikke-gray);
}
.stats-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
border-color: var(--nikke-accent);
}
.stats-card .icon {
font-size: 2.5rem;
margin-bottom: var(--spacing-md);
color: var(--nikke-accent);
}
.stats-card .value {
font-size: 2rem;
font-weight: 700;
color: var(--nikke-light);
margin-bottom: 0.5rem;
}
.stats-card .label {
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--nikke-gray-light);
font-weight: normal;
padding: 0;
display: block;
text-align: left;
}
.stats-card .decoration {
position: absolute;
top: 0;
right: 0;
height: 100%;
width: 5px;
background: linear-gradient(to bottom, var(--nikke-accent), transparent);
}
/* 图表卡片 */
.chart-card {
background-color: var(--nikke-gray-dark);
border-radius: var(--radius-md);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-md);
border: 1px solid var(--nikke-gray);
transition: all var(--transition-normal);
}
.chart-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-lg);
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
}
.chart-title {
font-weight: 600;
color: var(--nikke-light);
font-size: 1.2rem;
margin: 0;
}
.chart-body {
position: relative;
width: 100%;
}
.chart-card .btn-outline-light {
color: var(--nikke-gray-light);
border-color: var(--nikke-gray);
background-color: transparent;
font-size: 0.85rem;
padding: 0.25rem 0.75rem;
}
.chart-card .btn-outline-light:hover,
.chart-card .btn-outline-light.active {
color: var(--nikke-light);
background-color: var(--nikke-accent);
border-color: var(--nikke-accent);
}
/* 进度条 */
.nikke-progress {
height: 8px;
background-color: var(--nikke-gray);
border-radius: var(--radius-full);
overflow: hidden;
margin: 0.5rem 0;
}
.nikke-progress-bar {
height: 100%;
background: linear-gradient(to right, var(--nikke-accent-dark), var(--nikke-accent));
border-radius: var(--radius-full);
}
/* 活动列表 */
.activity-list {
background-color: var(--nikke-gray-dark);
border-radius: var(--radius-md);
overflow: hidden;
border: 1px solid var(--nikke-gray);
box-shadow: var(--shadow-md);
height: 100%;
}
.activity-header {
background-color: var(--nikke-primary);
color: var(--nikke-light);
padding: var(--spacing-md) var(--spacing-lg);
font-weight: 600;
font-size: 1.1rem;
border-bottom: 1px solid var(--nikke-primary-dark);
}
.activity-item {
display: flex;
align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid rgba(58, 58, 72, 0.5);
transition: background-color var(--transition-fast);
}
.activity-item:last-child {
border-bottom: none;
}
.activity-item:hover {
background-color: rgba(43, 57, 144, 0.1);
}
.activity-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--nikke-primary);
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-right: var(--spacing-md);
flex-shrink: 0;
}
.activity-content {
flex-grow: 1;
}
.activity-title {
color: var(--nikke-light);
margin-bottom: 5px;
}
.activity-time {
color: var(--nikke-gray-light);
font-size: 0.85rem;
}
/* 用户相关样式 */
.user-item {
display: flex;
align-items: center;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: var(--spacing-md);
border: 2px solid var(--nikke-accent);
background-color: var(--nikke-gray);
}
.user-name {
font-weight: 500;
color: var(--nikke-light);
}
.badge-admin {
display: inline-block;
background-color: var(--nikke-accent);
color: white;
padding: 0.35em 0.65em;
font-size: 0.75em;
font-weight: 700;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: var(--radius-sm);
text-transform: uppercase;
}
/* 通知 */
.nikke-notification {
position: fixed;
bottom: 20px;
right: 20px;
min-width: 300px;
padding: var(--spacing-md) var(--spacing-lg);
background-color: var(--nikke-gray-dark);
color: var(--nikke-light);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
z-index: 1050;
opacity: 0;
transform: translateY(20px);
transition: all var(--transition-normal);
}
.nikke-notification.show {
opacity: 1;
transform: translateY(0);
}
.nikke-notification.info {
border-left: 5px solid var(--nikke-info);
}
.nikke-notification.success {
border-left: 5px solid var(--nikke-success);
}
.nikke-notification.warning {
border-left: 5px solid var(--nikke-warning);
}
.nikke-notification.error {
border-left: 5px solid var(--nikke-danger);
}
.notification-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.notification-title {
font-weight: 600;
}
.notification-close {
background: none;
border: none;
color: var(--nikke-gray-light);
cursor: pointer;
}
.notification-close:hover {
color: var(--nikke-light);
}
.notification-body {
font-size: 0.9rem;
}
/* 仪表盘适应性 */
@media (max-width: 1199.98px) {
.nikke-stats {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 767.98px) {
.nikke-stats {
grid-template-columns: 1fr;
}
.stats-card {
margin-bottom: var(--spacing-md);
}
.nikke-notification {
min-width: calc(100% - 40px);
left: 20px;
right: 20px;
}
}
/* 自定义图表样式 */
canvas {
width: 100% !important;
}
/* 确保图表中的文字使用正确的颜色 */
.chartjs-render-monitor {
color: var(--nikke-light);
}
/* 图表工具提示定制 */
.chartjs-tooltip {
background-color: var(--nikke-gray-dark) !important;
border: 1px solid var(--nikke-gray) !important;
color: var(--nikke-light) !important;
border-radius: var(--radius-sm) !important;
box-shadow: var(--shadow-md) !important;
padding: var(--spacing-sm) var(--spacing-md) !important;
font-family: 'Exo 2', 'Noto Sans SC', sans-serif !important;
font-size: 0.85rem !important;
}
/* 页面加载动画 */
.dashboard-loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(23, 23, 31, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.dashboard-spinner {
width: 50px;
height: 50px;
border: 3px solid var(--nikke-gray);
border-top: 3px solid var(--nikke-accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 仪表盘特殊动画 */
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(232, 62, 140, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(232, 62, 140, 0); }
100% { box-shadow: 0 0 0 0 rgba(232, 62, 140, 0); }
}
.pulse-animation {
animation: pulse 2s infinite;
}

View File

@@ -0,0 +1,251 @@
/* NIKKE 国际化支持样式 */
[data-i18n],
[data-i18n-placeholder],
[data-i18n-title] {
transition: opacity 0.3s ease;
}
.lang-loading [data-i18n],
.lang-loading [data-i18n-placeholder],
.lang-loading [data-i18n-title] {
opacity: 0.5;
}
.language-transition-enter-active,
.language-transition-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.language-transition-enter,
.language-transition-leave-to {
opacity: 0;
transform: translateY(10px);
}
/* 语言切换器样式 - 放在右上角 */
.language-switcher {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
font-family: 'Exo 2', 'Noto Sans SC', sans-serif;
animation: fadeIn 0.5s ease forwards;
opacity: 0;
}
/* 登录页面特殊位置 */
body.login-page .language-switcher {
top: 20px;
right: 20px;
}
/* 仪表盘页面特殊位置 */
body:not(.login-page) .language-switcher {
top: 15px;
right: 170px; /* 预留用户菜单空间 */
}
.language-switcher-toggle {
background-color: rgba(35, 35, 45, 0.9);
color: white;
padding: 6px 12px;
border-radius: 20px;
cursor: pointer;
display: flex;
align-items: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
backdrop-filter: blur(4px);
}
.language-switcher-toggle:hover {
background-color: rgba(43, 57, 144, 0.9);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.language-switcher-toggle i.fa-globe {
margin-right: 8px;
color: var(--nikke-accent-light, #f16eac);
}
.language-switcher-toggle i.fa-chevron-down {
margin-left: 8px;
font-size: 12px;
transition: transform 0.3s ease;
opacity: 0.7;
}
.language-switcher.open .language-switcher-toggle i.fa-chevron-down {
transform: rotate(180deg);
}
.language-options {
position: absolute;
top: 100%;
right: 0;
margin-top: 5px;
background-color: rgba(35, 35, 45, 0.95);
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease, opacity 0.3s ease;
opacity: 0;
backdrop-filter: blur(4px);
}
.language-switcher.open .language-options {
max-height: 200px;
opacity: 1;
}
.language-option {
padding: 8px 15px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: background-color 0.2s ease;
white-space: nowrap;
}
.language-option:hover {
background-color: rgba(43, 57, 144, 0.5);
}
.language-option.active {
background-color: rgba(232, 62, 140, 0.2);
position: relative;
}
.language-option.active:after {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background-color: var(--nikke-accent, #e83e8c);
}
.language-name {
margin-right: 10px;
}
.language-code {
opacity: 0.5;
font-size: 0.8em;
}
/* RTL 支持 (针对阿拉伯语等从右到左的语言) */
[dir="rtl"] .language-switcher-toggle i.fa-globe {
margin-right: 0;
margin-left: 8px;
}
[dir="rtl"] .language-switcher-toggle i.fa-chevron-down {
margin-left: 0;
margin-right: 8px;
}
[dir="rtl"] .language-options {
right: auto;
left: 0;
}
[dir="rtl"] .language-option.active:after {
left: auto;
right: 0;
}
/* 语言特定字体 */
html[lang="zh"] body {
font-family: 'Noto Sans SC', 'Exo 2', sans-serif;
}
html[lang="ja"] body {
font-family: 'Noto Sans JP', 'Exo 2', sans-serif;
}
html[lang="ko"] body {
font-family: 'Noto Sans KR', 'Exo 2', sans-serif;
}
/* 提高可访问性 */
.language-option:focus {
outline: 2px solid var(--nikke-accent, #e83e8c);
outline-offset: -2px;
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 响应式调整 */
@media (max-width: 767.98px) {
.language-switcher-toggle {
padding: 4px 10px;
font-size: 0.85rem;
}
body:not(.login-page) .language-switcher {
top: 60px;
right: 15px;
}
.language-option {
padding: 6px 12px;
font-size: 0.85rem;
}
}
/* 导航栏集成的语言切换器 */
.language-switcher.navbar-integrated {
position: static;
opacity: 1;
}
.language-switcher.navbar-integrated .language-switcher-toggle {
background-color: transparent;
box-shadow: none;
padding: 4px 10px;
border-radius: var(--radius-md);
transition: background-color var(--transition-fast);
}
.language-switcher.navbar-integrated .language-switcher-toggle:hover {
background-color: rgba(43, 57, 144, 0.2);
transform: none;
box-shadow: none;
}
.language-switcher.navbar-integrated .language-options {
position: absolute;
top: 100%;
right: 0;
z-index: 1050;
margin-top: 10px;
}
/* 移动设备上的导航栏集成 */
@media (max-width: 767.98px) {
.language-switcher.navbar-integrated {
margin-bottom: 10px;
}
.language-switcher.navbar-integrated .language-options {
right: 0;
left: auto;
}
}
/* 登录页面的语言切换器 */
body.login-page .language-switcher {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
}

View File

@@ -0,0 +1,229 @@
body.login-page {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: url('/admin/assets/img/nikke-login-bg.jpg') no-repeat center center;
background-size: cover;
font-family: 'Exo 2', 'Noto Sans SC', sans-serif;
}
.login-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(23, 23, 31, 0.85), rgba(43, 57, 144, 0.7));
z-index: 1;
}
.login-container {
position: relative;
z-index: 2;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
padding: 20px;
}
.login-box {
width: 100%;
max-width: 420px;
background-color: rgba(35, 35, 45, 0.8);
backdrop-filter: blur(10px);
border-radius: 10px;
overflow: hidden;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3), 0 0 15px rgba(232, 62, 140, 0.5);
animation: fadeIn 0.8s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.login-header {
padding: 30px 40px 15px;
text-align: center;
background: linear-gradient(to right, var(--nikke-primary-dark), var(--nikke-primary));
position: relative;
overflow: hidden;
}
.login-header::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url('/admin/assets/img/nikke-pattern.png');
background-size: 200px;
opacity: 0.1;
z-index: 0;
}
.login-header .logo {
position: relative;
z-index: 1;
max-width: 120px;
margin-bottom: 15px;
}
.login-header h1 {
position: relative;
z-index: 1;
color: white;
font-size: 24px;
margin-bottom: 5px;
text-transform: uppercase;
letter-spacing: 1px;
}
.login-header p {
position: relative;
z-index: 1;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.login-body {
padding: 30px 40px;
}
.form-floating {
position: relative;
margin-bottom: 25px;
}
.form-floating label {
position: absolute;
top: 0;
left: 15px;
height: 100%;
padding: 1rem 0.75rem;
pointer-events: none;
border: 1px solid transparent;
transform-origin: 0 0;
transition: opacity .1s ease-in-out,transform .1s ease-in-out;
color: var(--nikke-gray-light);
display: flex;
align-items: center;
}
.form-floating .form-control {
height: 58px;
padding: 1.5rem 0.75rem 0.5rem;
color: white;
background-color: var(--nikke-gray);
border: 1px solid var(--nikke-gray-dark);
border-radius: 8px;
}
.form-floating .form-control:focus {
box-shadow: 0 0 0 0.25rem rgba(232, 62, 140, 0.25);
border-color: var(--nikke-accent);
}
.form-floating .form-control:focus ~ label,
.form-floating .form-control:not(:placeholder-shown) ~ label {
opacity: .65;
transform: scale(.85) translateY(-0.5rem) translateX(0.15rem);
}
.login-button {
width: 100%;
padding: 15px;
background: linear-gradient(to right, var(--nikke-accent-dark), var(--nikke-accent));
color: white;
border: none;
border-radius: 8px;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 1px;
margin-top: 10px;
cursor: pointer;
transition: all var(--transition-normal);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
position: relative;
overflow: hidden;
}
.login-button::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transform: translateX(-100%);
}
.login-button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(232, 62, 140, 0.4);
}
.login-button:hover::after {
transform: translateX(100%);
transition: transform 1s;
}
.error-message {
background-color: rgba(244, 66, 131, 0.2);
color: var(--nikke-danger);
padding: 12px;
border-radius: 8px;
font-size: 14px;
text-align: center;
margin: 15px 0;
display: none;
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}
.error-message.visible {
display: block;
}
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
40%, 60% { transform: translate3d(4px, 0, 0); }
}
.login-footer {
text-align: center;
padding: 0 40px 30px;
color: var(--nikke-gray-light);
font-size: 12px;
}
.version {
position: absolute;
bottom: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.3);
font-size: 12px;
z-index: 2;
}
/* 动画光效 */
.nikke-glow {
position: absolute;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(232, 62, 140, 0.4) 0%, rgba(232, 62, 140, 0) 70%);
border-radius: 50%;
pointer-events: none;
z-index: 1;
opacity: 0;
transition: opacity 1s;
}
body.login-page:hover .nikke-glow {
opacity: 1;
}

View File

@@ -0,0 +1,426 @@
:root {
/* NIKKE主题色系 */
--nikke-primary: #2b3990; /* NIKKE蓝色 */
--nikke-primary-light: #4754b9;
--nikke-primary-dark: #1a2370;
--nikke-accent: #e83e8c; /* NIKKE粉色 */
--nikke-accent-light: #f16eac;
--nikke-accent-dark: #c72c70;
/* 中性色 */
--nikke-dark: #17171f;
--nikke-gray-dark: #23232d;
--nikke-gray: #3a3a48;
--nikke-gray-light: #7c7c8a;
--nikke-light: #f8f9fc;
/* 功能色 */
--nikke-success: #32d296;
--nikke-warning: #faa05a;
--nikke-danger: #f44283;
--nikke-info: #3fafec;
/* 尺寸 */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
/* 阴影 */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.15);
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.2);
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.25);
--shadow-neon: 0 0 10px rgba(232, 62, 140, 0.5);
/* 过渡 */
--transition-fast: 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-normal: 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-slow: 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* 圆角 */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
}
/* 全局样式 */
body {
font-family: 'Exo 2', 'Noto Sans SC', sans-serif;
background: linear-gradient(135deg, var(--nikke-dark) 0%, rgba(26, 35, 112, 0.9) 100%);
color: var(--nikke-light);
line-height: 1.6;
position: relative;
}
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('/assets/img/nikke-bg-pattern.png');
background-size: cover;
background-position: center;
opacity: 0.2;
z-index: -1;
pointer-events: none;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
margin-bottom: var(--spacing-md);
color: var(--nikke-light);
text-transform: uppercase;
letter-spacing: 1px;
}
.display-4 {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: var(--spacing-lg);
color: var(--nikke-light);
text-shadow: 0 0 10px rgba(232, 62, 140, 0.5);
}
a {
color: var(--nikke-accent);
text-decoration: none;
transition: all var(--transition-fast);
position: relative;
}
a:hover {
color: var(--nikke-accent-light);
text-shadow: 0 0 8px rgba(232, 62, 140, 0.4);
}
a.nav-link:after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background-color: var(--nikke-accent);
transition: width var(--transition-normal);
}
a.nav-link:hover:after {
width: 100%;
}
/* 导航栏样式 */
.navbar {
background-color: var(--nikke-primary-dark) !important;
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid rgba(232, 62, 140, 0.3) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
}
.navbar-brand {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--nikke-light) !important;
font-size: 1.4rem;
display: flex;
align-items: center;
}
.navbar-brand:before {
content: '';
display: inline-block;
width: 28px;
height: 28px;
margin-right: 10px;
background-image: url('/admin/assets/img/nikke-logo-icon.png');
background-size: contain;
background-repeat: no-repeat;
}
.navbar-light .navbar-nav .nav-link {
color: rgba(255, 255, 255, 0.85) !important;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: var(--radius-sm);
transition: all var(--transition-fast);
}
.navbar-light .navbar-nav .nav-link:hover,
.navbar-light .navbar-nav .nav-link:focus,
.navbar-light .navbar-nav .nav-link.active {
color: var(--nikke-light) !important;
background-color: rgba(232, 62, 140, 0.2);
}
/* 容器样式 */
.container, .container-fluid {
padding-top: var(--spacing-lg);
padding-bottom: var(--spacing-lg);
}
/* 表格样式 */
.table {
background-color: var(--nikke-gray-dark);
border-radius: var(--radius-md);
overflow: hidden;
border: 1px solid var(--nikke-gray);
box-shadow: var(--shadow-md);
color: var(--nikke-light);
}
.table thead th {
background-color: var(--nikke-primary);
color: var(--nikke-light);
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 1px;
border-bottom: none;
padding: 1rem;
}
.table tbody tr:nth-of-type(odd) {
background-color: rgba(43, 57, 144, 0.1);
}
.table tbody tr {
transition: background-color var(--transition-fast);
}
.table tbody tr:hover {
background-color: rgba(232, 62, 140, 0.1);
}
.table td {
padding: 0.85rem 1rem;
vertical-align: middle;
border-top: 1px solid var(--nikke-gray);
}
/* 按钮样式 */
.btn {
border-radius: var(--radius-md);
text-transform: uppercase;
font-weight: 600;
letter-spacing: 1px;
padding: 0.5rem 1.25rem;
transition: all var(--transition-fast);
border: none;
box-shadow: var(--shadow-sm);
}
.btn-primary {
background-color: var(--nikke-primary);
border-color: var(--nikke-primary);
color: var(--nikke-light);
}
.btn-primary:hover {
background-color: var(--nikke-primary-light);
border-color: var(--nikke-primary-light);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(43, 57, 144, 0.4);
}
.btn-primary:focus, .btn-primary:active {
background-color: var(--nikke-primary-dark) !important;
border-color: var(--nikke-primary-dark) !important;
box-shadow: 0 0 0 0.25rem rgba(43, 57, 144, 0.5) !important;
}
.btn-danger {
background-color: var(--nikke-danger);
border-color: var(--nikke-danger);
}
.btn-danger:hover {
background-color: var(--nikke-accent-dark);
border-color: var(--nikke-accent-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(232, 62, 140, 0.4);
}
.btn-outline-primary {
color: var(--nikke-primary);
border-color: var(--nikke-primary);
}
.btn-outline-primary:hover {
background-color: var(--nikke-primary);
color: var(--nikke-light);
transform: translateY(-2px);
}
/* 表单样式 */
.form-control, .form-select {
background-color: var(--nikke-gray-dark);
border: 1px solid var(--nikke-gray);
color: var(--nikke-light);
border-radius: var(--radius-md);
padding: 0.6rem 1rem;
transition: all var(--transition-fast);
}
.form-control:focus, .form-select:focus {
background-color: var(--nikke-gray);
border-color: var(--nikke-accent);
color: var(--nikke-light);
box-shadow: 0 0 0 0.25rem rgba(232, 62, 140, 0.25);
}
.form-label {
color: var(--nikke-light);
font-weight: 500;
margin-bottom: 0.5rem;
}
.form-control::placeholder {
color: var(--nikke-gray-light);
}
/* 卡片样式 */
.card {
background-color: var(--nikke-gray-dark);
border: 1px solid var(--nikke-gray);
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--shadow-md);
transition: all var(--transition-normal);
}
.card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
}
.card-header {
background-color: var(--nikke-primary);
color: var(--nikke-light);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--nikke-primary-dark);
}
.card-body {
padding: 1.5rem;
color: var(--nikke-light);
}
/* 徽章样式 */
.badge {
font-weight: 600;
text-transform: uppercase;
padding: 0.35em 0.65em;
border-radius: var(--radius-sm);
}
.badge-admin {
background-color: var(--nikke-accent);
color: var(--nikke-light);
}
/* 页脚样式 */
.footer {
background-color: var(--nikke-gray-dark);
color: var(--nikke-gray-light);
padding: var(--spacing-md) 0;
border-top: 1px solid var(--nikke-gray);
}
/* 动画和效果 */
@keyframes glow {
0% { box-shadow: 0 0 5px rgba(232, 62, 140, 0.5); }
50% { box-shadow: 0 0 20px rgba(232, 62, 140, 0.8); }
100% { box-shadow: 0 0 5px rgba(232, 62, 140, 0.5); }
}
.glow-effect {
animation: glow 2s infinite;
}
/* 数据表格特殊样式 */
.admin-badge {
background-color: var(--nikke-accent);
color: white;
padding: 0.25em 0.5em;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
/* 自定义组件 */
.stat-card {
background-color: var(--nikke-gray-dark);
border: 1px solid var(--nikke-gray);
border-radius: var(--radius-md);
padding: 1.5rem;
box-shadow: var(--shadow-md);
margin-bottom: var(--spacing-lg);
transition: all var(--transition-normal);
overflow: hidden;
position: relative;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
}
.stat-card::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 100px;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(232, 62, 140, 0.1));
transform: skewX(-25deg) translateX(100%);
transition: transform 1s;
}
.stat-card:hover::after {
transform: skewX(-25deg) translateX(-150%);
}
.stat-card .icon {
font-size: 2.5rem;
margin-bottom: var(--spacing-md);
color: var(--nikke-accent);
}
.stat-card .value {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: var(--nikke-light);
}
.stat-card .label {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--nikke-gray-light);
}
/* 响应式调整 */
@media (max-width: 768px) {
.navbar-brand {
font-size: 1.2rem;
}
.display-4 {
font-size: 2rem;
}
.table {
font-size: 0.85rem;
}
}

View File

@@ -0,0 +1,215 @@
{
"app": {
"name": "NIKKE Admin Console",
"version": "Version v2.5.3",
"footer": "© 2025 - NIKKE: Goddess of Victory - Admin Console"
},
"auth": {
"login": "Login",
"username": "Username",
"password": "Password",
"enter": "Enter Console",
"welcome": "Please login to access admin features",
"verifying": "Verifying...",
"success": "Login successful",
"error": {
"required": "Please enter username and password",
"invalid": "Login failed, please check your credentials",
"network": "Network error, please try again later"
}
},
"common": {
"loading": "Loading...",
"confirm": "Confirm",
"cancel": "Cancel",
"save": "Save",
"delete": "Delete",
"edit": "Edit",
"actions": "Actions",
"search": "Search",
"status": {
"online": "Online",
"offline": "Offline"
},
"notifications": {
"title": "System Notification",
"dashboardRefreshed": "Dashboard data successfully refreshed"
}
},
"nav": {
"dashboard": "Dashboard",
"events": "Events",
"users": "Users",
"mail": "Mail",
"config": "Configuration",
"database": "Database",
"profile": "Profile",
"changePass": "Change Password",
"logout": "Logout"
},
"dashboard": {
"title": "Console Overview",
"refresh": "Refresh Data",
"refreshing": "Refreshing...",
"statsCards": {
"users": "Registered Users",
"server": "Server Status",
"events": "Active Events",
"mail": "Mail Today"
},
"charts": {
"activity": "User Activity Trends",
"server": "Server Resource Usage",
"periods": {
"day": "Day",
"week": "Week",
"month": "Month"
}
},
"serverStats": {
"cpu": "CPU Usage",
"memory": "Memory Usage",
"storage": "Storage Usage"
},
"activity": {
"title": "Recent Activity",
"newUser": "New User Registration",
"loginAlert": "Unusual Login Detected",
"configUpdate": "System Configuration Updated",
"rewards": "Global Rewards Distributed",
"newEvent": "New Event Created",
"bugfix": "Game Bug Fixed",
"timeFormat": {
"min": "minutes ago",
"hour": "hours ago",
"yesterday": "yesterday",
"daysAgo": "days ago"
}
},
"quickAccess": {
"users": {
"title": "User Management",
"desc": "Manage game user accounts, permissions and roles"
},
"events": {
"title": "Event Management",
"desc": "Create, edit and monitor game events"
},
"mail": {
"title": "Mail System",
"desc": "Send system mails and rewards to players"
},
"database": {
"title": "Database",
"desc": "Manage and maintain game database"
},
"enter": "Enter"
}
},
"users": {
"title": "User Management",
"add": "Add New User",
"table": {
"id": "ID",
"username": "Username",
"nickname": "Nickname",
"playerName": "Player Name",
"isAdmin": "Permission",
"isBanned": "Banned",
"actions": "Actions"
},
"search": {
"username": "Username",
"usernamePlaceholder": "Search username...",
"userType": "User Type",
"types": {
"all": "All",
"admin": "Admin",
"user": "Regular User"
},
"sort": "Sort By",
"sortOptions": {
"username": "Username",
"created": "Creation Date"
},
"button": "Search",
"searching": "Searching..."
},
"pagination": {
"prev": "Previous",
"next": "Next"
},
"modal": {
"add": "Add New User",
"close": "Close",
"username": "Username",
"nickname": "Nickname",
"password": "Password",
"isAdmin": "Admin Privileges",
"cancel": "Cancel",
"save": "Save User",
"saving": "Saving..."
},
"modify": {
"title": "Modify User Info",
"subtitle": "User Information",
"isAdmin": "Admin Privileges: ",
"disableGacha": "Disable Gacha System: ",
"disableGachaHint": "Allows all characters to have equal chances of getting pulled",
"isBanned": "Banned:",
"cheats": "Cheat Functions",
"campaign": "Campaign:",
"skipStages": "Skip Stages",
"characters": "Characters:",
"addAllChars": "Add All Characters",
"addChar": "Add Character",
"setCharLevels": "Set Character Levels",
"setSkillLevels": "Set Skill Levels",
"setCoreLevel": "Set Core Level",
"inventory": "Inventory:",
"addAllEquip": "Add All Equipment",
"addItem": "Add Item",
"misc": "Miscellaneous:",
"finishTutorials": "Finish All Tutorials",
"backToList": "Back to List"
},
"password": {
"title": "Change Password",
"changeButton": "Change Password",
"backToList": "Back to List",
"enterNewPassword": "Enter new password",
"hint": "Please enter a strong password with letters, numbers and special characters"
},
"delete": {
"title": "Delete User",
"confirmation": "Are you sure you want to delete this user?",
"backToList": "Back to List"
},
"notifications": {
"searchDone": "Search completed",
"requiredFields": "Please fill in the required fields",
"userAdded": "User added successfully! Please refresh the page to view."
}
},
"events": {
"config": {
"title": "Event Configuration",
"comingSoon": "Coming Soon!"
}
},
"mail": {
"title": "In-game Mail",
"comingSoon": "Coming Soon!"
},
"config": {
"server": {
"title": "Server Configuration",
"logLevel": "Log Level:"
},
"database": {
"title": "Database Configuration",
"reload": "Reload Database",
"reloadHint": "Loads changes from db.json into memory. Discards unsaved changes."
}
}
}

View File

@@ -0,0 +1,215 @@
{
"app": {
"name": "NIKKEアドミンコンソール",
"version": "バージョン v2.5.3",
"footer": "© 2025 - NIKKE: 勝利の女神 - アドミンコンソール"
},
"auth": {
"login": "ログイン",
"username": "ユーザー名",
"password": "パスワード",
"enter": "コンソールに入る",
"welcome": "管理機能にアクセスするにはログインしてください",
"verifying": "確認中...",
"success": "ログイン成功",
"error": {
"required": "ユーザー名とパスワードを入力してください",
"invalid": "ログインに失敗しました。認証情報を確認してください",
"network": "ネットワークエラー。後ほど再試行してください"
}
},
"common": {
"loading": "読み込み中...",
"confirm": "確認",
"cancel": "キャンセル",
"save": "保存",
"delete": "削除",
"edit": "編集",
"actions": "アクション",
"search": "検索",
"status": {
"online": "オンライン",
"offline": "オフライン"
},
"notifications": {
"title": "システム通知",
"dashboardRefreshed": "ダッシュボードデータが正常に更新されました"
}
},
"nav": {
"dashboard": "ダッシュボード",
"events": "イベント",
"users": "ユーザー",
"mail": "メール",
"config": "設定",
"database": "データベース",
"profile": "プロフィール",
"changePass": "パスワード変更",
"logout": "ログアウト"
},
"dashboard": {
"title": "コンソール概要",
"refresh": "データを更新",
"refreshing": "更新中...",
"statsCards": {
"users": "登録ユーザー",
"server": "サーバーステータス",
"events": "アクティブイベント",
"mail": "今日のメール"
},
"charts": {
"activity": "ユーザーアクティビティ傾向",
"server": "サーバーリソース使用状況",
"periods": {
"day": "日",
"week": "週",
"month": "月"
}
},
"serverStats": {
"cpu": "CPU使用率",
"memory": "メモリ使用率",
"storage": "ストレージ使用率"
},
"activity": {
"title": "最近のアクティビティ",
"newUser": "新規ユーザー登録",
"loginAlert": "異常なログインを検出",
"configUpdate": "システム設定が更新されました",
"rewards": "全体報酬を配布しました",
"newEvent": "新しいイベントが作成されました",
"bugfix": "ゲームのバグを修正しました",
"timeFormat": {
"min": "分前",
"hour": "時間前",
"yesterday": "昨日",
"daysAgo": "日前"
}
},
"quickAccess": {
"users": {
"title": "ユーザー管理",
"desc": "ゲームユーザーアカウント、権限、ロールを管理"
},
"events": {
"title": "イベント管理",
"desc": "ゲームイベントの作成、編集、監視"
},
"mail": {
"title": "メールシステム",
"desc": "プレイヤーにシステムメールと報酬を送信"
},
"database": {
"title": "データベース",
"desc": "ゲームデータベースの管理と保守"
},
"enter": "入る"
}
},
"users": {
"title": "ユーザー管理",
"add": "新規ユーザー追加",
"table": {
"id": "ID",
"username": "ユーザー名",
"nickname": "ニックネーム",
"playerName": "プレイヤー名",
"isAdmin": "権限",
"isBanned": "禁止",
"actions": "アクション"
},
"search": {
"username": "ユーザー名",
"usernamePlaceholder": "ユーザー名を検索...",
"userType": "ユーザータイプ",
"types": {
"all": "すべて",
"admin": "管理者",
"user": "一般ユーザー"
},
"sort": "並び替え",
"sortOptions": {
"username": "ユーザー名",
"created": "作成日"
},
"button": "検索",
"searching": "検索中..."
},
"pagination": {
"prev": "前へ",
"next": "次へ"
},
"modal": {
"add": "新規ユーザー追加",
"close": "閉じる",
"username": "ユーザー名",
"nickname": "ニックネーム",
"password": "パスワード",
"isAdmin": "管理者権限",
"cancel": "キャンセル",
"save": "ユーザーを保存",
"saving": "保存中..."
},
"modify": {
"title": "ユーザー情報の変更",
"subtitle": "ユーザー情報",
"isAdmin": "管理者権限: ",
"disableGacha": "ガチャシステムを無効化: ",
"disableGachaHint": "すべてのキャラクターが同じ確率で排出されるようになります",
"isBanned": "禁止:",
"cheats": "チート機能",
"campaign": "キャンペーン:",
"skipStages": "ステージスキップ",
"characters": "キャラクター:",
"addAllChars": "すべてのキャラクターを追加",
"addChar": "キャラクター追加",
"setCharLevels": "キャラクターレベル設定",
"setSkillLevels": "スキルレベル設定",
"setCoreLevel": "コアレベル設定",
"inventory": "インベントリ:",
"addAllEquip": "すべての装備を追加",
"addItem": "アイテム追加",
"misc": "その他:",
"finishTutorials": "すべてのチュートリアルを完了",
"backToList": "一覧に戻る"
},
"password": {
"title": "パスワード変更",
"changeButton": "パスワード変更",
"backToList": "リストに戻る",
"enterNewPassword": "新しいパスワードを入力",
"hint": "文字、数字、特殊文字を含む強力なパスワードを入力してください"
},
"delete": {
"title": "ユーザー削除",
"confirmation": "このユーザーを削除してもよろしいですか?",
"backToList": "一覧に戻る"
},
"notifications": {
"searchDone": "検索完了",
"requiredFields": "必須フィールドに入力してください",
"userAdded": "ユーザーが正常に追加されました!ページを更新して確認してください。"
}
},
"events": {
"config": {
"title": "イベント設定",
"comingSoon": "近日公開!"
}
},
"mail": {
"title": "ゲーム内メール",
"comingSoon": "近日公開!"
},
"config": {
"server": {
"title": "サーバー設定",
"logLevel": "ログレベル:"
},
"database": {
"title": "データベース設定",
"reload": "データベースを再読み込み",
"reloadHint": "db.jsonからの変更をメモリに読み込みます。保存されていない変更は破棄されます。"
}
}
}

View File

@@ -0,0 +1,215 @@
{
"app": {
"name": "NIKKE 관리 콘솔",
"version": "버전 v2.5.3",
"footer": "© 2025 - NIKKE: 승리의 여신 - 관리 콘솔"
},
"auth": {
"login": "로그인",
"username": "사용자 이름",
"password": "비밀번호",
"enter": "콘솔 입장",
"welcome": "관리 기능에 접근하려면 로그인하세요",
"verifying": "확인 중...",
"success": "로그인 성공",
"error": {
"required": "사용자 이름과 비밀번호를 입력하세요",
"invalid": "로그인 실패, 자격 증명을 확인하세요",
"network": "네트워크 오류, 나중에 다시 시도하세요"
}
},
"common": {
"loading": "로딩 중...",
"confirm": "확인",
"cancel": "취소",
"save": "저장",
"delete": "삭제",
"edit": "편집",
"actions": "작업",
"search": "검색",
"status": {
"online": "온라인",
"offline": "오프라인"
},
"notifications": {
"title": "시스템 알림",
"dashboardRefreshed": "대시보드 데이터가 성공적으로 새로고침되었습니다"
}
},
"nav": {
"dashboard": "대시보드",
"events": "이벤트",
"users": "사용자",
"mail": "메일",
"config": "구성",
"database": "데이터베이스",
"profile": "프로필",
"changePass": "비밀번호 변경",
"logout": "로그아웃"
},
"dashboard": {
"title": "콘솔 개요",
"refresh": "데이터 새로고침",
"refreshing": "새로고침 중...",
"statsCards": {
"users": "등록된 사용자",
"server": "서버 상태",
"events": "활성 이벤트",
"mail": "오늘의 메일"
},
"charts": {
"activity": "사용자 활동 추세",
"server": "서버 리소스 사용량",
"periods": {
"day": "일",
"week": "주",
"month": "월"
}
},
"serverStats": {
"cpu": "CPU 사용량",
"memory": "메모리 사용량",
"storage": "저장소 사용량"
},
"activity": {
"title": "최근 활동",
"newUser": "새 사용자 등록",
"loginAlert": "비정상 로그인 감지됨",
"configUpdate": "시스템 구성 업데이트됨",
"rewards": "전체 보상 배포됨",
"newEvent": "새 이벤트 생성됨",
"bugfix": "게임 버그 수정됨",
"timeFormat": {
"min": "분 전",
"hour": "시간 전",
"yesterday": "어제",
"daysAgo": "일 전"
}
},
"quickAccess": {
"users": {
"title": "사용자 관리",
"desc": "게임 사용자 계정, 권한 및 역할 관리"
},
"events": {
"title": "이벤트 관리",
"desc": "게임 이벤트 생성, 편집 및 모니터링"
},
"mail": {
"title": "메일 시스템",
"desc": "플레이어에게 시스템 메일 및 보상 전송"
},
"database": {
"title": "데이터베이스",
"desc": "게임 데이터베이스 관리 및 유지보수"
},
"enter": "입장"
}
},
"users": {
"title": "사용자 관리",
"add": "새 사용자 추가",
"table": {
"id": "ID",
"username": "사용자 이름",
"nickname": "닉네임",
"playerName": "플레이어 이름",
"isAdmin": "권한",
"isBanned": "차단됨",
"actions": "작업"
},
"search": {
"username": "사용자 이름",
"usernamePlaceholder": "사용자 이름 검색...",
"userType": "사용자 유형",
"types": {
"all": "전체",
"admin": "관리자",
"user": "일반 사용자"
},
"sort": "정렬 기준",
"sortOptions": {
"username": "사용자 이름",
"created": "생성일"
},
"button": "검색",
"searching": "검색 중..."
},
"pagination": {
"prev": "이전",
"next": "다음"
},
"modal": {
"add": "새 사용자 추가",
"close": "닫기",
"username": "사용자 이름",
"nickname": "닉네임",
"password": "비밀번호",
"isAdmin": "관리자 권한",
"cancel": "취소",
"save": "사용자 저장",
"saving": "저장 중..."
},
"modify": {
"title": "사용자 정보 수정",
"subtitle": "사용자 정보",
"isAdmin": "관리자 권한: ",
"disableGacha": "가챠 시스템 비활성화: ",
"disableGachaHint": "모든 캐릭터가 동일한 확률로 출현하도록 합니다",
"isBanned": "차단됨:",
"cheats": "치트 기능",
"campaign": "캠페인:",
"skipStages": "스테이지 건너뛰기",
"characters": "캐릭터:",
"addAllChars": "모든 캐릭터 추가",
"addChar": "캐릭터 추가",
"setCharLevels": "캐릭터 레벨 설정",
"setSkillLevels": "스킬 레벨 설정",
"setCoreLevel": "코어 레벨 설정",
"inventory": "인벤토리:",
"addAllEquip": "모든 장비 추가",
"addItem": "아이템 추가",
"misc": "기타:",
"finishTutorials": "모든 튜토리얼 완료",
"backToList": "목록으로 돌아가기"
},
"password": {
"title": "비밀번호 변경",
"changeButton": "비밀번호 변경",
"backToList": "목록으로 돌아가기",
"enterNewPassword": "새 비밀번호 입력",
"hint": "문자, 숫자 및 특수 문자를 포함한 강력한 비밀번호를 입력하세요"
},
"delete": {
"title": "사용자 삭제",
"confirmation": "이 사용자를 삭제하시겠습니까?",
"backToList": "목록으로 돌아가기"
},
"notifications": {
"searchDone": "검색 완료",
"requiredFields": "필수 필드를 작성해주세요",
"userAdded": "사용자가 성공적으로 추가되었습니다! 페이지를 새로고침하여 확인하세요."
}
},
"events": {
"config": {
"title": "이벤트 구성",
"comingSoon": "곧 출시 예정!"
}
},
"mail": {
"title": "게임 내 메일",
"comingSoon": "곧 출시 예정!"
},
"config": {
"server": {
"title": "서버 구성",
"logLevel": "로그 레벨:"
},
"database": {
"title": "데이터베이스 구성",
"reload": "데이터베이스 다시 로드",
"reloadHint": "db.json의 변경사항을 메모리에 로드합니다. 저장되지 않은 변경사항은 버려집니다."
}
}
}

View File

@@ -0,0 +1,215 @@
{
"app": {
"name": "胜利女神控制台",
"version": "版本 v2.5.3",
"footer": "© 2025 - NIKKE: 胜利女神 - 管理控制台"
},
"auth": {
"login": "登录",
"username": "用户名",
"password": "密码",
"enter": "进入控制台",
"welcome": "请登录以访问管理功能",
"verifying": "验证中...",
"success": "登录成功",
"error": {
"required": "请输入用户名和密码",
"invalid": "登录失败,请检查用户名和密码",
"network": "网络错误,请稍后重试"
}
},
"common": {
"loading": "加载中...",
"confirm": "确认",
"cancel": "取消",
"save": "保存",
"delete": "删除",
"edit": "编辑",
"actions": "操作",
"search": "搜索",
"status": {
"online": "在线",
"offline": "离线"
},
"notifications": {
"title": "系统通知",
"dashboardRefreshed": "仪表盘数据已成功刷新"
}
},
"nav": {
"dashboard": "仪表盘",
"events": "活动",
"users": "用户",
"mail": "邮件",
"config": "配置",
"database": "数据库",
"profile": "个人信息",
"changePass": "修改密码",
"logout": "退出登录"
},
"dashboard": {
"title": "控制台概览",
"refresh": "刷新数据",
"refreshing": "刷新中...",
"statsCards": {
"users": "注册用户",
"server": "服务器状态",
"events": "活动进行中",
"mail": "今日邮件"
},
"charts": {
"activity": "用户活跃度趋势",
"server": "服务器资源使用",
"periods": {
"day": "日",
"week": "周",
"month": "月"
}
},
"serverStats": {
"cpu": "CPU 使用率",
"memory": "内存使用率",
"storage": "存储使用率"
},
"activity": {
"title": "最近活动",
"newUser": "新用户注册",
"loginAlert": "检测到异常登录",
"configUpdate": "系统配置已更新",
"rewards": "发放全服奖励",
"newEvent": "新活动已创建",
"bugfix": "修复游戏漏洞",
"timeFormat": {
"min": "分钟前",
"hour": "小时前",
"yesterday": "昨天",
"daysAgo": "前天"
}
},
"quickAccess": {
"users": {
"title": "用户管理",
"desc": "管理游戏用户账号,权限和角色"
},
"events": {
"title": "活动管理",
"desc": "创建、编辑和监控游戏活动"
},
"mail": {
"title": "邮件系统",
"desc": "发送系统邮件和奖励给玩家"
},
"database": {
"title": "数据库",
"desc": "管理和维护游戏数据库"
},
"enter": "进入"
}
},
"users": {
"title": "用户管理",
"add": "添加新用户",
"table": {
"id": "ID",
"username": "用户名",
"nickname": "昵称",
"playerName": "玩家名称",
"isAdmin": "权限",
"isBanned": "禁止登录",
"actions": "操作"
},
"search": {
"username": "用户名",
"usernamePlaceholder": "搜索用户名...",
"userType": "用户类型",
"types": {
"all": "全部",
"admin": "管理员",
"user": "普通用户"
},
"sort": "排序方式",
"sortOptions": {
"username": "用户名",
"created": "创建时间"
},
"button": "搜索",
"searching": "搜索中..."
},
"pagination": {
"prev": "上一页",
"next": "下一页"
},
"modal": {
"add": "添加新用户",
"close": "关闭",
"username": "用户名",
"nickname": "昵称",
"password": "密码",
"isAdmin": "管理员权限",
"cancel": "取消",
"save": "保存用户",
"saving": "保存中..."
},
"modify": {
"title": "修改用户信息",
"subtitle": "用户信息",
"isAdmin": "管理员权限: ",
"disableGacha": "禁用抽卡系统: ",
"disableGachaHint": "允许所有角色有相同概率被抽到",
"isBanned": "禁止登录:",
"cheats": "作弊功能",
"campaign": "战役:",
"skipStages": "跳过关卡",
"characters": "角色:",
"addAllChars": "添加所有角色",
"addChar": "添加角色",
"setCharLevels": "设置角色等级",
"setSkillLevels": "设置技能等级",
"setCoreLevel": "设置核心等级",
"inventory": "物品:",
"addAllEquip": "添加所有装备",
"addItem": "添加物品",
"misc": "其他:",
"finishTutorials": "完成所有教程",
"backToList": "返回列表"
},
"password": {
"title": "修改密码",
"changeButton": "修改密码",
"backToList": "返回列表",
"enterNewPassword": "输入新密码",
"hint": "请输入强密码,包含字母、数字和特殊字符"
},
"delete": {
"title": "删除用户",
"confirmation": "您确定要删除这个用户吗?",
"backToList": "返回列表"
},
"notifications": {
"searchDone": "搜索完成",
"requiredFields": "请填写必填字段",
"userAdded": "用户添加成功!请刷新页面查看。"
}
},
"events": {
"config": {
"title": "活动配置",
"comingSoon": "即将上线!"
}
},
"mail": {
"title": "游戏内邮件",
"comingSoon": "即将上线!"
},
"config": {
"server": {
"title": "服务器配置",
"logLevel": "日志级别:"
},
"database": {
"title": "数据库配置",
"reload": "重新加载数据库",
"reloadHint": "从db.json加载更改到内存中。未保存的更改将丢失。"
}
}
}

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

View File

@@ -0,0 +1,357 @@
/**
* NIKKE控制台国际化支持 - 同步版
*/
class NikkeI18n {
constructor() {
this.supportedLanguages = ['zh', 'en', 'ja', 'ko'];
this.languageNames = {'zh': '简体中文', 'en': 'English', 'ja': '日本語', 'ko': '한국어'};
this.currentLang = localStorage.getItem('nikke_lang') || this.detectBrowserLanguage() || 'zh';
this.resources = {
};
this.isApplyingLanguage = false;
this.isRenderingSwitcher = false;
this.observer = null;
// 立即同步加载所有语言文件
this.loadAllLanguages();
this.init();
}
init() {
try {
document.documentElement.classList.add('lang-loading');
this.applyLanguage();
this.registerEventListeners();
this.renderLanguageSwitcher();
window.dispatchEvent(new CustomEvent('nikke:i18n-ready'));
} catch (error) {
console.error('初始化失败:', error);
this.fallbackToDefaultLanguage();
} finally {
document.documentElement.classList.remove('lang-loading');
}
}
detectBrowserLanguage() {
try {
const browserLang = navigator.language || navigator.userLanguage;
if (!browserLang) return 'en';
const baseLang = browserLang.split('-')[0];
return this.supportedLanguages.includes(baseLang) ? baseLang : 'en';
} catch {
return 'en';
}
}
fallbackToDefaultLanguage() {
this.currentLang = 'en';
try {
localStorage.setItem('nikke_lang', this.currentLang);
} catch (e) {}
this.applyLanguage();
}
loadAllLanguages() {
// 同步预加载所有语言文件
for (const lang of this.supportedLanguages) {
if (this.resources[lang]) continue;
try {
// 使用同步XMLHttpRequest加载语言文件
const xhr = new XMLHttpRequest();
xhr.open('GET', `/admin/assets/i18n/${lang}.json`, false); // false表示同步请求
xhr.send();
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
if (data && typeof data === 'object') {
this.resources[lang] = data;
} else {
throw new Error('格式无效');
}
} else {
throw new Error(`加载失败: ${lang} (${xhr.status})`);
}
} catch (error) {
console.error(`加载语言文件失败: ${lang}`, error);
}
}
// 确保当前语言已加载
if (!this.resources[this.currentLang]) {
// 当前语言未加载成功,回退到英文
console.warn(`当前语言 ${this.currentLang} 加载失败,回退到英文`);
this.currentLang = 'en';
try {
localStorage.setItem('nikke_lang', 'en');
} catch (e) {}
}
}
switchLanguage(lang) {
if (!this.supportedLanguages.includes(lang)) return;
try {
document.documentElement.classList.add('lang-loading');
// 确保语言资源已加载
if (!this.resources[lang]) {
try {
// 尝试同步加载缺失的语言资源
const xhr = new XMLHttpRequest();
xhr.open('GET', `/admin/assets/i18n/${lang}.json`, false);
xhr.send();
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
if (data && typeof data === 'object') {
this.resources[lang] = data;
} else {
throw new Error('格式无效');
}
} else {
throw new Error(`加载失败: ${lang} (${xhr.status})`);
}
} catch (error) {
console.error(`切换到语言 ${lang} 时加载失败`, error);
return;
}
}
this.currentLang = lang;
localStorage.setItem('nikke_lang', lang);
this.applyLanguage();
this.updateLanguageSwitcher();
window.dispatchEvent(new CustomEvent('nikke:languageChanged', {detail: {lang}}));
} catch (error) {
console.error(`切换失败: ${lang}`, error);
} finally {
document.documentElement.classList.remove('lang-loading');
}
}
t(key, params = {}) {
try {
if (!this.resources[this.currentLang]) return key;
const keys = key.split('.');
let value = this.resources[this.currentLang];
for (const k of keys) {
value = value?.[k];
if (value === undefined) {
if (this.resources['en']) {
let enValue = this.resources['en'];
for (const k of keys) {
enValue = enValue?.[k];
if (enValue === undefined) return key;
}
return typeof enValue === 'string' ? this.replaceParams(enValue, params) : key;
}
return key;
}
}
return typeof value === 'string' ? this.replaceParams(value, params) : key;
} catch {
return key;
}
}
replaceParams(text, params) {
if (!text) return '';
return text.replace(/\{\{([^}]+)}}/g, (_, key) =>
params[key.trim()] !== undefined ? params[key.trim()] : `{{${key}}}`
);
}
applyLanguage() {
if (this.isApplyingLanguage) return;
this.isApplyingLanguage = true;
try {
if (this.observer) this.observer.disconnect();
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
element.textContent = this.t(key);
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
const key = element.getAttribute('data-i18n-placeholder');
element.placeholder = this.t(key);
});
document.querySelectorAll('[data-i18n-title]').forEach(element => {
const key = element.getAttribute('data-i18n-title');
element.title = this.t(key);
});
document.documentElement.lang = this.currentLang;
} catch (error) {
console.error('应用语言失败:', error);
} finally {
if (this.observer) {
const mainContent = document.querySelector('.login-container') || document.body;
this.observer.observe(mainContent, {childList: true, subtree: true});
}
this.isApplyingLanguage = false;
}
}
registerEventListeners() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
this.observer = new MutationObserver(mutations => {
if (this.isApplyingLanguage || this.isRenderingSwitcher) return;
const hasRelevantChanges = mutations.some(mutation => {
if (mutation.type !== 'childList' || !mutation.addedNodes.length) return false;
return Array.from(mutation.addedNodes).some(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return false;
if (node.classList && (
node.classList.contains('language-switcher') ||
node.id === 'nikke-i18n-styles'
)) return false;
return (
(node.hasAttribute && (
node.hasAttribute('data-i18n') ||
node.hasAttribute('data-i18n-placeholder') ||
node.hasAttribute('data-i18n-title')
)) ||
(node.querySelector && node.querySelector('[data-i18n], [data-i18n-placeholder], [data-i18n-title]'))
);
});
});
if (hasRelevantChanges && !this.isApplyingLanguage) {
this.applyLanguage();
}
});
const mainContent = document.querySelector('.login-container, .nikke-dashboard') || document.body;
this.observer.observe(mainContent, {childList: true, subtree: true});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.applyLanguage());
} else {
this.applyLanguage();
}
document.addEventListener('visibilitychange', () => {
if (!document.hidden) this.applyLanguage();
});
}
renderLanguageSwitcher() {
if (this.isRenderingSwitcher) return;
this.isRenderingSwitcher = true;
try {
if (document.querySelector('.language-switcher')) {
this.isRenderingSwitcher = false;
return;
}
if (this.observer) this.observer.disconnect();
const switcherContainer = document.createElement('div');
switcherContainer.className = 'language-switcher';
switcherContainer.innerHTML = `
<div class="language-switcher-toggle">
<i class="fas fa-globe"></i>
<span class="current-language">${this.languageNames[this.currentLang]}</span>
<i class="fas fa-chevron-down"></i>
</div>
<div class="language-options"></div>
`;
const languageOptions = switcherContainer.querySelector('.language-options');
this.supportedLanguages.forEach(lang => {
const option = document.createElement('div');
option.className = `language-option ${lang === this.currentLang ? 'active' : ''}`;
option.setAttribute('data-lang', lang);
option.innerHTML = `
<span class="language-name">${this.languageNames[lang]}</span>
<span class="language-code">${lang.toUpperCase()}</span>
`;
option.addEventListener('click', (e) => {
e.stopPropagation();
this.switchLanguage(lang);
switcherContainer.classList.remove('open');
});
languageOptions.appendChild(option);
});
const toggle = switcherContainer.querySelector('.language-switcher-toggle');
toggle.addEventListener('click', (e) => {
e.stopPropagation();
switcherContainer.classList.toggle('open');
});
// 点击外部区域关闭语言选择器
const clickOutside = (e) => {
if (!switcherContainer.contains(e.target)) {
switcherContainer.classList.remove('open');
}
};
document.addEventListener('click', clickOutside);
// 找到合适的插入位置
let target = document.getElementById('navbar-language-switcher');
if (target) {
target.appendChild(switcherContainer);
switcherContainer.classList.add('navbar-integrated');
} else {
// 后备插入到body
document.body.appendChild(switcherContainer);
}
// 恢复DOM观察
if (this.observer) {
const mainContent = document.querySelector('.login-container') || document.body;
this.observer.observe(mainContent, {childList: true, subtree: true});
}
} catch (error) {
console.error('渲染语言切换器失败:', error);
} finally {
this.isRenderingSwitcher = false;
}
}
updateLanguageSwitcher() {
try {
const switcher = document.querySelector('.language-switcher');
if (!switcher) return;
const currentLanguageSpan = switcher.querySelector('.current-language');
if (currentLanguageSpan) {
currentLanguageSpan.textContent = this.languageNames[this.currentLang];
}
const options = switcher.querySelectorAll('.language-option');
options.forEach(option => {
const lang = option.getAttribute('data-lang');
if (lang === this.currentLang) {
option.classList.add('active');
} else {
option.classList.remove('active');
}
});
} catch (error) {
console.error('更新语言切换器失败:', error);
}
}
}
// 实例化
window.i18n = new NikkeI18n();

View File

@@ -0,0 +1,38 @@
// NIKKE登录页专用脚本
document.addEventListener('DOMContentLoaded', function() {
// 设置表单提交事件
const form = document.getElementById('loginForm');
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
AdminLogin();
});
}
// 允许按回车键提交
const passwordInput = document.getElementById('PasswordBox');
if (passwordInput) {
passwordInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
AdminLogin();
}
});
}
// 检查是否已经登录
const token = localStorage.getItem('token');
if (token) {
// 已登录用户直接跳转
// window.location.pathname = "/admin/dashboard";
}
// 添加动画效果
setTimeout(function() {
const loginBox = document.querySelector('.login-box');
if (loginBox) {
loginBox.style.opacity = '1';
loginBox.style.transform = 'translateY(0)';
}
}, 100);
});

View File

@@ -0,0 +1,42 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// NIKKE管理控制台API通用工具函数
function runCmd(cmdName, cb, p1, p2)
{
fetch("/adminapi/RunCmd", {
method: "POST",
body: JSON.stringify({
cmdName: cmdName,
p1: p1,
p2: p2
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": `Bearer ${localStorage.getItem('token')}`
}
})
.then((response) => response.json())
.then((json) => cb(json)).catch((error) => {
console.error("命令执行失败:", error);
alert("操作失败: " + error.message);
});
}
function runSimpleCmd(cmdName, p1, p2)
{
runCmd(cmdName, function(json){
if (json.ok)
alert("操作已完成");
else
alert("错误: " + json.error);
}, p1, p2);
}
function runSimpleCmdWithPr(cmdName, p1, p2Title)
{
let p2 = prompt(p2Title);
if (p2 === undefined || p2 == null || p2 == "") return;
runSimpleCmd(cmdName, p1, p2);
}

View File

@@ -1,35 +1,168 @@

<!DOCTYPE html>
<html lang="en">
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Security System Controller</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<title data-i18n="app.name">NIKKE: 胜利女神 - 管理控制台</title>
<!-- 字体 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
<!-- 图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 基础框架 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<link rel="stylesheet" href="/admin/assets/login.css">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<!-- 自定义样式 -->
<link rel="stylesheet" href="/admin/assets/css/nikke-theme.css">
<link rel="stylesheet" href="/admin/assets/css/nikke-login.css">
<link rel="stylesheet" href="/admin/assets/css/nikke-i18n.css">
<!-- 网站图标 -->
<link rel="icon" href="/admin/assets/img/favicon.ico" type="image/x-icon">
</head>
<body>
<div class="LoginBox">
<h1>Login</h1>
<form onsubmit="return false;">
<div class="mb-3">
<label for="UsernameBox" class="form-label">Username</label>
<input type="text" class="form-control" id="UsernameBox" name="username">
<body class="login-page">
<div class="login-overlay"></div>
<div class="nikke-glow"></div>
<div class="login-container">
<div class="login-box">
<div class="login-header">
<img src="/admin/assets/img/nikke-logo.png" alt="NIKKE" class="logo">
<h1 data-i18n="app.name">胜利女神控制台</h1>
<p data-i18n="auth.welcome">请登录以访问管理功能</p>
</div>
<div class="mb-3">
<label for="PasswordBox" class="form-label">Password</label>
<input type="password" class="form-control" id="PasswordBox" name="password">
<div class="login-body">
<form id="loginForm" onsubmit="return false;">
<div class="form-floating">
<input type="text" class="form-control" id="UsernameBox" name="username" placeholder=" " autocomplete="username">
<label for="UsernameBox"><i class="fas fa-user me-2"></i><span data-i18n="auth.username">用户名</span></label>
</div>
<div class="form-floating mb-3">
<input type="password" class="form-control" id="PasswordBox" name="password" placeholder=" " autocomplete="current-password">
<label for="PasswordBox"><i class="fas fa-lock me-2"></i><span data-i18n="auth.password">密码</span></label>
</div>
<div id="errormsg" class="error-message"></div>
<button type="button" class="login-button" onclick="AdminLogin()">
<i class="fas fa-sign-in-alt me-2"></i><span data-i18n="auth.enter">进入控制台</span>
</button>
</form>
</div>
<p id="errormsg" style="color: red;"></p>
<button class="btn btn-primary" onclick="AdminLogin()">Submit</button>
</form>
<div class="login-footer" data-i18n="app.name">
NIKKE: 胜利女神 - 管理员控制台
</div>
</div>
</div>
<div class="version" data-i18n="app.version">v2.5.3</div>
<!-- 脚本 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="/admin/assets/js/loginpage.js"></script>
<!-- 国际化支持 -->
<script src="/admin/assets/js/nikke-i18n.js"></script>
<script src="/admin/assets/js/nikke-login.js"></script>
<script>
// 鼠标跟随光效
document.addEventListener('mousemove', function(e) {
const glow = document.querySelector('.nikke-glow');
glow.style.left = (e.clientX - 100) + 'px';
glow.style.top = (e.clientY - 100) + 'px';
});
// 增强错误消息显示
function showErrorMessage(message) {
const errorMsg = document.getElementById('errormsg');
errorMsg.textContent = message;
errorMsg.classList.add('visible');
// 添加抖动动画效果
errorMsg.style.animation = 'none';
setTimeout(() => {
errorMsg.style.animation = 'shake 0.5s cubic-bezier(.36,.07,.19,.97) both';
}, 10);
}
// 覆盖原有的登录函数
async function AdminLogin() {
const username = document.getElementById("UsernameBox").value;
const password = document.getElementById("PasswordBox").value;
if (!username || !password) {
showErrorMessage(i18n.t('auth.error.required'));
return;
}
const loginBtn = document.querySelector('.login-button');
const originalText = loginBtn.innerHTML;
// 添加加载状态
loginBtn.innerHTML = `<i class="fas fa-circle-notch fa-spin"></i> ${i18n.t('auth.verifying')}`;
loginBtn.disabled = true;
try {
const response = await fetch("/adminapi/login", {
method: "POST",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.ok) {
localStorage.setItem("token", data.token);
// 成功效果
loginBtn.innerHTML = `<i class="fas fa-check"></i> ${i18n.t('auth.success')}`;
loginBtn.style.background = 'linear-gradient(to right, #32d296, #38ef7d)';
// 添加成功动画,然后跳转
setTimeout(() => {
window.location.pathname = "/admin/dashboard";
}, 800);
} else {
// 恢复按钮状态
loginBtn.innerHTML = originalText;
loginBtn.disabled = false;
// 显示错误
const errorMessage = data.message || data.title || i18n.t('auth.error.invalid');
showErrorMessage(errorMessage);
}
} catch (error) {
// 恢复按钮状态
loginBtn.innerHTML = originalText;
loginBtn.disabled = false;
// 显示错误
showErrorMessage(i18n.t('auth.error.network'));
console.error(error);
}
}
// 语言变更时刷新页面内容
window.addEventListener('nikke:languageChanged', function() {
// 更新标题
document.title = i18n.t('app.name');
// 更新错误消息
const errorMsg = document.getElementById('errormsg');
if (errorMsg.textContent) {
errorMsg.textContent = i18n.t('auth.error.invalid');
}
// 更新登录按钮
const loginBtn = document.querySelector('.login-button');
if (!loginBtn.disabled) {
loginBtn.innerHTML = `<i class="fas fa-sign-in-alt me-2"></i><span>${i18n.t('auth.enter')}</span>`;
}
});
</script>
</body>
</html>