feat: 重置任务状态时支持 force 参数,默认不启用 (#388)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-07-11 19:01:01 +08:00
committed by GitHub
parent 267e9373f9
commit adc2e32e58
14 changed files with 185 additions and 57 deletions

View File

@@ -20,7 +20,8 @@ import type {
Config,
DashBoardResponse,
SysInfo,
TaskStatus
TaskStatus,
ResetRequest
} from './types';
import { wsManager } from './ws';
@@ -150,12 +151,12 @@ class ApiClient {
return this.get<VideoResponse>(`/videos/${id}`);
}
async resetVideo(id: number): Promise<ApiResponse<ResetVideoResponse>> {
return this.post<ResetVideoResponse>(`/videos/${id}/reset`);
async resetVideo(id: number, request: ResetRequest): Promise<ApiResponse<ResetVideoResponse>> {
return this.post<ResetVideoResponse>(`/videos/${id}/reset`, request);
}
async resetAllVideos(): Promise<ApiResponse<ResetAllVideosResponse>> {
return this.post<ResetAllVideosResponse>('/videos/reset-all');
async resetAllVideos(request: ResetRequest): Promise<ApiResponse<ResetAllVideosResponse>> {
return this.post<ResetAllVideosResponse>('/videos/reset-all', request);
}
async updateVideoStatus(
@@ -245,8 +246,8 @@ const api = {
getVideoSources: () => apiClient.getVideoSources(),
getVideos: (params?: VideosRequest) => apiClient.getVideos(params),
getVideo: (id: number) => apiClient.getVideo(id),
resetVideo: (id: number) => apiClient.resetVideo(id),
resetAllVideos: () => apiClient.resetAllVideos(),
resetVideo: (id: number, request: ResetRequest) => apiClient.resetVideo(id, request),
resetAllVideos: (request: ResetRequest) => apiClient.resetAllVideos(request),
updateVideoStatus: (id: number, request: UpdateVideoStatusRequest) =>
apiClient.updateVideoStatus(id, request),
getCreatedFavorites: () => apiClient.getCreatedFavorites(),

View File

@@ -19,7 +19,7 @@
export let onsubmit: (request: UpdateVideoStatusRequest) => void;
// 视频任务名称(与后端 VideoStatus 对应)
const videoTaskNames = ['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分P下载'];
const videoTaskNames = ['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分下载'];
// 分页任务名称(与后端 PageStatus 对应)
const pageTaskNames = ['视频封面', '视频内容', '视频信息', '视频弹幕', '视频字幕'];
@@ -169,9 +169,9 @@
<SheetHeader class="px-6 pb-2">
<SheetTitle class="text-lg">编辑状态</SheetTitle>
<SheetDescription class="text-muted-foreground space-y-1 text-sm">
<div>修改视频和分页的下载状态。可将任务重置为未开始状态,或者标记为已完成。</div>
<div class="font-medium text-rose-600">
⚠️ 已完成任务被重置为未开始,任务重新执行时会覆盖现存文件
<div>自行编辑视频和分页的下载状态。可将任意子任务状态修改为“未开始”或“已完成</div>
<div class="leading-relaxed text-orange-600">
⚠️ 仅当分页下载状态不是“已完成”时,程序才会尝试执行分页下载
</div>
</SheetDescription>
</SheetHeader>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>
<CheckboxPrimitive.Root
bind:ref
data-slot="checkbox"
class={cn(
'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className
)}
bind:checked
bind:indeterminate
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<div data-slot="checkbox-indicator" class="text-current transition-none">
{#if checked}
<CheckIcon class="size-3.5" />
{:else if indeterminate}
<MinusIcon class="size-3.5" />
{/if}
</div>
{/snippet}
</CheckboxPrimitive.Root>

View File

@@ -0,0 +1,6 @@
import Root from './checkbox.svelte';
export {
Root,
//
Root as Checkbox
};

View File

@@ -4,6 +4,8 @@
import { Button } from '$lib/components/ui/button/index.js';
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import type { VideoInfo } from '$lib/types';
import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw';
import InfoIcon from '@lucide/svelte/icons/info';
@@ -21,10 +23,12 @@
export let customSubtitle: string = ''; // 自定义副标题
export let taskNames: string[] = []; // 自定义任务名称
export let showProgress: boolean = true; // 是否显示进度信息
export let onReset: (() => Promise<void>) | null = null; // 自定义重置函数
export let onReset: ((forceReset: boolean) => Promise<void>) | null = null; // 自定义重置函数
export let resetDialogOpen = false; // 导出对话框状态,让父组件可以控制
export let resetting = false;
let forceReset = false;
function getStatusText(status: number): string {
if (status === 7) {
return '已完成';
@@ -66,7 +70,7 @@
if (taskNames.length > 0) {
return taskNames[index] || `任务${index + 1}`;
}
const defaultTaskNames = ['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分P下载'];
const defaultTaskNames = ['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分下载'];
return defaultTaskNames[index] || `任务${index + 1}`;
}
@@ -77,10 +81,11 @@
async function handleReset() {
resetting = true;
if (onReset) {
await onReset();
await onReset(forceReset);
}
resetting = false;
resetDialogOpen = false;
forceReset = false;
}
function handleViewDetail() {
@@ -202,16 +207,43 @@
<AlertDialog.Root bind:open={resetDialogOpen}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>确认重置</AlertDialog.Title>
<AlertDialog.Title>重置视频</AlertDialog.Title>
<AlertDialog.Description>
确定要重置视频 "{displayTitle}"
的下载状态吗?此操作会将所有失败状态的下载状态重置为未开始,无法撤销。
确定要重置视频 <strong>"{displayTitle}"</strong> 的下载状态吗?
<br />
此操作会将所有的失败状态重置为未开始,<span class="text-destructive font-medium"
>无法撤销</span
>
</AlertDialog.Description>
</AlertDialog.Header>
<div class="space-y-4 py-4">
<div class="rounded-lg border border-orange-200 bg-orange-50 p-3">
<div class="mb-2 flex items-center space-x-2">
<Checkbox id="force-reset-all" bind:checked={forceReset} />
<Label for="force-reset-all" class="text-sm font-medium text-orange-700"
>⚠️ 强制重置</Label
>
</div>
<p class="text-xs leading-relaxed text-orange-700">
除重置失败状态外还会检查修复任务状态的标识位 <br />
版本升级引入新任务时勾选该选项进行重置,可以允许旧视频执行新任务
</p>
</div>
</div>
<AlertDialog.Footer>
<AlertDialog.Cancel>取消</AlertDialog.Cancel>
<AlertDialog.Action onclick={handleReset} disabled={resetting}>
{resetting ? '重置中...' : '确认重置'}
<AlertDialog.Cancel
onclick={() => {
forceReset = false;
}}>取消</AlertDialog.Cancel
>
<AlertDialog.Action
onclick={handleReset}
disabled={resetting}
class={forceReset ? 'bg-orange-600 hover:bg-orange-700' : ''}
>
{resetting ? '重置中...' : forceReset ? '确认强制重置' : '确认重置'}
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>

View File

@@ -104,6 +104,11 @@ export interface UpdateVideoStatusResponse {
pages: PageInfo[];
}
// 重置请求类型
export interface ResetRequest {
force: boolean;
}
// 收藏夹相关类型
export interface FavoriteWithSubscriptionStatus {
title: string;

View File

@@ -160,12 +160,12 @@
}}
mode="detail"
showActions={false}
taskNames={['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分P下载']}
taskNames={['视频封面', '视频信息', 'UP主头像', 'UP主信息', '分下载']}
bind:resetDialogOpen
bind:resetting
onReset={async () => {
onReset={async (forceReset: boolean) => {
try {
const result = await api.resetVideo(videoData!.video.id);
const result = await api.resetVideo(videoData!.video.id, { force: forceReset });
const data = result.data;
if (data.resetted) {
videoData = {

View File

@@ -5,6 +5,8 @@
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw';
import api from '$lib/api';
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import type { VideosResponse, VideoSourcesResponse, ApiError, VideoSource } from '$lib/types';
import { onMount } from 'svelte';
import { page } from '$app/stores';
@@ -33,6 +35,8 @@
let resetAllDialogOpen = false;
let resettingAll = false;
let forceReset = false;
let videoSources: VideoSourcesResponse | null = null;
let filters: Record<string, Filter> | null = null;
@@ -91,9 +95,9 @@
loadVideos(query, pageNum, videoSource);
}
async function handleResetVideo(id: number) {
async function handleResetVideo(id: number, forceReset: boolean) {
try {
const result = await api.resetVideo(id);
const result = await api.resetVideo(id, { force: forceReset });
const data = result.data;
if (data.resetted) {
toast.success('重置成功', {
@@ -117,7 +121,7 @@
async function handleResetAllVideos() {
resettingAll = true;
try {
const result = await api.resetAllVideos();
const result = await api.resetAllVideos({ force: forceReset });
const data = result.data;
if (data.resetted) {
toast.success('重置成功', {
@@ -225,7 +229,7 @@
disabled={resettingAll || loading}
>
<RotateCcwIcon class="mr-1.5 h-3 w-3 {resettingAll ? 'animate-spin' : ''}" />
重置所有
重置全部
</Button>
</div>
</div>
@@ -242,8 +246,8 @@
{#each videosData.videos as video (video.id)}
<VideoCard
{video}
onReset={async () => {
await handleResetVideo(video.id);
onReset={async (forceReset: boolean) => {
await handleResetVideo(video.id, forceReset);
}}
/>
{/each}
@@ -264,29 +268,50 @@
</div>
{/if}
<!-- 重置所有视频确认对话框 -->
<AlertDialog.Root bind:open={resetAllDialogOpen}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>重置所有视频</AlertDialog.Title>
<AlertDialog.Title>重置全部视频</AlertDialog.Title>
<AlertDialog.Description>
此操作将重置所有视频和分页的失败状态为未下载状态,使它们在下次下载任务中重新尝试。
<br />
<strong class="text-destructive">此操作不可撤销,确定要继续吗?</strong>
确定要重置<strong>全部视频</strong>的下载状态吗?<br />
此操作会将所有的失败状态重置为未开始,<span class="text-destructive font-medium"
>无法撤销</span
>
</AlertDialog.Description>
</AlertDialog.Header>
<div class="space-y-4 py-4">
<div class="rounded-lg border border-orange-200 bg-orange-50 p-3">
<div class="mb-2 flex items-center space-x-2">
<Checkbox id="force-reset-all" bind:checked={forceReset} />
<Label for="force-reset-all" class="text-sm font-medium text-orange-700"
>⚠️ 强制重置</Label
>
</div>
<p class="text-xs leading-relaxed text-orange-700">
除重置失败状态外还会检查修复任务状态的标识位 <br />
版本升级引入新任务时勾选该选项进行重置,可以允许旧视频执行新任务
</p>
</div>
</div>
<AlertDialog.Footer>
<AlertDialog.Cancel disabled={resettingAll}>取消</AlertDialog.Cancel>
<AlertDialog.Cancel
disabled={resettingAll}
onclick={() => {
forceReset = false;
}}>取消</AlertDialog.Cancel
>
<AlertDialog.Action
onclick={handleResetAllVideos}
disabled={resettingAll}
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
class={forceReset ? 'bg-orange-600 hover:bg-orange-700' : ''}
>
{#if resettingAll}
<RotateCcwIcon class="mr-2 h-4 w-4 animate-spin" />
重置中...
{:else}
确认重置
{forceReset ? '确认强制重置' : '确认重置'}
{/if}
</AlertDialog.Action>
</AlertDialog.Footer>