task-2(壳层/路由): 冻结 /admin-vue/ base 与 History 路由契约为单一事实源
新增 src/config/app.ts(APP_BASE_PATH/ensureAppBasePath/joinAdminPath), vite.config base、router createWebHistory、AdminLayout logo 路径统一消费常量, 禁止散落字面量;8 用例覆盖归一化/幂等/空段/单段/非法输入与字面量单源。
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAdminSessionStore } from '@/stores/admin-session'
|
||||
import { joinAdminPath } from '@/config/app'
|
||||
import type { AdminMenuNode } from '@/types/admin'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const session = useAdminSessionStore()
|
||||
const collapsed = ref(false)
|
||||
|
||||
const activePath = computed(() => route.path)
|
||||
const groups = computed(() => session.menuTree.filter((node) => node.children?.length))
|
||||
const pageTitle = computed(() => String(route.meta.title || '管理后台'))
|
||||
const logoUrl = joinAdminPath('assets', 'logo.jpg')
|
||||
|
||||
function menuPath(node: AdminMenuNode): string {
|
||||
return node.route || '/account/users'
|
||||
}
|
||||
|
||||
function visibleChildren(node: AdminMenuNode) {
|
||||
return (node.children || []).filter((child) => child.route)
|
||||
}
|
||||
|
||||
function canSee(node: AdminMenuNode) {
|
||||
return session.isSuperAdmin.valueOf() || hasMenu(node, String(route.meta.menuKey || '')) || node.route === activePath.value
|
||||
}
|
||||
|
||||
function hasMenu(node: AdminMenuNode, key: string): boolean {
|
||||
return node.key === key || (node.children || []).some((child) => hasMenu(child, key))
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
await session.signOut()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '退出登录失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-shell" :class="{ 'is-collapsed': collapsed }">
|
||||
<aside class="admin-sidebar">
|
||||
<div class="admin-brand">
|
||||
<img :src="logoUrl" alt="数富AI" class="admin-brand-logo" />
|
||||
<div v-if="!collapsed" class="admin-brand-copy">
|
||||
<strong>数富AI</strong>
|
||||
<span>电商运营管理后台</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-scrollbar class="admin-menu-scroll">
|
||||
<el-menu :default-active="activePath" :collapse="collapsed" router class="admin-menu">
|
||||
<template v-for="group in groups" :key="group.key">
|
||||
<el-sub-menu :index="group.key">
|
||||
<template #title>
|
||||
<span class="menu-group-title">{{ group.name }}</span>
|
||||
</template>
|
||||
<el-menu-item
|
||||
v-for="item in visibleChildren(group)"
|
||||
:key="item.key"
|
||||
:index="menuPath(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</el-menu>
|
||||
<el-empty v-if="!groups.length && !session.loading" description="暂无可用菜单" :image-size="72" />
|
||||
</el-scrollbar>
|
||||
|
||||
<button class="sidebar-collapse" type="button" @click="collapsed = !collapsed">
|
||||
{{ collapsed ? '展开菜单' : '收起菜单' }}
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<section class="admin-main">
|
||||
<header class="admin-topbar">
|
||||
<div>
|
||||
<div class="admin-kicker">运营管理控制台</div>
|
||||
<h1>{{ pageTitle }}</h1>
|
||||
</div>
|
||||
<div class="admin-user">
|
||||
<div class="admin-user-meta">
|
||||
<strong>{{ session.user?.username || '当前用户' }}</strong>
|
||||
<span>{{ session.user?.role || '' }}</span>
|
||||
</div>
|
||||
<el-button text type="danger" @click="signOut">退出登录</el-button>
|
||||
</div>
|
||||
</header>
|
||||
<main class="admin-content">
|
||||
<el-alert v-if="session.error" :title="session.error" type="error" show-icon :closable="false" />
|
||||
<RouterView />
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user