add logout && fix theme

This commit is contained in:
jxxghp
2023-06-24 17:26:00 +08:00
parent db40e4a800
commit 4a6cde67b3
5 changed files with 152 additions and 166 deletions
+17 -4
View File
@@ -1,22 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTheme } from 'vuetify' import type { ThemeSwitcherTheme } from '@layouts/types';
import type { ThemeSwitcherTheme } from '@layouts/types' import { ref, watch } from 'vue';
import { useTheme } from 'vuetify';
const props = defineProps<{ const props = defineProps<{
themes: ThemeSwitcherTheme[] themes: ThemeSwitcherTheme[]
}>() }>()
const { name: themeName, global: globalTheme } = useTheme() const { name: themeName, global: globalTheme } = useTheme()
const { state: currentThemeName, next: getNextThemeName, index: currentThemeIndex } = useCycleList(props.themes.map(t => t.name), { initialValue: themeName })
const savedTheme = ref(localStorage.getItem('theme') ?? themeName)
const { state: currentThemeName, next: getNextThemeName, index: currentThemeIndex } = useCycleList(props.themes.map(t => t.name), { initialValue: savedTheme.value })
const changeTheme = () => { const changeTheme = () => {
globalTheme.name.value = getNextThemeName() const nextTheme = getNextThemeName()
globalTheme.name.value = nextTheme
savedTheme.value = nextTheme
localStorage.setItem('theme', nextTheme)
} }
// Update icon if theme is changed from other sources // Update icon if theme is changed from other sources
watch(() => globalTheme.name.value, val => { watch(() => globalTheme.name.value, val => {
currentThemeName.value = val currentThemeName.value = val
}) })
// Apply saved theme on page load
onMounted(() => {
globalTheme.name.value = savedTheme.value
})
</script> </script>
<template> <template>
+21 -48
View File
@@ -1,30 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import avatar1 from '@images/avatars/avatar-1.png' import router from "@/router";
import avatar1 from "@images/avatars/avatar-1.png";
// 执行注销操作
const logout = () => {
// 清除登录状态信息,例如删除令牌
localStorage.removeItem("token");
// 重定向到登录页面或其他适当的页面
router.push("/login");
};
</script> </script>
<template> <template>
<VBadge <VBadge dot location="bottom right" offset-x="3" offset-y="3" color="success" bordered>
dot <VAvatar class="cursor-pointer" color="primary" variant="tonal">
location="bottom right"
offset-x="3"
offset-y="3"
color="success"
bordered
>
<VAvatar
class="cursor-pointer"
color="primary"
variant="tonal"
>
<VImg :src="avatar1" /> <VImg :src="avatar1" />
<!-- SECTION Menu --> <!-- SECTION Menu -->
<VMenu <VMenu activator="parent" width="230" location="bottom end" offset="14px">
activator="parent"
width="230"
location="bottom end"
offset="14px"
>
<VList> <VList>
<!-- 👉 User Avatar & Name --> <!-- 👉 User Avatar & Name -->
<VListItem> <VListItem>
@@ -37,19 +31,14 @@ import avatar1 from '@images/avatars/avatar-1.png'
offset-y="3" offset-y="3"
color="success" color="success"
> >
<VAvatar <VAvatar color="primary" variant="tonal">
color="primary"
variant="tonal"
>
<VImg :src="avatar1" /> <VImg :src="avatar1" />
</VAvatar> </VAvatar>
</VBadge> </VBadge>
</VListItemAction> </VListItemAction>
</template> </template>
<VListItemTitle class="font-weight-semibold"> <VListItemTitle class="font-weight-semibold"> 管理员 </VListItemTitle>
管理员
</VListItemTitle>
<VListItemSubtitle>Admin</VListItemSubtitle> <VListItemSubtitle>Admin</VListItemSubtitle>
</VListItem> </VListItem>
<VDivider class="my-2" /> <VDivider class="my-2" />
@@ -57,11 +46,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
<!-- 👉 Profile --> <!-- 👉 Profile -->
<VListItem link> <VListItem link>
<template #prepend> <template #prepend>
<VIcon <VIcon class="me-2" icon="mdi-account-outline" size="22" />
class="me-2"
icon="mdi-account-outline"
size="22"
/>
</template> </template>
<VListItemTitle>个人中心</VListItemTitle> <VListItemTitle>个人中心</VListItemTitle>
@@ -70,11 +55,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
<!-- 👉 Settings --> <!-- 👉 Settings -->
<VListItem link> <VListItem link>
<template #prepend> <template #prepend>
<VIcon <VIcon class="me-2" icon="mdi-cog-outline" size="22" />
class="me-2"
icon="mdi-cog-outline"
size="22"
/>
</template> </template>
<VListItemTitle>设置</VListItemTitle> <VListItemTitle>设置</VListItemTitle>
@@ -83,11 +64,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
<!-- 👉 FAQ --> <!-- 👉 FAQ -->
<VListItem link> <VListItem link>
<template #prepend> <template #prepend>
<VIcon <VIcon class="me-2" icon="mdi-help-circle-outline" size="22" />
class="me-2"
icon="mdi-help-circle-outline"
size="22"
/>
</template> </template>
<VListItemTitle>帮助</VListItemTitle> <VListItemTitle>帮助</VListItemTitle>
@@ -97,13 +74,9 @@ import avatar1 from '@images/avatars/avatar-1.png'
<VDivider class="my-2" /> <VDivider class="my-2" />
<!-- 👉 Logout --> <!-- 👉 Logout -->
<VListItem to="/login"> <VListItem @click="logout">
<template #prepend> <template #prepend>
<VIcon <VIcon class="me-2" icon="mdi-logout" size="22" />
class="me-2"
icon="mdi-logout"
size="22"
/>
</template> </template>
<VListItemTitle>注销</VListItemTitle> <VListItemTitle>注销</VListItemTitle>
+14 -32
View File
@@ -1,52 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import misc404 from '@images/pages/404.png' import misc404 from "@images/pages/404.png";
import miscMaskDark from '@images/pages/misc-mask-dark.png' import miscMaskDark from "@images/pages/misc-mask-dark.png";
import miscMaskLight from '@images/pages/misc-mask-light.png' import miscMaskLight from "@images/pages/misc-mask-light.png";
import tree from '@images/pages/tree.png' import tree from "@images/pages/tree.png";
import { useTheme } from 'vuetify' import { useTheme } from "vuetify";
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme();
const authThemeMask = computed(() => { const authThemeMask = computed(() => {
return vuetifyTheme.global.name.value === 'light' return vuetifyTheme.global.name.value === "light" ? miscMaskLight : miscMaskDark;
? miscMaskLight });
: miscMaskDark
})
</script> </script>
<template> <template>
<div class="misc-wrapper"> <div class="misc-wrapper">
<ErrorHeader <ErrorHeader
error-code="404" error-code="404"
error-title="Page Not Found " error-title="页面不存在 "
error-description="We couldn't find the page you are looking for." error-description="您想要访问的页面不存在请检查地址是否正确."
/> />
<!-- 👉 Image --> <!-- 👉 Image -->
<div class="misc-avatar w-100 text-center"> <div class="misc-avatar w-100 text-center">
<VImg <VImg :src="misc404" alt="Coming Soon" :max-width="800" class="mx-auto" />
:src="misc404" <VBtn to="/" class="mt-10"> 返回 </VBtn>
alt="Coming Soon"
:max-width="800"
class="mx-auto"
/>
<VBtn
to="/"
class="mt-10"
>
Back to Home
</VBtn>
</div> </div>
<!-- 👉 Footer --> <!-- 👉 Footer -->
<VImg <VImg :src="tree" class="misc-footer-tree d-none d-md-block" />
:src="tree"
class="misc-footer-tree d-none d-md-block"
/>
<VImg <VImg :src="authThemeMask" class="misc-footer-img d-none d-md-block" />
:src="authThemeMask"
class="misc-footer-img d-none d-md-block"
/>
</div> </div>
</template> </template>
+62 -81
View File
@@ -1,77 +1,77 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTheme } from 'vuetify' import { useTheme } from "vuetify";
import api from '@/api' import api from "@/api";
import router from '@/router' import router from "@/router";
import logo from '@images/logo.svg?raw' import logo from "@images/logo.svg?raw";
import authV1MaskDark from '@images/pages/auth-v1-mask-dark.png' import authV1MaskDark from "@images/pages/auth-v1-mask-dark.png";
import authV1MaskLight from '@images/pages/auth-v1-mask-light.png' import authV1MaskLight from "@images/pages/auth-v1-mask-light.png";
import authV1Tree2 from '@images/pages/auth-v1-tree-2.png' import authV1Tree2 from "@images/pages/auth-v1-tree-2.png";
import authV1Tree from '@images/pages/auth-v1-tree.png' import authV1Tree from "@images/pages/auth-v1-tree.png";
const form = ref({ const form = ref({
username: '', username: "",
password: '', password: "",
remember: true, remember: true,
}) });
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme();
const authThemeMask = computed(() => { const authThemeMask = computed(() => {
return vuetifyTheme.global.name.value === 'light' return vuetifyTheme.global.name.value === "light" ? authV1MaskLight : authV1MaskDark;
? authV1MaskLight });
: authV1MaskDark
})
const isPasswordVisible = ref(false) const isPasswordVisible = ref(false);
const errorMessage = ref('') const errorMessage = ref("");
// 登录获取token事件 // 登录获取token事件
const login = () => { const login = () => {
errorMessage.value = '' errorMessage.value = "";
if (!form.value.username || !form.value.password) { if (!form.value.username || !form.value.password) {
errorMessage.value = '请输入用户名和密码' errorMessage.value = "请输入用户名和密码";
return return;
} }
// 用户名密码 // 用户名密码
const formData = new FormData(); const formData = new FormData();
formData.append('username', form.value.username); formData.append("username", form.value.username);
formData.append('password', form.value.password); formData.append("password", form.value.password);
// 请求token // 请求token
api.post('/login/access-token', formData) api
.then((response: { access_token: string }) => { .post("/login/access-token", formData, {
// 获取token headers: {
const token = response.access_token Accept: "application/json", // 设置 Accept 类型
// 将token保存在本地存储中,用于后续请求 },
localStorage.setItem('token', token) })
// 跳转到首页 .then((response: any) => {
router.push('/') // 获取token
}) const token = response.access_token;
.catch((error: any) => { // 将token保存在本地存储中,用于后续请求
// 登录失败,显示错误提示 localStorage.setItem("token", token);
if (!error.response) { //保存保持登录状态
errorMessage.value = '登录失败,请检查网络连接' localStorage.setItem("remember", form.value.remember.toString());
} else if (error.response.status === 401) { // 跳转到首页
errorMessage.value = '登录失败,请检查用户名和密码是否正确' router.push("/");
} else if (error.response.status === 403) { })
errorMessage.value = '登录失败,您没有权限访问' .catch((error: any) => {
} else if (error.response.status === 500) { // 登录失败,显示错误提示
errorMessage.value = '登录失败,服务器错误' if (!error.response) {
} else { errorMessage.value = "登录失败,请检查网络连接";
errorMessage.value = `登录失败 ${error.response.status},请检查用户名和密码是否正确` } else if (error.response.status === 401) {
} errorMessage.value = "登录失败,请检查用户名和密码是否正确";
}) } else if (error.response.status === 403) {
} errorMessage.value = "登录失败,您没有权限访问";
} else if (error.response.status === 500) {
errorMessage.value = "登录失败,服务器错误";
} else {
errorMessage.value = `登录失败 ${error.response.status},请检查用户名和密码是否正确`;
}
});
};
</script> </script>
<template> <template>
<div class="auth-wrapper d-flex align-center justify-center pa-4"> <div class="auth-wrapper d-flex align-center justify-center pa-4">
<VCard <VCard class="auth-card pa-4 pt-7" max-width="448" min-width="448">
class="auth-card pa-4 pt-7"
max-width="448"
min-width="448"
>
<VCardItem class="justify-center"> <VCardItem class="justify-center">
<template #prepend> <template #prepend>
<div class="d-flex"> <div class="d-flex">
@@ -85,12 +85,8 @@ const login = () => {
</VCardItem> </VCardItem>
<VCardText class="pt-2"> <VCardText class="pt-2">
<h5 class="text-h5 font-weight-semibold mb-1"> <h5 class="text-h5 font-weight-semibold mb-1">欢迎使用 MoviePilot! 👋🏻</h5>
欢迎使用 MoviePilot! 👋🏻 <p class="mb-0">请输入用户名密码登录</p>
</h5>
<p class="mb-0">
请输入用户名密码登录
</p>
</VCardText> </VCardText>
<VCardText> <VCardText>
@@ -98,12 +94,7 @@ const login = () => {
<VRow> <VRow>
<!-- username --> <!-- username -->
<VCol cols="12"> <VCol cols="12">
<VTextField <VTextField v-model="form.username" label="用户名" type="text" required />
v-model="form.username"
label="用户名"
type="text"
required
/>
</VCol> </VCol>
<!-- password --> <!-- password -->
@@ -112,7 +103,9 @@ const login = () => {
v-model="form.password" v-model="form.password"
label="密码" label="密码"
:type="isPasswordVisible ? 'text' : 'password'" :type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'" :append-inner-icon="
isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
@click:append-inner="isPasswordVisible = !isPasswordVisible" @click:append-inner="isPasswordVisible = !isPasswordVisible"
required required
/> />
@@ -121,20 +114,11 @@ const login = () => {
<!-- remember me checkbox --> <!-- remember me checkbox -->
<div class="d-flex align-center justify-space-between flex-wrap mt-1 mb-4"> <div class="d-flex align-center justify-space-between flex-wrap mt-1 mb-4">
<VCheckbox <VCheckbox v-model="form.remember" label="保持登录" required />
v-model="form.remember"
label="保持登录"
required
/>
</div> </div>
<!-- login button --> <!-- login button -->
<VBtn <VBtn block type="submit"> 登录 </VBtn>
block
type="submit"
>
登录
</VBtn>
</VCol> </VCol>
</VRow> </VRow>
</VForm> </VForm>
@@ -154,10 +138,7 @@ const login = () => {
/> />
<!-- bg img --> <!-- bg img -->
<VImg <VImg class="auth-footer-mask d-none d-md-block" :src="authThemeMask" />
class="auth-footer-mask d-none d-md-block"
:src="authThemeMask"
/>
</div> </div>
</template> </template>
+38 -1
View File
@@ -10,31 +10,49 @@ const router = createRouter({
children: [ children: [
{ {
path: 'dashboard', path: 'dashboard',
component: () => import('../pages/dashboard.vue'), component: () => import('../pages/dashboard.vue')
}, },
{ {
path: 'account-settings', path: 'account-settings',
component: () => import('../pages/account-settings.vue'), component: () => import('../pages/account-settings.vue'),
meta: {
requiresAuth: true
}
}, },
{ {
path: 'typography', path: 'typography',
component: () => import('../pages/typography.vue'), component: () => import('../pages/typography.vue'),
meta: {
requiresAuth: true
}
}, },
{ {
path: 'icons', path: 'icons',
component: () => import('../pages/icons.vue'), component: () => import('../pages/icons.vue'),
meta: {
requiresAuth: true
}
}, },
{ {
path: 'cards', path: 'cards',
component: () => import('../pages/cards.vue'), component: () => import('../pages/cards.vue'),
meta: {
requiresAuth: true
}
}, },
{ {
path: 'tables', path: 'tables',
component: () => import('../pages/tables.vue'), component: () => import('../pages/tables.vue'),
meta: {
requiresAuth: true
}
}, },
{ {
path: 'form-layouts', path: 'form-layouts',
component: () => import('../pages/form-layouts.vue'), component: () => import('../pages/form-layouts.vue'),
meta: {
requiresAuth: true
}
}, },
], ],
}, },
@@ -59,4 +77,23 @@ const router = createRouter({
], ],
}) })
// 导航守卫
router.beforeEach((to, from, next) => {
const isAuthenticated = checkLoginStatus() // 检查用户是否已登录
if (to.meta.requiresAuth && !isAuthenticated) {
// 如果路由需要登录权限且用户未登录,则跳转到登录页面
next('/login')
} else {
// 否则,允许继续进行路由导航
next()
}
})
// 检查用户登录状态的函数
function checkLoginStatus() {
// 在此处检查用户的登录状态,例如从本地存储中读取令牌或其他登录相关的信息
const token = localStorage.getItem('token')
return !!token // 返回一个布尔值,表示用户是否已登录
}
export default router export default router