diff --git a/index.html b/index.html index 14a43e7..b1a9ea5 100644 --- a/index.html +++ b/index.html @@ -9,17 +9,27 @@ // 立即检测主题,在任何内容渲染前执行 (function() { var d = document.documentElement; + var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + var isDark = prefersDark; // 默认跟随系统 + try { var saved = localStorage.getItem('ui-state'); - var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - var isDark = saved ? JSON.parse(saved).isDarkMode : prefersDark; - if (isDark) d.classList.add('dark'); - } catch (e) { - // 默认跟随系统 - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - d.classList.add('dark'); + if (saved) { + var state = JSON.parse(saved); + var themeMode = state.themeMode || 'system'; + + if (themeMode === 'dark') { + isDark = true; + } else if (themeMode === 'light') { + isDark = false; + } + // themeMode === 'system' 时使用 prefersDark } + } catch (e) { + // 解析失败,使用系统偏好 } + + if (isDark) d.classList.add('dark'); })(); diff --git a/src/components/ResultItem.vue b/src/components/ResultItem.vue new file mode 100644 index 0000000..284bc6b --- /dev/null +++ b/src/components/ResultItem.vue @@ -0,0 +1,66 @@ + + + + + + diff --git a/src/components/SearchHeader.vue b/src/components/SearchHeader.vue index c0ecd0f..a877168 100644 --- a/src/components/SearchHeader.vue +++ b/src/components/SearchHeader.vue @@ -122,12 +122,12 @@ - +
- +
-
+
@@ -590,6 +590,8 @@ watch(() => searchStore.customApi, (newApi) => { } }) +let hasScrolledToResults = false + async function handleSearch() { if (!searchQuery.value.trim()) {return} @@ -597,6 +599,7 @@ async function handleSearch() { searchStore.clearResults() searchStore.isSearching = true searchStore.errorMessage = '' + hasScrolledToResults = false // 重置滚动标志 const searchParams = new URLSearchParams() searchParams.set('game', searchQuery.value.trim()) @@ -605,6 +608,19 @@ async function handleSearch() { searchParams.set('api', customApi.value.trim()) } + // 在 game 模式下,搜索开始时就并行发起 VNDB 请求 + const queryForVndb = searchQuery.value.trim() + if (searchMode.value === 'game') { + fetchVndbData(queryForVndb).then((vndbData) => { + // 检查搜索词是否仍匹配(防止快速切换搜索时数据错乱) + if (vndbData && searchStore.searchQuery === queryForVndb) { + searchStore.vndbInfo = vndbData + } + }).catch(() => { + // VNDB 请求失败不影响主搜索 + }) + } + try { await searchGameStream(searchParams, { onTotal: (total) => { @@ -614,11 +630,11 @@ async function handleSearch() { searchStore.searchProgress = { current, total } }, onPlatformResult: (data) => { - const isFirstResult = searchStore.platformResults.size === 0 searchStore.setPlatformResult(data.name, data) - // 第一个结果出现时滚动到结果区域 - if (isFirstResult) { + // 等待至少 3 个平台结果后滚动到结果区域(只滚动一次) + if (!hasScrolledToResults && searchStore.platformResults.size >= 3) { + hasScrolledToResults = true // 使用 requestAnimationFrame + setTimeout 确保 DOM 已更新 window.requestAnimationFrame(() => { setTimeout(() => { @@ -642,6 +658,22 @@ async function handleSearch() { searchStore.isSearching = false playCelebration() // 搜索完成音效 + // 如果结果不足 3 个但有结果,且还没滚动过,则现在滚动 + if (!hasScrolledToResults && searchStore.platformResults.size > 0) { + hasScrolledToResults = true + window.requestAnimationFrame(() => { + setTimeout(() => { + const resultsEl = document.getElementById('results') + if (resultsEl) { + const headerOffset = 80 + const elementPosition = resultsEl.getBoundingClientRect().top + const offsetPosition = elementPosition + window.pageYOffset - headerOffset + window.scrollTo({ top: offsetPosition, behavior: 'smooth' }) + } + }, 50) + }) + } + // 保存搜索历史 const resultCount = searchStore.totalResults saveSearchHistory({ @@ -657,14 +689,6 @@ async function handleSearch() { playCaution() // 错误音效 }, }) - - // 获取 VNDB 数据 - if (searchMode.value === 'game') { - const vndbData = await fetchVndbData(searchQuery.value.trim()) - if (vndbData) { - searchStore.vndbInfo = vndbData - } - } } catch (error) { searchStore.errorMessage = error instanceof Error ? error.message : '搜索失败' @@ -1012,9 +1036,8 @@ defineExpose({ /* 错误卡片样式 */ .error-card { - background: linear-gradient(135deg, rgba(254, 242, 242, 0.95), rgba(254, 226, 226, 0.95)); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); + background: linear-gradient(135deg, rgba(var(--color-error, 254, 242, 242), var(--opacity-panel, 0.85)), rgba(254, 226, 226, var(--opacity-panel, 0.85))); + border-radius: var(--radius-lg, 1rem); border-radius: 1rem; padding: 1rem; border: 1px solid rgba(239, 68, 68, 0.2); @@ -1138,11 +1161,9 @@ defineExpose({ border-radius: 1rem; background: linear-gradient( 135deg, - rgba(255, 255, 255, 0.85) 0%, - rgba(255, 228, 242, 0.75) 100% + rgba(255, 255, 255, 0.95) 0%, + rgba(255, 245, 250, 0.92) 100% ); - backdrop-filter: blur(12px) saturate(180%); - -webkit-backdrop-filter: blur(12px) saturate(180%); z-index: -1; } @@ -1209,25 +1230,17 @@ defineExpose({ } } -/* 液态玻璃模式切换器 */ +/* 模式切换器 - 半透明效果 */ .liquid-mode-switch { - background: rgba(255, 255, 255, 0.25); - backdrop-filter: blur(12px) saturate(180%); - -webkit-backdrop-filter: blur(12px) saturate(180%); - border: 1px solid rgba(255, 255, 255, 0.3); - box-shadow: - 0 6px 12px rgba(0, 0, 0, 0.12), - 0 0 20px rgba(255, 20, 147, 0.06), - inset 0 1px 1px rgba(255, 255, 255, 0.6); + background: rgba(var(--color-bg-light, 255, 255, 255), var(--opacity-button, 0.75)); + border: var(--border-thin, 1px) solid rgba(var(--color-primary, 255, 20, 147), var(--opacity-border, 0.15)); + box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.1)); } .dark .liquid-mode-switch { - background: rgba(30, 30, 40, 0.4); - border-color: rgba(255, 255, 255, 0.15); - box-shadow: - 0 6px 12px rgba(0, 0, 0, 0.2), - 0 0 20px rgba(255, 105, 180, 0.08), - inset 0 1px 1px rgba(255, 255, 255, 0.1); + background: rgba(var(--color-bg-dark, 30, 41, 59), var(--opacity-button-dark, 0.75)); + border-color: rgba(var(--color-primary-light, 255, 105, 180), var(--opacity-border-dark, 0.2)); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); } /* 模式切换按钮 hover 效果 */ diff --git a/src/components/SearchHistoryModal.vue b/src/components/SearchHistoryModal.vue index 1322e0e..0e9e374 100644 --- a/src/components/SearchHistoryModal.vue +++ b/src/components/SearchHistoryModal.vue @@ -222,35 +222,30 @@ onUnmounted(() => { .history-modal { background: linear-gradient( 180deg, - rgba(255, 255, 255, 0.95) 0%, - rgba(255, 251, 235, 0.98) 100% + rgba(var(--color-bg-light, 255, 255, 255), var(--opacity-panel, 0.85)) 0%, + rgba(255, 253, 245, var(--opacity-panel-hover, 0.9)) 100% ); - backdrop-filter: blur(40px) saturate(1.5); - -webkit-backdrop-filter: blur(40px) saturate(1.5); - border: 1px solid rgba(251, 191, 36, 0.2); + border: var(--border-thin, 1px) solid rgba(var(--color-warning, 251, 191, 36), var(--opacity-border, 0.15)); + border-radius: var(--radius-2xl, 1.5rem); } /* 历史记录面板 - 右下角弹出 (暗色模式) */ .dark .history-modal { background: linear-gradient( 180deg, - rgba(30, 41, 59, 0.95) 0%, - rgba(30, 27, 17, 0.98) 100% + rgba(var(--color-bg-dark, 30, 41, 59), var(--opacity-panel-dark, 0.88)) 0%, + rgba(30, 27, 17, var(--opacity-panel-dark-hover, 0.92)) 100% ) !important; - backdrop-filter: blur(40px) saturate(1.5) !important; - -webkit-backdrop-filter: blur(40px) saturate(1.5) !important; - border: 1px solid rgba(251, 191, 36, 0.1) !important; + border: var(--border-thin, 1px) solid rgba(var(--color-warning, 251, 191, 36), var(--opacity-border-dark, 0.2)) !important; } /* 头部样式 */ .history-header { - background: rgba(255, 255, 255, 0.8); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(var(--color-bg-light, 255, 255, 255), var(--opacity-header, 0.7)); } .dark .history-header { - background: rgba(30, 41, 59, 0.8) !important; + background: rgba(var(--color-bg-dark, 30, 41, 59), var(--opacity-header-dark, 0.7)) !important; } /* 历史记录项 */ diff --git a/src/components/SearchResults.vue b/src/components/SearchResults.vue index 90c70eb..76147b9 100644 --- a/src/components/SearchResults.vue +++ b/src/components/SearchResults.vue @@ -1,15 +1,20 @@ @@ -154,12 +129,13 @@ import { useSearchStore } from '@/stores/search' import type { PlatformData } from '@/stores/search' import { playTap } from '@/composables/useSound' +import LazyRender from '@/components/LazyRender.vue' +import ResultItem from '@/components/ResultItem.vue' import { ExternalLink, AlertTriangle, Crown, List, - Link as LinkIcon, ArrowDown, CheckCircle, Star, @@ -181,18 +157,6 @@ import { const searchStore = useSearchStore() -// 从URL中提取路径 -function extractPath(url: string): string { - try { - const urlObj = new URL(url) - // 返回路径部分(去掉域名) - return urlObj.pathname + urlObj.search + urlObj.hash - } catch { - // 如果URL解析失败,返回完整URL - return url - } -} - // 获取站点所有结果的唯一标签 function getUniqueTags(platformData: PlatformData) { const allTags = new Set() @@ -215,13 +179,13 @@ function loadMore(platformName: string) { searchStore.loadMoreResults(platformName, 20) } -// 新增:卡片边框颜色 +// 新增:卡片边框颜色(去掉 hover 过渡) function getBorderClass(color: string) { const classes: Record = { - lime: 'border-lime-300 dark:border-lime-700/50 hover:border-lime-400 dark:hover:border-lime-600', - white: 'border-gray-300 dark:border-slate-600 hover:border-gray-400 dark:hover:border-slate-500', - gold: 'border-yellow-300 dark:border-yellow-700/50 hover:border-yellow-400 dark:hover:border-yellow-600', - red: 'border-red-300 dark:border-red-700/50 hover:border-red-400 dark:hover:border-red-600', + lime: 'border-lime-300 dark:border-lime-700/50', + white: 'border-gray-300 dark:border-slate-600', + gold: 'border-yellow-300 dark:border-yellow-700/50', + red: 'border-red-300 dark:border-red-700/50', } return classes[color] || 'border-gray-300 dark:border-slate-600' } @@ -248,12 +212,12 @@ function getHeaderTextColor(color: string) { return classes[color] || 'text-gray-700 dark:text-gray-300' } -// 新增:推荐标签样式(更醒目) +// 新增:推荐标签样式(使用纯色,避免渐变) function getRecommendChipClass(color: string) { const classes: Record = { - lime: 'bg-gradient-to-r from-lime-400 to-green-500 text-white border-lime-500', - gold: 'bg-gradient-to-r from-yellow-400 to-orange-500 text-white border-yellow-500', - red: 'bg-gradient-to-r from-red-400 to-pink-500 text-white border-red-500', + lime: 'bg-lime-500 text-white border-lime-600', + gold: 'bg-yellow-500 text-white border-yellow-600', + red: 'bg-red-500 text-white border-red-600', } return classes[color] || '' } @@ -346,69 +310,31 @@ function getTagLabel(tag: string) {