mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-07 00:29:56 +08:00
优化探索页UI
This commit is contained in:
@@ -7,7 +7,8 @@ const display = useDisplay()
|
||||
|
||||
// 元素
|
||||
const slideview_content = ref()
|
||||
// 分页切换状态
|
||||
const sliderContainer = ref()
|
||||
// 分页切换状态: 0-左边不可用 1-两边可用 2-右边不可用 3-两边都不可用
|
||||
const disabled = ref(0)
|
||||
// 记录滚动值
|
||||
const slideview_scrollLeft = ref(0)
|
||||
@@ -21,6 +22,10 @@ let card_width: number
|
||||
let card_max: number
|
||||
// 当前定位
|
||||
let card_current: number
|
||||
// 是否鼠标悬停在容器上
|
||||
const isHovering = ref(false)
|
||||
// 获取传入的链接地址
|
||||
const props: any = inject('rankingPropsKey', { linkurl: '', title: '' })
|
||||
|
||||
// 分页切换
|
||||
function slideNext(next: boolean) {
|
||||
@@ -30,12 +35,10 @@ function slideNext(next: boolean) {
|
||||
run_to_left_px = card_index * card_width
|
||||
if (run_to_left_px >= slideview_content.value.scrollWidth - slideview_content.value.clientWidth)
|
||||
run_to_left_px = slideview_content.value.scrollWidth - slideview_content.value.clientWidth
|
||||
// console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`)
|
||||
} else {
|
||||
const card_index = card_current - card_max
|
||||
run_to_left_px = card_index * card_width
|
||||
if (run_to_left_px <= 0) run_to_left_px = 0
|
||||
// console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`)
|
||||
}
|
||||
slideview_content.value.scrollTo({
|
||||
top: 0,
|
||||
@@ -46,6 +49,7 @@ function slideNext(next: boolean) {
|
||||
|
||||
// 计算最大显示数量
|
||||
function countMaxNumber() {
|
||||
if (!slideview_content.value || !slideview_content.value.firstElementChild) return
|
||||
slide_card_length = slideview_content.value.children.length
|
||||
card_width = slideview_content.value.firstElementChild.getBoundingClientRect().width
|
||||
slide_gap_px = slideview_content.value.scrollWidth / slide_card_length - card_width
|
||||
@@ -71,6 +75,21 @@ function countDisabled() {
|
||||
else disabled.value = 1
|
||||
}
|
||||
|
||||
// 处理鼠标进入
|
||||
function handleMouseEnter() {
|
||||
isHovering.value = true
|
||||
}
|
||||
|
||||
// 处理鼠标离开
|
||||
function handleMouseLeave() {
|
||||
isHovering.value = false
|
||||
}
|
||||
|
||||
// 检测是否有足够内容可显示
|
||||
const hasEnoughContent = computed(() => {
|
||||
return slide_card_length > card_max
|
||||
})
|
||||
|
||||
// 组件加载完成
|
||||
onMounted(() => {
|
||||
// 初次获取元素参数
|
||||
@@ -78,61 +97,222 @@ onMounted(() => {
|
||||
// 窗口大小发生改变时
|
||||
window.addEventListener('resize', countMaxNumber)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 卸载事件
|
||||
window.removeEventListener('resize', countMaxNumber)
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
if (slideview_scrollLeft.value !== 0) {
|
||||
// console.log(`onActivated: to_scrollLeft, ${slideview_scrollLeft.value}`)
|
||||
slideview_content.value.scrollLeft = slideview_scrollLeft.value
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-between mt-3">
|
||||
<slot name="title">
|
||||
<SlideViewTitle />
|
||||
</slot>
|
||||
<div v-if="disabled !== 3 && display.mdAndUp.value" class="me-1 d-flex">
|
||||
<VBtn
|
||||
class="rounded-circle"
|
||||
variant="text"
|
||||
icon="mdi-chevron-left"
|
||||
color="grey"
|
||||
:disabled="disabled === 0"
|
||||
@click="slideNext(false)"
|
||||
/>
|
||||
<VBtn
|
||||
class="rounded-circle"
|
||||
variant="text"
|
||||
icon="mdi-chevron-right"
|
||||
color="grey"
|
||||
:disabled="disabled === 2"
|
||||
@click="slideNext(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="slideview_content"
|
||||
class="slideview_content grid grid-rows-1 grid-flow-col justify-start gap-4 p-3"
|
||||
tabindex="0"
|
||||
@scroll="countDisabled"
|
||||
<div
|
||||
ref="sliderContainer"
|
||||
class="slider-container"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<slot name="content" />
|
||||
<div class="slider-header">
|
||||
<slot name="title">
|
||||
<SlideViewTitle />
|
||||
</slot>
|
||||
|
||||
<!-- 查看全部按钮 -->
|
||||
<RouterLink
|
||||
v-if="props.linkurl"
|
||||
:to="props.linkurl"
|
||||
class="view-all-button"
|
||||
>
|
||||
<span>全部</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" class="arrow-svg">
|
||||
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
|
||||
</svg>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div class="slider-content-wrapper">
|
||||
<div class="slider-content-container">
|
||||
<div
|
||||
ref="slideview_content"
|
||||
class="slider-content"
|
||||
tabindex="0"
|
||||
@scroll="countDisabled"
|
||||
>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 左侧导航按钮 -->
|
||||
<button
|
||||
class="nav-button nav-button-left"
|
||||
@click.stop="slideNext(false)"
|
||||
v-show="isHovering && disabled !== 0 && disabled !== 3"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 右侧导航按钮 -->
|
||||
<button
|
||||
class="nav-button nav-button-right"
|
||||
@click.stop="slideNext(true)"
|
||||
v-show="isHovering && disabled !== 2 && disabled !== 3"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.slideview_content {
|
||||
.slider-container {
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
// 移除padding,按钮放置在外部
|
||||
}
|
||||
|
||||
.slider-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 0 8px;
|
||||
gap: 16px;
|
||||
|
||||
& > :first-child {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.view-all-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
padding: 4px 10px;
|
||||
border-radius: 16px;
|
||||
transition: all 0.25s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.15);
|
||||
box-shadow: 0 2px 8px rgba(var(--v-theme-primary), 0.1);
|
||||
transform: translateY(-1px);
|
||||
|
||||
.arrow-svg {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.arrow-svg {
|
||||
transition: transform 0.3s ease;
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-content-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slider-content-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--v-theme-surface), 0.9);
|
||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
svg {
|
||||
fill: rgb(var(--v-theme-on-surface));
|
||||
opacity: 0.8;
|
||||
transition: all 0.3s ease;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
background-color: rgba(var(--v-theme-surface), 1);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22);
|
||||
border-color: rgba(var(--v-theme-on-surface), 0.15);
|
||||
|
||||
svg {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-button-left {
|
||||
left: -19px; // 半径
|
||||
}
|
||||
|
||||
.nav-button-right {
|
||||
right: -19px; // 半径
|
||||
}
|
||||
|
||||
.slider-content {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-auto-flow: column;
|
||||
justify-content: start;
|
||||
gap: 16px;
|
||||
padding: 8px 12px;
|
||||
overflow: scroll hidden !important;
|
||||
-ms-overflow-style: none !important;
|
||||
overscroll-behavior-x: contain !important;
|
||||
scrollbar-width: none !important;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.slideview_content::-webkit-scrollbar {
|
||||
display: none;
|
||||
.slider-container:hover .nav-button[style*="display: none;"] ~ .nav-button,
|
||||
.slider-container:hover .nav-button {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.nav-button[style*="display: none;"] {
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,10 +4,41 @@ const props: any = inject('rankingPropsKey')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ms-1">
|
||||
<RouterLink :to="props?.linkurl ? props?.linkurl : ''" class="slider-title">
|
||||
<span>{{ props?.title }}</span>
|
||||
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
||||
</RouterLink>
|
||||
<div class="title-wrapper">
|
||||
<div class="title-section">
|
||||
<div class="title-badge"></div>
|
||||
<h3 class="title-text">{{ props?.title }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title-badge {
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--v-theme-on-background), 0.95);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user