1. 后端新增menu字段接口和route权限字段,重构权限接口返回菜单树 2. 前端替换硬编码侧边菜单为动态渲染,新增菜单store和图标映射逻辑 3. 调整权限路由元信息和初始化权限数据
170 lines
4.1 KiB
Vue
170 lines
4.1 KiB
Vue
<template>
|
|
<el-container class="admin-root">
|
|
<el-aside width="220px" class="aside">
|
|
<div class="brand">{{sysTitle}}</div>
|
|
<el-menu
|
|
:default-active="active"
|
|
router
|
|
background-color="#0f172a"
|
|
text-color="#e2e8f0"
|
|
active-text-color="#38bdf8"
|
|
>
|
|
<!-- 动态渲染菜单 -->
|
|
<template v-for="menu in auth.menu" :key="menu.id">
|
|
<!-- 有子菜单的顶级菜单 -->
|
|
<el-sub-menu
|
|
v-if="menu.children && menu.children.length > 0"
|
|
:index="menu.route"
|
|
>
|
|
<template #title>
|
|
<el-icon><component :is="getIcon(menu.code)" /></el-icon>
|
|
<span>{{ menu.name }}</span>
|
|
</template>
|
|
<el-menu-item
|
|
v-for="child in menu.children"
|
|
:key="child.id"
|
|
:index="child.route"
|
|
>
|
|
<span>{{ child.name }}</span>
|
|
</el-menu-item>
|
|
</el-sub-menu>
|
|
<!-- 无子菜单的顶级菜单 -->
|
|
<el-menu-item
|
|
v-else
|
|
:index="menu.route"
|
|
>
|
|
<el-icon><component :is="getIcon(menu.code)" /></el-icon>
|
|
<span>{{ menu.name }}</span>
|
|
</el-menu-item>
|
|
</template>
|
|
</el-menu>
|
|
</el-aside>
|
|
<el-container>
|
|
<el-header class="header">
|
|
<div class="right">
|
|
<span class="nick">{{ auth.user?.nickname || auth.user?.username }}</span>
|
|
<el-button type="primary" link @click="onLogout">退出</el-button>
|
|
</div>
|
|
</el-header>
|
|
<el-main class="main">
|
|
<div class="page-title-bar">
|
|
<h1 class="page-title">{{ pageTitle }}</h1>
|
|
</div>
|
|
<div class="page-content">
|
|
<router-view />
|
|
</div>
|
|
</el-main>
|
|
</el-container>
|
|
</el-container>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref, markRaw } from "vue";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import { Key, Setting, Folder } from "@element-plus/icons-vue";
|
|
import { ElMessage } from "element-plus";
|
|
import { useAuthStore } from "@/modules/admin/stores/auth";
|
|
import axios from "axios";
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const auth = useAuthStore();
|
|
const sysTitle = ref("");
|
|
|
|
const active = computed(() => route.path);
|
|
const pageTitle = computed(() => (route.meta.title as string) || "");
|
|
|
|
// 图标映射
|
|
const iconMap: Record<string, any> = {
|
|
"perm": markRaw(Key),
|
|
"system": markRaw(Setting),
|
|
};
|
|
|
|
function getIcon(code: string) {
|
|
// 查找匹配的图标
|
|
for (const key in iconMap) {
|
|
if (code.startsWith(key)) {
|
|
return iconMap[key];
|
|
}
|
|
}
|
|
return Folder;
|
|
}
|
|
|
|
async function getSysTitle() {
|
|
const baseURL = import.meta.env.VITE_API_BASE || "/api";
|
|
let res = await axios.get(`${baseURL}/settings/public?setting_id=1`)
|
|
if (res.data) {
|
|
sysTitle.value = res.data.value;
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
await getSysTitle();
|
|
if (!auth.user) {
|
|
await auth.fetchProfile();
|
|
}
|
|
await auth.fetchPermissions();
|
|
} catch {
|
|
ElMessage.error("获取用户信息失败,请重新登录");
|
|
await auth.logout();
|
|
router.replace({ name: "admin-login", query: { redirect: route.fullPath } });
|
|
}
|
|
});
|
|
|
|
async function onLogout() {
|
|
await auth.logout();
|
|
router.push({ name: "admin-login" });
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.admin-root {
|
|
height: 100vh;
|
|
}
|
|
.aside {
|
|
background: #0f172a;
|
|
color: #e2e8f0;
|
|
}
|
|
.brand {
|
|
padding: 18px 16px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
|
|
}
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
background: #fff;
|
|
}
|
|
.right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.page-title-bar {
|
|
flex-shrink: 0;
|
|
margin-bottom: 16px;
|
|
}
|
|
.page-title {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
line-height: 1.4;
|
|
}
|
|
.nick {
|
|
color: var(--el-text-color-secondary);
|
|
font-size: 14px;
|
|
}
|
|
.main {
|
|
background: #f8fafc;
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
</style>
|