待看名单增加搜索和正倒序查看

This commit is contained in:
2025-09-10 14:25:43 +08:00
parent 6e71ea3f55
commit e87fd2e332
2 changed files with 196 additions and 4 deletions
+167 -1
View File
@@ -42,6 +42,32 @@
</div> </div>
</div> </div>
<!-- 搜索和排序控制区域 -->
<div class="watchlist-controls">
<div class="search-box">
<svg viewBox="0 0 24 24" fill="currentColor" class="search-icon">
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</svg>
<input v-model="searchQuery" type="text" placeholder="搜索标题或URL..." class="search-input" />
<button v-if="searchQuery" @click="clearSearch" class="clear-search-btn" title="清除搜索">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</svg>
</button>
</div>
<div class="sort-controls">
<button @click="toggleSortOrder" class="sort-btn" :title="sortOrder === 'desc' ? '切换为升序' : '切换为降序'">
<svg viewBox="0 0 24 24" fill="currentColor" class="sort-icon">
<path v-if="sortOrder === 'desc'" d="M3 12l2-2 7 7 10-10 2 2L12 21z" />
<path v-else d="M12 3l10 10-2 2-7-7-7 7-2-2z" />
</svg>
<span class="sort-text">{{ sortOrder === 'desc' ? '最新' : '最旧' }}</span>
</button>
</div>
</div>
<div class="watchlist-content"> <div class="watchlist-content">
<div v-if="loading && items.length === 0" class="loading"> <div v-if="loading && items.length === 0" class="loading">
<div class="loading-spinner"></div> <div class="loading-spinner"></div>
@@ -57,6 +83,15 @@
<button @click="fetchItems" class="retry-btn">重试</button> <button @click="fetchItems" class="retry-btn">重试</button>
</div> </div>
<div v-else-if="filteredAndSortedItems.length === 0 && searchQuery" class="empty">
<svg viewBox="0 0 24 24" fill="currentColor" class="empty-icon">
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</svg>
<span>没有找到匹配的项目</span>
<p>尝试调整搜索词或清除搜索条件</p>
</div>
<div v-else-if="items.length === 0" class="empty"> <div v-else-if="items.length === 0" class="empty">
<svg viewBox="0 0 24 24" fill="currentColor" class="empty-icon"> <svg viewBox="0 0 24 24" fill="currentColor" class="empty-icon">
<path <path
@@ -67,7 +102,8 @@
</div> </div>
<div v-else class="items-list"> <div v-else class="items-list">
<div v-for="item in items" :key="item.id" class="watchlist-item" :class="{ current: isCurrentUrl(item.url) }"> <div v-for="item in filteredAndSortedItems" :key="item.id" class="watchlist-item"
:class="{ current: isCurrentUrl(item.url) }">
<div class="item-main" @click="navigateToItem(item)"> <div class="item-main" @click="navigateToItem(item)">
<div class="item-title" :title="item.title">{{ item.title }}</div> <div class="item-title" :title="item.title">{{ item.title }}</div>
<div class="item-url" :title="item.url">{{ formatUrl(item.url) }}</div> <div class="item-url" :title="item.url">{{ formatUrl(item.url) }}</div>
@@ -240,6 +276,10 @@ const addMode = ref<'single' | 'batch'>('single');
const batchUrls = ref(''); const batchUrls = ref('');
const autoGenerateTitle = ref(true); const autoGenerateTitle = ref(true);
// 搜索和排序
const searchQuery = ref('');
const sortOrder = ref<'asc' | 'desc'>('desc'); // 'desc' 表示最新,'asc' 表示最旧
// Store和Router // Store和Router
const watchlistStore = useWatchlistStore(); const watchlistStore = useWatchlistStore();
const route = useRoute(); const route = useRoute();
@@ -251,6 +291,29 @@ const itemCount = computed(() => watchlistStore.itemCount);
const loading = computed(() => watchlistStore.loading); const loading = computed(() => watchlistStore.loading);
const error = computed(() => watchlistStore.error); const error = computed(() => watchlistStore.error);
// 过滤和排序后的待看项目
const filteredAndSortedItems = computed(() => {
let filteredItems = [...items.value];
// 搜索过滤
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase();
filteredItems = filteredItems.filter(item =>
item.title.toLowerCase().includes(query) ||
item.url.toLowerCase().includes(query)
);
}
// 排序
filteredItems.sort((a, b) => {
const dateA = new Date(a.createdAt).getTime();
const dateB = new Date(b.createdAt).getTime();
return sortOrder.value === 'desc' ? dateB - dateA : dateA - dateB;
});
return filteredItems;
});
// 获取当前页面完整URL // 获取当前页面完整URL
const getCurrentPageUrl = () => { const getCurrentPageUrl = () => {
return window.location.href; return window.location.href;
@@ -505,6 +568,16 @@ const saveAdd = async () => {
} }
}; };
// 切换排序顺序
const toggleSortOrder = () => {
sortOrder.value = sortOrder.value === 'desc' ? 'asc' : 'desc';
};
// 清除搜索
const clearSearch = () => {
searchQuery.value = '';
};
// 格式化URL显示 // 格式化URL显示
const formatUrl = (url: string) => { const formatUrl = (url: string) => {
try { try {
@@ -725,6 +798,99 @@ watch(() => route.fullPath, () => {
height: 1rem; height: 1rem;
} }
/* 搜索和排序控制区域样式 */
.watchlist-controls {
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
gap: 0.5rem;
background: #f8fafc;
}
.search-box {
flex: 1;
display: flex;
align-items: center;
background: #f3f4f6;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
padding: 0.375rem 0.75rem;
gap: 0.5rem;
}
.search-icon {
width: 1.125rem;
height: 1.125rem;
color: #6b7280;
}
.search-input {
flex: 1;
border: none;
background: none;
font-size: 0.875rem;
color: #374151;
outline: none;
}
.search-input::placeholder {
color: #9ca3af;
}
.clear-search-btn {
background: none;
border: none;
cursor: pointer;
color: #6b7280;
padding: 0.25rem;
border-radius: 0.25rem;
transition: all 0.2s;
}
.clear-search-btn:hover {
background: #f3f4f6;
color: #374151;
}
.clear-search-btn svg {
width: 0.875rem;
height: 0.875rem;
}
.sort-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.sort-btn {
display: flex;
align-items: center;
gap: 0.25rem;
background: #f3f4f6;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
padding: 0.375rem 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.sort-btn:hover {
background: #e5e7eb;
}
.sort-icon {
width: 1rem;
height: 1rem;
color: #6b7280;
}
.sort-text {
font-size: 0.875rem;
color: #374151;
}
/* 内容样式 */ /* 内容样式 */
.watchlist-content { .watchlist-content {
max-height: 28rem; max-height: 28rem;
+27 -1
View File
@@ -387,9 +387,32 @@ watch(() => route.query, () => {
} }
}); });
// 组件卸载时清理缓存 // 键盘事件处理
const handleKeyDown = (event: KeyboardEvent) => {
// 检查是否在输入框中,如果是则不处理
const target = event.target as HTMLElement;
if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT')) {
return;
}
// 只有在有多页且有作品时才处理键盘事件
if (totalPages.value <= 1 || !artworks.value || artworks.value.length === 0) {
return;
}
if (event.key === 'ArrowLeft' && currentPage.value > 1) {
event.preventDefault();
goToPage(currentPage.value - 1);
} else if (event.key === 'ArrowRight' && currentPage.value < totalPages.value) {
event.preventDefault();
goToPage(currentPage.value + 1);
}
};
// 组件卸载时清理缓存和事件监听器
onUnmounted(() => { onUnmounted(() => {
clearCache(); clearCache();
window.removeEventListener('keydown', handleKeyDown);
}); });
onMounted(async () => { onMounted(async () => {
@@ -417,6 +440,9 @@ onMounted(async () => {
await fetchRankingData(1); await fetchRankingData(1);
} }
// 添加键盘事件监听器
window.addEventListener('keydown', handleKeyDown);
// 恢复滚动位置 // 恢复滚动位置
setTimeout(() => { setTimeout(() => {
restoreScrollPosition(route.fullPath); restoreScrollPosition(route.fullPath);