refactor: migrate page-specific action buttons to dynamic FABs for PWA mode compatibility

This commit is contained in:
jxxghp
2026-05-17 11:01:47 +08:00
parent 5e8489c620
commit f01971ee3a
5 changed files with 63 additions and 107 deletions

View File

@@ -1031,7 +1031,7 @@ export default {
doubanGlobalTVRankings: 'Douban Global TV Rankings',
noCategoryContent: 'No content to display in current category',
configureContent: 'Configure Display Content',
customizeContent: 'Customize Content',
customizeContent: 'Customize Recommendations',
selectContentToDisplay: 'Select content you want to display on the page',
selectAll: 'Select All',
selectNone: 'Select None',
@@ -2959,7 +2959,7 @@ export default {
},
transferHistory: {
title: 'Transfer History',
searchPlaceholder: 'Search transfer records',
searchPlaceholder: 'Search (supports * ? wildcards)',
titleColumn: 'Title',
pathColumn: 'Path',
modeColumn: 'Mode',

View File

@@ -1025,7 +1025,7 @@ export default {
doubanGlobalTVRankings: '豆瓣全球剧集榜',
noCategoryContent: '当前分类下没有可显示的内容',
configureContent: '设置显示内容',
customizeContent: '自定义内容',
customizeContent: '自定义推荐',
selectContentToDisplay: '选择您想在页面显示的内容',
selectAll: '全选',
selectNone: '全不选',
@@ -2904,7 +2904,7 @@ export default {
},
transferHistory: {
title: '转移历史',
searchPlaceholder: '搜索转移记录',
searchPlaceholder: '搜索(支持 * ? 通配符)',
titleColumn: '标题',
pathColumn: '路径',
modeColumn: '转移方式',

View File

@@ -1026,7 +1026,7 @@ export default {
doubanGlobalTVRankings: '豆瓣全球劇集榜',
noCategoryContent: '當前分類下沒有可顯示的內容',
configureContent: '設置顯示內容',
customizeContent: '自定義內容',
customizeContent: '自定義推薦',
selectContentToDisplay: '選擇您想在頁面顯示的內容',
selectAll: '全選',
selectNone: '全不選',
@@ -2906,7 +2906,7 @@ export default {
},
transferHistory: {
title: '轉移歷史',
searchPlaceholder: '搜索轉移記錄',
searchPlaceholder: '搜索(支援 * ? 萬用字元)',
titleColumn: '標題',
pathColumn: '路徑',
modeColumn: '轉移方式',

View File

@@ -5,9 +5,12 @@ import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
import { useDynamicButton } from '@/composables/useDynamicButton'
import { usePWA } from '@/composables/usePWA'
import { getItemColor, initializeItemColors } from '@/utils/colorUtils'
const display = useDisplay()
const { appMode } = usePWA()
// 国际化
const { t } = useI18n()
@@ -21,6 +24,10 @@ const currentCategory = ref(t('recommend.all'))
// 使用动态标签页
const { registerHeaderTab } = useDynamicHeaderTab()
function openRecommendSettings() {
dialog.value = true
}
const viewList = reactive<{ apipath: string; linkurl: string; title: string; type: string }[]>([
{
apipath: 'recommend/tmdb_trending',
@@ -218,17 +225,12 @@ const categoryItems = computed(() => [
registerHeaderTab({
items: categoryItems,
modelValue: currentCategory,
appendButtons: [
{
icon: 'mdi-tune',
variant: 'text',
color: 'grey',
class: 'settings-icon-button',
action: () => {
dialog.value = true
},
},
],
})
useDynamicButton({
icon: 'mdi-tune',
onClick: openRecommendSettings,
show: computed(() => appMode.value),
})
// 页面是否准备就绪
@@ -346,7 +348,19 @@ onActivated(async () => {
<!-- 快速滚动到顶部按钮 -->
<Teleport to="body" v-if="route.path === '/recommend'">
<VScrollToTopBtn />
<div v-if="!appMode" class="compact-fab-stack">
<VFab
icon="mdi-tune"
color="primary"
appear
class="compact-fab compact-fab--primary"
@click="openRecommendSettings"
/>
</div>
</Teleport>
<Teleport to="body" v-if="route.path === '/recommend'">
<VScrollToTopBtn :offset-fab="!appMode" />
</Teleport>
</div>
</template>

View File

@@ -11,11 +11,15 @@ import TorrentFilterBar from '@/components/filter/TorrentFilterBar.vue'
import { useI18n } from 'vue-i18n'
import { useGlobalSettingsStore } from '@/stores/global'
import { useTorrentFilter, type FilterState } from '@/composables/useTorrentFilter'
import { useDynamicButton } from '@/composables/useDynamicButton'
import { usePWA } from '@/composables/usePWA'
import { useToast } from 'vue-toastification'
// 国际化
const { t } = useI18n()
const { appMode } = usePWA()
// 提示框
const toast = useToast()
@@ -225,6 +229,19 @@ const filteredCardDataList = ref<Array<SearchTorrent>>([])
// 是否刷新过
const isRefreshed = ref(false)
const viewToggleIcon = computed(() => (viewType.value === 'card' ? 'mdi-view-list-outline' : 'mdi-view-grid-outline'))
// 搜索结果视图切换收纳到页面动态按钮中,和仪表盘的设置按钮保持一致。
function toggleViewType() {
changeViewType(viewType.value === 'card' ? 'row' : 'card')
}
useDynamicButton({
icon: viewToggleIcon,
onClick: toggleViewType,
show: computed(() => appMode.value && isRefreshed.value),
})
// 是否正在重新搜索
const isRefreshing = ref(false)
@@ -1211,18 +1228,6 @@ onUnmounted(() => {
</div>
</div>
<!-- 重新设计的视图切换按钮 -->
<div class="view-toggle-container">
<div class="view-toggle-buttons">
<div class="active-indicator" :class="viewType"></div>
<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>
</VCard>
<!-- 搜索结果 -->
@@ -1328,9 +1333,22 @@ onUnmounted(() => {
<!-- 初始加载状态 -->
<LoadingBanner v-else-if="!isRefreshed && !isSearchLoading" />
<Teleport to="body" v-if="route.path === '/resource'">
<div v-if="isRefreshed && !appMode" class="compact-fab-stack">
<VFab
:icon="viewToggleIcon"
color="primary"
appear
class="compact-fab compact-fab--primary"
@click="toggleViewType"
/>
</div>
</Teleport>
<!-- 滚动到顶部按钮 -->
<Teleport to="body" v-if="route.path === '/resource'">
<VScrollToTopBtn />
<VScrollToTopBtn :offset-fab="isRefreshed && !appMode" />
</Teleport>
</div>
</template>
@@ -1465,58 +1483,6 @@ onUnmounted(() => {
font-size: 0.75rem;
}
/* 重新设计的视图切换按钮 */
.view-toggle-container {
position: relative;
}
.view-toggle-buttons {
position: relative;
display: flex;
padding: 4px;
border-radius: 8px;
background-color: rgba(var(--v-theme-surface-variant), 0.1);
isolation: isolate; /* Create new stacking context */
}
.active-indicator {
position: absolute;
z-index: 1;
border-radius: 6px;
background-color: rgb(var(--v-theme-surface));
block-size: 36px;
box-shadow:
0 1px 3px rgba(0, 0, 0, 12%),
0 1px 2px rgba(0, 0, 0, 24%);
inline-size: 40px;
inset-block-start: 4px;
inset-inline-start: 4px;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.active-indicator.row {
transform: translateX(40px);
}
.view-toggle-btn {
position: relative;
z-index: 2; /* Sit on top of indicator */
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
block-size: 36px;
cursor: pointer;
inline-size: 40px;
transition: all 0.2s ease;
}
.view-toggle-btn:hover:not(.active) {
border-radius: 6px;
background-color: rgba(var(--v-theme-primary), 0.05);
}
/* 重新搜索按钮 */
.refresh-search-btn {
border-radius: 8px !important;
@@ -1690,30 +1656,6 @@ onUnmounted(() => {
grid-template-columns: 1fr;
}
.view-toggle-container {
flex-shrink: 0;
}
.view-toggle-buttons {
padding: 2px;
}
.active-indicator {
block-size: 32px;
inline-size: 36px;
inset-block-start: 2px;
inset-inline-start: 2px;
}
.active-indicator.row {
transform: translateX(36px);
}
.view-toggle-btn {
block-size: 32px;
inline-size: 36px;
}
.refresh-search-btn {
block-size: 36px !important;
inline-size: 36px !important;