feat: 加入塑料前端 (#262)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-02-19 01:47:09 +08:00
committed by GitHub
parent 59305c0bb4
commit d3b4559b2d
37 changed files with 1830 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import '../app.css';
import { Toaster } from '$lib/components/ui/sonner';
let { children } = $props();
</script>
<Toaster />
{@render children()}

View File

@@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = true;

181
web/src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,181 @@
<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>