feat: 增加设置功能与统计信息展示

* 在 `App.vue` 中引入 `SettingsModal` 和 `StatsCorner` 组件,增强用户设置和统计信息的可视化。
* 更新 `TopToolbar` 组件,添加设置按钮以便用户快速访问设置。
* 在 `SearchHeader.vue` 中优化布局,提升视觉效果和用户体验。
This commit is contained in:
AdingApkgg
2025-11-20 04:11:49 +08:00
parent 06166500e7
commit 81533bbd9f
5 changed files with 398 additions and 86 deletions

View File

@@ -8,12 +8,19 @@
/>
<main class="flex-1 flex flex-col min-h-screen">
<TopToolbar :current-background-url="randomImageUrl" />
<SearchHeader />
<StatsCorner />
<TopToolbar :current-background-url="randomImageUrl" @open-settings="openSettings" />
<SearchHeader ref="searchHeaderRef" />
<SearchResults />
<FloatingButtons />
<CommentsModal />
<VndbPanel />
<SettingsModal
:is-open="isSettingsOpen"
:custom-api="searchStore.customApi"
@close="closeSettings"
@save="saveSettings"
/>
</main>
</div>
</template>
@@ -22,12 +29,14 @@
import { computed, onMounted, onUnmounted, ref } from "vue";
import { imageDB } from "@/utils/imageDB";
import { useSearchStore } from "@/stores/search";
import StatsCorner from "@/components/StatsCorner.vue";
import TopToolbar from "@/components/TopToolbar.vue";
import SearchHeader from "@/components/SearchHeader.vue";
import SearchResults from "@/components/SearchResults.vue";
import FloatingButtons from "@/components/FloatingButtons.vue";
import CommentsModal from "@/components/CommentsModal.vue";
import VndbPanel from "@/components/VndbPanel.vue";
import SettingsModal from "@/components/SettingsModal.vue";
const searchStore = useSearchStore();
const randomImageUrl = ref("");
@@ -38,6 +47,9 @@ const shuffledQueue = ref<string[]>([]);
let fetchInterval: number | null = null;
let displayInterval: number | null = null;
// 设置模态框
const isSettingsOpen = ref(false);
const MAX_CACHE_SIZE = 10000; // 最大缓存 10000 张图片
const CLEANUP_BATCH_SIZE = 2000; // 每次清理 2000 张
const FETCH_INTERVAL = 5000; // 5秒获取一次
@@ -368,6 +380,20 @@ onUnmounted(() => {
// 关闭数据库连接
imageDB.close();
});
// 设置相关函数
function openSettings() {
isSettingsOpen.value = true;
}
function closeSettings() {
isSettingsOpen.value = false;
}
function saveSettings(customApi: string) {
// 保存自定义 API 到 store
searchStore.setCustomApi(customApi);
}
</script>
<style>

View File

@@ -1,9 +1,10 @@
<template>
<div class="container mx-auto w-full px-4 sm:px-6 lg:px-8 py-4 sm:py-6">
<div class="flex flex-col items-center gap-4 sm:gap-6">
<!-- 主内容区域 - 垂直居中布局 -->
<div class="flex flex-col items-center justify-center min-h-[60vh]">
<!-- Title with gamepad icon and status -->
<div
class="header-title flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 my-12 sm:my-12 animate-fade-in-down"
class="header-title flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 mb-8 sm:mb-12 animate-fade-in-down"
>
<h1
class="text-3xl sm:text-4xl lg:text-5xl font-bold text-center text-white drop-shadow-[0_4px_8px_rgba(0,0,0,0.3)] flex items-center gap-2 sm:gap-3"
@@ -26,6 +27,7 @@
<SearchHistory
ref="searchHistoryRef"
@select="handleHistorySelect"
class="w-full max-w-2xl px-2 sm:px-0 mb-4"
/>
<!-- Search Form -->
@@ -48,22 +50,6 @@
/>
</div>
<!-- Custom API Input -->
<div class="relative">
<i
class="fas fa-link absolute left-3 sm:left-4 top-3 sm:top-4 text-gray-400 text-lg sm:text-xl pointer-events-none z-10"
></i>
<input
v-model="customApi"
type="url"
placeholder="自定义 API 地址 (可选)"
class="w-full pl-10 sm:pl-12 pr-4 py-3 sm:py-4 text-sm sm:text-base rounded-2xl bg-white/98 dark:bg-slate-800/95 text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-400 backdrop-blur-md shadow-lg focus:shadow-2xl focus:scale-[1.01] transition-all outline-none border-2 border-transparent focus:border-pink-500 dark:focus:border-purple-500"
/>
<p class="text-xs text-white/90 dark:text-slate-300 drop-shadow-md mt-2 font-medium">
例如: https://api.searchgal.homes 或 http://127.0.0.1:8898
</p>
</div>
<!-- Search Button and Mode Selector -->
<div class="flex flex-col gap-3">
<button
@@ -79,67 +65,42 @@
>
</button>
<!-- Search Mode Selector -->
<div class="flex justify-center gap-2 sm:gap-3">
<button
type="button"
@click="searchMode = 'game'"
:class="[
'mode-chip px-6 py-2 rounded-full font-medium transition-all flex items-center gap-2',
searchMode === 'game'
? 'bg-pink-500 dark:bg-purple-600 text-white shadow-lg scale-105'
: 'bg-white/90 dark:bg-slate-700/90 text-gray-700 dark:text-slate-200 hover:bg-white dark:hover:bg-slate-600',
]"
>
<i class="fas fa-gamepad"></i>
<span>游戏</span>
</button>
<button
type="button"
@click="searchMode = 'patch'"
:class="[
'mode-chip px-6 py-2 rounded-full font-medium transition-all flex items-center gap-2',
searchMode === 'patch'
? 'bg-pink-500 dark:bg-purple-600 text-white shadow-lg scale-105'
: 'bg-white/90 dark:bg-slate-700/90 text-gray-700 dark:text-slate-200 hover:bg-white dark:hover:bg-slate-600',
]"
>
<i class="fas fa-tools"></i>
<span>补丁</span>
</button>
</div>
<!-- Busuanzi Statistics -->
<div
class="flex justify-center items-center gap-6 text-sm text-white/90 drop-shadow-md font-medium mt-2"
>
<span class="flex items-center gap-1">
<i class="fas fa-eye"></i>
<span id="busuanzi_value_page_pv" class="font-semibold">-</span>
</span>
<span class="flex items-center gap-1">
<i class="fas fa-user"></i>
<span id="busuanzi_value_site_uv" class="font-semibold">-</span>
</span>
</div>
<!-- Version and GitHub Info -->
<div class="flex flex-wrap items-center justify-center gap-2 sm:gap-3 mt-4">
<div
class="px-3 py-1.5 rounded-full bg-white/90 dark:bg-slate-700/90 backdrop-blur-md text-gray-600 dark:text-slate-300 font-medium shadow-md flex items-center gap-2 text-sm"
>
<span>251007</span>
<!-- Search Mode Selector - 胶囊开关 -->
<div class="flex justify-center">
<div class="mode-switch-container relative bg-white/90 dark:bg-slate-700/90 backdrop-blur-md rounded-full p-1 shadow-lg">
<!-- 滑动背景 -->
<div
class="mode-slider absolute top-1 bottom-1 rounded-full bg-gradient-to-r from-pink-500 to-pink-600 dark:from-purple-600 dark:to-purple-700 shadow-md transition-all duration-300 ease-out"
:style="{
left: searchMode === 'game' ? '4px' : 'calc(50%)',
width: 'calc(50% - 4px)'
}"
/>
<!-- 游戏按钮 -->
<button
type="button"
@click="searchMode = 'game'"
class="mode-option relative z-10 px-6 py-2 rounded-full font-medium transition-all duration-300 flex items-center gap-2"
:class="searchMode === 'game' ? 'text-white' : 'text-gray-700 dark:text-slate-300'"
>
<i class="fas fa-gamepad"></i>
<span>游戏</span>
</button>
<!-- 补丁按钮 -->
<button
type="button"
@click="searchMode = 'patch'"
class="mode-option relative z-10 px-6 py-2 rounded-full font-medium transition-all duration-300 flex items-center gap-2"
:class="searchMode === 'patch' ? 'text-white' : 'text-gray-700 dark:text-slate-300'"
>
<i class="fas fa-tools"></i>
<span>补丁</span>
</button>
</div>
<a
href="https://github.com/Moe-Sakura/SearchGal"
target="_blank"
rel="noopener noreferrer"
class="px-3 py-1.5 rounded-full bg-white/90 dark:bg-slate-700/90 backdrop-blur-md text-gray-700 dark:text-slate-200 hover:text-pink-500 dark:hover:text-purple-400 font-medium hover:scale-105 transition-all shadow-md flex items-center gap-2 text-sm"
>
<i class="fab fa-github"></i>
<span>GitHub</span>
</a>
</div>
</div>
</div>
</form>
@@ -153,7 +114,7 @@
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="searchStore.errorMessage" class="w-full max-w-2xl">
<div v-if="searchStore.errorMessage" class="w-full max-w-2xl px-2 sm:px-0 mt-4">
<div
class="bg-red-50 dark:bg-red-900/30 border-2 border-red-200 dark:border-red-800/50 rounded-2xl p-4 shadow-lg"
>
@@ -167,12 +128,13 @@
</div>
</div>
</Transition>
</div>
<!-- Usage Notice -->
<div class="w-full max-w-4xl mt-6 sm:mt-8 px-2 sm:px-0 animate-fade-in animation-delay-1000">
<div
class="usage-notice bg-white/75 dark:bg-slate-800/90 backdrop-blur-md rounded-2xl sm:rounded-3xl shadow-xl p-4 sm:p-6 lg:p-8"
>
<!-- Usage Notice - 独立于居中区域 -->
<div class="w-full max-w-4xl mx-auto mt-8 sm:mt-12 px-2 sm:px-0 animate-fade-in animation-delay-1000">
<div
class="usage-notice bg-white/75 dark:bg-slate-800/90 backdrop-blur-md rounded-2xl sm:rounded-3xl shadow-xl p-4 sm:p-6 lg:p-8"
>
<h2
class="text-xl sm:text-2xl font-bold text-gray-800 dark:text-slate-100 mb-4 sm:mb-6 flex items-center gap-2"
>
@@ -264,7 +226,6 @@
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
@@ -474,4 +435,28 @@ async function handleSearch() {
transform: translateX(10px);
}
}
/* 胶囊开关样式 */
.mode-switch-container {
display: inline-flex;
position: relative;
}
.mode-slider {
pointer-events: none;
}
.mode-option {
min-width: 100px;
justify-content: center;
}
/* 响应式调整 */
@media (max-width: 640px) {
.mode-option {
min-width: 80px;
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
}
</style>

View File

@@ -0,0 +1,203 @@
<template>
<!-- 遮罩层 -->
<Transition
enter-active-class="transition-opacity duration-200 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-opacity duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="isOpen"
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
@click.self="close"
>
<!-- 对话框 -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div
v-if="isOpen"
class="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl sm:rounded-3xl shadow-2xl w-full max-w-2xl max-h-[90vh] mx-4 flex flex-col overflow-hidden border border-white/30 dark:border-slate-700/50"
@click.stop
>
<!-- 标题栏 -->
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-pink-100 dark:border-slate-700">
<i class="fas fa-cog text-pink-500 dark:text-purple-400 text-xl sm:text-2xl"></i>
<h2 class="text-lg sm:text-xl font-bold text-gray-800 dark:text-slate-100 flex-1">设置</h2>
<button
@click="close"
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-pink-50 text-gray-500 hover:text-pink-500 dark:text-slate-400 dark:hover:bg-slate-700/50 dark:hover:text-purple-400 transition-all duration-200"
>
<i class="fas fa-times text-xl"></i>
</button>
</div>
<!-- 内容区域 -->
<div class="flex-1 overflow-y-auto p-4 sm:p-6 custom-scrollbar">
<div class="space-y-6">
<!-- API 设置 -->
<div class="setting-section">
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
<i class="fas fa-server text-pink-500 dark:text-purple-400"></i>
<span>API 设置</span>
</h3>
<div class="space-y-4">
<!-- 自定义 API 地址 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-2">
自定义 API 地址
</label>
<div class="relative">
<i class="fas fa-link absolute left-3 sm:left-4 top-3 sm:top-4 text-gray-400 text-lg sm:text-xl pointer-events-none z-10"></i>
<input
v-model="localCustomApi"
type="url"
placeholder="https://cfapi.searchgal.homes"
class="w-full pl-10 sm:pl-12 pr-4 py-3 sm:py-4 text-sm sm:text-base rounded-xl bg-white dark:bg-slate-700/50 backdrop-blur-md shadow-md focus:shadow-lg focus:scale-[1.01] transition-all outline-none border-2 border-transparent focus:border-pink-500 dark:focus:border-purple-500 text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-400"
/>
</div>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-2">
留空使用默认 API 地址例如: https://cfapi.searchgal.homes 或 http://127.0.0.1:8787
</p>
</div>
<!-- API 状态 -->
<div class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800/50 rounded-xl p-4">
<div class="flex items-start gap-3">
<i class="fas fa-info-circle text-blue-500 dark:text-blue-400 text-lg mt-0.5"></i>
<div class="flex-1 text-sm text-blue-700 dark:text-blue-300">
<p class="font-semibold mb-1">关于自定义 API</p>
<p>您可以使用自己部署的后端 API 进行搜索API 需要兼容 SearchGal 的接口规范</p>
</div>
</div>
</div>
</div>
</div>
<!-- 更多设置可以在这里添加 -->
</div>
</div>
<!-- 底部操作栏 -->
<div class="flex items-center justify-end gap-3 px-4 sm:px-6 py-4 border-t border-pink-100 dark:border-slate-700">
<button
@click="reset"
class="px-4 py-2 rounded-xl text-gray-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-slate-700 transition-all font-medium"
>
<i class="fas fa-undo mr-2"></i>
重置
</button>
<button
@click="save"
class="px-6 py-2 rounded-xl bg-gradient-to-r from-pink-500 to-pink-600 dark:from-purple-600 dark:to-purple-700 text-white font-semibold shadow-lg hover:shadow-xl hover:scale-105 transition-all"
>
<i class="fas fa-check mr-2"></i>
保存
</button>
</div>
</div>
</Transition>
</div>
</Transition>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps<{
isOpen: boolean
customApi: string
}>()
const emit = defineEmits<{
close: []
save: [customApi: string]
}>()
const localCustomApi = ref(props.customApi)
// 监听外部变化
watch(() => props.customApi, (newValue) => {
localCustomApi.value = newValue
})
// 监听打开状态,同步数据
watch(() => props.isOpen, (isOpen) => {
if (isOpen) {
localCustomApi.value = props.customApi
}
})
function close() {
emit('close')
}
function save() {
emit('save', localCustomApi.value)
close()
}
function reset() {
localCustomApi.value = ''
}
</script>
<style scoped>
/* 自定义滚动条 - 粉色渐变 */
.custom-scrollbar::-webkit-scrollbar {
width: 10px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgb(236, 72, 153), rgb(139, 92, 246));
border-radius: 10px;
transition: background 0.3s ease;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, rgb(219, 39, 119), rgb(124, 58, 237));
}
/* 暗色模式滚动条 */
.dark .custom-scrollbar::-webkit-scrollbar-track {
background: rgba(30, 41, 59, 0.3);
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgb(139, 92, 246), rgb(99, 102, 241));
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, rgb(124, 58, 237), rgb(79, 70, 229));
}
/* 设置区块 */
.setting-section {
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<div
class="fixed top-4 left-4 z-40 flex flex-col gap-2 animate-fade-in"
>
<!-- 访问统计 -->
<div
class="stats-card bg-white/90 dark:bg-slate-800/90 backdrop-blur-md rounded-2xl shadow-lg px-4 py-3 flex items-center gap-3 border border-white/30 dark:border-slate-700/50"
>
<div class="flex items-center gap-2">
<i class="fas fa-eye text-pink-500 dark:text-purple-400"></i>
<span id="busuanzi_value_page_pv" class="font-semibold text-gray-800 dark:text-slate-100">-</span>
</div>
<div class="w-px h-4 bg-gray-300 dark:bg-slate-600"></div>
<div class="flex items-center gap-2">
<i class="fas fa-user text-pink-500 dark:text-purple-400"></i>
<span id="busuanzi_value_site_uv" class="font-semibold text-gray-800 dark:text-slate-100">-</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// 左上角访客统计组件
</script>
<style scoped>
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 640px) {
.stats-card {
font-size: 0.875rem;
padding: 0.5rem 0.75rem;
}
}
</style>

View File

@@ -22,6 +22,26 @@
<i :class="showCopiedTip ? 'fas fa-check' : 'fas fa-share-alt'" class="text-lg sm:text-xl"></i>
</button>
<!-- GitHub 按钮 -->
<a
href="https://github.com/Moe-Sakura/SearchGal"
target="_blank"
rel="noopener noreferrer"
aria-label="访问 GitHub 仓库"
class="toolbar-button github-button"
>
<i class="fab fa-github text-lg sm:text-xl"></i>
</a>
<!-- 设置按钮 -->
<button
@click="openSettings"
aria-label="设置"
class="toolbar-button settings-button"
>
<i class="fas fa-cog text-lg sm:text-xl"></i>
</button>
<!-- 主题切换按钮 -->
<button
@click="cycleTheme"
@@ -96,6 +116,11 @@ const props = defineProps<{
currentBackgroundUrl?: string
}>()
// Emits
const emit = defineEmits<{
openSettings: []
}>()
// 状态
const showSaveTip = ref(false)
const showCopiedTip = ref(false)
@@ -178,6 +203,11 @@ async function shareSearch() {
}
}
// 打开设置
function openSettings() {
emit('openSettings')
}
// 保存背景图(使用源格式和文件名)
async function saveBackgroundImage() {
if (!props.currentBackgroundUrl) return
@@ -331,6 +361,24 @@ watch(themeMode, () => {
transform: scale(0.95);
}
/* GitHub 按钮特殊样式 */
.github-button {
color: rgb(31, 41, 55);
text-decoration: none;
}
.dark .github-button {
color: rgb(226, 232, 240);
}
.github-button:hover {
border-color: rgba(31, 41, 55, 0.5);
}
.dark .github-button:hover {
border-color: rgba(226, 232, 240, 0.5);
}
/* 主题按钮特殊样式 */
.theme-button:hover {
border-color: rgba(245, 158, 11, 0.5);