feat: 更新首页结构与样式,移除旧版主文件

*   在 `index.html` 中添加了新的元数据和样式,以提升SEO和用户体验。
*   移除 `src/main.js` 文件,简化项目结构,集中管理逻辑。
*   新增 `SearchHeader.vue` 组件,重构搜索表单和状态显示,优化用户交互。
*   更新样式以符合 Material 3 设计规范,增强视觉一致性。
This commit is contained in:
AdingApkgg
2025-11-17 15:47:13 +08:00
parent aca43a9853
commit 6db38cc30c
3 changed files with 317 additions and 26 deletions

View File

@@ -1,10 +1,22 @@
<!DOCTYPE html>
<html lang="zh">
<html lang="zh" class="loading">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SearchGal - Galgame 聚合搜索</title>
<!-- 防止 FOUC - 立即执行的脚本 -->
<script>
// 页面加载时添加 loading 类
document.documentElement.classList.add('loading');
// 页面完全加载后移除 loading 类并添加 loaded 类
window.addEventListener('DOMContentLoaded', function() {
document.documentElement.classList.remove('loading');
document.documentElement.classList.add('loaded');
});
</script>
<style>
@import "tailwindcss";
@@ -245,19 +257,6 @@
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Galgame 聚合搜索" />
<link rel="shortcut icon" href="./gamepad-solid.svg" type="image/svg+xml" />
<link fetchpriority="low" href="https://registry.npmmirror.com/artalk/latest/files/dist/Artalk.css"
rel="stylesheet" />
<link fetchpriority="low" href="https://registry.npmmirror.com/lightgallery/latest/files/css/lightgallery.css"
rel="stylesheet" />
<link rel="stylesheet" href="https://registry.npmmirror.com/animate.css/latest/files/animate.min.css"
fetchpriority="high" />
<link rel="stylesheet"
href="https://registry.npmmirror.com/@fortawesome/fontawesome-free/latest/files/css/all.min.css"
fetchpriority="high" />
<script importance="high" src="https://registry.npmmirror.com/quicklink/latest/files/dist/quicklink.umd.js"></script>
<script async importance="high" src="https://registry.npmmirror.com/pace-js/latest/files/pace.min.js"></script>
<link href="https://registry.npmmirror.com/pace-js/latest/files/themes/blue/pace-theme-flash.css" importance="high"
rel="stylesheet" />
</head>
<body class="group/body">
@@ -454,28 +453,22 @@
<div class="fixed bottom-16 right-3 flex flex-col space-y-3 z-50">
<button id="scrollToTopBtn"
class="bg-indigo-600 hover:bg-indigo-700 text-white p-3 rounded-full shadow-lg transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-indigo-300 flex items-center justify-center translate-z-0 transition-opacity duration-500 ease-in-out group-[.locked-mode]/body:opacity-0 group-[.locked-mode]/body:pointer-events-none"
class="bg-indigo-600 hover:bg-indigo-700 text-white p-3 rounded-full shadow-lg transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-indigo-300 flex items-center justify-center translate-z-0 transition-opacity duration-500 ease-in-out group-[.locked-mode]/body:opacity-0 group-[.locked-mode]/body:pointer-events-none border-0"
title="回到顶部">
<i class="fas fa-arrow-up"></i>
</button>
<a href="#Comments" id="scrollToCommentsBtn"
class="bg-pink-500 hover:bg-pink-600 text-white p-3 rounded-full shadow-lg transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-pink-300 flex items-center justify-center transition-opacity duration-500 ease-in-out group-[.locked-mode]/body:opacity-0 group-[.locked-mode]/body:pointer-events-none"
class="bg-pink-500 hover:bg-pink-600 text-white p-3 rounded-full shadow-lg transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-pink-300 flex items-center justify-center transition-opacity duration-500 ease-in-out group-[.locked-mode]/body:opacity-0 group-[.locked-mode]/body:pointer-events-none border-0"
title="直达评论">
<i class="fas fa-comments"></i>
</a>
<button id="lock-view-btn"
class="bg-green-500 hover:bg-green-600 text-white p-3 rounded-full shadow-lg transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-green-300 flex items-center justify-center max-md:hidden opacity-0 pointer-events-none"
class="bg-green-500 hover:bg-green-600 text-white p-3 rounded-full shadow-lg transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-green-300 flex items-center justify-center max-md:hidden opacity-0 pointer-events-none border-0"
title="显示游戏介绍">
<i class="fas fa-eye"></i>
</button>
</div>
<script src="https://registry.npmmirror.com/instant.page/latest/files/instantpage.js" type="module"></script>
<script fetchpriority="low"
src="https://registry.npmmirror.com/lightgallery/latest/files/lightgallery.min.js"></script>
<script fetchpriority="low" src="https://registry.npmmirror.com/artalk/latest/files/dist/Artalk.js"></script>
<script fetchpriority="low"
src="https://registry.npmmirror.com/@artalk/plugin-lightbox/latest/files/dist/artalk-plugin-lightbox.js"></script>
<script type="module" src="/src/main.js" fetchpriority="high"></script>
<script async src="https://registry.npmmirror.com/js-asuna/latest/files/js/bsz.pure.mini.js"
fetchpriority="low"></script>

View File

@@ -0,0 +1,187 @@
<template>
<div class="container mx-auto w-full px-8 py-6">
<div class="flex flex-col items-center gap-6">
<!-- Logo with backdrop -->
<div class="w-20 h-20 bg-gradient-to-br from-pink-500 to-indigo-600 rounded-full flex items-center justify-center text-white font-bold text-3xl shadow-2xl backdrop-blur-sm border-4 border-white/50">
SG
</div>
<!-- Title with text shadow -->
<h1 class="text-5xl font-bold text-center text-white drop-shadow-[0_4px_8px_rgba(0,0,0,0.3)]">
Galgame 聚合搜索
</h1>
<!-- Search Form -->
<form @submit.prevent="handleSearch" class="w-full max-w-2xl">
<div class="flex flex-col gap-4">
<!-- Search Input -->
<input
v-model="searchQuery"
type="text"
name="game"
placeholder="游戏或补丁关键字词"
class="w-full px-4 py-3 bg-white/90 backdrop-blur-sm border-2 border-white/50 rounded-[8px] focus:outline-none focus:border-indigo-400 focus:bg-white/95 transition-all shadow-lg"
required
/>
<!-- Custom API Input -->
<div class="relative">
<input
v-model="customApi"
type="url"
placeholder="自定义 API 地址 (可选)"
class="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-[8px] text-sm focus:outline-none focus:border-indigo-500"
/>
<p class="text-xs text-gray-500 mt-1">例如: 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
type="submit"
:disabled="searchStore.searchDisabled"
class="py-3 px-6 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-[8px] disabled:opacity-50 transition-all relative overflow-hidden"
>
<span
v-if="searchStore.isSearching"
class="absolute left-0 top-0 h-full bg-pink-400/80 transition-all duration-300"
:style="{ width: progressWidth + '%' }"
/>
<span class="relative z-10">
<span v-if="!searchStore.isSearching">开始搜索</span>
<span v-else>进度: {{ searchStore.searchProgress.current }} / {{ searchStore.searchProgress.total }}</span>
</span>
</button>
<!-- Search Mode Selector -->
<div class="flex gap-0 rounded-[8px] overflow-hidden shadow-sm">
<label
class="flex-1 px-4 py-2 text-center cursor-pointer transition-all"
:class="searchMode === 'game' ? 'bg-indigo-600 text-white' : 'bg-white text-indigo-700'"
>
<input type="radio" value="game" v-model="searchMode" class="hidden" />
<i class="fas fa-gamepad mr-1"></i>游戏
</label>
<label
class="flex-1 px-4 py-2 text-center cursor-pointer transition-all border-l border-gray-200"
:class="searchMode === 'patch' ? 'bg-pink-500 text-white' : 'bg-white text-pink-700'"
>
<input type="radio" value="patch" v-model="searchMode" class="hidden" />
<i class="fas fa-wrench mr-1"></i>补丁
</label>
<a
href="https://status.searchgal.homes"
target="_blank"
class="px-4 py-2 bg-white text-green-600 border-l border-gray-200 flex items-center gap-1"
>
<i class="fas fa-circle text-xs"></i>正常
</a>
</div>
</div>
</div>
</form>
<!-- Error Message -->
<div v-if="searchStore.errorMessage" class="w-full max-w-2xl bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{{ searchStore.errorMessage }}
</div>
<!-- Usage Notice -->
<div class="w-full max-w-4xl mt-8">
<h2 class="text-2xl font-bold text-gray-800 mb-4">咱家的使用须知</h2>
<ul class="space-y-2 text-gray-700">
<li> 首先衷心感谢 <a href="https://saop.cc/" target="_blank" class="text-indigo-600 hover:underline">@Asuna</a> 大佬提供的服务器和技术支持</li>
<li> 本程序纯属 <strong>爱发电</strong>仅供绅士们交流学习使用务必请大家 <strong>支持正版 Galgame</strong></li>
<li> 本站只做互联网内容的 <strong>聚合搬运工</strong>搜索结果均来自第三方站点</li>
<li> 游戏介绍和人物信息数据由 <a href="https://vndb.org/" target="_blank" class="text-indigo-600 hover:underline">VNDB</a> 提供由AI大模型翻译</li>
<li> 郑重呼吁请务必支持 Galgame 正版让爱与梦想延续</li>
</ul>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useSearchStore } from '@/stores/search'
import { searchGameStream, fetchVndbData } from '@/api/search'
const searchStore = useSearchStore()
const searchQuery = ref('')
const customApi = ref('')
const searchMode = ref<'game' | 'patch'>('game')
const progressWidth = computed(() => {
if (searchStore.searchProgress.total === 0) return 0
return (searchStore.searchProgress.current / searchStore.searchProgress.total) * 100
})
async function handleSearch() {
if (!searchQuery.value.trim()) {
searchStore.errorMessage = '游戏名称不能为空'
return
}
// 检查冷却时间
const now = Date.now()
const COOLDOWN_MS = 30 * 1000
if (now - searchStore.lastSearchTime < COOLDOWN_MS) {
const timeLeft = Math.ceil((COOLDOWN_MS - (now - searchStore.lastSearchTime)) / 1000)
searchStore.errorMessage = `请等待 ${timeLeft} 秒后再搜索。`
return
}
// 重置状态
searchStore.clearResults()
searchStore.isSearching = true
searchStore.lastSearchTime = now
searchStore.searchProgress = { current: 0, total: 0 }
// 构建搜索参数
const searchParams = new URLSearchParams({
game: searchQuery.value.trim(),
mode: searchMode.value,
})
if (customApi.value.trim()) {
searchParams.set('api', customApi.value.trim())
}
// 开始搜索
try {
// 并行获取 VNDB 数据
const vndbPromise = fetchVndbData(searchQuery.value.trim())
// 流式搜索
await searchGameStream(searchParams, {
onTotal: (total) => {
searchStore.searchProgress.total = total
},
onProgress: (current, total) => {
searchStore.searchProgress.current = current
searchStore.searchProgress.total = total
},
onPlatformResult: (platformData) => {
// 保存完整的平台数据(包含颜色、错误等信息)
searchStore.setPlatformResult(platformData.name, platformData)
},
onComplete: async () => {
// 等待 VNDB 数据
const vndbData = await vndbPromise
if (vndbData) {
searchStore.vndbInfo = vndbData
}
searchStore.isSearching = false
searchStore.isFirstSearch = false
},
onError: (error) => {
searchStore.errorMessage = error
searchStore.isSearching = false
}
})
} catch (error) {
searchStore.errorMessage = error instanceof Error ? error.message : '搜索失败'
searchStore.isSearching = false
}
}
</script>

View File

@@ -1,3 +1,40 @@
// TailwindCSS v4 (与线上版本一致)
import "tailwindcss";
// Pace.js 页面加载进度条
import 'pace-js';
import 'pace-js/themes/blue/pace-theme-flash.css';
// Animate.css 动画库
import 'animate.css';
// Font Awesome 图标
import '@fortawesome/fontawesome-free/css/all.min.css';
// Artalk 评论系统
import Artalk from 'artalk';
import 'artalk/dist/Artalk.css';
// Artalk Lightbox 插件 (无需单独导入 CSS)
import { ArtalkLightboxPlugin } from '@artalk/plugin-lightbox';
// LightGallery
import lightGallery from 'lightgallery';
import 'lightgallery/css/lightgallery.css';
// Quicklink 预加载
import { listen } from 'quicklink';
// Instant.page 预加载
import 'instant.page';
// Fancybox 样式和脚本
import { Fancybox } from "@fancyapps/ui";
import "@fancyapps/ui/dist/fancybox/fancybox.css";
// Lozad.js 懒加载
import lozad from 'lozad';
// -- 全局常量与状态 --
const VNDB_API_BASE_URL = "https://api.vndb.org/kana";
@@ -80,7 +117,7 @@ scrollToCommentsBtn.addEventListener("click", (e) => {
/**
* 页面加载后初始化
*/
window.addEventListener("DOMContentLoaded", () => {
function initializePage() {
// Store the original background image
// The original background is no longer set on load.
if (backgroundLayer) {
@@ -101,14 +138,80 @@ window.addEventListener("DOMContentLoaded", () => {
magicCheckbox.checked = true;
}
quicklink.listen({ priority: true });
listen({ priority: true });
Artalk.init({
el: "#Comments",
pageKey: "https://searchgal.homes", // Original domain from user's file
server: "https://artalk.saop.cc",
site: "Galgame 聚合搜索",
useBackendConf: false,
plugins: [ArtalkLightboxPlugin],
});
// 初始化 Fancybox
Fancybox.bind("[data-fancybox]", {
// Fancybox 配置选项
Toolbar: {
display: {
left: ["infobar"],
middle: [
"zoomIn",
"zoomOut",
"toggle1to1",
"rotateCCW",
"rotateCW",
"flipX",
"flipY",
],
right: ["slideshow", "thumbs", "close"],
},
},
Images: {
zoom: true,
Panzoom: {
maxScale: 3,
},
},
// 启用键盘导航
Keyboard: true,
// 启用滚轮缩放
wheel: "zoom",
// 中文本地化
l10n: {
CLOSE: "关闭",
NEXT: "下一个",
PREV: "上一个",
MODAL: "您可以使用键盘关闭此模态",
ERROR: "出错了,请稍后再试",
IMAGE_ERROR: "未找到图片",
ELEMENT_NOT_FOUND: "未找到 HTML 元素",
AJAX_NOT_FOUND: "加载 AJAX 时出错:未找到",
AJAX_FORBIDDEN: "加载 AJAX 时出错:禁止访问",
IFRAME_ERROR: "加载页面时出错",
TOGGLE_ZOOM: "切换缩放级别",
TOGGLE_THUMBS: "切换缩略图",
TOGGLE_SLIDESHOW: "切换幻灯片",
TOGGLE_FULLSCREEN: "切换全屏",
DOWNLOAD: "下载",
},
});
// 初始化 Lozad.js 懒加载
const observer = lozad('.lozad', {
rootMargin: '200px 0px', // 提前 200px 开始加载
threshold: 0.01,
enableAutoReload: true,
loaded: function(el) {
// 图片加载完成后添加淡入动画
el.classList.add('opacity-0');
setTimeout(() => {
el.classList.remove('opacity-0');
el.classList.add('transition-opacity', 'duration-300', 'opacity-100');
}, 10);
}
});
observer.observe();
siteNavigationDiv = document.createElement("div");
siteNavigationDiv.id = "siteNavigation";
// Initial classes are minimal, updateNavigationLayout will set full classes
@@ -197,7 +300,15 @@ window.addEventListener("DOMContentLoaded", () => {
}
}
});
});
}
// 立即调用初始化函数(支持已经加载完的情况)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePage);
} else {
// 文档已经加载完成,立即执行
initializePage();
}
let isViewLocked = false;