This commit is contained in:
jxxghp
2023-06-30 21:37:55 +08:00
parent 9b6ded99f3
commit 5e49219f28
12 changed files with 182 additions and 200 deletions

View File

@@ -54,7 +54,7 @@ $card-spacer-content: 16px;
}
.bg-var-theme-background {
background-color: rgba(var(--v-theme-on-background), var(--v-hover-opacity)) !important;
background-color: rgba(var(--v-theme-background), var(--v-hover-opacity)) !important;
}
// [/^bg-light-(\w+)$/, ([, w]) => ({ backgroundColor: `rgba(var(--v-theme-${w}), var(--v-activated-opacity))` })],

View File

@@ -27,7 +27,6 @@ h6,
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.v-application,
.text-body-1,
.text-body-2,
.text-subtitle-1,

View File

@@ -11,7 +11,7 @@
// If navbar is contained => Squeeze navbar content on scroll
@if variables.$layout-vertical-nav-navbar-is-contained {
padding-inline: 1.2rem;
padding-inline: 1rem;
}
}

View File

@@ -27,7 +27,6 @@ h6,
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.v-application,
.text-body-1,
.text-body-2,
.text-subtitle-1,

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import VerticalNav from '@layouts/components/VerticalNav.vue'
import { useDisplay } from 'vuetify'
import VerticalNav from "@layouts/components/VerticalNav.vue";
import { useDisplay } from "vuetify";
export default defineComponent({
setup(props, { slots }) {
const isOverlayNavActive = ref(false)
const isLayoutOverlayVisible = ref(false)
const toggleIsOverlayNavActive = useToggle(isOverlayNavActive)
const isOverlayNavActive = ref(false);
const isLayoutOverlayVisible = ref(false);
const toggleIsOverlayNavActive = useToggle(isOverlayNavActive);
const route = useRoute()
const { mdAndDown } = useDisplay()
const route = useRoute();
const { mdAndDown } = useDisplay();
// This is alternative to below two commented watcher
// We want to show overlay if overlay nav is visible and want to hide overlay if overlay is hidden and vice versa.
syncRef(isOverlayNavActive, isLayoutOverlayVisible)
syncRef(isOverlayNavActive, isLayoutOverlayVisible);
const scrollDistance = ref(window.scrollY)
const scrollDistance = ref(window.scrollY);
onMounted(() => {
window.addEventListener('scroll', () => {
scrollDistance.value = window.scrollY
})
})
window.addEventListener("scroll", () => {
scrollDistance.value = window.scrollY;
});
});
return () => {
// 👉 Vertical nav
@@ -29,84 +29,63 @@ export default defineComponent({
VerticalNav,
{ isOverlayNavActive: isOverlayNavActive.value, toggleIsOverlayNavActive },
{
'nav-header': () => slots['vertical-nav-header']?.(),
'before-nav-items': () => slots['before-vertical-nav-items']?.(),
'default': () => slots['vertical-nav-content']?.(),
'after-nav-items': () => slots['after-vertical-nav-items']?.(),
},
)
"nav-header": () => slots["vertical-nav-header"]?.(),
"before-nav-items": () => slots["before-vertical-nav-items"]?.(),
default: () => slots["vertical-nav-content"]?.(),
"after-nav-items": () => slots["after-vertical-nav-items"]?.(),
}
);
// 👉 Navbar
const navbar = h(
'header',
{ class: ['layout-navbar navbar-blur'] },
[
h(
'div',
{ class: 'navbar-content-container' },
slots.navbar?.({
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive,
}),
),
],
)
const navbar = h("header", { class: ["layout-navbar navbar-blur"] }, [
h(
"div",
{ class: "navbar-content-container" },
slots.navbar?.({
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive,
})
),
]);
const main = h(
'main',
{ class: 'layout-page-content' },
h('div', { class: 'page-content-container' }, slots.default?.()),
)
"main",
{ class: "layout-page-content" },
h("div", { class: "page-content-container" }, slots.default?.())
);
// 👉 Footer
const footer = h(
'footer',
{ class: 'layout-footer' },
[
h(
'div',
{ class: 'footer-content-container' },
slots.footer?.(),
),
],
)
const footer = h("footer", { class: "layout-footer" }, [
h("div", { class: "footer-content-container" }, slots.footer?.()),
]);
// 👉 Overlay
const layoutOverlay = h(
'div',
{
class: ['layout-overlay', { visible: isLayoutOverlayVisible.value }],
onClick: () => { isLayoutOverlayVisible.value = !isLayoutOverlayVisible.value },
const layoutOverlay = h("div", {
class: ["layout-overlay", { visible: isLayoutOverlayVisible.value }],
onClick: () => {
isLayoutOverlayVisible.value = !isLayoutOverlayVisible.value;
},
)
});
return h(
'div',
"div",
{
class: [
'layout-wrapper layout-nav-type-vertical layout-navbar-static layout-footer-static layout-content-width-fluid',
'layout-navbar-sticky',
mdAndDown.value && 'layout-overlay-nav',
"layout-wrapper layout-nav-type-vertical layout-navbar-static layout-footer-static layout-content-width-fluid",
"layout-navbar-sticky",
mdAndDown.value && "layout-overlay-nav",
route.meta.layoutWrapperClasses,
(scrollDistance.value > 20) && 'window-scrolled',
scrollDistance.value > 20 && "window-scrolled",
],
},
[
verticalNav,
h(
'div',
{ class: 'layout-content-wrapper' },
[
navbar,
main,
footer,
],
),
h("div", { class: "layout-content-wrapper" }, [navbar, main, footer]),
layoutOverlay,
],
)
}
]
);
};
},
})
});
</script>
<style lang="scss">

View File

@@ -5,11 +5,17 @@
@use "@configured-variables" as variables;
html {
min-height: calc(100% + env(safe-area-inset-top))
min-height: calc(100% + env(safe-area-inset-top));
}
body, #app {
min-block-size: 100%;
body,
#app,
.v-application {
min-height: 100%;
}
.layout-vertical-nav, .layout-navbar {
padding-top: env(safe-area-inset-top);
}
.layout-page-content {

View File

@@ -7,4 +7,5 @@
html {
box-sizing: border-box;
min-height: calc(100% + env(safe-area-inset-top))
}

View File

@@ -17,7 +17,7 @@ import UserProfile from "@/layouts/components/UserProfile.vue";
<template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center">
<!-- 👉 Vertical nav toggle in overlay mode -->
<IconBtn class="ms-n3 d-lg-none" @click="toggleVerticalOverlayNavActive(true)">
<IconBtn class="ms-n2 d-lg-none" @click="toggleVerticalOverlayNavActive(true)">
<VIcon icon="mdi-menu" />
</IconBtn>
@@ -29,7 +29,7 @@ import UserProfile from "@/layouts/components/UserProfile.vue";
</IconBtn>
<span class="d-none d-md-flex align-center text-disabled">
<span class="me-3">Search</span>
<span class="me-3">搜索</span>
<span class="meta-key">&#8984;K</span>
</span>
</div>

View File

@@ -1,119 +1,111 @@
<script setup lang="ts">
import { requiredValidator } from '@/@validators';
import api from '@/api';
import router from '@/router';
import logo from '@images/logo.svg?raw';
import type { VForm } from 'vuetify/components/VForm';
import { useStore } from 'vuex';
import { requiredValidator } from "@/@validators";
import api from "@/api";
import router from "@/router";
import logo from "@images/logo.svg?raw";
import type { VForm } from "vuetify/components/VForm";
import { useStore } from "vuex";
// Vuex Store
const store = useStore()
const store = useStore();
// 表单
const form = ref({
username: '',
password: '',
username: "",
password: "",
remember: true,
})
const refForm = ref<InstanceType<typeof VForm> | null>(null)
});
const refForm = ref<InstanceType<typeof VForm> | null>(null);
// 密码输入
const isPasswordVisible = ref(false)
const isPasswordVisible = ref(false);
// 错误信息
const errorMessage = ref('')
const errorMessage = ref("");
// 背景图片
const backgroundImageUrl = ref('')
const backgroundImageUrl = ref("");
// 获取背景图片
const fetchBackgroundImage = async () => {
api
.get('/login/wallpaper')
.get("/login/wallpaper")
.then((response: any) => {
backgroundImageUrl.value = response.message
backgroundImageUrl.value = response.message;
})
.catch((error: any) => {
console.log(error)
})
}
console.log(error);
});
};
// 登录获取token事件
const login = () => {
errorMessage.value = ''
errorMessage.value = "";
// 进行表单校验
if (!form.value.username || !form.value.password) {
return
return;
}
// 用户名密码
const formData = new FormData()
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, {
.post("/login/access-token", formData, {
headers: {
Accept: 'application/json', // 设置 Accept 类型
Accept: "application/json", // 设置 Accept 类型
},
})
.then((response: any) => {
// 获取token
const token = response.access_token
const token = response.access_token;
// 更新token和remember状态到Vuex Store
store.dispatch('auth/updateToken', token)
store.dispatch('auth/updateRemember', form.value.remember)
store.dispatch("auth/updateToken", token);
store.dispatch("auth/updateRemember", form.value.remember);
// 跳转到首页
router.push('/')
router.push("/");
})
.catch((error: any) => {
// 登录失败,显示错误提示
if (!error.response)
errorMessage.value = '登录失败,请检查网络连接'
if (!error.response) errorMessage.value = "登录失败,请检查网络连接";
else if (error.response.status === 401)
errorMessage.value = '登录失败,请检查用户名和密码是否正确'
errorMessage.value = "登录失败,请检查用户名和密码是否正确";
else if (error.response.status === 403)
errorMessage.value = '登录失败,您没有权限访问'
else if (error.response.status === 500)
errorMessage.value = '登录失败,服务器错误'
errorMessage.value = "登录失败,您没有权限访问";
else if (error.response.status === 500) errorMessage.value = "登录失败,服务器错误";
else
errorMessage.value = `登录失败 ${error.response.status},请检查用户名和密码是否正确`
})
}
errorMessage.value = `登录失败 ${error.response.status},请检查用户名和密码是否正确`;
});
};
// 自动登录
onMounted(() => {
// 从Vuex Store中获取token和remember状态
const token = store.state.auth.token
const remember = store.state.auth.remember
const token = store.state.auth.token;
const remember = store.state.auth.remember;
// 如果token存在且保持登录状态为true则跳转到首页
if (token && remember) {
router.push('/')
router.push("/");
} else {
// 获取背景图片
fetchBackgroundImage();
}
else {
// 获取背景图片
fetchBackgroundImage()
}
},
)
});
</script>
<template>
<div
class="auth-wrapper d-flex align-center justify-center pa-4 fade-in"
class="auth-wrapper d-flex align-center justify-center pa-4 fade-in w-full h-full overflow-hidden"
:style="{ backgroundImage: `url(${backgroundImageUrl})` }"
>
<VCard class="auth-card pa-7">
<VCardItem class="justify-center">
<VCard class="auth-card pa-7" width="25rem">
<VCardItem class="justify-center mb-7">
<template #prepend>
<div class="d-flex">
<div v-html="logo" />
@@ -125,17 +117,8 @@ onMounted(() => {
</VCardTitle>
</VCardItem>
<VCardText class="pt-2">
<h5 class="text-h5 font-weight-semibold mb-1">
欢迎使用 MoviePilot! 👋🏻
</h5>
<p class="mb-0">
请输入用户名密码登录
</p>
</VCardText>
<VCardText>
<VForm @submit.prevent="()=>{}" ref="refForm">
<VForm @submit.prevent="() => {}" ref="refForm">
<VRow>
<!-- username -->
<VCol cols="12">
@@ -160,30 +143,17 @@ onMounted(() => {
@click:append-inner="isPasswordVisible = !isPasswordVisible"
/>
<div
v-if="errorMessage"
class="text-error mt-1"
>
<div v-if="errorMessage" class="text-error mt-1">
{{ errorMessage }}
</div>
<!-- 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"
@click="login"
>
登录
</VBtn>
<VBtn block type="submit" @click="login"> 登录 </VBtn>
</VCol>
</VRow>
</VForm>