角色权限、RequirePermission、/me permissions 与 migration 000015; Reviewer Approve → Closed。Next:ECR-013B Contract Definition。 Co-authored-by: Cursor <cursoragent@cursor.com>
83 lines
2.3 KiB
Vue
83 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted } from 'vue'
|
|
import { RouterLink, RouterView, useRouter } from 'vue-router'
|
|
import BrandLogo from '@/components/BrandLogo.vue'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
|
|
const auth = useAuthStore()
|
|
const router = useRouter()
|
|
|
|
onMounted(() => {
|
|
void auth.hydrate()
|
|
})
|
|
|
|
async function onLogout() {
|
|
await auth.logout()
|
|
await router.push({ name: 'login' })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="shell">
|
|
<aside class="nav">
|
|
<RouterLink class="brand" to="/">
|
|
<BrandLogo size="nav" />
|
|
</RouterLink>
|
|
<nav>
|
|
<RouterLink to="/">概览</RouterLink>
|
|
<RouterLink to="/analytics">数据</RouterLink>
|
|
<RouterLink to="/content">内容</RouterLink>
|
|
<RouterLink to="/users">用户</RouterLink>
|
|
<RouterLink to="/orders">订单</RouterLink>
|
|
<RouterLink to="/audit">审计</RouterLink>
|
|
</nav>
|
|
<div class="foot">
|
|
<span class="muted">{{ auth.username || '管理员' }}</span>
|
|
<span v-if="auth.role" class="role">{{ auth.role }} · {{ auth.permissions.length }} 权</span>
|
|
<button class="btn ghost" type="button" @click="onLogout">退出</button>
|
|
</div>
|
|
</aside>
|
|
<main class="main">
|
|
<RouterView />
|
|
</main>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.shell { display: grid; grid-template-columns: 220px 1fr; min-height: 100vh; }
|
|
.nav {
|
|
padding: 1.15rem 1rem;
|
|
border-right: 1px solid rgba(240, 230, 226, 0.9);
|
|
background: rgba(255, 253, 251, 0.88);
|
|
backdrop-filter: blur(12px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.25rem;
|
|
}
|
|
.brand {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0.15rem 0.25rem;
|
|
}
|
|
nav { display: flex; flex-direction: column; gap: 0.35rem; }
|
|
nav a {
|
|
padding: 0.5rem 0.7rem;
|
|
border-radius: 12px;
|
|
color: var(--muted);
|
|
font-weight: 550;
|
|
}
|
|
nav a.router-link-active {
|
|
background: linear-gradient(135deg, #fff0ec, #ffe4e0);
|
|
color: var(--accent);
|
|
font-weight: 700;
|
|
box-shadow: 0 4px 12px rgba(229, 77, 66, 0.08);
|
|
}
|
|
.foot { margin-top: auto; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
.role { font-size: 0.75rem; color: var(--muted); }
|
|
.main { padding: 1.5rem 1.75rem; }
|
|
@media (max-width: 800px) {
|
|
.shell { grid-template-columns: 1fr; }
|
|
.nav { border-right: 0; border-bottom: 1px solid var(--line); }
|
|
}
|
|
</style>
|