mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-05-09 00:34:20 +08:00
@@ -178,6 +178,10 @@ export default tseslint.config(
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// <script setup> 中变量在 template 使用,ESLint 无法感知
|
||||
'no-useless-assignment': 'off',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
38
package.json
38
package.json
@@ -5,37 +5,39 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
"lint:fix": "eslint . --fix",
|
||||
"check": "vue-tsc --noEmit && eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@tailwindcss/vite": "^4.2.0",
|
||||
"@types/node": "^25.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
||||
"@typescript-eslint/parser": "^8.56.0",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"eslint": "^9.39.3",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^25.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"eslint": "^10.1.0",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"tailwindcss": "^4.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"vite": "^7.3.1",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^6.0.2",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"vite": "^8.0.3",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vue-tsc": "^3.2.5",
|
||||
"vue-tsc": "^3.2.6",
|
||||
"workbox-window": "^7.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/noto-sans-sc": "^5.2.9",
|
||||
"@tanstack/vue-virtual": "^3.13.18",
|
||||
"@tanstack/vue-virtual": "^3.13.23",
|
||||
"artalk": "^2.9.1",
|
||||
"lucide-vue-next": "^0.575.0",
|
||||
"lucide-vue-next": "^1.0.0",
|
||||
"pinia": "^3.0.4",
|
||||
"prismjs": "^1.30.0",
|
||||
"vue": "^3.5.28",
|
||||
"vue": "^3.5.31",
|
||||
"vue-prism-editor": "2.0.0-alpha.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.30.1"
|
||||
"packageManager": "pnpm@10.33.0"
|
||||
}
|
||||
|
||||
1484
pnpm-lock.yaml
generated
1484
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -146,7 +146,7 @@ function initArtalk() {
|
||||
try {
|
||||
artalkInstance = Artalk.init({
|
||||
el: '#Comments',
|
||||
pageKey: 'https://www.searchgal.top/',
|
||||
pageKey: '/',
|
||||
server: 'https://artalk.saop.cc',
|
||||
site: '旮旯聚搜',
|
||||
darkMode: 'auto',
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
>
|
||||
<!-- 面板 -->
|
||||
<div
|
||||
ref="panelRef"
|
||||
class="keyboard-help-panel glassmorphism-card rounded-3xl shadow-2xl shadow-black/20 w-full max-w-md overflow-hidden"
|
||||
>
|
||||
<!-- 标题栏 -->
|
||||
@@ -161,7 +160,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
import { playTransitionDown } from '@/composables/useSound'
|
||||
import {
|
||||
@@ -172,7 +170,6 @@ import {
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const uiStore = useUIStore()
|
||||
const panelRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function close() {
|
||||
playTransitionDown()
|
||||
|
||||
@@ -522,13 +522,7 @@ let searchStartTime = 0
|
||||
// 友情链接
|
||||
import friendsData from '@/data/friends.json'
|
||||
|
||||
interface FriendLink {
|
||||
name: string
|
||||
desc: string
|
||||
url: string
|
||||
logo: string
|
||||
}
|
||||
const friendLinks = ref<FriendLink[]>(friendsData.friends || [])
|
||||
const friendLinks = ref(friendsData.friends || [])
|
||||
|
||||
// 友链 logo 加载失败时显示占位符
|
||||
function handleFriendLogoError(e: Event) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
>
|
||||
<div
|
||||
v-if="uiStore.isVndbPanelOpen && searchStore.vndbInfo"
|
||||
ref="modalRef"
|
||||
class="fixed z-50 flex flex-col vndb-page shadow-2xl shadow-black/20 inset-0 md:inset-6 md:m-auto md:w-[900px] md:max-w-[calc(100%-3rem)] md:h-[800px] md:max-h-[calc(100%-3rem)] md:rounded-3xl"
|
||||
>
|
||||
<!-- 顶部导航栏 -->
|
||||
@@ -597,7 +596,7 @@ const translateError = ref(false)
|
||||
|
||||
// 标签翻译状态
|
||||
const isTranslatingTags = ref(false)
|
||||
const translatedTags = ref<Map<string, string>>(new Map())
|
||||
const translatedTags = ref(new Map())
|
||||
const showOriginalTags = ref(false)
|
||||
const translateTagsError = ref(false)
|
||||
|
||||
@@ -608,7 +607,7 @@ const isLoadingCharacters = ref(false)
|
||||
const isLoadingQuotes = ref(false)
|
||||
|
||||
// 名言翻译状态
|
||||
const translatedQuotes = ref<Map<string, string>>(new Map())
|
||||
const translatedQuotes = ref(new Map())
|
||||
const isTranslatingQuotes = ref(false)
|
||||
const translateQuotesError = ref(false)
|
||||
const showOriginalQuotes = ref(false)
|
||||
@@ -653,7 +652,6 @@ function toggleSection(section: keyof typeof expandedSections.value) {
|
||||
expandedSections.value[section] = !expandedSections.value[section]
|
||||
}
|
||||
|
||||
const modalRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// 计算 VNDB URL
|
||||
const vndbUrl = computed(() => {
|
||||
@@ -924,17 +922,6 @@ function openGallery(startIndex: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化性别(保留备用)
|
||||
function _formatSex(sex: string): string {
|
||||
const sexMap: Record<string, string> = {
|
||||
'm': '男性',
|
||||
'f': '女性',
|
||||
'b': '双性',
|
||||
'n': '无性',
|
||||
}
|
||||
return sexMap[sex] || sex
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateString: string): string {
|
||||
if (!dateString) {return '未知'}
|
||||
@@ -1106,16 +1093,6 @@ function getTagCategoryClass(category: string): string {
|
||||
return categoryMap[category] || 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-400'
|
||||
}
|
||||
|
||||
// 格式化标签分类名称(保留备用)
|
||||
function _formatTagCategory(category: string): string {
|
||||
const categoryMap: Record<string, string> = {
|
||||
'cont': '内容',
|
||||
'tech': '技术',
|
||||
'ero': '色情',
|
||||
}
|
||||
return categoryMap[category] || category
|
||||
}
|
||||
|
||||
// 格式化关系类型
|
||||
function formatRelation(relation: string): string {
|
||||
const relationMap: Record<string, string> = {
|
||||
|
||||
@@ -129,10 +129,9 @@ export const vTextScroll = {
|
||||
// 获取当前实际文本内容(排除克隆的内容)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const inner = el.querySelector('.text-scroll-inner')!
|
||||
let currentContent = ''
|
||||
let currentContent: string
|
||||
|
||||
if (inner) {
|
||||
// 获取不包含克隆的文本
|
||||
const clone = inner.querySelector('.text-scroll-clone')
|
||||
if (clone) {
|
||||
currentContent = inner.textContent?.replace(clone.textContent || '', '') || ''
|
||||
@@ -140,11 +139,9 @@ export const vTextScroll = {
|
||||
currentContent = inner.textContent || ''
|
||||
}
|
||||
} else {
|
||||
// 内容被 Vue 重新渲染,需要重新包装
|
||||
currentContent = el.textContent || ''
|
||||
}
|
||||
|
||||
// 如果内容变化或结构被破坏,重新初始化
|
||||
if (!inner || currentContent !== extEl._textScrollContent) {
|
||||
const newContent = el.textContent || ''
|
||||
extEl._textScrollContent = newContent
|
||||
|
||||
@@ -17,13 +17,13 @@ export function useVndbTranslation() {
|
||||
|
||||
// 标签翻译状态
|
||||
const isTranslatingTags = ref(false)
|
||||
const translatedTags = ref<Map<string, string>>(new Map())
|
||||
const translatedTags = ref(new Map())
|
||||
const showOriginalTags = ref(false)
|
||||
const translateTagsError = ref(false)
|
||||
|
||||
// 名言翻译状态
|
||||
const isTranslatingQuotes = ref(false)
|
||||
const translatedQuotes = ref<Map<string, string>>(new Map())
|
||||
const translatedQuotes = ref(new Map())
|
||||
const showOriginalQuotes = ref(false)
|
||||
const translateQuotesError = ref(false)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"name": "VNS",
|
||||
"desc": "Visual Novel",
|
||||
"url": "https://gal.saop.cc",
|
||||
"logo": "https://gal.saop.cc/images/logo.svg"
|
||||
"logo": "https://gal.saop.cc/img/logo.svg"
|
||||
},
|
||||
{
|
||||
"name": "KisuGal",
|
||||
|
||||
@@ -2,31 +2,16 @@ import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { VndbInfo } from './search'
|
||||
|
||||
interface CachedVndbInfo {
|
||||
query: string
|
||||
data: VndbInfo
|
||||
timestamp: number
|
||||
expiresAt: number
|
||||
}
|
||||
|
||||
interface SearchResultData {
|
||||
platforms: Map<string, unknown>
|
||||
vndbInfo?: unknown
|
||||
}
|
||||
|
||||
interface CachedSearchResults {
|
||||
query: string
|
||||
mode: 'game' | 'patch'
|
||||
data: SearchResultData
|
||||
timestamp: number
|
||||
expiresAt: number
|
||||
}
|
||||
|
||||
export const useCacheStore = defineStore('cache', () => {
|
||||
// 状态
|
||||
const vndbCache = ref<Map<string, CachedVndbInfo>>(new Map())
|
||||
const searchResultsCache = ref<Map<string, CachedSearchResults>>(new Map())
|
||||
const imageCache = ref<Map<string, string>>(new Map()) // URL -> base64
|
||||
const vndbCache = ref(new Map())
|
||||
const searchResultsCache = ref(new Map())
|
||||
const imageCache = ref(new Map()) // URL -> base64
|
||||
|
||||
// 配置
|
||||
const vndbCacheDuration = ref(30 * 60 * 1000) // 30 分钟
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
interface LoadingImage {
|
||||
src: string
|
||||
startTime: number
|
||||
status: 'loading' | 'loaded' | 'error'
|
||||
loadTime?: number
|
||||
}
|
||||
|
||||
export const useLazyLoadStore = defineStore('lazyLoad', () => {
|
||||
// 状态
|
||||
const images = ref<Map<string, LoadingImage>>(new Map())
|
||||
const images = ref(new Map())
|
||||
const sessionStats = ref({
|
||||
totalLoaded: 0,
|
||||
totalErrors: 0,
|
||||
|
||||
@@ -24,7 +24,7 @@ export const useSearchStore = defineStore('search', () => {
|
||||
const searchQuery = ref('')
|
||||
const searchMode = ref<'game' | 'patch'>('game')
|
||||
const customApi = ref('')
|
||||
const platformResults = ref<Map<string, PlatformData>>(new Map())
|
||||
const platformResults = ref(new Map())
|
||||
const vndbInfo = ref<VndbInfo | null>(null)
|
||||
const isSearching = ref(false)
|
||||
const searchProgress = ref({ current: 0, total: 0 })
|
||||
|
||||
@@ -58,7 +58,7 @@ const DEFAULT_SETTINGS: UserSettings = {
|
||||
|
||||
export const useSettingsStore = defineStore('settings', () => {
|
||||
// 状态
|
||||
const settings = ref<UserSettings>({ ...DEFAULT_SETTINGS })
|
||||
const settings = ref({ ...DEFAULT_SETTINGS })
|
||||
const isInitialized = ref(false)
|
||||
|
||||
// 设置变更历史(用于撤销)
|
||||
|
||||
@@ -51,14 +51,14 @@ export const useStatsStore = defineStore('stats', () => {
|
||||
]))
|
||||
|
||||
// 访客统计(不蒜子)
|
||||
const visitorStats = ref<VisitorStats>({
|
||||
const visitorStats = ref({
|
||||
pv: 0,
|
||||
uv: 0,
|
||||
lastUpdated: 0,
|
||||
})
|
||||
|
||||
// 应用统计
|
||||
const appStats = ref<AppStats>({
|
||||
const appStats = ref({
|
||||
totalSearches: 0,
|
||||
gameSearches: 0,
|
||||
patchSearches: 0,
|
||||
|
||||
@@ -9,7 +9,7 @@ const CUSTOM_CSS_STORAGE_KEY = 'searchgal_custom_css'
|
||||
* 获取系统主题偏好
|
||||
*/
|
||||
export function getSystemTheme(): 'light' | 'dark' {
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
|
||||
return 'dark'
|
||||
}
|
||||
return 'light'
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
@@ -23,7 +21,6 @@
|
||||
|
||||
/* Vue */
|
||||
"types": ["node"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
|
||||
@@ -19,12 +19,7 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
// 响应式语法糖(如需要可启用)
|
||||
defineModel: true,
|
||||
},
|
||||
}),
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
// PWA 配置
|
||||
VitePWA({
|
||||
@@ -128,68 +123,47 @@ export default defineConfig({
|
||||
|
||||
// 构建优化
|
||||
build: {
|
||||
// 使用现代浏览器目标
|
||||
target: 'esnext',
|
||||
// 启用 CSS 代码分割
|
||||
cssCodeSplit: true,
|
||||
// 压缩选项
|
||||
minify: 'esbuild',
|
||||
// 源码映射(生产环境关闭)
|
||||
sourcemap: false,
|
||||
// Chunk 大小警告阈值 (KB)
|
||||
chunkSizeWarningLimit: 600,
|
||||
// Rollup 配置
|
||||
rollupOptions: {
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
// 资源文件名
|
||||
assetFileNames: (assetInfo) => {
|
||||
const name = assetInfo.name || '';
|
||||
// 字体文件
|
||||
if (/\.(woff2?|eot|ttf|otf)$/i.test(name)) {
|
||||
return 'fonts/[name]-[hash][extname]';
|
||||
}
|
||||
// 图片文件
|
||||
if (/\.(png|jpe?g|gif|svg|webp|ico)$/i.test(name)) {
|
||||
return 'images/[name]-[hash][extname]';
|
||||
}
|
||||
// CSS 文件
|
||||
if (/\.css$/i.test(name)) {
|
||||
return 'css/[name]-[hash][extname]';
|
||||
}
|
||||
return 'assets/[name]-[hash][extname]';
|
||||
},
|
||||
// JS 入口文件名
|
||||
entryFileNames: 'js/[name]-[hash].js',
|
||||
// JS Chunk 文件名
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
// 手动分包
|
||||
manualChunks: (id) => {
|
||||
if (id.includes('node_modules')) {
|
||||
// Vue 核心
|
||||
if (id.includes('/vue/') || id.includes('/@vue/')) {
|
||||
return 'vue-core';
|
||||
}
|
||||
// Pinia 状态管理
|
||||
if (id.includes('/pinia/')) {
|
||||
return 'pinia';
|
||||
}
|
||||
// UI 库
|
||||
if (id.includes('/lucide-vue-next/')) {
|
||||
return 'ui-libs';
|
||||
}
|
||||
// Artalk 评论
|
||||
if (id.includes('/artalk/')) {
|
||||
return 'artalk';
|
||||
}
|
||||
// 代码编辑器
|
||||
if (id.includes('/prismjs/') || id.includes('/vue-prism-editor/')) {
|
||||
return 'editor';
|
||||
}
|
||||
// Fancybox
|
||||
if (id.includes('/@fancyapps/')) {
|
||||
return 'fancybox';
|
||||
}
|
||||
// 其他第三方库
|
||||
return 'vendor';
|
||||
}
|
||||
},
|
||||
@@ -199,24 +173,14 @@ export default defineConfig({
|
||||
|
||||
// 依赖优化
|
||||
optimizeDeps: {
|
||||
// 预构建的依赖
|
||||
include: [
|
||||
'vue',
|
||||
'pinia',
|
||||
'lucide-vue-next',
|
||||
],
|
||||
// 排除不需要预构建的
|
||||
exclude: ['artalk'],
|
||||
},
|
||||
|
||||
// esbuild 配置
|
||||
esbuild: {
|
||||
// 生产环境移除 console 和 debugger
|
||||
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
|
||||
// 压缩选项
|
||||
legalComments: 'none',
|
||||
},
|
||||
|
||||
// CSS 配置
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
|
||||
Reference in New Issue
Block a user