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

View File

@@ -3,7 +3,13 @@
import SettingsIcon from '@lucide/svelte/icons/settings';
import * as Sidebar from '$lib/components/ui/sidebar/index.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 { VIDEO_SOURCES } from '$lib/consts';
@@ -15,7 +21,11 @@
const items = Object.values(VIDEO_SOURCES);
function handleSourceClick(sourceType: string, sourceId: number) {
setVideoSourceFilter(sourceType, sourceId.toString());
setVideoSourceFilter({
type: sourceType,
id: sourceId.toString()
});
resetCurrentPage();
goto(`/${ToQuery($appStateStore)}`);
if (sidebar.isMobile) {
sidebar.setOpenMobile(false);
@@ -52,7 +62,7 @@
<div class="flex-1">
<Sidebar.Group>
<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>

View File

@@ -76,22 +76,11 @@
async function handleReset() {
resetting = true;
try {
if (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;
if (onReset) {
await onReset();
}
resetting = false;
resetDialogOpen = false;
}
function handleViewDetail() {

View File

@@ -2,35 +2,35 @@ import { writable } from 'svelte/store';
export interface AppState {
query: string;
currentPage: number;
videoSource: {
key: string;
value: string;
};
type: string;
id: string;
} | null;
}
// 创建应用状态store
export const appStateStore = writable<AppState>({
query: '',
videoSource: {
key: '',
value: ''
}
currentPage: 0,
videoSource: null,
});
export const ToQuery = (state: AppState): string => {
const { query, videoSource } = state;
const params = new URLSearchParams();
if (state.currentPage > 0) {
params.set('page', String(state.currentPage));
}
if (query.trim()) {
params.set('query', query);
}
if (videoSource.key && videoSource.value) {
params.set(videoSource.key, videoSource.value);
if (videoSource && videoSource.type && videoSource.id) {
params.set(videoSource.type, videoSource.id);
}
const queryString = params.toString();
return queryString ? `?${queryString}` : '';
};
// 便捷的设置方法
export const setQuery = (query: string) => {
appStateStore.update((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) => ({
...state,
videoSource: { key, value }
videoSource: filter,
}));
};
export const clearVideoSourceFilter = () => {
appStateStore.update((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 = () => {
appStateStore.set({
query: '',
videoSource: { key: '', value: '' }
currentPage: 0,
videoSource: null,
});
};
// 保留旧的接口以兼容现有代码
export const filterStore = writable({ key: '', value: '' });
export const setFilter = setVideoSourceFilter;
export const clearFilter = clearVideoSourceFilter;

View File

@@ -4,7 +4,7 @@
import SearchBar from '$lib/components/search-bar.svelte';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
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 { breadcrumbStore } from '$lib/stores/breadcrumb';
import BreadCrumb from '$lib/components/bread-crumb.svelte';
@@ -18,6 +18,7 @@
async function handleSearch(query: string) {
setQuery(query);
resetCurrentPage();
goto(`/${ToQuery($appStateStore)}`);
}

View File

@@ -21,52 +21,57 @@
import {
appStateStore,
clearVideoSourceFilter,
resetCurrentPage,
setAll,
setCurrentPage,
setQuery,
setVideoSourceFilter,
ToQuery
} from '$lib/stores/filter';
import { toast } from 'svelte-sonner';
import { Title } from '$lib/components/ui/card';
const pageSize = 20;
let videosData: VideosResponse | null = null;
let loading = false;
let currentPage = 0;
const pageSize = 20;
let currentFilter: { type: string; id: string } | null = null;
let lastSearch: string | null = null;
// 重置所有视频相关状态
let resetAllDialogOpen = false;
let resettingAll = false;
// 从URL参数获取筛选条件
function getFilterFromURL(searchParams: URLSearchParams) {
function getApiParams(searchParams: URLSearchParams) {
let videoSource = null;
for (const source of Object.values(VIDEO_SOURCES)) {
const value = searchParams.get(source.type);
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 getFilterName(type: string, id: string): string {
function getFilterContent(type: string, id: string) {
const filterTitle = Object.values(VIDEO_SOURCES).find((s) => s.type === type)?.title || '';
let filterName = '';
const videoSources = $videoSourceStore;
if (!videoSources || !type || !id) return '';
const sources = videoSources[type as keyof VideoSourcesResponse];
const source = sources?.find((s) => s.id.toString() === id);
return source?.name || '';
}
// 获取筛选项标题
function getFilterTitle(type: string): string {
const sourceConfig = Object.values(VIDEO_SOURCES).find((s) => s.type === type);
return sourceConfig?.title || '';
if (videoSources && type && id) {
const sources = videoSources[type as keyof VideoSourcesResponse];
filterName = sources?.find((s) => s.id.toString() === id)?.name || '';
}
return {
title: filterTitle,
name: filterName
};
}
async function loadVideos(
query?: string,
query: string,
pageNum: number = 0,
filter?: { type: string; id: string } | null
) {
@@ -76,19 +81,14 @@
page: pageNum,
page_size: pageSize
};
if (query) {
params.query = query;
}
// 添加筛选参数
if (filter) {
params[filter.type] = parseInt(filter.id);
}
const result = await api.getVideos(params);
videosData = result.data;
currentPage = pageNum;
} catch (error) {
console.error('加载视频失败:', error);
toast.error('加载视频失败', {
@@ -100,44 +100,56 @@
}
async function handlePageChange(pageNum: number) {
const query = ToQuery($appStateStore);
if (query) {
goto(`/${query}&page=${pageNum}`);
} else {
goto(`/?page=${pageNum}`);
}
setCurrentPage(pageNum);
goto(`/${ToQuery($appStateStore)}`);
}
async function handleSearchParamsChange() {
const query = $page.url.searchParams.get('query');
currentFilter = getFilterFromURL($page.url.searchParams);
setQuery(query || '');
if (currentFilter) {
setVideoSourceFilter(currentFilter.type, currentFilter.id);
} else {
clearVideoSourceFilter();
}
loadVideos(query || '', parseInt($page.url.searchParams.get('page') || '0'), currentFilter);
async function handleSearchParamsChange(searchParams: URLSearchParams) {
const { query, videoSource, pageNum } = getApiParams(searchParams);
setAll(query, pageNum, videoSource);
loadVideos(query, pageNum, videoSource);
}
function handleFilterRemove() {
clearVideoSourceFilter();
resetCurrentPage();
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() {
resettingAll = true;
try {
const result = await api.resetAllVideos();
const data = result.data;
if (data.resetted) {
toast.success('重置成功', {
description: `已重置 ${data.resetted_videos_count} 个视频和 ${data.resetted_pages_count} 个分页`
});
// 重新加载当前页面的视频数据
const query = $page.url.searchParams.get('query');
loadVideos(query || '', currentPage, currentFilter);
const { query, currentPage, videoSource } = $appStateStore;
await loadVideos(query, currentPage, videoSource);
} else {
toast.info('没有需要重置的视频');
}
@@ -154,7 +166,7 @@
$: if ($page.url.search !== lastSearch) {
lastSearch = $page.url.search;
handleSearchParamsChange();
handleSearchParamsChange($page.url.searchParams);
}
onMount(async () => {
@@ -167,15 +179,20 @@
});
$: totalPages = videosData ? Math.ceil(videosData.total_count / pageSize) : 0;
$: filterTitle = currentFilter ? getFilterTitle(currentFilter.type) : '';
$: filterName = currentFilter ? getFilterName(currentFilter.type, currentFilter.id) : '';
$: filterContent = $appStateStore.videoSource
? getFilterContent($appStateStore.videoSource.type, $appStateStore.videoSource.id)
: { title: '', name: '' };
</script>
<svelte:head>
<title>主页 - Bili Sync</title>
</svelte:head>
<FilterBadge {filterTitle} {filterName} onRemove={handleFilterRemove} />
<FilterBadge
filterTitle={filterContent.title}
filterName={filterContent.name}
onRemove={handleFilterRemove}
/>
<!-- 统计信息 -->
{#if videosData}
@@ -214,13 +231,22 @@
>
{#each videosData.videos as video (video.id)}
<div style="max-width: 400px; width: 100%;">
<VideoCard {video} />
<VideoCard
{video}
onReset={async () => {
await handleResetVideo(video.id);
}}
/>
</div>
{/each}
</div>
<!-- 翻页组件 -->
<Pagination {currentPage} {totalPages} onPageChange={handlePageChange} />
<Pagination
currentPage={$appStateStore.currentPage}
{totalPages}
onPageChange={handlePageChange}
/>
{:else}
<div class="flex items-center justify-center py-12">
<div class="space-y-2 text-center">

View File

@@ -24,10 +24,8 @@
toast.error('无效的视频ID');
return;
}
loading = true;
error = null;
try {
const result = await api.getVideo(videoId);
videoData = result.data;
@@ -114,12 +112,17 @@
onReset={async () => {
try {
const result = await api.resetVideo((videoData as VideoResponse).video.id);
if (result.data.resetted) {
const data = result.data;
if (data.resetted) {
videoData = {
video: result.data.video,
pages: result.data.pages
video: data.video,
pages: data.pages
};
toast.success('重置成功');
} else {
toast.info('重置无效', {
description: `视频「${data.video.name}」没有失败的状态,无需重置`
});
}
} catch (error) {
console.error('重置失败:', error);