mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-04-22 22:00:00 +08:00
* 更新多个组件的样式以使用新的主题颜色变量,确保在不同模式下的视觉一致性。 * 优化滚动条样式,提升整体用户体验。 * 在 `SearchHeader.vue` 和 `VndbPanel.vue` 中调整背景和文本颜色,增强可读性。 * 增加对 `customApi` 的监听,确保设置变化时的实时更新。
294 lines
7.6 KiB
Vue
294 lines
7.6 KiB
Vue
<template>
|
|
<div class="floating-buttons fixed bottom-4 sm:bottom-6 right-4 sm:right-6 flex flex-col gap-2 sm:gap-3 z-40">
|
|
<!-- 回到顶部按钮 -->
|
|
<button
|
|
v-show="showScrollToTop"
|
|
aria-label="回到顶部"
|
|
class="fab-button scroll-top-btn"
|
|
@click="scrollToTop"
|
|
>
|
|
<i class="fas fa-arrow-up" />
|
|
</button>
|
|
|
|
<!-- 站点导航按钮 -->
|
|
<button
|
|
v-show="searchStore.hasResults"
|
|
:aria-label="showPlatformNav ? '关闭站点导航' : '打开站点导航'"
|
|
class="fab-button nav-btn"
|
|
:class="{ 'nav-open': showPlatformNav }"
|
|
@click="togglePlatformNav"
|
|
>
|
|
<i
|
|
:class="
|
|
showPlatformNav ? 'fas fa-times' : 'fas fa-th'
|
|
"
|
|
/>
|
|
</button>
|
|
|
|
<!-- 作品介绍按钮 -->
|
|
<button
|
|
v-show="searchStore.vndbInfo"
|
|
:aria-label="searchStore.isVndbPanelOpen ? '关闭作品介绍' : '打开作品介绍'"
|
|
class="fab-button vndb-btn"
|
|
:class="{ 'vndb-open': searchStore.isVndbPanelOpen }"
|
|
@click="toggleVndbPanel"
|
|
>
|
|
<i
|
|
:class="
|
|
searchStore.isVndbPanelOpen ? 'fas fa-times' : 'fas fa-book'
|
|
"
|
|
/>
|
|
</button>
|
|
|
|
<!-- 评论按钮 -->
|
|
<button
|
|
:aria-label="searchStore.isCommentsModalOpen ? '关闭评论' : '打开评论'"
|
|
class="fab-button comments-btn"
|
|
:class="{ 'comments-open': searchStore.isCommentsModalOpen }"
|
|
@click="toggleComments"
|
|
>
|
|
<i
|
|
:class="
|
|
searchStore.isCommentsModalOpen ? 'fas fa-times' : 'fas fa-comment'
|
|
"
|
|
/>
|
|
</button>
|
|
|
|
<!-- 站点导航面板 -->
|
|
<Transition
|
|
enter-active-class="transition-all duration-300 ease-out"
|
|
enter-from-class="opacity-0 translate-x-full"
|
|
enter-to-class="opacity-100 translate-x-0"
|
|
leave-active-class="transition-all duration-300 ease-in"
|
|
leave-from-class="opacity-100 translate-x-0"
|
|
leave-to-class="opacity-0 translate-x-full"
|
|
>
|
|
<div
|
|
v-if="showPlatformNav && searchStore.hasResults"
|
|
class="fixed bottom-4 sm:bottom-6 right-16 sm:right-20 bg-white/95 backdrop-blur-xl rounded-2xl shadow-2xl overflow-hidden border border-white/30 max-h-[70vh] flex flex-col"
|
|
style="width: 200px"
|
|
>
|
|
<div class="p-3 border-b border-gray-200 bg-gradient-to-r from-theme-primary/5 to-theme-accent/5">
|
|
<div class="flex items-center gap-2">
|
|
<i class="fas fa-th text-theme-primary text-sm" />
|
|
<span class="font-bold text-sm text-gray-800">站点导航</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overflow-y-auto flex-1 custom-scrollbar">
|
|
<button
|
|
v-for="[platformName, platformData] in searchStore.platformResults"
|
|
:key="platformName"
|
|
class="w-full px-3 py-2.5 flex items-center gap-2 hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-0 text-left"
|
|
:class="getItemClass(platformData.color)"
|
|
@click="scrollToPlatform(platformName)"
|
|
>
|
|
<i :class="getIcon(platformData.color)" class="text-base" />
|
|
<span class="platform-name flex-1 text-xs font-medium text-gray-700 truncate">{{ platformName }}</span>
|
|
<span class="count-badge px-1.5 py-0.5 rounded-full bg-gray-100 text-gray-600 text-xs font-semibold">
|
|
{{ platformData.items.length }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { useSearchStore } from '@/stores/search'
|
|
|
|
const searchStore = useSearchStore()
|
|
const showScrollToTop = ref(false)
|
|
const showPlatformNav = ref(false)
|
|
|
|
function scrollToTop() {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}
|
|
|
|
function toggleComments() {
|
|
searchStore.toggleCommentsModal()
|
|
}
|
|
|
|
function toggleVndbPanel() {
|
|
searchStore.toggleVndbPanel()
|
|
}
|
|
|
|
function togglePlatformNav() {
|
|
showPlatformNav.value = !showPlatformNav.value
|
|
}
|
|
|
|
function scrollToPlatform(platformName: string) {
|
|
const platformElements = document.querySelectorAll('[data-platform]')
|
|
const targetElement = Array.from(platformElements).find(
|
|
el => el.getAttribute('data-platform') === platformName,
|
|
) as HTMLElement
|
|
|
|
if (targetElement) {
|
|
const yOffset = -80
|
|
const y = targetElement.getBoundingClientRect().top + window.pageYOffset + yOffset
|
|
window.scrollTo({ top: y, behavior: 'smooth' })
|
|
// 滚动后关闭导航
|
|
showPlatformNav.value = false
|
|
}
|
|
}
|
|
|
|
function getItemClass(color: string) {
|
|
const classes: Record<string, string> = {
|
|
lime: 'item-lime',
|
|
white: 'item-white',
|
|
gold: 'item-gold',
|
|
red: 'item-red',
|
|
}
|
|
return classes[color] || 'item-white'
|
|
}
|
|
|
|
function getIcon(color: string) {
|
|
const icons: Record<string, string> = {
|
|
lime: 'fas fa-star',
|
|
white: 'fas fa-circle',
|
|
gold: 'fas fa-dollar-sign',
|
|
red: 'fas fa-times-circle',
|
|
}
|
|
return icons[color] || 'fas fa-circle'
|
|
}
|
|
|
|
function handleScroll() {
|
|
showScrollToTop.value = window.scrollY > 200
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('scroll', handleScroll)
|
|
handleScroll()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('scroll', handleScroll)
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fab-button {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 18px;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
box-shadow: 0 6px 20px rgba(236, 72, 153, 0.4), 0 3px 10px rgba(0, 0, 0, 0.15);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.fab-button {
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 22px;
|
|
font-size: 22px;
|
|
box-shadow: 0 8px 24px rgba(236, 72, 153, 0.4), 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.fab-button {
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: 24px;
|
|
font-size: 24px;
|
|
}
|
|
}
|
|
|
|
.fab-button:hover {
|
|
box-shadow: 0 12px 36px rgba(236, 72, 153, 0.5), 0 6px 20px rgba(0, 0, 0, 0.2);
|
|
transform: translateY(-4px) scale(1.08) rotate(5deg);
|
|
}
|
|
|
|
.fab-button:active {
|
|
transform: translateY(-2px) scale(1.02);
|
|
box-shadow: 0 6px 20px rgba(236, 72, 153, 0.4);
|
|
}
|
|
|
|
.scroll-top-btn {
|
|
background: linear-gradient(135deg, rgb(99, 102, 241), rgb(79, 70, 229));
|
|
color: white;
|
|
}
|
|
|
|
.comments-btn {
|
|
background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark));
|
|
color: white;
|
|
}
|
|
|
|
.comments-btn.comments-open {
|
|
background: linear-gradient(135deg, rgb(156, 163, 175), rgb(107, 114, 128));
|
|
color: white;
|
|
}
|
|
|
|
.vndb-btn {
|
|
background: linear-gradient(135deg, var(--theme-accent), var(--theme-accent-dark));
|
|
color: white;
|
|
}
|
|
|
|
.vndb-btn.vndb-open {
|
|
background: linear-gradient(135deg, rgb(156, 163, 175), rgb(107, 114, 128));
|
|
color: white;
|
|
}
|
|
|
|
.fab-button i {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.fab-button:hover i {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.nav-btn {
|
|
background: linear-gradient(135deg, rgb(16, 185, 129), rgb(5, 150, 105));
|
|
color: white;
|
|
}
|
|
|
|
.nav-btn.nav-open {
|
|
background: linear-gradient(135deg, rgb(156, 163, 175), rgb(107, 114, 128));
|
|
color: white;
|
|
}
|
|
|
|
/* 自定义滚动条 */
|
|
.custom-scrollbar::-webkit-scrollbar {
|
|
width: 4px;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
background-color: rgba(156, 163, 175, 0.5);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
background-color: rgba(156, 163, 175, 0.7);
|
|
}
|
|
|
|
.item-lime i {
|
|
color: rgb(132, 204, 22);
|
|
}
|
|
|
|
.item-white i {
|
|
color: rgb(156, 163, 175);
|
|
}
|
|
|
|
.item-gold i {
|
|
color: rgb(234, 179, 8);
|
|
}
|
|
|
|
.item-red i {
|
|
color: rgb(239, 68, 68);
|
|
}
|
|
</style>
|