mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-05-10 00:44:13 +08:00
feat: 更新首页结构与样式,移除旧版主文件
* 在 `index.html` 中添加了新的元数据和样式,以提升SEO和用户体验。 * 移除 `src/main.js` 文件,简化项目结构,集中管理逻辑。 * 新增 `SearchHeader.vue` 组件,重构搜索表单和状态显示,优化用户交互。 * 更新样式以符合 Material 3 设计规范,增强视觉一致性。
This commit is contained in:
39
index.html
39
index.html
@@ -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>
|
||||
|
||||
187
src/components/SearchHeader.vue
Normal file
187
src/components/SearchHeader.vue
Normal 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>
|
||||
117
src/main.js
117
src/main.js
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user