fix: 修复一些小问题,优化细节体验 (#352)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-06-04 21:15:19 +08:00
committed by GitHub
parent c528152986
commit 6226fa7c4d
6 changed files with 144 additions and 97 deletions
+13 -3
View File
@@ -3,7 +3,13 @@
import SettingsIcon from '@lucide/svelte/icons/settings'; import SettingsIcon from '@lucide/svelte/icons/settings';
import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import { useSidebar } from '$lib/components/ui/sidebar/context.svelte.js'; import { useSidebar } from '$lib/components/ui/sidebar/context.svelte.js';
import { appStateStore, setVideoSourceFilter, clearAll, ToQuery } from '$lib/stores/filter'; import {
appStateStore,
setVideoSourceFilter,
clearAll,
ToQuery,
resetCurrentPage
} from '$lib/stores/filter';
import { type VideoSourcesResponse } from '$lib/types'; import { type VideoSourcesResponse } from '$lib/types';
import { VIDEO_SOURCES } from '$lib/consts'; import { VIDEO_SOURCES } from '$lib/consts';
@@ -15,7 +21,11 @@
const items = Object.values(VIDEO_SOURCES); const items = Object.values(VIDEO_SOURCES);
function handleSourceClick(sourceType: string, sourceId: number) { function handleSourceClick(sourceType: string, sourceId: number) {
setVideoSourceFilter(sourceType, sourceId.toString()); setVideoSourceFilter({
type: sourceType,
id: sourceId.toString()
});
resetCurrentPage();
goto(`/${ToQuery($appStateStore)}`); goto(`/${ToQuery($appStateStore)}`);
if (sidebar.isMobile) { if (sidebar.isMobile) {
sidebar.setOpenMobile(false); sidebar.setOpenMobile(false);
@@ -52,7 +62,7 @@
<div class="flex-1"> <div class="flex-1">
<Sidebar.Group> <Sidebar.Group>
<Sidebar.GroupLabel <Sidebar.GroupLabel
class="text-muted-foreground mb-2 px-2 text-xs font-medium tracking-wider uppercase" class="text-muted-foreground mb-2 px-2 text-xs font-medium uppercase tracking-wider"
> >
视频来源 视频来源
</Sidebar.GroupLabel> </Sidebar.GroupLabel>
+4 -15
View File
@@ -76,22 +76,11 @@
async function handleReset() { async function handleReset() {
resetting = true; resetting = true;
try { if (onReset) {
if (onReset) { await onReset();
await onReset();
} else {
await api.resetVideo(video.id);
window.location.reload();
}
} catch (error) {
console.error('重置失败:', error);
toast.error('重置失败', {
description: (error as ApiError).message
});
} finally {
resetting = false;
resetDialogOpen = false;
} }
resetting = false;
resetDialogOpen = false;
} }
function handleViewDetail() { function handleViewDetail() {
+38 -20
View File
@@ -2,35 +2,35 @@ import { writable } from 'svelte/store';
export interface AppState { export interface AppState {
query: string; query: string;
currentPage: number;
videoSource: { videoSource: {
key: string; type: string;
value: string; id: string;
}; } | null;
} }
// 创建应用状态store
export const appStateStore = writable<AppState>({ export const appStateStore = writable<AppState>({
query: '', query: '',
videoSource: { currentPage: 0,
key: '', videoSource: null,
value: ''
}
}); });
export const ToQuery = (state: AppState): string => { export const ToQuery = (state: AppState): string => {
const { query, videoSource } = state; const { query, videoSource } = state;
const params = new URLSearchParams(); const params = new URLSearchParams();
if (state.currentPage > 0) {
params.set('page', String(state.currentPage));
}
if (query.trim()) { if (query.trim()) {
params.set('query', query); params.set('query', query);
} }
if (videoSource.key && videoSource.value) { if (videoSource && videoSource.type && videoSource.id) {
params.set(videoSource.key, videoSource.value); params.set(videoSource.type, videoSource.id);
} }
const queryString = params.toString(); const queryString = params.toString();
return queryString ? `?${queryString}` : ''; return queryString ? `?${queryString}` : '';
}; };
// 便捷的设置方法
export const setQuery = (query: string) => { export const setQuery = (query: string) => {
appStateStore.update((state) => ({ appStateStore.update((state) => ({
...state, ...state,
@@ -38,28 +38,46 @@ export const setQuery = (query: string) => {
})); }));
}; };
export const setVideoSourceFilter = (key: string, value: string) => { export const setVideoSourceFilter = (filter: { type: string; id: string }) => {
appStateStore.update((state) => ({ appStateStore.update((state) => ({
...state, ...state,
videoSource: { key, value } videoSource: filter,
})); }));
}; };
export const clearVideoSourceFilter = () => { export const clearVideoSourceFilter = () => {
appStateStore.update((state) => ({ appStateStore.update((state) => ({
...state, ...state,
videoSource: { key: '', value: '' } videoSource: null,
})); }));
}; };
export const setCurrentPage = (page: number) => {
appStateStore.update((state) => ({
...state,
currentPage: page,
}));
};
export const resetCurrentPage = () => {
appStateStore.update((state) => ({
...state,
currentPage: 0,
}));
};
export const setAll = (query: string, currentPage: number, videoSource: { type: string; id: string } | null) => {
appStateStore.set({
query,
currentPage,
videoSource,
});
};
export const clearAll = () => { export const clearAll = () => {
appStateStore.set({ appStateStore.set({
query: '', query: '',
videoSource: { key: '', value: '' } currentPage: 0,
videoSource: null,
}); });
}; };
// 保留旧的接口以兼容现有代码
export const filterStore = writable({ key: '', value: '' });
export const setFilter = setVideoSourceFilter;
export const clearFilter = clearVideoSourceFilter;
+2 -1
View File
@@ -4,7 +4,7 @@
import SearchBar from '$lib/components/search-bar.svelte'; import SearchBar from '$lib/components/search-bar.svelte';
import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { appStateStore, setQuery, ToQuery } from '$lib/stores/filter'; import { appStateStore, resetCurrentPage, setQuery, ToQuery } from '$lib/stores/filter';
import { Toaster } from '$lib/components/ui/sonner/index.js'; import { Toaster } from '$lib/components/ui/sonner/index.js';
import { breadcrumbStore } from '$lib/stores/breadcrumb'; import { breadcrumbStore } from '$lib/stores/breadcrumb';
import BreadCrumb from '$lib/components/bread-crumb.svelte'; import BreadCrumb from '$lib/components/bread-crumb.svelte';
@@ -18,6 +18,7 @@
async function handleSearch(query: string) { async function handleSearch(query: string) {
setQuery(query); setQuery(query);
resetCurrentPage();
goto(`/${ToQuery($appStateStore)}`); goto(`/${ToQuery($appStateStore)}`);
} }
+79 -53
View File
@@ -21,52 +21,57 @@
import { import {
appStateStore, appStateStore,
clearVideoSourceFilter, clearVideoSourceFilter,
resetCurrentPage,
setAll,
setCurrentPage,
setQuery, setQuery,
setVideoSourceFilter, setVideoSourceFilter,
ToQuery ToQuery
} from '$lib/stores/filter'; } from '$lib/stores/filter';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { Title } from '$lib/components/ui/card';
const pageSize = 20;
let videosData: VideosResponse | null = null; let videosData: VideosResponse | null = null;
let loading = false; let loading = false;
let currentPage = 0;
const pageSize = 20;
let currentFilter: { type: string; id: string } | null = null;
let lastSearch: string | null = null; let lastSearch: string | null = null;
// 重置所有视频相关状态
let resetAllDialogOpen = false; let resetAllDialogOpen = false;
let resettingAll = false; let resettingAll = false;
// 从URL参数获取筛选条件 function getApiParams(searchParams: URLSearchParams) {
function getFilterFromURL(searchParams: URLSearchParams) { let videoSource = null;
for (const source of Object.values(VIDEO_SOURCES)) { for (const source of Object.values(VIDEO_SOURCES)) {
const value = searchParams.get(source.type); const value = searchParams.get(source.type);
if (value) { if (value) {
return { type: source.type, id: value }; videoSource = { type: source.type, id: value };
} }
} }
return null; return {
query: searchParams.get('query') || '',
videoSource,
pageNum: parseInt(searchParams.get('page') || '0')
};
} }
// 获取筛选项名称 function getFilterContent(type: string, id: string) {
function getFilterName(type: string, id: string): string { const filterTitle = Object.values(VIDEO_SOURCES).find((s) => s.type === type)?.title || '';
let filterName = '';
const videoSources = $videoSourceStore; const videoSources = $videoSourceStore;
if (!videoSources || !type || !id) return ''; if (videoSources && type && id) {
const sources = videoSources[type as keyof VideoSourcesResponse];
const sources = videoSources[type as keyof VideoSourcesResponse]; filterName = sources?.find((s) => s.id.toString() === id)?.name || '';
const source = sources?.find((s) => s.id.toString() === id); }
return source?.name || ''; return {
} title: filterTitle,
name: filterName
// 获取筛选项标题 };
function getFilterTitle(type: string): string {
const sourceConfig = Object.values(VIDEO_SOURCES).find((s) => s.type === type);
return sourceConfig?.title || '';
} }
async function loadVideos( async function loadVideos(
query?: string, query: string,
pageNum: number = 0, pageNum: number = 0,
filter?: { type: string; id: string } | null filter?: { type: string; id: string } | null
) { ) {
@@ -76,19 +81,14 @@
page: pageNum, page: pageNum,
page_size: pageSize page_size: pageSize
}; };
if (query) { if (query) {
params.query = query; params.query = query;
} }
// 添加筛选参数
if (filter) { if (filter) {
params[filter.type] = parseInt(filter.id); params[filter.type] = parseInt(filter.id);
} }
const result = await api.getVideos(params); const result = await api.getVideos(params);
videosData = result.data; videosData = result.data;
currentPage = pageNum;
} catch (error) { } catch (error) {
console.error('加载视频失败:', error); console.error('加载视频失败:', error);
toast.error('加载视频失败', { toast.error('加载视频失败', {
@@ -100,44 +100,56 @@
} }
async function handlePageChange(pageNum: number) { async function handlePageChange(pageNum: number) {
const query = ToQuery($appStateStore); setCurrentPage(pageNum);
if (query) { goto(`/${ToQuery($appStateStore)}`);
goto(`/${query}&page=${pageNum}`);
} else {
goto(`/?page=${pageNum}`);
}
} }
async function handleSearchParamsChange() { async function handleSearchParamsChange(searchParams: URLSearchParams) {
const query = $page.url.searchParams.get('query'); const { query, videoSource, pageNum } = getApiParams(searchParams);
currentFilter = getFilterFromURL($page.url.searchParams); setAll(query, pageNum, videoSource);
setQuery(query || ''); loadVideos(query, pageNum, videoSource);
if (currentFilter) {
setVideoSourceFilter(currentFilter.type, currentFilter.id);
} else {
clearVideoSourceFilter();
}
loadVideos(query || '', parseInt($page.url.searchParams.get('page') || '0'), currentFilter);
} }
function handleFilterRemove() { function handleFilterRemove() {
clearVideoSourceFilter(); clearVideoSourceFilter();
resetCurrentPage();
goto(`/${ToQuery($appStateStore)}`); goto(`/${ToQuery($appStateStore)}`);
} }
async function handleResetVideo(id: number) {
try {
const result = await api.resetVideo(id);
const data = result.data;
if (data.resetted) {
toast.success('重置成功', {
description: `视频「${data.video.name}」已重置`
});
const { query, currentPage, videoSource } = $appStateStore;
await loadVideos(query, currentPage, videoSource);
} else {
toast.info('重置无效', {
description: `视频「${data.video.name}」没有失败的状态,无需重置`
});
}
} catch (error) {
console.error('重置失败:', error);
toast.error('重置失败', {
description: (error as ApiError).message
});
}
}
async function handleResetAllVideos() { async function handleResetAllVideos() {
resettingAll = true; resettingAll = true;
try { try {
const result = await api.resetAllVideos(); const result = await api.resetAllVideos();
const data = result.data; const data = result.data;
if (data.resetted) { if (data.resetted) {
toast.success('重置成功', { toast.success('重置成功', {
description: `已重置 ${data.resetted_videos_count} 个视频和 ${data.resetted_pages_count} 个分页` description: `已重置 ${data.resetted_videos_count} 个视频和 ${data.resetted_pages_count} 个分页`
}); });
// 重新加载当前页面的视频数据 const { query, currentPage, videoSource } = $appStateStore;
const query = $page.url.searchParams.get('query'); await loadVideos(query, currentPage, videoSource);
loadVideos(query || '', currentPage, currentFilter);
} else { } else {
toast.info('没有需要重置的视频'); toast.info('没有需要重置的视频');
} }
@@ -154,7 +166,7 @@
$: if ($page.url.search !== lastSearch) { $: if ($page.url.search !== lastSearch) {
lastSearch = $page.url.search; lastSearch = $page.url.search;
handleSearchParamsChange(); handleSearchParamsChange($page.url.searchParams);
} }
onMount(async () => { onMount(async () => {
@@ -167,15 +179,20 @@
}); });
$: totalPages = videosData ? Math.ceil(videosData.total_count / pageSize) : 0; $: totalPages = videosData ? Math.ceil(videosData.total_count / pageSize) : 0;
$: filterTitle = currentFilter ? getFilterTitle(currentFilter.type) : ''; $: filterContent = $appStateStore.videoSource
$: filterName = currentFilter ? getFilterName(currentFilter.type, currentFilter.id) : ''; ? getFilterContent($appStateStore.videoSource.type, $appStateStore.videoSource.id)
: { title: '', name: '' };
</script> </script>
<svelte:head> <svelte:head>
<title>主页 - Bili Sync</title> <title>主页 - Bili Sync</title>
</svelte:head> </svelte:head>
<FilterBadge {filterTitle} {filterName} onRemove={handleFilterRemove} /> <FilterBadge
filterTitle={filterContent.title}
filterName={filterContent.name}
onRemove={handleFilterRemove}
/>
<!-- 统计信息 --> <!-- 统计信息 -->
{#if videosData} {#if videosData}
@@ -214,13 +231,22 @@
> >
{#each videosData.videos as video (video.id)} {#each videosData.videos as video (video.id)}
<div style="max-width: 400px; width: 100%;"> <div style="max-width: 400px; width: 100%;">
<VideoCard {video} /> <VideoCard
{video}
onReset={async () => {
await handleResetVideo(video.id);
}}
/>
</div> </div>
{/each} {/each}
</div> </div>
<!-- 翻页组件 --> <!-- 翻页组件 -->
<Pagination {currentPage} {totalPages} onPageChange={handlePageChange} /> <Pagination
currentPage={$appStateStore.currentPage}
{totalPages}
onPageChange={handlePageChange}
/>
{:else} {:else}
<div class="flex items-center justify-center py-12"> <div class="flex items-center justify-center py-12">
<div class="space-y-2 text-center"> <div class="space-y-2 text-center">
+8 -5
View File
@@ -24,10 +24,8 @@
toast.error('无效的视频ID'); toast.error('无效的视频ID');
return; return;
} }
loading = true; loading = true;
error = null; error = null;
try { try {
const result = await api.getVideo(videoId); const result = await api.getVideo(videoId);
videoData = result.data; videoData = result.data;
@@ -114,12 +112,17 @@
onReset={async () => { onReset={async () => {
try { try {
const result = await api.resetVideo((videoData as VideoResponse).video.id); const result = await api.resetVideo((videoData as VideoResponse).video.id);
if (result.data.resetted) { const data = result.data;
if (data.resetted) {
videoData = { videoData = {
video: result.data.video, video: data.video,
pages: result.data.pages pages: data.pages
}; };
toast.success('重置成功'); toast.success('重置成功');
} else {
toast.info('重置无效', {
description: `视频「${data.video.name}」没有失败的状态,无需重置`
});
} }
} catch (error) { } catch (error) {
console.error('重置失败:', error); console.error('重置失败:', error);