@@ -0,0 +1,851 @@
|
||||
/* 简约风格的后台管理样式 */
|
||||
|
||||
:root {
|
||||
--primary: #0071e3;
|
||||
--success: #34c759;
|
||||
--danger: #ef4444;
|
||||
--warning: #ff9500;
|
||||
--bg: #f5f5f7;
|
||||
--card: #ffffff;
|
||||
--text: #1d1d1f;
|
||||
--text-secondary: #86868b;
|
||||
--border: rgba(0, 0, 0, 0.1);
|
||||
--shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
--shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 认证页面 */
|
||||
.auth-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.auth-box {
|
||||
background: var(--card);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
box-shadow: var(--shadow-hover);
|
||||
animation: fadeInUp 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 0 auto 1rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.auth-header h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-header p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s;
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.password-strength {
|
||||
margin-top: 0.5rem;
|
||||
height: 3px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.password-strength::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: var(--danger);
|
||||
transition: width 0.3s, background 0.3s;
|
||||
}
|
||||
|
||||
.password-strength.weak::after {
|
||||
width: 33%;
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
.password-strength.medium::after {
|
||||
width: 66%;
|
||||
background: var(--warning);
|
||||
}
|
||||
|
||||
.password-strength.strong::after {
|
||||
width: 100%;
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0051a5;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 113, 227, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #e0e0e0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
padding: 0.5rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: #fee;
|
||||
color: var(--danger);
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
.error-message.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-link {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.auth-link:hover {
|
||||
color: #0051a5;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 管理界面 */
|
||||
.admin-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
background: var(--card);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#current-user {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
width: 260px;
|
||||
background: var(--card);
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--bg);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: rgba(0, 113, 227, 0.08);
|
||||
color: var(--primary);
|
||||
border-left-color: var(--primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
.page.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
box-shadow: var(--shadow);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-hover);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.table-container {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table thead {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.data-table tbody tr {
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.data-table tbody tr:hover {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 模态框 */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: fadeIn 0.2s;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: var(--shadow-hover);
|
||||
animation: fadeInUp 0.3s;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 1.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* 文件浏览器 */
|
||||
.file-browser {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.file-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
/* 编辑器 */
|
||||
.editor-container {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.code-editor {
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
resize: vertical;
|
||||
background: var(--card);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.code-editor:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1);
|
||||
}
|
||||
|
||||
.config-controls {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config-controls select {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 日志 */
|
||||
.logs-container {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.logs-content {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.log-entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-entry:hover {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: var(--text-secondary);
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
font-weight: 600;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.log-level.INFO {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.log-level.WARN {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.log-level.ERROR {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 设置页面 */
|
||||
.settings-container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.database-info,
|
||||
.system-stats,
|
||||
.os-info {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.info-item,
|
||||
.stat-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.info-item:last-child,
|
||||
.stat-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label,
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.info-value,
|
||||
.stat-value {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background: rgba(52, 199, 89, 0.1);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.database-password,
|
||||
.database-convert {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.password-warning,
|
||||
.convert-warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid var(--warning);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #856404;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.password-form .form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#password-result,
|
||||
#convert-result {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#password-result.success,
|
||||
#convert-result.success {
|
||||
background: rgba(52, 199, 89, 0.1);
|
||||
border: 1px solid var(--success);
|
||||
color: var(--success);
|
||||
display: block;
|
||||
}
|
||||
|
||||
#password-result.error,
|
||||
#convert-result.error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid var(--danger);
|
||||
color: var(--danger);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.convert-form {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.convert-form select {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.admin-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
:root {
|
||||
--background: #fbf7ef;
|
||||
--foreground: #1c1917;
|
||||
--card: rgba(255, 255, 255, 0.9);
|
||||
--card-foreground: #1c1917;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #1c1917;
|
||||
--primary: #5f6f45;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f5f5f4;
|
||||
--secondary-foreground: #1c1917;
|
||||
--muted: #f5f5f4;
|
||||
--muted-foreground: #57534e;
|
||||
--accent: #b56e45;
|
||||
--accent-foreground: #ffffff;
|
||||
--destructive: #b91c1c;
|
||||
--destructive-foreground: #ffffff;
|
||||
--border: #d6d3d1;
|
||||
--input: #d6d3d1;
|
||||
--ring: #7a8a67;
|
||||
--radius: 0.75rem;
|
||||
--shadow: 0 18px 40px rgba(28, 25, 23, 0.08);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body.shad-app {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
color: var(--foreground);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(181, 110, 69, 0.1), transparent 20rem),
|
||||
radial-gradient(circle at top right, rgba(122, 138, 103, 0.1), transparent 20rem),
|
||||
var(--background);
|
||||
}
|
||||
|
||||
.site-header {
|
||||
border-bottom: 1px solid rgba(214, 211, 209, 0.8);
|
||||
padding: 34px 0 30px;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.header-inner h1 {
|
||||
margin: 10px 0 0;
|
||||
max-width: 760px;
|
||||
font-size: clamp(2rem, 4vw, 3rem);
|
||||
line-height: 1.08;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.header-inner p {
|
||||
margin: 14px 0 0;
|
||||
max-width: 760px;
|
||||
color: var(--muted-foreground);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
color: var(--primary);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.summary-card span {
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.summary-card strong {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.summary-card p {
|
||||
margin: 8px 0 0;
|
||||
color: var(--muted-foreground);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title h2 {
|
||||
margin: 6px 0 0;
|
||||
}
|
||||
|
||||
.section-title p {
|
||||
margin: 0;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.125rem 0.35rem;
|
||||
background: var(--muted);
|
||||
font-family: "Cascadia Code", "JetBrains Mono", monospace;
|
||||
}
|
||||
|
||||
.shad-container {
|
||||
width: min(1180px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.shad-hero {
|
||||
border-bottom: 1px solid rgba(214, 211, 209, 0.8);
|
||||
}
|
||||
|
||||
.shad-hero__inner {
|
||||
min-height: 280px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 48px 0 40px;
|
||||
}
|
||||
|
||||
.shad-hero__copy {
|
||||
max-width: 780px;
|
||||
}
|
||||
|
||||
.shad-hero__title {
|
||||
margin: 16px 0 0;
|
||||
font-size: clamp(2rem, 4vw, 3.25rem);
|
||||
line-height: 1.05;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.shad-hero__text {
|
||||
margin: 16px 0 0;
|
||||
color: var(--muted-foreground);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.shad-main {
|
||||
padding: 28px 0 56px;
|
||||
}
|
||||
|
||||
.shad-section__head {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.shad-section__head h2 {
|
||||
margin: 0;
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.shad-section__head p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.shad-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.shad-card,
|
||||
.shad-dialog__content {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--card);
|
||||
color: var(--card-foreground);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.shad-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.shad-card--state {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.shad-card--danger {
|
||||
border-color: rgba(185, 28, 28, 0.24);
|
||||
}
|
||||
|
||||
.shad-card--state h3,
|
||||
.shad-product__title h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.shad-card--state p,
|
||||
.shad-product__title p,
|
||||
.shad-history__item p {
|
||||
margin: 8px 0 0;
|
||||
color: var(--muted-foreground);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.shad-product {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.shad-product__head {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.shad-product__icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
flex: 0 0 52px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.875rem;
|
||||
background: rgba(22, 101, 52, 0.08);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.shad-product__icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.shad-stack {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.shad-stack--row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.shad-stack--wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.shad-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid transparent;
|
||||
background: rgba(22, 101, 52, 0.1);
|
||||
color: var(--primary);
|
||||
font-size: 0.82rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shad-badge--outline {
|
||||
background: transparent;
|
||||
border-color: var(--border);
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.shad-badge--muted {
|
||||
background: var(--muted);
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.shad-badge--subtle {
|
||||
background: rgba(217, 119, 6, 0.12);
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.shad-panel {
|
||||
border: 1px solid rgba(214, 211, 209, 0.8);
|
||||
border-radius: calc(var(--radius) - 0.15rem);
|
||||
padding: 16px;
|
||||
background: linear-gradient(180deg, rgba(28, 25, 23, 0.02), rgba(28, 25, 23, 0.04));
|
||||
}
|
||||
|
||||
.shad-panel__main {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.shad-panel__main strong {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 1.4rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.shad-meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.shad-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.shad-muted {
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.shad-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shad-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.shad-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40px;
|
||||
padding: 0 16px;
|
||||
border-radius: calc(var(--radius) - 0.2rem);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
|
||||
}
|
||||
|
||||
.shad-button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.shad-button--primary {
|
||||
background: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
.shad-button--primary:hover {
|
||||
background: #14532d;
|
||||
}
|
||||
|
||||
.shad-button--secondary {
|
||||
background: var(--secondary);
|
||||
color: var(--secondary-foreground);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.shad-button--secondary:hover {
|
||||
background: #ede9e7;
|
||||
}
|
||||
|
||||
.shad-dialog {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.shad-dialog.is-open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.shad-dialog__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(28, 25, 23, 0.45);
|
||||
}
|
||||
|
||||
.shad-dialog__content {
|
||||
position: relative;
|
||||
width: min(680px, 100%);
|
||||
max-height: min(80vh, 720px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shad-dialog__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 18px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.shad-dialog__header h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.shad-dialog__body {
|
||||
padding: 8px 20px 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.shad-icon-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--popover);
|
||||
color: var(--popover-foreground);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.shad-history {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.shad-history__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid rgba(214, 211, 209, 0.8);
|
||||
}
|
||||
|
||||
.shad-history__item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.shad-footer {
|
||||
border-top: 1px solid rgba(214, 211, 209, 0.8);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.shad-footer__inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 18px 0 24px;
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.header-inner,
|
||||
.section-title {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-actions,
|
||||
.summary-grid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.shad-hero__inner {
|
||||
min-height: auto;
|
||||
padding: 40px 0 32px;
|
||||
}
|
||||
|
||||
.shad-meta-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.shad-actions,
|
||||
.shad-footer__inner,
|
||||
.shad-panel__main,
|
||||
.shad-history__item,
|
||||
.shad-product__head {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.shad-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 5.5 MiB |
@@ -0,0 +1,532 @@
|
||||
// 后台管理 JavaScript
|
||||
|
||||
// 获取 Cookie
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return null;
|
||||
}
|
||||
|
||||
// API 请求封装
|
||||
async function apiRequest(url, options = {}) {
|
||||
const token = getCookie('token');
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// 如果是401,跳转到登录页
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/admin/login';
|
||||
return;
|
||||
}
|
||||
throw new Error(data.error || '请求失败');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载当前用户信息
|
||||
async function loadCurrentUser() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/me');
|
||||
const userEl = document.getElementById('current-user');
|
||||
if (userEl && data.user) {
|
||||
userEl.textContent = `${data.user.username}${data.user.is_admin ? ' (管理员)' : ''}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load user error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 登出
|
||||
document.getElementById('logout-btn')?.addEventListener('click', async () => {
|
||||
try {
|
||||
await apiRequest('/admin/logout', { method: 'POST' });
|
||||
document.cookie = 'token=; path=/; max-age=0';
|
||||
window.location.href = '/admin/login';
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
// 即使失败也跳转
|
||||
document.cookie = 'token=; path=/; max-age=0';
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
});
|
||||
|
||||
// 页面导航
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const page = item.dataset.page;
|
||||
|
||||
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
|
||||
item.classList.add('active');
|
||||
document.getElementById(`page-${page}`).classList.add('active');
|
||||
|
||||
// 加载对应页面数据
|
||||
if (page === 'routes') {
|
||||
loadRoutes();
|
||||
} else if (page === 'logs') {
|
||||
loadLogs();
|
||||
} else if (page === 'files') {
|
||||
loadFiles();
|
||||
} else if (page === 'config') {
|
||||
loadConfig();
|
||||
} else if (page === 'database') {
|
||||
loadDatabaseInfo();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 加载数据库信息
|
||||
async function loadDatabaseInfo() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/database');
|
||||
const dbDetails = document.getElementById('db-details');
|
||||
const osDetails = document.getElementById('os-details');
|
||||
const dbConfig = document.getElementById('db-config');
|
||||
const passwordSection = document.getElementById('database-password-section');
|
||||
|
||||
dbDetails.innerHTML = `
|
||||
<p><strong>类型:</strong> ${data.database.type}</p>
|
||||
<p><strong>状态:</strong> ${data.database.status}</p>
|
||||
${data.database.file ? `<p><strong>文件:</strong> ${data.database.file}</p>` : ''}
|
||||
${data.database.cgo_support !== undefined ? `<p><strong>CGO 支持:</strong> ${data.database.cgo_support ? '是' : '否'}</p>` : ''}
|
||||
`;
|
||||
|
||||
osDetails.innerHTML = `
|
||||
<p><strong>操作系统:</strong> ${data.os.os}</p>
|
||||
<p><strong>架构:</strong> ${data.os.arch}</p>
|
||||
`;
|
||||
|
||||
// 加载数据库配置
|
||||
try {
|
||||
const configData = await apiRequest('/admin/api/database/config');
|
||||
dbConfig.innerHTML = `
|
||||
<p><strong>类型:</strong> ${configData.config.type}</p>
|
||||
<p><strong>主机:</strong> ${configData.config.host}</p>
|
||||
<p><strong>端口:</strong> ${configData.config.port}</p>
|
||||
<p><strong>用户:</strong> ${configData.config.user}</p>
|
||||
<p><strong>数据库:</strong> ${configData.config.database}</p>
|
||||
<p><strong>已设置密码:</strong> ${configData.config.has_password ? '是' : '否'}</p>
|
||||
`;
|
||||
|
||||
// 如果是 MySQL,显示密码修改界面
|
||||
if (configData.config.type === 'mysql') {
|
||||
passwordSection.style.display = 'block';
|
||||
} else {
|
||||
passwordSection.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load database config error:', error);
|
||||
dbConfig.innerHTML = '<p>无法加载配置信息</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load database info error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据库信息
|
||||
document.getElementById('refresh-db-btn')?.addEventListener('click', loadDatabaseInfo);
|
||||
|
||||
// 转换数据库
|
||||
document.getElementById('convert-db-btn')?.addEventListener('click', async () => {
|
||||
const targetType = document.getElementById('target-db-type').value;
|
||||
|
||||
if (!confirm(`确定要转换数据库类型吗?\n\n目标类型: ${targetType.toUpperCase()}\n\n此操作会导出当前数据并导入到新数据库。请确保已备份数据!`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultEl = document.getElementById('convert-result');
|
||||
resultEl.className = '';
|
||||
resultEl.textContent = '正在转换...';
|
||||
resultEl.style.display = 'block';
|
||||
|
||||
try {
|
||||
const result = await apiRequest('/admin/api/database/convert', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
target_type: targetType,
|
||||
}),
|
||||
});
|
||||
|
||||
resultEl.className = 'success';
|
||||
resultEl.textContent = result.message || '数据库转换成功!';
|
||||
loadDatabaseInfo();
|
||||
loadLogs();
|
||||
} catch (error) {
|
||||
resultEl.className = 'error';
|
||||
resultEl.textContent = '转换失败: ' + error.message;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新数据库密码
|
||||
document.getElementById('password-form')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const currentPassword = document.getElementById('current-password').value;
|
||||
const newPassword = document.getElementById('new-password').value;
|
||||
const confirmPassword = document.getElementById('confirm-password').value;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert('新密码和确认密码不一致!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('确定要更新数据库 root 密码吗?\n\n更新后需要修改环境变量 DB_PASSWORD 并重启服务器!')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultEl = document.getElementById('password-result');
|
||||
resultEl.className = '';
|
||||
resultEl.textContent = '正在更新密码...';
|
||||
resultEl.style.display = 'block';
|
||||
|
||||
try {
|
||||
const result = await apiRequest('/admin/api/database/password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword,
|
||||
confirm_password: confirmPassword,
|
||||
}),
|
||||
});
|
||||
|
||||
resultEl.className = 'success';
|
||||
resultEl.textContent = result.message || '密码更新成功!';
|
||||
|
||||
// 清空表单
|
||||
document.getElementById('password-form').reset();
|
||||
|
||||
loadLogs();
|
||||
} catch (error) {
|
||||
resultEl.className = 'error';
|
||||
resultEl.textContent = '更新失败: ' + error.message;
|
||||
}
|
||||
});
|
||||
|
||||
// 加载系统信息
|
||||
async function loadSystemInfo() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/system');
|
||||
document.getElementById('stat-users').textContent = data.users;
|
||||
document.getElementById('stat-routes').textContent = data.routes;
|
||||
document.getElementById('stat-logs').textContent = data.logs;
|
||||
document.getElementById('stat-time').textContent = data.server_time;
|
||||
} catch (error) {
|
||||
console.error('Load system info error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载路由
|
||||
async function loadRoutes() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/routes');
|
||||
const tbody = document.getElementById('routes-table-body');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (data.routes && data.routes.length > 0) {
|
||||
data.routes.forEach(route => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${route.id}</td>
|
||||
<td><span class="badge">${route.method}</span></td>
|
||||
<td>${route.path}</td>
|
||||
<td>${route.type}</td>
|
||||
<td>${route.description || '-'}</td>
|
||||
<td>${route.is_active ? '✅' : '❌'}</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary" onclick="editRoute(${route.id})">编辑</button>
|
||||
<button class="btn btn-danger" onclick="deleteRoute(${route.id})">删除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} else {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="empty-state">暂无路由</td></tr>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load routes error:', error);
|
||||
const tbody = document.getElementById('routes-table-body');
|
||||
if (tbody) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="empty-state">加载失败: ' + error.message + '</td></tr>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加路由
|
||||
document.getElementById('add-route-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('route-modal-title').textContent = '添加路由';
|
||||
document.getElementById('route-form').reset();
|
||||
document.getElementById('route-id').value = '';
|
||||
document.getElementById('route-modal').classList.add('show');
|
||||
});
|
||||
|
||||
// 编辑路由
|
||||
window.editRoute = async function(id) {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/routes');
|
||||
const route = data.routes.find(r => r.id === id);
|
||||
|
||||
if (route) {
|
||||
document.getElementById('route-modal-title').textContent = '编辑路由';
|
||||
document.getElementById('route-id').value = route.id;
|
||||
document.getElementById('route-method').value = route.method;
|
||||
document.getElementById('route-path').value = route.path;
|
||||
document.getElementById('route-type').value = route.type;
|
||||
document.getElementById('route-handler').value = route.handler;
|
||||
document.getElementById('route-description').value = route.description || '';
|
||||
document.getElementById('route-active').checked = route.is_active;
|
||||
document.getElementById('route-order').value = route.order;
|
||||
document.getElementById('route-modal').classList.add('show');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Edit route error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除路由
|
||||
window.deleteRoute = async function(id) {
|
||||
if (!confirm('确定要删除这个路由吗?')) return;
|
||||
|
||||
try {
|
||||
await apiRequest(`/admin/api/routes/${id}`, { method: 'DELETE' });
|
||||
loadRoutes();
|
||||
} catch (error) {
|
||||
alert('删除失败: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存路由
|
||||
document.getElementById('route-save-btn')?.addEventListener('click', async () => {
|
||||
const id = document.getElementById('route-id').value;
|
||||
const routeData = {
|
||||
method: document.getElementById('route-method').value,
|
||||
path: document.getElementById('route-path').value,
|
||||
type: document.getElementById('route-type').value,
|
||||
handler: document.getElementById('route-handler').value,
|
||||
description: document.getElementById('route-description').value,
|
||||
is_active: document.getElementById('route-active').checked,
|
||||
order: parseInt(document.getElementById('route-order').value) || 0,
|
||||
};
|
||||
|
||||
try {
|
||||
if (id) {
|
||||
await apiRequest(`/admin/api/routes/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(routeData),
|
||||
});
|
||||
} else {
|
||||
await apiRequest('/admin/api/routes', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(routeData),
|
||||
});
|
||||
}
|
||||
document.getElementById('route-modal').classList.remove('show');
|
||||
loadRoutes();
|
||||
} catch (error) {
|
||||
alert('保存失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭模态框
|
||||
document.querySelectorAll('.modal-close, #route-cancel-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.getElementById('route-modal').classList.remove('show');
|
||||
});
|
||||
});
|
||||
|
||||
// 加载日志
|
||||
async function loadLogs() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/logs?limit=100');
|
||||
const logsEl = document.getElementById('logs-content');
|
||||
if (!logsEl) return;
|
||||
|
||||
logsEl.innerHTML = '';
|
||||
|
||||
if (data.logs && data.logs.length > 0) {
|
||||
data.logs.forEach(log => {
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'log-entry';
|
||||
entry.innerHTML = `
|
||||
<span class="log-time">${log.time || ''}</span>
|
||||
<span class="log-level ${log.level || 'INFO'}">${log.level || 'INFO'}</span>
|
||||
<span class="log-message">${log.message || ''}</span>
|
||||
`;
|
||||
logsEl.appendChild(entry);
|
||||
});
|
||||
|
||||
logsEl.scrollTop = logsEl.scrollHeight;
|
||||
} else {
|
||||
logsEl.innerHTML = '<div class="empty-state">暂无日志</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load logs error:', error);
|
||||
const logsEl = document.getElementById('logs-content');
|
||||
if (logsEl) {
|
||||
logsEl.innerHTML = '<div class="empty-state">加载失败: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新日志
|
||||
document.getElementById('refresh-logs-btn')?.addEventListener('click', loadLogs);
|
||||
|
||||
// 清空日志
|
||||
document.getElementById('clear-logs-btn')?.addEventListener('click', () => {
|
||||
const logsEl = document.getElementById('logs-content');
|
||||
if (logsEl) {
|
||||
logsEl.innerHTML = '<div class="empty-state">日志已清空</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// 加载文件
|
||||
async function loadFiles() {
|
||||
const fileBrowser = document.getElementById('file-browser');
|
||||
if (!fileBrowser) return;
|
||||
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/files?dir=public/downloads');
|
||||
fileBrowser.innerHTML = '';
|
||||
|
||||
if (data.files && data.files.length > 0) {
|
||||
data.files.forEach(file => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'file-item';
|
||||
item.innerHTML = `
|
||||
<div>
|
||||
<strong>${file.name}</strong>
|
||||
<small style="display: block; color: #86868b;">
|
||||
${file.is_dir ? '📁 目录' : `📄 ${formatBytes(file.size)}`} • ${file.mod_time || ''}
|
||||
</small>
|
||||
</div>
|
||||
${!file.is_dir ? `<button class="btn btn-secondary" onclick="readFile('${file.name}')">查看</button>` : ''}
|
||||
`;
|
||||
fileBrowser.appendChild(item);
|
||||
});
|
||||
} else {
|
||||
fileBrowser.innerHTML = '<div class="empty-state">暂无文件</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load files error:', error);
|
||||
fileBrowser.innerHTML = '<div class="empty-state">加载失败: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('refresh-files-btn')?.addEventListener('click', loadFiles);
|
||||
|
||||
// 读取文件
|
||||
window.readFile = async function(filename) {
|
||||
const fullPath = `public/downloads/${filename}`;
|
||||
|
||||
try {
|
||||
const data = await apiRequest(`/admin/api/file?path=${encodeURIComponent(fullPath)}`);
|
||||
// 显示文件内容在模态框中或新窗口
|
||||
const content = data.content.substring(0, 5000) + (data.content.length > 5000 ? '\n\n... (内容过长,已截断)' : '');
|
||||
alert('文件内容:\n\n' + content);
|
||||
} catch (error) {
|
||||
alert('读取文件失败: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载配置
|
||||
async function loadConfig() {
|
||||
const file = document.getElementById('config-select').value;
|
||||
try {
|
||||
const response = await fetch(`/public/${file}`);
|
||||
const data = await response.json();
|
||||
document.getElementById('config-editor').value = JSON.stringify(data, null, 2);
|
||||
} catch (error) {
|
||||
console.error('Load config error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('load-config-btn')?.addEventListener('click', loadConfig);
|
||||
|
||||
// 保存配置
|
||||
document.getElementById('save-config-btn')?.addEventListener('click', async () => {
|
||||
const file = document.getElementById('config-select').value;
|
||||
const content = document.getElementById('config-editor').value;
|
||||
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
const result = await apiRequest('/admin/api/config', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
file: file,
|
||||
content: jsonData,
|
||||
reload: false, // 仅保存
|
||||
}),
|
||||
});
|
||||
alert(result.message || '配置保存成功!');
|
||||
} catch (error) {
|
||||
alert('保存失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 保存并立即加载配置
|
||||
document.getElementById('save-reload-config-btn')?.addEventListener('click', async () => {
|
||||
const file = document.getElementById('config-select').value;
|
||||
const content = document.getElementById('config-editor').value;
|
||||
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
const result = await apiRequest('/admin/api/config', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
file: file,
|
||||
content: jsonData,
|
||||
reload: true, // 保存并立即加载
|
||||
}),
|
||||
});
|
||||
alert(result.message || '配置已保存并立即生效!');
|
||||
// 刷新日志以显示加载信息
|
||||
if (document.getElementById('page-logs')?.classList.contains('active')) {
|
||||
loadLogs();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('保存失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 工具函数
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadCurrentUser();
|
||||
loadSystemInfo();
|
||||
|
||||
// 定期更新系统信息
|
||||
setInterval(loadSystemInfo, 30000); // 每30秒更新一次
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
// 认证相关 JavaScript
|
||||
|
||||
// 检查是否已登录
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const token = getCookie('token');
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch('/admin/me', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// 已登录,跳转到管理页面
|
||||
window.location.href = '/admin';
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 Cookie
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return null;
|
||||
}
|
||||
|
||||
// API 请求封装
|
||||
async function apiRequest(url, options = {}) {
|
||||
const token = getCookie('token');
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: '请求失败' }));
|
||||
throw new Error(error.error || '请求失败');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showError(message) {
|
||||
const errorEl = document.getElementById('auth-error');
|
||||
if (errorEl) {
|
||||
errorEl.textContent = message;
|
||||
errorEl.classList.add('show');
|
||||
setTimeout(() => {
|
||||
errorEl.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录表单处理
|
||||
const loginForm = document.getElementById('login-form');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(loginForm);
|
||||
const data = {
|
||||
username: formData.get('username'),
|
||||
password: formData.get('password')
|
||||
};
|
||||
|
||||
const submitBtn = loginForm.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span>登录中...</span>';
|
||||
|
||||
try {
|
||||
const result = await apiRequest('/admin/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
// 设置 token cookie
|
||||
document.cookie = `token=${result.token}; path=/; max-age=86400; SameSite=Lax`;
|
||||
|
||||
// 跳转到管理页面
|
||||
window.location.href = '/admin';
|
||||
} catch (error) {
|
||||
showError(error.message || '登录失败');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 注册表单处理
|
||||
const registerForm = document.getElementById('register-form');
|
||||
if (registerForm) {
|
||||
// 密码强度检测
|
||||
const passwordInput = document.getElementById('password');
|
||||
const passwordStrength = document.getElementById('password-strength');
|
||||
|
||||
if (passwordInput && passwordStrength) {
|
||||
passwordInput.addEventListener('input', (e) => {
|
||||
const password = e.target.value;
|
||||
let strength = 'weak';
|
||||
|
||||
if (password.length >= 8) {
|
||||
const hasUpper = /[A-Z]/.test(password);
|
||||
const hasLower = /[a-z]/.test(password);
|
||||
const hasNumber = /[0-9]/.test(password);
|
||||
const hasSpecial = /[^A-Za-z0-9]/.test(password);
|
||||
|
||||
const score = [hasUpper, hasLower, hasNumber, hasSpecial].filter(Boolean).length;
|
||||
|
||||
if (score >= 3) {
|
||||
strength = 'strong';
|
||||
} else if (score >= 2) {
|
||||
strength = 'medium';
|
||||
}
|
||||
}
|
||||
|
||||
passwordStrength.className = `password-strength ${strength}`;
|
||||
});
|
||||
}
|
||||
|
||||
registerForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const password = document.getElementById('password').value;
|
||||
const confirmPassword = document.getElementById('confirm-password').value;
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
showError('两次输入的密码不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(registerForm);
|
||||
const data = {
|
||||
username: formData.get('username'),
|
||||
email: formData.get('email'),
|
||||
password: password
|
||||
};
|
||||
|
||||
const submitBtn = registerForm.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span>注册中...</span>';
|
||||
|
||||
try {
|
||||
const result = await apiRequest('/admin/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
// 设置 token cookie
|
||||
document.cookie = `token=${result.token}; path=/; max-age=86400; SameSite=Lax`;
|
||||
|
||||
// 跳转到管理页面
|
||||
window.location.href = '/admin';
|
||||
} catch (error) {
|
||||
showError(error.message || '注册失败');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时检查认证状态
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 如果已经在登录/注册页面,不需要检查
|
||||
if (window.location.pathname.includes('/admin/login') ||
|
||||
window.location.pathname.includes('/admin/register')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已登录
|
||||
checkAuth();
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
// 设置页面 JavaScript
|
||||
|
||||
// 加载数据库信息
|
||||
async function loadDatabaseInfo() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/database');
|
||||
const configData = await apiRequest('/admin/api/database/config');
|
||||
|
||||
document.getElementById('db-type').textContent = data.database.type;
|
||||
document.getElementById('db-status').textContent = data.database.status;
|
||||
document.getElementById('db-status').className = 'info-value status-badge status-connected';
|
||||
|
||||
if (data.database.file) {
|
||||
document.getElementById('db-file-item').style.display = 'flex';
|
||||
document.getElementById('db-file').textContent = data.database.file;
|
||||
}
|
||||
|
||||
// 如果是 MySQL,显示密码修改界面
|
||||
if (configData.config.type === 'mysql') {
|
||||
document.getElementById('database-password-section').style.display = 'block';
|
||||
}
|
||||
|
||||
// 操作系统信息
|
||||
document.getElementById('os-type').textContent = data.os.os;
|
||||
document.getElementById('os-arch').textContent = data.os.arch;
|
||||
} catch (error) {
|
||||
console.error('Load database info error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载系统信息
|
||||
async function loadSystemInfo() {
|
||||
try {
|
||||
const data = await apiRequest('/admin/api/system');
|
||||
document.getElementById('stat-users').textContent = data.users;
|
||||
document.getElementById('stat-routes').textContent = data.routes;
|
||||
document.getElementById('stat-logs').textContent = data.logs;
|
||||
document.getElementById('stat-time').textContent = data.server_time;
|
||||
} catch (error) {
|
||||
console.error('Load system info error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据库密码
|
||||
const passwordForm = document.getElementById('password-form');
|
||||
if (passwordForm) {
|
||||
passwordForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const currentPassword = document.getElementById('current-password').value;
|
||||
const newPassword = document.getElementById('new-password').value;
|
||||
const confirmPassword = document.getElementById('confirm-password').value;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert('新密码和确认密码不一致!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('确定要更新数据库 root 密码吗?\n\n更新后需要修改环境变量 DB_PASSWORD 并重启服务器!')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultEl = document.getElementById('password-result');
|
||||
resultEl.className = '';
|
||||
resultEl.textContent = '正在更新密码...';
|
||||
resultEl.style.display = 'block';
|
||||
|
||||
try {
|
||||
const result = await apiRequest('/admin/api/database/password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword,
|
||||
confirm_password: confirmPassword,
|
||||
}),
|
||||
});
|
||||
|
||||
resultEl.className = 'success';
|
||||
resultEl.textContent = result.message || '密码更新成功!';
|
||||
passwordForm.reset();
|
||||
} catch (error) {
|
||||
resultEl.className = 'error';
|
||||
resultEl.textContent = '更新失败: ' + error.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 转换数据库
|
||||
const convertDbBtn = document.getElementById('convert-db-btn');
|
||||
if (convertDbBtn) {
|
||||
convertDbBtn.addEventListener('click', async () => {
|
||||
const targetType = document.getElementById('target-db-type').value;
|
||||
|
||||
if (!confirm(`确定要转换数据库类型吗?\n\n目标类型: ${targetType.toUpperCase()}\n\n此操作会导出当前数据并导入到新数据库。请确保已备份数据!`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultEl = document.getElementById('convert-result');
|
||||
resultEl.className = '';
|
||||
resultEl.textContent = '正在转换...';
|
||||
resultEl.style.display = 'block';
|
||||
|
||||
try {
|
||||
const result = await apiRequest('/admin/api/database/convert', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
target_type: targetType,
|
||||
}),
|
||||
});
|
||||
|
||||
resultEl.className = 'success';
|
||||
resultEl.textContent = result.message || '数据库转换成功!';
|
||||
loadDatabaseInfo();
|
||||
} catch (error) {
|
||||
resultEl.className = 'error';
|
||||
resultEl.textContent = '转换失败: ' + error.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadDatabaseInfo();
|
||||
loadSystemInfo();
|
||||
|
||||
// 定期更新系统信息
|
||||
setInterval(loadSystemInfo, 30000); // 每30秒更新一次
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"comment": "English Language Pack",
|
||||
"nav.home": "Home",
|
||||
"nav.toolbox": "Toolbox",
|
||||
"nav.logs": "Logs",
|
||||
"nav.settings": "Settings",
|
||||
"home.greeting.morning": "Good morning, have a wonderful day.",
|
||||
"home.greeting.noon": "Good afternoon, time for a break.",
|
||||
"home.greeting.afternoon": "Good afternoon, keep up the good work.",
|
||||
"home.greeting.evening": "Good evening, time to relax.",
|
||||
"home.greeting.night": "It's late, please rest.",
|
||||
"home.welcome": "Welcome to YMhut Box, wish you a great day.",
|
||||
"home.announcement": "Announcement",
|
||||
"home.announcement.failed": "Failed to load announcement...",
|
||||
"home.announcement.viewFull": "View Full Announcement",
|
||||
"home.search.placeholder": "Smart Search: Enter query...",
|
||||
"home.search.button": "Search",
|
||||
"home.search.disabled": "Smart Search is currently disabled",
|
||||
"home.search.results.stats": "Found about {count} results (in {time}ms)",
|
||||
"home.search.results.viewFull": "View Full Results (incl. Advanced Options)",
|
||||
"home.search.results.empty.title": "No results found for \"{query}\"",
|
||||
"home.search.results.empty.sub": "Please try different keywords.",
|
||||
"home.search.failed.title": "Search Failed",
|
||||
"home.updates": "Update Log",
|
||||
"home.updates.close": "Close",
|
||||
"home.updates.modal.title": "Full Announcement",
|
||||
"tool.smartSearch.name": "Smart Search",
|
||||
"tool.smartSearch.desc": "AI aggregated search for high-quality results",
|
||||
"common.loading": "Loading...",
|
||||
"common.search": "Search",
|
||||
"common.backToToolbox": "Back to Toolbox",
|
||||
"common.options": "Advanced Options",
|
||||
"common.error": "Error",
|
||||
"common.loading.tool": "Loading tool module...",
|
||||
"common.notification.title.success": "Success",
|
||||
"common.notification.title.error": "Error",
|
||||
"common.notification.title.info": "Info",
|
||||
"settings.appearance": "Appearance",
|
||||
"settings.updates": "Update Management",
|
||||
"settings.about": "About",
|
||||
"settings.status.monitor": "Status Monitor",
|
||||
"settings.status.cpu": "CPU",
|
||||
"settings.status.mem": "Memory",
|
||||
"settings.status.gpu": "GPU",
|
||||
"settings.status.uptime": "Uptime",
|
||||
"settings.appearance.title": "Appearance Settings",
|
||||
"settings.appearance.theme": "Theme",
|
||||
"settings.appearance.language": "Language",
|
||||
"settings.appearance.language.auto": "Auto (Default)",
|
||||
"settings.appearance.language.zh-CN": "简体中文",
|
||||
"settings.appearance.language.en-US": "English",
|
||||
"settings.appearance.language.restartMsg": "Language change will take effect after restart.",
|
||||
"settings.appearance.bg": "Custom Background",
|
||||
"settings.appearance.bg.select": "Select",
|
||||
"settings.appearance.bg.clear": "Clear",
|
||||
"settings.appearance.bg.opacity": "Background Opacity",
|
||||
"settings.appearance.card.opacity": "Card Opacity",
|
||||
"settings.traffic.title": "Traffic Statistics",
|
||||
"settings.traffic.total": "Total Usage",
|
||||
"settings.traffic.chart.empty.title": "No traffic history",
|
||||
"settings.traffic.chart.empty.sub": "Data will be recorded starting today.",
|
||||
"settings.update.title": "Update Management",
|
||||
"settings.update.checkBtn": "Check for Updates",
|
||||
"settings.update.checking": "Checking...",
|
||||
"settings.update.checkDefault": "Click button to check for new version",
|
||||
"settings.about.title": "About & Environment",
|
||||
"settings.about.version": "Current Version",
|
||||
"settings.about.developer": "Developer",
|
||||
"settings.about.moreInfo": "More Info & Credits",
|
||||
"settings.about.env.title": "Installed Environments"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"comment": "简体中文语言包",
|
||||
"nav.home": "主页",
|
||||
"nav.toolbox": "工具箱",
|
||||
"nav.logs": "日志",
|
||||
"nav.settings": "设置",
|
||||
"home.greeting.morning": "早上好, 新的一天元气满满",
|
||||
"home.greeting.noon": "中午好, 午休时间到了",
|
||||
"home.greeting.afternoon": "下午好, 继续努力吧",
|
||||
"home.greeting.evening": "晚上好, 放松一下吧",
|
||||
"home.greeting.night": "凌晨了, 注意休息哦",
|
||||
"home.welcome": "欢迎使用 YMhut Box, 愿你拥有美好的一天。",
|
||||
"home.announcement": "公告",
|
||||
"home.announcement.failed": "公告加载失败...",
|
||||
"home.announcement.viewFull": "查看完整公告",
|
||||
"home.search.placeholder": "智能搜索:输入查询内容...",
|
||||
"home.search.button": "搜索",
|
||||
"home.search.disabled": "智能搜索工具当前不可用",
|
||||
"home.search.results.stats": "找到约 {count} 条结果 (耗时 {time}ms)",
|
||||
"home.search.results.viewFull": "查看完整结果 (含高级选项)",
|
||||
"home.search.results.empty.title": "未找到关于 \"{query}\" 的结果",
|
||||
"home.search.results.empty.sub": "请尝试更换关键词。",
|
||||
"home.search.failed.title": "搜索失败",
|
||||
"home.updates": "更新日志",
|
||||
"home.updates.close": "关闭",
|
||||
"home.updates.modal.title": "完整公告",
|
||||
"tool.smartSearch.name": "智能搜索",
|
||||
"tool.smartSearch.desc": "AI 聚合搜索,获取高质量结果",
|
||||
"common.loading": "加载中...",
|
||||
"common.search": "搜索",
|
||||
"common.backToToolbox": "返回工具箱",
|
||||
"common.options": "高级选项",
|
||||
"common.error": "错误",
|
||||
"common.loading.tool": "正在初始化工具模块...",
|
||||
"common.notification.title.success": "成功",
|
||||
"common.notification.title.error": "错误",
|
||||
"common.notification.title.info": "提示",
|
||||
"settings.appearance": "外观",
|
||||
"settings.updates": "更新管理",
|
||||
"settings.about": "关于",
|
||||
"settings.status.monitor": "状态监控",
|
||||
"settings.status.cpu": "CPU",
|
||||
"settings.status.mem": "内存",
|
||||
"settings.status.gpu": "GPU",
|
||||
"settings.status.uptime": "运行时长",
|
||||
"settings.appearance.title": "外观设置",
|
||||
"settings.appearance.theme": "界面主题",
|
||||
"settings.appearance.language": "语言 (Language)",
|
||||
"settings.appearance.language.auto": "自动 (Auto)",
|
||||
"settings.appearance.language.zh-CN": "简体中文",
|
||||
"settings.appearance.language.en-US": "English",
|
||||
"settings.appearance.language.restartMsg": "语言设置将在重启后生效。",
|
||||
"settings.appearance.bg": "自定义背景",
|
||||
"settings.appearance.bg.select": "选择",
|
||||
"settings.appearance.bg.clear": "清除",
|
||||
"settings.appearance.bg.opacity": "背景透明度",
|
||||
"settings.appearance.card.opacity": "卡片透明度",
|
||||
"settings.traffic.title": "流量统计",
|
||||
"settings.traffic.total": "累计使用流量",
|
||||
"settings.traffic.chart.empty.title": "暂无历史流量数据",
|
||||
"settings.traffic.chart.empty.sub": "数据将从今天开始记录",
|
||||
"settings.update.title": "更新管理",
|
||||
"settings.update.checkBtn": "检查更新",
|
||||
"settings.update.checking": "正在检查...",
|
||||
"settings.update.checkDefault": "点击按钮检查新版本",
|
||||
"settings.about.title": "关于与软件环境",
|
||||
"settings.about.version": "当前版本",
|
||||
"settings.about.developer": "开发者",
|
||||
"settings.about.moreInfo": "更多信息与鸣谢",
|
||||
"settings.about.env.title": "已安装的开发环境"
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"enabled": true,
|
||||
"icon": "fas fa-image",
|
||||
"id": "image",
|
||||
"layout": {
|
||||
"aspect_ratio": "16:9",
|
||||
"columns": 1,
|
||||
"show_preview": true,
|
||||
"transition_effect": "fade"
|
||||
},
|
||||
"name": "随机图片",
|
||||
"subcategories": [
|
||||
{
|
||||
"api_url": "https://xjj.ymhut.bid/xjj",
|
||||
"description": "精选小姐姐图片",
|
||||
"downloadable": true,
|
||||
"id": "xjj",
|
||||
"name": "小姐姐",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://pic2.zhimg.com/v2-379be37e0b4d372aa60046f9ce771f12_r.jpg"
|
||||
},
|
||||
{
|
||||
"api_url": "https://v2.xxapi.cn/api/baisi?return=302",
|
||||
"description": "随机白丝图片",
|
||||
"downloadable": true,
|
||||
"id": "baisi",
|
||||
"name": "白丝",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://n.sinaimg.cn/sinacn10112/760/w640h920/20200126/4b00-innckcf8208822.jpg"
|
||||
},
|
||||
{
|
||||
"api_url": "https://v2.xxapi.cn/api/heisi?return=302",
|
||||
"description": "随机黑丝图片",
|
||||
"downloadable": true,
|
||||
"id": "heisi",
|
||||
"name": "黑丝",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/static/6902725194a8c081767ee82373d3b017.jpeg"
|
||||
},
|
||||
{
|
||||
"api_url": "https://api.pearapi.ai/api/beautifulgirl?type=image",
|
||||
"description": "三坑少女图(包含动漫、漫画、游戏)",
|
||||
"downloadable": true,
|
||||
"id": "third_girl",
|
||||
"name": "三坑少女(4K)",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://www.sgpjbg.com/FileUpload/News/c358f121-6683-490b-beed-6debb44e4824.jpg"
|
||||
},
|
||||
{
|
||||
"api_url": "https://apii.ctose.cn/api/cy/api/",
|
||||
"description": "miku的随机图",
|
||||
"downloadable": true,
|
||||
"id": "miku",
|
||||
"name": "初音未来",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://apii.ctose.cn/api/cy/api/"
|
||||
},
|
||||
{
|
||||
"api_url": "https://api.suyanw.cn/api/mao.php",
|
||||
"description": "猫羽雫的随机图",
|
||||
"downloadable": true,
|
||||
"id": "猫羽雫",
|
||||
"name": "猫羽雫",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://api.suyanw.cn/api/mao.php"
|
||||
},
|
||||
{
|
||||
"api_url": "https://api.suyanw.cn/api/scenery.php",
|
||||
"description": "随机高清壁纸",
|
||||
"downloadable": true,
|
||||
"id": "wappller",
|
||||
"name": "高清壁纸",
|
||||
"refresh_interval": 30,
|
||||
"supported_formats": [
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
],
|
||||
"thumbnail_url": "https://api.suyanw.cn/api/scenery.php"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"icon": "fas fa-video",
|
||||
"id": "video",
|
||||
"layout": {
|
||||
"aspect_ratio": "16:9",
|
||||
"auto_play": false,
|
||||
"columns": 1,
|
||||
"show_preview": true,
|
||||
"transition_effect": "slide"
|
||||
},
|
||||
"name": "随机视频",
|
||||
"subcategories": [
|
||||
{
|
||||
"api_url": "https://dh.lt6.ltd/xjj/video.php",
|
||||
"description": "随机风格类型视频",
|
||||
"downloadable": true,
|
||||
"id": "radom_xjj_leixing",
|
||||
"name": "小姐姐不同风格视频",
|
||||
"refresh_interval": 60,
|
||||
"supported_formats": [
|
||||
"mp4",
|
||||
"webm"
|
||||
],
|
||||
"thumbnail_url": "https://n.sinaimg.cn/sinacn19/176/w888h888/20181119/0c26-hmhhnqt1050818.jpg"
|
||||
},
|
||||
{
|
||||
"api_url": "https://api.mmp.cc/api/miss?type=mp4",
|
||||
"description": "随机风格小姐姐的视频",
|
||||
"downloadable": true,
|
||||
"id": "radom_xjj_short",
|
||||
"name": "短视频",
|
||||
"refresh_interval": 60,
|
||||
"supported_formats": [
|
||||
"mp4",
|
||||
"webm"
|
||||
],
|
||||
"thumbnail_url": "https://weather-real.oss-cn-shanghai.aliyuncs.com/weather/2025-06-17/1750091559255t7FNOX.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-09-9T17:45:00Z",
|
||||
"layout_version": "1.0.6",
|
||||
"ui_config": {
|
||||
"animations": {
|
||||
"duration": 300,
|
||||
"transition_effect": "fade"
|
||||
},
|
||||
"dark_mode": false,
|
||||
"default_view": "grid",
|
||||
"show_thumbnails": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"modules": []
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"ai-translation": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"baidu-hot": {
|
||||
"enabled": true,
|
||||
"message": "百度热榜接口正在维护,预计短时间内不会恢复。"
|
||||
},
|
||||
"bili-hot-ranking": {
|
||||
"enabled": true,
|
||||
"message": "B站热搜接口正在维护,预计短时间内不会恢复。"
|
||||
},
|
||||
"comment": "工具状态控制文件。 'enabled: false' 将禁用该工具。",
|
||||
"comment_screening_room": "随机放映室使用 '分类ID.子分类ID' 作为键",
|
||||
"dns-query": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"image.baisi": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"image.heisi": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"image.wappller": {
|
||||
"enabled": true,
|
||||
"message": "高清壁纸 暂时下线,请先浏览其他分类。"
|
||||
},
|
||||
"image.xjj": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"image.猫羽雫": {
|
||||
"enabled": true,
|
||||
"message": "猫羽雫 暂时下线,请先浏览其他分类。"
|
||||
},
|
||||
"ip-info": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"ip-query": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"smart-search": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
},
|
||||
"video.radom_xjj_leixing": {
|
||||
"enabled": true,
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"api_keys": {
|
||||
"uapipro": ""
|
||||
},
|
||||
"app_version": "2.0.6.2",
|
||||
"build": "2",
|
||||
"channel": "stable",
|
||||
"title": "YMhut Box 2.0.6.2",
|
||||
"message": "本版本重点修复覆盖安装后白屏退出、用户目录 runtime 占用、语言包膨胀和设置页初始化问题,并继续完善 WinUI 3 工具型工作台体验。",
|
||||
"message_md": "# YMhut Box 2.0.6.2\n\n本版本继续收尾 WinUI 3 工具型工作台:修复覆盖安装/直启稳定性、用户目录 runtime 残留、自检结果页卡顿、排行榜/资讯显示、中文日志和设置页初始化问题,并新增安装器输出框、Markdown 公告、媒体播放器与随机放映室增强、价格/指标图表化展示。",
|
||||
"release_notes": "修复 EXE/latest 直启和覆盖安装后因语言资源布局导致的白屏退出;发布布局改为纯 lang\\zh-CN 与 lang\\en-US,移除多余语言包和旧 resources\\lang 压缩依赖;启动与自检链路禁止在用户数据目录保存 Runtime/runtime/Runtimes/runtimes 等运行时副本,旧残留会在启动和安装时清理;完善启动自检、安装完整性检查、服务状态结果页、工具箱与工具详情布局、结果/原始输出渲染、排行榜/资讯结构化显示、设置页控制中心和系统概况实时图表;修复设置页初始化失败、中文模式英文漏出、部分日志英文展示、天气胶囊图标缺失以及关闭确认记住选择等问题。",
|
||||
"release_notes_md": "## Bug 修复\n\n- 修复 EXE/latest 直启和覆盖安装后因语言资源布局导致的白屏退出,失败时写入清晰日志并显示可读错误。\n- 杜绝用户数据目录生成 Runtime/runtime/Runtimes/runtimes 或完整程序 payload 副本,旧残留会在启动和安装时清理。\n- 修复自检结果页加载大量历史明细时卡顿或短暂无响应的问题,改为确认后摘要优先、明细按需加载。\n- 修复中文模式下部分日志仍显示英文的问题,错误码、HTTP 状态和反馈状态仍保留必要原文。\n- 修复排行榜、热榜和资讯类工具被“已隐藏远程地址,仅展示脱敏来源名称”提示干扰后无法生成卡片的问题,同时继续隐藏远程 URL。\n- 修复设置页初始化失败、关闭确认记住选择、天气胶囊部分状态缺少动画图标等问题。\n\n## 支持增强\n\n- 发布布局统一为纯 `lang\\zh-CN` / `lang\\en-US`,移除多余语言包、根目录 culture 目录和旧 `resources\\lang` 压缩依赖。\n- 客户端公告、首页公告、关于页更新弹窗和更新日志弹窗支持 Markdown 标题、列表、表格、代码与链接,解析失败时回退纯文本。\n- 随机放映室支持远程媒体重定向解析,并为图片、视频、音频加载提供进度提示。\n- 数据类工具增强价格/指标结果展示,黄金价格等结果可同时显示摘要、表格和趋势折线图。\n\n## 新增能力\n\n- 安装引导程序在提取文件阶段新增只读输出框,展示旧版本清理、payload 提取、依赖检查和安装收尾进度。\n- 新增启动/安装完整性自检结果入口,服务状态页可确认后查看结果、复制摘要和导出 JSON。\n- 媒体播放器补齐播放列表、常用播放控制、倍速、音量、全屏、图片/视频/音频混播和常见系统解码格式入口。\n- 随机放映室迁移旧版展示思路,改为图片、视频、音频三段式远程媒体浏览。\n\n## 体验重构\n\n- 工具箱与工具详情页继续向高密度 WinUI 工具工作台收束,结果区固定提供“结果”和“原始输出”。\n- 设置控制中心增加分页滚动提示,系统概况图表补齐网格、坐标和实时信息。\n- 主题继续参考 Microsoft Store 与 Windows 媒体播放器的中性 Fluent 风格,不使用渐变,蓝色为主强调,橙/红仅用于警示。\n- 随机放映室和播放器 UI 更接近 Win11 媒体体验,加载、失败、保存、全屏等状态更清晰。",
|
||||
"category_list": [
|
||||
{
|
||||
"icon": "monitor",
|
||||
"id": "system",
|
||||
"name": "系统工具"
|
||||
},
|
||||
{
|
||||
"icon": "code",
|
||||
"id": "developer",
|
||||
"name": "开发工具"
|
||||
},
|
||||
{
|
||||
"icon": "image",
|
||||
"id": "image",
|
||||
"name": "图像工具"
|
||||
}
|
||||
],
|
||||
"detected_product": "YMhut Box",
|
||||
"detected_packages": {
|
||||
"YMhut Box": [
|
||||
{
|
||||
"version": "2.0.6.2",
|
||||
"extension": "exe",
|
||||
"fileName": "YMhut_Box_WinUI_Setup_2.0.6.2.exe",
|
||||
"downloadPath": "/downloads/YMhut_Box_WinUI_Setup_2.0.6.2.exe",
|
||||
"size": "待发布",
|
||||
"sizeBytes": 0,
|
||||
"updateDate": "2026-06-14",
|
||||
"updateTime": "2026-06-14 00:00:00"
|
||||
},
|
||||
{
|
||||
"version": "2.0.6.2",
|
||||
"extension": "exe",
|
||||
"fileName": "YMhut_Box_WinUI_Setup_2.0.6.2_Light.exe",
|
||||
"downloadPath": "/downloads/YMhut_Box_WinUI_Setup_2.0.6.2_Light.exe",
|
||||
"size": "待发布",
|
||||
"sizeBytes": 0,
|
||||
"updateDate": "2026-06-14",
|
||||
"updateTime": "2026-06-14 00:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"download_mirrors": [
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "primary",
|
||||
"name": "官方直连",
|
||||
"sha256": "",
|
||||
"type": "direct",
|
||||
"url": "https://update.ymhut.cn/downloads/YMhut_Box_WinUI_Setup_2.0.6.2.exe"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "light",
|
||||
"name": "轻量安装包",
|
||||
"sha256": "",
|
||||
"type": "direct",
|
||||
"url": "https://update.ymhut.cn/downloads/YMhut_Box_WinUI_Setup_2.0.6.2_Light.exe"
|
||||
}
|
||||
],
|
||||
"download_url": "https://update.ymhut.cn/downloads/YMhut_Box_WinUI_Setup_2.0.6.2.exe",
|
||||
"home_notes": "v2.0.6.2 聚焦安装布局、启动稳定性和 WinUI 工作台体验:覆盖安装会清理旧程序布局和 runtime 残留,用户目录只保留设置、日志、缓存和轻量状态;语言资源只保留中英双语并归入 lang;工具箱、工具详情、结果渲染、排行榜/资讯卡片、设置控制中心、系统概况和服务状态页继续向原生 Fluent 工具软件风格收束。",
|
||||
"last_update_notes": {
|
||||
"v2.0.5.3 稳定性": "延续启动首页、插件扫描、反馈中心和日志展示修复;本版本进一步处理覆盖安装、语言资源和用户目录 runtime 残留。",
|
||||
"v2.0.5.3 安装包体积": "上一版新增轻量安装包通道;本版本继续收束语言包和旧资源布局,避免无关文件混入发布目录。",
|
||||
"v2.0.5.3 设置外观": "上一版加入窗口材质设置;本版本继续完善设置页控制中心、系统概况实时图表和中文本地化。"
|
||||
},
|
||||
"last_updated": "2026-06-14T00:00:00Z",
|
||||
"tool_metadata": {},
|
||||
"update_notes": {
|
||||
"启动与覆盖安装": "修复覆盖安装后启动白屏或应用自行退出的问题;旧压缩语言布局不再回退复制到用户目录,失败时写入清晰日志并显示可读错误。",
|
||||
"用户目录瘦身": "用户数据目录不再生成 Runtime、runtime、Runtimes 或 runtimes 文件夹;启动、自检和安装器都会清理旧 runtime 残留,避免大型运行时副本占用本机存储。",
|
||||
"语言资源布局": "发布布局统一为纯 lang,保留 lang\\zh-CN 与 lang\\en-US;移除其他语言包、根目录 culture 目录和 resources\\lang 压缩残留。",
|
||||
"启动自检": "迁移并改造旧版启动初始化思路:快速预检负责用户目录、设置、数据库、下载队列、安装根和关键资源;月度完整性检查在窗口可用后后台运行。",
|
||||
"自检结果页": "服务状态页新增启动自检与安装完整性入口;查看结果前增加确认提示和加载进度,历史先加载摘要,用户选择后再读取完整明细,降低大量结果渲染造成的卡顿。",
|
||||
"排行榜与资讯": "修复部分排行榜、热榜和资讯类工具因“已隐藏远程地址,仅展示脱敏来源名称”提示混入结构化解析而无法显示卡片内容的问题,同时继续隐藏远程地址。",
|
||||
"工具箱与工具详情": "工具箱改为更高密度的原生 WinUI 工作台布局;工具内容区域按功能类型优化输入、结果、原始输出、复制、搜索和导出体验。",
|
||||
"设置页": "修复设置页初始化失败;控制中心分页限制为 5 项可视并增加动态上下箭头提示;系统概况加入网格坐标、暗色绘图区和更多实时系统信息。",
|
||||
"主题与视觉": "主题改为微软商店/媒体播放器式中性 Fluent 风格,移除渐变主视觉,蓝色作为主强调色,橙红仅用于更新、警告和风险行为。",
|
||||
"天气胶囊": "补齐阴天、未知、离线等状态的基础图标和轻量动效,遵守关闭动画与高对比度设置。",
|
||||
"本地化与日志": "修复工具箱与安全、风险确认、默认工具范围、设置弹窗和高频日志的中文模式英文漏出;反馈码和原始错误信息仍保留必要英文。"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user