Files
schoolNews/schoolNewsWeb/src/views/admin/AdminLayout.vue

174 lines
3.6 KiB
Vue
Raw Normal View History

2025-10-28 19:04:35 +08:00
<template>
<div class="admin-layout">
<!-- 头部区域主标题和副标题 -->
<div class="admin-layout-header">
<div class="header-content">
<h1 class="main-title">{{ title }}</h1>
<p class="subtitle" v-if="subtitle">{{ subtitle }}</p>
</div>
</div>
<!-- 标签页导航当前路由的兄弟路由 -->
<div class="admin-layout-tabs" v-if="menus.length > 0">
<div class="tab-item-container">
<router-link
v-for="menu in menus"
:key="menu.path"
:to="getFullPath(menu)"
class="tab-item"
:class="{ active: isActive(menu) }"
>
<img
v-if="menu.meta?.icon"
:src="String(PUBLIC_IMG_PATH + '/' + menu.meta.icon)"
class="tab-icon"
:alt="String(menu.meta?.title || '')"
/>
<span class="tab-text">{{ menu.meta?.title }}</span>
</router-link>
</div>
</div>
<!-- 内容区域 -->
<div class="admin-layout-content">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute, RouteRecordRaw } from 'vue-router';
import { getParentChildrenRoutes } from '@/utils/routeUtils';
import { PUBLIC_IMG_PATH} from '@/config'
// 定义 props
defineProps<{
title: string;
subtitle?: string;
}>();
const route = useRoute();
2025-11-14 19:17:49 +08:00
console.log(route.path);
2025-10-28 19:04:35 +08:00
// 获取兄弟路由(计算属性,确保响应式更新)
const menus = computed(() => {
return getParentChildrenRoutes(route);
});
2025-11-14 19:17:49 +08:00
console.log(menus.value);
2025-10-28 19:04:35 +08:00
// 获取完整路径
function getFullPath(menu: RouteRecordRaw): string {
// 如果路径是相对路径,需要拼接父路径
if (menu.path?.startsWith('/')) {
return menu.path;
}
// 获取父路径
const parentPath = route.matched.length >= 2
? route.matched[route.matched.length - 2].path
: '';
return `${parentPath}/${menu.path}`.replace(/\/+/g, '/');
}
// 判断是否为当前激活路由
function isActive(menu: RouteRecordRaw): boolean {
const fullPath = getFullPath(menu);
return route.path === fullPath || route.path.startsWith(fullPath + '/');
}
</script>
<style scoped lang="scss">
.admin-layout {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.admin-layout-header {
padding: 4px 0 24px 0;
.header-content {
.main-title {
font-size: 20px;
font-weight: 600;
margin: 0 0 4px 0;
line-height: 28px;
color: #1D2129;
}
.subtitle {
font-size: 14px;
margin: 0;
line-height: 22px;
color: #86909C;
}
}
}
.admin-layout-tabs {
display: flex;
gap: 0;
padding: 0;
overflow-x: auto;
margin-bottom: 24px;
.tab-item-container {
display: flex;
border-radius: 15px;
gap: 0;
overflow-x: auto;
}
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background-color: #C9CDD4;
border-radius: 2px;
}
.tab-item {
position: relative;
display: flex;
align-items: center;
background-color: #FFFFFF;
gap: 8px;
padding: 14px 20px;
font-size: 14px;
color: #4E5969;
text-decoration: none;
white-space: nowrap;
transition: all 0.2s ease;
border-bottom: 2px solid transparent;
.tab-icon {
width: 16px;
height: 16px;
object-fit: contain;
flex-shrink: 0;
}
.tab-text {
line-height: 1;
}
&:hover {
color: #165DFF;
}
&.active {
color: #165DFF;
font-weight: 500;
border-bottom-color: #165DFF;
}
}
}
.admin-layout-content {
flex: 1;
box-sizing: border-box;
}
</style>