待看名单增加搜索和正倒序查看
This commit is contained in:
@@ -42,6 +42,32 @@
|
||||
</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 v-if="loading && items.length === 0" class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
@@ -57,6 +83,15 @@
|
||||
<button @click="fetchItems" class="retry-btn">重试</button>
|
||||
</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">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" class="empty-icon">
|
||||
<path
|
||||
@@ -67,7 +102,8 @@
|
||||
</div>
|
||||
|
||||
<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-title" :title="item.title">{{ item.title }}</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 autoGenerateTitle = ref(true);
|
||||
|
||||
// 搜索和排序
|
||||
const searchQuery = ref('');
|
||||
const sortOrder = ref<'asc' | 'desc'>('desc'); // 'desc' 表示最新,'asc' 表示最旧
|
||||
|
||||
// Store和Router
|
||||
const watchlistStore = useWatchlistStore();
|
||||
const route = useRoute();
|
||||
@@ -251,6 +291,29 @@ const itemCount = computed(() => watchlistStore.itemCount);
|
||||
const loading = computed(() => watchlistStore.loading);
|
||||
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
|
||||
const getCurrentPageUrl = () => {
|
||||
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显示
|
||||
const formatUrl = (url: string) => {
|
||||
try {
|
||||
@@ -725,6 +798,99 @@ watch(() => route.fullPath, () => {
|
||||
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 {
|
||||
max-height: 28rem;
|
||||
|
||||
@@ -294,7 +294,7 @@ const goToPage = (page: number) => {
|
||||
const handleArtworkClick = (artwork: Artwork) => {
|
||||
// 保存当前页面的滚动位置
|
||||
saveScrollPosition(route.fullPath);
|
||||
|
||||
|
||||
router.push({
|
||||
path: `/artwork/${artwork.id}`,
|
||||
query: {
|
||||
@@ -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(() => {
|
||||
clearCache();
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -416,7 +439,10 @@ onMounted(async () => {
|
||||
} else {
|
||||
await fetchRankingData(1);
|
||||
}
|
||||
|
||||
|
||||
// 添加键盘事件监听器
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// 恢复滚动位置
|
||||
setTimeout(() => {
|
||||
restoreScrollPosition(route.fullPath);
|
||||
|
||||
Reference in New Issue
Block a user