mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-08-28 03:28:05 +08:00
fix: 修复一些小问题,优化细节体验 (#352)
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user