补充普通用户无权限页面
This commit is contained in:
parent
9ac6f47b5e
commit
85145d67c4
@ -3,6 +3,7 @@ import { onMounted, ref } from "vue";
|
||||
export function useAppShell() {
|
||||
const setupState = ref(null);
|
||||
const authenticated = ref(null);
|
||||
const isAdmin = ref(null);
|
||||
const bootstrapping = ref(true);
|
||||
const error = ref("");
|
||||
const routePath = window.location.pathname.replace(/\/+$/, "") || "/";
|
||||
@ -20,10 +21,12 @@ export function useAppShell() {
|
||||
await checkAuth();
|
||||
} else {
|
||||
authenticated.value = false;
|
||||
isAdmin.value = false;
|
||||
}
|
||||
} catch (err) {
|
||||
setupState.value = null;
|
||||
authenticated.value = null;
|
||||
isAdmin.value = null;
|
||||
error.value = err.message || "无法连接核心服务,请先启动 scert serve";
|
||||
} finally {
|
||||
bootstrapping.value = false;
|
||||
@ -36,16 +39,19 @@ export function useAppShell() {
|
||||
});
|
||||
if (response.status === 401) {
|
||||
authenticated.value = false;
|
||||
isAdmin.value = false;
|
||||
return;
|
||||
}
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(result.error || "读取登录状态失败");
|
||||
authenticated.value = result.authenticated === true;
|
||||
isAdmin.value = result.is_admin === true || ["super_admin", "admin"].includes(result.role);
|
||||
}
|
||||
|
||||
function handleInitialized() {
|
||||
setupState.value = { ...setupState.value, initialized: true };
|
||||
authenticated.value = false;
|
||||
isAdmin.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -59,6 +65,7 @@ export function useAppShell() {
|
||||
return {
|
||||
setupState,
|
||||
authenticated,
|
||||
isAdmin,
|
||||
bootstrapping,
|
||||
error,
|
||||
routePath,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { useAppShell } from "./App.js";
|
||||
import AccessDenied from "./views/AccessDenied/AccessDenied.vue";
|
||||
import Dashboard from "./views/Dashboard/Dashboard.vue";
|
||||
import Login from "./views/Login/Login.vue";
|
||||
import PasswordReset from "./views/PasswordReset/PasswordReset.vue";
|
||||
@ -9,6 +10,7 @@ import SuperAdminReset from "./views/SuperAdminReset/SuperAdminReset.vue";
|
||||
const {
|
||||
setupState,
|
||||
authenticated,
|
||||
isAdmin,
|
||||
bootstrapping,
|
||||
error,
|
||||
routePath,
|
||||
@ -44,5 +46,9 @@ const {
|
||||
|
||||
<Login v-else-if="setupState && authenticated === false" />
|
||||
|
||||
<AccessDenied
|
||||
v-else-if="setupState && authenticated === true && isAdmin === false"
|
||||
/>
|
||||
|
||||
<Dashboard v-else-if="setupState && authenticated === true" />
|
||||
</template>
|
||||
|
||||
60
frontend/src/views/AccessDenied/AccessDenied.css
Normal file
60
frontend/src/views/AccessDenied/AccessDenied.css
Normal file
@ -0,0 +1,60 @@
|
||||
.access-denied-page {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 32px 20px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(35, 106, 135, 0.18), transparent 42%),
|
||||
#eef4f6;
|
||||
}
|
||||
|
||||
.access-denied-card {
|
||||
width: min(100%, 560px);
|
||||
padding: 48px;
|
||||
border: 1px solid rgba(31, 65, 78, 0.16);
|
||||
border-radius: 28px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
box-shadow: 0 24px 70px rgba(31, 65, 78, 0.14);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.access-denied-mark {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: contain;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.access-denied-card h1 {
|
||||
margin: 12px 0 16px;
|
||||
color: #173746;
|
||||
font-size: clamp(28px, 5vw, 40px);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.access-denied-description {
|
||||
margin: 0 auto 24px;
|
||||
max-width: 440px;
|
||||
color: #60747d;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.access-denied-error {
|
||||
margin: 0 0 18px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e6b8b8;
|
||||
border-radius: 12px;
|
||||
color: #a13b3b;
|
||||
background: #fff4f4;
|
||||
}
|
||||
|
||||
.access-denied-card .primary-button {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.access-denied-card {
|
||||
padding: 32px 24px;
|
||||
border-radius: 22px;
|
||||
}
|
||||
}
|
||||
29
frontend/src/views/AccessDenied/AccessDenied.js
Normal file
29
frontend/src/views/AccessDenied/AccessDenied.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { ref } from "vue";
|
||||
|
||||
export function useAccessDenied() {
|
||||
const loggingOut = ref(false);
|
||||
const error = ref("");
|
||||
|
||||
async function logout() {
|
||||
if (loggingOut.value) return;
|
||||
loggingOut.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const response = await fetch("/api/v1/auth/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || "退出登录失败");
|
||||
}
|
||||
window.location.assign("/login");
|
||||
} catch (err) {
|
||||
error.value = err.message || "退出登录失败";
|
||||
} finally {
|
||||
loggingOut.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return { loggingOut, error, logout };
|
||||
}
|
||||
26
frontend/src/views/AccessDenied/AccessDenied.vue
Normal file
26
frontend/src/views/AccessDenied/AccessDenied.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<main class="access-denied-page">
|
||||
<section class="access-denied-card" role="status" aria-live="polite">
|
||||
<img class="access-denied-mark" src="/Scert.png" alt="Scert 标志" />
|
||||
<p class="eyebrow">SCERT / 访问控制</p>
|
||||
<h1>当前账号没有管理权限</h1>
|
||||
<p class="access-denied-description">
|
||||
你已经登录成功,但当前账号只能使用普通用户功能,不能访问证书、服务器和系统设置管理页面。
|
||||
</p>
|
||||
<p v-if="error" class="access-denied-error">{{ error }}</p>
|
||||
<button class="primary-button" type="button" :disabled="loggingOut" @click="logout">
|
||||
{{ loggingOut ? "正在退出……" : "退出并切换账号" }}
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAccessDenied } from "./AccessDenied.js";
|
||||
|
||||
const { loggingOut, error, logout } = useAccessDenied();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "./AccessDenied.css";
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user