364 lines
10 KiB
Vue
364 lines
10 KiB
Vue
<template>
|
|
<div class="lux-shell">
|
|
<div class="lux-shell-bg" aria-hidden="true">
|
|
<div class="lux-orb lux-orb-1" />
|
|
<div class="lux-orb lux-orb-2" />
|
|
<div class="lux-orb lux-orb-3" />
|
|
</div>
|
|
|
|
<el-container class="lux-shell-body">
|
|
<el-aside
|
|
class="sidebar-glass"
|
|
:class="{ 'is-collapsed': isSidebarCollapsed }"
|
|
@mouseenter="isSidebarCollapsed = false"
|
|
@mouseleave="isSidebarCollapsed = true"
|
|
>
|
|
<div class="sidebar-inner">
|
|
<div class="sidebar-logo">
|
|
<SidebarLogo :collapsed="isSidebarCollapsed" />
|
|
</div>
|
|
|
|
<div class="sidebar-nav-wrap">
|
|
<nav
|
|
class="sidebar-rail"
|
|
:class="{ 'is-active': isSidebarCollapsed }"
|
|
aria-label="主导航"
|
|
>
|
|
<a
|
|
v-for="item in railNavItems"
|
|
:key="item.title"
|
|
href="#"
|
|
class="sidebar-rail-item"
|
|
:title="item.title"
|
|
@click.prevent="handleRailClick"
|
|
>
|
|
<el-icon><component :is="item.icon" /></el-icon>
|
|
</a>
|
|
</nav>
|
|
|
|
<el-menu
|
|
class="lux-menu lux-menu--expanded"
|
|
:class="{ 'is-active': !isSidebarCollapsed }"
|
|
:default-active="activeMenu"
|
|
v-model:openeds="openedMenus"
|
|
background-color="transparent"
|
|
text-color="#cbd5e1"
|
|
active-text-color="#7dd3fc"
|
|
router
|
|
>
|
|
<el-sub-menu index="1">
|
|
<template #title>
|
|
<el-icon><Headset /></el-icon>
|
|
<span>耳机管理</span>
|
|
</template>
|
|
<el-menu-item index="/brand">
|
|
<el-icon><Collection /></el-icon>
|
|
<span>品牌管理</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="/model">
|
|
<el-icon><List /></el-icon>
|
|
<span>型号管理</span>
|
|
</el-menu-item>
|
|
</el-sub-menu>
|
|
<el-sub-menu index="upgrade">
|
|
<template #title>
|
|
<el-icon><Upload /></el-icon>
|
|
<span>升级管理</span>
|
|
</template>
|
|
<el-menu-item index="/ota">
|
|
<el-icon><Document /></el-icon>
|
|
<span>OTA 管理</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="/blacklist">
|
|
<el-icon><Warning /></el-icon>
|
|
<span>黑名单管理</span>
|
|
</el-menu-item>
|
|
<el-menu-item index="/ota-target-device">
|
|
<el-icon><Aim /></el-icon>
|
|
<span>定向升级</span>
|
|
</el-menu-item>
|
|
</el-sub-menu>
|
|
<el-sub-menu index="share-code">
|
|
<template #title>
|
|
<el-icon><Share /></el-icon>
|
|
<span>分享码</span>
|
|
</template>
|
|
<el-menu-item index="/share-code/log">
|
|
<el-icon><Document /></el-icon>
|
|
<span>分享日志</span>
|
|
</el-menu-item>
|
|
</el-sub-menu>
|
|
<el-sub-menu index="toolbox">
|
|
<template #title>
|
|
<el-icon><Tools /></el-icon>
|
|
<span>工具箱</span>
|
|
</template>
|
|
<el-menu-item index="/toolbox/luxsin-controller">
|
|
<el-icon><Operation /></el-icon>
|
|
<span>luxsin-controller</span>
|
|
</el-menu-item>
|
|
</el-sub-menu>
|
|
</el-menu>
|
|
</div>
|
|
</div>
|
|
</el-aside>
|
|
|
|
<el-container class="lux-right-col">
|
|
<el-header class="lux-header">
|
|
<el-breadcrumb class="lux-breadcrumb" separator="/">
|
|
<el-breadcrumb-item>首页</el-breadcrumb-item>
|
|
<el-breadcrumb-item v-if="breadcrumbSection">{{ breadcrumbSection }}</el-breadcrumb-item>
|
|
<el-breadcrumb-item v-if="breadcrumbTitle">{{ breadcrumbTitle }}</el-breadcrumb-item>
|
|
</el-breadcrumb>
|
|
<el-dropdown
|
|
trigger="click"
|
|
placement="bottom-end"
|
|
popper-class="lux-user-dropdown"
|
|
@command="handleUserMenuCommand"
|
|
>
|
|
<el-button
|
|
circle
|
|
type="primary"
|
|
class="lux-header-user-btn"
|
|
title="用户菜单"
|
|
aria-label="用户菜单"
|
|
>
|
|
<el-icon :size="18"><User /></el-icon>
|
|
</el-button>
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item v-if="currentUsername" disabled class="lux-user-dropdown-name">
|
|
{{ currentUsername }}
|
|
</el-dropdown-item>
|
|
<el-dropdown-item v-if="isSuperAdminUser" command="users" :divided="!!currentUsername">
|
|
账号管理
|
|
</el-dropdown-item>
|
|
<el-dropdown-item
|
|
command="password"
|
|
:divided="!!currentUsername && !isSuperAdminUser"
|
|
>
|
|
修改密码
|
|
</el-dropdown-item>
|
|
<el-dropdown-item command="logout">
|
|
退出登录
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</el-header>
|
|
<el-main class="lux-main">
|
|
<TabsView />
|
|
</el-main>
|
|
<el-footer class="lux-footer" height="auto">
|
|
<span class="lux-footer-copy">©Powered By EafonYang</span>
|
|
</el-footer>
|
|
</el-container>
|
|
</el-container>
|
|
|
|
<ChangePasswordDialog v-model="changePasswordVisible" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { ElMessage } from 'element-plus'
|
|
import { Headset, Collection, List, Upload, Document, Share, User, Warning, Aim, Tools, Operation } from '@element-plus/icons-vue'
|
|
import { clearAuth, getUser, isSuperAdmin } from '@/utils/auth'
|
|
import TabsView from '@/components/TabsView.vue'
|
|
import SidebarLogo from '@/components/SidebarLogo.vue'
|
|
import ChangePasswordDialog from '@/components/ChangePasswordDialog.vue'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const activeMenu = computed(() => route.path)
|
|
|
|
const isSidebarCollapsed = ref(true)
|
|
|
|
const openedMenus = ref([])
|
|
|
|
const isSuperAdminUser = computed(() => isSuperAdmin())
|
|
const currentUsername = computed(() => getUser()?.username || '')
|
|
const changePasswordVisible = ref(false)
|
|
|
|
function resolveOpenedMenus(path) {
|
|
if (path.startsWith('/brand') || path.startsWith('/model')) return ['1']
|
|
if (path.startsWith('/ota') || path.startsWith('/blacklist') || path.startsWith('/ota-target-device')) return ['upgrade']
|
|
if (path.startsWith('/share-code')) return ['share-code']
|
|
if (path.startsWith('/toolbox')) return ['toolbox']
|
|
return []
|
|
}
|
|
|
|
watch(
|
|
() => route.path,
|
|
(path) => {
|
|
openedMenus.value = resolveOpenedMenus(path)
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const railNavItems = [
|
|
{ title: '耳机管理', icon: Headset },
|
|
{ title: '升级管理', icon: Upload },
|
|
{ title: '分享码', icon: Share },
|
|
{ title: '工具箱', icon: Tools }
|
|
]
|
|
|
|
function handleRailClick() {
|
|
isSidebarCollapsed.value = false
|
|
}
|
|
|
|
const breadcrumbTitle = computed(() => {
|
|
if (route.path === '/home') return ''
|
|
const t = route.meta?.title
|
|
return typeof t === 'string' ? t : ''
|
|
})
|
|
|
|
const breadcrumbSection = computed(() => {
|
|
const path = route.path || ''
|
|
if (path.startsWith('/brand') || path.startsWith('/model')) return '耳机管理'
|
|
if (path.startsWith('/ota') || path.startsWith('/blacklist') || path.startsWith('/ota-target-device')) return '升级管理'
|
|
if (path.startsWith('/share-code')) return '分享码'
|
|
if (path.startsWith('/toolbox')) return '工具箱'
|
|
if (path.startsWith('/system')) return '系统管理'
|
|
return ''
|
|
})
|
|
|
|
const handleLogout = () => {
|
|
clearAuth()
|
|
ElMessage.success('已退出登录')
|
|
router.push('/login')
|
|
}
|
|
|
|
function handleUserMenuCommand(command) {
|
|
if (command === 'logout') {
|
|
handleLogout()
|
|
return
|
|
}
|
|
if (command === 'users') {
|
|
router.push('/system/users')
|
|
return
|
|
}
|
|
if (command === 'password') {
|
|
changePasswordVisible.value = true
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.lux-right-col {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar-inner {
|
|
width: 220px;
|
|
min-width: 220px;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.sidebar-nav-wrap {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.sidebar-rail,
|
|
.lux-menu--expanded {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.sidebar-rail:not(.is-active),
|
|
.lux-menu--expanded:not(.is-active) {
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
}
|
|
|
|
.sidebar-rail.is-active,
|
|
.lux-menu--expanded.is-active {
|
|
visibility: visible;
|
|
pointer-events: auto;
|
|
opacity: 1;
|
|
}
|
|
|
|
.sidebar-logo {
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.sidebar-glass.is-collapsed .sidebar-logo {
|
|
overflow: visible;
|
|
}
|
|
|
|
.sidebar-rail {
|
|
padding: 8px 0 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 4px;
|
|
}
|
|
|
|
.sidebar-rail-item {
|
|
width: 64px;
|
|
height: 44px;
|
|
border-radius: 10px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #cbd5e1;
|
|
text-decoration: none;
|
|
transition: background-color 0.15s ease, color 0.15s ease;
|
|
}
|
|
|
|
.sidebar-rail-item:hover {
|
|
color: #e2e8f0;
|
|
background: rgba(255, 255, 255, 0.07);
|
|
}
|
|
|
|
.sidebar-rail-item.is-active {
|
|
color: #e0f2fe;
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(56, 189, 248, 0.18),
|
|
rgba(251, 113, 133, 0.12)
|
|
);
|
|
box-shadow:
|
|
0 0 0 1px rgba(56, 189, 248, 0.2) inset,
|
|
inset 3px 0 0 rgba(251, 113, 133, 0.6);
|
|
}
|
|
|
|
.sidebar-rail-item .el-icon {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.lux-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
.lux-breadcrumb {
|
|
min-width: 0;
|
|
padding-right: 8px;
|
|
}
|
|
|
|
.lux-breadcrumb :deep(.el-breadcrumb__inner) {
|
|
color: #cbd5e1;
|
|
}
|
|
|
|
.lux-breadcrumb :deep(.el-breadcrumb__inner.is-link:hover) {
|
|
color: #7dd3fc;
|
|
}
|
|
</style>
|