修复预设过多切换主题卡顿问题,更改预设图标样式

This commit is contained in:
2025-12-07 22:07:26 +08:00
parent dce07797b3
commit fdf9f4d5a8
5 changed files with 475 additions and 55 deletions
+86 -21
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
import type { ExtendedPreset, PresetType } from '../../types';
import IconPresetType from '../icons/IconPresetType.vue';
const props = defineProps<{
presets: ExtendedPreset[];
@@ -15,17 +16,73 @@ const emit = defineEmits<{
(e: 'share', preset: ExtendedPreset): void;
}>();
function getTypeIcon(type: PresetType) {
const icons: Record<string, string> = {
positive: '🪄',
negative: '⛔',
setting: '⚙️',
style: '🖌️',
character: '🧙',
scene: '🏞️',
custom: '🧩'
};
return icons[type] || '🧩';
// Lazy Loading Logic
const PAGE_SIZE = 20;
const displayLimit = ref(PAGE_SIZE);
const containerRef = ref<HTMLElement | null>(null);
const sentinelRef = ref<HTMLElement | null>(null);
let observer: IntersectionObserver | null = null;
const displayedPresets = computed(() => {
return props.presets.slice(0, displayLimit.value);
});
watch(() => props.presets, () => {
displayLimit.value = PAGE_SIZE;
if (containerRef.value) {
containerRef.value.scrollTop = 0;
}
// Reset observer if needed, but the sentinel remains or is recreated
nextTick(() => {
checkIntersection();
});
});
function checkIntersection() {
if (observer && sentinelRef.value) {
// Re-observe just in case
observer.disconnect();
observer.observe(sentinelRef.value);
}
}
onMounted(() => {
observer = new IntersectionObserver((entries) => {
if (entries[0] && entries[0].isIntersecting) {
loadMore();
}
}, {
root: containerRef.value,
rootMargin: '200px',
threshold: 0.1
});
if (sentinelRef.value) {
observer.observe(sentinelRef.value);
}
});
onUnmounted(() => {
if (observer) {
observer.disconnect();
observer = null;
}
});
// Watch sentinel ref changes (e.g. when switching from empty to having presets)
watch(sentinelRef, (newEl) => {
if (observer) {
observer.disconnect();
if (newEl) {
observer.observe(newEl);
}
}
});
function loadMore() {
if (displayLimit.value < props.presets.length) {
displayLimit.value += PAGE_SIZE;
}
}
function getTypeLabel(type: PresetType) {
@@ -47,18 +104,19 @@ function formatDate(dateStr: string) {
</script>
<template>
<div class="preset-list-container">
<div class="preset-list-container" ref="containerRef">
<div v-if="presets.length === 0" class="empty-state">
<div class="empty-icon">📭</div>
<p class="empty-text">暂无预设</p>
</div>
<div v-else class="preset-grid">
<div v-for="preset in presets" :key="preset.id" class="preset-card">
<div class="card-header">
<div class="preset-type" :title="getTypeLabel(preset.type)">
{{ getTypeIcon(preset.type) }}
</div>
<template v-else>
<div class="preset-grid">
<div v-for="preset in displayedPresets" :key="preset.id" class="preset-card nav-btn">
<div class="card-header">
<div class="preset-type" :title="getTypeLabel(preset.type)">
<IconPresetType :type="preset.type" width="24" height="24" />
</div>
<h4 class="preset-name" :title="preset.name">{{ preset.name }}</h4>
<div class="preset-actions">
<button @click="emit('apply', preset)" class="action-btn apply-btn" title="应用预设">
@@ -126,6 +184,8 @@ function formatDate(dateStr: string) {
</div>
</div>
</div>
<div ref="sentinelRef" class="sentinel" style="height: 20px; width: 100%;"></div>
</template>
</div>
</template>
@@ -162,11 +222,13 @@ function formatDate(dateStr: string) {
background-color: var(--color-bg-primary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 1rem;
padding: 0.75rem;
display: flex;
flex-direction: column;
align-items: stretch;
transition: all 0.2s ease;
position: relative;
text-align: left;
}
.preset-card:hover {
@@ -183,8 +245,11 @@ function formatDate(dateStr: string) {
.preset-type {
font-size: 1.25rem;
margin-right: 0.75rem;
margin-right: 0.5rem;
flex-shrink: 0;
line-height: 1;
display: flex;
align-items: center;
}
.preset-name {