Files
bili-sync-ai/web/src/routes/+page.svelte
2025-02-19 01:47:09 +08:00

182 lines
5.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { onMount } from 'svelte';
import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button';
import VideoItem from '$lib/components/VideoItem.svelte';
import { listVideos, getVideoSources } from '$lib/api';
import type { VideoInfo, VideoSourcesResponse } from '$lib/types';
// API Token 管理
let apiToken: string = localStorage.getItem('auth_token') || '';
function updateToken() {
localStorage.setItem('auth_token', apiToken);
}
// 定义分类列表
const categories: (keyof VideoSourcesResponse)[] = [
'collection',
'favorite',
'submission',
'watch_later'
];
let activeCategory: keyof VideoSourcesResponse = 'collection';
let searchQuery = '';
let videos: VideoInfo[] = [];
let total = 0;
let currentPage = 0;
const pageSize = 10;
// 视频列表模型及全局选中模型(只全局允许选中一个)
let videoListModels: VideoSourcesResponse = {
collection: [],
favorite: [],
submission: [],
watch_later: []
};
// 移除 per 分类选中,新增全局 selectedModel
let selectedModel: { category: keyof VideoSourcesResponse; id: number } | null = null;
// 控制侧边栏各分类的折叠状态true 为折叠
let collapse: { [key in keyof VideoSourcesResponse]?: boolean } = {
collection: false,
favorite: false,
submission: false,
watch_later: false
};
// 新增:定义 collapse 信号,用于让每个 VideoItem 收起详情
let videoCollapseSignal = false;
// 加载视频列表模型
async function fetchVideoListModels() {
videoListModels = await getVideoSources();
// 默认选中第一个有数据的模型
for (const key of categories) {
if (videoListModels[key]?.length) {
selectedModel = { category: key, id: videoListModels[key][0].id };
break;
}
}
// 默认使用 activeCategory 对应的选中 id 加载视频
fetchVideos();
}
// 加载视频列表,根据当前 activeCategory 对应的 selectedModel 发起请求
async function fetchVideos() {
const params: any = {};
if (selectedModel && selectedModel.category === activeCategory) {
params[`${activeCategory}`] = selectedModel.id.toString();
}
if (searchQuery) params.query = searchQuery;
params.page_size = pageSize;
params.page = currentPage;
const listRes = await listVideos(params);
videos = listRes.videos;
total = listRes.total_count;
}
onMount(fetchVideoListModels);
$: activeCategory, currentPage, searchQuery, fetchVideos();
function onSearch() {
currentPage = 0;
fetchVideos();
}
function prevPage() {
if (currentPage > 0) {
currentPage -= 1;
videoCollapseSignal = !videoCollapseSignal;
fetchVideos();
// 平滑滚动到顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
function nextPage() {
if ((currentPage + 1) * pageSize < total) {
currentPage += 1;
videoCollapseSignal = !videoCollapseSignal;
fetchVideos();
// 平滑滚动到顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
// 点击侧边栏项时更新 activeCategory 和全局选中模型 id
function selectModel(category: keyof VideoSourcesResponse, id: number) {
activeCategory = category;
selectedModel = { category, id };
currentPage = 0;
videoCollapseSignal = !videoCollapseSignal;
fetchVideos();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
</script>
<svelte:head>
<title>bili-sync 管理页</title>
</svelte:head>
<div class="my-4">
<Input placeholder="API Token" bind:value={apiToken} on:change={updateToken} />
</div>
<div class="flex">
<!-- 左侧侧边栏 -->
<aside class="w-1/4 border-r p-4">
<h2 class="mb-4 text-xl font-bold">视频列表模型</h2>
{#each categories as cat}
<div class="mb-4">
<!-- 点击标题切换折叠状态 -->
<button
class="w-full text-left font-semibold"
on:click={() => (collapse[cat] = !collapse[cat])}
>
{cat}
{collapse[cat] ? '▶' : '▼'}
</button>
{#if !collapse[cat]}
{#if videoListModels[cat]?.length}
<ul class="ml-4">
{#each videoListModels[cat] as model}
<li class="mb-1">
<button
class="w-full rounded px-2 py-1 text-left hover:bg-gray-100 {selectedModel &&
selectedModel.category === cat &&
selectedModel.id === model.id
? 'bg-gray-200'
: ''}"
on:click={() => selectModel(cat, model.id)}
>
{model.name}
</button>
</li>
{/each}
</ul>
{:else}
<p class="ml-4 text-gray-500">无数据</p>
{/if}
{/if}
</div>
{/each}
</aside>
<!-- 主内容区域 -->
<main class="flex-1 p-4">
<div class="mb-4">
<Input placeholder="搜索视频..." bind:value={searchQuery} on:change={onSearch} />
</div>
<div>
{#each videos as video}
<VideoItem {video} collapseSignal={videoCollapseSignal} />
{/each}
</div>
<div class="mt-4 flex items-center justify-between">
<Button onclick={prevPage} disabled={currentPage === 0}>上一页</Button>
<span>{currentPage + 1} 页,共 {Math.ceil(total / pageSize)}</span>
<Button onclick={nextPage} disabled={(currentPage + 1) * pageSize >= total}>下一页</Button>
</div>
</main>
</div>