|
|
|
|
@@ -1,16 +1,33 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref, onMounted } from 'vue'
|
|
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
|
|
import { ElMessageBox } from 'element-plus'
|
|
|
|
|
import { fetchKnowledgeBases } from '@/views/knowledge/useKnowledge'
|
|
|
|
|
import { useDatabase } from '@/views/database/useDatabase'
|
|
|
|
|
import { useAuth } from '@/composables/useAuth'
|
|
|
|
|
|
|
|
|
|
// 下拉菜单展开状态
|
|
|
|
|
const userDropdownVisible = ref(false)
|
|
|
|
|
|
|
|
|
|
// 退出确认弹窗状态
|
|
|
|
|
const showLogoutConfirm = ref(false)
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
|
|
|
|
// 获取当前用户信息
|
|
|
|
|
const { getUser, logout: authLogout } = useAuth()
|
|
|
|
|
const currentUser = ref(getUser())
|
|
|
|
|
|
|
|
|
|
// 计算用户首字母缩写
|
|
|
|
|
const userInitials = computed(() => {
|
|
|
|
|
const name = currentUser.value?.username || currentUser.value?.name || 'User'
|
|
|
|
|
const parts = name.split(' ')
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
return (parts[0][0] + parts[1][0]).toUpperCase()
|
|
|
|
|
}
|
|
|
|
|
return name.substring(0, 2).toUpperCase()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 获取 Knowledge 数量
|
|
|
|
|
const knowledgeCount = ref(0)
|
|
|
|
|
const fetchKnowledgeCount = async () => {
|
|
|
|
|
@@ -60,11 +77,11 @@ const group3 = computed(() => [
|
|
|
|
|
{ name: 'Memory', icon: 'fa-brain', path: '/memory' },
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// 第4组: Dashboard, Account, Settings
|
|
|
|
|
// 第4组: Dashboard, Models, Logs
|
|
|
|
|
const group4 = computed(() => [
|
|
|
|
|
{ name: 'Dashboard', icon: 'fa-gauge', path: '/dashboard' },
|
|
|
|
|
{ name: 'Account', icon: 'fa-user', path: '/account' },
|
|
|
|
|
{ name: 'Settings', icon: 'fa-gear', path: '/settings' },
|
|
|
|
|
{ name: 'Models', icon: 'fa-brain', path: '/settings' },
|
|
|
|
|
{ name: 'Logs', icon: 'fa-file-lines', path: '/logs' },
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const activeMenu = computed(() => {
|
|
|
|
|
@@ -83,31 +100,43 @@ const navigateTo = (item: MenuItem) => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通知设置弹窗状态
|
|
|
|
|
const showNotificationSettings = ref(false)
|
|
|
|
|
|
|
|
|
|
// 用户菜单操作
|
|
|
|
|
const handleUserCommand = (command: string) => {
|
|
|
|
|
switch (command) {
|
|
|
|
|
case 'settings':
|
|
|
|
|
// 全局设置
|
|
|
|
|
router.push('/settings')
|
|
|
|
|
case 'notifications':
|
|
|
|
|
// 通知设置
|
|
|
|
|
showNotificationSettings.value = true
|
|
|
|
|
break
|
|
|
|
|
case 'account':
|
|
|
|
|
// 账户设置
|
|
|
|
|
router.push('/account')
|
|
|
|
|
break
|
|
|
|
|
case 'userManagement':
|
|
|
|
|
// 用户管理
|
|
|
|
|
router.push('/user-management')
|
|
|
|
|
break
|
|
|
|
|
case 'logout':
|
|
|
|
|
// 退出登录
|
|
|
|
|
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning',
|
|
|
|
|
}).then(() => {
|
|
|
|
|
// 清除登录状态
|
|
|
|
|
localStorage.removeItem('token')
|
|
|
|
|
router.push('/login')
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
// 退出登录 - 显示自定义确认弹窗
|
|
|
|
|
showLogoutConfirm.value = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确认退出登录
|
|
|
|
|
const confirmLogout = () => {
|
|
|
|
|
showLogoutConfirm.value = false
|
|
|
|
|
userDropdownVisible.value = false
|
|
|
|
|
authLogout()
|
|
|
|
|
router.push('/login')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 取消退出
|
|
|
|
|
const cancelLogout = () => {
|
|
|
|
|
showLogoutConfirm.value = false
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
@@ -186,7 +215,7 @@ const handleUserCommand = (command: string) => {
|
|
|
|
|
<!-- 分隔线3 -->
|
|
|
|
|
<li class="my-4 border-t border-dark-500"></li>
|
|
|
|
|
|
|
|
|
|
<!-- 第4组: Dashboard, Account, Settings -->
|
|
|
|
|
<!-- 第4组: Dashboard, Notifications, Account, Model Settings, Logs -->
|
|
|
|
|
<li v-for="item in group4" :key="item.name">
|
|
|
|
|
<a
|
|
|
|
|
href="javascript:void(0)"
|
|
|
|
|
@@ -198,11 +227,6 @@ const handleUserCommand = (command: string) => {
|
|
|
|
|
<i :class="['fa-solid', item.icon, 'w-5', 'text-center']"></i>
|
|
|
|
|
<span>{{ item.name }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="item.name === 'Settings'" class="flex gap-1">
|
|
|
|
|
<span class="w-2 h-2 rounded-full bg-primary-orange"></span>
|
|
|
|
|
<span class="w-2 h-2 rounded-full bg-yellow-500"></span>
|
|
|
|
|
<span class="w-2 h-2 rounded-full bg-gray-500"></span>
|
|
|
|
|
</div>
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
@@ -210,65 +234,218 @@ const handleUserCommand = (command: string) => {
|
|
|
|
|
|
|
|
|
|
<!-- 底部用户信息 -->
|
|
|
|
|
<div class="border-t border-dark-500">
|
|
|
|
|
<el-dropdown trigger="click" @command="handleUserCommand" class="user-dropdown" @visible-change="(v: boolean) => userDropdownVisible = v">
|
|
|
|
|
<div class="w-full flex items-center justify-between cursor-pointer hover:bg-dark-600 px-4 py-3 transition-colors">
|
|
|
|
|
<div class="relative">
|
|
|
|
|
<div
|
|
|
|
|
class="w-full flex items-center justify-between cursor-pointer hover:bg-dark-600 px-4 py-3 transition-colors"
|
|
|
|
|
@click="userDropdownVisible = !userDropdownVisible"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
<img src="https://picsum.photos/id/64/40/40" alt="User Avatar" class="w-8 h-8 rounded-full object-cover">
|
|
|
|
|
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-primary-orange to-red-500 flex items-center justify-center text-white font-medium text-xs">
|
|
|
|
|
{{ userInitials }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="min-w-0">
|
|
|
|
|
<div class="font-medium text-sm text-gray-300 truncate">Alex Smith</div>
|
|
|
|
|
<div class="text-xs text-gray-500 truncate">alex@gmail.com</div>
|
|
|
|
|
<div class="font-medium text-sm text-gray-200 truncate">{{ currentUser?.username || currentUser?.name || 'User' }}</div>
|
|
|
|
|
<div class="text-xs text-gray-500 truncate">{{ currentUser?.email || 'user@example.com' }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<i :class="['fa-solid', userDropdownVisible ? 'fa-chevron-up' : 'fa-chevron-down', 'text-xs', 'text-gray-500']"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
<el-dropdown-menu>
|
|
|
|
|
<el-dropdown-item command="settings">
|
|
|
|
|
<i class="fa-solid fa-gear w-4 text-center"></i>
|
|
|
|
|
Settings
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
<el-dropdown-item command="userManagement">
|
|
|
|
|
<i class="fa-solid fa-user w-4 text-center"></i>
|
|
|
|
|
Users
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
<el-dropdown-item divided command="logout">
|
|
|
|
|
<i class="fa-solid fa-arrow-right-from-bracket w-4 text-center"></i>
|
|
|
|
|
Sign Out
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dropdown>
|
|
|
|
|
|
|
|
|
|
<!-- 简洁下拉弹窗 -->
|
|
|
|
|
<Transition name="dropdown-fade">
|
|
|
|
|
<div v-if="userDropdownVisible" class="user-dropdown-panel" @click.stop>
|
|
|
|
|
<div class="dropdown-menu">
|
|
|
|
|
<div class="menu-item" @click="handleUserCommand('notifications')">
|
|
|
|
|
<i class="fa-solid fa-bell text-gray-400"></i>
|
|
|
|
|
<span class="text-gray-300">Notifications</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="menu-item" @click="handleUserCommand('account')">
|
|
|
|
|
<i class="fa-solid fa-user text-gray-400"></i>
|
|
|
|
|
<span class="text-gray-300">Account</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="menu-item">
|
|
|
|
|
<i class="fa-solid fa-circle-question text-gray-400"></i>
|
|
|
|
|
<span class="text-gray-300">Help & Support</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="dropdown-divider"></div>
|
|
|
|
|
|
|
|
|
|
<div class="dropdown-footer">
|
|
|
|
|
<div class="menu-item logout" @click="handleUserCommand('logout')">
|
|
|
|
|
<i class="fa-solid fa-arrow-right-from-bracket text-gray-400"></i>
|
|
|
|
|
<span class="text-gray-300">Sign Out</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 点击外部关闭 -->
|
|
|
|
|
<div v-if="userDropdownVisible" class="fixed inset-0 z-40" @click="userDropdownVisible = false"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<!-- 退出确认弹窗 -->
|
|
|
|
|
<Teleport to="body">
|
|
|
|
|
<Transition name="fade">
|
|
|
|
|
<div v-if="showLogoutConfirm" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click="cancelLogout">
|
|
|
|
|
<div class="bg-dark-700 rounded-xl w-full max-w-sm border border-dark-500 shadow-2xl" @click.stop>
|
|
|
|
|
<div class="p-6 text-center">
|
|
|
|
|
<div class="w-14 h-14 rounded-full bg-red-500/20 flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<i class="fa-solid fa-arrow-right-from-bracket text-red-400 text-xl"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<h3 class="text-lg font-semibold text-white mb-2">Sign Out</h3>
|
|
|
|
|
<p class="text-gray-400 text-sm">Are you sure you want to sign out?</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex border-t border-dark-500">
|
|
|
|
|
<button @click="cancelLogout" class="flex-1 py-3 text-gray-400 hover:text-white hover:bg-dark-600 transition-colors">
|
|
|
|
|
Cancel
|
|
|
|
|
</button>
|
|
|
|
|
<button @click="confirmLogout" class="flex-1 py-3 text-red-400 hover:bg-red-500/10 transition-colors border-l border-dark-500">
|
|
|
|
|
Sign Out
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</Teleport>
|
|
|
|
|
|
|
|
|
|
<!-- 通知设置弹窗 -->
|
|
|
|
|
<Teleport to="body">
|
|
|
|
|
<Transition name="fade">
|
|
|
|
|
<div v-if="showNotificationSettings" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click="showNotificationSettings = false">
|
|
|
|
|
<div class="bg-dark-700 rounded-xl w-full max-w-md border border-dark-500 shadow-2xl" @click.stop>
|
|
|
|
|
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
|
|
|
|
<h3 class="text-lg font-semibold">Notification Settings</h3>
|
|
|
|
|
<button @click="showNotificationSettings = false" class="text-gray-400 hover:text-white transition-colors">
|
|
|
|
|
<i class="fa-solid fa-xmark text-xl"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="p-5 space-y-4">
|
|
|
|
|
<div class="flex items-center justify-between py-3 border-b border-dark-500">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-medium text-white">Email Notifications</div>
|
|
|
|
|
<div class="text-sm text-gray-400">Receive email for important updates</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input type="checkbox" class="sr-only peer" checked>
|
|
|
|
|
<div class="w-11 h-6 bg-dark-500 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-orange"></div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center justify-between py-3 border-b border-dark-500">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-medium text-white">Push Notifications</div>
|
|
|
|
|
<div class="text-sm text-gray-400">Receive push notifications in browser</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input type="checkbox" class="sr-only peer" checked>
|
|
|
|
|
<div class="w-11 h-6 bg-dark-500 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-orange"></div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center justify-between py-3 border-b border-dark-500">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-medium text-white">System Alerts</div>
|
|
|
|
|
<div class="text-sm text-gray-400">Get notified about system events</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input type="checkbox" class="sr-only peer" checked>
|
|
|
|
|
<div class="w-11 h-6 bg-dark-500 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-orange"></div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center justify-between py-3">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-medium text-white">Agent Updates</div>
|
|
|
|
|
<div class="text-sm text-gray-400">Notifications about agent activities</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input type="checkbox" class="sr-only peer">
|
|
|
|
|
<div class="w-11 h-6 bg-dark-500 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-orange"></div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
|
|
|
|
<button @click="showNotificationSettings = false" class="px-4 py-2 rounded-lg bg-primary-orange text-white hover:bg-orange-600 transition-colors">
|
|
|
|
|
Save Changes
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</Teleport>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.user-dropdown {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
.user-dropdown .el-dropdown-menu {
|
|
|
|
|
background-color: #262626;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 6px;
|
|
|
|
|
/* 用户下拉弹窗 */
|
|
|
|
|
.user-dropdown-panel {
|
|
|
|
|
position: absolute;
|
|
|
|
|
bottom: 100%;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
background: #1f1f1f;
|
|
|
|
|
border: 1px solid #404040;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
min-width: 200px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
|
|
|
|
z-index: 50;
|
|
|
|
|
}
|
|
|
|
|
.user-dropdown .el-dropdown-menu__item {
|
|
|
|
|
color: white;
|
|
|
|
|
padding: 8px 14px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
|
|
.dropdown-menu {
|
|
|
|
|
padding: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.15s ease;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
.user-dropdown .el-dropdown-menu__item:hover {
|
|
|
|
|
background-color: #F97316;
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
|
|
.menu-item:hover {
|
|
|
|
|
background: #333;
|
|
|
|
|
}
|
|
|
|
|
.user-dropdown .el-dropdown-menu__item--divided {
|
|
|
|
|
border-top: 1px solid #404040;
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
|
|
|
|
.dropdown-divider {
|
|
|
|
|
height: 1px;
|
|
|
|
|
background: #333;
|
|
|
|
|
margin: 4px 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dropdown-footer {
|
|
|
|
|
padding: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-item.logout:hover {
|
|
|
|
|
background: rgba(239, 68, 68, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 下拉动画 */
|
|
|
|
|
.dropdown-fade-enter-active,
|
|
|
|
|
.dropdown-fade-leave-active {
|
|
|
|
|
transition: all 0.15s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dropdown-fade-enter-from,
|
|
|
|
|
.dropdown-fade-leave-to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: translateY(5px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 退出确认弹窗动画 */
|
|
|
|
|
.fade-enter-active,
|
|
|
|
|
.fade-leave-active {
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.fade-enter-from,
|
|
|
|
|
.fade-leave-to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|