feat: 重置任务状态时支持 force 参数,默认不启用 (#388)
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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>
|
||||
|
||||
36
web/src/lib/components/ui/checkbox/checkbox.svelte
Normal file
36
web/src/lib/components/ui/checkbox/checkbox.svelte
Normal 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>
|
||||
6
web/src/lib/components/ui/checkbox/index.ts
Normal file
6
web/src/lib/components/ui/checkbox/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from './checkbox.svelte';
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -104,6 +104,11 @@ export interface UpdateVideoStatusResponse {
|
||||
pages: PageInfo[];
|
||||
}
|
||||
|
||||
// 重置请求类型
|
||||
export interface ResetRequest {
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
// 收藏夹相关类型
|
||||
export interface FavoriteWithSubscriptionStatus {
|
||||
title: string;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user