测试 Gemini Pro 2.5

好的,我已经明白了您的需求。您希望:

  1. 增大概览消息(Toast)的尺寸:使其更加醒目。
  2. 消息持久性:当在用户中心内部切换不同标签页(例如从"个人信息"切换到"安全设置")时,右下角弹出的提示消息不应立即消失,而是应该等到其设定的显示时间结束后才消失。

针对这些需求,我会进行以下调整:

1. cdn/css/style.css

* 调整 .toast-message 及其内部元素(如 .toast-icon, .toast-body)的 padding, font-size, min-width, max-width 等样式,以增大整体视觉尺寸。

* 确保关闭按钮和进度条也相应调整大小。

2. cdn/js/main.js

* 现有的 showMessage 函数中的计时器逻辑 (setTimeout(removeToast, duration)) 已经是独立于页面内部导航的。这意味着当您在用户中心内部切换标签页时,只要 Toast 的显示时间未到,它就不会消失。这个机制已经满足了您的第二个需求。

以下是修改后的文件内容:

css 复制代码
/*
--------------------------------------------------------------------------------
 qmwneb946/my/my-b30fba1fe5de85674b52ba4a36498afbff0a9c2e/cdn/css/style.css
--------------------------------------------------------------------------------
*/
:root {
--primary-color: #1abc9c;
--primary-color-dark: #16a085;
--secondary-color: #7f8c8d;
--accent-color: #3498db;
--success-color: #2ecc71;
--danger-color: #e74c3c;
--warning-color: #f39c12;
--info-color: #3498db;
--text-color-base: #34495e;
--text-color-muted: #7f8c8d;
--text-color-inverted: #ffffff;
--heading-color: #2c3e50;
--bg-color-light: #f4f6f8;
--surface-color-light: #ffffff;
--border-color-light: #dfe4e8;
--bg-color-dark: #1e272e;
--surface-color-dark: #2c3a47;
--border-color-dark: #4a5568;
--font-family-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-family-mono: 'SFMono-Regular', Consolas, "Liberation Mono", Menlo, Courier, monospace;
--border-radius: 8px;
--box-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--box-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--box-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--sidebar-width: 260px;
--top-bar-height: 64px;
--current-bg-color: var(--bg-color-light);
--current-surface-color: var(--surface-color-light);
--current-text-color: var(--text-color-base);
--current-text-muted-color: var(--text-color-muted);
--current-heading-color: var(--heading-color);
--current-border-color: var(--border-color-light);
--current-input-bg: var(--surface-color-light);
--current-input-placeholder-color: #95a5a6;
}
body.dark-mode {
--current-bg-color: var(--bg-color-dark);
--current-surface-color: var(--surface-color-dark);
--current-text-color: #e2e8f0;
--current-text-muted-color: #a0aec0;
--current-heading-color: #e2e8f0;
--current-border-color: var(--border-color-dark);
--current-input-bg: var(--surface-color-dark);
--current-input-placeholder-color: #718096;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: var(--font-family-sans);
line-height: 1.6;
margin: 0;
padding: 0;
background-color: var(--current-bg-color);
color: var(--current-text-color);
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.hidden { display: none !important; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover { color: var(--primary-color-dark); text-decoration: underline; }
h1, h2, h3, h4, h5, h6 {
color: var(--current-heading-color);
margin-top: 0;
margin-bottom: 0.75em;
font-weight: 600;
line-height: 1.3;
}
h1 { font-size: 2.25rem; }
h2 { font-size: 1.875rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
.app-wrapper {
display: flex;
min-height: 100vh;
}
.app-wrapper.logged-out-layout .sidebar { display: none; }
.app-wrapper.logged-out-layout .main-content { margin-left: 0; }
.app-wrapper.logged-in-layout .sidebar { display: block; }
.top-bar {
background-color: var(--current-surface-color);
height: var(--top-bar-height);
border-bottom: 1px solid var(--current-border-color);
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: var(--box-shadow-sm);
}
.top-bar-left { display: flex; align-items: center; }
.site-title { font-size: 1.25rem; font-weight: 600; color: var(--current-heading-color); margin-left: 10px; }
.top-bar-right { display: flex; align-items: center; gap: 10px; }
.sidebar-toggle-button, .theme-toggle-button, .user-menu-button, .messaging-button {
background: none;
border: none;
color: var(--current-text-muted-color);
padding: 8px;
border-radius: var(--border-radius);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.sidebar-toggle-button:hover, .theme-toggle-button:hover, .user-menu-button:hover, .messaging-button:hover {
background-color: color-mix(in srgb, var(--current-surface-color) 80%, var(--current-bg-color));
color: var(--current-text-color);
}
.messaging-button.active {
color: var(--primary-color);
background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
}
.h-5 { height: 1.25rem !important; }
.w-5 { width: 1.25rem !important; }
.h-6 { height: 1.5rem !important; }
.w-6 { width: 1.5rem !important; }
.mr-1 { margin-right: 0.25rem !important; }
.mr-2 { margin-right: 0.5rem !important; }
.sidebar-nav .sidebar-link svg {
    height: 1.25rem;
    width: 1.25rem;
    margin-right: 0.5rem;
    display: inline-block;
    vertical-align: middle;
    position: relative;
    top: -0.05em;
    flex-shrink: 0;
}
.unread-badge {
background-color: var(--danger-color);
color: var(--text-color-inverted);
border-radius: 50%;
padding: 0px 5px;
font-size: 0.75em;
min-width: 1.7em;
height: 1.7em;
line-height: 1.7em;
text-align: center;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: bold;
vertical-align: middle;
box-sizing: border-box;
}
.messaging-button .unread-badge {
position: absolute;
top: 4px;
right: 4px;
}
.contact-meta .unread-badge {
margin-top: 2px;
}
.auth-actions a.button { margin-left: 10px; }
.user-info-dropdown { position: relative; }
.user-menu-button .username-text { margin-left: 6px; font-weight: 500; }
.dropdown-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
background-color: var(--current-surface-color);
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-md);
min-width: 200px;
z-index: 1010;
padding: 8px 0;
}
.dropdown-user-email { padding: 8px 16px; font-size: 0.875rem; color: var(--current-text-muted-color); }
.dropdown-divider { border-top: 1px solid var(--current-border-color); margin: 8px 0; }
.dropdown-item {
display: flex;
align-items: center;
padding: 8px 16px;
color: var(--current-text-color);
font-size: 0.95rem;
background: none;
border: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.dropdown-item:hover { background-color: color-mix(in srgb, var(--current-surface-color) 85%, var(--current-bg-color)); }
.dropdown-item.logout { color: var(--danger-color); }
.dropdown-item svg {
    height: 1.25rem;
    width: 1.25rem;
    margin-right: 0.5rem;
    flex-shrink: 0;
}
.sidebar {
width: var(--sidebar-width);
background-color: var(--current-surface-color);
border-right: 1px solid var(--current-border-color);
position: fixed;
top: var(--top-bar-height);
left: 0;
bottom: 0;
padding: 20px 0;
overflow-y: auto;
z-index: 999;
transition: transform 0.3s ease-in-out;
box-shadow: var(--box-shadow-sm);
}
.sidebar.open { transform: translateX(0); }
.sidebar-nav ul { list-style: none; padding: 0; margin: 0; }
.sidebar-nav .sidebar-link {
display: flex;
align-items: center;
padding: 12px 20px;
color: var(--current-text-muted-color);
font-weight: 500;
border-left: 3px solid transparent;
transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.sidebar-nav .sidebar-link:hover {
color: var(--primary-color);
background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
}
.sidebar-nav .sidebar-link.selected {
color: var(--primary-color);
background-color: color-mix(in srgb, var(--primary-color) 15%, transparent);
border-left-color: var(--primary-color);
font-weight: 600;
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 998;
}
.main-content {
flex-grow: 1;
padding-top: var(--top-bar-height);
transition: margin-left 0.3s ease-in-out;
}
.main-content .container {
padding: 25px;
margin: 0;
max-width: none;
background-color: var(--current-bg-color);
box-shadow: none;
border-radius: 0;
}
.app-wrapper.logged-in-layout .main-content {
margin-left: var(--sidebar-width);
}
.app-wrapper.logged-out-layout .main-content {
margin-left: 0;
}
.main-content h3 {
font-size: 1.75rem;
margin-bottom: 25px;
border-bottom: 1px solid var(--current-border-color);
padding-bottom: 10px;
}
.auth-section {
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 40px;
}
.form-container {
background-color: var(--current-surface-color);
padding: 30px 40px;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-md);
width: 100%;
max-width: 450px;
}
.form-container h2 { text-align: center; margin-bottom: 25px; font-size: 1.75rem; }
.form-group { margin-bottom: 20px; }
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
font-size: 0.9rem;
}
input[type="email"], input[type="password"], input[type="text"], input[type="tel"], input[type="url"], input[type="search"], textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
font-size: 1rem;
color: var(--current-text-color);
background-color: var(--current-input-bg);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input:focus, textarea:focus {
border-color: var(--primary-color);
outline: 0;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 20%, transparent);
}
input::placeholder, textarea::placeholder { color: var(--current-input-placeholder-color); }
input[readonly], input[disabled] {
background-color: color-mix(in srgb, var(--current-input-bg) 80%, var(--current-bg-color)) !important;
opacity: 0.7;
cursor: not-allowed;
}
.input-hint { font-size: 0.8rem; color: var(--current-text-muted-color); margin-top: 4px; }
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
border: 1px solid transparent;
border-radius: var(--border-radius);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
text-align: center;
white-space: nowrap;
}
.button.primary { background-color: var(--primary-color); color: var(--text-color-inverted); border-color: var(--primary-color); }
.button.primary:hover { background-color: var(--primary-color-dark); border-color: var(--primary-color-dark); }
.button.secondary { background-color: var(--secondary-color); color: var(--text-color-inverted); border-color: var(--secondary-color); }
.button.secondary:hover { background-color: color-mix(in srgb, var(--secondary-color) 85%, black); border-color: color-mix(in srgb, var(--secondary-color) 85%, black); }
.button.success { background-color: var(--success-color); color: var(--text-color-inverted); border-color: var(--success-color); }
.button.success:hover { background-color: color-mix(in srgb, var(--success-color) 85%, black); border-color: color-mix(in srgb, var(--success-color) 85%, black); }
.button.danger { background-color: var(--danger-color); color: var(--text-color-inverted); border-color: var(--danger-color); }
.button.danger:hover { background-color: color-mix(in srgb, var(--danger-color) 85%, black); border-color: color-mix(in srgb, var(--danger-color) 85%, black); }
.button.full-width { width: 100%; margin-top: 10px; }
.button.small { padding: 8px 16px; font-size: 0.875rem; }
.button svg {
    height: 1.25rem;
    width: 1.25rem;
    margin-right: 0.25rem;
}
.form-actions { margin-top: 20px; display: flex; gap: 10px; justify-content: flex-start; }
.toggle-link { text-align: center; margin-top: 20px; font-size: 0.9rem; }
.account-content .tab-pane { padding: 15px; }
.setting-block { margin-bottom: 30px; padding: 20px; background-color: var(--current-surface-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-sm); }
.setting-block h4 { font-size: 1.15rem; margin-bottom: 15px; color: var(--current-heading-color); }
.description-text { font-size: 0.9rem; color: var(--current-text-muted-color); margin-bottom: 15px; }
.status-display { margin-bottom: 15px; font-size: 0.95rem; }
#2fa-current-status { font-weight: bold; }
#2fa-controls button { margin-right: 10px; }
.otpauth-uri-text {
word-break: break-all; font-family: var(--font-family-mono);
background: color-mix(in srgb, var(--current-bg-color) 90%, var(--current-surface-color));
padding: 10px; border-radius: 4px; margin: 10px 0; display: block;
border: 1px dashed var(--current-border-color);
}
#qrcode-display {
background: white; padding: 10px; border: 1px solid var(--border-color-light);
display: inline-block; margin: 15px auto;
}
body.dark-mode #qrcode-display { background: var(--surface-color-light); border-color: var(--border-color-light); }
.api-key-display { margin-top: 15px; }
.api-key-value-container { display: flex; align-items: center; margin-bottom: 10px; }
.api-key-value-container input[type="text"] { flex-grow: 1; margin-right: 10px; }
.external-link { color: var(--accent-color); font-weight: 500; }
.external-link:hover { color: color-mix(in srgb, var(--accent-color) 80%, black); }
hr.section-divider { border: none; border-top: 1px solid var(--current-border-color); margin: 30px 0; }
.security-settings-list {
list-style: none;
padding: 0;
margin: 0;
}
.security-setting-item {
background-color: var(--current-surface-color);
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
margin-bottom: 15px;
box-shadow: var(--box-shadow-sm);
}
.setting-entry {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
cursor: pointer;
font-weight: 500;
color: var(--current-heading-color);
transition: background-color 0.2s ease;
}
.setting-entry:hover {
background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));
}
.setting-entry .entry-title {
flex-grow: 1;
}
.setting-entry .entry-status {
font-size: 0.85rem;
color: var(--current-text-muted-color);
margin-right: 10px;
}
.setting-entry .entry-arrow {
font-size: 0.8rem;
transition: transform 0.2s ease-in-out;
}
.setting-entry.open .entry-arrow {
transform: rotate(90deg);
}
.setting-content-panel {
padding: 0 20px 20px 20px;
border-top: 1px solid var(--current-border-color);
background-color: color-mix(in srgb, var(--current-surface-color) 98%, var(--current-bg-color));
}
.application-card {
background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
padding: 15px; margin-bottom: 15px;
}
.application-card h4 { font-size: 1.1em; color: var(--primary-color); margin-top: 0; margin-bottom: 8px; }
.application-card p { font-size: 0.9em; margin-bottom: 5px; color: var(--current-text-muted-color); word-break: break-word; }
.application-card code {
background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent);
padding: 2px 5px; border-radius: 3px; font-family: var(--font-family-mono); word-break: break-all;
}
.new-client-secret-warning {
background-color: var(--warning-color); color: var(--text-color-inverted);
padding: 10px; border-radius: var(--border-radius); margin-top: 15px; font-size: 0.9em;
}
body.dark-mode .new-client-secret-warning {
background-color: color-mix(in srgb, var(--warning-color) 70%, black);
color: var(--current-text-color);
}
.messaging-tab-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid var(--current-border-color);
}
.messaging-tab-header h3 {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
font-size: 1.75rem;
}
.messaging-tab-header svg {
color: var(--primary-color);
height: 28px;
width: 28px;
margin-right: 10px;
}
.new-conversation-trigger {
margin-bottom: 25px;
display: flex;
gap: 10px;
align-items: center;
}
.new-conversation-trigger input[type="email"] {
flex-grow: 1;
font-size: 0.95rem;
padding: 10px 12px;
}
.new-conversation-trigger button.button.small {
padding: 10px 15px;
font-size: 0.9rem;
flex-shrink: 0;
}
.messaging-layout-new {
display: flex;
height: calc(100vh - var(--top-bar-height) - 230px);
min-height: 450px;
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
background-color: var(--current-surface-color);
overflow: hidden;
box-shadow: var(--box-shadow-sm);
}
.messaging-contacts-panel {
width: 320px;
min-width: 280px;
border-right: 1px solid var(--current-border-color);
display: flex;
flex-direction: column;
background-color: var(--current-surface-color);
}
.contact-search-bar {
padding: 12px 15px;
border-bottom: 1px solid var(--current-border-color);
display: flex;
gap: 8px;
background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));
}
.contact-search-bar input[type="search"] {
flex-grow: 1;
padding: 8px 10px;
font-size: 0.9rem;
}
.contact-search-bar button {
padding: 8px 12px;
font-size: 0.85rem;
}
.recent-contacts-title {
padding: 12px 15px;
font-size: 0.75rem;
font-weight: 600;
color: var(--current-text-muted-color);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0;
border-bottom: 1px solid var(--current-border-color);
background-color: color-mix(in srgb, var(--current-surface-color) 98%, var(--current-bg-color));
}
.contact-list {
flex-grow: 1;
overflow-y: auto;
list-style-type: none;
padding: 0;
margin: 0;
}
.contact-list li {
display: flex;
align-items: center;
padding: 10px 15px;
cursor: pointer;
border-bottom: 1px solid var(--current-border-color);
transition: background-color 0.2s ease;
}
.contact-list li:last-child { border-bottom: none; }
.contact-list li:hover { background-color: color-mix(in srgb, var(--current-surface-color) 90%, var(--current-bg-color)); }
.contact-list li.selected { background-color: var(--primary-color); color: var(--text-color-inverted); }
.contact-list li.selected .contact-name,
.contact-list li.selected .contact-last-message,
.contact-list li.selected .contact-meta .contact-time,
.contact-list li.selected .unread-badge { color: var(--text-color-inverted) !important; }
.contact-list li.selected .contact-avatar { border: 2px solid var(--text-color-inverted); }
.contact-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background-color: var(--secondary-color);
color: var(--text-color-inverted);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 12px;
font-size: 1rem;
overflow: hidden;
flex-shrink: 0;
}
.contact-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.contact-info {
flex-grow: 1;
overflow: hidden;
}
.contact-name {
font-weight: 500;
display: block; margin-bottom: 2px; color: var(--current-heading-color);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
font-size: 0.95rem;
}
.contact-last-message {
font-size: 0.8rem;
color: var(--current-text-muted-color);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display:block;
}
.contact-meta {
margin-left: auto;
text-align: right;
font-size: 0.75rem;
color: var(--current-text-muted-color);
min-width: 50px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
line-height: 1.3;
}
.contact-time {
display: block;
margin-bottom: 3px;
}
.message-display-panel {
flex-grow: 1;
display: flex;
flex-direction: column;
background-color: var(--current-bg-color);
}
.messages-list {
flex-grow: 1; overflow-y: auto; padding: 20px;
display: flex; flex-direction: column;
}
.messages-list .messages-loading-indicator-wrapper,
.messages-list .load-more-messages-button-wrapper {
    display: flex;
    justify-content: center;
    padding: 10px 0;
}
.messages-list .messages-loading-indicator-wrapper .spinner {
    border: 3px solid var(--current-border-color);
    border-top: 3px solid var(--primary-color);
    border-radius: 50%;
    width: 20px;
    height: 20px;
    animation: spin 1s linear infinite;
}
.messages-list .load-more-messages-button-wrapper .load-more-messages-button {
    font-size: 0.8em;
    padding: 6px 12px;
    background-color: transparent;
    border: 1px solid var(--primary-color);
    color: var(--primary-color);
}
.messages-list .load-more-messages-button-wrapper .load-more-messages-button:hover {
    background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
}
@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
.empty-messages-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: var(--current-text-muted-color);
height: 100%;
padding: 20px;
}
.empty-messages-placeholder svg {
margin-bottom: 15px;
opacity: 0.6;
width: 64px;
height: 64px;
}
.empty-messages-placeholder p {
font-size: 1.2em;
font-weight: 500;
margin-bottom: 8px;
color: var(--current-heading-color);
}
.empty-messages-placeholder span {
font-size: 0.9em;
max-width: 300px;
}
.message-item {
max-width: 75%; padding: 8px 12px; border-radius: 12px;
margin-bottom: 10px; line-height: 1.5; word-wrap: break-word;
}
.message-item.sent {
background-color: var(--primary-color); color: var(--text-color-inverted);
align-self: flex-end; border-bottom-right-radius: 4px;
}
.message-item.received {
background-color: var(--current-surface-color);
color: var(--current-text-color);
align-self: flex-start; border-bottom-left-radius: 4px;
border: 1px solid var(--current-border-color);
}
.message-sender { font-size: 0.75em; color: var(--current-text-muted-color); margin-bottom: 2px; display: block; }
body.dark-mode .message-item.sent .message-sender { color: rgba(255,255,255,0.6); }
.message-content { font-size: 0.95em; }
.message-content p:first-child { margin-top: 0; }
.message-content p:last-child { margin-bottom: 0; }
.message-content ul, .message-content ol { margin: 8px 0 8px 20px; padding-left: 0; }
.message-content pre {
background-color: color-mix(in srgb, var(--current-text-color) 8%, transparent);
border: 1px solid var(--current-border-color);
padding: 8px; border-radius: 4px; overflow-x: auto;
font-size: 0.85em; margin: 8px 0; font-family: var(--font-family-mono);
}
body.dark-mode .message-content pre {
background-color: color-mix(in srgb, var(--current-surface-color) 80%, var(--current-bg-color));
}
.message-time { font-size: 0.7rem; color: var(--current-text-muted-color); margin-top: 4px; text-align: right; display: block; opacity: 0.8; }
body.dark-mode .message-item.sent .message-time { color: rgba(255,255,255,0.5); }
.message-input-area {
display: flex; padding: 15px;
border-top: 1px solid var(--current-border-color);
background-color: var(--current-surface-color);
}
.message-input-area textarea {
flex-grow: 1; padding: 10px;
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
resize: none; margin-right: 10px;
font-family: var(--font-family-sans); font-size: 0.95rem;
min-height: 42px; max-height: 120px; overflow-y: auto;
background-color: var(--current-input-bg); color: var(--current-text-color);
}
.message-input-area button { padding: 10px 18px; }
.api-usage-section pre {
background-color: color-mix(in srgb, var(--current-bg-color) 95%, var(--current-surface-color));
color: var(--current-text-color);
padding: 15px; border-radius: var(--border-radius); overflow-x: auto;
font-family: var(--font-family-mono); font-size: 0.875em;
border: 1px solid var(--current-border-color); margin-bottom: 15px;
}
.api-usage-section code.inline-code {
background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent);
padding: 2px 5px; border-radius: 3px; font-family: var(--font-family-mono);
}
.admin-panel-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.9em; }
.admin-panel-table th, .admin-panel-table td { border: 1px solid var(--current-border-color); padding: 8px 12px; text-align: left; }
.admin-panel-table th { background-color: var(--current-surface-color); font-weight: 600; }
.admin-panel-table tr:nth-child(even) { background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));}
.admin-panel-table td code { background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent); padding: 2px 4px; border-radius: 3px; font-family: var(--font-family-mono); }
.status-active { color: var(--success-color); font-weight: bold; }
.status-inactive, .status-suspended { color: var(--danger-color); font-weight: bold; }
.admin-actions button { margin-right: 5px; }
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
top: 0;
height: 100vh;
border-right: 1px solid var(--current-border-color);
}
.sidebar.open {
transform: translateX(0);
}
.app-wrapper.logged-in-layout .main-content {
margin-left: 0;
}
.app-wrapper.logged-out-layout .main-content {
margin-left: 0;
}
.messaging-layout-new {
flex-direction: column;
height: calc(100vh - var(--top-bar-height) - 70px);
}
.messaging-contacts-panel {
width: 100%; max-width: none; height: 40%; min-height: 250px;
border-right: none; border-bottom: 1px solid var(--current-border-color);
}
.message-display-panel { height: 60%; }
}
@media (min-width: 769px) {
.sidebar-toggle-button { display: none; }
.sidebar-overlay { display: none !important; }
.sidebar { transform: translateX(0); }
}
#login-form-el .cf-turnstile,
#register-form-el .cf-turnstile {
margin-left: 30px;
margin-right: 30px;
margin-top: 20px;
margin-bottom: 20px;
}
.modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0,0,0,0.6); display: flex;
justify-content: center; align-items: center; z-index: 2000;
opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}
.modal.active { opacity: 1; visibility: visible; transition: opacity 0.3s ease; }
.modal-content {
background-color: var(--current-surface-color); color: var(--current-text-color);
padding: 25px 30px; border-radius: var(--border-radius);
box-shadow: 0 5px 15px rgba(0,0,0,0.3); width: 90%; max-width: 500px;
transform: translateY(-20px); transition: transform 0.3s ease;
}
.modal.active .modal-content { transform: translateY(0); }
.toast-container {
    position: fixed;
    bottom: 25px; /* Increased distance from bottom */
    right: 25px; /* Increased distance from right */
    z-index: 9999;
    display: flex;
    flex-direction: column-reverse;
    gap: 12px; /* Increased gap */
    max-height: calc(100vh - 50px);
    overflow-y: auto;
    overflow-x: hidden;
}
.toast-message {
    background-color: var(--current-surface-color);
    color: var(--current-text-color);
    padding: 16px 22px; /* Increased padding */
    border-radius: var(--border-radius); /* Using slightly larger global radius */
    box-shadow: var(--box-shadow-lg);
    display: flex;
    align-items: center;
    min-width: 320px; /* Increased min-width */
    max-width: 420px; /* Increased max-width */
    opacity: 0;
    transform: translateX(calc(100% + 25px));
    transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.215, 0.610, 0.355, 1);
    position: relative;
    overflow: hidden;
    border-left: 6px solid transparent; /* Thicker border */
}
.toast-message.show {
    opacity: 1;
    transform: translateX(0);
}
.toast-icon {
    flex-shrink: 0;
    margin-right: 15px; /* Increased margin */
    width: 24px; /* Increased icon size */
    height: 24px; /* Increased icon size */
}
.toast-icon svg {
    width: 100%;
    height: 100%;
}
.toast-body {
    flex-grow: 1;
    font-size: 1rem; /* Increased font size */
    line-height: 1.5; /* Adjusted line height for readability */
}
.toast-close-button {
    background: none;
    border: none;
    color: var(--current-text-muted-color);
    font-size: 1.5rem; /* Increased close button size */
    font-weight: bold;
    cursor: pointer;
    padding: 0 0 0 18px; /* Increased padding */
    margin-left: auto;
    line-height: 1;
    align-self: flex-start;
}
.toast-close-button:hover {
    color: var(--current-text-color);
}
.toast-progress-bar {
    position: absolute;
    bottom: 0;
    left: 0;
    height: 5px; /* Thicker progress bar */
    width: 100%;
    background-color: rgba(0,0,0,0.2);
    animation: shrink var(--toast-duration, 5000ms) linear forwards;
}
body.dark-mode .toast-progress-bar {
    background-color: rgba(255,255,255,0.2);
}
@keyframes shrink {
    from { width: 100%; }
    to { width: 0%; }
}
.toast-message.toast-success {
    border-left-color: var(--success-color);
}
.toast-message.toast-success .toast-icon svg {
    fill: var(--success-color);
}
.toast-message.toast-success .toast-progress-bar {
    background-color: var(--success-color);
}
.toast-message.toast-error {
    border-left-color: var(--danger-color);
}
.toast-message.toast-error .toast-icon svg {
    fill: var(--danger-color);
}
.toast-message.toast-error .toast-progress-bar {
    background-color: var(--danger-color);
}
.toast-message.toast-info {
    border-left-color: var(--info-color);
}
.toast-message.toast-info .toast-icon svg {
    fill: var(--info-color);
}
.toast-message.toast-info .toast-progress-bar {
    background-color: var(--info-color);
}
.toast-message.toast-warning {
    border-left-color: var(--warning-color);
}
.toast-message.toast-warning .toast-icon svg {
    fill: var(--warning-color);
}
.toast-message.toast-warning .toast-progress-bar {
    background-color: var(--warning-color);
}
javascript 复制代码
/*
--------------------------------------------------------------------------------
 qmwneb946/my/my-b30fba1fe5de85674b52ba4a36498afbff0a9c2e/cdn/js/main.js
--------------------------------------------------------------------------------
*/
let TURNSTILE_SITE_KEY = '1x00000000000000000000AA';
const activeTurnstileWidgets = new Map();
let loginEmailFor2FA = null;
let currentUserData = null;
let authSection, loggedInSection, loginFormEl, registerFormEl, adminSection;
let topBarUserEmailEl, topBarUserUsernameEl, topBarUserInfoEl, topBarAuthButtonsEl, topBarLogoutButtonEl, userMenuButtonEl, userDropdownMenuEl, topBarAccountLinkEl, topBarAdminLinkEl;
let sidebarEl, sidebarToggleEl, mainContentContainerEl, sidebarOverlayEl;
let accountTabLinks = [], adminTabLinks = [];
let tabPanes = [];
let themeToggleButton, themeToggleDarkIcon, themeToggleLightIcon;
let unreadMessagesIndicator;
let appWrapper;
let topBarMessagingButton;
let userPresenceSocket = null;
const pathToPaneIdMap = {
    '/user/profile': 'tab-content-personal-info',
    '/user/security': 'tab-content-security-settings',
    '/user/api-keys': 'tab-content-api-keys',
    '/user/applications': 'tab-content-my-applications',
    '/user/messaging': 'tab-content-messaging',
    '/user/admin': 'tab-content-admin-users',
    '/user/admin/users': 'tab-content-admin-users',
    '/user/admin/apps': 'tab-content-admin-apps'
};
const icons = {
    success: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>`,
    error: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times-circle" class="svg-inline--fa fa-times-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"></path></svg>`,
    info: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="info-circle" class="svg-inline--fa fa-info-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"></path></svg>`,
    warning: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="exclamation-triangle" class="svg-inline--fa fa-exclamation-triangle fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.982 12.654z"></path></svg>`
};
function renderTurnstile(containerElement) {
    if (!containerElement || !window.turnstile || typeof window.turnstile.render !== 'function') return;
    if (!TURNSTILE_SITE_KEY) { return; }
    if (activeTurnstileWidgets.has(containerElement)) {
        try { turnstile.remove(activeTurnstileWidgets.get(containerElement)); } catch (e) { }
        activeTurnstileWidgets.delete(containerElement);
    }
    containerElement.innerHTML = '';
    try {
        const widgetId = turnstile.render(containerElement, {
            sitekey: TURNSTILE_SITE_KEY,
            callback: (token) => {
                const specificCallbackName = containerElement.getAttribute('data-callback');
                if (specificCallbackName && typeof window[specificCallbackName] === 'function') {
                    window[specificCallbackName](token);
                }
            }
        });
        if (widgetId) activeTurnstileWidgets.set(containerElement, widgetId);
    } catch (e) { }
}
function removeTurnstile(containerElement) {
    if (containerElement && activeTurnstileWidgets.has(containerElement)) {
        try { turnstile.remove(activeTurnstileWidgets.get(containerElement)); } catch (e) { }
        activeTurnstileWidgets.delete(containerElement);
    }
}
function resetTurnstileInContainer(containerElement) {
    if (containerElement && activeTurnstileWidgets.has(containerElement)) {
        try { turnstile.reset(activeTurnstileWidgets.get(containerElement)); } catch (e) { }
    } else if (containerElement) {
        renderTurnstile(containerElement);
    }
}
function showMessage(text, type = 'error', isHtml = false, duration = 5000) {
    let toastContainer = document.getElementById('toast-container');
    if (!toastContainer) {
        toastContainer = document.createElement('div');
        toastContainer.id = 'toast-container';
        toastContainer.className = 'toast-container bottom-right';
        document.body.appendChild(toastContainer);
    }
    const toast = document.createElement('div');
    toast.className = `toast-message toast-${type}`;
    toast.style.setProperty('--toast-duration', `${duration}ms`);
    const iconDiv = document.createElement('div');
    iconDiv.className = 'toast-icon';
    iconDiv.innerHTML = icons[type] || icons.info;
    const bodyDiv = document.createElement('div');
    bodyDiv.className = 'toast-body';
    if (isHtml) {
        bodyDiv.innerHTML = text;
    } else {
        bodyDiv.textContent = text;
    }
    const closeButton = document.createElement('button');
    closeButton.className = 'toast-close-button';
    closeButton.innerHTML = '&times;';
    closeButton.setAttribute('aria-label', 'close');
    const progressBar = document.createElement('div');
    progressBar.className = 'toast-progress-bar';
    toast.appendChild(iconDiv);
    toast.appendChild(bodyDiv);
    toast.appendChild(closeButton);
    toast.appendChild(progressBar);
    toastContainer.appendChild(toast);
    requestAnimationFrame(() => {
        setTimeout(() => {
          toast.classList.add('show');
        }, 50);
    });
    const removeToast = () => {
        toast.classList.remove('show');
        toast.addEventListener('transitionend', () => {
            if (toast.parentElement) {
                toast.parentElement.removeChild(toast);
            }
        }, { once: true });
        setTimeout(() => {
            if (toast.parentElement) {
                toast.parentElement.removeChild(toast);
            }
        }, 500);
    };
    const timerId = setTimeout(removeToast, duration);
    closeButton.onclick = () => {
        clearTimeout(timerId);
        removeToast();
    };
}
function clearMessages() {
    const toastContainer = document.getElementById('toast-container');
    if (toastContainer) {
        while (toastContainer.firstChild) {
            toastContainer.removeChild(toastContainer.firstChild);
        }
    }
}
async function apiCall(endpoint, method = 'GET', body = null) {
    const options = { method, headers: {}, credentials: 'include' };
    if (body) { options.headers['Content-Type'] = 'application/json'; options.body = JSON.stringify(body); }
    try {
        const response = await fetch(endpoint, options);
        let resultData = {};
        const contentType = response.headers.get("content-type");
        if (contentType && contentType.includes("application/json") && response.status !== 204) {
             try { resultData = await response.json(); } catch (e) { }
        }
        return { ok: response.ok, status: response.status, data: resultData };
    } catch (e) {
        showMessage('发生网络或服务器错误,请稍后重试。', 'error');
        return { ok: false, status: 0, data: { error: '网络错误' } };
    }
}
function updateUnreadMessagesIndicatorUI(count) {
    const localUnreadIndicator = document.getElementById('unread-messages-indicator');
    const localMessagingButton = document.getElementById('top-bar-messaging-button');
    if (!localUnreadIndicator || !localMessagingButton) return;
    if (count > 0) {
        localUnreadIndicator.textContent = count;
        localUnreadIndicator.classList.remove('hidden');
        localMessagingButton.classList.add('active');
    } else {
        localUnreadIndicator.textContent = '';
        localUnreadIndicator.classList.add('hidden');
        localMessagingButton.classList.remove('active');
    }
}
function connectUserPresenceWebSocket() {
    if (userPresenceSocket && (userPresenceSocket.readyState === WebSocket.OPEN || userPresenceSocket.readyState === WebSocket.CONNECTING)) {
        return;
    }
    if (!currentUserData || !currentUserData.email) {
        return;
    }
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${protocol}//${window.location.host}/api/ws/user`;
    userPresenceSocket = new WebSocket(wsUrl);
    userPresenceSocket.onopen = () => {
    };
    userPresenceSocket.onmessage = (event) => {
        try {
            const message = JSON.parse(event.data);
            if (message.type === "CONVERSATIONS_LIST") {
                if (typeof window.handleConversationsListUpdate === 'function') {
                    window.handleConversationsListUpdate(message.data);
                }
            } else if (message.type === "UNREAD_COUNT_TOTAL") {
                updateUnreadMessagesIndicatorUI(message.data.unread_count);
            } else if (message.type === "CONVERSATION_UPDATE") {
                 if (typeof window.handleSingleConversationUpdate === 'function') {
                    window.handleSingleConversationUpdate(message.data);
                }
            }
        } catch (e) {
        }
    };
    userPresenceSocket.onclose = (event) => {
        userPresenceSocket = null;
        if (currentUserData && currentUserData.email) {
            setTimeout(connectUserPresenceWebSocket, 5000);
        }
    };
    userPresenceSocket.onerror = (error) => {
    };
}
function applyTheme(isDark) {
    document.body.classList.toggle('dark-mode', isDark);
    if (themeToggleDarkIcon) themeToggleDarkIcon.style.display = isDark ? 'block' : 'none';
    if (themeToggleLightIcon) themeToggleLightIcon.style.display = isDark ? 'none' : 'block';
    const qrCodeDisplay = document.getElementById('qrcode-display');
    const otpAuthUriTextDisplay = document.getElementById('otpauth-uri-text-display');
    if (qrCodeDisplay && typeof QRCode !== 'undefined' && otpAuthUriTextDisplay) {
        const otpauthUri = otpAuthUriTextDisplay.textContent;
        if (otpauthUri && qrCodeDisplay.innerHTML.includes('canvas')) {
            qrCodeDisplay.innerHTML = '';
            new QRCode(qrCodeDisplay, {
                text: otpauthUri, width: 180, height: 180,
                colorDark: isDark ? "#e2e8f0" : "#000000",
                colorLight: "#ffffff",
                correctLevel: QRCode.CorrectLevel.H
            });
        }
    }
}
function toggleSidebar() {
    if (sidebarEl && sidebarOverlayEl && appWrapper) {
        const isOpen = sidebarEl.classList.toggle('open');
        sidebarOverlayEl.classList.toggle('hidden', !isOpen);
        appWrapper.classList.toggle('sidebar-open-app', isOpen);
    }
}
function activatePane(paneIdToActivate) {
    if (!paneIdToActivate) return;
    const allNavLinks = [...accountTabLinks, ...adminTabLinks];
    allNavLinks.forEach(link => {
        link.classList.toggle('selected', link.dataset.paneId === paneIdToActivate);
    });
    if (topBarMessagingButton) {
        topBarMessagingButton.classList.toggle('active', paneIdToActivate === 'tab-content-messaging');
    }
    if (!mainContentContainerEl) return;
    tabPanes.forEach(pane => {
        const turnstileDivsInPane = pane.querySelectorAll('.cf-turnstile');
        if (pane.id === paneIdToActivate) {
            pane.classList.remove('hidden');
            turnstileDivsInPane.forEach(div => renderTurnstile(div));
            mainContentContainerEl.classList.toggle('messaging-active', pane.id === 'tab-content-messaging');
            if (pane.id === 'tab-content-api-keys' && typeof window.initializeApiKeysTab === 'function') window.initializeApiKeysTab();
            if (pane.id === 'tab-content-my-applications' && typeof window.loadOauthAppsTabData === 'function') window.loadOauthAppsTabData();
            if (pane.id === 'tab-content-messaging' && typeof window.loadMessagingTabData === 'function') window.loadMessagingTabData();
            if (pane.id === 'tab-content-security-settings' && typeof window.initializeSecuritySettings === 'function') {
                if (currentUserData) {
                    window.initializeSecuritySettings(currentUserData);
                } else {
                    apiCall('/api/me').then(response => {
                        if (response.ok && response.data) {
                            currentUserData = response.data;
                            window.initializeSecuritySettings(currentUserData);
                        }
                    });
                }
            }
            if (pane.id === 'tab-content-admin-users' && typeof window.loadAdminUsersData === 'function') window.loadAdminUsersData();
            if (pane.id === 'tab-content-admin-apps' && typeof window.loadAdminOauthClientsData === 'function') window.loadAdminOauthClientsData();
        } else {
            turnstileDivsInPane.forEach(div => removeTurnstile(div));
            pane.classList.add('hidden');
        }
    });
    clearMessages();
    const newlyCreatedApiKeyDisplayDiv = document.getElementById('newly-created-api-key-display');
    const newOauthClientCredentialsDiv = document.getElementById('new-oauth-client-credentials');
    if (newlyCreatedApiKeyDisplayDiv) newlyCreatedApiKeyDisplayDiv.classList.add('hidden');
    if (newOauthClientCredentialsDiv) newOauthClientCredentialsDiv.classList.add('hidden');
    if (window.innerWidth < 769 && sidebarEl && sidebarEl.classList.contains('open')) {
        toggleSidebar();
    }
    if (paneIdToActivate === 'tab-content-messaging') {
        if (typeof window.updateUnreadMessagesIndicator === 'function') {
             window.updateUnreadMessagesIndicator();
        } else if (userPresenceSocket && userPresenceSocket.readyState === WebSocket.OPEN) {
            userPresenceSocket.send(JSON.stringify({type: "REQUEST_INITIAL_STATE"}));
        }
    }
}
function displayCorrectView(userData) {
    clearMessages();
    currentUserData = userData;
    const isLoggedIn = !!userData?.email;
    const isAdmin = isLoggedIn && userData.is_admin === true;
    document.querySelectorAll('.cf-turnstile').forEach(div => removeTurnstile(div));
    if (topBarUserInfoEl) topBarUserInfoEl.classList.toggle('hidden', !isLoggedIn);
    if (isLoggedIn && topBarUserEmailEl) topBarUserEmailEl.textContent = userData.email || '未知邮箱';
    if (isLoggedIn && topBarUserUsernameEl) topBarUserUsernameEl.textContent = userData.username || '用户';
    if (topBarAuthButtonsEl) topBarAuthButtonsEl.classList.toggle('hidden', isLoggedIn);
    if (topBarMessagingButton) topBarMessagingButton.classList.toggle('hidden', !isLoggedIn);
    if (topBarAdminLinkEl) topBarAdminLinkEl.classList.toggle('hidden', !isAdmin);
    const adminTabsUl = document.getElementById('admin-tabs');
    if (adminTabsUl) adminTabsUl.classList.toggle('hidden', !isAdmin);
    const currentPath = window.location.pathname;
    if (isLoggedIn) {
        if (sidebarEl) sidebarEl.classList.remove('hidden');
        if (appWrapper) appWrapper.classList.add('logged-in-layout');
        if (appWrapper) appWrapper.classList.remove('logged-out-layout');
        if (['/', '/user/login', '/user/register'].includes(currentPath)) {
            window.location.pathname = isAdmin ? '/user/admin/users' : '/user/profile';
            return;
        }
        if(authSection) authSection.classList.add('hidden');
        if(loggedInSection) loggedInSection.classList.toggle('hidden', currentPath.startsWith('/user/admin'));
        if(adminSection) adminSection.classList.toggle('hidden', !currentPath.startsWith('/user/admin'));
        if (typeof window.initializePersonalInfoForm === 'function') window.initializePersonalInfoForm(userData);
        connectUserPresenceWebSocket();
        let paneIdToActivate = pathToPaneIdMap[currentPath];
        if (!paneIdToActivate) {
            paneIdToActivate = isAdmin ? 'tab-content-admin-users' : 'tab-content-personal-info';
        }
        if (!isAdmin && paneIdToActivate.startsWith('tab-content-admin-')) {
             window.location.pathname = '/user/profile'; return;
        }
        activatePane(paneIdToActivate);
        const allNavLinks = [...accountTabLinks, ...adminTabLinks];
        allNavLinks.forEach(link => {
            link.classList.toggle('selected', link.dataset.paneId === paneIdToActivate);
        });
    } else {
        if (userPresenceSocket && userPresenceSocket.readyState === WebSocket.OPEN) {
            userPresenceSocket.close();
            userPresenceSocket = null;
        }
        if (sidebarEl) sidebarEl.classList.add('hidden');
        if (appWrapper) appWrapper.classList.remove('logged-in-layout');
        if (appWrapper) appWrapper.classList.add('logged-out-layout');
        if (mainContentContainerEl) mainContentContainerEl.classList.remove('messaging-active');
        if (Object.keys(pathToPaneIdMap).includes(currentPath) || currentPath === '/user/account' || currentPath.startsWith('/user/admin')) {
            window.location.pathname = '/user/login'; return;
        }
        if(loggedInSection) loggedInSection.classList.add('hidden');
        if(adminSection) adminSection.classList.add('hidden');
        if(authSection) authSection.classList.remove('hidden');
        const login2FASection = document.getElementById('login-2fa-section');
        const loginFormContainer = document.getElementById('login-form');
        const registerFormContainer = document.getElementById('register-form');
        if (currentPath === '/' || currentPath === '/user/login') {
            if(loginFormContainer) { loginFormContainer.classList.remove('hidden'); if(loginFormEl) loginFormEl.reset(); renderTurnstile(loginFormContainer.querySelector('.cf-turnstile')); }
            if(registerFormContainer) registerFormContainer.classList.add('hidden');
        } else if (currentPath === '/user/register') {
            if(loginFormContainer) loginFormContainer.classList.add('hidden');
            if(registerFormContainer) { registerFormContainer.classList.remove('hidden'); if(registerFormEl) registerFormEl.reset(); renderTurnstile(registerFormContainer.querySelector('.cf-turnstile'));}
        }
        if(login2FASection) login2FASection.classList.add('hidden');
        loginEmailFor2FA = null;
        updateUnreadMessagesIndicatorUI(0);
    }
}
async function fetchAppConfigAndInitialize() {
    try {
        const response = await apiCall('/api/config');
        if (response.ok && response.data.turnstileSiteKey) {
            TURNSTILE_SITE_KEY = response.data.turnstileSiteKey;
        }
    } catch (error) { }
    await checkLoginStatus();
}
async function checkLoginStatus() {
    const { ok, status, data } = await apiCall('/api/me');
    displayCorrectView(ok && data.email ? data : null);
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has('registered') && (window.location.pathname === '/user/login' || window.location.pathname === '/')) {
        showMessage('注册成功!请使用您的邮箱或用户名登录。', 'success');
        const newUrl = new URL(window.location);
        newUrl.searchParams.delete('registered');
        window.history.replaceState({}, document.title, newUrl.toString());
    }
}
document.addEventListener('DOMContentLoaded', () => {
    authSection = document.getElementById('auth-section');
    loggedInSection = document.getElementById('logged-in-section');
    adminSection = document.getElementById('admin-section');
    loginFormEl = document.getElementById('login-form-el');
    registerFormEl = document.getElementById('register-form-el');
    appWrapper = document.getElementById('app-wrapper');
    topBarUserEmailEl = document.getElementById('top-bar-user-email');
    topBarUserUsernameEl = document.getElementById('top-bar-user-username');
    topBarUserInfoEl = document.getElementById('top-bar-user-info');
    topBarAuthButtonsEl = document.getElementById('top-bar-auth-buttons');
    topBarLogoutButtonEl = document.getElementById('top-bar-logout-button');
    userMenuButtonEl = document.getElementById('user-menu-button');
    userDropdownMenuEl = document.getElementById('user-dropdown-menu');
    topBarAccountLinkEl = document.getElementById('top-bar-account-link');
    topBarAdminLinkEl = document.getElementById('top-bar-admin-link');
    topBarMessagingButton = document.getElementById('top-bar-messaging-button');
    sidebarEl = document.getElementById('sidebar');
    sidebarToggleEl = document.getElementById('sidebar-toggle');
    mainContentContainerEl = document.getElementById('main-content').querySelector('.container');
    sidebarOverlayEl = document.getElementById('sidebar-overlay');
    accountTabLinks = Array.from(document.querySelectorAll('#account-tabs .sidebar-link'));
    adminTabLinks = Array.from(document.querySelectorAll('#admin-tabs .sidebar-link'));
    tabPanes = Array.from(document.querySelectorAll('.tab-pane'));
    themeToggleButton = document.getElementById('theme-toggle-button');
    themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
    themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
    unreadMessagesIndicator = document.getElementById('unread-messages-indicator');
    let isDarkMode = localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
    applyTheme(isDarkMode);
    if (themeToggleButton) {
        themeToggleButton.addEventListener('click', () => {
            isDarkMode = !isDarkMode;
            localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
            applyTheme(isDarkMode);
        });
    }
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
        if (localStorage.getItem('theme') === null) {
            isDarkMode = e.matches;
            applyTheme(isDarkMode);
        }
    });
    [...accountTabLinks, ...adminTabLinks].forEach(link => {
        link.addEventListener('click', (event) => {
        });
    });
    if (topBarMessagingButton) {
        topBarMessagingButton.addEventListener('click', (e) => {
            e.preventDefault();
            window.location.pathname = '/user/messaging';
        });
    }
    if (sidebarToggleEl) sidebarToggleEl.addEventListener('click', toggleSidebar);
    if (sidebarOverlayEl) sidebarOverlayEl.addEventListener('click', toggleSidebar);
    if (userMenuButtonEl && userDropdownMenuEl) {
        userMenuButtonEl.addEventListener('click', (event) => {
            event.stopPropagation();
            userDropdownMenuEl.classList.toggle('hidden');
        });
        document.addEventListener('click', (event) => {
            if (!userDropdownMenuEl.classList.contains('hidden') && !userMenuButtonEl.contains(event.target) && !userDropdownMenuEl.contains(event.target)) {
                userDropdownMenuEl.classList.add('hidden');
            }
        });
    }
    if (topBarAccountLinkEl) {
        topBarAccountLinkEl.addEventListener('click', (e) => {
            e.preventDefault();
            window.location.pathname = '/user/profile';
            if (userDropdownMenuEl) userDropdownMenuEl.classList.add('hidden');
        });
    }
    if (topBarAdminLinkEl) {
        topBarAdminLinkEl.addEventListener('click', (e) => {
            e.preventDefault();
            window.location.pathname = '/user/admin/users';
            if (userDropdownMenuEl) userDropdownMenuEl.classList.add('hidden');
        });
    }
    if (loginFormEl) loginFormEl.addEventListener('submit', (event) => handleAuth(event, 'login'));
    if (registerFormEl) registerFormEl.addEventListener('submit', (event) => handleAuth(event, 'register'));
    if (topBarLogoutButtonEl) topBarLogoutButtonEl.addEventListener('click', handleLogout);
    fetchAppConfigAndInitialize();
});
window.handleAuth = async function(event, type) {
    event.preventDefault(); clearMessages();
    const form = event.target;
    const turnstileContainer = form.querySelector('.cf-turnstile');
    const turnstileToken = form.querySelector('[name="cf-turnstile-response"]')?.value;
    const login2FASection = document.getElementById('login-2fa-section');
    const loginTotpCodeInput = document.getElementById('login-totp-code');
    if (!turnstileToken && turnstileContainer) {
        showMessage('人机验证失败,请刷新页面或稍后重试。', 'error');
        if (turnstileContainer) resetTurnstileInContainer(turnstileContainer);
        return;
    }
    let endpoint = '', requestBody = {};
    if (type === 'login') {
        const identifier = form.elements['identifier'].value, password = form.elements['password'].value;
        const totpCode = loginTotpCodeInput ? loginTotpCodeInput.value : '';
        if (!identifier || !password) { showMessage('邮箱/用户名和密码不能为空。'); return; }
        if (loginEmailFor2FA && totpCode) { endpoint = '/api/login/2fa-verify'; requestBody = { email: loginEmailFor2FA, totpCode }; }
        else { endpoint = '/api/login'; requestBody = { identifier, password, turnstileToken }; }
    } else {
        endpoint = '/api/register';
        const {email, username, password, confirmPassword, phoneNumber} = Object.fromEntries(new FormData(form));
        if (password !== confirmPassword) { showMessage('两次输入的密码不一致。'); return; }
        if (!email || !username || !password) { showMessage('邮箱、用户名和密码为必填项。'); return; }
        if (password.length < 6) { showMessage('密码至少需要6个字符。'); return; }
        requestBody = { email, username, password, confirmPassword, phoneNumber, turnstileToken };
    }
    const { ok, status, data } = await apiCall(endpoint, 'POST', requestBody);
    if (turnstileContainer) resetTurnstileInContainer(turnstileContainer);
    if (ok && data.success) {
        if (data.twoFactorRequired && data.email) {
            showMessage('需要两步验证。请输入验证码。', 'info'); loginEmailFor2FA = data.email;
            if(login2FASection) login2FASection.classList.remove('hidden'); if(loginTotpCodeInput) loginTotpCodeInput.focus();
        } else {
            form.reset();
            if(login2FASection) login2FASection.classList.add('hidden'); loginEmailFor2FA = null;
            if (type === 'login' || (type === 'login' && loginEmailFor2FA) || (data.twoFactorRequired === undefined)) {
                 window.location.href = (data.is_admin === true) ? '/user/admin/users' : '/user/profile';
            } else {
                 window.location.href = '/user/login?registered=true';
            }
        }
    } else {
        showMessage(data.error || ('操作失败 (' + status + ')'), 'error', data.details ? true : false);
        if (type === 'login' && loginEmailFor2FA && status !== 401 && login2FASection) { login2FASection.classList.remove('hidden'); }
        else if (status === 401 && data.error === '两步验证码无效' && login2FASection) {
            login2FASection.classList.remove('hidden'); if(loginTotpCodeInput) { loginTotpCodeInput.value = ''; loginTotpCodeInput.focus(); }
        } else if (login2FASection) { login2FASection.classList.add('hidden'); loginEmailFor2FA = null; }
    }
};
window.handleLogout = async function() {
    clearMessages();
    if (userPresenceSocket && userPresenceSocket.readyState === WebSocket.OPEN) {
        userPresenceSocket.close();
    }
    if (typeof window.closeActiveConversationSocket === 'function') {
        window.closeActiveConversationSocket();
    }
    await apiCall('/api/logout', 'POST');
    currentUserData = null;
    window.location.href = '/user/login';
};
window.turnstileCallbackLogin = function(token) { };
window.turnstileCallbackRegister = function(token) { };
window.turnstileCallbackPasteApi = function(token) { };
window.turnstileCallbackCloudPc = function(token) { };
window.turnstileCallbackOauthClient = function(token) { };
window.copyToClipboard = function(text, itemNameToCopy = '内容') {
    if (!text) { showMessage('没有可复制的'+itemNameToCopy+'。', 'warning'); return; }
    navigator.clipboard.writeText(text).then(() => {
         showMessage(itemNameToCopy + '已复制到剪贴板!', 'success');
    }).catch(err => {
        showMessage('复制失败: ' + err, 'error');
    });
};
window.escapeHtml = function(unsafe) {
    if (unsafe === null || typeof unsafe === 'undefined') return '';
    return String(unsafe)
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
};
window.apiCall = apiCall;
window.showMessage = showMessage;
window.clearMessages = clearMessages;
window.renderTurnstile = renderTurnstile;
window.removeTurnstile = removeTurnstile;
window.resetTurnstileInContainer = resetTurnstileInContainer;
window.checkLoginStatus = checkLoginStatus;
window.isValidEmail = function(email) {
    if (typeof email !== 'string') return false;
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
};
window.updateUnreadMessagesIndicatorUI = updateUnreadMessagesIndicatorUI;
相关推荐
仙人掌_lz2 小时前
小型语言模型:为何“小”才是“大”?
人工智能·ai·语言模型·自然语言处理·llm
陈纬度啊10 小时前
P7-大规模语言模型分布式训练与微调框架调研文档
ai
DevangLic12 小时前
ffmpeg baidu
人工智能·pytorch·python·学习·ai·ffmpeg
在未来等你19 小时前
互联网大厂Java求职面试:AI与云原生架构实战解析
java·spring boot·低代码·ai·云原生·面试·架构设计
小白跃升坊21 小时前
最炫酷的企业 AI 助手来啦(含前端源码)!
ai·大语言模型·maxkb
码观天工1 天前
.NET AI 生态关键拼图:全面解读 AI Extensions 和 Vector Extensions 如何重塑.NET开发生态
ai·c#·.net·向量数据库
装不满的克莱因瓶2 天前
【小白AI教程】大模型知识扫盲通识
人工智能·数学建模·ai·大模型·llm·llama·rag
要养家的程序猿2 天前
RagFlow参数配置&测试
人工智能·ai
鸭鸭鸭进京赶烤2 天前
2025年绿色材料与制造技术国际学术会议(GMMT 2025)
人工智能·计算机视觉·ai·aigc·制造