snack-mall/admin-snack/src/layouts/BasicLayout.vue

252 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount, ref, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
Expand,
Fold,
ArrowDown,
SwitchButton,
User as UserIcon,
Bell,
ChatDotRound
} from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import { useAppStore } from '@/stores/modules/app'
import { useUserStore } from '@/stores/modules/user'
import SideMenu from './components/SideMenu.vue'
import NavBreadcrumb from './components/NavBreadcrumb.vue'
import TabsNav from './components/TabsNav.vue'
const route = useRoute()
const router = useRouter()
const appStore = useAppStore()
const userStore = useUserStore()
const collapsed = computed(() => appStore.sidebarCollapsed)
/**
* 路由视图刷新控制:监听 TabsNav 派发的 'app:refresh-view' 事件
* 通过 v-if 短暂卸载再挂载 router-view达到刷新当前页面的效果
*/
const viewKey = ref(0)
function onRefreshView() {
viewKey.value++
}
function toggleSidebar() {
appStore.toggleSidebar()
}
function onResize() {
const width = window.innerWidth
appStore.toggleDevice(width < 992 ? 'mobile' : 'desktop')
if (width < 992) appStore.sidebarCollapsed = true
}
onMounted(() => {
onResize()
window.addEventListener('resize', onResize)
window.addEventListener('app:refresh-view', onRefreshView)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', onResize)
window.removeEventListener('app:refresh-view', onRefreshView)
})
async function handleCommand(cmd: string) {
if (cmd === 'logout') {
try {
await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
type: 'warning',
confirmButtonText: '退出',
cancelButtonText: '取消'
})
await userStore.logout()
router.push('/login')
} catch {
/* 用户取消 */
}
} else if (cmd === 'profile') {
router.push('/profile')
}
}
</script>
<template>
<el-container class="basic-layout">
<!-- 侧边栏 -->
<el-aside :width="collapsed ? '64px' : '220px'" class="sidebar">
<div class="logo">
<div class="logo-icon">S</div>
<transition name="fade">
<span v-if="!collapsed" class="logo-text">零食商城</span>
</transition>
</div>
<SideMenu :collapsed="collapsed" />
</el-aside>
<el-container class="right-container">
<!-- 顶栏 -->
<el-header class="header">
<div class="header-left">
<el-button text class="collapse-btn" @click="toggleSidebar">
<el-icon :size="20">
<component :is="collapsed ? Expand : Fold" />
</el-icon>
</el-button>
<NavBreadcrumb />
</div>
<div class="header-right">
<el-tooltip content="公告" placement="bottom">
<el-button text :icon="Bell" circle />
</el-tooltip>
<el-tooltip content="客服消息" placement="bottom">
<el-button text :icon="ChatDotRound" circle />
</el-tooltip>
<el-dropdown trigger="click" @command="handleCommand">
<div class="user-info">
<el-avatar :size="32" :src="userStore.avatar">
<el-icon><UserIcon /></el-icon>
</el-avatar>
<span class="username">{{ userStore.username || '管理员' }}</span>
<el-icon><ArrowDown /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile">
<el-icon><UserIcon /></el-icon>
</el-dropdown-item>
<el-dropdown-item command="logout" divided>
<el-icon><SwitchButton /></el-icon> 退
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<!-- 标签导航栏 -->
<TabsNav />
<!-- 主内容区 -->
<el-main class="main">
<router-view v-slot="{ Component, route: r }">
<transition name="slide-up" mode="out-in">
<keep-alive>
<component :is="Component" :key="viewKey + '-' + r.fullPath" />
</keep-alive>
</transition>
</router-view>
</el-main>
</el-container>
</el-container>
</template>
<style lang="scss" scoped>
.basic-layout {
height: 100vh;
overflow: hidden;
}
.sidebar {
background: linear-gradient(180deg, #1e3a8a 0%, #1e40af 100%);
transition: width 0.25s ease;
overflow: hidden;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);
flex-shrink: 0;
}
.logo {
height: $header-height;
display: flex;
align-items: center;
padding: 0 16px;
gap: 10px;
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
.logo-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.2);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 18px;
flex-shrink: 0;
}
.logo-text {
font-size: 16px;
font-weight: 600;
white-space: nowrap;
}
}
.right-container {
display: flex;
flex-direction: column;
overflow: hidden;
flex: 1;
}
.header {
background: #fff;
height: $header-height;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
border-bottom: 1px solid $color-border-light;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
z-index: 10;
flex-shrink: 0;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.collapse-btn {
padding: 8px;
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 12px;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: $bg-hover;
}
.username {
font-size: 14px;
color: $color-text-primary;
}
}
.main {
background: $bg-page;
padding: 20px;
overflow-y: auto;
flex: 1;
}
</style>