mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-08 17:26:41 +08:00
rollback menu layout
This commit is contained in:
@@ -1,92 +1,209 @@
|
|||||||
<!-- Thanks: https://markus.oberlehner.net/blog/transition-to-height-auto-with-vue/ -->
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Transition } from 'vue'
|
import { Transition } from 'vue'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
import VerticalNav from '@layouts/components/VerticalNav.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TransitionExpand',
|
setup(props, { slots }) {
|
||||||
setup(_, { slots }) {
|
const isOverlayNavActive = ref(false)
|
||||||
const onEnter = (element: HTMLElement) => {
|
const isLayoutOverlayVisible = ref(false)
|
||||||
const width = getComputedStyle(element).width
|
const toggleIsOverlayNavActive = useToggle(isOverlayNavActive)
|
||||||
|
|
||||||
element.style.width = width
|
const route = useRoute()
|
||||||
element.style.position = 'absolute'
|
const { mdAndDown } = useDisplay()
|
||||||
element.style.visibility = 'hidden'
|
|
||||||
element.style.height = 'auto'
|
|
||||||
|
|
||||||
const height = getComputedStyle(element).height
|
// ℹ️ 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)
|
||||||
|
|
||||||
element.style.width = ''
|
const scrollDistance = ref(window.scrollY)
|
||||||
element.style.position = ''
|
|
||||||
element.style.visibility = ''
|
|
||||||
element.style.height = '0px'
|
|
||||||
|
|
||||||
// Force repaint to make sure the
|
onMounted(() => {
|
||||||
// animation is triggered correctly.
|
window.addEventListener('scroll', () => {
|
||||||
// eslint-disable-next-line no-unused-expressions
|
scrollDistance.value = window.scrollY
|
||||||
getComputedStyle(element).height
|
|
||||||
|
|
||||||
// Trigger the animation.
|
|
||||||
// We use `requestAnimationFrame` because we need
|
|
||||||
// to make sure the browser has finished
|
|
||||||
// painting after setting the `height`
|
|
||||||
// to `0` in the line above.
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
element.style.height = height
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
const onAfterEnter = (element: HTMLElement) => {
|
return () => {
|
||||||
element.style.height = 'auto'
|
// 👉 Vertical nav
|
||||||
}
|
const verticalNav = h(
|
||||||
|
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']?.(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const onLeave = (element: HTMLElement) => {
|
// 👉 Navbar
|
||||||
const height = getComputedStyle(element).height
|
const navbar = h('header', { class: ['layout-navbar navbar-blur'] }, [
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'navbar-content-container' },
|
||||||
|
slots.navbar?.({
|
||||||
|
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
element.style.height = height
|
const main = h(
|
||||||
|
'main',
|
||||||
|
{ class: 'layout-page-content' },
|
||||||
|
h(Transition, { name: 'fade-slide', mode: 'out-in', appear: true }, () =>
|
||||||
|
h('section', { class: 'page-content-container' }, slots.default?.()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
// Force repaint to make sure the
|
// 👉 根据路由 meta 决定 footer 高度
|
||||||
// animation is triggered correctly.
|
const shouldShowFooter = !route.meta.hideFooter
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
getComputedStyle(element).height
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
// 👉 Footer
|
||||||
element.style.height = '0px'
|
const footer = h('footer', { class: 'layout-footer' }, [
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: ['footer-content-container', !shouldShowFooter && 'footer-content-container-noheight'],
|
||||||
|
},
|
||||||
|
slots.footer?.(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
// 👉 Overlay
|
||||||
|
const layoutOverlay = h('div', {
|
||||||
|
class: ['layout-overlay', 'touch-none', { visible: isLayoutOverlayVisible.value }],
|
||||||
|
onClick: () => {
|
||||||
|
isLayoutOverlayVisible.value = !isLayoutOverlayVisible.value
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return () => h(
|
return h(
|
||||||
h(Transition),
|
'div',
|
||||||
{
|
{
|
||||||
name: 'expand',
|
class: [
|
||||||
onEnter,
|
'layout-wrapper layout-nav-type-vertical layout-navbar-static layout-footer-static layout-content-width-fluid',
|
||||||
onAfterEnter,
|
'layout-navbar-fixed',
|
||||||
onLeave,
|
mdAndDown.value && 'layout-overlay-nav',
|
||||||
},
|
route.meta.layoutWrapperClasses,
|
||||||
() => slots.default?.(),
|
scrollDistance.value && 'window-scrolled',
|
||||||
)
|
],
|
||||||
|
},
|
||||||
|
[verticalNav, h('div', { class: 'layout-content-wrapper' }, [navbar, main, footer]), layoutOverlay],
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.expand-enter-active,
|
@use '@configured-variables' as variables;
|
||||||
.expand-leave-active {
|
@use '@layouts/styles/placeholders';
|
||||||
overflow: hidden;
|
@use '@layouts/styles/mixins';
|
||||||
transition: block-size var(--expand-transition-duration, 0.25s) ease;
|
|
||||||
|
.layout-wrapper.layout-nav-type-vertical {
|
||||||
|
// TODO(v2): Check why we need height in vertical nav & min-height in horizontal nav
|
||||||
|
block-size: 100%;
|
||||||
|
|
||||||
|
.layout-content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-block-size: calc(var(--vh, 1vh) * 100);
|
||||||
|
transition: padding-inline-start 0.2s ease-in-out;
|
||||||
|
will-change: padding-inline-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-navbar {
|
||||||
|
position: fixed;
|
||||||
|
width: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
|
||||||
|
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
|
||||||
|
inset-block-start: 0;
|
||||||
|
|
||||||
|
.navbar-content-container {
|
||||||
|
block-size: calc(env(safe-area-inset-top) + variables.$layout-vertical-nav-navbar-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
.layout-wrapper.layout-nav-type-vertical {
|
||||||
|
.layout-navbar {
|
||||||
|
@if variables.$layout-vertical-nav-navbar-is-contained {
|
||||||
|
@include mixins.boxed-content;
|
||||||
|
} @else {
|
||||||
|
.navbar-content-container {
|
||||||
|
@include mixins.boxed-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-navbar-fixed .layout-navbar {
|
||||||
|
@extend %layout-navbar-fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-navbar-hidden .layout-navbar {
|
||||||
|
@extend %layout-navbar-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Footer
|
||||||
|
.layout-footer {
|
||||||
|
@include mixins.boxed-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Layout overlay
|
||||||
|
.layout-overlay {
|
||||||
|
position: fixed;
|
||||||
|
z-index: variables.$layout-overlay-z-index;
|
||||||
|
background-color: rgb(0 0 0 / 60%);
|
||||||
|
cursor: pointer;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.25s ease-in-out;
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.layout-overlay-nav) .layout-content-wrapper {
|
||||||
|
padding-inline-start: variables.$layout-vertical-nav-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust right column pl when vertical nav is collapsed
|
||||||
|
&.layout-vertical-nav-collapsed .layout-content-wrapper {
|
||||||
|
padding-inline-start: variables.$layout-vertical-nav-collapsed-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Content height fixed
|
||||||
|
&.layout-content-height-fixed {
|
||||||
|
.layout-content-wrapper {
|
||||||
|
max-block-size: calc(var(--vh) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-page-content {
|
||||||
|
// display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.page-content-container {
|
||||||
|
inline-size: 100%;
|
||||||
|
|
||||||
|
> :first-child {
|
||||||
|
max-block-size: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-enter-from,
|
.layout-wrapper.layout-nav-type-vertical.layout-overlay-nav {
|
||||||
.expand-leave-to {
|
.layout-navbar {
|
||||||
block-size: 0;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
* {
|
|
||||||
backface-visibility: hidden;
|
|
||||||
perspective: 62.5rem;
|
|
||||||
transform: translateZ(0);
|
|
||||||
will-change: block-size;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -117,23 +117,13 @@ function handleNavScroll(evt: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-items {
|
.nav-items {
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
block-size: 100%;
|
block-size: 100%;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0 0 16px 0;
|
|
||||||
|
|
||||||
/* 完全隐藏滚动条 */
|
// ℹ️ We no loner needs this overflow styles as perfect scrollbar applies it
|
||||||
&::-webkit-scrollbar {
|
// overflow-x: hidden;
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
scrollbar-width: none; /* Firefox */
|
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
|
||||||
|
|
||||||
> li {
|
// // ℹ️ We used `overflow-y` instead of `overflow` to mitigate overflow x. Revert back if any issue found.
|
||||||
margin-block-end: 2px;
|
// overflow-y: auto;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item-title {
|
.nav-item-title {
|
||||||
|
|||||||
@@ -1,29 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { NavLink } from '@layouts/types'
|
import type { NavLink } from '@layouts/types'
|
||||||
|
|
||||||
// 定义类型必须使用vuetify中正确导出的类型
|
defineProps<{
|
||||||
const props = defineProps<{
|
|
||||||
item: NavLink
|
item: NavLink
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li class="nav-link" :class="{ disabled: item.disable }">
|
||||||
class="nav-link"
|
<Component :is="item.to ? 'RouterLink' : 'a'" :to="item.to" :href="item.href">
|
||||||
:class="{ disabled: item.disable }"
|
<VIcon :icon="item.icon" class="nav-item-icon" />
|
||||||
>
|
<!-- 👉 Title -->
|
||||||
<Component
|
|
||||||
:is="item.to ? 'RouterLink' : 'a'"
|
|
||||||
:to="item.to"
|
|
||||||
:href="item.href"
|
|
||||||
class="link-wrapper"
|
|
||||||
>
|
|
||||||
<VIcon
|
|
||||||
v-if="item.icon != null"
|
|
||||||
:icon="item.icon?.toString()"
|
|
||||||
size="20"
|
|
||||||
class="nav-item-icon"
|
|
||||||
/>
|
|
||||||
<span class="nav-item-title">
|
<span class="nav-item-title">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</span>
|
</span>
|
||||||
@@ -33,54 +20,13 @@ const props = defineProps<{
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.layout-vertical-nav {
|
.layout-vertical-nav {
|
||||||
.nav-link {
|
.nav-link a {
|
||||||
margin: 1px 16px;
|
display: flex;
|
||||||
position: relative;
|
align-items: center;
|
||||||
|
border-radius: 0 3.125rem 3.125rem 0 !important;
|
||||||
&.disabled {
|
cursor: pointer;
|
||||||
opacity: 0.6;
|
margin-inline-end: 1.125em;
|
||||||
pointer-events: none;
|
padding-inline: 1.375rem 1rem;
|
||||||
}
|
|
||||||
|
|
||||||
.router-link-active {
|
|
||||||
background-color: rgb(var(--v-theme-primary));
|
|
||||||
position: relative;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
.nav-item-icon,
|
|
||||||
.nav-item-title {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a, .link-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 8px 10px;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover:not(.router-link-active) {
|
|
||||||
background-color: rgba(var(--v-theme-on-surface), 0.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-icon {
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.75);
|
|
||||||
margin-right: 8px;
|
|
||||||
min-width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-title {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.85);
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -8,22 +8,19 @@ defineProps<{
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li class="nav-section-title">
|
<li class="nav-section-title">
|
||||||
<div class="title-text">{{ item.heading }}</div>
|
<div class="title-wrapper">
|
||||||
|
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||||
|
<span class="title-text" v-text="item.heading" />
|
||||||
|
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.layout-vertical-nav {
|
.layout-vertical-nav {
|
||||||
.nav-section-title {
|
.nav-section-title {
|
||||||
margin: 16px 16px 6px 16px;
|
padding-left: 1.375rem;
|
||||||
position: relative;
|
padding-right: 1rem;
|
||||||
|
|
||||||
.title-text {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: rgb(var(--v-theme-primary));
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user