mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-07 05:02:45 +08:00
add logout && fix theme
This commit is contained in:
@@ -1,22 +1,35 @@
|
||||
<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<{
|
||||
themes: ThemeSwitcherTheme[]
|
||||
}>()
|
||||
|
||||
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 = () => {
|
||||
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
|
||||
watch(() => globalTheme.name.value, val => {
|
||||
currentThemeName.value = val
|
||||
})
|
||||
|
||||
// Apply saved theme on page load
|
||||
onMounted(() => {
|
||||
globalTheme.name.value = savedTheme.value
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
<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>
|
||||
|
||||
<template>
|
||||
<VBadge
|
||||
dot
|
||||
location="bottom right"
|
||||
offset-x="3"
|
||||
offset-y="3"
|
||||
color="success"
|
||||
bordered
|
||||
>
|
||||
<VAvatar
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<VBadge dot location="bottom right" offset-x="3" offset-y="3" color="success" bordered>
|
||||
<VAvatar class="cursor-pointer" color="primary" variant="tonal">
|
||||
<VImg :src="avatar1" />
|
||||
|
||||
<!-- SECTION Menu -->
|
||||
<VMenu
|
||||
activator="parent"
|
||||
width="230"
|
||||
location="bottom end"
|
||||
offset="14px"
|
||||
>
|
||||
<VMenu activator="parent" width="230" location="bottom end" offset="14px">
|
||||
<VList>
|
||||
<!-- 👉 User Avatar & Name -->
|
||||
<VListItem>
|
||||
@@ -37,19 +31,14 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||
offset-y="3"
|
||||
color="success"
|
||||
>
|
||||
<VAvatar
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<VAvatar color="primary" variant="tonal">
|
||||
<VImg :src="avatar1" />
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
</VListItemAction>
|
||||
</template>
|
||||
|
||||
<VListItemTitle class="font-weight-semibold">
|
||||
管理员
|
||||
</VListItemTitle>
|
||||
<VListItemTitle class="font-weight-semibold"> 管理员 </VListItemTitle>
|
||||
<VListItemSubtitle>Admin</VListItemSubtitle>
|
||||
</VListItem>
|
||||
<VDivider class="my-2" />
|
||||
@@ -57,11 +46,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||
<!-- 👉 Profile -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="mdi-account-outline"
|
||||
size="22"
|
||||
/>
|
||||
<VIcon class="me-2" icon="mdi-account-outline" size="22" />
|
||||
</template>
|
||||
|
||||
<VListItemTitle>个人中心</VListItemTitle>
|
||||
@@ -70,11 +55,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||
<!-- 👉 Settings -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="mdi-cog-outline"
|
||||
size="22"
|
||||
/>
|
||||
<VIcon class="me-2" icon="mdi-cog-outline" size="22" />
|
||||
</template>
|
||||
|
||||
<VListItemTitle>设置</VListItemTitle>
|
||||
@@ -83,11 +64,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||
<!-- 👉 FAQ -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="mdi-help-circle-outline"
|
||||
size="22"
|
||||
/>
|
||||
<VIcon class="me-2" icon="mdi-help-circle-outline" size="22" />
|
||||
</template>
|
||||
|
||||
<VListItemTitle>帮助</VListItemTitle>
|
||||
@@ -97,13 +74,9 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||
<VDivider class="my-2" />
|
||||
|
||||
<!-- 👉 Logout -->
|
||||
<VListItem to="/login">
|
||||
<VListItem @click="logout">
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="mdi-logout"
|
||||
size="22"
|
||||
/>
|
||||
<VIcon class="me-2" icon="mdi-logout" size="22" />
|
||||
</template>
|
||||
|
||||
<VListItemTitle>注销</VListItemTitle>
|
||||
|
||||
@@ -1,52 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import misc404 from '@images/pages/404.png'
|
||||
import miscMaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import miscMaskLight from '@images/pages/misc-mask-light.png'
|
||||
import tree from '@images/pages/tree.png'
|
||||
import { useTheme } from 'vuetify'
|
||||
import misc404 from "@images/pages/404.png";
|
||||
import miscMaskDark from "@images/pages/misc-mask-dark.png";
|
||||
import miscMaskLight from "@images/pages/misc-mask-light.png";
|
||||
import tree from "@images/pages/tree.png";
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
const vuetifyTheme = useTheme()
|
||||
const vuetifyTheme = useTheme();
|
||||
const authThemeMask = computed(() => {
|
||||
return vuetifyTheme.global.name.value === 'light'
|
||||
? miscMaskLight
|
||||
: miscMaskDark
|
||||
})
|
||||
return vuetifyTheme.global.name.value === "light" ? miscMaskLight : miscMaskDark;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="misc-wrapper">
|
||||
<ErrorHeader
|
||||
error-code="404"
|
||||
error-title="Page Not Found ⚠️"
|
||||
error-description="We couldn't find the page you are looking for."
|
||||
error-title="页面不存在 ⚠️"
|
||||
error-description="您想要访问的页面不存在,请检查地址是否正确."
|
||||
/>
|
||||
|
||||
<!-- 👉 Image -->
|
||||
<div class="misc-avatar w-100 text-center">
|
||||
<VImg
|
||||
:src="misc404"
|
||||
alt="Coming Soon"
|
||||
:max-width="800"
|
||||
class="mx-auto"
|
||||
/>
|
||||
<VBtn
|
||||
to="/"
|
||||
class="mt-10"
|
||||
>
|
||||
Back to Home
|
||||
</VBtn>
|
||||
<VImg :src="misc404" alt="Coming Soon" :max-width="800" class="mx-auto" />
|
||||
<VBtn to="/" class="mt-10"> 返回 </VBtn>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Footer -->
|
||||
<VImg
|
||||
:src="tree"
|
||||
class="misc-footer-tree d-none d-md-block"
|
||||
/>
|
||||
<VImg :src="tree" class="misc-footer-tree d-none d-md-block" />
|
||||
|
||||
<VImg
|
||||
:src="authThemeMask"
|
||||
class="misc-footer-img d-none d-md-block"
|
||||
/>
|
||||
<VImg :src="authThemeMask" class="misc-footer-img d-none d-md-block" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import { useTheme } from 'vuetify'
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
import api from '@/api'
|
||||
import router from '@/router'
|
||||
import logo from '@images/logo.svg?raw'
|
||||
import authV1MaskDark from '@images/pages/auth-v1-mask-dark.png'
|
||||
import authV1MaskLight from '@images/pages/auth-v1-mask-light.png'
|
||||
import authV1Tree2 from '@images/pages/auth-v1-tree-2.png'
|
||||
import authV1Tree from '@images/pages/auth-v1-tree.png'
|
||||
import api from "@/api";
|
||||
import router from "@/router";
|
||||
import logo from "@images/logo.svg?raw";
|
||||
import authV1MaskDark from "@images/pages/auth-v1-mask-dark.png";
|
||||
import authV1MaskLight from "@images/pages/auth-v1-mask-light.png";
|
||||
import authV1Tree2 from "@images/pages/auth-v1-tree-2.png";
|
||||
import authV1Tree from "@images/pages/auth-v1-tree.png";
|
||||
|
||||
const form = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
username: "",
|
||||
password: "",
|
||||
remember: true,
|
||||
})
|
||||
});
|
||||
|
||||
const vuetifyTheme = useTheme()
|
||||
const vuetifyTheme = useTheme();
|
||||
|
||||
const authThemeMask = computed(() => {
|
||||
return vuetifyTheme.global.name.value === 'light'
|
||||
? authV1MaskLight
|
||||
: authV1MaskDark
|
||||
})
|
||||
return vuetifyTheme.global.name.value === "light" ? authV1MaskLight : authV1MaskDark;
|
||||
});
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const isPasswordVisible = ref(false);
|
||||
const errorMessage = ref("");
|
||||
|
||||
// 登录获取token事件
|
||||
const login = () => {
|
||||
errorMessage.value = ''
|
||||
errorMessage.value = "";
|
||||
if (!form.value.username || !form.value.password) {
|
||||
errorMessage.value = '请输入用户名和密码'
|
||||
return
|
||||
errorMessage.value = "请输入用户名和密码";
|
||||
return;
|
||||
}
|
||||
// 用户名密码
|
||||
const formData = new FormData();
|
||||
formData.append('username', form.value.username);
|
||||
formData.append('password', form.value.password);
|
||||
formData.append("username", form.value.username);
|
||||
formData.append("password", form.value.password);
|
||||
// 请求token
|
||||
api.post('/login/access-token', formData)
|
||||
.then((response: { access_token: string }) => {
|
||||
// 获取token
|
||||
const token = response.access_token
|
||||
// 将token保存在本地存储中,用于后续请求
|
||||
localStorage.setItem('token', token)
|
||||
// 跳转到首页
|
||||
router.push('/')
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// 登录失败,显示错误提示
|
||||
if (!error.response) {
|
||||
errorMessage.value = '登录失败,请检查网络连接'
|
||||
} 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},请检查用户名和密码是否正确`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
api
|
||||
.post("/login/access-token", formData, {
|
||||
headers: {
|
||||
Accept: "application/json", // 设置 Accept 类型
|
||||
},
|
||||
})
|
||||
.then((response: any) => {
|
||||
// 获取token
|
||||
const token = response.access_token;
|
||||
// 将token保存在本地存储中,用于后续请求
|
||||
localStorage.setItem("token", token);
|
||||
//保存保持登录状态
|
||||
localStorage.setItem("remember", form.value.remember.toString());
|
||||
// 跳转到首页
|
||||
router.push("/");
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// 登录失败,显示错误提示
|
||||
if (!error.response) {
|
||||
errorMessage.value = "登录失败,请检查网络连接";
|
||||
} 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>
|
||||
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
<VCard
|
||||
class="auth-card pa-4 pt-7"
|
||||
max-width="448"
|
||||
min-width="448"
|
||||
>
|
||||
<VCard class="auth-card pa-4 pt-7" max-width="448" min-width="448">
|
||||
<VCardItem class="justify-center">
|
||||
<template #prepend>
|
||||
<div class="d-flex">
|
||||
@@ -85,12 +85,8 @@ const login = () => {
|
||||
</VCardItem>
|
||||
|
||||
<VCardText class="pt-2">
|
||||
<h5 class="text-h5 font-weight-semibold mb-1">
|
||||
欢迎使用 MoviePilot! 👋🏻
|
||||
</h5>
|
||||
<p class="mb-0">
|
||||
请输入用户名密码登录
|
||||
</p>
|
||||
<h5 class="text-h5 font-weight-semibold mb-1">欢迎使用 MoviePilot! 👋🏻</h5>
|
||||
<p class="mb-0">请输入用户名密码登录</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
@@ -98,12 +94,7 @@ const login = () => {
|
||||
<VRow>
|
||||
<!-- username -->
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="form.username"
|
||||
label="用户名"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<VTextField v-model="form.username" label="用户名" type="text" required />
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
@@ -112,7 +103,9 @@ const login = () => {
|
||||
v-model="form.password"
|
||||
label="密码"
|
||||
: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"
|
||||
required
|
||||
/>
|
||||
@@ -121,20 +114,11 @@ const login = () => {
|
||||
|
||||
<!-- remember me checkbox -->
|
||||
<div class="d-flex align-center justify-space-between flex-wrap mt-1 mb-4">
|
||||
<VCheckbox
|
||||
v-model="form.remember"
|
||||
label="保持登录"
|
||||
required
|
||||
/>
|
||||
<VCheckbox v-model="form.remember" label="保持登录" required />
|
||||
</div>
|
||||
|
||||
<!-- login button -->
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
登录
|
||||
</VBtn>
|
||||
<VBtn block type="submit"> 登录 </VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
@@ -154,10 +138,7 @@ const login = () => {
|
||||
/>
|
||||
|
||||
<!-- bg img -->
|
||||
<VImg
|
||||
class="auth-footer-mask d-none d-md-block"
|
||||
:src="authThemeMask"
|
||||
/>
|
||||
<VImg class="auth-footer-mask d-none d-md-block" :src="authThemeMask" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -10,31 +10,49 @@ const router = createRouter({
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: () => import('../pages/dashboard.vue'),
|
||||
component: () => import('../pages/dashboard.vue')
|
||||
},
|
||||
{
|
||||
path: 'account-settings',
|
||||
component: () => import('../pages/account-settings.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'typography',
|
||||
component: () => import('../pages/typography.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'icons',
|
||||
component: () => import('../pages/icons.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'cards',
|
||||
component: () => import('../pages/cards.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tables',
|
||||
component: () => import('../pages/tables.vue'),
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'form-layouts',
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user