feat: 在视频页显示视频属于哪个视频源 (#676)
This commit is contained in:
@@ -77,6 +77,10 @@ pub struct VideoInfo {
|
||||
pub should_download: bool,
|
||||
#[serde(serialize_with = "serde_video_download_status")]
|
||||
pub download_status: u32,
|
||||
pub collection_id: Option<i32>,
|
||||
pub favorite_id: Option<i32>,
|
||||
pub submission_id: Option<i32>,
|
||||
pub watch_later_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, DerivePartialModel, FromQueryResult)]
|
||||
|
||||
@@ -200,6 +200,10 @@ pub async fn clear_and_reset_video_status(
|
||||
valid: video_info.valid,
|
||||
should_download: video_info.should_download,
|
||||
download_status: video_info.download_status,
|
||||
collection_id: video_info.collection_id,
|
||||
favorite_id: video_info.favorite_id,
|
||||
submission_id: video_info.submission_id,
|
||||
watch_later_id: video_info.watch_later_id,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -13,13 +13,17 @@
|
||||
BrushCleaningIcon,
|
||||
UserIcon,
|
||||
SquareArrowOutUpRightIcon,
|
||||
EllipsisIcon
|
||||
EllipsisIcon,
|
||||
HeartIcon,
|
||||
FolderIcon,
|
||||
ClockIcon
|
||||
} from '@lucide/svelte/icons';
|
||||
import { goto } from '$app/navigation';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
||||
|
||||
// 将 bvid 设置为可选属性,但保留 VideoInfo 的其它所有属性
|
||||
export let video: Omit<VideoInfo, 'bvid'> & { bvid?: string };
|
||||
export let source: { type: string; name: string } | null = null; // 视频源信息
|
||||
export let showActions: boolean = true; // 控制是否显示操作按钮
|
||||
export let mode: 'default' | 'detail' | 'page' = 'default'; // 卡片模式
|
||||
export let customTitle: string = ''; // 自定义标题
|
||||
@@ -132,8 +136,8 @@
|
||||
</script>
|
||||
|
||||
<Card class={cardClasses}>
|
||||
<CardHeader class="shrink-0 pb-3">
|
||||
<div class="flex min-w-0 items-start justify-between gap-3">
|
||||
<CardHeader class="shrink-0 pb-1">
|
||||
<div class="flex min-w-0 items-start justify-between gap-3 {source ? 'min-h-12' : ''}">
|
||||
<CardTitle
|
||||
class="line-clamp-2 min-w-0 flex-1 cursor-default {mode === 'default'
|
||||
? 'text-sm'
|
||||
@@ -157,6 +161,24 @@
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if source}
|
||||
<div class="text-muted-foreground mt-2 flex min-w-0 items-center justify-end gap-1 text-sm">
|
||||
<Badge variant="outline" class="shrink-0 px-1.5 py-0.5">
|
||||
{#if source.type === 'favorite'}
|
||||
<HeartIcon class="h-3.5 w-3.5 shrink-0" />
|
||||
{:else if source.type === 'collection'}
|
||||
<FolderIcon class="h-3.5 w-3.5 shrink-0" />
|
||||
{:else if source.type === 'submission'}
|
||||
<UserIcon class="h-3.5 w-3.5 shrink-0" />
|
||||
{:else if source.type === 'watch_later'}
|
||||
<ClockIcon class="h-3.5 w-3.5 shrink-0" />
|
||||
{/if}
|
||||
<span class="min-w-0 truncate" title={source.name}>
|
||||
{source.name}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
</CardHeader>
|
||||
<CardContent
|
||||
class={mode === 'default' ? 'flex min-w-0 flex-1 flex-col justify-end pt-0 pb-3' : 'pt-0 pb-4'}
|
||||
|
||||
@@ -35,6 +35,10 @@ export interface VideoInfo {
|
||||
valid: boolean;
|
||||
should_download: boolean;
|
||||
download_status: [number, number, number, number, number];
|
||||
collection_id?: number;
|
||||
favorite_id?: number;
|
||||
submission_id?: number;
|
||||
watch_later_id?: number;
|
||||
}
|
||||
|
||||
export interface VideosResponse {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
VideoSourcesResponse,
|
||||
ApiError,
|
||||
VideoSource,
|
||||
UpdateFilteredVideoStatusRequest
|
||||
UpdateFilteredVideoStatusRequest,
|
||||
VideoInfo
|
||||
} from '$lib/types';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
@@ -39,6 +40,7 @@
|
||||
import FilteredStatusEditor from '$lib/components/filtered-status-editor.svelte';
|
||||
import StatusFilter from '$lib/components/status-filter.svelte';
|
||||
import ValidationFilter from '$lib/components/validation-filter.svelte';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
|
||||
const pageSize = 20;
|
||||
|
||||
@@ -56,7 +58,9 @@
|
||||
let updatingAll = false;
|
||||
|
||||
let videoSources: VideoSourcesResponse | null = null;
|
||||
let videoSourcesLoaded = false;
|
||||
let filters: Record<string, Filter> | null = null;
|
||||
let sourceMap: SvelteMap<string, { type: string; name: string }> = new SvelteMap();
|
||||
|
||||
function getApiParams(searchParams: URLSearchParams) {
|
||||
let videoSource = null;
|
||||
@@ -246,6 +250,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoSource(video: VideoInfo): { type: string; name: string } | null {
|
||||
if (video.collection_id != null) {
|
||||
return sourceMap.get(`collection:${video.collection_id}`) || null;
|
||||
}
|
||||
if (video.favorite_id != null) {
|
||||
return sourceMap.get(`favorite:${video.favorite_id}`) || null;
|
||||
}
|
||||
if (video.submission_id != null) {
|
||||
return sourceMap.get(`submission:${video.submission_id}`) || null;
|
||||
}
|
||||
if (video.watch_later_id != null) {
|
||||
return sourceMap.get(`watch_later:${video.watch_later_id}`) || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取筛选条件的显示数组
|
||||
function getFilterDescriptionParts(): string[] {
|
||||
const state = $appStateStore;
|
||||
@@ -284,7 +304,7 @@
|
||||
return parts;
|
||||
}
|
||||
|
||||
$: if ($page.url.search !== lastSearch) {
|
||||
$: if (videoSourcesLoaded && $page.url.search !== lastSearch) {
|
||||
lastSearch = $page.url.search;
|
||||
handleSearchParamsChange($page.url.searchParams);
|
||||
}
|
||||
@@ -304,8 +324,19 @@
|
||||
}
|
||||
])
|
||||
);
|
||||
sourceMap.clear();
|
||||
for (const source of Object.values(VIDEO_SOURCES)) {
|
||||
const sourceList = videoSources[source.type as keyof VideoSourcesResponse] as VideoSource[];
|
||||
for (const item of sourceList) {
|
||||
sourceMap.set(`${source.type}:${item.id}`, {
|
||||
type: source.type,
|
||||
name: item.name
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filters = null;
|
||||
sourceMap.clear();
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
@@ -315,6 +346,7 @@
|
||||
}
|
||||
]);
|
||||
videoSources = (await api.getVideoSources()).data;
|
||||
videoSourcesLoaded = true;
|
||||
});
|
||||
|
||||
$: totalPages = videosData ? Math.ceil(videosData.total_count / pageSize) : 0;
|
||||
@@ -435,6 +467,7 @@
|
||||
{#each videosData.videos as video (video.id)}
|
||||
<VideoCard
|
||||
{video}
|
||||
source={getVideoSource(video)}
|
||||
onReset={async (forceReset: boolean) => {
|
||||
await handleResetVideo(video.id, forceReset);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user