03 - 使用layout搭建界面框架
约 489 字大约 2 分钟
2025-04-14
MainLayout.vue 示例代码
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RouterView, useRouter, useRoute } from 'vue-router'
import { useTopMenuStore } from '@/stores/topmenu'
const router = useRouter()
const route = useRoute()
const menuStore = useTopMenuStore()
// 顶部菜单数据
const topMenuData = [
{ path: '/chat', name: '智能对话' },
{ path: '/config', name: '系统配置' },
{ path: '/task', name: '任务日志' },
{ path: '/flow', name: '流程中心' },
]
const isHovered = ref<boolean[]>(new Array(topMenuData.length).fill(false))
const hover = (index: number) => {
isHovered.value[index] = true
}
const unhover = (index: number) => {
isHovered.value[index] = false
}
const selectRoute = (path: string) => {
menuStore.setSelectedRoute(path)
router.push(path)
}
onMounted(() => {
menuStore.setSelectedRoute(route.path)
})
</script>
<template>
<div class="layout">
<!-- 顶部菜单 -->
<header class="header">
<div class="header-left">
<!-- <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="32" height="32" /> -->
<img alt="Vue logo" class="logo" src="@/assets/ht.gif" width="32" height="32" />
<span class="logo">AI工具</span>
</div>
<div class="header-center">
<ul class="top-menu">
<li
v-for="(route, index) in topMenuData"
:key="index"
:class="{ active: menuStore.selectedRoute === route.path, hover: isHovered[index] }"
@mouseenter="hover(index)"
@mouseleave="unhover(index)"
@click="selectRoute(route.path)"
>
<router-link :to="route.path">{{ route.name }}</router-link>
</li>
</ul>
</div>
<div class="header-right">
<!-- <button class="btn">注册</button> -->
<!-- <button class="btn">登录</button> -->
</div>
</header>
<div class="main-container">
<!-- 主体内容 -->
<main class="content">
<router-view v-slot="{ Component }">
<transition name="slide">
<component :is="Component" />
</transition>
</router-view>
<slot></slot>
</main>
</div>
</div>
</template>
<style scoped>
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background-color: #007bff;
color: white;
height: 60px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header-left {
display: flex;
align-items: center;
}
.header-left .logo {
height: 32px;
line-height: 32px;
font-size: 32px;
font-weight: bold;
margin-right: 10px;
}
.header-center .top-menu {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.header-center .top-menu li {
list-style-type: none;
float: left;
margin-left: 30px;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 20px;
a {
color: white;
}
}
li.last:after {
clear: both;
}
li.active {
background-color: #003366;
border-radius: 8px;
}
li.hover {
background-color: #6699cc;
border-radius: 8px;
}
/*
.top-menu-item {
margin: 0 15px;
cursor: pointer;
}
.top-menu-item:hover {
text-decoration: underline;
}
.header-right .btn {
margin-left: 10px;
padding: 5px 10px;
border: none;
border-radius: 4px;
background-color: white;
color: #007bff;
cursor: pointer;
}
.header-right .btn:hover {
background-color: #f0f0f0;
} */
.main-container {
display: flex;
flex: 1;
}
.content {
flex: 1;
/* padding: 20px; */
overflow-y: auto;
}
/* 浅色主题 */
.theme-light {
background-color: #ffffff;
color: #333333;
}
.theme-light .sidebar {
background-color: #f8f9fa;
}
/* 深色主题 */
.theme-dark {
background-color: #1a1a1a;
color: #ffffff;
}
.theme-dark .sidebar {
background-color: #2d2d2d;
}
</style>