first commit

This commit is contained in:
hero920103 committed 2026-04-15 22:56:44 +08:00
commit 40a78f0032
77 files changed
+102842

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
/* 关于页 - 支持作者卡片 */
.about-donate-card {
max-width: 760px;
margin: 0 auto;
text-align: center;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 2rem 1.5rem;
box-shadow: var(--shadow-sm);
}
.about-donate-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-color);
margin: 0 0 1.25rem;
}
.about-donate-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 2rem;
align-items: stretch;
}
.about-donate-item {
display: flex;
justify-content: center;
align-items: center;
min-height: 360px;
}
.about-donate-img {
width: 100%;
max-width: 300px;
max-height: 360px;
object-fit: contain;
object-position: center;
border-radius: 10px;
display: block;
margin: 0 auto;
}
.about-donate-sub {
font-size: 0.82rem;
color: var(--text-secondary);
margin: 1rem 0 0;
}
@media (max-width: 768px) {
.about-donate-card {
max-width: 420px;
}
.about-donate-grid {
grid-template-columns: 1fr;
}
.about-donate-item {
min-height: auto;
}
.about-donate-img {
max-width: 300px;
max-height: none;
}
}
File diff suppressed because it is too large. Load diff
+241
View File
@@ -0,0 +1,241 @@
/* ================================
管理员功能样式 - 用户管理和数据管理
================================ */
/* 用户管理样式 */
.user-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: 1px solid var(--border-color);
border-radius: 12px;
}
.user-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.user-card .card-body {
padding: 1.25rem;
}
.user-card .badge {
font-size: 0.75rem;
padding: 0.375rem 0.75rem;
}
/* 数据管理样式 */
.table-container {
max-height: 600px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: 8px;
}
.table th {
position: sticky;
top: 0;
background: var(--dark-color);
color: white;
z-index: 10;
border: none;
font-weight: 600;
font-size: 0.875rem;
padding: 0.75rem;
}
.table td {
padding: 0.75rem;
font-size: 0.875rem;
border-bottom: 1px solid var(--border-color);
vertical-align: middle;
}
.table tbody tr:hover {
background-color: rgba(79, 70, 229, 0.05);
}
/* 数据统计卡片 */
.data-stats {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
color: white;
border: none;
}
.data-stats .card-body {
padding: 0.75rem;
}
/* 表格状态样式 */
#loadingTable,
#noTableSelected,
#noTableData {
padding: 3rem 1rem;
}
#loadingTable .spinner-border {
width: 3rem;
height: 3rem;
border-width: 0.3em;
}
#noTableSelected i,
#noTableData i {
font-size: 4rem;
color: var(--secondary-color);
opacity: 0.5;
}
/* 按钮组样式 */
.btn-group .btn {
border-radius: 0;
}
.btn-group .btn:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.btn-group .btn:last-child {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
/* 表格选择器样式 */
#tableSelect {
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 0.75rem;
font-size: 0.875rem;
transition: border-color 0.2s ease;
}
#tableSelect:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(79, 70, 229, 0.25);
}
/* 记录计数样式 */
#recordCount {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
}
/* 响应式调整 */
@media (max-width: 768px) {
.user-card {
margin-bottom: 1rem;
}
.table-container {
max-height: 400px;
}
.table th,
.table td {
padding: 0.5rem;
font-size: 0.8rem;
}
.btn-group {
flex-direction: column;
}
.btn-group .btn {
border-radius: 6px !important;
margin-bottom: 0.25rem;
}
}
/* 加载状态动画 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.user-card,
.table-container {
animation: fadeIn 0.3s ease-out;
}
/* 表格滚动条样式 */
.table-container::-webkit-scrollbar {
width: 8px;
}
.table-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-container::-webkit-scrollbar-thumb {
background: var(--secondary-color);
border-radius: 4px;
}
.table-container::-webkit-scrollbar-thumb:hover {
background: var(--primary-color);
}
/* 文本截断样式 */
.text-truncate-custom {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
/* 状态徽章样式 */
.status-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 12px;
}
/* 工具提示样式增强 */
[title] {
cursor: help;
}
/* 空状态图标样式 */
.empty-state-icon {
font-size: 4rem;
color: var(--secondary-color);
opacity: 0.3;
margin-bottom: 1rem;
}
/* 操作按钮样式 */
.action-buttons .btn {
margin: 0 0.125rem;
padding: 0.375rem 0.75rem;
}
.action-buttons .btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
/* 统计卡片渐变背景 */
.stats-card-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
}
.stats-card-info {
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
}
.stats-card-success {
background: linear-gradient(135deg, var(--success-color) 0%, #0d9488 100%);
}
.stats-card-warning {
background: linear-gradient(135deg, var(--warning-color) 0%, #d97706 100%);
}
+13
View File
@@ -0,0 +1,13 @@
@import url('variables.css');
@import url('layout.css');
@import url('dashboard.css');
@import url('logs.css');
@import url('keywords.css');
@import url('accounts.css');
@import url('items.css');
@import url('notifications.css');
@import url('components.css');
@import url('about.css');
@import url('admin.css');
+848
View File
@@ -0,0 +1,848 @@
/* ================================
通用卡片样式 - 适用于所有菜单的卡片
================================ */
/* 旋转动画 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spin {
animation: spin 1s linear infinite;
}
.card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
margin-bottom: 2rem;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.card-header {
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
color: white;
border: none;
border-radius: 16px 16px 0 0 !important;
font-weight: 600;
font-size: 1.1rem;
padding: 1.25rem 1.5rem;
}
.card-body {
padding: 1.5rem;
}
.btn {
border-radius: 10px;
font-weight: 500;
transition: all 0.3s ease;
padding: 0.5rem 1rem;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
border: none;
}
.btn-success {
background: linear-gradient(135deg, var(--success-color), #059669);
border: none;
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), #dc2626);
border: none;
}
.btn-outline-primary {
border: 2px solid var(--primary-color);
color: var(--primary-color);
}
.btn-outline-primary:hover {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.btn-outline-success {
border: 2px solid var(--success-color);
color: var(--success-color);
}
.btn-outline-success:hover {
background: var(--success-color);
border-color: var(--success-color);
color: white;
}
.btn-outline-info {
border: 2px solid #0dcaf0;
color: #0dcaf0;
}
.btn-outline-info:hover {
background: #0dcaf0;
border-color: #0dcaf0;
color: white;
}
.btn-outline-danger {
border: 2px solid var(--danger-color);
color: var(--danger-color);
}
.btn-outline-danger:hover {
background: var(--danger-color);
border-color: var(--danger-color);
color: white;
}
/* ================================
通用表格样式 - 适用于所有菜单的表格
================================ */
.table {
margin-bottom: 0;
background: transparent;
}
.table th {
border-top: none;
border-bottom: 2px solid var(--border-color);
font-weight: 600;
color: var(--dark-color);
background: rgba(79, 70, 229, 0.05);
padding: 1rem;
}
.table td {
padding: 1rem;
border-bottom: 1px solid var(--border-color);
vertical-align: middle;
}
.table tbody tr:hover {
background: rgba(79, 70, 229, 0.02);
}
.badge {
font-weight: 500;
padding: 5px 10px;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.cookie-value {
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
font-size: 0.85rem;
background: #f8fafc;
padding: 0.75rem;
border-radius: 8px;
border: 1px solid #e5e7eb;
word-break: break-all;
line-height: 1.4;
max-height: 120px;
overflow-y: auto;
white-space: pre-wrap;
}
.loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(248, 250, 252, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
pointer-events: none;
}
.loading-spinner {
width: 3rem;
height: 3rem;
}
[data-theme="dark"] .loading {
background-color: rgba(15, 23, 42, 0.45);
}
.keyword-editor {
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.modal-content {
border-radius: 10px;
border: none;
}
.modal-header {
border-bottom: 1px solid rgba(0,0,0,0.05);
}
.modal-footer {
border-top: 1px solid rgba(0,0,0,0.05);
}
.cookie-id {
font-weight: 600;
color: var(--primary-color);
font-size: 1rem;
}
.empty-state {
text-align: center;
padding: 3rem 2rem;
color: var(--secondary-color);
}
.empty-state i {
opacity: 0.5;
}
.form-control, .form-select {
border-radius: 10px;
border: 2px solid var(--border-color);
transition: all 0.3s ease;
padding: 0.75rem 1rem;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
transform: translateY(-1px);
}
.form-label {
font-weight: 600;
color: var(--dark-color);
margin-bottom: 0.5rem;
}
/* ================================
通用模态框样式 - 适用于所有菜单的模态框
================================ */
.modal-content {
border: none;
border-radius: 16px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
backdrop-filter: blur(10px);
}
.modal-header {
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
color: white;
border: none;
border-radius: 16px 16px 0 0;
}
.toast-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
}
.toast {
border: none;
border-radius: 12px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
backdrop-filter: blur(10px);
}
.btn-group .btn {
margin-right: 0.25rem;
}
.btn-group .btn:last-child {
margin-right: 0;
}
/* ================================
AI回复配置 - 全量界面视觉增强
================================ */
#aiReplyConfigModal .modal-dialog {
max-width: 900px;
}
#aiReplyConfigModal .ai-reply-content {
overflow: hidden;
}
#aiReplyConfigModal .ai-reply-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
#aiReplyConfigModal .ai-reply-title-group {
display: flex;
flex-direction: column;
gap: 2px;
}
#aiReplyConfigModal .ai-reply-subtitle {
font-size: 0.82rem;
color: rgba(255, 255, 255, 0.84);
}
#aiReplyConfigModal .ai-reply-form {
display: flex;
flex-direction: column;
gap: 12px;
}
#aiReplyConfigModal .ai-modal-intro {
display: flex;
gap: 12px;
align-items: flex-start;
padding: 12px 14px;
border-radius: 14px;
border: 1px solid rgba(79, 70, 229, 0.16);
background: linear-gradient(120deg, rgba(99, 102, 241, 0.1), rgba(6, 182, 212, 0.1));
}
#aiReplyConfigModal .ai-intro-icon {
width: 30px;
height: 30px;
border-radius: 8px;
background: rgba(79, 70, 229, 0.16);
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
#aiReplyConfigModal .ai-intro-title {
font-weight: 700;
color: var(--dark-color);
line-height: 1.2;
}
#aiReplyConfigModal .ai-intro-text {
font-size: 0.86rem;
color: var(--secondary-color);
margin-top: 2px;
}
#aiReplyConfigModal .ai-section-card {
border-radius: 16px;
overflow: hidden;
}
#aiReplyConfigModal .ai-section-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
#aiReplyConfigModal .ai-section-title {
display: flex;
flex-direction: column;
}
#aiReplyConfigModal .ai-section-desc {
font-size: 0.76rem;
opacity: 0.88;
}
#aiReplyConfigModal .ai-section-tag {
font-size: 0.68rem;
padding: 4px 10px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.45);
letter-spacing: 0.02em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.92);
}
#aiReplyConfigModal .ai-basic-card .card-body {
background:
radial-gradient(circle at right top, rgba(79, 70, 229, 0.09), transparent 45%),
radial-gradient(circle at left bottom, rgba(6, 182, 212, 0.09), transparent 42%);
}
#aiReplyConfigModal .ai-basic-overview {
display: grid;
grid-template-columns: 1.25fr 1fr;
gap: 12px;
}
#aiReplyConfigModal .ai-account-panel,
#aiReplyConfigModal .ai-enable-panel,
#aiReplyConfigModal .ai-config-block,
#aiReplyConfigModal .ai-config-grid {
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(79, 70, 229, 0.14);
border-radius: 14px;
padding: 14px;
}
#aiReplyConfigModal .ai-account-field {
display: flex;
align-items: center;
gap: 10px;
}
#aiReplyConfigModal .ai-account-field i {
width: 34px;
height: 34px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
color: #fff;
font-size: 1rem;
flex-shrink: 0;
}
#aiReplyConfigModal .ai-account-field .form-control {
font-weight: 600;
}
#aiReplyConfigModal .ai-enable-panel {
display: flex;
align-items: center;
}
#aiReplyConfigModal .ai-enable-switch {
margin: 0;
width: 100%;
}
#aiReplyConfigModal .ai-enable-switch .form-check-input {
width: 2.7rem;
height: 1.4rem;
}
#aiReplyConfigModal .ai-config-stack {
display: flex;
flex-direction: column;
gap: 12px;
}
#aiReplyConfigModal .ai-preset-controls {
display: flex;
align-items: center;
gap: 10px;
}
#aiReplyConfigModal .ai-preset-controls .form-select {
flex: 1;
}
#aiReplyConfigModal .ai-preset-actions {
display: flex;
gap: 8px;
flex-shrink: 0;
}
#aiReplyConfigModal .ai-config-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
#aiReplyConfigModal .ai-config-item {
min-width: 0;
}
#aiReplyConfigModal .ai-config-item-wide {
grid-column: 1 / -1;
}
#aiReplyConfigModal .ai-endpoint-preview {
margin-top: 8px;
padding: 8px 10px;
border-radius: 10px;
border: 1px dashed rgba(16, 185, 129, 0.45);
background: rgba(16, 185, 129, 0.07);
color: #047857;
font-family: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
font-size: 0.82rem;
line-height: 1.4;
word-break: break-all;
min-height: 34px;
}
#aiReplyConfigModal .ai-bargain-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
#aiReplyConfigModal .ai-metric-card {
border-radius: 12px;
border: 1px solid rgba(79, 70, 229, 0.14);
background: rgba(255, 255, 255, 0.76);
padding: 12px;
}
#aiReplyConfigModal .ai-prompt-tip {
border-radius: 12px;
border: 1px dashed rgba(14, 165, 233, 0.42);
background: rgba(14, 165, 233, 0.08);
color: #0369a1;
font-size: 0.86rem;
padding: 10px 12px;
}
#aiReplyConfigModal .ai-prompt-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
#aiReplyConfigModal .ai-prompt-card {
border-radius: 12px;
border: 1px solid rgba(79, 70, 229, 0.14);
background: rgba(255, 255, 255, 0.76);
padding: 12px;
}
#aiReplyConfigModal .ai-prompt-card-wide {
grid-column: 1 / -1;
}
#aiReplyConfigModal .ai-prompt-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 2px;
}
#aiReplyConfigModal .ai-prompt-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
#aiReplyConfigModal .ai-prompt-dot-price {
background: #f59e0b;
}
#aiReplyConfigModal .ai-prompt-dot-tech {
background: #06b6d4;
}
#aiReplyConfigModal .ai-prompt-dot-default {
background: #6366f1;
}
#aiReplyConfigModal .ai-prompt-textarea {
height: 132px;
min-height: 132px;
resize: vertical;
}
#aiReplyConfigModal .ai-test-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
#aiReplyConfigModal .ai-test-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 2px;
}
#aiReplyConfigModal .ai-test-btn {
min-width: 124px;
}
#aiReplyConfigModal .ai-test-result .alert {
border-radius: 12px;
border: 1px solid rgba(59, 130, 246, 0.28);
}
#aiReplyConfigModal .ai-reply-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
#aiReplyConfigModal .ai-footer-note {
font-size: 0.82rem;
color: var(--secondary-color);
}
#aiReplyConfigModal .ai-footer-actions {
display: flex;
gap: 8px;
}
@media (max-width: 768px) {
#aiReplyConfigModal .ai-reply-header {
align-items: center;
}
#aiReplyConfigModal .ai-reply-subtitle {
display: none;
}
#aiReplyConfigModal .ai-modal-intro {
padding: 10px 12px;
}
#aiReplyConfigModal .ai-bargain-grid,
#aiReplyConfigModal .ai-prompt-grid,
#aiReplyConfigModal .ai-test-grid,
#aiReplyConfigModal .ai-basic-overview,
#aiReplyConfigModal .ai-config-grid {
grid-template-columns: 1fr;
}
#aiReplyConfigModal .ai-preset-controls {
flex-direction: column;
align-items: stretch;
}
#aiReplyConfigModal .ai-preset-actions {
justify-content: flex-end;
}
#aiReplyConfigModal .ai-test-actions,
#aiReplyConfigModal .ai-reply-footer {
flex-direction: column;
align-items: stretch;
}
#aiReplyConfigModal .ai-footer-actions {
justify-content: flex-end;
}
}
@media (max-width: 768px) {
.container {
margin-top: 1rem;
padding: 0 1rem;
}
.card-body {
padding: 1rem;
}
.btn {
font-size: 0.875rem;
padding: 0.375rem 0.75rem;
}
.cookie-value {
font-size: 0.75rem;
max-height: 80px;
}
.btn-group {
flex-direction: column;
}
.btn-group .btn {
margin-right: 0;
margin-bottom: 0.25rem;
}
/* 移动端商品表格优化 */
#itemsTableBody .btn-group {
flex-direction: row;
}
#itemsTableBody .btn-group .btn {
padding: 0.2rem 0.4rem;
font-size: 0.75rem;
}
}
/* ================================
API参数提示样式
================================ */
.param-item {
display: flex;
align-items: center;
padding: 0.25rem 0;
}
.param-name {
background-color: #f8f9fa;
color: #495057;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
font-weight: 500;
border: 1px solid #dee2e6;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
display: inline-block;
}
.param-name:hover {
background-color: #e9ecef;
border-color: #adb5bd;
transform: translateY(-1px);
}
#postParamsHelp,
#editPostParamsHelp {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
}
#postParamsHelp .alert,
#editPostParamsHelp .alert {
margin-bottom: 0;
border-radius: 6px;
}
/* 参数项点击效果 */
.param-name:active {
transform: translateY(0);
background-color: #dee2e6;
}
/* ================================
API参数提示样式
================================ */
.param-item {
display: flex;
align-items: center;
padding: 0.25rem 0;
}
.param-name {
background-color: #f8f9fa;
color: #495057;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
font-weight: 500;
border: 1px solid #dee2e6;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
display: inline-block;
}
.param-name:hover {
background-color: #e9ecef;
border-color: #adb5bd;
transform: translateY(-1px);
}
#postParamsHelp,
#editPostParamsHelp {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
}
#postParamsHelp .alert,
#editPostParamsHelp .alert {
margin-bottom: 0;
border-radius: 6px;
}
/* 参数项点击效果 */
.param-name:active {
transform: translateY(0);
background-color: #dee2e6;
}
/* ================================
菜单排序列表样式
================================ */
.menu-sort-list {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 500px;
overflow-y: auto;
}
.menu-sort-item {
display: flex;
align-items: center;
padding: 10px 15px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
cursor: default;
transition: all 0.2s ease;
}
.menu-sort-item:hover {
background: #e9ecef;
}
.menu-sort-item.dragging {
opacity: 0.5;
background: #d1d5db;
border-style: dashed;
}
.menu-sort-item.drag-over {
border-color: var(--primary-color);
background: rgba(79, 70, 229, 0.1);
}
.menu-sort-item .drag-handle {
cursor: grab;
padding: 5px 10px 5px 0;
color: #6b7280;
font-size: 1.2rem;
}
.menu-sort-item .drag-handle:active {
cursor: grabbing;
}
.menu-sort-item .menu-icon {
width: 24px;
margin-right: 10px;
color: var(--primary-color);
}
.menu-sort-item .menu-name {
flex: 1;
font-weight: 500;
}
.menu-sort-item .menu-checkbox {
margin-left: 10px;
}
.menu-sort-item .badge {
margin-left: 8px;
font-size: 0.7rem;
}
File diff suppressed because it is too large. Load diff
+52
View File
@@ -0,0 +1,52 @@
/* ================================
【商品管理菜单】表格样式
================================ */
#itemsTableBody .btn-group {
white-space: nowrap;
}
#itemsTableBody .btn-group .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
#itemsTableBody .btn-group .btn:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
#itemsTableBody .btn-group .btn:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 0;
}
#itemsTableBody .btn-group .btn i {
font-size: 0.875rem;
}
/* 表格操作列样式 */
.table td:last-child {
text-align: center;
vertical-align: middle;
padding: 0.5rem 0.25rem;
}
/* 表格文本截断优化 */
.table td {
vertical-align: middle;
}
.table td[title] {
cursor: help;
}
/* 商品表格特定优化 */
#itemsTableBody td:nth-child(4),
#itemsTableBody td:nth-child(5) {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
+940
View File
@@ -0,0 +1,940 @@
/* 关键词管理现代化样式 */
.keyword-container {
background: white;
border-radius: 16px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: all 0.3s ease;
}
.keyword-header {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
color: white;
padding: 1.5rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.keyword-header h3 {
margin: 0;
font-weight: 600;
display: flex;
align-items: center;
}
.keyword-header .account-badge {
background: rgba(255, 255, 255, 0.2);
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
}
.keyword-input-area {
padding: 2rem;
background: #f8fafc;
border-bottom: 1px solid #e5e7eb;
}
.keyword-input-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
.keyword-input-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.keyword-btn-row {
display: flex;
gap: 0.75rem;
}
.input-field {
position: relative;
}
.input-field label {
position: absolute;
top: -0.5rem;
left: 0.75rem;
background: #f8fafc;
padding: 0 0.5rem;
font-size: 0.75rem;
font-weight: 500;
color: #6b7280;
z-index: 1;
}
.input-field input, .input-field select, .input-field textarea {
width: 100%;
padding: 1rem 0.75rem 0.75rem;
border: 2px solid #e5e7eb;
border-radius: 12px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: white;
}
.input-field input:focus, .input-field select:focus, .input-field textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.input-field textarea {
resize: vertical;
font-family: inherit;
line-height: 1.5;
}
/* 多选下拉框样式 */
.input-field select[multiple] {
min-height: 120px;
padding: 0.5rem;
}
.input-field select[multiple] option {
padding: 0.5rem 0.75rem;
border-radius: 6px;
margin-bottom: 4px;
cursor: pointer;
}
.input-field select[multiple] option:hover {
background-color: #f3f4f6;
}
.input-field select[multiple] option:checked {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 500;
}
.add-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
border-radius: 12px;
padding: 1rem 1.5rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.add-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.3);
}
.add-btn:active {
transform: translateY(0);
}
/* ================================
【自动回复菜单】相关样式
================================ */
.keywords-list {
padding: 1.5rem 2rem 2rem;
max-height: 500px;
overflow-y: auto;
}
.keyword-item {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
margin-bottom: 1rem;
transition: all 0.3s ease;
overflow: hidden;
}
.keyword-item:hover {
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
transform: translateY(-1px);
}
.keyword-item-header {
padding: 1rem 1.5rem;
background: #f8fafc;
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-between;
}
.keyword-tag {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
.keyword-actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
width: 36px;
height: 36px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.edit-btn {
background: #f3f4f6;
color: #6b7280;
}
.edit-btn:hover {
background: #e5e7eb;
color: #374151;
}
.edit-btn-disabled {
background: #f9fafb !important;
color: #9ca3af !important;
cursor: not-allowed !important;
}
.edit-btn-disabled:hover {
background: #f9fafb !important;
color: #9ca3af !important;
}
.delete-btn {
background: #fef2f2;
color: #ef4444;
}
.delete-btn:hover {
background: #fee2e2;
color: #dc2626;
}
.keyword-content {
padding: 1.5rem;
}
.reply-text {
color: #374151;
line-height: 1.6;
margin: 0;
font-size: 0.875rem;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: #6b7280;
}
.empty-state i {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.empty-state h3 {
margin: 0 0 0.5rem;
color: #374151;
}
.empty-state p {
margin: 0 0 2rem;
font-size: 0.875rem;
}
.quick-add-btn {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
color: white;
border: none;
border-radius: 12px;
padding: 0.75rem 1.5rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.quick-add-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
/* 账号选择器现代化 */
.account-selector {
background: white;
border-radius: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 2rem;
margin-bottom: 2rem;
}
.selector-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.selector-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.25rem;
}
.selector-title {
margin: 0;
color: #1f2937;
font-weight: 600;
}
.selector-subtitle {
margin: 0;
color: #6b7280;
font-size: 0.875rem;
}
.account-select-wrapper {
position: relative;
}
.account-select {
width: 100%;
padding: 1rem 1.25rem;
border: 2px solid #e5e7eb;
border-radius: 12px;
font-size: 1rem;
background: white;
cursor: pointer;
transition: all 0.3s ease;
}
.account-select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
/* ================================
【自动回复菜单】图片关键词样式
================================ */
.btn-image {
background: linear-gradient(135deg, #28a745, #20c997) !important;
border: none !important;
margin-left: 10px;
}
.btn-image:hover {
background: linear-gradient(135deg, #218838, #1ea085) !important;
transform: translateY(-1px);
}
.keyword-input-group .add-btn {
white-space: nowrap;
min-width: auto;
}
.image-preview {
margin-top: 10px;
}
.preview-container {
padding: 10px;
border: 2px dashed #ddd;
border-radius: 8px;
text-align: center;
background-color: #f8f9fa;
}
.keyword-type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
margin-left: 8px;
}
.keyword-type-text {
background-color: #e3f2fd;
color: #1976d2;
}
.keyword-type-image {
background-color: #f3e5f5;
color: #7b1fa2;
}
/* 关键词列表中的图片预览 */
.keyword-image-preview {
max-width: 60px;
max-height: 60px;
border-radius: 4px;
border: 1px solid #ddd;
cursor: pointer;
}
.keyword-image-preview:hover {
border-color: #007bff;
transform: scale(1.05);
transition: all 0.2s ease;
}
/* ================================
【关键词分组显示】新样式
================================ */
.keyword-group-item {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
margin-bottom: 1.5rem;
transition: all 0.3s ease;
overflow: hidden;
}
.keyword-group-item:hover {
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
}
.keyword-group-header {
padding: 1rem 1.5rem;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-between;
}
.keyword-group-title {
display: flex;
align-items: center;
gap: 0.75rem;
font-weight: 600;
color: #1f2937;
}
.keyword-count-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
}
.keyword-group-reply {
padding: 1rem 1.5rem;
background: #fefce8;
border-bottom: 1px solid #fef08a;
font-size: 0.9rem;
}
.keyword-group-reply strong {
color: #854d0e;
margin-right: 0.5rem;
}
.reply-text-content {
color: #374151;
font-weight: 500;
}
.keyword-group-content {
padding: 1.5rem;
}
.keyword-section, .item-section {
margin-bottom: 1.25rem;
}
.keyword-section:last-child, .item-section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 0.875rem;
font-weight: 600;
color: #6b7280;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.chips-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.keyword-chip, .item-chip {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%);
color: #5b21b6;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.2s ease;
}
.keyword-chip:hover, .item-chip:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(91, 33, 182, 0.2);
}
.item-chip {
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
color: #1e40af;
max-width: 400px;
}
.item-chip:hover {
box-shadow: 0 4px 8px rgba(30, 64, 175, 0.2);
}
.chip-remove-btn {
background: rgba(0, 0, 0, 0.1);
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
padding: 0;
color: inherit;
}
.chip-remove-btn:hover {
background: rgba(0, 0, 0, 0.2);
transform: scale(1.1);
}
.chip-remove-btn i {
font-size: 0.875rem;
font-weight: bold;
}
/* 回复内容编辑按钮 */
.reply-edit-btn {
background: none;
border: 1px solid #d1d5db;
border-radius: 6px;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
color: #6b7280;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
margin-left: 0.75rem;
flex-shrink: 0;
}
.reply-edit-btn:hover {
background: #f3f4f6;
color: #374151;
border-color: #9ca3af;
}
.reply-edit-area {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
margin-top: 0.5rem;
}
.reply-edit-textarea {
width: 100%;
padding: 0.75rem;
border: 2px solid #667eea;
border-radius: 8px;
font-size: 0.875rem;
font-family: inherit;
line-height: 1.5;
resize: vertical;
min-height: 60px;
background: white;
color: #374151;
transition: border-color 0.2s ease;
}
.reply-edit-textarea:focus {
outline: none;
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
}
.reply-edit-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.reply-save-btn, .reply-cancel-btn {
padding: 0.375rem 0.75rem;
border: none;
border-radius: 6px;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.reply-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.reply-save-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
}
.reply-cancel-btn {
background: #f3f4f6;
color: #6b7280;
}
.reply-cancel-btn:hover {
background: #e5e7eb;
color: #374151;
}
/* ================================
暗色模式适配 - 关键词管理
================================ */
[data-theme="dark"] .keyword-container {
background: var(--card-bg);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .keyword-input-area {
background: var(--bg-secondary);
border-bottom-color: var(--border-color);
}
[data-theme="dark"] .input-field label {
background: var(--bg-secondary);
color: var(--text-secondary);
}
[data-theme="dark"] .input-field input,
[data-theme="dark"] .input-field select,
[data-theme="dark"] .input-field textarea {
background: var(--input-bg);
border-color: var(--input-border);
color: var(--text-color);
}
[data-theme="dark"] .input-field input:focus,
[data-theme="dark"] .input-field select:focus,
[data-theme="dark"] .input-field textarea:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
[data-theme="dark"] .input-field input::placeholder,
[data-theme="dark"] .input-field textarea::placeholder {
color: var(--text-secondary);
}
[data-theme="dark"] .input-field select[multiple] option {
background: var(--input-bg);
color: var(--text-color);
}
[data-theme="dark"] .input-field select[multiple] option:hover {
background-color: var(--bg-secondary);
}
[data-theme="dark"] .input-field select[multiple] option:checked {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
/* 暗色 - 关键词列表 */
[data-theme="dark"] .keyword-item {
background: var(--card-bg);
border-color: var(--border-color);
}
[data-theme="dark"] .keyword-item-header {
background: var(--bg-secondary);
border-bottom-color: var(--border-color);
}
[data-theme="dark"] .keyword-content {
background: var(--card-bg);
}
[data-theme="dark"] .reply-text {
color: var(--text-color);
}
/* 暗色 - 关键词分组 */
[data-theme="dark"] .keyword-group-item {
background: var(--card-bg);
border-color: var(--border-color);
}
[data-theme="dark"] .keyword-group-header {
background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--input-bg) 100%);
border-bottom-color: var(--border-color);
}
[data-theme="dark"] .keyword-group-title {
color: var(--text-color);
}
[data-theme="dark"] .keyword-group-reply {
background: rgba(133, 77, 14, 0.15);
border-bottom-color: rgba(254, 240, 138, 0.2);
}
[data-theme="dark"] .keyword-group-reply strong {
color: #fcd34d;
}
[data-theme="dark"] .reply-text-content {
color: var(--text-color);
}
[data-theme="dark"] .keyword-group-content {
background: var(--card-bg);
}
[data-theme="dark"] .section-title {
color: var(--text-secondary);
}
/* 暗色 - Chips */
[data-theme="dark"] .keyword-chip {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25) 0%, rgba(124, 58, 237, 0.25) 100%);
color: #c4b5fd;
}
[data-theme="dark"] .keyword-chip:hover {
box-shadow: 0 4px 8px rgba(139, 92, 246, 0.3);
}
[data-theme="dark"] .item-chip {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.25) 0%, rgba(37, 99, 235, 0.25) 100%);
color: #93c5fd;
}
[data-theme="dark"] .item-chip:hover {
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
}
[data-theme="dark"] .chip-remove-btn {
background: rgba(255, 255, 255, 0.15);
}
[data-theme="dark"] .chip-remove-btn:hover {
background: rgba(255, 255, 255, 0.25);
}
/* 暗色 - 操作按钮 */
[data-theme="dark"] .edit-btn {
background: var(--input-bg);
color: var(--text-color);
}
[data-theme="dark"] .edit-btn:hover {
background: var(--border-color);
}
[data-theme="dark"] .delete-btn {
background: rgba(239, 68, 68, 0.15);
color: #fca5a5;
}
[data-theme="dark"] .delete-btn:hover {
background: rgba(239, 68, 68, 0.25);
color: #f87171;
}
/* 暗色 - 空状态 */
[data-theme="dark"] .empty-state {
color: var(--text-secondary);
}
[data-theme="dark"] .empty-state h3 {
color: var(--text-color);
}
/* 暗色 - 账号选择器 */
[data-theme="dark"] .account-selector {
background: var(--card-bg);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .selector-title {
color: var(--text-color);
}
[data-theme="dark"] .selector-subtitle {
color: var(--text-secondary);
}
[data-theme="dark"] .account-select {
background: var(--input-bg);
border-color: var(--input-border);
color: var(--text-color);
}
[data-theme="dark"] .account-select:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
/* 暗色 - 图片预览 */
[data-theme="dark"] .preview-container {
background-color: var(--bg-secondary);
border-color: var(--border-color);
}
[data-theme="dark"] .keyword-image-preview {
border-color: var(--border-color);
}
[data-theme="dark"] .keyword-image-preview:hover {
border-color: #667eea;
}
/* 暗色 - 类型徽章 */
[data-theme="dark"] .keyword-type-text {
background-color: rgba(25, 118, 210, 0.2);
color: #93c5fd;
}
[data-theme="dark"] .keyword-type-image {
background-color: rgba(123, 31, 162, 0.2);
color: #d8b4fe;
}
/* 暗色 - 回复编辑 */
[data-theme="dark"] .reply-edit-btn {
border-color: var(--border-color);
color: var(--text-secondary);
}
[data-theme="dark"] .reply-edit-btn:hover {
background: var(--input-bg);
color: var(--text-color);
border-color: var(--text-secondary);
}
[data-theme="dark"] .reply-edit-textarea {
background: var(--input-bg);
border-color: #667eea;
color: var(--text-color);
}
[data-theme="dark"] .reply-edit-textarea:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
[data-theme="dark"] .reply-cancel-btn {
background: var(--input-bg);
color: var(--text-secondary);
}
[data-theme="dark"] .reply-cancel-btn:hover {
background: var(--border-color);
color: var(--text-color);
}
/* 暗色 - Modal 内关键词样式 */
[data-theme="dark"] .modal .input-field input,
[data-theme="dark"] .modal .input-field textarea {
background: var(--input-bg);
border-color: var(--input-border);
color: var(--text-color);
}
/* 响应式调整 */
@media (max-width: 768px) {
.keyword-input-row {
grid-template-columns: 1fr;
}
.keyword-btn-row {
flex-direction: column;
}
.keyword-item-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.keyword-actions {
align-self: flex-end;
}
.keyword-input-group {
flex-direction: column;
align-items: stretch;
}
.keyword-input-group .add-btn {
margin-left: 0;
margin-top: 10px;
}
.keyword-group-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.chips-container {
gap: 0.375rem;
}
.keyword-chip, .item-chip {
font-size: 0.8125rem;
padding: 0.375rem 0.625rem;
}
.item-chip {
max-width: 100%;
}
}
+306
View File
@@ -0,0 +1,306 @@
/* ================================
通用布局 - 侧边栏和导航
================================ */
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 250px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
color: white;
z-index: 1000;
transition: all 0.3s ease;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar-header {
padding: 1.5rem 1rem;
border-bottom: 1px solid rgba(255,255,255,0.1);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
}
/* 暗色模式切换按钮 */
.dark-mode-toggle {
background: rgba(255, 255, 255, 0.15);
border: none;
color: white;
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
}
.dark-mode-toggle:hover {
background: rgba(255, 255, 255, 0.25);
transform: scale(1.05);
}
.dark-mode-toggle i {
font-size: 1rem;
transition: transform 0.3s ease;
}
.dark-mode-toggle:hover i {
transform: rotate(15deg);
}
.sidebar-brand {
font-size: 1.25rem;
font-weight: 700;
color: white;
text-decoration: none;
display: flex;
align-items: center;
}
.sidebar-nav {
padding: 1rem 0;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
/* 侧边栏滚动条样式 */
.sidebar-nav::-webkit-scrollbar {
width: 6px;
}
.sidebar-nav::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.sidebar-nav::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
transition: background 0.3s ease;
}
.sidebar-nav::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* Firefox滚动条样式 */
.sidebar-nav {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) rgba(255, 255, 255, 0.1);
}
.nav-item {
margin: 0.25rem 0;
}
.nav-link {
display: flex;
align-items: center;
padding: 0.75rem 1.5rem;
color: rgba(255,255,255,0.8);
text-decoration: none;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.nav-link:hover {
color: white;
background: rgba(255,255,255,0.1);
border-left-color: rgba(255,255,255,0.5);
}
.nav-link.active {
color: white;
background: rgba(255,255,255,0.15);
border-left-color: white;
}
.nav-link i {
margin-right: 0.75rem;
width: 20px;
text-align: center;
}
.nav-divider {
padding: 0.5rem 1rem;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: 0.5rem;
}
.nav-divider small {
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* 主内容区域 */
.main-content {
margin-left: 250px;
min-height: 100vh;
background: var(--bg-color);
transition: margin-left 0.3s ease;
}
.content-header {
background: white;
padding: 1.5rem 2rem;
border-bottom: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
}
.content-body {
padding: 2rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.show {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.mobile-toggle {
display: block !important;
}
}
.mobile-toggle {
display: none;
position: fixed;
top: 1rem;
left: 1rem;
z-index: 1001;
background: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
padding: 0.5rem;
box-shadow: var(--shadow-md);
}
/* 侧边栏折叠样式 */
.sidebar.collapsed {
width: 70px;
}
.sidebar.collapsed .sidebar-brand span,
.sidebar.collapsed .nav-link span {
display: none;
}
.sidebar.collapsed .sidebar-brand {
justify-content: center;
}
.sidebar.collapsed .sidebar-brand img {
margin: 0 !important;
}
.sidebar.collapsed .nav-link {
justify-content: center;
padding: 0.75rem;
}
.sidebar.collapsed .nav-link i {
margin: 0;
font-size: 1.2rem;
}
.sidebar.collapsed .nav-divider {
text-align: center;
padding: 0.5rem 0;
}
.sidebar.collapsed .nav-divider small {
display: none;
}
.sidebar.collapsed .sidebar-collapse-btn {
justify-content: center;
}
.sidebar.collapsed .sidebar-collapse-btn span {
display: none;
}
.sidebar.collapsed .sidebar-collapse-btn i {
transform: rotate(180deg);
}
/* 折叠状态下的主内容区域 */
body.sidebar-collapsed .main-content {
margin-left: 70px;
}
/* 侧边栏底部折叠按钮 */
.sidebar-footer {
padding: 1rem;
border-top: 1px solid rgba(255,255,255,0.1);
flex-shrink: 0;
}
.sidebar-collapse-btn {
display: flex;
align-items: center;
width: 100%;
padding: 0.5rem 1rem;
background: rgba(255,255,255,0.1);
color: rgba(255,255,255,0.8);
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.sidebar-collapse-btn:hover {
background: rgba(255,255,255,0.2);
color: white;
}
.sidebar-collapse-btn i {
margin-right: 0.5rem;
transition: transform 0.3s ease;
}
/* 内容区域样式 */
.content-section {
display: none;
}
.content-section.active {
display: block;
}
/* 在线客服页面特殊样式 - 使用flex布局填满高度 */
#online-im-section.active {
display: flex;
flex-direction: column;
height: calc(100vh);
}
#online-im-section .content-header {
flex-shrink: 0;
}
#online-im-section .content-body {
flex: 1;
overflow: hidden;
}
+875
View File
@@ -0,0 +1,875 @@
/* ================================
【日志管理菜单】相关样式
================================ */
.log-container {
height: 70vh;
overflow-y: auto;
background-color: #1e1e1e;
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
line-height: 1.2;
padding: 8px;
border-radius: 4px;
}
.log-entry {
margin-bottom: 0px;
padding: 1px 0;
word-wrap: break-word;
white-space: pre-wrap;
line-height: 1.2;
}
.log-entry.DEBUG {
color: #9cdcfe;
}
.log-entry.INFO {
color: #4ec9b0;
}
.log-entry.WARNING {
color: #dcdcaa;
}
.log-entry.ERROR {
color: #f48771;
}
.log-entry.CRITICAL {
color: #ff6b6b;
font-weight: bold;
}
.log-timestamp {
color: #808080;
}
.log-level {
font-weight: bold;
margin: 0 5px;
}
.log-source {
color: #569cd6;
}
.log-message {
color: inherit;
}
.log-container::-webkit-scrollbar {
width: 8px;
}
.log-container::-webkit-scrollbar-track {
background: #2d2d30;
}
.log-container::-webkit-scrollbar-thumb {
background: #464647;
border-radius: 4px;
}
.log-container::-webkit-scrollbar-thumb:hover {
background: #5a5a5c;
}
/* 日志过滤标签样式 */
.filter-badge {
cursor: pointer;
transition: all 0.2s;
margin-right: 0.5rem;
margin-bottom: 0.25rem;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 32px;
padding: 0.45rem 0.8rem;
line-height: 1;
}
.filter-badge:hover {
transform: scale(1.05);
}
.filter-badge.active {
box-shadow: 0 0 0 2px rgba(255,255,255,0.5);
}
.toolbar-count-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 32px;
padding: 0.5rem 0.875rem;
line-height: 1;
white-space: nowrap;
}
.risk-event-category-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 28px;
padding: 0.38rem 0.68rem;
border-radius: 999px;
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0.01em;
border: 1px solid transparent;
}
.risk-event-category-trigger {
background: rgba(79, 70, 229, 0.1);
color: var(--primary-color);
border-color: rgba(79, 70, 229, 0.14);
}
.risk-event-category-refresh {
background: rgba(16, 185, 129, 0.1);
color: var(--success-color);
border-color: rgba(16, 185, 129, 0.14);
}
.risk-event-category-error {
background: rgba(239, 68, 68, 0.1);
color: var(--danger-color);
border-color: rgba(239, 68, 68, 0.14);
}
.risk-event-category-neutral {
background: rgba(100, 116, 139, 0.1);
color: #475569;
border-color: rgba(100, 116, 139, 0.14);
}
[data-theme="dark"] .risk-event-category-trigger {
background: rgba(99, 102, 241, 0.22);
color: #c7d2fe;
}
[data-theme="dark"] .risk-event-category-refresh {
background: rgba(16, 185, 129, 0.2);
color: #a7f3d0;
}
[data-theme="dark"] .risk-event-category-error {
background: rgba(239, 68, 68, 0.2);
color: #fecaca;
}
[data-theme="dark"] .risk-event-category-neutral {
background: rgba(100, 116, 139, 0.22);
color: #e2e8f0;
}
.risk-log-toolbar-wrap {
position: relative;
}
.risk-tool-card {
position: relative;
overflow: hidden;
height: 100%;
border-radius: 16px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(250, 251, 252, 0.96));
box-shadow: 0 14px 30px -28px rgba(15, 23, 42, 0.25);
padding: 0.82rem 0.9rem;
}
.risk-tool-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
opacity: 0.9;
pointer-events: none;
}
.risk-slider-stats-panel,
.risk-log-filter-panel {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.96));
display: flex;
flex-direction: column;
}
.risk-tool-card-header {
position: relative;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.7rem;
z-index: 1;
}
.risk-tool-card-meta {
display: flex;
align-items: flex-start;
gap: 0.7rem;
min-width: 0;
}
.risk-tool-card-icon {
width: 34px;
height: 34px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
font-size: 0.95rem;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.52);
}
.risk-tool-card-icon.primary,
.risk-tool-card-icon.warning {
background: rgba(79, 70, 229, 0.1);
color: var(--primary-color);
}
.risk-tool-card-kicker {
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--primary-color);
margin-bottom: 0.12rem;
}
.risk-tool-card h6 {
font-size: 0.94rem;
line-height: 1.2;
}
.risk-tool-card p {
font-size: 0.76rem;
line-height: 1.35;
}
.risk-tool-chip {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.38rem 0.72rem;
border-radius: 999px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: rgba(79, 70, 229, 0.05);
color: var(--text-secondary);
font-size: 0.76rem;
font-weight: 600;
white-space: nowrap;
flex: 0 0 auto;
}
.risk-slider-range-group {
display: inline-flex;
align-items: center;
gap: 0.2rem;
padding: 0.18rem;
border-radius: 999px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(79, 70, 229, 0.04);
flex: 0 0 auto;
}
.risk-slider-range-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 29px;
padding: 0.3rem 0.62rem;
border: none;
border-radius: 999px;
background: transparent;
color: var(--text-secondary);
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
white-space: nowrap;
transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.risk-slider-range-btn:hover {
background: rgba(79, 70, 229, 0.08);
color: var(--text-color);
}
.risk-slider-range-btn.is-active {
background: var(--primary-color);
color: #fff;
box-shadow: 0 6px 16px rgba(79, 70, 229, 0.18);
}
.risk-slider-hero {
position: relative;
z-index: 1;
margin-top: 0.72rem;
margin-bottom: 0.62rem;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 0.75rem;
}
.risk-slider-rate-label {
font-size: 0.72rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 0.12rem;
}
.risk-slider-rate {
font-size: clamp(1.72rem, 2.7vw, 2.15rem);
line-height: 0.95;
font-weight: 800;
color: var(--primary-hover);
letter-spacing: -0.03em;
}
.risk-slider-total {
display: inline-flex;
align-items: center;
min-height: 32px;
padding: 0.38rem 0.68rem;
border-radius: 12px;
background: rgba(79, 70, 229, 0.05);
color: var(--text-secondary);
font-size: 0.78rem;
font-weight: 600;
white-space: nowrap;
}
.risk-slider-stats-grid {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.55rem;
}
.risk-slider-stat-chip {
border-radius: 12px;
min-height: 72px;
padding: 0.72rem 0.78rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.55rem;
background: rgba(248, 250, 252, 0.95);
border: 1px solid rgba(148, 163, 184, 0.14);
}
.risk-slider-stat-chip span {
font-size: 0.8rem;
font-weight: 600;
color: var(--text-secondary);
}
.risk-slider-stat-chip strong {
font-size: 1.18rem;
line-height: 1;
}
.risk-slider-stat-chip.success strong {
color: var(--success-color);
}
.risk-slider-stat-chip.danger strong {
color: var(--danger-color);
}
.risk-slider-stats-foot {
position: relative;
z-index: 1;
margin-top: 0.6rem;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.55rem;
}
.risk-slider-foot-item {
padding-top: 0.42rem;
border-top: 1px dashed rgba(148, 163, 184, 0.34);
}
.risk-slider-foot-item span {
display: block;
font-size: 0.68rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 0.1rem;
}
.risk-slider-foot-item strong {
font-size: 0.84rem;
color: var(--text-color);
}
.risk-filter-panel-body {
position: relative;
z-index: 1;
margin-top: 0.72rem;
display: grid;
grid-template-rows: minmax(0, 1fr) auto;
gap: 0.58rem;
flex: 1 1 auto;
}
.risk-filter-grid {
display: grid;
grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
grid-template-areas:
"account account"
"limit event";
gap: 0.72rem 0.62rem;
align-content: space-between;
min-height: 100%;
}
.risk-filter-field,
.risk-filter-action {
min-width: 0;
display: flex;
flex-direction: column;
}
.risk-filter-field-account {
grid-area: account;
}
.risk-filter-field-limit {
grid-area: limit;
}
.risk-filter-field-event {
grid-area: event;
}
.risk-log-filter-panel .form-label {
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--text-secondary);
margin-bottom: 0.3rem;
}
.risk-filter-field .form-select,
.risk-filter-field .form-control {
min-height: 38px;
padding-top: 0.42rem;
padding-bottom: 0.42rem;
background: rgba(255, 255, 255, 0.96);
}
.risk-filter-action {
grid-column: span 2;
}
.risk-filter-action > div {
width: 100%;
justify-content: flex-end;
}
.risk-filter-refresh-btn {
min-height: 38px;
width: auto;
min-width: 136px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
font-weight: 600;
}
.risk-filter-status-bar {
position: relative;
margin-top: 0;
padding-top: 0.42rem;
border-top: 1px solid rgba(148, 163, 184, 0.18);
}
.risk-filter-status-controls {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
width: 100%;
}
.risk-log-filter-badges {
gap: 0.45rem !important;
flex: 1 1 auto;
align-items: center;
}
.risk-filter-status-refresh {
min-width: 118px;
flex: 0 0 auto;
}
.risk-log-filter-badges .filter-badge {
margin-right: 0;
margin-bottom: 0;
min-height: 30px;
padding: 0.36rem 0.68rem;
border-radius: 999px;
border: 1px solid transparent;
box-shadow: none;
font-size: 0.74rem;
font-weight: 600;
line-height: 1;
}
.risk-log-filter-badges .filter-badge.active {
box-shadow: 0 10px 22px -18px rgba(15, 23, 42, 0.55);
transform: translateY(-1px);
}
.risk-log-filter-badges .filter-badge-neutral {
background: rgba(100, 116, 139, 0.08);
color: #475569;
border-color: rgba(100, 116, 139, 0.12);
}
.risk-log-filter-badges .filter-badge-neutral.active {
background: #64748b;
color: #fff;
}
.risk-log-filter-badges .filter-badge-processing {
background: rgba(245, 158, 11, 0.08);
color: #b45309;
border-color: rgba(245, 158, 11, 0.12);
}
.risk-log-filter-badges .filter-badge-processing.active {
background: #f59e0b;
color: #fff;
}
.risk-log-filter-badges .filter-badge-success {
background: rgba(16, 185, 129, 0.08);
color: #0f9b76;
border-color: rgba(16, 185, 129, 0.12);
}
.risk-log-filter-badges .filter-badge-success.active {
background: #10b981;
color: #fff;
}
.risk-log-filter-badges .filter-badge-danger {
background: rgba(239, 68, 68, 0.08);
color: #dc2626;
border-color: rgba(239, 68, 68, 0.12);
}
.risk-log-filter-badges .filter-badge-danger.active {
background: #ef4444;
color: #fff;
}
.risk-log-table-wrap {
width: 100%;
}
.risk-log-table {
width: 100%;
table-layout: fixed;
}
.risk-log-table th,
.risk-log-table td {
vertical-align: top;
}
.risk-log-table td {
word-break: break-word;
}
.risk-log-col-time {
width: 12%;
}
.risk-log-col-account {
width: 11%;
}
.risk-log-col-category {
width: 8%;
}
.risk-log-col-scene {
width: 8%;
}
.risk-log-col-status {
width: 6%;
}
.risk-log-col-summary {
width: 14%;
}
.risk-log-col-outcome {
width: 14%;
}
.risk-log-col-duration {
width: 5%;
}
.risk-log-col-session {
width: 16%;
}
.risk-log-col-action {
width: 6%;
}
.risk-log-summary-cell,
.risk-log-outcome-cell {
min-width: 0;
width: 100%;
}
.risk-log-summary-cell {
line-height: 1.42;
}
.risk-log-cell-summary,
.risk-log-cell-outcome {
overflow: hidden;
}
.risk-log-cell-session {
white-space: normal;
word-break: break-all;
line-height: 1.35;
font-size: 0.78rem;
}
.risk-log-outcome-cell details {
max-width: 100%;
}
.risk-log-outcome-cell pre {
max-width: 100%;
white-space: pre-wrap;
word-break: break-word;
}
[data-theme="dark"] .risk-tool-card {
border-color: rgba(148, 163, 184, 0.12);
background: linear-gradient(180deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.92));
box-shadow: 0 18px 34px -30px rgba(2, 6, 23, 0.92);
}
[data-theme="dark"] .risk-tool-card-icon.primary,
[data-theme="dark"] .risk-tool-card-icon.warning {
background: rgba(99, 102, 241, 0.22);
color: #c7d2fe;
}
[data-theme="dark"] .risk-tool-chip {
background: rgba(99, 102, 241, 0.16);
border-color: rgba(148, 163, 184, 0.14);
color: #cbd5e1;
}
[data-theme="dark"] .risk-slider-range-group {
background: rgba(99, 102, 241, 0.1);
border-color: rgba(148, 163, 184, 0.14);
}
[data-theme="dark"] .risk-slider-range-btn {
color: #cbd5e1;
}
[data-theme="dark"] .risk-slider-range-btn:hover {
background: rgba(129, 140, 248, 0.14);
color: #e2e8f0;
}
[data-theme="dark"] .risk-slider-range-btn.is-active {
background: rgba(129, 140, 248, 0.92);
color: #0f172a;
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.26);
}
[data-theme="dark"] .risk-slider-rate {
color: #c7d2fe;
}
[data-theme="dark"] .risk-slider-total {
background: rgba(99, 102, 241, 0.14);
color: #cbd5e1;
}
[data-theme="dark"] .risk-slider-stat-chip {
background: rgba(15, 23, 42, 0.68);
border-color: rgba(148, 163, 184, 0.12);
}
[data-theme="dark"] .risk-slider-foot-item,
[data-theme="dark"] .risk-filter-status-bar {
border-top-color: rgba(148, 163, 184, 0.12);
}
[data-theme="dark"] .risk-filter-field .form-select {
background: rgba(15, 23, 42, 0.68);
}
[data-theme="dark"] .risk-log-filter-badges .filter-badge-neutral {
background: rgba(100, 116, 139, 0.22);
color: #e2e8f0;
}
[data-theme="dark"] .risk-log-filter-badges .filter-badge-processing {
background: rgba(245, 158, 11, 0.22);
color: #fde68a;
}
[data-theme="dark"] .risk-log-filter-badges .filter-badge-success {
background: rgba(16, 185, 129, 0.2);
color: #a7f3d0;
}
[data-theme="dark"] .risk-log-filter-badges .filter-badge-danger {
background: rgba(239, 68, 68, 0.2);
color: #fecaca;
}
@media (max-width: 991.98px) {
.risk-log-table {
min-width: 1080px;
table-layout: auto;
}
.risk-tool-card-header,
.risk-slider-hero {
flex-direction: column;
align-items: flex-start;
}
.risk-filter-status-controls {
align-items: flex-start;
}
}
@media (max-width: 767.98px) {
.risk-filter-panel-body {
grid-template-rows: auto auto;
}
.risk-filter-grid {
grid-template-columns: minmax(0, 1fr) minmax(100px, 0.78fr);
grid-template-areas:
"account account"
"limit event";
align-content: start;
min-height: auto;
}
.risk-filter-action {
grid-column: 1 / -1;
}
.risk-filter-status-controls {
flex-direction: column;
align-items: stretch;
}
.risk-filter-status-refresh {
width: 100%;
}
}
@media (max-width: 575.98px) {
.risk-tool-card {
padding: 0.78rem;
}
.risk-filter-grid,
.risk-slider-stats-grid,
.risk-slider-stats-foot {
grid-template-columns: 1fr;
}
.risk-filter-panel-body {
grid-template-rows: auto auto;
}
.risk-filter-grid {
grid-template-areas:
"account"
"limit"
"event";
min-height: auto;
}
.risk-filter-action {
grid-column: 1 / -1;
}
}
/* 自动刷新指示器 */
.auto-refresh-indicator {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* 日志行悬停效果 */
.log-entry:hover {
background: rgba(255,255,255,0.05);
}
/* 商品搜索相关样式 */
.item-card {
transition: transform 0.2s;
border: none;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.item-card:hover {
transform: translateY(-2px);
}
.item-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 10px;
}
.price {
color: #e74c3c;
font-weight: bold;
font-size: 1.2em;
}
.seller-name {
color: #6c757d;
font-size: 0.9em;
}
.want-count {
margin-top: 10px;
}
.want-count .badge {
font-size: 0.8em;
}
+83
View File
@@ -0,0 +1,83 @@
/* ================================
【通知渠道菜单】卡片样式
================================ */
.channel-type-card {
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
min-height: 180px; /* 设置最小高度,让卡片更紧凑 */
}
.channel-type-card .card-body {
padding: 1rem; /* 减少内边距 */
}
.channel-type-card .channel-icon {
transition: transform 0.3s ease;
margin-bottom: 0.75rem !important; /* 减少图标下方间距 */
}
.channel-type-card .channel-icon i {
font-size: 2.2rem !important; /* 减小图标大小 */
}
.channel-type-card .card-title {
font-size: 1.1rem; /* 减小标题字体 */
margin-bottom: 0.5rem;
}
.channel-type-card .card-text {
font-size: 0.85rem; /* 减小描述文字 */
margin-bottom: 0.75rem;
}
.channel-type-card .btn {
transition: all 0.3s ease;
font-size: 0.85rem; /* 减小按钮文字 */
padding: 0.375rem 0.75rem; /* 减小按钮内边距 */
}
.channel-type-card:hover {
transform: translateY(-3px); /* 减少悬停位移 */
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
border-color: var(--bs-primary);
}
.channel-type-card:hover .channel-icon {
transform: scale(1.05); /* 减少悬停缩放 */
}
.channel-type-card:hover .btn {
transform: scale(1.02); /* 减少按钮悬停缩放 */
}
/* 响应式调整 */
@media (max-width: 768px) {
.channel-type-card {
margin-bottom: 10px;
min-height: 160px; /* 移动端稍微减小高度 */
}
.channel-type-card .card-body {
padding: 0.75rem; /* 移动端减少内边距 */
}
.channel-type-card .channel-icon i {
font-size: 2rem !important; /* 移动端图标更小 */
}
.channel-type-card .card-title {
font-size: 1rem; /* 移动端标题更小 */
}
.channel-type-card .card-text {
font-size: 0.8rem; /* 移动端描述文字更小 */
}
}
/* 大屏幕优化 */
@media (min-width: 1400px) {
.channel-type-card {
min-height: 200px; /* 大屏幕稍微增加高度 */
}
}
+550
View File
@@ -0,0 +1,550 @@
/* ================================
全局变量和基础样式
================================ */
:root {
--primary-color: #4f46e5;
--primary-hover: #4338ca;
--primary-light: #6366f1;
--secondary-color: #6b7280;
--success-color: #10b981;
--danger-color: #ef4444;
--warning-color: #f59e0b;
--light-color: #f9fafb;
--dark-color: #1f2937;
--border-color: #e5e7eb;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
/* 暗色模式变量 */
--bg-color: #f8fafc;
--bg-secondary: #ffffff;
--text-color: #1f2937;
--text-secondary: #6b7280;
--card-bg: #ffffff;
--input-bg: #ffffff;
--input-border: #d1d5db;
--table-stripe: rgba(0, 0, 0, 0.02);
--modal-bg: #ffffff;
}
/* 暗色模式 */
[data-theme="dark"] {
--bg-color: #0f172a;
--bg-secondary: #1e293b;
--text-color: #f1f5f9;
--text-secondary: #94a3b8;
--card-bg: #1e293b;
--input-bg: #334155;
--input-border: #475569;
--border-color: #334155;
--light-color: #1e293b;
--dark-color: #f1f5f9;
--table-stripe: rgba(255, 255, 255, 0.02);
--modal-bg: #1e293b;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
}
body {
font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--bg-color);
color: var(--text-color);
min-height: 100vh;
padding: 0;
margin: 0;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 暗色模式下的通用样式覆盖 */
[data-theme="dark"] .card {
background: var(--card-bg);
border-color: var(--border-color);
}
[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select {
background-color: var(--input-bg);
border-color: var(--input-border);
color: var(--text-color);
}
[data-theme="dark"] .form-control:focus,
[data-theme="dark"] .form-select:focus {
background-color: var(--input-bg);
border-color: var(--primary-color);
color: var(--text-color);
}
[data-theme="dark"] .form-control::placeholder {
color: var(--text-secondary);
}
[data-theme="dark"] .table {
color: var(--text-color);
}
[data-theme="dark"] .table > :not(caption) > * > * {
background-color: var(--card-bg);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .table-striped > tbody > tr:nth-of-type(odd) > * {
background-color: var(--table-stripe);
}
[data-theme="dark"] .modal-content {
background-color: var(--modal-bg);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .modal-header {
border-color: var(--border-color);
}
[data-theme="dark"] #aiReplyConfigModal .ai-account-panel,
[data-theme="dark"] #aiReplyConfigModal .ai-enable-panel,
[data-theme="dark"] #aiReplyConfigModal .ai-config-block,
[data-theme="dark"] #aiReplyConfigModal .ai-config-grid,
[data-theme="dark"] #aiReplyConfigModal .ai-metric-card,
[data-theme="dark"] #aiReplyConfigModal .ai-prompt-card {
background: rgba(30, 41, 59, 0.78);
border-color: rgba(99, 102, 241, 0.32);
}
[data-theme="dark"] #aiReplyConfigModal .ai-modal-intro {
background: linear-gradient(120deg, rgba(99, 102, 241, 0.22), rgba(6, 182, 212, 0.22));
border-color: rgba(99, 102, 241, 0.38);
}
[data-theme="dark"] #aiReplyConfigModal .ai-intro-icon {
background: rgba(99, 102, 241, 0.3);
color: #c7d2fe;
}
[data-theme="dark"] #aiReplyConfigModal .ai-intro-title {
color: var(--text-color);
}
[data-theme="dark"] #aiReplyConfigModal .ai-intro-text,
[data-theme="dark"] #aiReplyConfigModal .ai-footer-note {
color: var(--text-secondary);
}
[data-theme="dark"] #aiReplyConfigModal .ai-section-tag {
border-color: rgba(255, 255, 255, 0.35);
color: rgba(255, 255, 255, 0.9);
}
[data-theme="dark"] #aiReplyConfigModal .ai-basic-card .card-body {
background:
radial-gradient(circle at right top, rgba(99, 102, 241, 0.2), transparent 45%),
radial-gradient(circle at left bottom, rgba(6, 182, 212, 0.2), transparent 42%);
}
[data-theme="dark"] #aiReplyConfigModal .ai-account-field i {
box-shadow: 0 6px 18px rgba(79, 70, 229, 0.35);
}
[data-theme="dark"] #aiReplyConfigModal .ai-endpoint-preview {
border-color: rgba(16, 185, 129, 0.42);
background: rgba(16, 185, 129, 0.15);
color: #6ee7b7;
}
[data-theme="dark"] #aiReplyConfigModal .ai-prompt-tip {
border-color: rgba(14, 165, 233, 0.4);
background: rgba(14, 165, 233, 0.14);
color: #7dd3fc;
}
[data-theme="dark"] #aiReplyConfigModal .ai-test-result .alert {
border-color: rgba(59, 130, 246, 0.4);
background-color: rgba(30, 58, 138, 0.25);
color: var(--text-color);
}
[data-theme="dark"] .modal-footer {
border-color: var(--border-color);
}
[data-theme="dark"] .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
[data-theme="dark"] .dropdown-menu {
background-color: var(--card-bg);
border-color: var(--border-color);
}
[data-theme="dark"] .dropdown-item {
color: var(--text-color);
}
[data-theme="dark"] .dropdown-item:hover {
background-color: var(--input-bg);
}
[data-theme="dark"] .list-group-item {
background-color: var(--card-bg);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .badge.bg-light {
background-color: var(--input-bg) !important;
color: var(--text-color) !important;
}
[data-theme="dark"] .text-muted {
color: var(--text-secondary) !important;
}
[data-theme="dark"] .border {
border-color: var(--border-color) !important;
}
[data-theme="dark"] hr {
border-color: var(--border-color);
}
[data-theme="dark"] .bg-white {
background-color: var(--card-bg) !important;
}
[data-theme="dark"] .bg-light {
background-color: var(--bg-secondary) !important;
}
/* 暗色模式下的主内容区域 */
[data-theme="dark"] .main-content {
background: var(--bg-color);
}
[data-theme="dark"] .content-header {
background: var(--card-bg);
border-color: var(--border-color);
}
/* 暗色模式下的统计卡片 */
[data-theme="dark"] .stat-card {
background: var(--card-bg);
}
/* 暗色模式下的输入框组 */
[data-theme="dark"] .input-group-text {
background-color: var(--input-bg);
border-color: var(--input-border);
color: var(--text-color);
}
/* 暗色模式下的表单标签 */
[data-theme="dark"] .form-label,
[data-theme="dark"] .form-check-label,
[data-theme="dark"] .col-form-label {
color: var(--text-color);
}
/* 暗色模式下的卡片标题和文字 */
[data-theme="dark"] .card-title,
[data-theme="dark"] .card-text,
[data-theme="dark"] .card-header,
[data-theme="dark"] .card-body {
color: var(--text-color);
}
/* 暗色模式下的纯文本表单控件 */
[data-theme="dark"] .form-control-plaintext {
color: var(--text-color);
}
/* 暗色模式下的表格表头 */
[data-theme="dark"] .table th {
color: var(--text-color);
}
/* 暗色模式下的 Bootstrap 文字颜色类 */
[data-theme="dark"] .text-dark {
color: var(--text-color) !important;
}
[data-theme="dark"] .text-body {
color: var(--text-color) !important;
}
/* 暗色模式下的 alert 组件 */
[data-theme="dark"] .alert {
color: var(--text-color);
border-color: var(--border-color);
}
[data-theme="dark"] .alert-info {
background-color: rgba(59, 130, 246, 0.2);
border-color: rgba(59, 130, 246, 0.3);
color: #93c5fd;
}
[data-theme="dark"] .alert-warning {
background-color: rgba(245, 158, 11, 0.2);
border-color: rgba(245, 158, 11, 0.3);
color: #fcd34d;
}
[data-theme="dark"] .alert-danger {
background-color: rgba(239, 68, 68, 0.2);
border-color: rgba(239, 68, 68, 0.3);
color: #fca5a5;
}
[data-theme="dark"] .alert-success {
background-color: rgba(16, 185, 129, 0.2);
border-color: rgba(16, 185, 129, 0.3);
color: #6ee7b7;
}
/* 暗色模式下的状态徽章 */
[data-theme="dark"] .status-badge.enabled {
background-color: rgba(16, 185, 129, 0.2);
color: #6ee7b7;
}
[data-theme="dark"] .status-badge.disabled {
background-color: rgba(239, 68, 68, 0.2);
color: #fca5a5;
}
/* 暗色模式下的空状态图标 */
[data-theme="dark"] .empty-state i,
[data-theme="dark"] .text-center i[style*="color: #ccc"] {
color: var(--text-secondary) !important;
}
/* 暗色模式下的标题和段落 */
[data-theme="dark"] h1, [data-theme="dark"] h2, [data-theme="dark"] h3,
[data-theme="dark"] h4, [data-theme="dark"] h5, [data-theme="dark"] h6 {
color: var(--text-color);
}
[data-theme="dark"] p {
color: var(--text-color);
}
/* 暗色模式下的小字和次要文字 */
[data-theme="dark"] small,
[data-theme="dark"] .small,
[data-theme="dark"] .text-secondary {
color: var(--text-secondary) !important;
}
/* 暗色模式下的账号禁用状态 */
[data-theme="dark"] .account-row.disabled {
background-color: rgba(0, 0, 0, 0.2);
}
/* 暗色模式下的 Cookie 值显示 */
[data-theme="dark"] .cookie-value {
background-color: var(--input-bg);
color: var(--text-color);
}
/* 暗色模式下的选择器标题 */
[data-theme="dark"] .selector-title,
[data-theme="dark"] .selector-subtitle {
color: var(--text-color);
}
/* 暗色模式下的关键词管理 */
[data-theme="dark"] .keyword-container,
[data-theme="dark"] .keyword-input-area {
background-color: var(--card-bg);
border-color: var(--border-color);
}
[data-theme="dark"] .keyword-group-title,
[data-theme="dark"] .section-title,
[data-theme="dark"] .reply-text,
[data-theme="dark"] .reply-text-content {
color: var(--text-color);
}
[data-theme="dark"] .empty-state,
[data-theme="dark"] .empty-state h3 {
color: var(--text-secondary);
}
[data-theme="dark"] .edit-btn {
background-color: var(--input-bg);
color: var(--text-color);
}
[data-theme="dark"] .input-field label {
color: var(--text-secondary);
}
/* 内容区域的 nav-tabs 样式修复(覆盖侧边栏样式) */
.content-body .nav-tabs .nav-link {
color: #495057;
background-color: transparent;
border: 1px solid transparent;
border-top-left-radius: 0.375rem;
border-top-right-radius: 0.375rem;
padding: 0.5rem 1rem;
}
.content-body .nav-tabs .nav-link:hover {
color: #0d6efd;
border-color: #e9ecef #e9ecef #dee2e6;
background-color: transparent;
}
.content-body .nav-tabs .nav-link.active {
color: #495057;
background-color: #fff;
border-color: #dee2e6 #dee2e6 #fff;
}
/* 暗色模式下的 nav-tabs */
[data-theme="dark"] .nav-tabs {
border-color: var(--border-color);
}
[data-theme="dark"] .nav-tabs .nav-link,
[data-theme="dark"] .content-body .nav-tabs .nav-link {
color: var(--text-secondary);
background-color: transparent;
}
[data-theme="dark"] .nav-tabs .nav-link.active,
[data-theme="dark"] .content-body .nav-tabs .nav-link.active {
background-color: var(--card-bg);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .nav-tabs .nav-link:hover,
[data-theme="dark"] .content-body .nav-tabs .nav-link:hover {
border-color: var(--border-color);
color: var(--text-color);
}
/* 暗色模式下的 tab-content */
[data-theme="dark"] .tab-content {
border-color: var(--border-color) !important;
background-color: var(--card-bg);
}
/* 暗色模式下的模板预览区域 */
[data-theme="dark"] .bg-light {
background-color: var(--bg-color) !important;
color: var(--text-color);
}
/* 暗色模式下的 accordion */
[data-theme="dark"] .accordion-item {
background-color: var(--card-bg);
border-color: var(--border-color);
}
[data-theme="dark"] .accordion-button {
background-color: var(--card-bg);
color: var(--text-color);
}
[data-theme="dark"] .accordion-button:not(.collapsed) {
background-color: var(--input-bg);
color: var(--text-color);
}
[data-theme="dark"] .accordion-body {
background-color: var(--card-bg);
color: var(--text-color);
}
/* 暗色模式下的账号选择器 */
[data-theme="dark"] .account-selector {
background-color: var(--card-bg);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .account-select {
background-color: var(--input-bg);
border-color: var(--input-border);
color: var(--text-color);
}
[data-theme="dark"] .account-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
[data-theme="dark"] .selector-title {
color: var(--text-color);
}
[data-theme="dark"] .selector-subtitle {
color: var(--text-secondary);
}
[data-theme="dark"] .account-select-wrapper select option {
background-color: var(--card-bg);
color: var(--text-color);
}
/* 暗色模式下的表单帮助文字 */
[data-theme="dark"] .form-text {
color: var(--text-secondary);
}
/* 暗色模式下的菜单管理列表 */
[data-theme="dark"] .menu-sort-item {
background-color: var(--input-bg);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .menu-sort-item:hover {
background-color: var(--card-bg);
}
[data-theme="dark"] .menu-sort-item.dragging {
background-color: var(--bg-secondary);
}
[data-theme="dark"] .menu-sort-item .drag-handle {
color: var(--text-secondary);
}
[data-theme="dark"] .menu-sort-item .menu-name {
color: var(--text-color);
}
/* 暗色模式下的全局滚动条 */
[data-theme="dark"] {
scrollbar-color: #475569 #1e293b;
}
[data-theme="dark"] ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
[data-theme="dark"] ::-webkit-scrollbar-track {
background: #1e293b;
border-radius: 4px;
}
[data-theme="dark"] ::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 4px;
}
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
background: #64748b;
}
[data-theme="dark"] ::-webkit-scrollbar-corner {
background: #1e293b;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

+5032
View File
File diff suppressed because it is too large. Load diff
+20248
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+7
View File
@@ -0,0 +1,7 @@
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slLine truncated
//# sourceMappingURL=bootstrap.bundle.min.js.map
+6
View File
@@ -0,0 +1,6 @@
@charset "UTF-8";/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+961
View File
@@ -0,0 +1,961 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 闲鱼管理系统</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<link rel="stylesheet" href="/static/lib/bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="/static/lib/bootstrap-icons/bootstrap-icons.css">
<style>
:root {
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--success-color: #198754;
--danger-color: #dc3545;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
width: 100%;
max-width: 400px;
}
.login-header {
background: var(--primary-color);
color: white;
padding: 30px;
text-align: center;
}
.login-header h2 {
margin: 0;
font-weight: 600;
}
.login-header p {
margin: 10px 0 0 0;
opacity: 0.9;
}
.login-body {
padding: 40px 30px;
}
.form-control {
border-radius: 10px;
border: 2px solid #e9ecef;
padding: 12px 15px;
font-size: 16px;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.btn-login {
background: var(--primary-color);
border: none;
border-radius: 10px;
padding: 12px;
font-size: 16px;
font-weight: 600;
width: 100%;
transition: all 0.3s ease;
}
.btn-login:hover {
background: #0b5ed7;
transform: translateY(-2px);
}
.input-group {
position: relative;
margin-bottom: 20px;
}
.input-group i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--secondary-color);
z-index: 10;
}
.input-group .form-control {
padding-left: 45px;
}
.alert {
border-radius: 10px;
border: none;
}
.loading {
display: none;
}
.loading.show {
display: inline-block;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<img src="/static/favicon.png" alt="Logo" height="64" class="mb-3">
<h2>欢迎登录</h2>
<p>闲鱼管理系统</p>
</div>
<div class="login-body">
<!-- 登录方式选择 -->
<div class="login-tabs mb-4">
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="loginType" id="usernameLogin" value="username" checked>
<label class="btn btn-outline-primary" for="usernameLogin">用户名/密码</label>
<input type="radio" class="btn-check" name="loginType" id="emailPasswordLogin" value="email-password">
<label class="btn btn-outline-primary" for="emailPasswordLogin">邮箱/密码</label>
<input type="radio" class="btn-check" name="loginType" id="emailCodeLogin" value="email-code">
<label class="btn btn-outline-primary" for="emailCodeLogin">邮箱/验证码</label>
</div>
</div>
<form id="loginForm">
<!-- 用户名/密码登录 -->
<div id="usernameLoginForm" class="login-form-section">
<div class="input-group">
<i class="bi bi-person-fill"></i>
<input type="text" class="form-control" id="username" placeholder="请输入用户名">
</div>
<div class="input-group">
<i class="bi bi-lock-fill"></i>
<input type="password" class="form-control" id="password" placeholder="请输入密码">
</div>
<!-- 登录验证码 -->
<div id="securityCaptchaSection" class="mb-3">
<label class="form-label"><i class="bi bi-shield-check text-primary me-1"></i>安全验证</label>
<div class="row g-2">
<div class="col-7">
<input type="text" class="form-control" id="securityCaptchaCode" placeholder="输入验证码" maxlength="4" autocomplete="off">
</div>
<div class="col-5">
<div class="captcha-container" style="position: relative; height: 38px;">
<img id="securityCaptchaImage" src="" alt="安全验证码"
style="width: 100%; height: 100%; border: 1px solid #ddd; border-radius: 5px; cursor: pointer; object-fit: contain; background: #f8f9fa;"
onclick="refreshSecurityCaptcha()" title="点击刷新验证码">
<div id="securityCaptchaLoading" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
</div>
</div>
</div>
</div>
<div class="form-text text-muted">
<i class="bi bi-info-circle me-1"></i>请输入图中验证码,点击图片可刷新
</div>
</div>
</div>
<!-- 邮箱/密码登录 -->
<div id="emailPasswordLoginForm" class="login-form-section" style="display: none;">
<div class="input-group">
<i class="bi bi-envelope-fill"></i>
<input type="email" class="form-control" id="emailForPassword" placeholder="请输入邮箱地址">
</div>
<div class="input-group">
<i class="bi bi-lock-fill"></i>
<input type="password" class="form-control" id="emailPassword" placeholder="请输入密码">
</div>
<!-- 登录验证码(邮箱密码登录用) -->
<div id="emailSecurityCaptchaSection" class="mb-3">
<label class="form-label"><i class="bi bi-shield-check text-primary me-1"></i>安全验证</label>
<div class="row g-2">
<div class="col-7">
<input type="text" class="form-control" id="emailSecurityCaptchaCode" placeholder="输入验证码" maxlength="4" autocomplete="off">
</div>
<div class="col-5">
<div class="captcha-container" style="position: relative; height: 38px;">
<img id="emailSecurityCaptchaImage" src="" alt="安全验证码"
style="width: 100%; height: 100%; border: 1px solid #ddd; border-radius: 5px; cursor: pointer; object-fit: contain; background: #f8f9fa;"
onclick="refreshEmailSecurityCaptcha()" title="点击刷新验证码">
<div id="emailSecurityCaptchaLoading" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
</div>
</div>
</div>
</div>
<div class="form-text text-muted">
<i class="bi bi-info-circle me-1"></i>请输入图中验证码,点击图片可刷新
</div>
</div>
</div>
<!-- 邮箱/验证码登录 -->
<div id="emailCodeLoginForm" class="login-form-section" style="display: none;">
<div class="input-group">
<i class="bi bi-envelope-fill"></i>
<input type="email" class="form-control" id="emailForCode" placeholder="请输入邮箱地址">
</div>
<!-- 图形验证码 -->
<div class="mb-3">
<label class="form-label">图形验证码</label>
<div class="row g-2">
<div class="col-7">
<input type="text" class="form-control" id="captchaCode" placeholder="输入4位验证码" maxlength="4">
</div>
<div class="col-5">
<div class="captcha-container" style="position: relative;">
<img id="captchaImage" src="" alt="图形验证码"
style="width: 100%; height: 38px; border: 1px solid #ddd; border-radius: 5px; cursor: pointer;"
onclick="refreshCaptcha()">
<div id="captchaLoading" class="text-center" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
<div class="spinner-border spinner-border-sm" role="status"></div>
</div>
</div>
</div>
</div>
<div class="form-text">
<span id="captchaStatus">请输入图形验证码,点击图片可刷新</span>
</div>
</div>
<!-- 邮箱验证码 -->
<div class="input-group">
<input type="text" class="form-control" id="verificationCode" placeholder="输入6位验证码" maxlength="6">
<button type="button" class="btn btn-outline-secondary" id="sendCodeBtn" onclick="sendVerificationCode()" disabled>
发送验证码
</button>
</div>
<div class="form-text mb-3">
<span id="codeStatus">请先验证图形验证码</span>
</div>
</div>
<div id="errorAlert" class="alert alert-danger d-none" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<span id="errorMessage"></span>
</div>
<button type="submit" class="btn btn-primary btn-login">
<span class="loading spinner-border spinner-border-sm me-2" role="status"></span>
<span id="loginText">登录</span>
</button>
</form>
<!-- 注册链接 -->
<div id="registerSection" class="text-center mt-3">
<span class="text-muted">还没有账号?</span>
<a href="/register.html" class="text-decoration-none">
<i class="bi bi-person-plus me-1"></i>立即注册
</a>
</div>
<!-- 默认账号提示 -->
<div id="defaultLoginInfo" class="mt-4 p-3 bg-light rounded-3">
<div class="text-center">
<small class="text-muted">
<i class="bi bi-info-circle me-1"></i>
默认登录账号
</small>
</div>
<div class="mt-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<small class="text-muted">用户名:</small>
<code class="bg-white px-2 py-1 rounded">admin</code>
</div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">密码:</small>
<code class="bg-white px-2 py-1 rounded">admin123</code>
</div>
</div>
<div class="text-center mt-2">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="fillDefaultCredentials()">
<i class="bi bi-arrow-down-circle me-1"></i>
使用默认账号
</button>
</div>
</div>
</div>
</div>
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script>
const loginForm = document.getElementById('loginForm');
const errorAlert = document.getElementById('errorAlert');
const errorMessage = document.getElementById('errorMessage');
const loading = document.querySelector('.loading');
const loginText = document.getElementById('loginText');
// 安全验证码相关变量
let securityCaptchaId = null;
let emailSecurityCaptchaId = null;
let securityCaptchaRequired = true; // 默认需要验证码,会通过API检查
let loginCaptchaEnabled = true; // 登录验证码是否启用
// 检查登录验证码是否启用
async function checkLoginCaptchaEnabled() {
try {
const response = await fetch('/api/login-captcha-enabled');
const result = await response.json();
loginCaptchaEnabled = result.enabled;
securityCaptchaRequired = result.enabled;
// 根据设置显示或隐藏验证码区域
const securityCaptchaSection = document.getElementById('securityCaptchaSection');
const emailSecurityCaptchaSection = document.getElementById('emailSecurityCaptchaSection');
if (!loginCaptchaEnabled) {
if (securityCaptchaSection) securityCaptchaSection.style.display = 'none';
if (emailSecurityCaptchaSection) emailSecurityCaptchaSection.style.display = 'none';
} else {
// 根据当前登录方式显示验证码
const loginType = document.querySelector('input[name="loginType"]:checked')?.value;
if (loginType === 'username' && securityCaptchaSection) {
securityCaptchaSection.style.display = 'block';
} else if (loginType === 'email-password' && emailSecurityCaptchaSection) {
emailSecurityCaptchaSection.style.display = 'block';
}
}
return loginCaptchaEnabled;
} catch (error) {
console.error('检查登录验证码状态失败:', error);
return true; // 出错时默认需要验证码
}
}
function showError(message) {
errorMessage.textContent = message;
errorAlert.classList.remove('d-none');
}
function hideError() {
errorAlert.classList.add('d-none');
}
function setLoading(isLoading) {
if (isLoading) {
loading.classList.add('show');
loginText.textContent = '登录中...';
loginForm.querySelector('button').disabled = true;
} else {
loading.classList.remove('show');
loginText.textContent = '登录';
loginForm.querySelector('button').disabled = false;
}
}
// 检查是否需要安全验证码
async function checkSecurityCaptchaRequired() {
try {
const response = await fetch('/captcha/check-required');
const result = await response.json();
securityCaptchaRequired = result.required;
if (result.required) {
showSecurityCaptcha();
}
return result.required;
} catch (error) {
console.error('检查验证码状态失败:', error);
return false;
}
}
// 显示安全验证码
function showSecurityCaptcha() {
const loginType = document.querySelector('input[name="loginType"]:checked').value;
if (loginType === 'username') {
document.getElementById('securityCaptchaSection').style.display = 'block';
loadSecurityCaptcha();
} else if (loginType === 'email-password') {
document.getElementById('emailSecurityCaptchaSection').style.display = 'block';
loadEmailSecurityCaptcha();
}
securityCaptchaRequired = true;
}
// 加载安全验证码(用户名登录)
async function loadSecurityCaptcha() {
const captchaImage = document.getElementById('securityCaptchaImage');
const captchaLoading = document.getElementById('securityCaptchaLoading');
captchaLoading.style.display = 'block';
captchaImage.style.opacity = '0.5';
try {
const response = await fetch('/captcha/generate');
// 获取验证码ID
securityCaptchaId = response.headers.get('X-Captcha-Id');
// 获取图片
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
captchaImage.src = imageUrl;
// 清空输入框
document.getElementById('securityCaptchaCode').value = '';
} catch (error) {
console.error('加载安全验证码失败:', error);
} finally {
captchaLoading.style.display = 'none';
captchaImage.style.opacity = '1';
}
}
// 刷新安全验证码
function refreshSecurityCaptcha() {
loadSecurityCaptcha();
}
// 加载安全验证码(邮箱密码登录)
async function loadEmailSecurityCaptcha() {
const captchaImage = document.getElementById('emailSecurityCaptchaImage');
const captchaLoading = document.getElementById('emailSecurityCaptchaLoading');
captchaLoading.style.display = 'block';
captchaImage.style.opacity = '0.5';
try {
const response = await fetch('/captcha/generate');
// 获取验证码ID
emailSecurityCaptchaId = response.headers.get('X-Captcha-Id');
// 获取图片
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
captchaImage.src = imageUrl;
// 清空输入框
document.getElementById('emailSecurityCaptchaCode').value = '';
} catch (error) {
console.error('加载安全验证码失败:', error);
} finally {
captchaLoading.style.display = 'none';
captchaImage.style.opacity = '1';
}
}
// 刷新邮箱登录安全验证码
function refreshEmailSecurityCaptcha() {
loadEmailSecurityCaptcha();
}
// 填充默认登录凭据
function fillDefaultCredentials() {
document.getElementById('username').value = 'admin';
document.getElementById('password').value = 'admin123';
hideError();
// 添加一个小动画效果
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
usernameInput.style.backgroundColor = '#e3f2fd';
passwordInput.style.backgroundColor = '#e3f2fd';
setTimeout(() => {
usernameInput.style.backgroundColor = '';
passwordInput.style.backgroundColor = '';
}, 1000);
}
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideError();
setLoading(true);
const loginType = document.querySelector('input[name="loginType"]:checked').value;
let loginData = {};
try {
if (loginType === 'username') {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!username || !password) {
showError('请输入用户名和密码');
return;
}
loginData = { username, password };
// 只有在验证码启用时才验证
if (loginCaptchaEnabled) {
const captchaCode = document.getElementById('securityCaptchaCode').value.trim();
if (!captchaCode) {
showError('请输入验证码');
return;
}
loginData.captcha_id = securityCaptchaId;
loginData.captcha_code = captchaCode;
}
} else if (loginType === 'email-password') {
const email = document.getElementById('emailForPassword').value;
const password = document.getElementById('emailPassword').value;
if (!email || !password) {
showError('请输入邮箱和密码');
return;
}
if (!validateEmail(email)) {
showError('请输入有效的邮箱地址');
return;
}
loginData = { email, password };
// 只有在验证码启用时才验证
if (loginCaptchaEnabled) {
const emailCaptchaCode = document.getElementById('emailSecurityCaptchaCode').value.trim();
if (!emailCaptchaCode) {
showError('请输入验证码');
return;
}
loginData.captcha_id = emailSecurityCaptchaId;
loginData.captcha_code = emailCaptchaCode;
}
} else if (loginType === 'email-code') {
const email = document.getElementById('emailForCode').value;
const verificationCode = document.getElementById('verificationCode').value;
if (!captchaVerified) {
showError('请先验证图形验证码');
return;
}
if (!email) {
showError('请输入邮箱地址');
return;
}
if (!validateEmail(email)) {
showError('请输入有效的邮箱地址');
return;
}
if (!verificationCode || verificationCode.length !== 6) {
showError('请输入6位验证码');
return;
}
loginData = { email, verification_code: verificationCode };
}
const response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(loginData)
});
const result = await response.json();
if (result.success) {
// 保存token到localStorage
localStorage.setItem('auth_token', result.token);
// 保存用户信息到localStorage
const userInfo = {
user_id: result.user_id,
username: result.username,
is_admin: result.is_admin
};
localStorage.setItem('user_info', JSON.stringify(userInfo));
// 检查是否有重定向URL
const redirectUrl = localStorage.getItem('redirectAfterLogin');
if (redirectUrl) {
// 清除重定向URL
localStorage.removeItem('redirectAfterLogin');
// 跳转到原来的页面
window.location.href = redirectUrl;
} else {
// 默认跳转到管理页面
window.location.href = '/admin';
}
} else {
showError(result.message);
// 登录失败,刷新验证码(仅在验证码启用时)
if (loginCaptchaEnabled) {
if (loginType === 'username') {
refreshSecurityCaptcha();
} else if (loginType === 'email-password') {
refreshEmailSecurityCaptcha();
}
}
}
} catch (error) {
showError('登录失败,请检查网络连接');
} finally {
setLoading(false);
}
});
// 检查是否已经登录
const token = localStorage.getItem('auth_token');
if (token) {
// 验证token是否有效
fetch('/verify', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(result => {
if (result.authenticated) {
window.location.href = '/admin';
}
})
.catch(() => {
// token无效,清除
localStorage.removeItem('auth_token');
});
}
// 登录方式切换相关变量
let captchaVerified = false;
let countdownTimer = null;
let countdownSeconds = 0;
let sessionId = generateSessionId();
// 生成会话ID
function generateSessionId() {
return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
}
// 登录方式切换
function switchLoginType() {
const loginType = document.querySelector('input[name="loginType"]:checked').value;
// 隐藏所有表单
document.querySelectorAll('.login-form-section').forEach(section => {
section.style.display = 'none';
});
// 显示对应表单并加载验证码(仅在验证码启用时)
if (loginType === 'username') {
document.getElementById('usernameLoginForm').style.display = 'block';
// 根据验证码设置显示/隐藏验证码区域
const securityCaptchaSection = document.getElementById('securityCaptchaSection');
if (loginCaptchaEnabled) {
securityCaptchaSection.style.display = 'block';
loadSecurityCaptcha();
} else {
securityCaptchaSection.style.display = 'none';
}
} else if (loginType === 'email-password') {
document.getElementById('emailPasswordLoginForm').style.display = 'block';
// 根据验证码设置显示/隐藏验证码区域
const emailSecurityCaptchaSection = document.getElementById('emailSecurityCaptchaSection');
if (loginCaptchaEnabled) {
emailSecurityCaptchaSection.style.display = 'block';
loadEmailSecurityCaptcha();
} else {
emailSecurityCaptchaSection.style.display = 'none';
}
} else if (loginType === 'email-code') {
document.getElementById('emailCodeLoginForm').style.display = 'block';
// 每次切换到邮箱验证码登录时都加载验证码
loadCaptcha();
}
hideError();
}
// 加载图形验证码
async function loadCaptcha() {
const captchaImage = document.getElementById('captchaImage');
const captchaLoading = document.getElementById('captchaLoading');
const captchaStatus = document.getElementById('captchaStatus');
captchaLoading.style.display = 'block';
captchaImage.style.display = 'none';
try {
const response = await fetch('/generate-captcha', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_id: sessionId })
});
const result = await response.json();
if (result.success) {
captchaImage.src = result.captcha_image;
captchaStatus.textContent = '请输入图形验证码,点击图片可刷新';
captchaVerified = false;
updateSendCodeButton();
} else {
captchaStatus.textContent = '图形验证码加载失败,请刷新页面重试';
}
} catch (error) {
console.error('加载图形验证码失败:', error);
captchaStatus.textContent = '图形验证码加载失败,请检查网络连接';
} finally {
captchaLoading.style.display = 'none';
captchaImage.style.display = 'block';
}
}
// 刷新图形验证码
function refreshCaptcha() {
loadCaptcha();
}
// 验证图形验证码
async function verifyCaptcha() {
const captchaCode = document.getElementById('captchaCode').value.trim();
const captchaStatus = document.getElementById('captchaStatus');
if (!captchaCode) {
captchaStatus.textContent = '请输入图形验证码';
captchaVerified = false;
updateSendCodeButton();
return;
}
try {
const response = await fetch('/verify-captcha', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: sessionId,
captcha_code: captchaCode
})
});
const result = await response.json();
if (result.success) {
captchaStatus.innerHTML = '<span style="color: green;">✓ 图形验证码验证成功</span>';
captchaVerified = true;
} else {
captchaStatus.innerHTML = '<span style="color: red;">✗ 图形验证码错误,请重新输入</span>';
captchaVerified = false;
refreshCaptcha(); // 自动刷新验证码
}
updateSendCodeButton();
} catch (error) {
console.error('验证图形验证码失败:', error);
captchaStatus.innerHTML = '<span style="color: red;">验证失败,请检查网络连接</span>';
captchaVerified = false;
updateSendCodeButton();
}
}
// 更新发送验证码按钮状态
function updateSendCodeButton() {
const sendCodeBtn = document.getElementById('sendCodeBtn');
const email = document.getElementById('emailForCode').value.trim();
const codeStatus = document.getElementById('codeStatus');
if (captchaVerified && email && validateEmail(email)) {
sendCodeBtn.disabled = false;
codeStatus.textContent = '图形验证码已验证,可以发送邮箱验证码';
} else {
sendCodeBtn.disabled = true;
if (!captchaVerified) {
codeStatus.textContent = '请先验证图形验证码';
} else if (!email) {
codeStatus.textContent = '请先输入邮箱地址';
} else if (!validateEmail(email)) {
codeStatus.textContent = '请输入有效的邮箱地址';
}
}
}
// 邮箱格式验证
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// 开始倒计时
function startCountdown() {
countdownSeconds = 60;
const sendCodeBtn = document.getElementById('sendCodeBtn');
const codeStatus = document.getElementById('codeStatus');
sendCodeBtn.disabled = true;
countdownTimer = setInterval(() => {
countdownSeconds--;
sendCodeBtn.textContent = `重新发送 (${countdownSeconds}s)`;
codeStatus.innerHTML = `<span class="countdown">验证码已发送,${countdownSeconds}秒后可重新发送</span>`;
if (countdownSeconds <= 0) {
clearInterval(countdownTimer);
sendCodeBtn.disabled = false;
sendCodeBtn.textContent = '发送验证码';
codeStatus.textContent = '可以重新发送验证码';
}
}, 1000);
}
// 发送邮箱验证码
async function sendVerificationCode() {
const email = document.getElementById('emailForCode').value.trim();
if (!captchaVerified) {
showError('请先验证图形验证码');
return;
}
if (!email) {
showError('请先输入邮箱地址');
return;
}
if (!validateEmail(email)) {
showError('请输入有效的邮箱地址');
return;
}
try {
const response = await fetch('/send-verification-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
type: 'login',
session_id: sessionId
})
});
const result = await response.json();
if (result.success) {
startCountdown();
} else {
showError(result.message);
}
} catch (error) {
console.error('发送验证码失败:', error);
showError('发送验证码失败,请检查网络连接');
}
}
// 检查注册状态
async function checkRegistrationStatus() {
try {
const response = await fetch('/registration-status');
if (response.ok) {
const data = await response.json();
const registerSection = document.getElementById('registerSection');
if (registerSection) {
registerSection.style.display = data.enabled ? 'block' : 'none';
}
}
} catch (error) {
console.error('检查注册状态失败:', error);
// 出错时默认显示注册链接
}
}
// 检查默认登录信息显示状态
async function checkLoginInfoStatus() {
try {
const response = await fetch('/login-info-status');
const result = await response.json();
const defaultLoginInfo = document.getElementById('defaultLoginInfo');
if (result.enabled) {
defaultLoginInfo.style.display = 'block';
} else {
defaultLoginInfo.style.display = 'none';
}
} catch (error) {
console.error('检查登录信息显示状态失败:', error);
// 出错时默认显示
document.getElementById('defaultLoginInfo').style.display = 'block';
}
}
// 事件监听器
document.addEventListener('DOMContentLoaded', async function() {
// 检查注册状态
checkRegistrationStatus();
// 检查默认登录信息显示状态
checkLoginInfoStatus();
// 检查登录验证码是否启用
await checkLoginCaptchaEnabled();
// 如果验证码启用,加载登录验证码(用户名登录是默认选中的)
if (loginCaptchaEnabled) {
loadSecurityCaptcha();
}
// 登录方式切换
document.querySelectorAll('input[name="loginType"]').forEach(radio => {
radio.addEventListener('change', switchLoginType);
});
// 图形验证码输入框事件(邮箱验证码登录用)
const captchaCodeInput = document.getElementById('captchaCode');
if (captchaCodeInput) {
captchaCodeInput.addEventListener('input', function() {
if (this.value.length === 4) {
verifyCaptcha();
} else {
captchaVerified = false;
updateSendCodeButton();
document.getElementById('captchaStatus').textContent = '请输入4位图形验证码';
}
});
}
// 邮箱输入框事件
const emailForCodeInput = document.getElementById('emailForCode');
if (emailForCodeInput) {
emailForCodeInput.addEventListener('input', updateSendCodeButton);
}
});
</script>
</body>
</html>
+570
View File
@@ -0,0 +1,570 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册 - 闲鱼管理系统</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<link rel="stylesheet" href="/static/lib/bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="/static/lib/bootstrap-icons/bootstrap-icons.css">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
}
.register-container {
background: white;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
overflow: hidden;
max-width: 420px;
width: 100%;
max-height: 95vh;
overflow-y: auto;
}
.register-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.2rem;
text-align: center;
}
.register-header h2 {
margin-bottom: 0.3rem;
font-size: 1.4rem;
}
.register-header p {
margin-bottom: 0;
font-size: 0.85rem;
}
.register-body {
padding: 1.2rem;
}
.form-control {
border-radius: 8px;
border: 1px solid #e9ecef;
padding: 8px 12px;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.form-control:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.15rem rgba(102, 126, 234, 0.2);
}
.btn-register {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
padding: 10px;
font-weight: 600;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.btn-register:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-code {
border-radius: 8px;
border: 1px solid #667eea;
color: #667eea;
font-weight: 600;
transition: all 0.3s ease;
padding: 8px 12px;
font-size: 0.85rem;
}
.btn-code:hover {
background: #667eea;
color: white;
}
.btn-code:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.login-link {
text-align: center;
margin-top: 0.8rem;
}
.login-link a {
color: #667eea;
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
}
.login-link a:hover {
text-decoration: underline;
}
.alert {
border-radius: 8px;
border: none;
padding: 0.6rem 1rem;
font-size: 0.85rem;
}
.input-group {
margin-bottom: 0.8rem;
}
.countdown {
color: #6c757d;
font-size: 0.8rem;
}
.mb-3 {
margin-bottom: 0.8rem !important;
}
.form-label {
margin-bottom: 0.3rem;
font-weight: 600;
font-size: 0.85rem;
color: #495057;
}
.form-text {
margin-top: 0.2rem;
font-size: 0.75rem;
}
.captcha-container img {
height: 32px !important;
}
/* 响应式优化 */
@media (max-height: 700px) {
.register-header {
padding: 1rem;
}
.register-header h2 {
font-size: 1.2rem;
margin-bottom: 0.2rem;
}
.register-header p {
font-size: 0.8rem;
}
.register-body {
padding: 1rem;
}
.mb-3 {
margin-bottom: 0.6rem !important;
}
.form-control {
padding: 6px 10px;
}
.btn-code {
padding: 6px 10px;
font-size: 0.8rem;
}
}
@media (max-width: 480px) {
.register-container {
margin: 5px;
max-width: none;
}
.row.g-2 > * {
padding-left: 0.25rem;
padding-right: 0.25rem;
}
}
</style>
</head>
<body>
<div class="register-container">
<div class="register-header">
<h2 class="mb-0">
<i class="bi bi-person-plus-fill me-2"></i>
用户注册
</h2>
<p class="mb-0 mt-2 opacity-75">注册后即可使用闲鱼管理系统</p>
</div>
<div class="register-body">
<div id="alertContainer"></div>
<form id="registerForm">
<div class="mb-3">
<label for="username" class="form-label">
<i class="bi bi-person me-1"></i>用户名
</label>
<input type="text" class="form-control" id="username" name="username"
placeholder="3-20个字符,支持字母、数字、下划线" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">
<i class="bi bi-envelope me-1"></i>邮箱地址
</label>
<input type="email" class="form-control" id="email" name="email"
placeholder="用于接收验证码和重要通知" required>
</div>
<div class="mb-3">
<label for="captchaCode" class="form-label">
<i class="bi bi-shield-lock me-1"></i>图形验证码
</label>
<div class="row g-2">
<div class="col-7">
<input type="text" class="form-control" id="captchaCode" name="captchaCode"
placeholder="输入4位验证码" maxlength="4" required>
</div>
<div class="col-5">
<div class="captcha-container" style="position: relative;">
<img id="captchaImage" src="" alt="图形验证码"
style="width: 100%; height: 32px; border: 1px solid #ddd; border-radius: 5px; cursor: pointer;"
onclick="refreshCaptcha()">
<div id="captchaLoading" class="text-center" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
<div class="spinner-border spinner-border-sm" role="status"></div>
</div>
</div>
</div>
</div>
<div class="form-text">
<span id="captchaStatus">请输入图形验证码,点击图片可刷新</span>
</div>
</div>
<div class="mb-3">
<label for="verificationCode" class="form-label">
<i class="bi bi-shield-check me-1"></i>邮箱验证码
</label>
<div class="input-group">
<input type="text" class="form-control" id="verificationCode" name="verificationCode"
placeholder="输入6位验证码" maxlength="6" required>
<button type="button" class="btn btn-code" id="sendCodeBtn" onclick="sendVerificationCode()" disabled>
发送验证码
</button>
</div>
<div class="form-text">
<span id="codeStatus">请先验证图形验证码</span>
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">
<i class="bi bi-lock me-1"></i>设置密码
</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="至少6个字符" required>
</div>
<div class="mb-3">
<label for="confirmPassword" class="form-label">
<i class="bi bi-lock-fill me-1"></i>确认密码
</label>
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword"
placeholder="再次输入密码" required>
</div>
<button type="submit" class="btn btn-register btn-primary w-100 mt-2">
<i class="bi bi-person-plus me-1"></i>立即注册
</button>
</form>
<div class="login-link">
<span class="text-muted">已有账号?</span>
<a href="/login.html">立即登录</a>
</div>
</div>
</div>
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script>
let countdownTimer = null;
let countdownSeconds = 0;
let sessionId = generateSessionId();
let captchaVerified = false;
function generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
function showAlert(message, type = 'danger') {
const alertContainer = document.getElementById('alertContainer');
const alertHtml = `
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
<i class="bi bi-${type === 'success' ? 'check-circle' : 'exclamation-triangle'}-fill me-2"></i>
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
alertContainer.innerHTML = alertHtml;
}
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
async function loadCaptcha() {
const captchaImage = document.getElementById('captchaImage');
const captchaLoading = document.getElementById('captchaLoading');
const captchaStatus = document.getElementById('captchaStatus');
try {
captchaLoading.style.display = 'block';
captchaImage.style.display = 'none';
const response = await fetch('/generate-captcha', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_id: sessionId })
});
const result = await response.json();
if (result.success) {
captchaImage.src = result.captcha_image;
captchaStatus.textContent = '请输入图形验证码,点击图片可刷新';
captchaVerified = false;
updateSendCodeButton();
} else {
captchaStatus.textContent = '图形验证码加载失败,请刷新页面重试';
}
} catch (error) {
console.error('加载图形验证码失败:', error);
captchaStatus.textContent = '图形验证码加载失败,请检查网络连接';
} finally {
captchaLoading.style.display = 'none';
captchaImage.style.display = 'block';
}
}
function refreshCaptcha() {
sessionId = generateSessionId(); // 生成新的session ID
loadCaptcha();
// 不再自动清空验证码输入框,让用户自己决定是否清空
}
async function verifyCaptcha() {
const captchaCode = document.getElementById('captchaCode').value.trim();
const captchaStatus = document.getElementById('captchaStatus');
if (!captchaCode) {
captchaStatus.textContent = '请输入图形验证码';
captchaVerified = false;
updateSendCodeButton();
return;
}
try {
const response = await fetch('/verify-captcha', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: sessionId,
captcha_code: captchaCode
})
});
const result = await response.json();
if (result.success) {
captchaStatus.innerHTML = '<span style="color: green;">✓ 图形验证码验证成功</span>';
captchaVerified = true;
} else {
captchaStatus.innerHTML = '<span style="color: red;">✗ 图形验证码错误,请重新输入</span>';
captchaVerified = false;
refreshCaptcha(); // 自动刷新验证码
}
updateSendCodeButton();
} catch (error) {
console.error('验证图形验证码失败:', error);
captchaStatus.innerHTML = '<span style="color: red;">验证失败,请检查网络连接</span>';
captchaVerified = false;
updateSendCodeButton();
}
}
function updateSendCodeButton() {
const sendCodeBtn = document.getElementById('sendCodeBtn');
const email = document.getElementById('email').value.trim();
const codeStatus = document.getElementById('codeStatus');
if (captchaVerified && email && validateEmail(email)) {
sendCodeBtn.disabled = false;
codeStatus.textContent = '图形验证码已验证,可以发送邮箱验证码';
} else {
sendCodeBtn.disabled = true;
if (!captchaVerified) {
codeStatus.textContent = '请先验证图形验证码';
} else if (!email) {
codeStatus.textContent = '请先输入邮箱地址';
} else if (!validateEmail(email)) {
codeStatus.textContent = '请输入有效的邮箱地址';
}
}
}
function startCountdown() {
countdownSeconds = 60;
const sendCodeBtn = document.getElementById('sendCodeBtn');
const codeStatus = document.getElementById('codeStatus');
sendCodeBtn.disabled = true;
countdownTimer = setInterval(() => {
countdownSeconds--;
sendCodeBtn.textContent = `重新发送 (${countdownSeconds}s)`;
codeStatus.innerHTML = `<span class="countdown">验证码已发送,${countdownSeconds}秒后可重新发送</span>`;
if (countdownSeconds <= 0) {
clearInterval(countdownTimer);
sendCodeBtn.disabled = false;
sendCodeBtn.textContent = '发送验证码';
codeStatus.textContent = '可以重新发送验证码';
}
}, 1000);
}
async function sendVerificationCode() {
const email = document.getElementById('email').value.trim();
if (!captchaVerified) {
showAlert('请先验证图形验证码');
return;
}
if (!email) {
showAlert('请先输入邮箱地址');
return;
}
if (!validateEmail(email)) {
showAlert('请输入有效的邮箱地址');
return;
}
try {
const response = await fetch('/send-verification-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
session_id: sessionId
})
});
const result = await response.json();
if (result.success) {
showAlert(result.message, 'success');
startCountdown();
} else {
showAlert(result.message);
}
} catch (error) {
console.error('发送验证码失败:', error);
showAlert('发送验证码失败,请检查网络连接');
}
}
document.getElementById('registerForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const username = formData.get('username').trim();
const email = formData.get('email').trim();
const captchaCode = formData.get('captchaCode').trim();
const verificationCode = formData.get('verificationCode').trim();
const password = formData.get('password');
const confirmPassword = formData.get('confirmPassword');
// 前端验证
if (!captchaVerified) {
showAlert('请先验证图形验证码');
return;
}
if (username.length < 3 || username.length > 20) {
showAlert('用户名长度必须在3-20个字符之间');
return;
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
showAlert('用户名只能包含字母、数字和下划线');
return;
}
if (!validateEmail(email)) {
showAlert('请输入有效的邮箱地址');
return;
}
if (verificationCode.length !== 6) {
showAlert('请输入6位验证码');
return;
}
if (password.length < 6) {
showAlert('密码长度至少6个字符');
return;
}
if (password !== confirmPassword) {
showAlert('两次输入的密码不一致');
return;
}
try {
const response = await fetch('/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
email: email,
verification_code: verificationCode,
password: password
})
});
const result = await response.json();
if (result.success) {
showAlert(result.message, 'success');
setTimeout(() => {
window.location.href = '/login.html';
}, 2000);
} else {
showAlert(result.message);
}
} catch (error) {
console.error('注册失败:', error);
showAlert('注册失败,请检查网络连接');
}
});
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadCaptcha();
});
// 图形验证码输入框事件
document.getElementById('captchaCode').addEventListener('input', function() {
if (this.value.length === 4) {
verifyCaptcha();
} else {
captchaVerified = false;
updateSendCodeButton();
document.getElementById('captchaStatus').textContent = '请输入4位图形验证码';
}
});
// 邮箱输入框事件
document.getElementById('email').addEventListener('input', function() {
updateSendCodeButton();
});
document.getElementById('email').addEventListener('blur', function() {
const email = this.value.trim();
updateSendCodeButton();
});
</script>
</body>
</html>
+149
View File
@@ -0,0 +1,149 @@
'🎉1.发布 v1.9.2:修正账号运行态总览布局与链路判定,补强真实实例运行态读取,减少业务消息流误判未就绪;',
'🔧2.修复链路就绪统计:运行态总览统一按 WS / Session / Token / 业务流 四条主链路统计,避免出现 1 / 5 与 0 / 4 混用;',
'🔧3.修复运行态来源:优先读取账号真实活跃实例,临时 XianyuLive 实例不再注册全局实例表,避免业务消息流状态被临时实例覆盖;',
'⚡4.优化运行态总览布局:账号详情改为左侧四个状态卡、右侧链路就绪摘要卡,业务消息流补充连接未就绪与恢复中的兜底展示;',
'🎉1.发布 v1.9.1:新增业务消息流假在线检测与恢复展示,补强账号运行态诊断与前端自动刷新能力;',
'🎉2.新增业务消息流看门狗:区分心跳包与真实业务包,长时间只有心跳时自动关闭旧 WebSocket 并触发重连;',
'🎉3.新增消息流运行态诊断:账号运行态补充最近业务包、同步包、真实买家消息与假在线重连时间,支持识别“连接已通但消息流停滞”;',
'⚡4.优化运行态展示与刷新:仪表盘和账号详情新增“消息流”状态摘要,并在连接恢复中的短时间窗口自动重试刷新,减少状态滞后;',
'🎉1.发布 v1.9.0:升级账号保活诊断与历史订单同步,新增仪表盘运行态摘要和 GitHub 公告栏,并补齐多项登录与通知链路修复;',
'🎉2.新增账号诊断与运行态摘要:账号页按实际链路展示 WS / Session / Token / 轻保活 等状态,仪表盘账号卡片新增运行态摘要;',
'🎉3.重写历史订单同步:切换卖家工作台接口,补齐订单平台时间字段链路,并新增同步入口、状态面板与进度展示;',
'🔧4.修复历史订单同步稳定性:收紧同步时间范围与数量限制,降低大范围抓取导致的异常与超时;',
'🔧5.修复登录识别与恢复状态:收紧登录表单识别,找不到账号框/密码框时先复检已登录态和验证页;验证类型不明时不再默认按人脸处理,前后端展示与通知统一按实际验证类型显示;',
'🔧6.修复账号重新启用后资料被清空的问题,避免恢复启用时覆盖已有配置;',
'⚡7.优化自动回复优先级顺序,减少多规则命中时的回复偏差;',
'🎉8.新增 GitHub 公告栏:仪表盘支持展示公告横幅,并可点击查看历史公告记录;',
'🎉1.发布 v1.8.4:修复订单详情规格解析问题,新增滑块验证统计范围筛选,并优化风控日志与账号管理文案;',
'🔧2.修复订单详情规格解析失败导致自动发货被阻断的问题,补强规格提取链路的稳定性(by @82762294);',
'⚡3.优化滑块验证统计:新增当日 / 7天 / 所有范围筛选,统计卡片文案与交互更清晰;',
'⚡4.优化风控日志展示:处理结果列去除无用元数据展开,前端信息更简洁,排查更直观;',
'⚡5.优化账号管理入口与说明文案:明确扫码登录、账密登录、手动刷新 Cookie 与导入 Cookie 的使用场景,并补充账密配置指引;',
'🎉1.发布 v1.8.3:修复有头模式白屏、自动刷新Session过期导致滑块连败,并优化滑块验证策略与Token预检重试机制;',
'🔧2.修复有头模式白屏:完整反检测脚本会覆盖 document.fonts / EventTarget / Performance.now 等浏览器核心API导致页面无法渲染,有头模式改用轻量反检测脚本;',
'🔧3.修复自动刷新Session过期滑块连败:自动Token刷新路径改用干净上下文(force_clean_context),避免持久化上下文中过期Session数据导致风控升级和滑块反复失败;',
'🔧4.修复刷新模式登录状态假象:注入旧Cookie可能让前端显示"已登录"但服务端Session已过期,新增服务端Session有效性验证,过期时自动清除Cookie并重新登录;',
'⚡5.优化Token预检重试机制:手动刷新后的Token预检新增最多3次重试(渐进等待),应对密码登录Cookie在服务端生效延迟;',
'⚡6.优化滑块验证策略权重:降低历史成功率低的conservative策略权重(0.18→0.08),提高standard策略权重(0.47→0.57);',
'⚡7.优化滑块重试策略:第3次及以后优先使用学习参数加大抖动变体,无学习数据时才轮换固定策略;增加重试间隔(2-3秒→4-6秒)降低反爬触发风险;',
'⚡8.优化密码登录浏览器画像:复用完整浏览器画像配置(viewport/UA/locale)与captcha验证流程保持一致,自动刷新路径同步启用策略学习;',
'🔧9.修复持久化上下文页面异常:无iframe且无已登录态时自动清除Cookie和缓存并重新加载,恢复登录表单;',
'🔧1.发布 v1.8.2:修复 Token 刷新循环因属性未初始化导致崩溃,以及手动刷新认证预检因 asyncio 变量遮蔽导致失败的问题;',
'🔧2.修复 Token 刷新循环中 last_token_refresh_status 属性未在 __init__ 初始化,首次访问时抛出 AttributeError 导致刷新循环异常退出;',
'🔧3.修复手动刷新认证预检中 asyncio.run() 因函数内后续局部 import asyncio 遮蔽全局导入,触发 UnboundLocalError 导致预检失败;',
'🎉1.发布 v1.8.1:补齐 v1.8.0 之后的稳定性更新,重点收口滑块/Token/会话恢复、手动刷新交接、通知链路、订单买家识别与多数量文本卡券发送;',
'🔧2.修复滑块与会话恢复链路:稳定滑块恢复与令牌刷新,滑块成功后回写有效会话 Cookie,并保护关键会话字段不被不完整快照覆盖;',
'🔧3.修复刷新交接与初始化鉴权:手动刷新后新增 Token 预检、交接恢复窗口、鉴权失败冷静期与恢复锁,减少 WebSocket 已连通但因 Token 获取失败反复重试;',
'🔧4.修复通知与验证收口:统一通知派发路径,修复推送冷却、人脸验证通知类型/文案/模板渲染,并收口定时刷新误报;',
'⚡5.优化风控统计口径:账密登录/手动刷新流程里的滑块验证也会记录到风控日志与滑块统计,排查更完整;',
'🔧6.修复订单买家识别:取消订单后的系统卡片不再覆盖真实 buyer_id,避免订单买家信息被异常值污染;',
'⚡7.优化多数量纯文本卡券消息:支持 API 文本卡券批量合并发送,减少重复刷屏,卡券发货提示更简洁;',
'🎉1.发布 v1.8.0:风控日志升级为结构化会话链路,滑块验证、Token过期、账密登录和扫码刷新均支持统一追踪;',
'⚡2.优化风控看板:滑块统计改为结构化会话口径,新增更准确的成功/失败统计、筛选与详情展示;',
'⚡3.优化滑块验证反检测:引入稳定指纹配置、拟人轨迹、Cookie预热和多轮重试策略,提升通过率与稳定性;',
'🔧4.修复手动刷新与自动刷新互踩:新增手动刷新保护期、扫码登录缓冲和Cookie有效性校验;',
'🔧5.修复密码登录风控收口不稳定:新增失败退避、状态管理和浏览器侧兜底判定,减少登录回滚误判;',
'⚡6.优化图片上传错误追踪:新增错误类型、状态码和错误信息记录,便于前端给出更准确提示;',
'🔧7.修复商品同步后详情不更新:同步指定页/所有页时会强制刷新已有商品详情;',
'⚡8.优化商品管理文案:明确区分“同步商品”和“刷新列表”,避免误操作;',
'🔧1.修复扫码登录人脸验证会话丢失:风控场景改为在服务端保持原始会话并生成验证二维码;',
'🔧2.修复扫码成功后再次进入滑块验证:新增真实Cookie合并与首次Token预热保护;',
'⚡3.优化扫码风控状态收口:增加浏览器侧兜底判定,验证完成后可更稳定进入登录成功;',
'⚡4.优化扫码登录前端提示:减少重复提示并统一验证过程中的状态反馈;',
'🔧1.修复订单号误识别:收紧消息中的订单号提取规则,避免普通消息里的 messageId 被误判成订单并生成处理中假订单;',
'🔧2.修复销售额卡片获取失败:统一销售统计口径并跳过空金额、脏金额订单,避免看板接口报错;',
'⚡3.优化仪表盘展示:重构账号概览、订单数据看板、销售趋势与发货日志布局,关键信息更清晰易读;',
'⚡4.优化发货日志:拆分规则、匹配结果、触发方式和规格状态列,并简化规格状态显示便于快速排查;',
'🔧1.修复热更新历史清单读取逻辑:优先读取上一版 Release 资产中的 update_files.json,避免 deleted_files 丢失;',
'🔧2.修复同版本热更新可能回滚清单生成脚本:补齐删除清单并完善后续版本生成逻辑;',
'🎉1.新增账号列表商品一键擦亮入口:可批量执行当前在售商品擦亮;',
'🎉2.新增每日定时擦亮任务:支持按账号配置启用状态、执行时段与随机延迟;',
'⚡3.优化后台定时任务调度:新增执行结果记录,可查看下次执行时间和最近运行情况;',
'⚡4.优化管理端擦亮操作体验:补充擦亮入口与设置弹窗,日常运营更方便;',
'⚡5.优化账号管理页列宽与仪表盘赞助按钮样式:提升界面可读性与交互一致性;',
'🔧1.修复订单规格识别混乱:优先从结构化订单响应提取真实skuInfo与数量,减少页面元素缺失导致的漏识别;',
'🔧2.修复单规格误判为双规格:正文兜底改为去重并在候选歧义时直接放弃,阻止标题/描述污染spec2;',
'⚡3.优化正文规格过滤:仅允许更像真实SKU字段的规格名进入兜底,过滤时间、广告文案和无关冒号文本;',
'🔧4.修复历史残留第二规格污染:订单明确解析为单规格时,自动清空旧spec_name_2/spec_value_2;',
'⚡5.优化多规格商品缓存复用条件:刷新订单详情时要求金额、状态和主规格同时有效,降低脏缓存复用风险;',
'🔧1.修复系统消息状态优先级与消息分流逻辑:阻止订单状态回退,并减少系统通知噪音;',
'🔧2.修复订单消息漏单:扩展订单号提取来源,增强sid兜底查单与近邻回退;',
'⚡3.优化订单详情补刷:优先采用结构化状态信号,并增加状态门控与冷却;',
'🔧4.修复无规格商品误发:收紧规则匹配与sid兜底发货链路,降低串单风险;',
'🔧5.修复小刀订单漏发:新增成功证据持久化,缺少完整待发货卡片时仍可自动发货兜底;',
'🔧6.修复闲鱼币抵扣金额误判:优先保留或推导真实实付金额,避免把原价当成交价;',
'⚡7.优化发货日志展示:新增“已跳过”状态,并过滤成功后的重复skipped记录;',
'🔧1.修复简化消息buyer_id误提取:区分sid与PNM格式消息,不再将会话ID误提取为buyer_id,避免防串单校验误判;',
'🔧2.修复无效buyer_id污染订单数据:新增buyer_id可信度校验,识别unknown_user等无效占位符,防串单校验时自动跳过不可信值;',
'🔧3.修复数据库buyer_id被占位符覆盖:更新订单时跳过无效buyer_id覆盖已有有效值,新增订单时自动净化为空值;',
'🎉4.新增PNM格式消息解析:从message[4]提取buyer_id、买家昵称和商品ID,提升订单信息完整度;',
'🔧5.修复订单详情锁事件循环不匹配:Web API调用时自动重建锁,避免跨事件循环死锁;',
'⚡6.统一buyer_id/user_id默认值:各处unknown_user占位符改为None,避免脏数据进入订单与发货链路;',
'🔧1.修复买家昵称污染:拦截系统文案进入订单与发货日志,避免记录错误昵称;',
'🔧2.修复小刀商品归属误判:缓存未命中时回退刷新商品列表,避免误跳过自动发货;',
'🔧3.修复连续下单串单:旧关单消息不再污染后续新订单状态;',
'🔧4.修复sid简化消息误判:已发货/已完成/已关闭订单不再误报“未找到订单”;',
'⚡5.统一北京时间读取:前端时间显示与销售统计、日期筛选统一按北京时间口径处理;',
'🎉1.热更新弹窗新增“本次跳过”和“忽略此版本”操作,支持按版本跳过当前更新提示;',
'⚡2.优化仪表盘更新入口:检查更新按钮新增自动检查开关和忽略版本管理,设置仅当前浏览器生效;',
'🔧1.优化单规格自动发货策略:优先匹配单规格规则,精确未命中时可降级匹配普通关键字规则;',
'⚡2.新增唯一普通规则兜底:单规格精确匹配失败时,仅在唯一命中一条普通规则时自动发货;',
'⚡3.发货日志新增“单规兜底”标记,便于区分精确命中和普通规则降级命中;',
'🔧1.补发 captcha_control.html 热更新资源,避免 v1.5.5 下该文件因 Release 资源未刷新而反复提示更新;',
'⚡2.优化热更新检查日志:不再逐文件输出“文件已是最新”,改为输出更新检查汇总;',
'🎉1.优化热更新清单生成:自动扫描 Python/HTML/静态资源与前端源码文件,无需手动维护白名单;',
'🎉2.新增发版预检查脚本:发布前可检查版本号、改名/删除文件及未跟踪热更新文件;',
'🎉3.支持热更新删除旧文件:更新前自动备份,再按清单安全清理历史旧文件;',
'⚡4.优化发版流程:update_files.json 改为由 GitHub Actions 自动生成并上传 Release,无需提交到仓库;',
'🔧1.统一热更新相关文本文件行尾:补充 .gitattributes 规则,固定 Python/JS/HTML/CSS 为 LF;',
'🔧2.修复同版本误报更新:避免因 CRLF/LF 差异导致热更新将同版本文件判定为可更新;',
'🔧1.修复热更新管理员权限判断:执行更新时按管理员身份校验,不再强依赖用户名必须为 admin;',
'🔧2.修复热更新失败提示:前端优先展示后端返回的 detail 信息,避免只显示“未知错误”;',
'🎉1.GitHub Actions 自动生成更新清单:发布 Release 前自动生成并上传 update_files.json;',
'⚡2.优化热更新检测:本地版本变更后无需重启服务即可重新检查更新;',
'🔧3.修复热更新权限判断:按管理员身份校验,不再强依赖用户名必须为 admin;',
'🔧4.优化更新失败提示:前端可直接显示后端 detail 错误信息,便于排查;',
'🎉1.接入 GitHub Releases 在线更新:支持从最新 Release 读取 update_files.json 检查热更新;',
'🎉2.新增仪表盘检查更新入口:管理员可在版本区直接检查并执行热更新;',
'⚡3.优化更新清单解析:兼容 GitHub 资产返回 application/octet-stream,避免检查更新失败;',
'⚡4.优化版本区显示:版本号与检查更新入口统一为 badge 样式并修复垂直居中;',
'🎉5.新增 GitHub Actions 自动发布工作流:push 到 main 且版本变化后可自动创建 tag 和 Release;',
'🎉1.新增仪表盘发货日志面板:与账号详情5:5并排展示,可查看最近发货事件;',
'🎉2.新增发货日志接口:支持读取最近发货日志(/delivery-logs/recent);',
'⚡3.优化发货追踪:自动发货与手动发货统一记录真实发货事件;',
'⚡4.优化日志字段:新增规则关键词、匹配模式(精确/兜底)、渠道(自动/手动)与失败原因;',
'🔧5.修复自动确认发货失败后仍继续发送发货内容的问题,现已改为失败即阻断;',
'🔧6.修复简化消息路径重复确认导致漏发风险,统一由_auto_delivery执行一次确认;',
'🔧7.修复小刀流程误发货:待刀成仅执行免拼,成功小刀待发货才触发发货;',
'🔧8.修复非系统消息可触发自动发货的问题,并增强sid兜底订单一致性校验;',
'🎉1.新增AI回复配置API类型能力:支持OpenAI Chat/Responses、Gemini、Anthropic、Azure OpenAI、Ollama;',
'🔧2.修复DashScope兼容模式误判为百炼应用导致“未找到app_id”报错的问题;',
'⚡3.优化AI配置预设:增加api_type维度,预设保存/切换/自动匹配更准确;',
'⚡4.优化AI回复配置弹窗:按连接层/策略层/语义层/验证层重构布局,提升设计感;',
'⚡5.优化AI配置暗色模式和移动端适配,视觉层级更清晰;',
'⚡6.优化API类型:下线“DashScope(百炼应用)”入口,历史值自动映射为自动识别;',
'⚡7.优化提示词编辑区:三个输入框高度统一;',
'⚡8.优化页面标题:浏览器标题统一为“闲鱼管理系统”;',
'🎉1.新增回复延迟配置:账号列表支持设置1-10秒防抖延迟,修改后实时生效;',
'⚡2.优化系统消息过滤:关键字改为部分匹配,减少符号/空格差异导致的漏匹配;',
'⚡3.优化系统消息过滤精确度:避免误匹配买家正常消息(如“你已发货”);',
'⚡4.优化账号列表布局:表格内容居中并调整列宽分配;',
'🎉1.新增暗色模式支持,可在系统设置中切换主题;',
'🔧2.修复暗色模式刷新页面闪烁问题;',
'🔧3.修复点击导航链接会刷新页面的问题;',
'🎉4.下单时自动获取并保存买家昵称;',
'🔧5.修复下单时买家昵称提取错误的问题;',
'🔧6.修复订单管理商品ID提取错误的问题;',
'🔧7.修复递归搜索误提取tradeId等非商品ID的问题;',
'⚡8.优化滑块验证重试策略,缩短等待时间;',
'🔧9.修复交易关闭时订单状态不更新的问题;',
'🎉10.新增qq直接回复闲鱼消息,无需在进入闲鱼(注意只能有公网访问的才行);
①回复哪条消息引用即可
②系统设置维护qq回复秘钥
③在qq上给机器人发送 咸鱼绑定 key:url 其中key是你系统设置配置的秘钥,url 是你公网服务器的 ip加端口,例如 zheshimiyao:http://10.12.13.14:8080
④如果没有公网,可联系我购买内网穿透服务,2块一个月,物美价廉',
'🎉2.支持自动回复暂停时间支持设置为0;',
'🎉3.修复自动回复关键词无法修改的问题;',
'🎉4.修复订单管理普通用户无法查看的问题;',
'🎉5.删除自动发货界面的发货数量,防止误解,想实现多数量发货在商品管理开启;',
'🎉6.增加项目使用人数统计;',
@@ -0,0 +1,421 @@
// ==UserScript==
// @name 闲鱼聊天暗色模式
// @namespace https://github.com/xianyu-auto-reply
// @version 2.0.0
// @description 为闲鱼聊天页面添加暗色模式支持
// @author XianyuAutoReply
// @match https://www.goofish.com/im*
// @match https://www.goofish.com/personal*
// @match https://goofish.com/im*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const STORAGE_KEY = 'goofish_dark_mode';
function isSystemDarkMode() {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function getDarkModeSetting() {
const saved = GM_getValue(STORAGE_KEY, 'auto');
if (saved === 'auto') return isSystemDarkMode();
return saved === 'true';
}
// 简洁的暗色配色 - 参考 macOS/Discord 风格
const darkModeCSS = `
/* ==================== 闲鱼聊天暗色模式 v2.0 ==================== */
/* CSS 变量定义 */
:root {
--dark-bg-1: #1a1a1a;
--dark-bg-2: #242424;
--dark-bg-3: #2d2d2d;
--dark-bg-4: #363636;
--dark-bg-hover: #3d3d3d;
--dark-border: #404040;
--dark-text-1: #ffffff;
--dark-text-2: #b3b3b3;
--dark-text-3: #808080;
--dark-accent: #0a84ff;
--dark-accent-hover: #409cff;
--dark-msg-self: #0a84ff;
--dark-msg-other: #363636;
--dark-success: #30d158;
--dark-warning: #ff9f0a;
--dark-danger: #ff453a;
}
/* ===== 全局重置 ===== */
html, body {
background-color: var(--dark-bg-1) !important;
color: var(--dark-text-1) !important;
}
/* ===== 所有容器 ===== */
body > div,
#ice-container,
#ice-container *,
#root,
[class*="content-container"],
[class*="page-im"],
[class*="im-main"],
[class*="chat-container"],
[class*="chat-main"],
.ant-layout,
.ant-layout-has-sider,
.ant-layout-content,
.ant-layout-sider,
.ant-layout-sider-children {
background-color: var(--dark-bg-1) !important;
}
/* ===== 会话列表 ===== */
[class*="conversation-list"],
[class*="conv-list-scroll"],
[class*="conv-header"] {
background-color: var(--dark-bg-1) !important;
border-color: var(--dark-border) !important;
}
[class*="conversation-item"] {
background-color: var(--dark-bg-1) !important;
border-color: var(--dark-border) !important;
transition: background-color 0.15s ease;
}
[class*="conversation-item"]:hover {
background-color: var(--dark-bg-3) !important;
}
/* ===== 聊天区域 ===== */
[class*="message-list"],
[class*="scroll-container"],
.infinite-scroll-component,
.infinite-scroll-component__outerdiv {
background-color: var(--dark-bg-2) !important;
}
/* ===== 消息气泡 ===== */
[class*="message-text-left"],
[style*="background-color: rgb(242, 242, 244)"],
[style*="background-color: rgb(242,242,244)"] {
background-color: var(--dark-msg-other) !important;
color: var(--dark-text-1) !important;
border-radius: 16px !important;
}
[class*="message-text-right"],
[style*="background-color: rgb(255, 230, 15)"],
[style*="background-color: rgb(255,230,15)"] {
background-color: var(--dark-msg-self) !important;
color: #ffffff !important;
border-radius: 16px !important;
}
[class*="message-text"] *,
[class*="message-text-left"] *,
[class*="message-text-right"] * {
color: inherit !important;
}
/* ===== 文字颜色 ===== */
/* 用户昵称等主要文字 */
[class*="text1--"],
[class*="nick"],
[class*="name"] {
color: var(--dark-text-1) !important;
}
/* 次要文字(摘要、时间等)*/
[class*="text2--"],
[class*="time"],
[class*="desc"] {
color: var(--dark-text-2) !important;
}
/* 强制覆盖内联深色文字 */
[style*="color: rgb(0, 0, 0)"],
[style*="color: rgb(31, 31, 31)"],
[style*="color: rgb(51, 51, 51)"],
[style*="color:rgb(0,"],
[style*="color:rgb(31,"],
[style*="color:rgb(51,"] {
color: var(--dark-text-1) !important;
}
[style*="color: rgb(102, 102, 102)"],
[style*="color: rgb(153, 153, 153)"],
[style*="color: rgb(163, 163, 163)"] {
color: var(--dark-text-2) !important;
}
/* ===== 发送框 ===== */
[class*="sendbox"] {
background-color: var(--dark-bg-2) !important;
border-color: var(--dark-border) !important;
}
[class*="sendbox-bottom"],
[class*="sendbox-topbar"] {
background-color: var(--dark-bg-2) !important;
}
/* ===== 输入框 ===== */
textarea,
input[type="text"],
.ant-input,
[class*="textarea"] {
background-color: var(--dark-bg-3) !important;
color: var(--dark-text-1) !important;
border-color: var(--dark-border) !important;
border-radius: 8px !important;
}
textarea::placeholder,
.ant-input::placeholder {
color: var(--dark-text-3) !important;
}
textarea:focus,
.ant-input:focus {
border-color: var(--dark-accent) !important;
box-shadow: 0 0 0 2px rgba(10, 132, 255, 0.2) !important;
}
/* ===== 按钮 ===== */
.ant-btn {
background-color: var(--dark-bg-3) !important;
color: var(--dark-text-1) !important;
border-color: var(--dark-border) !important;
}
.ant-btn:hover {
background-color: var(--dark-bg-hover) !important;
}
[class*="sendbox-bottom"] .ant-btn:not([disabled]) {
background-color: var(--dark-accent) !important;
color: #ffffff !important;
border: none !important;
}
[class*="sendbox-bottom"] .ant-btn:not([disabled]):hover {
background-color: var(--dark-accent-hover) !important;
}
[class*="sendbox-bottom"] .ant-btn[disabled] {
background-color: var(--dark-bg-4) !important;
color: var(--dark-text-3) !important;
opacity: 0.6;
}
/* ===== 订单状态标签 ===== */
[class*="order-wait"] {
background-color: rgba(255, 159, 10, 0.15) !important;
color: var(--dark-warning) !important;
}
[class*="order-success"] {
background-color: rgba(48, 209, 88, 0.15) !important;
color: var(--dark-success) !important;
}
[class*="order-close"] {
background-color: rgba(255, 69, 58, 0.15) !important;
color: var(--dark-danger) !important;
}
/* ===== 顶部商品栏 ===== */
[class*="message-topbar"],
[class*="messageHeadBtnContainer"] {
background-color: var(--dark-bg-2) !important;
border-color: var(--dark-border) !important;
}
/* ===== 价格 ===== */
[class*="price"],
[class*="money"] {
color: var(--dark-warning) !important;
}
/* ===== 链接 ===== */
a {
color: var(--dark-accent) !important;
}
a:hover {
color: var(--dark-accent-hover) !important;
}
/* ===== 下拉菜单 ===== */
.ant-dropdown,
.ant-dropdown-menu {
background-color: var(--dark-bg-3) !important;
border-color: var(--dark-border) !important;
}
.ant-dropdown-menu-item {
color: var(--dark-text-1) !important;
}
.ant-dropdown-menu-item:hover {
background-color: var(--dark-bg-hover) !important;
}
/* ===== 右侧工具栏 ===== */
[class*="sidebar-container"],
[class*="sidebar-item-wrap"],
[class*="sidebar-item-container"] {
background-color: var(--dark-bg-1) !important;
}
[class*="sidebar-item-wrap"]:hover {
background-color: var(--dark-bg-3) !important;
}
[class*="sidebar-item-text"] {
color: var(--dark-text-2) !important;
}
/* ===== 图标 ===== */
.anticon,
[class*="Icon"],
[class*="icon"] {
color: var(--dark-text-1) !important;
opacity: 0.85;
}
/* 图片类图标(如清除未读等)增加亮度 */
img[alt],
img[src*="icon"],
img[src*="Icon"],
[class*="icon"] img,
[class*="header"] img {
filter: brightness(1.3) contrast(1.1) !important;
}
/* 头像图片不处理 */
img[style*="border-radius"],
[class*="avatar"] img {
filter: none !important;
}
/* ===== 骨架屏 ===== */
.ant-skeleton-avatar,
.ant-skeleton-button {
background: linear-gradient(90deg, var(--dark-bg-3) 25%, var(--dark-bg-4) 50%, var(--dark-bg-3) 75%) !important;
background-size: 200% 100% !important;
}
/* ===== 滚动条 ===== */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--dark-bg-4);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--dark-text-3);
}
.rc-virtual-list-scrollbar-thumb {
background-color: var(--dark-bg-4) !important;
}
/* ===== 强制覆盖白色背景 ===== */
[style*="background-color: rgb(255, 255, 255)"],
[style*="background-color: rgb(252, 252, 252)"],
[style*="background-color: rgb(247, 248, 250)"],
[style*="background: rgb(255, 255, 255)"],
[style*="background: white"],
[style*="background-color: white"] {
background-color: var(--dark-bg-2) !important;
}
/* ===== 图片容器 ===== */
.ant-image,
[class*="image-container"] {
background-color: var(--dark-bg-3) !important;
border-radius: 8px;
}
/* ===== 文字通用继承 ===== */
p, span, div, li, label, h1, h2, h3, h4, h5, h6 {
color: inherit;
}
/* ===== 表单元素 ===== */
.ant-select,
.ant-select-selector,
.ant-input-group-addon {
background-color: var(--dark-bg-3) !important;
border-color: var(--dark-border) !important;
color: var(--dark-text-1) !important;
}
/* ===== 模态框 ===== */
.ant-modal-content,
.ant-modal-header,
.ant-modal-body,
.ant-modal-footer {
background-color: var(--dark-bg-2) !important;
border-color: var(--dark-border) !important;
color: var(--dark-text-1) !important;
}
.ant-modal-close-x {
color: var(--dark-text-2) !important;
}
`;
function injectDarkMode() {
if (getDarkModeSetting()) {
GM_addStyle(darkModeCSS);
console.log('[闲鱼暗色模式] v2.0 已启用');
}
}
function toggleDarkMode() {
const current = GM_getValue(STORAGE_KEY, 'auto');
let next;
if (current === 'auto') {
next = 'true';
} else if (current === 'true') {
next = 'false';
} else {
next = 'auto';
}
GM_setValue(STORAGE_KEY, next);
location.reload();
}
const currentSetting = GM_getValue(STORAGE_KEY, 'auto');
const statusText = currentSetting === 'auto' ? '跟随系统' : (currentSetting === 'true' ? '已开启' : '已关闭');
GM_registerMenuCommand(`切换暗色模式 (当前: ${statusText})`, toggleDarkMode);
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (GM_getValue(STORAGE_KEY, 'auto') === 'auto') {
location.reload();
}
});
}
injectDarkMode();
})();
+1
View File
@@ -0,0 +1 @@
v1.9.2
+568
View File
@@ -0,0 +1,568 @@
const crypto = require('crypto')
const generate_mid = () => {
return "" + Math.floor(1e3 * Math.random()) + new Date().getTime() + " 0"
}
const generate_uuid = () => {
return "-" + Date.now() + "1"
}
const generate_device_id = (user_id) => {
for (var ee, et = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""), en = [], eo = Math.random, ei = 0; ei < 36; ei++)
8 === ei || 13 === ei || 18 === ei || 23 === ei ? en[ei] = "-" : 14 === ei ? en[ei] = "4" : (ee = 0 | 16 * eo(),
en[ei] = et[19 === ei ? 3 & ee | 8 : ee]);
return en.join("") + "-" + user_id
}
const generate_sign = (t, token, data) => {
const j = t
const h = 34839810
const msg = token + "&" + j + "&" + h + "&" + data
const md5 = crypto.createHash('md5')
md5.update(msg)
return md5.digest('hex')
}
eM = new TextDecoder("utf-8");
eL = null
var sv = Array(4096);
function sg() {
var ee, et = eI[aG++];
if (!(et >= 160) || !(et < 192))
return aG--,
sy(a5());
if (et -= 160,
aX >= aG)
return eR.slice(aG - aq, (aG += et) - aq);
if (!(0 == aX && eO < 180))
return se(et);
var en = (et << 5 ^ (et > 1 ? eZ.getUint16(aG) : et > 0 ? eI[aG] : 0)) & 4095
, eo = sv[en]
, ei = aG
, ec = aG + et - 3
, eu = 0;
if (eo && eo.bytes == et) {
for (; ei < ec;) {
if ((ee = eZ.getUint32(ei)) != eo[eu++]) {
ei = 1879048192;
break
}
ei += 4
}
for (ec += 3; ei < ec;)
if ((ee = eI[ei++]) != eo[eu++]) {
ei = 1879048192;
break
}
if (ei === ec)
return aG = ei,
eo.string;
ec -= 3,
ei = aG
}
for (eo = [],
sv[en] = eo,
eo.bytes = et; ei < ec;)
ee = eZ.getUint32(ei),
eo.push(ee),
ei += 4;
for (ec += 3; ei < ec;)
ee = eI[ei++],
eo.push(ee);
var ed = et < 16 ? su(et) : sl(et);
return null != ed ? eo.string = ed : eo.string = se(et)
}
function sy(ee) {
if ("string" == typeof ee)
return ee;
if ("number" == typeof ee || "boolean" == typeof ee || "bigint" == typeof ee)
return ee.toString();
if (null == ee)
return ee + "";
throw Error("Invalid property type for record", typeof ee)
}
r9 = function (ee) {
return atob(rW(ee))
}
rW = function (ee) {
return ee.replace(/[^A-Za-z0-9\+\/]/g, "")
}
aW = {
"useRecords": false,
"mapsAsObjects": true
}
aJ = false
eP = []
function a3(ee) {
if (!aW.trusted && !aJ) {
var et, en, eo = eP.sharedLength || 0;
eo < eP.length && (eP.length = eo)
}
if (aW.randomAccessStructure && eI[aG] < 64 && eI[aG] >= 32 && ej ? (et = ej(eI, aG, eO, aW),
eI = null,
!(ee && ee.lazy) && et && (et = et.toJSON()),
aG = eO) : et = a5(),
eD && (aG = eD.postBundlePosition,
eD = null),
aJ && (eP.restoreStructures = null),
aG == eO)
eP && eP.restoreStructures && a4(),
eP = null,
eI = null,
eL && (eL = null);
else if (aG > eO)
throw Error("Unexpected end of MessagePack data");
else if (!aJ) {
try {
en = JSON.stringify(et, function (ee, et) {
return "bigint" == typeof et ? "".concat(et, "n") : et
}).slice(0, 100)
} catch (ee) {
en = "(JSON view not available " + ee + ")"
}
throw Error("Data read, but end of buffer not reached " + en)
}
return et
}
function so(ee) {
if (ee < 16 && (et = su(ee)))
return et;
if (ee > 64 && eM)
return eM.decode(eI.subarray(aG, aG += ee));
var et, en = aG + ee, eo = [];
for (et = ""; aG < en;) {
var ei = eI[aG++];
if ((128 & ei) === 0)
eo.push(ei);
else if ((224 & ei) === 192) {
var ec = 63 & eI[aG++];
eo.push((31 & ei) << 6 | ec)
} else if ((240 & ei) === 224) {
var eu = 63 & eI[aG++]
, ed = 63 & eI[aG++];
eo.push((31 & ei) << 12 | eu << 6 | ed)
} else if ((248 & ei) === 240) {
var ef = (7 & ei) << 18 | (63 & eI[aG++]) << 12 | (63 & eI[aG++]) << 6 | 63 & eI[aG++];
ef > 65535 && (ef -= 65536,
eo.push(ef >>> 10 & 1023 | 55296),
ef = 56320 | 1023 & ef),
eo.push(ef)
} else
eo.push(ei);
eo.length >= 4096 && (et += sc.apply(String, eo),
eo.length = 0)
}
return eo.length > 0 && (et += sc.apply(String, eo)),
et
}
se = so;
var sc = String.fromCharCode;
function sl(ee) {
for (var et = aG, en = Array(ee), eo = 0; eo < ee; eo++) {
var ei = eI[aG++];
if ((128 & ei) > 0) {
aG = et;
return
}
en[eo] = ei
}
return sc.apply(String, en)
}
function su(ee) {
if (ee < 4) {
if (ee < 2) {
if (0 === ee)
return "";
var et = eI[aG++];
if ((128 & et) > 1) {
aG -= 1;
return
}
return sc(et)
}
var en = eI[aG++]
, eo = eI[aG++];
if ((128 & en) > 0 || (128 & eo) > 0) {
aG -= 2;
return
}
if (ee < 3)
return sc(en, eo);
var ei = eI[aG++];
if ((128 & ei) > 0) {
aG -= 3;
return
}
return sc(en, eo, ei)
}
var ec = eI[aG++]
, eu = eI[aG++]
, ed = eI[aG++]
, ef = eI[aG++];
if ((128 & ec) > 0 || (128 & eu) > 0 || (128 & ed) > 0 || (128 & ef) > 0) {
aG -= 4;
return
}
if (ee < 6) {
if (4 === ee)
return sc(ec, eu, ed, ef);
var ep = eI[aG++];
if ((128 & ep) > 0) {
aG -= 5;
return
}
return sc(ec, eu, ed, ef, ep)
}
if (ee < 8) {
var em = eI[aG++]
, eh = eI[aG++];
if ((128 & em) > 0 || (128 & eh) > 0) {
aG -= 6;
return
}
if (ee < 7)
return sc(ec, eu, ed, ef, em, eh);
var ev = eI[aG++];
if ((128 & ev) > 0) {
aG -= 7;
return
}
return sc(ec, eu, ed, ef, em, eh, ev)
}
var eg = eI[aG++]
, eC = eI[aG++]
, eS = eI[aG++]
, ew = eI[aG++];
if ((128 & eg) > 0 || (128 & eC) > 0 || (128 & eS) > 0 || (128 & ew) > 0) {
aG -= 8;
return
}
if (ee < 10) {
if (8 === ee)
return sc(ec, eu, ed, ef, eg, eC, eS, ew);
var eE = eI[aG++];
if ((128 & eE) > 0) {
aG -= 9;
return
}
return sc(ec, eu, ed, ef, eg, eC, eS, ew, eE)
}
if (ee < 12) {
var ek = eI[aG++]
, e_ = eI[aG++];
if ((128 & ek) > 0 || (128 & e_) > 0) {
aG -= 10;
return
}
if (ee < 11)
return sc(ec, eu, ed, ef, eg, eC, eS, ew, ek, e_);
var eT = eI[aG++];
if ((128 & eT) > 0) {
aG -= 11;
return
}
return sc(ec, eu, ed, ef, eg, eC, eS, ew, ek, e_, eT)
}
var eN = eI[aG++]
, eM = eI[aG++]
, eO = eI[aG++]
, eP = eI[aG++];
if ((128 & eN) > 0 || (128 & eM) > 0 || (128 & eO) > 0 || (128 & eP) > 0) {
aG -= 12;
return
}
if (ee < 14) {
if (12 === ee)
return sc(ec, eu, ed, ef, eg, eC, eS, ew, eN, eM, eO, eP);
var eR = eI[aG++];
if ((128 & eR) > 0) {
aG -= 13;
return
}
return sc(ec, eu, ed, ef, eg, eC, eS, ew, eN, eM, eO, eP, eR)
}
var eD = eI[aG++]
, eL = eI[aG++];
if ((128 & eD) > 0 || (128 & eL) > 0) {
aG -= 14;
return
}
if (ee < 15)
return sc(ec, eu, ed, ef, eg, eC, eS, ew, eN, eM, eO, eP, eD, eL);
var eZ = eI[aG++];
if ((128 & eZ) > 0) {
aG -= 15;
return
}
return sc(ec, eu, ed, ef, eg, eC, eS, ew, eN, eM, eO, eP, eD, eL, eZ)
}
function ss(ee) {
if (aW.mapsAsObjects) {
for (var et = {}, en = 0; en < ee; en++) {
var eo = sg();
"__proto__" === eo && (eo = "__proto_"),
et[eo] = a5()
}
return et
}
for (var ei = new Map, ec = 0; ec < ee; ec++)
ei.set(a5(), a5());
return ei
}
function a5() {
var ee, et = eI[aG++];
if (et < 160) {
if (et < 128) {
if (et < 64)
return et;
var en = eP[63 & et] || aW.getStructures && a9()[63 & et];
return en ? (en.read || (en.read = a8(en, 63 & et)),
en.read()) : et
}
if (et < 144) {
if (et -= 128,
aW.mapsAsObjects) {
for (var eo = {}, ei = 0; ei < et; ei++) {
var ec = sg();
"__proto__" === ec && (ec = "__proto_"),
eo[ec] = a5()
}
return eo
}
for (var eu = new Map, ed = 0; ed < et; ed++)
eu.set(a5(), a5());
return eu
}
for (var ef = Array(et -= 144), ep = 0; ep < et; ep++)
ef[ep] = a5();
return aW.freezeData ? Object.freeze(ef) : ef
}
if (et < 192) {
var em = et - 160;
if (aX >= aG)
return eR.slice(aG - aq, (aG += em) - aq);
if (0 == aX && eO < 140) {
var eh = em < 16 ? su(em) : sl(em);
if (null != eh)
return eh
}
return se(em)
}
switch (et) {
case 192:
return null;
case 193:
if (eD) {
if ((ee = a5()) > 0)
return eD[1].slice(eD.position1, eD.position1 += ee);
return eD[0].slice(eD.position0, eD.position0 -= ee)
}
return aY;
case 194:
return !1;
case 195:
return !0;
case 196:
if (void 0 === (ee = eI[aG++]))
throw Error("Unexpected end of buffer");
return sm(ee);
case 197:
return ee = eZ.getUint16(aG),
aG += 2,
sm(ee);
case 198:
return ee = eZ.getUint32(aG),
aG += 4,
sm(ee);
case 199:
return sh(eI[aG++]);
case 200:
return ee = eZ.getUint16(aG),
aG += 2,
sh(ee);
case 201:
return ee = eZ.getUint32(aG),
aG += 4,
sh(ee);
case 202:
if (ee = eZ.getFloat32(aG),
aW.useFloat32 > 2) {
var ev = sT[(127 & eI[aG]) << 1 | eI[aG + 1] >> 7];
return aG += 4,
(ev * ee + (ee > 0 ? .5 : -.5) >> 0) / ev
}
return aG += 4,
ee;
case 203:
return ee = eZ.getFloat64(aG),
aG += 8,
ee;
case 204:
return eI[aG++];
case 205:
return ee = eZ.getUint16(aG),
aG += 2,
ee;
case 206:
return ee = eZ.getUint32(aG),
aG += 4,
ee;
case 207:
return "number" === aW.int64AsType ? ee = 4294967296 * eZ.getUint32(aG) + eZ.getUint32(aG + 4) : "string" === aW.int64AsType ? ee = eZ.getBigUint64(aG).toString() : "auto" === aW.int64AsType ? (ee = eZ.getBigUint64(aG)) <= BigInt(2) << BigInt(52) && (ee = Number(ee)) : ee = eZ.getBigUint64(aG),
aG += 8,
ee;
case 208:
return eZ.getInt8(aG++);
case 209:
return ee = eZ.getInt16(aG),
aG += 2,
ee;
case 210:
return ee = eZ.getInt32(aG),
aG += 4,
ee;
case 211:
return "number" === aW.int64AsType ? ee = 4294967296 * eZ.getInt32(aG) + eZ.getUint32(aG + 4) : "string" === aW.int64AsType ? ee = eZ.getBigInt64(aG).toString() : "auto" === aW.int64AsType ? (ee = eZ.getBigInt64(aG)) >= BigInt(-2) << BigInt(52) && ee <= BigInt(2) << BigInt(52) && (ee = Number(ee)) : ee = eZ.getBigInt64(aG),
aG += 8,
ee;
case 212:
if (114 == (ee = eI[aG++]))
return sC(63 & eI[aG++]);
var eg = aK[ee];
if (eg) {
if (eg.read)
return aG++,
eg.read(a5());
if (eg.noBuffer)
return aG++,
eg();
return eg(eI.subarray(aG, ++aG))
}
throw Error("Unknown extension " + ee);
case 213:
if (114 == (ee = eI[aG]))
return aG++,
sC(63 & eI[aG++], eI[aG++]);
return sh(2);
case 214:
return sh(4);
case 215:
return sh(8);
case 216:
return sh(16);
case 217:
if (ee = eI[aG++],
aX >= aG)
return eR.slice(aG - aq, (aG += ee) - aq);
return st(ee);
case 218:
if (ee = eZ.getUint16(aG),
aG += 2,
aX >= aG)
return eR.slice(aG - aq, (aG += ee) - aq);
return sn(ee);
case 219:
if (ee = eZ.getUint32(aG),
aG += 4,
aX >= aG)
return eR.slice(aG - aq, (aG += ee) - aq);
return sr(ee);
case 220:
return ee = eZ.getUint16(aG),
aG += 2,
si(ee);
case 221:
return ee = eZ.getUint32(aG),
aG += 4,
si(ee);
case 222:
return ee = eZ.getUint16(aG),
aG += 2,
ss(ee);
case 223:
return ee = eZ.getUint32(aG),
aG += 4,
ss(ee);
default:
if (et >= 224)
return et - 256;
if (void 0 === et) {
var eC = Error("Unexpected end of MessagePack data");
throw eC.incomplete = !0,
eC
}
throw Error("Unknown MessagePack token " + et)
}
}
sn = so
var a6 = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
const decrypt = (ee) => {
for (var et, en, eo = r9(ee), ei = eo.length, ec = new Uint8Array(ei), eu = 0; eu < ei; eu++) {
var ed = eo.charCodeAt(eu);
ec[eu] = ed
}
en = {}
et = ec;
et.buffer || et.constructor !== ArrayBuffer || (et = "undefined" != typeof Buffer ? Buffer.from(et) : new Uint8Array(et)),
"object" == typeof en ? (eO = en.end || et.length,
aG = en.start || 0) : (aG = 0,
eO = en > -1 ? en : et.length),
aX = 0,
eR = null,
eD = null,
eI = et;
eM = new TextDecoder("utf-8");
eL = null
eP = [];
eZ = et.dataView || (et.dataView = new DataView(et.buffer, et.byteOffset, et.byteLength))
res = a3(en)
// ec = ecc;
// rA = 0;
// rB = 0;
// ed = [];
// eu = ec.length;
// rj = {
// "useRecords": false,
// "mapsAsObjects": true
// }
// // ei 是 TextDecoder
// ei = new TextDecoder("utf-8");
// eh = new DataView(ec.buffer, ec.byteOffset, ec.byteLength);
// ec.dataView = eh;
// let res = rK()
const replacer = (key, value) => {
if (typeof value === 'bigint') {
return value.toString();
}
return value;
};
res = JSON.stringify(res, replacer);
return res;
}
// console.log(generate_mid())
// console.log(generate_uuid())
// console.log(generate_device_id())
// console.log(generate_sign('5f65b00f83994987e334f97360d69557', '{"sessionTypes":"1,19"}'))
// let msg = "ggGLAYEBtTIyMDI2NDA5MTgwNzlAZ29vZmlzaAKzNDc4MTI4NzAwMDBAZ29vZmlzaAOxMzQwMjM5MTQ3MjUwMy5QTk0EAAXPAAABlYW04bIGggFlA4UBoAKjMTExA6AEAQXaADR7ImF0VXNlcnMiOltdLCJjb250ZW50VHlwZSI6MSwidGV4dCI6eyJ0ZXh0IjoiMTExIn19BwIIAQkACoupX3BsYXRmb3Jtp2FuZHJvaWSmYml6VGFn2gBBeyJzb3VyY2VJZCI6IlM6MSIsIm1lc3NhZ2VJZCI6ImYzNjkwMmVmZjQ1NDQ1YmRiMmQxYjBmZDE2OGY4MjY0In2sZGV0YWlsTm90aWNlozExMadleHRKc29u2gBLeyJxdWlja1JlcGx5IjoiMSIsIm1lc3NhZ2VJZCI6ImYzNjkwMmVmZjQ1NDQ1YmRiMmQxYjBmZDE2OGY4MjY0IiwidGFnIjoidSJ9r3JlbWluZGVyQ29udGVudKMxMTGucmVtaW5kZXJOb3RpY2W15Y+R5p2l5LiA5p2h5paw5raI5oGvrXJlbWluZGVyVGl0bGWmc2hh5L+uq3JlbWluZGVyVXJs2gCbZmxlYW1hcmtldDovL21lc3NhZ2VfY2hhdD9pdGVtSWQ9ODk3NzQyNzQ4MDExJnBlZXJVc2VySWQ9MjIwMjY0MDkxODA3OSZwZWVyVXNlck5pY2s9dCoqKjEmc2lkPTQ3ODEyODcwMDAwJm1lc3NhZ2VJZD1mMzY5MDJlZmY0NTQ0NWJkYjJkMWIwZmQxNjhmODI2NCZhZHY9bm+sc2VuZGVyVXNlcklkrTIyMDI2NDA5MTgwNzmuc2VuZGVyVXNlclR5cGWhMKtzZXNzaW9uVHlwZaExDAEDgahuZWVkUHVzaKR0cnVl";
//
// msg = "ggGLAYEBsjMxNDk2MzcwNjNAZ29vZmlzaAKzNDc5ODMzODkwOTZAZ29vZmlzaAOxMzQxNjU2NTI3NDU0Mi5QTk0EAAXPAAABlbKji20GggFlA4UBoAK6W+aIkeW3suaLjeS4i++8jOW+heS7mOasvl0DoAQaBdoEKnsiY29udGVudFR5cGUiOjI2LCJkeENhcmQiOnsiaXRlbSI6eyJtYWluIjp7ImNsaWNrUGFyYW0iOnsiYXJnMSI6Ik1zZ0NhcmQiLCJhcmdzIjp7InNvdXJjZSI6ImltIiwidGFza19pZCI6IjNleFFKSE9UbVBVMSIsIm1zZ19pZCI6ImNjOGJjMmRmN2M5MzRkZjA4NmUwNTY3Y2I2OWYxNTczIn19LCJleENvbnRlbnQiOnsiYmdDb2xvciI6IiNGRkZGRkYiLCJidXR0b24iOnsiYmdDb2xvciI6IiNGRkU2MEYiLCJib3JkZXJDb2xvciI6IiNGRkU2MEYiLCJjbGlja1BhcmFtIjp7ImFyZzEiOiJNc2dDYXJkQWN0aW9uIiwiYXJncyI6eyJzb3VyY2UiOiJpbSIsInRhc2tfaWQiOiIzZXhRSkhPVG1QVTEiLCJtc2dfaWQiOiJjYzhiYzJkZjdjOTM0ZGYwODZlMDU2N2NiNjlmMTU3MyJ9fSwiZm9udENvbG9yIjoiIzMzMzMzMyIsInRhcmdldFVybCI6ImZsZWFtYXJrZXQ6Ly9hZGp1c3RfcHJpY2U/Zmx1dHRlcj10cnVlJmJpek9yZGVySWQ9MjUwMzY4ODEyNjM1NjYzNjM3MCIsInRleHQiOiLkv67mlLnku7fmoLwifSwiZGVzYyI6Iuivt+WPjOaWueayn+mAmuWPiuaXtuehruiupOS7t+agvCIsImRlc2NDb2xvciI6IiNBM0EzQTMiLCJ0aXRsZSI6IuaIkeW3suaLjeS4i++8jOW+heS7mOasviIsInVwZ3JhZGUiOnsidGFyZ2V0VXJsIjoiaHR0cHM6Ly9oNS5tLmdvb2Zpc2guY29tL2FwcC9pZGxlRmlzaC1GMmUvZm0tZG93bmxhb2QvaG9tZS5odG1sP25vUmVkcmllY3Q9dHJ1ZSZjYW5CYWNrPXRydWUmY2hlY2tWZXJzaW9uPXRydWUiLCJ2ZXJzaW9uIjoiNy43LjkwIn19LCJ0YXJnZXRVcmwiOiJmbGVhbWFya2V0Oi8vb3JkZXJfZGV0YWlsP2lkPTI1MDM2ODgxMjYzNTY2MzYzNzAmcm9sZT1zZWxsZXIifX0sInRlbXBsYXRlIjp7Im5hbWUiOiJpZGxlZmlzaF9tZXNzYWdlX3RyYWRlX2NoYXRfY2FyZCIsInVybCI6Imh0dHBzOi8vZGluYW1pY3guYWxpYmFiYXVzZXJjb250ZW50LmNvbS9wdWIvaWRsZWZpc2hfbWVzc2FnZV90cmFkZV9jaGF0X2NhcmQvMTY2NzIyMjA1Mjc2Ny9pZGxlZmlzaF9tZXNzYWdlX3RyYWRlX2NoYXRfY2FyZC56aXAiLCJ2ZXJzaW9uIjoiMTY2NzIyMjA1Mjc2NyJ9fX0HAQgBCQAK3gAQpmJpelRhZ9oAe3sic291cmNlSWQiOiJDMkM6M2V4UUpIT1RtUFUxIiwidGFza05hbWUiOiLlt7Lmi43kuItf5pyq5LuY5qy+X+WNluWutiIsIm1hdGVyaWFsSWQiOiIzZXhRSkhPVG1QVTEiLCJ0YXNrSWQiOiIzZXhRSkhPVG1QVTEifbFjbG9zZVB1c2hSZWNlaXZlcqVmYWxzZbFjbG9zZVVucmVhZE51bWJlcqVmYWxzZaxkZXRhaWxOb3RpY2W6W+aIkeW3suaLjeS4i++8jOW+heS7mOasvl2nZXh0SnNvbtoBr3sibXNnQXJncyI6eyJ0YXNrX2lkIjoiM2V4UUpIT1RtUFUxIiwic291cmNlIjoiaW0iLCJtc2dfaWQiOiJjYzhiYzJkZjdjOTM0ZGYwODZlMDU2N2NiNjlmMTU3MyJ9LCJxdWlja1JlcGx5IjoiMSIsIm1zZ0FyZzEiOiJNc2dDYXJkIiwidXBkYXRlS2V5IjoiNDc5ODMzODkwOTY6MjUwMzY4ODEyNjM1NjYzNjM3MDoxX25vdF9wYXlfc2VsbGVyIiwibWVzc2FnZUlkIjoiY2M4YmMyZGY3YzkzNGRmMDg2ZTA1NjdjYjY5ZjE1NzMiLCJtdWx0aUNoYW5uZWwiOnsiaHVhd2VpIjoiRVhQUkVTUyIsInhpYW9taSI6IjEwODAwMCIsIm9wcG8iOiJFWFBSRVNTIiwiaG9ub3IiOiJOT1JNQUwiLCJhZ29vIjoicHJvZHVjdCIsInZpdm8iOiJPUkRFUiJ9LCJjb250ZW50VHlwZSI6IjI2IiwiY29ycmVsYXRpb25Hcm91cElkIjoiM2V4UUpIT1RtUFUxX0ZGcjRHT1NuOE9RbyJ9qHJlY2VpdmVyrTIyMDI2NDA5MTgwNzmrcmVkUmVtaW5kZXKy562J5b6F5Lmw5a625LuY5qy+sHJlZFJlbWluZGVyU3R5bGWhMa9yZW1pbmRlckNvbnRlbnS6W+aIkeW3suaLjeS4i++8jOW+heS7mOasvl2ucmVtaW5kZXJOb3RpY2W75Lmw5a625bey5ouN5LiL77yM5b6F5LuY5qy+rXJlbWluZGVyVGl0bGW75Lmw5a625bey5ouN5LiL77yM5b6F5LuY5qy+q3JlbWluZGVyVXJs2gCaZmxlYW1hcmtldDovL21lc3NhZ2VfY2hhdD9pdGVtSWQ9OTAwMDUyNjQ0Mjc3JnBlZXJVc2VySWQ9MzE0OTYzNzA2MyZwZWVyVXNlck5pY2s955S3KioqeSZzaWQ9NDc5ODMzODkwOTYmbWVzc2FnZUlkPWNjOGJjMmRmN2M5MzRkZjA4NmUwNTY3Y2I2OWYxNTczJmFkdj1ub6xzZW5kZXJVc2VySWSqMzE0OTYzNzA2M65zZW5kZXJVc2VyVHlwZaEwq3Nlc3Npb25UeXBloTGqdXBkYXRlSGVhZKR0cnVlDAEDgahuZWVkUHVzaKR0cnVl"
// msg = "hAGzNDc5ODMzODkwOTZAZ29vZmlzaAIBA4KrcmVkUmVtaW5kZXKy562J5b6F5Lmw5a625LuY5qy+sHJlZFJlbWluZGVyU3R5bGWhMQTPAAABlbMlNng="
// res = decrypt(msg);
// console.log(res);