Remove anime.js dependency and refactor SearchResults and SettingsModal components for improved performance and consistency

- Eliminated the use of anime.js in favor of simpler CSS transitions and effects.
- Updated SearchResults.vue to implement virtual scrolling for better performance with large result sets.
- Refactored SettingsModal.vue to enhance button interactions and remove unnecessary animation directives.
- Improved overall styling and structure for a more cohesive user experience across components.
This commit is contained in:
AdingApkgg
2025-12-26 22:42:36 +08:00
parent 9fd8f0005a
commit 59ca9f924c
5 changed files with 104 additions and 77 deletions

View File

@@ -0,0 +1,66 @@
<script setup lang="ts">
import { Link as LinkIcon } from 'lucide-vue-next'
defineProps<{
index: number
source: {
title: string
url: string
}
}>()
// 从URL中提取路径
function extractPath(url: string): string {
try {
const urlObj = new URL(url)
return urlObj.pathname + urlObj.search + urlObj.hash
} catch {
return url
}
}
</script>
<template>
<div
class="result-item group p-3 sm:p-4 rounded-xl
bg-white/60 dark:bg-slate-700/60
hover:bg-white/80 dark:hover:bg-slate-700/80
border border-gray-200/40 dark:border-slate-600/40
hover:border-theme-primary/30 dark:hover:border-theme-accent/30"
>
<!-- 标题行 -->
<div class="flex items-start gap-2 sm:gap-3">
<span class="text-theme-primary dark:text-theme-accent text-sm font-bold mt-0.5 shrink-0 opacity-60 group-hover:opacity-100">
{{ index + 1 }}.
</span>
<a
:href="source.url"
target="_blank"
rel="noopener noreferrer"
class="text-gray-800 dark:text-slate-200 group-hover:text-theme-primary dark:group-hover:text-theme-accent font-semibold flex-1 text-sm sm:text-base break-words leading-relaxed"
>
{{ source.title }}
</a>
</div>
<!-- 资源相对路径从URL中提取 -->
<div v-if="source.url" class="flex items-center gap-2 mt-2 ml-6 sm:ml-8">
<LinkIcon :size="12" class="text-theme-primary/50 dark:text-theme-accent/50" />
<span class="text-xs text-gray-500 dark:text-slate-400 break-all font-mono bg-gray-100/80 dark:bg-slate-800/80 px-2 py-1 rounded">
{{ extractPath(source.url) }}
</span>
</div>
</div>
</template>
<style scoped>
/* 结果项 - 简化动画,仅使用 transform */
.result-item {
transition: transform 0.15s ease-out, background-color 0.15s ease-out;
}
.result-item:hover {
transform: translateX(4px);
}
</style>

View File

@@ -81,46 +81,33 @@
<span class="text-red-700 dark:text-red-300 font-medium">{{ platformData.error }}</span>
</div>
<!-- 搜索结果列表 - 使用 contain 优化布局性能 + 懒渲染 -->
<div v-if="getDisplayedResults(platformData).length > 0" class="results-list space-y-2 contain-layout">
<LazyRender
v-for="(result, index) in getDisplayedResults(platformData)"
:key="result.url || index"
:once="true"
min-height="72px"
root-margin="300px 0px"
>
<div
class="result-item group p-3 sm:p-4 rounded-xl
bg-white/60 dark:bg-slate-700/60
hover:bg-white/80 dark:hover:bg-slate-700/80
border border-gray-200/40 dark:border-slate-600/40
hover:border-theme-primary/30 dark:hover:border-theme-accent/30"
>
<!-- 标题行 -->
<div class="flex items-start gap-2 sm:gap-3">
<span class="text-theme-primary dark:text-theme-accent text-sm font-bold mt-0.5 shrink-0 opacity-60 group-hover:opacity-100">
{{ index + 1 }}.
</span>
<a
:href="result.url"
target="_blank"
rel="noopener noreferrer"
class="text-gray-800 dark:text-slate-200 group-hover:text-theme-primary dark:group-hover:text-theme-accent font-semibold flex-1 text-sm sm:text-base break-words leading-relaxed"
>
{{ result.title }}
</a>
</div>
<!-- 资源相对路径从URL中提取 -->
<div v-if="result.url" class="flex items-center gap-2 mt-2 ml-6 sm:ml-8">
<LinkIcon :size="12" class="text-theme-primary/50 dark:text-theme-accent/50" />
<span class="text-xs text-gray-500 dark:text-slate-400 break-all font-mono bg-gray-100/80 dark:bg-slate-800/80 px-2 py-1 rounded">
{{ extractPath(result.url) }}
</span>
</div>
<!-- 搜索结果列表 - 使用虚拟滚动优化大量结果 -->
<div v-if="getDisplayedResults(platformData).length > 0" class="results-list contain-layout">
<!-- 结果数量少于 15 时使用普通列表 -->
<template v-if="getDisplayedResults(platformData).length < 15">
<div class="space-y-2">
<ResultItem
v-for="(result, index) in getDisplayedResults(platformData)"
:key="result.url || index"
:index="index"
:source="result"
/>
</div>
</LazyRender>
</template>
<!-- 结果数量超过 15 时使用虚拟滚动 -->
<VirtualList
v-else
class="virtual-list-container"
:data-key="'url'"
:data-sources="getDisplayedResults(platformData)"
:estimate-size="80"
:keeps="20"
item-class="mb-2"
>
<template #default="{ item, index }">
<ResultItem :index="index" :source="item" />
</template>
</VirtualList>
</div>
<!-- 加载更多按钮 -->
@@ -162,12 +149,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 VirtualList from 'vue-virtual-scroll-list'
import {
ExternalLink,
AlertTriangle,
Crown,
List,
Link as LinkIcon,
ArrowDown,
CheckCircle,
Star,
@@ -189,18 +177,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<string>()
@@ -386,6 +362,12 @@ function getTagLabel(tag: string) {
contain: layout style;
}
/* 虚拟滚动列表容器 */
.virtual-list-container {
max-height: 600px;
overflow-y: auto;
}
/* Tailwind 动画 - 优化为仅使用 transform 和 opacity */
.animate-fade-in {
animation: fadeIn 0.5s ease-out;

View File

@@ -12,13 +12,11 @@
>
<!-- 顶部导航栏 -->
<div
v-anime:100="'slideUp'"
class="flex-shrink-0 flex items-center justify-between px-4 sm:px-6 py-3 sm:py-4 border-b border-white/10 dark:border-slate-700/50 glassmorphism-navbar select-none md:rounded-t-3xl"
>
<!-- 返回按钮 - 仅移动端 -->
<button
v-tap
class="flex items-center gap-1 text-[#ff1493] dark:text-[#ff69b4] font-medium transition-colors md:hidden"
class="flex items-center gap-1 text-[#ff1493] dark:text-[#ff69b4] font-medium transition-colors active:scale-95 md:hidden"
@click="close"
>
<ChevronLeft :size="24" />
@@ -35,8 +33,7 @@
<div class="flex items-center gap-2">
<!-- 保存按钮 -->
<button
v-tap
class="px-4 py-1.5 rounded-full text-white text-sm font-semibold bg-gradient-to-r from-[#ff1493] to-[#d946ef] shadow-lg shadow-pink-500/25"
class="px-4 py-1.5 rounded-full text-white text-sm font-semibold bg-[#ff1493] hover:bg-[#e0117f] active:scale-95 transition-all shadow-lg shadow-pink-500/25"
@click="save"
>
保存
@@ -58,7 +55,6 @@
<div class="max-w-3xl mx-auto px-4 sm:px-6 py-6 sm:py-8 space-y-6">
<!-- 主题设置卡片 -->
<div
v-anime:100="'cardIn'"
class="settings-card"
>
<div class="flex items-center gap-3 mb-4">
@@ -113,7 +109,6 @@
<!-- API 设置卡片 -->
<div
v-anime:150="'cardIn'"
class="settings-card"
>
<div class="flex items-center gap-3 mb-4">
@@ -127,11 +122,10 @@
</div>
<!-- API 选项列表 -->
<div v-anime-stagger:50="'slideRight'" class="space-y-2">
<div class="space-y-2">
<button
v-for="option in apiOptions"
:key="option.value"
v-tap
type="button"
:class="[
'w-full flex flex-col sm:flex-row sm:items-center sm:justify-between p-3 sm:p-4 rounded-xl transition-all duration-200 text-left',
@@ -217,7 +211,6 @@
<!-- 自定义样式卡片 -->
<div
v-anime:200="'cardIn'"
class="settings-card"
>
<div class="flex items-center gap-3 mb-4">
@@ -257,7 +250,6 @@
<!-- 重置区域 -->
<div
v-anime:250="'cardIn'"
class="settings-card bg-red-50/50 dark:bg-red-950/20 border-red-200/50 dark:border-red-900/30"
>
<div class="flex items-center justify-between">
@@ -271,8 +263,7 @@
</div>
</div>
<button
v-tap
class="px-4 py-2 rounded-xl text-red-600 dark:text-red-400 font-medium bg-white dark:bg-slate-800 border border-red-200 dark:border-red-800/50 hover:bg-red-50 dark:hover:bg-red-950/50 transition-colors"
class="px-4 py-2 rounded-xl text-red-600 dark:text-red-400 font-medium bg-white dark:bg-slate-800 border border-red-200 dark:border-red-800/50 hover:bg-red-50 dark:hover:bg-red-950/50 active:scale-95 transition-all"
@click="reset"
>
重置

View File

@@ -34,18 +34,11 @@ import { vRipple } from './directives/vRipple'
// 文本滚动指令
import { vTextScroll } from './composables/useTextScroll'
// Anime.js 动画指令
import { vAnime, vAnimeStagger, vHover, vTap } from './composables/useAnime'
const app = createApp(App)
// 注册全局指令
app.directive('ripple', vRipple)
app.directive('text-scroll', vTextScroll)
app.directive('anime', vAnime)
app.directive('anime-stagger', vAnimeStagger)
app.directive('hover', vHover)
app.directive('tap', vTap)
const pinia = createPinia()
// 配置 Pinia 插件

View File

@@ -90,10 +90,6 @@ export default defineConfig({
if (id.includes('/lucide-vue-next/')) {
return 'ui-libs';
}
// 动画库
if (id.includes('/animejs/')) {
return 'anime';
}
// Artalk 评论
if (id.includes('/artalk/')) {
return 'artalk';
@@ -125,7 +121,6 @@ export default defineConfig({
'vue',
'pinia',
'lucide-vue-next',
'animejs',
],
// 排除不需要预构建的
exclude: ['artalk'],