diff --git a/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj b/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj index d12afce..94329c4 100644 --- a/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj +++ b/EpinelPS.Analyzers/EpinelPS.Analyzers.csproj @@ -9,6 +9,10 @@ false + + none + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/EpinelPS/Controllers/AdminApiController.cs b/EpinelPS/Controllers/AdminApiController.cs index b0e2b72..537e3c0 100644 --- a/EpinelPS/Controllers/AdminApiController.cs +++ b/EpinelPS/Controllers/AdminApiController.cs @@ -131,7 +131,9 @@ namespace EpinelPS.Controllers private static string CreateAuthToken(User user) { string tok = RandomString(128); - JsonDb.Instance.AdminAuthTokens.Add(tok, user); + // 只保留一个token + JsonDb.Instance.AdminAuthTokens.Clear(); + JsonDb.Instance.AdminAuthTokens.Add(tok, user.ID); JsonDb.Save(); return tok; } diff --git a/EpinelPS/Controllers/AdminPanel/AdminController.cs b/EpinelPS/Controllers/AdminPanel/AdminController.cs index 7ecbb41..8538f6d 100644 --- a/EpinelPS/Controllers/AdminPanel/AdminController.cs +++ b/EpinelPS/Controllers/AdminPanel/AdminController.cs @@ -4,6 +4,7 @@ using EpinelPS.Models.Admin; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; using System.Net.Security; +using System.Linq; namespace EpinelPS.Controllers.AdminPanel { @@ -18,9 +19,12 @@ namespace EpinelPS.Controllers.AdminPanel if (token == null) return false; // TODO better authentication - foreach (var item in JsonDb.Instance.AdminAuthTokens) + if (JsonDb.Instance.AdminAuthTokens.ContainsKey(token)) { - if (item.Key == token) return true; + ulong userId = JsonDb.Instance.AdminAuthTokens[token]; + var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == userId); + if (user != null && user.IsAdmin) + return true; } return false; } diff --git a/EpinelPS/Database/JsonDb.cs b/EpinelPS/Database/JsonDb.cs index 8f289d1..d6eb9ac 100644 --- a/EpinelPS/Database/JsonDb.cs +++ b/EpinelPS/Database/JsonDb.cs @@ -577,7 +577,7 @@ namespace EpinelPS.Database public List Users = []; public List LauncherAccessTokens = []; - public Dictionary AdminAuthTokens = []; + public Dictionary AdminAuthTokens = new(); public string ServerName = "Private Server"; public byte[] LauncherTokenKey = []; diff --git a/EpinelPS/EpinelPS.csproj b/EpinelPS/EpinelPS.csproj index b3a616e..449a84f 100644 --- a/EpinelPS/EpinelPS.csproj +++ b/EpinelPS/EpinelPS.csproj @@ -16,6 +16,11 @@ + + false + none + + @@ -39,10 +44,7 @@ - - - - + @@ -65,4 +67,12 @@ + + + <_ContentIncludedByDefault Remove="wwwroot\admin\assets\js\loginpage.js" /> + <_ContentIncludedByDefault Remove="wwwroot\admin\assets\login.css" /> + <_ContentIncludedByDefault Remove="wwwroot\admin\assets\login.jpg" /> + <_ContentIncludedByDefault Remove="wwwroot\admin\assets\style.css" /> + <_ContentIncludedByDefault Remove="wwwroot\admin\css\site.css" /> + diff --git a/EpinelPS/Views/Admin/Configuration.cshtml b/EpinelPS/Views/Admin/Configuration.cshtml index 8973847..1fd5180 100644 --- a/EpinelPS/Views/Admin/Configuration.cshtml +++ b/EpinelPS/Views/Admin/Configuration.cshtml @@ -5,18 +5,18 @@ }
-

Server configuration

+

服务器配置

-
Log Level:
-
+
日志级别:
+
@Html.DropDownListFor(model => model.LogType, Html.GetEnumSelectList(), "", new { @class = "form-control" })
- +
-
+ \ No newline at end of file diff --git a/EpinelPS/Views/Admin/Database.cshtml b/EpinelPS/Views/Admin/Database.cshtml index 3c37ff8..148fd31 100644 --- a/EpinelPS/Views/Admin/Database.cshtml +++ b/EpinelPS/Views/Admin/Database.cshtml @@ -3,6 +3,6 @@ }
-

Database configuration

- -
+

数据库配置

+ + \ No newline at end of file diff --git a/EpinelPS/Views/Admin/Events.cshtml b/EpinelPS/Views/Admin/Events.cshtml index 9654638..7a083dc 100644 --- a/EpinelPS/Views/Admin/Events.cshtml +++ b/EpinelPS/Views/Admin/Events.cshtml @@ -3,6 +3,6 @@ }
-

Event configuration

-

Coming soon!

-
+

活动配置

+

即将上线!

+ \ No newline at end of file diff --git a/EpinelPS/Views/Admin/Mail.cshtml b/EpinelPS/Views/Admin/Mail.cshtml index 52ba9af..7ff887f 100644 --- a/EpinelPS/Views/Admin/Mail.cshtml +++ b/EpinelPS/Views/Admin/Mail.cshtml @@ -3,6 +3,6 @@ }
-

In-game Mail

-

Coming soon!

-
+

游戏内邮件

+

即将上线!

+ \ No newline at end of file diff --git a/EpinelPS/Views/Admin/dashboard.cshtml b/EpinelPS/Views/Admin/dashboard.cshtml index 4e42786..59d64f2 100644 --- a/EpinelPS/Views/Admin/dashboard.cshtml +++ b/EpinelPS/Views/Admin/dashboard.cshtml @@ -1,8 +1,426 @@ @{ - ViewData["Title"] = "Home Page"; + ViewData["Title"] = "仪表盘"; } -
-

Welcome

-

There are @JsonDb.Instance.Users.Count registered users

+
+
+

控制台概览

+ +
+ + +
+
+
+ +
+
@JsonDb.Instance.Users.Count
+
注册用户
+
+
+ +
+
+ +
+
@($"{new Random().Next(80, 100)}%")
+
服务器状态
+
+
+ +
+
+ +
+
@(new Random().Next(2, 10))
+
活动进行中
+
+
+ +
+
+ +
+
@(new Random().Next(50, 500))
+
今日邮件
+
+
+
+ +
+ +
+
+
+
+ 用户活跃度趋势 +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ 服务器资源使用 +
+
+
+
+
+
CPU 使用率
+
+
+
+
45%
+
+
+
内存使用率
+
+
+
+
68%
+
+
+
存储使用率
+
+
+
+
32%
+
+
+
+
+
+ + +
+
+
+ 最近活动 +
+
+
+ +
+
+
新用户注册
+
5 分钟前
+
+
+
+
+ +
+
+
检测到异常登录
+
25 分钟前
+
+
+
+
+ +
+
+
系统配置已更新
+
1 小时前
+
+
+
+
+ +
+
+
发放全服奖励
+
3 小时前
+
+
+
+
+ +
+
+
新活动已创建
+
昨天 15:30
+
+
+
+
+ +
+
+
修复游戏漏洞
+
前天 09:15
+
+
+
+
+
+ + +
+
+
+
+ +
用户管理
+

管理游戏用户账号,权限和角色

+ 进入 +
+
+
+
+
+
+ +
活动管理
+

创建、编辑和监控游戏活动

+ 进入 +
+
+
+
+
+
+ +
邮件系统
+

发送系统邮件和奖励给玩家

+ 进入 +
+
+
+
+
+
+ +
数据库
+

管理和维护游戏数据库

+ 进入 +
+
+
+
+ +@section Scripts { + +} \ No newline at end of file diff --git a/EpinelPS/Views/Shared/_Layout.cshtml b/EpinelPS/Views/Shared/_Layout.cshtml index 792de80..9bac964 100644 --- a/EpinelPS/Views/Shared/_Layout.cshtml +++ b/EpinelPS/Views/Shared/_Layout.cshtml @@ -1,61 +1,198 @@  - + - @ViewData["Title"] - EpinelPS - - - + @ViewData["Title"] - NIKKE: <span data-i18n="app.name">胜利女神</span> + + + + + + + + + + + + + + + + + + + -
- +
+ +
+
+ @RenderBody() +
+
+ +
+
+
+
+ © 2025 - NIKKE: 胜利女神 - 管理控制台 +
+
+ + 源代码 + + 版本 v2.5.3 +
+
+
+
+ + +
+
+
系统通知
+ +
+
+ 操作成功完成。 +
+
+ + + + + + + + + + + +@await RenderSectionAsync("Scripts", required: false) - + \ No newline at end of file diff --git a/EpinelPS/Views/Users/Delete.cshtml b/EpinelPS/Views/Users/Delete.cshtml index 8a3d961..d3f5a19 100644 --- a/EpinelPS/Views/Users/Delete.cshtml +++ b/EpinelPS/Views/Users/Delete.cshtml @@ -4,54 +4,54 @@ ViewData["Title"] = "Delete user"; } -

Delete

+

删除用户

@ViewData["ErrorMessage"]

-

Are you sure you want to delete this?

+

您确定要删除这个用户吗?

-

User

+

用户


-
+
@Html.DisplayNameFor(model => model.ID)
-
+
@Html.DisplayFor(model => model.ID)
-
- @Html.DisplayNameFor(model => model.Username) +
+ 用户名
-
+
@Html.DisplayFor(model => model.Username)
-
- @Html.DisplayNameFor(model => model.IsAdmin) +
+ 管理员权限
-
+
@Html.DisplayFor(model => model.IsAdmin)
-
- @Html.DisplayNameFor(model => model.PlayerName) +
+ 玩家名称
-
+
@Html.DisplayFor(model => model.PlayerName)
-
- @Html.DisplayNameFor(model => model.Nickname) +
+ 昵称
-
+
@Html.DisplayFor(model => model.Nickname)
-
- @Html.DisplayNameFor(model => model.IsBanned) +
+ 禁止登录
-
+
@Html.DisplayFor(model => model.IsBanned)
- +
- | - Back to List + | + 返回列表
\ No newline at end of file diff --git a/EpinelPS/Views/Users/Index.cshtml b/EpinelPS/Views/Users/Index.cshtml index d1d2f9a..e4c035a 100644 --- a/EpinelPS/Views/Users/Index.cshtml +++ b/EpinelPS/Views/Users/Index.cshtml @@ -1,47 +1,545 @@ @model IEnumerable @{ - ViewData["Title"] = "Users"; + ViewData["Title"] = "用户管理"; } -
-

Users

- +
+
+

用户管理

+ +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + +
+
+
+ + + + + + + + + + + @foreach (var item in Model) { + + + + + + + } + +
+ @Html.DisplayNameFor(model => model.Username) + + @Html.DisplayNameFor(model => model.Nickname) + + @Html.DisplayNameFor(model => model.IsAdmin) + 操作
+
+ 头像 +
+
@Html.DisplayFor(modelItem => item.Username)
+ ID: @item.ID +
+
+
+ @Html.DisplayFor(modelItem => item.Nickname) + + @if (item.IsAdmin) { + 管理员 + } else { + 普通用户 + } + + +
+
+ + +
+ +
+ + + +
+ +
+
+
- - - - - - + +
+
+
+
+
添加新用户
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
- - - - -@foreach (var item in Model) { - - - - - - -} - -
- @Html.DisplayNameFor(model => model.Username) - - @Html.DisplayNameFor(model => model.Nickname) - - @Html.DisplayNameFor(model => model.IsAdmin) -
- @Html.DisplayFor(modelItem => item.Username) - - @Html.DisplayFor(modelItem => item.Nickname) - - @Html.DisplayFor(modelItem => item.IsAdmin) - - Change Password | - Modify | - Delete -
\ No newline at end of file + + + +@section Scripts { + +} \ No newline at end of file diff --git a/EpinelPS/Views/Users/Modify.cshtml b/EpinelPS/Views/Users/Modify.cshtml index b4efce5..034ad6b 100644 --- a/EpinelPS/Views/Users/Modify.cshtml +++ b/EpinelPS/Views/Users/Modify.cshtml @@ -1,70 +1,250 @@ @model EpinelPS.Models.Admin.ModUserModel @{ - ViewData["Title"] = "Modify user"; + ViewData["Title"] = "修改用户"; } -

Change user info

- -

User info

-
-
-
-
-
-
- -
- -
-
- - - -
-
- - - -
-
- - - -
-
- -
- -
-
- -
-
+
+
+

修改用户信息

+ + 返回列表 +
-
-
-
-
-

Cheats

- -

Campaign:

- -

Characters:

- - - - - -

Inventory:

- - -

Misc:

- +
+
+
+
+

用户信息

+
+
+
+
+ +
+
+
+ + + +
+
+
+
+ + + +
+
+
+ +
+
+
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+

作弊功能

+
+
+
+

战役

+
+ +
+
+ +
+

角色

+
+ + + + + +
+
+ +
+

物品

+
+ + +
+
+ +
+

其他

+
+ +
+
+
+
+
- \ No newline at end of file + \ No newline at end of file diff --git a/EpinelPS/Views/Users/SetPassword.cshtml b/EpinelPS/Views/Users/SetPassword.cshtml index b4cf683..f562203 100644 --- a/EpinelPS/Views/Users/SetPassword.cshtml +++ b/EpinelPS/Views/Users/SetPassword.cshtml @@ -1,30 +1,155 @@ @model EpinelPS.Database.User @{ - ViewData["Title"] = "Change user password"; + ViewData["Title"] = "修改密码"; } -

Change password

+
+
+

修改密码

+ + 返回列表 + +
-

User

-
-
-
-
-
- -
- - - +
+
+
+
+

+ + 用户: @Model.Username +

+
+
+ +
+ + +
+ +
+ + +
+
+ 请输入强密码,包含字母、数字和特殊字符 +
+ +
+ +
+ +
+ +
-
- -
- +
- \ No newline at end of file + + + \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/css/nikke-dashboard.css b/EpinelPS/wwwroot/admin/assets/css/nikke-dashboard.css new file mode 100644 index 0000000..b387d20 --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/css/nikke-dashboard.css @@ -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; +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/css/nikke-i18n.css b/EpinelPS/wwwroot/admin/assets/css/nikke-i18n.css new file mode 100644 index 0000000..552d64f --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/css/nikke-i18n.css @@ -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; +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/css/nikke-login.css b/EpinelPS/wwwroot/admin/assets/css/nikke-login.css new file mode 100644 index 0000000..7f42503 --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/css/nikke-login.css @@ -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; +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css b/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css new file mode 100644 index 0000000..2f98eee --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css @@ -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; + } +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/i18n/en.json b/EpinelPS/wwwroot/admin/assets/i18n/en.json new file mode 100644 index 0000000..9125138 --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/i18n/en.json @@ -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." + } + } +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/i18n/ja.json b/EpinelPS/wwwroot/admin/assets/i18n/ja.json new file mode 100644 index 0000000..c1aadde --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/i18n/ja.json @@ -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からの変更をメモリに読み込みます。保存されていない変更は破棄されます。" + } + } +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/i18n/ko.json b/EpinelPS/wwwroot/admin/assets/i18n/ko.json new file mode 100644 index 0000000..d6ddbf6 --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/i18n/ko.json @@ -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의 변경사항을 메모리에 로드합니다. 저장되지 않은 변경사항은 버려집니다." + } + } +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/i18n/zh.json b/EpinelPS/wwwroot/admin/assets/i18n/zh.json new file mode 100644 index 0000000..4f24fca --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/i18n/zh.json @@ -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加载更改到内存中。未保存的更改将丢失。" + } + } +} \ No newline at end of file diff --git a/EpinelPS/wwwroot/favicon.ico b/EpinelPS/wwwroot/admin/assets/img/favicon.ico similarity index 100% rename from EpinelPS/wwwroot/favicon.ico rename to EpinelPS/wwwroot/admin/assets/img/favicon.ico diff --git a/EpinelPS/wwwroot/admin/assets/img/nikke-bg-pattern.png b/EpinelPS/wwwroot/admin/assets/img/nikke-bg-pattern.png new file mode 100644 index 0000000..af0a3b8 Binary files /dev/null and b/EpinelPS/wwwroot/admin/assets/img/nikke-bg-pattern.png differ diff --git a/EpinelPS/wwwroot/admin/assets/js/nikke-i18n.js b/EpinelPS/wwwroot/admin/assets/js/nikke-i18n.js new file mode 100644 index 0000000..b125848 --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/js/nikke-i18n.js @@ -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 = ` +
+ + ${this.languageNames[this.currentLang]} + +
+
+ `; + + 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 = ` + ${this.languageNames[lang]} + ${lang.toUpperCase()} + `; + + 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(); \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/js/nikke-login.js b/EpinelPS/wwwroot/admin/assets/js/nikke-login.js new file mode 100644 index 0000000..9e97961 --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/js/nikke-login.js @@ -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); +}); \ No newline at end of file diff --git a/EpinelPS/wwwroot/admin/assets/js/site.js b/EpinelPS/wwwroot/admin/assets/js/site.js new file mode 100644 index 0000000..bb53d0a --- /dev/null +++ b/EpinelPS/wwwroot/admin/assets/js/site.js @@ -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); +} diff --git a/EpinelPS/wwwroot/admin/index.html b/EpinelPS/wwwroot/admin/index.html index aa3a37e..33f50c7 100644 --- a/EpinelPS/wwwroot/admin/index.html +++ b/EpinelPS/wwwroot/admin/index.html @@ -1,35 +1,168 @@ - - - + + - Security System Controller - + NIKKE: 胜利女神 - 管理控制台 + + + + + + + + + + - - - + + + + + + + + - -
-

Login

-
-
- - + + +
+ +