补充普通用户无权限页面
This commit is contained in:
parent
9ac6f47b5e
commit
85145d67c4
@ -3,6 +3,7 @@ import { onMounted, ref } from "vue";
|
|||||||
export function useAppShell() {
|
export function useAppShell() {
|
||||||
const setupState = ref(null);
|
const setupState = ref(null);
|
||||||
const authenticated = ref(null);
|
const authenticated = ref(null);
|
||||||
|
const isAdmin = ref(null);
|
||||||
const bootstrapping = ref(true);
|
const bootstrapping = ref(true);
|
||||||
const error = ref("");
|
const error = ref("");
|
||||||
const routePath = window.location.pathname.replace(/\/+$/, "") || "/";
|
const routePath = window.location.pathname.replace(/\/+$/, "") || "/";
|
||||||
@ -20,10 +21,12 @@ export function useAppShell() {
|
|||||||
await checkAuth();
|
await checkAuth();
|
||||||
} else {
|
} else {
|
||||||
authenticated.value = false;
|
authenticated.value = false;
|
||||||
|
isAdmin.value = false;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setupState.value = null;
|
setupState.value = null;
|
||||||
authenticated.value = null;
|
authenticated.value = null;
|
||||||
|
isAdmin.value = null;
|
||||||
error.value = err.message || "无法连接核心服务,请先启动 scert serve";
|
error.value = err.message || "无法连接核心服务,请先启动 scert serve";
|
||||||
} finally {
|
} finally {
|
||||||
bootstrapping.value = false;
|
bootstrapping.value = false;
|
||||||
@ -36,16 +39,19 @@ export function useAppShell() {
|
|||||||
});
|
});
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
authenticated.value = false;
|
authenticated.value = false;
|
||||||
|
isAdmin.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await response.json().catch(() => ({}));
|
const result = await response.json().catch(() => ({}));
|
||||||
if (!response.ok) throw new Error(result.error || "读取登录状态失败");
|
if (!response.ok) throw new Error(result.error || "读取登录状态失败");
|
||||||
authenticated.value = result.authenticated === true;
|
authenticated.value = result.authenticated === true;
|
||||||
|
isAdmin.value = result.is_admin === true || ["super_admin", "admin"].includes(result.role);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInitialized() {
|
function handleInitialized() {
|
||||||
setupState.value = { ...setupState.value, initialized: true };
|
setupState.value = { ...setupState.value, initialized: true };
|
||||||
authenticated.value = false;
|
authenticated.value = false;
|
||||||
|
isAdmin.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -59,6 +65,7 @@ export function useAppShell() {
|
|||||||
return {
|
return {
|
||||||
setupState,
|
setupState,
|
||||||
authenticated,
|
authenticated,
|
||||||
|
isAdmin,
|
||||||
bootstrapping,
|
bootstrapping,
|
||||||
error,
|
error,
|
||||||
routePath,
|
routePath,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useAppShell } from "./App.js";
|
import { useAppShell } from "./App.js";
|
||||||
|
import AccessDenied from "./views/AccessDenied/AccessDenied.vue";
|
||||||
import Dashboard from "./views/Dashboard/Dashboard.vue";
|
import Dashboard from "./views/Dashboard/Dashboard.vue";
|
||||||
import Login from "./views/Login/Login.vue";
|
import Login from "./views/Login/Login.vue";
|
||||||
import PasswordReset from "./views/PasswordReset/PasswordReset.vue";
|
import PasswordReset from "./views/PasswordReset/PasswordReset.vue";
|
||||||
@ -9,6 +10,7 @@ import SuperAdminReset from "./views/SuperAdminReset/SuperAdminReset.vue";
|
|||||||
const {
|
const {
|
||||||
setupState,
|
setupState,
|
||||||
authenticated,
|
authenticated,
|
||||||
|
isAdmin,
|
||||||
bootstrapping,
|
bootstrapping,
|
||||||
error,
|
error,
|
||||||
routePath,
|
routePath,
|
||||||
@ -44,5 +46,9 @@ const {
|
|||||||
|
|
||||||
<Login v-else-if="setupState && authenticated === false" />
|
<Login v-else-if="setupState && authenticated === false" />
|
||||||
|
|
||||||
|
<AccessDenied
|
||||||
|
v-else-if="setupState && authenticated === true && isAdmin === false"
|
||||||
|
/>
|
||||||
|
|
||||||
<Dashboard v-else-if="setupState && authenticated === true" />
|
<Dashboard v-else-if="setupState && authenticated === true" />
|
||||||
</template>
|
</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