mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-06 00:01:33 +08:00
feat: 优化搜索结果界面UI,感谢 @madrays
This commit is contained in:
@@ -37,6 +37,9 @@ const sites = route.query?.sites?.toString() ?? ''
|
||||
// 视图类型,从localStorage中读取
|
||||
const viewType = ref<string>(localStorage.getItem('MPTorrentsViewType') ?? 'card')
|
||||
|
||||
// 视图切换中
|
||||
const isViewChanging = ref(false)
|
||||
|
||||
// 数据列表
|
||||
const dataList = ref<Array<Context>>([])
|
||||
|
||||
@@ -77,9 +80,17 @@ function stopLoadingProgress() {
|
||||
}
|
||||
|
||||
// 设置视图类型
|
||||
function setViewType(type: string) {
|
||||
localStorage.setItem('MPTorrentsViewType', type)
|
||||
viewType.value = type
|
||||
function changeViewType(newType: string) {
|
||||
if (viewType.value !== newType) {
|
||||
isViewChanging.value = true
|
||||
viewType.value = newType
|
||||
localStorage.setItem('MPTorrentsViewType', newType)
|
||||
|
||||
// 模拟视图切换的加载过程
|
||||
setTimeout(() => {
|
||||
isViewChanging.value = false
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取搜索列表数据
|
||||
@@ -141,39 +152,378 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" :text="progressText" :progress="progressValue" />
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
:error-title="errorTitle"
|
||||
:error-description="errorDescription"
|
||||
/>
|
||||
<div v-if="dataList.length > 0">
|
||||
<TorrentRowListView v-if="viewType === 'list'" :items="dataList" />
|
||||
<TorrentCardListView v-else :items="dataList" />
|
||||
</div>
|
||||
<!-- 视图切换 -->
|
||||
<div v-if="isRefreshed">
|
||||
<VFab
|
||||
v-if="viewType === 'list'"
|
||||
icon="mdi-view-grid"
|
||||
location="bottom"
|
||||
size="x-large"
|
||||
absolute
|
||||
app
|
||||
appear
|
||||
@click="setViewType('card')"
|
||||
:class="{ 'mb-12': appMode }"
|
||||
/>
|
||||
<VFab
|
||||
v-else
|
||||
icon="mdi-view-list"
|
||||
location="bottom"
|
||||
size="x-large"
|
||||
fixed
|
||||
app
|
||||
appear
|
||||
@click="setViewType('list')"
|
||||
:class="{ 'mb-12': appMode }"
|
||||
/>
|
||||
<div>
|
||||
<!-- 加载进度条 -->
|
||||
<VFadeTransition>
|
||||
<div v-if="progressValue > 0 && progressValue < 100" class="search-progress-container">
|
||||
<div class="search-progress-card">
|
||||
<div class="progress-header">
|
||||
<VIcon icon="mdi-movie-search" color="primary" size="small" class="me-2" />
|
||||
<span class="progress-title">{{ progressText }}</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-wrapper">
|
||||
<div class="progress-bar" :style="{ width: `${progressValue}%` }"></div>
|
||||
</div>
|
||||
<div class="progress-percentage">{{ Math.ceil(progressValue) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VFadeTransition>
|
||||
|
||||
<!-- 精简标题栏 -->
|
||||
<div v-if="isRefreshed" class="search-header d-flex align-center mb-4">
|
||||
<div class="search-info-container d-flex align-center flex-wrap">
|
||||
<div class="search-title text-primary">资源搜索结果</div>
|
||||
<div class="search-tags d-flex flex-wrap">
|
||||
<VChip v-if="keyword" class="search-tag" color="primary" size="small" variant="flat">
|
||||
关键词: {{ keyword }}
|
||||
</VChip>
|
||||
<VChip v-if="title" class="search-tag" color="primary" size="small" variant="flat"> 标题: {{ title }} </VChip>
|
||||
<VChip v-if="year" class="search-tag" color="primary" size="small" variant="flat"> 年份: {{ year }} </VChip>
|
||||
<VChip v-if="season" class="search-tag" color="primary" size="small" variant="flat"> 季: {{ season }} </VChip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<!-- 重新设计的视图切换按钮 -->
|
||||
<div class="view-toggle-container">
|
||||
<div class="view-toggle-buttons">
|
||||
<button class="view-toggle-btn" :class="{ active: viewType === 'card' }" @click="changeViewType('card')">
|
||||
<VIcon icon="mdi-view-grid-outline" :color="viewType === 'card' ? 'primary' : undefined" />
|
||||
</button>
|
||||
<button class="view-toggle-btn" :class="{ active: viewType === 'row' }" @click="changeViewType('row')">
|
||||
<VIcon icon="mdi-view-list-outline" :color="viewType === 'row' ? 'primary' : undefined" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视图切换加载状态 -->
|
||||
<VFadeTransition>
|
||||
<div v-if="isRefreshed && isViewChanging" class="view-changing-container">
|
||||
<div class="view-changing-content">
|
||||
<div class="pulse-loader">
|
||||
<div class="pulse-circle"></div>
|
||||
<div class="pulse-circle"></div>
|
||||
<div class="pulse-circle"></div>
|
||||
</div>
|
||||
<div class="view-changing-text">切换视图</div>
|
||||
</div>
|
||||
</div>
|
||||
</VFadeTransition>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<div v-if="isRefreshed && dataList.length > 0 && !isViewChanging" class="search-results-container">
|
||||
<!-- 卡片视图模式 -->
|
||||
<VFadeTransition>
|
||||
<TorrentCardListView v-if="viewType === 'card'" :items="dataList" />
|
||||
</VFadeTransition>
|
||||
|
||||
<!-- 列表视图模式 -->
|
||||
<VFadeTransition>
|
||||
<TorrentRowListView v-if="viewType === 'row'" :items="dataList" />
|
||||
</VFadeTransition>
|
||||
</div>
|
||||
|
||||
<!-- 无数据显示 -->
|
||||
<div v-else-if="isRefreshed && !isViewChanging" class="d-flex flex-column align-center justify-center py-8">
|
||||
<NoDataFound
|
||||
:title="errorTitle"
|
||||
:description="errorDescription"
|
||||
:image="require('@images/illustrations/no-results.png')"
|
||||
/>
|
||||
<VBtn class="mt-4" color="primary" prepend-icon="mdi-magnify" to="/"> 返回首页 </VBtn>
|
||||
</div>
|
||||
|
||||
<!-- 初始加载状态 -->
|
||||
<div v-else-if="!isRefreshed" class="initial-loading-container">
|
||||
<div class="initial-loading-content">
|
||||
<div class="wave-loader">
|
||||
<div class="wave-dot"></div>
|
||||
<div class="wave-dot"></div>
|
||||
<div class="wave-dot"></div>
|
||||
<div class="wave-dot"></div>
|
||||
</div>
|
||||
<div class="initial-loading-text">搜索中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-progress-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.search-progress-card {
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid rgba(var(--v-theme-primary), 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.progress-bar-wrapper {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background-color: rgba(var(--v-theme-on-surface), 0.08);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgb(var(--v-theme-primary)) 0%,
|
||||
rgb(var(--v-theme-primary)) 70%,
|
||||
rgba(var(--v-theme-primary), 0.8) 100%
|
||||
);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 精简标题栏样式 */
|
||||
.search-header {
|
||||
padding: 12px 16px;
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.search-info-container {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.search-tags {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-tag {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* 重新设计的视图切换按钮 */
|
||||
.view-toggle-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.view-toggle-buttons {
|
||||
display: flex;
|
||||
background-color: rgba(var(--v-theme-surface-variant), 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.view-toggle-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.view-toggle-btn.active {
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.view-toggle-btn:hover:not(.active) {
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
}
|
||||
|
||||
/* 视图切换加载状态 */
|
||||
.view-changing-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(var(--v-theme-background), 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.view-changing-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.pulse-loader {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pulse-circle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
animation: pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.pulse-circle:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.pulse-circle:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.view-changing-text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* 初始加载状态 */
|
||||
.initial-loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.initial-loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.wave-loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.wave-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
animation: wave 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.wave-dot:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.wave-dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.wave-dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.wave-dot:nth-child(4) {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
}
|
||||
|
||||
.initial-loading-text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.search-results-container {
|
||||
min-height: 50vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.search-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-info-container {
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.view-toggle-container {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user