@@ -51,6 +71,8 @@ import { ref, computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' +import UiIcon from '@/components/UiIcon.vue' +import ThemeToggle from '@/components/ThemeToggle.vue' const route = useRoute() const router = useRouter() @@ -69,7 +91,7 @@ const navGroups = computed(() => { group.items.push({ path: menu.path, label: menu.name, - icon: menu.icon || '📄' + icon: menu.icon || 'file' }) } return groups @@ -93,161 +115,284 @@ function handleLogout() { .admin-layout { display: flex; height: 100%; + min-height: 100dvh; } .sidebar { - width: var(--sidebar-width); - background: var(--bg-secondary); - border-right: 1px solid var(--border); + position: relative; display: flex; + width: var(--sidebar-width); + flex: 0 0 var(--sidebar-width); flex-direction: column; - flex-shrink: 0; + overflow: hidden; + border-right: 1px solid var(--border); + background: + radial-gradient(circle at 45% -10%, rgba(183, 243, 107, 0.08), transparent 25%), + rgba(13, 16, 21, 0.96); + box-shadow: 18px 0 50px rgba(0, 0, 0, 0.16); + backdrop-filter: blur(20px); +} + +.sidebar::after { + position: absolute; + top: 0; + right: -1px; + width: 1px; + height: 24%; + background: linear-gradient(to bottom, var(--accent), transparent); + box-shadow: 0 0 16px rgba(183, 243, 107, 0.42); + content: ""; } .sidebar-brand { display: flex; align-items: center; - gap: 10px; - padding: 20px 16px; - font-size: 16px; - font-weight: 600; - border-bottom: 1px solid var(--border); + gap: 11px; + padding: 20px 18px 18px; } .brand-icon { - font-size: 20px; + display: grid; + width: 36px; + height: 36px; + flex: 0 0 auto; + place-items: center; + border: 1px solid rgba(183, 243, 107, 0.42); + border-radius: 11px; + background: var(--accent); + color: #12170f; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 4px 0 #55782f, 0 10px 24px rgba(110, 166, 54, 0.18); +} + +.brand-copy { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.brand-copy strong { + font-size: 16px; + letter-spacing: -0.02em; +} + +.brand-copy small { + color: var(--text-muted); + font-family: "Cascadia Code", Consolas, monospace; + font-size: 9px; + letter-spacing: 0.14em; } .sidebar-member-link { - display: block; - margin: 0 12px 8px; - padding: 8px 12px; - font-size: 13px; + display: flex; + align-items: center; + gap: 8px; + margin: 0 12px 10px; + padding: 9px 11px; + border: 1px solid var(--border); + border-radius: 10px; + background: rgba(255, 255, 255, 0.02); color: var(--text-secondary); - text-decoration: none; - border-radius: 8px; + font-size: 12px; + transition: border-color 160ms ease, background 160ms ease, color 160ms ease, transform 160ms ease; } + .sidebar-member-link:hover { + border-color: rgba(183, 243, 107, 0.24); + background: var(--accent-soft); color: var(--accent); - background: var(--bg-hover); + transform: translateX(2px); } .sidebar-nav { flex: 1; - padding: 12px 8px; + padding: 4px 10px 14px; overflow-y: auto; } .nav-group-title { - padding: 12px 12px 6px; - font-size: 11px; - font-weight: 600; + padding: 15px 10px 7px; color: var(--text-muted); - letter-spacing: 0.04em; + font-size: 10px; + font-weight: 750; + letter-spacing: 0.11em; text-transform: uppercase; } .nav-item { + position: relative; display: flex; + min-height: 42px; align-items: center; gap: 10px; - padding: 10px 12px; - border-radius: 8px; + margin-bottom: 3px; + padding: 8px 10px; + overflow: hidden; + border: 1px solid transparent; + border-radius: 11px; color: var(--text-secondary); - font-size: 14px; - margin-bottom: 2px; - transition: all 0.15s; + font-size: 13px; + transition: transform 180ms var(--ease-spring), border-color 160ms ease, background 160ms ease, color 160ms ease; } .nav-item:hover { - background: var(--bg-hover); + border-color: var(--border); + background: rgba(255, 255, 255, 0.035); color: var(--text-primary); + transform: translateX(2px); } .nav-item.router-link-active { - background: var(--accent); - color: white; + border-color: rgba(183, 243, 107, 0.28); + background: linear-gradient(90deg, rgba(183, 243, 107, 0.14), rgba(183, 243, 107, 0.045)); + color: var(--accent); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.025); } .nav-icon { - font-size: 16px; + display: grid; + width: 28px; + height: 28px; + flex: 0 0 auto; + place-items: center; + border-radius: 8px; + background: rgba(255, 255, 255, 0.035); +} + +.router-link-active .nav-icon { + background: rgba(183, 243, 107, 0.12); + box-shadow: 0 0 18px rgba(183, 243, 107, 0.08); +} + +.nav-label { + flex: 1; +} + +.nav-pip { + width: 5px; + height: 5px; + border-radius: 50%; + background: currentColor; + opacity: 0; + box-shadow: 0 0 9px currentColor; + transition: opacity 160ms ease; +} + +.router-link-active .nav-pip { + opacity: 1; } .sidebar-footer { - padding: 12px; + padding: 13px; border-top: 1px solid var(--border); + background: rgba(0, 0, 0, 0.14); +} + +.sidebar-theme-toggle { + margin: 0 4px 8px; } .admin-user { display: flex; align-items: center; - gap: 8px; - padding: 8px; - font-size: 13px; - margin-bottom: 8px; + gap: 10px; + margin-bottom: 10px; + padding: 6px 4px; } .avatar { - width: 28px; - height: 28px; - border-radius: 50%; - background: var(--accent); - display: flex; - align-items: center; - justify-content: center; + display: grid; + width: 34px; + height: 34px; + flex: 0 0 auto; + place-items: center; + border: 1px solid rgba(183, 243, 107, 0.3); + border-radius: 10px; + background: var(--accent-soft); + color: var(--accent); font-size: 12px; - font-weight: 600; + font-weight: 750; +} + +.admin-user-copy { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.admin-user-copy strong { + overflow: hidden; + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.admin-user-copy small { + color: var(--text-muted); + font-size: 10px; } .logout-btn { width: 100%; - font-size: 13px; + font-size: 12px; } .main-area { - flex: 1; display: flex; - flex-direction: column; min-width: 0; + flex: 1; + flex-direction: column; } .topbar { display: none; + min-height: var(--header-height); align-items: center; gap: 12px; - height: var(--header-height); - padding: 0 16px; + padding: 0 14px; border-bottom: 1px solid var(--border); - background: var(--bg-secondary); + background: rgba(12, 15, 20, 0.88); + backdrop-filter: blur(18px); } .menu-btn { - padding: 8px; - font-size: 18px; + display: grid; + width: 38px; + height: 38px; + place-items: center; + border: 1px solid var(--border); + border-radius: 10px; + background: rgba(255, 255, 255, 0.025); } .page-title { - font-size: 16px; - font-weight: 500; flex: 1; + font-size: 14px; + font-weight: 650; } .member-link { - font-size: 13px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 7px 10px; + border: 1px solid var(--border); + border-radius: 9px; color: var(--text-secondary); - text-decoration: none; - padding: 6px 12px; - border-radius: 6px; -} -.member-link:hover { - color: var(--accent); - background: var(--bg-hover); + font-size: 12px; } .main-content { flex: 1; overflow-y: auto; - padding: 24px; + padding: clamp(24px, 3vw, 42px); +} + +.main-content > :deep(*) { + max-width: 1480px; + margin-right: auto; + margin-left: auto; } .sidebar-overlay { @@ -257,28 +402,31 @@ function handleLogout() { @media (max-width: 768px) { .sidebar { position: fixed; - top: 0; - left: 0; - bottom: 0; + inset: 0 auto 0 0; z-index: 100; transform: translateX(-100%); - transition: transform 0.3s; + transition: transform 280ms var(--ease-spring); } + .sidebar.open { transform: translateX(0); } + .sidebar-overlay.active { - display: block; position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.5); z-index: 99; + display: block; + background: rgba(3, 4, 6, 0.72); + backdrop-filter: blur(6px); } + .topbar { display: flex; } + .main-content { - padding: 16px; + padding: 20px 14px 28px; } } diff --git a/frontend-admin/src/main.js b/frontend-admin/src/main.js index aa07ea0..2444452 100644 --- a/frontend-admin/src/main.js +++ b/frontend-admin/src/main.js @@ -2,8 +2,11 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import router from './router' +import { initializeTheme } from './composables/useTheme' import './assets/main.css' +initializeTheme() + const app = createApp(App) app.use(createPinia()) app.use(router) diff --git a/frontend-admin/src/views/ConversationsView.vue b/frontend-admin/src/views/ConversationsView.vue index 191adbd..9861786 100644 --- a/frontend-admin/src/views/ConversationsView.vue +++ b/frontend-admin/src/views/ConversationsView.vue @@ -73,7 +73,9 @@ class="message" :class="msg.role" > -
{{ msg.role === 'user' ? '用户' : 'AI' }}
+
+ +
@@ -116,6 +119,7 @@ import { ref, computed, onMounted, nextTick, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import api from '@/api' +import UiIcon from '@/components/UiIcon.vue' const route = useRoute() const router = useRouter() @@ -257,14 +261,6 @@ function getAttachments(msg) { return list.filter(att => att && typeof att === 'object') } -function documentIcon(att) { - const name = (att?.name || '').toLowerCase() - const mime = att?.mime || '' - if (name.endsWith('.pdf') || mime.includes('pdf')) return '📕' - if (name.endsWith('.doc') || name.endsWith('.docx') || mime.includes('word')) return '📘' - return '📄' -} - function previewImage(url) { window.open(url, '_blank') } @@ -340,8 +336,8 @@ function formatDate(d) { } .conv-item.active { - background: rgba(99, 102, 241, 0.15); - border: 1px solid rgba(99, 102, 241, 0.35); + background: var(--accent-soft); + border: 1px solid rgba(183, 243, 107, 0.28); } .conv-item-title { @@ -459,8 +455,8 @@ function formatDate(d) { } .message.user .message-content { - background: rgba(99, 102, 241, 0.2); - border: 1px solid rgba(99, 102, 241, 0.3); + background: var(--accent-soft); + border: 1px solid rgba(183, 243, 107, 0.24); } .message-time { diff --git a/frontend-admin/src/views/DashboardView.vue b/frontend-admin/src/views/DashboardView.vue index fa15261..b5b5ddb 100644 --- a/frontend-admin/src/views/DashboardView.vue +++ b/frontend-admin/src/views/DashboardView.vue @@ -6,26 +6,30 @@
-
- 👥 +
+ {{ stats.users }} 用户总数 -
-
- 💬 + 01 + +
+ {{ stats.conversations }} 会话总数 -
-
- 📝 + 02 + +
+ {{ stats.messages }} 消息总数 -
-
- 📈 + 03 + +
+ {{ stats.today_messages }} 今日消息 -
+ 04 +