From 75fcca7ce349fd0002a1289d90dfd8a71f24a739 Mon Sep 17 00:00:00 2001 From: zwzzrl <66906196+zwzzrl@users.noreply.github.com> Date: Tue, 10 Mar 2026 22:21:18 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ModalInput.tsx | 2 + src/lang/en/tasks.json | 4 +- .../home/offlinedownload/TaskProgress.tsx | 163 ++++++++++++ src/pages/home/offlinedownload/task.ts | 64 +++++ src/pages/home/toolbar/OfflineDownload.tsx | 235 ++++++++++++------ 5 files changed, 387 insertions(+), 81 deletions(-) create mode 100644 src/pages/home/offlinedownload/TaskProgress.tsx create mode 100644 src/pages/home/offlinedownload/task.ts diff --git a/src/components/ModalInput.tsx b/src/components/ModalInput.tsx index fd6ede9b4..6312d38ba 100644 --- a/src/components/ModalInput.tsx +++ b/src/components/ModalInput.tsx @@ -25,6 +25,7 @@ export type ModalInputProps = { title: string isRenamingFile?: boolean onSubmit?: (text: string) => void + onSubmitWithValue?: (text: string,setValue: (value: string) => void) => void type?: string defaultValue?: string loading?: boolean @@ -92,6 +93,7 @@ export const ModalInput = (props: ModalInputProps) => { } } props.onSubmit?.(value()) + props.onSubmitWithValue?.(value(),setValue) } const handleInput = (newValue: string) => { diff --git a/src/lang/en/tasks.json b/src/lang/en/tasks.json index a96897f90..e48e703f4 100644 --- a/src/lang/en/tasks.json +++ b/src/lang/en/tasks.json @@ -50,7 +50,9 @@ "path": "Destination Path", "transfer_src": "Source Path", "transfer_src_local": "Source Path (Local)", - "transfer_dst": "Destination Path" + "transfer_dst": "Destination Path", + "list_title": "Offline Download Tasks", + "no_tasks": "No ongoing tasks" }, "decompress": { "src": "Source Path", diff --git a/src/pages/home/offlinedownload/TaskProgress.tsx b/src/pages/home/offlinedownload/TaskProgress.tsx new file mode 100644 index 000000000..72bec1b1f --- /dev/null +++ b/src/pages/home/offlinedownload/TaskProgress.tsx @@ -0,0 +1,163 @@ +// src/pages/home/toolbar/TaskProgress.tsx +import { + VStack, + HStack, + Text, + Badge, + Progress, + ProgressIndicator, + Box, +} from "@hope-ui/solid"; +import { TaskInfo } from "./task"; +import { getFileSize } from "~/utils"; +import { Show, createSignal, JSX } from "solid-js"; +import { useT } from "~/hooks"; +import { me } from "~/store"; + +// 复制 helper.tsx 中的 getPath 函数(用于生成路径链接) +const getPath = ( + device: string, + path: string, + asLink: boolean = true, +): JSX.Element => { + const fullPath = (device === "/" ? "" : device) + path; + const prefix = me().base_path === "/" ? "" : me().base_path; + const accessible = fullPath.startsWith(prefix); + const [underline, setUnderline] = createSignal(false); + return accessible && asLink ? ( + setUnderline(true)} + onMouseOut={() => setUnderline(false)} + href={fullPath.slice(prefix.length)} + > + {fullPath} + + ) : ( +

{fullPath}

+ ); +}; + +// 解析任务名称,返回文件名和路径信息 +const parseTaskName = (name: string) => { + // download 类型:download 文件名 to (路径) + let match = name.match(/^download (.+) to \((.+)\)$/); + if (match) { + return { + type: "download" as const, + fileName: match[1], + path: match[2], + }; + } + // transfer/upload 类型:transfer [设备](路径) to [目标设备](目标路径) 或 upload [文件名](URL) to [目标设备](目标路径) + match = name.match( + /^(transfer|upload) \[(.*?)\]\((.*?)\) to \[(.*?)\]\((.*?)\)$/, + ); + if (match) { + const type = match[1] as "transfer" | "upload"; + const bracketContent = match[2]; // 方括号内:transfer 为设备,upload 为文件名 + const urlOrPath = match[3]; // 圆括号内:transfer 为路径,upload 为 URL + const dstDevice = match[4]; + const dstPath = match[5]; + + if (type === "transfer") { + // 从路径中提取文件名(最后一段,去除参数) + const fileName = urlOrPath.split("/").pop()?.split("?")[0] || "未知文件"; + return { + type, + fileName, + srcDevice: bracketContent, + srcPath: urlOrPath, + dstDevice, + dstPath, + }; + } else { + // upload 类型:文件名直接取自方括号 + return { + type, + fileName: bracketContent, + srcDevice: "", + srcPath: urlOrPath, + dstDevice, + dstPath, + }; + } + } + return null; +}; + +export const StatusColor = { + 0: "neutral", + 1: "info", + 2: "warning", + 3: "danger", + 4: "success", + 5: "info", +} as const; + +export const TaskItem = (props: TaskInfo) => { + const t = useT(); + const parsed = parseTaskName(props.name); + + return ( + + {parsed ? ( + <> + {parsed.fileName} + {parsed.type === "download" && parsed.path && ( + + {t("tasks.attr.offline_download.path")}:{" "} + {getPath("", parsed.path)} + + )} + {parsed.type === "transfer" && parsed.dstPath && ( + + {t("tasks.attr.offline_download.transfer_dst")}:{" "} + {getPath(parsed.dstDevice, parsed.dstPath)} + + )} + {parsed.type === "upload" && parsed.dstPath && ( + + {t("tasks.attr.offline_download.path")}:{" "} + {getPath(parsed.dstDevice, parsed.dstPath)} + + )} + + ) : ( + {props.name} + )} + + + {t("tasks.state." + props.state)} + + {getFileSize(props.total_bytes)} + + + + + + + {props.error} + + + + ); +}; + +export default TaskItem; diff --git a/src/pages/home/offlinedownload/task.ts b/src/pages/home/offlinedownload/task.ts new file mode 100644 index 000000000..6892330a3 --- /dev/null +++ b/src/pages/home/offlinedownload/task.ts @@ -0,0 +1,64 @@ +// src/store/task.ts +import { createSignal } from "solid-js"; +import { createStore } from "solid-js/store"; +import { r } from "~/utils"; + +export interface TaskInfo { + id: string; + name: string; + creator: string; + creator_role: number; + state: number; + status: string; + progress: number; + start_time: string | null; + end_time: string | null; + total_bytes: number; + error: string; +} + +const [tasks, setTasks] = createStore([]); +const [loading, setLoading] = createSignal(false); + +export const fetchTasks = async (showLoading = true) => { + if (showLoading) setLoading(true); + try { + const [respOld, respNew] = await Promise.all([ + r.get("/task/offline_download/undone").catch(() => ({ data: [] })), + r + .get("/task/offline_download_transfer/undone") + .catch(() => ({ data: [] })), + ]); + + const taskMap = new Map(); + + const oldTasks = respOld.data || []; + oldTasks.forEach((item: any) => { + if (!item.state) item.state = 0; + taskMap.set(item.id, item); + }); + + const newTasks = respNew.data || []; + newTasks.forEach((item: any) => { + taskMap.set(item.id, item); + }); + + const mergedTasks = Array.from(taskMap.values()); + + // 按 start_time 降序排序(最新的在前),null 值视为最旧,排在后面 + mergedTasks.sort((a, b) => { + if (!a.start_time && !b.start_time) return 0; + if (!a.start_time) return 1; + if (!b.start_time) return -1; + return b.start_time.localeCompare(a.start_time); // 字符串降序比较 + }); + + setTasks(mergedTasks); + } catch (e) { + console.error("Failed to fetch tasks:", e); + } finally { + if (showLoading) setLoading(false); + } +}; + +export const useTasks = () => ({ tasks, loading, fetchTasks }); diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index 7eaa6793e..a206ccc84 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -1,17 +1,35 @@ -import { Box, createDisclosure } from "@hope-ui/solid" -import { ModalInput, SelectWrapper } from "~/components" -import { useFetch, useRouter, useT } from "~/hooks" +import { + Box, + createDisclosure, + HStack, + Heading, + Button, + VStack, + Text, +} from "@hope-ui/solid"; +import { ModalInput, SelectWrapper } from "~/components"; +import { useFetch, useRouter, useT } from "~/hooks"; import { offlineDownload, bus, handleRespWithNotifySuccess, r, handleResp, -} from "~/utils" -import { createSignal, onCleanup, onMount } from "solid-js" -import { PResp } from "~/types" -import bencode from "bencode" -import crypto from "crypto-js" +} from "~/utils"; +import { + createSignal, + onCleanup, + onMount, + createEffect, + For, + Show, +} from "solid-js"; +import { PResp } from "~/types"; +import bencode from "bencode"; +import crypto from "crypto-js"; +import { useTasks } from "../offlinedownload/task"; +import TaskItem from "../offlinedownload/TaskProgress"; +import { FullLoading } from "~/components/FullLoading"; const deletePolicies = [ "upload_download_stream", @@ -19,99 +37,139 @@ const deletePolicies = [ "delete_on_upload_failed", "delete_never", "delete_always", -] as const +] as const; -type DeletePolicy = (typeof deletePolicies)[number] +type DeletePolicy = (typeof deletePolicies)[number]; function utf8Decode(data: Uint8Array): string { - return crypto.enc.Utf8.stringify(crypto.lib.WordArray.create(data)) + return crypto.enc.Utf8.stringify(crypto.lib.WordArray.create(data)); } function toMagnetUrl(torrentBuffer: Uint8Array) { - const data = bencode.decode(torrentBuffer as any) - const infoEncode = bencode.encode(data.info) as unknown as Uint8Array + const data = bencode.decode(torrentBuffer as any); + const infoEncode = bencode.encode(data.info) as unknown as Uint8Array; const infoHash = crypto .SHA1(crypto.lib.WordArray.create(infoEncode)) - .toString() - let params = {} as any + .toString(); + let params = {} as any; if (Number.isInteger(data?.info?.length)) { - params.xl = data.info.length + params.xl = data.info.length; } if (data.info.name) { - params.dn = utf8Decode(data.info.name) + params.dn = utf8Decode(data.info.name); } if (data.announce) { - params.tr = utf8Decode(data.announce) + params.tr = utf8Decode(data.announce); } return `magnet:?xt=urn:btih:${infoHash}&${new URLSearchParams( params, - ).toString()}` + ).toString()}`; } export const OfflineDownload = () => { - const t = useT() - const [tools, setTools] = createSignal([] as string[]) + const t = useT(); + const [tools, setTools] = createSignal([] as string[]); const [toolsLoading, reqTool] = useFetch((): PResp => { - return r.get("/public/offline_download_tools") - }) - const [tool, setTool] = createSignal("") + return r.get("/public/offline_download_tools"); + }); + const [tool, setTool] = createSignal(""); const [deletePolicy, setDeletePolicy] = createSignal( "upload_download_stream", - ) + ); onMount(async () => { - const resp = await reqTool() + const resp = await reqTool(); handleResp(resp, (data) => { - setTools(data) - setTool(data[0]) - }) - }) - - const { isOpen, onOpen, onClose } = createDisclosure() - const [loading, ok] = useFetch(offlineDownload) - const { pathname } = useRouter() + setTools(data); + setTool(data[0]); + }); + }); + + const { isOpen, onOpen, onClose } = createDisclosure(); + const [loading, ok] = useFetch(offlineDownload); + const { pathname } = useRouter(); + + // 监听工具栏事件 const handler = (name: string) => { if (name === "offline_download") { - onOpen() + onOpen(); + setShowTasks(true); } - } - bus.on("tool", handler) + }; + bus.on("tool", handler); onCleanup(() => { - bus.off("tool", handler) - }) + bus.off("tool", handler); + }); + + const { tasks, loading: tasksLoading, fetchTasks } = useTasks(); + const [showTasks, setShowTasks] = createSignal(false); + + const handleSubmit = async ( + urls: string, + setValue: (value: string) => void, + ) => { + const resp = await ok(pathname(), urls.split("\n"), tool(), deletePolicy()); + handleRespWithNotifySuccess(resp, () => { + fetchTasks(false); // 不显示 loading + setValue(""); + }); + }; + + // 定时刷新任务进度(仅当显示任务列表时) + let timer: number | undefined; + createEffect(() => { + if (showTasks()) { + // 打开时立即获取一次最新数据,不显示 loading + fetchTasks(false); + // 启动定时器,每隔3秒刷新(也不显示 loading) + timer = setInterval(() => fetchTasks(false), 3000); + } else { + clearInterval(timer); + timer = undefined; + } + }); - // convert torrent file to magnet link + onCleanup(() => { + clearInterval(timer); + }); + // 拖拽种子文件转换为磁力链接 const handleTorrentFileDrop = async ( e: DragEvent, setValue: (value: string) => void, ) => { - e.preventDefault() + e.preventDefault(); if (e.dataTransfer?.files.length) { - const values = [] + const values = []; for (const file of e.dataTransfer.files) { if (file.name.toLowerCase().endsWith(".torrent")) { try { - const buffer = await file.arrayBuffer() - values.push(toMagnetUrl(new Uint8Array(buffer))) + const buffer = await file.arrayBuffer(); + values.push(toMagnetUrl(new Uint8Array(buffer))); } catch (err) { - console.error("Failed to convert torrent file to magnet link:", err) + console.error( + "Failed to convert torrent file to magnet link:", + err, + ); } } } if (values.length) { - setValue(values.join("\n")) + setValue(values.join("\n")); } } - } + }; return ( { + onClose(); + setShowTasks(false); + }} loading={toolsLoading() || loading()} tips={t("home.toolbar.offline_download-tips")} onDrop={handleTorrentFileDrop} @@ -124,47 +182,64 @@ export const OfflineDownload = () => { v !== "SimpleHttp" && deletePolicy() === "upload_download_stream" ) { - setDeletePolicy("delete_on_upload_succeed") + setDeletePolicy("delete_on_upload_succeed"); } - setTool(v) + setTool(v); }} options={tools().map((tool) => { - return { value: tool, label: tool } + return { value: tool, label: tool }; })} /> } bottomSlot={ - - setDeletePolicy(v as DeletePolicy)} - options={deletePolicies - .filter((policy) => - policy == "upload_download_stream" - ? tool() === "SimpleHttp" - : true, - ) - .map((policy) => { - return { + + + setDeletePolicy(v as DeletePolicy)} + options={deletePolicies + .filter((policy) => + policy === "upload_download_stream" + ? tool() === "SimpleHttp" + : true, + ) + .map((policy) => ({ value: policy, label: t(`home.toolbar.delete_policy.${policy}`), - } - })} - /> - + }))} + /> + + + {/* 任务列表 */} + + + + + {t("tasks.attr.offline_download.list_title")} + + + }> + + {(task) => } + + + {t("tasks.attr.offline_download.no_tasks")} + + + + + + + } - onSubmit={async (urls) => { - const resp = await ok( - pathname(), - urls.split("\n"), - tool(), - deletePolicy(), - ) - handleRespWithNotifySuccess(resp, () => { - onClose() - }) - }} + onSubmitWithValue={handleSubmit} /> - ) -} + ); +}; From a0395eeb9c7fe70bb842a0594a1b12893f7270c2 Mon Sep 17 00:00:00 2001 From: zwzzrl <66906196+zwzzrl@users.noreply.github.com> Date: Tue, 10 Mar 2026 22:30:42 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8=E4=BA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/offlinedownload/TaskProgress.tsx | 77 ++++---- src/pages/home/offlinedownload/task.ts | 76 ++++---- src/pages/home/toolbar/OfflineDownload.tsx | 165 +++++++++--------- 3 files changed, 157 insertions(+), 161 deletions(-) diff --git a/src/pages/home/offlinedownload/TaskProgress.tsx b/src/pages/home/offlinedownload/TaskProgress.tsx index 72bec1b1f..173d98318 100644 --- a/src/pages/home/offlinedownload/TaskProgress.tsx +++ b/src/pages/home/offlinedownload/TaskProgress.tsx @@ -5,25 +5,24 @@ import { Text, Badge, Progress, - ProgressIndicator, - Box, -} from "@hope-ui/solid"; -import { TaskInfo } from "./task"; -import { getFileSize } from "~/utils"; -import { Show, createSignal, JSX } from "solid-js"; -import { useT } from "~/hooks"; -import { me } from "~/store"; + ProgressIndicator +} from "@hope-ui/solid" +import { TaskInfo } from "./task" +import { getFileSize } from "~/utils" +import { Show, createSignal, JSX } from "solid-js" +import { useT } from "~/hooks" +import { me } from "~/store" // 复制 helper.tsx 中的 getPath 函数(用于生成路径链接) const getPath = ( device: string, path: string, - asLink: boolean = true, + asLink: boolean = true ): JSX.Element => { - const fullPath = (device === "/" ? "" : device) + path; - const prefix = me().base_path === "/" ? "" : me().base_path; - const accessible = fullPath.startsWith(prefix); - const [underline, setUnderline] = createSignal(false); + const fullPath = (device === "/" ? "" : device) + path + const prefix = me().base_path === "/" ? "" : me().base_path + const accessible = fullPath.startsWith(prefix) + const [underline, setUnderline] = createSignal(false) return accessible && asLink ? ( ) : (

{fullPath}

- ); -}; + ) +} // 解析任务名称,返回文件名和路径信息 const parseTaskName = (name: string) => { // download 类型:download 文件名 to (路径) - let match = name.match(/^download (.+) to \((.+)\)$/); + let match = name.match(/^download (.+) to \((.+)\)$/) if (match) { return { type: "download" as const, fileName: match[1], - path: match[2], - }; + path: match[2] + } } // transfer/upload 类型:transfer [设备](路径) to [目标设备](目标路径) 或 upload [文件名](URL) to [目标设备](目标路径) match = name.match( - /^(transfer|upload) \[(.*?)\]\((.*?)\) to \[(.*?)\]\((.*?)\)$/, - ); + /^(transfer|upload) \[(.*?)\]\((.*?)\) to \[(.*?)\]\((.*?)\)$/ + ) if (match) { - const type = match[1] as "transfer" | "upload"; - const bracketContent = match[2]; // 方括号内:transfer 为设备,upload 为文件名 - const urlOrPath = match[3]; // 圆括号内:transfer 为路径,upload 为 URL - const dstDevice = match[4]; - const dstPath = match[5]; + const type = match[1] as "transfer" | "upload" + const bracketContent = match[2] // 方括号内:transfer 为设备,upload 为文件名 + const urlOrPath = match[3] // 圆括号内:transfer 为路径,upload 为 URL + const dstDevice = match[4] + const dstPath = match[5] if (type === "transfer") { // 从路径中提取文件名(最后一段,去除参数) - const fileName = urlOrPath.split("/").pop()?.split("?")[0] || "未知文件"; + const fileName = urlOrPath.split("/").pop()?.split("?")[0] || "未知文件" return { type, fileName, srcDevice: bracketContent, srcPath: urlOrPath, dstDevice, - dstPath, - }; + dstPath + } } else { // upload 类型:文件名直接取自方括号 return { @@ -79,12 +78,12 @@ const parseTaskName = (name: string) => { srcDevice: "", srcPath: urlOrPath, dstDevice, - dstPath, - }; + dstPath + } } } - return null; -}; + return null +} export const StatusColor = { 0: "neutral", @@ -92,12 +91,12 @@ export const StatusColor = { 2: "warning", 3: "danger", 4: "success", - 5: "info", -} as const; + 5: "info" +} as const export const TaskItem = (props: TaskInfo) => { - const t = useT(); - const parsed = parseTaskName(props.name); + const t = useT() + const parsed = parseTaskName(props.name) return ( { - ); -}; + ) +} -export default TaskItem; +export default TaskItem diff --git a/src/pages/home/offlinedownload/task.ts b/src/pages/home/offlinedownload/task.ts index 6892330a3..6fe0f7d26 100644 --- a/src/pages/home/offlinedownload/task.ts +++ b/src/pages/home/offlinedownload/task.ts @@ -1,64 +1,64 @@ // src/store/task.ts -import { createSignal } from "solid-js"; -import { createStore } from "solid-js/store"; -import { r } from "~/utils"; +import { createSignal } from "solid-js" +import { createStore } from "solid-js/store" +import { r } from "~/utils" export interface TaskInfo { - id: string; - name: string; - creator: string; - creator_role: number; - state: number; - status: string; - progress: number; - start_time: string | null; - end_time: string | null; - total_bytes: number; - error: string; + id: string + name: string + creator: string + creator_role: number + state: number + status: string + progress: number + start_time: string | null + end_time: string | null + total_bytes: number + error: string } -const [tasks, setTasks] = createStore([]); -const [loading, setLoading] = createSignal(false); +const [tasks, setTasks] = createStore([]) +const [loading, setLoading] = createSignal(false) export const fetchTasks = async (showLoading = true) => { - if (showLoading) setLoading(true); + if (showLoading) setLoading(true) try { const [respOld, respNew] = await Promise.all([ r.get("/task/offline_download/undone").catch(() => ({ data: [] })), r .get("/task/offline_download_transfer/undone") - .catch(() => ({ data: [] })), - ]); + .catch(() => ({ data: [] })) + ]) - const taskMap = new Map(); + const taskMap = new Map() - const oldTasks = respOld.data || []; + const oldTasks = respOld.data || [] oldTasks.forEach((item: any) => { - if (!item.state) item.state = 0; - taskMap.set(item.id, item); - }); + if (!item.state) item.state = 0 + taskMap.set(item.id, item) + }) - const newTasks = respNew.data || []; + const newTasks = respNew.data || [] newTasks.forEach((item: any) => { - taskMap.set(item.id, item); - }); + taskMap.set(item.id, item) + }) - const mergedTasks = Array.from(taskMap.values()); + const mergedTasks = Array.from(taskMap.values()) // 按 start_time 降序排序(最新的在前),null 值视为最旧,排在后面 mergedTasks.sort((a, b) => { - if (!a.start_time && !b.start_time) return 0; - if (!a.start_time) return 1; - if (!b.start_time) return -1; - return b.start_time.localeCompare(a.start_time); // 字符串降序比较 - }); + if (!a.start_time && !b.start_time) return 0 + if (!a.start_time) return 1 + if (!b.start_time) return -1 + return b.start_time.localeCompare(a.start_time) // 字符串降序比较 + }) - setTasks(mergedTasks); + setTasks(mergedTasks) } catch (e) { - console.error("Failed to fetch tasks:", e); + console.error("Failed to fetch tasks:", e) } finally { - if (showLoading) setLoading(false); + if (showLoading) setLoading(false) } -}; +} -export const useTasks = () => ({ tasks, loading, fetchTasks }); +export const useTasks = () => ({ tasks, loading, fetchTasks }) diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index a206ccc84..5fb10ae24 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -5,161 +5,158 @@ import { Heading, Button, VStack, - Text, -} from "@hope-ui/solid"; -import { ModalInput, SelectWrapper } from "~/components"; -import { useFetch, useRouter, useT } from "~/hooks"; + Text +} from "@hope-ui/solid" +import { ModalInput, SelectWrapper } from "~/components" +import { useFetch, useRouter, useT } from "~/hooks" import { offlineDownload, bus, handleRespWithNotifySuccess, r, - handleResp, -} from "~/utils"; + handleResp +} from "~/utils" import { createSignal, onCleanup, onMount, createEffect, For, - Show, -} from "solid-js"; -import { PResp } from "~/types"; -import bencode from "bencode"; -import crypto from "crypto-js"; -import { useTasks } from "../offlinedownload/task"; -import TaskItem from "../offlinedownload/TaskProgress"; -import { FullLoading } from "~/components/FullLoading"; + Show +} from "solid-js" +import { PResp } from "~/types" +import bencode from "bencode" +import crypto from "crypto-js" +import { useTasks } from "../offlinedownload/task" +import TaskItem from "../offlinedownload/TaskProgress" +import { FullLoading } from "~/components/FullLoading" const deletePolicies = [ "upload_download_stream", "delete_on_upload_succeed", "delete_on_upload_failed", "delete_never", - "delete_always", -] as const; + "delete_always" +] as const -type DeletePolicy = (typeof deletePolicies)[number]; +type DeletePolicy = (typeof deletePolicies)[number] function utf8Decode(data: Uint8Array): string { - return crypto.enc.Utf8.stringify(crypto.lib.WordArray.create(data)); + return crypto.enc.Utf8.stringify(crypto.lib.WordArray.create(data)) } function toMagnetUrl(torrentBuffer: Uint8Array) { - const data = bencode.decode(torrentBuffer as any); - const infoEncode = bencode.encode(data.info) as unknown as Uint8Array; + const data = bencode.decode(torrentBuffer as any) + const infoEncode = bencode.encode(data.info) as unknown as Uint8Array const infoHash = crypto .SHA1(crypto.lib.WordArray.create(infoEncode)) - .toString(); - let params = {} as any; + .toString() + let params = {} as any if (Number.isInteger(data?.info?.length)) { - params.xl = data.info.length; + params.xl = data.info.length } if (data.info.name) { - params.dn = utf8Decode(data.info.name); + params.dn = utf8Decode(data.info.name) } if (data.announce) { - params.tr = utf8Decode(data.announce); + params.tr = utf8Decode(data.announce) } return `magnet:?xt=urn:btih:${infoHash}&${new URLSearchParams( - params, - ).toString()}`; + params + ).toString()}` } export const OfflineDownload = () => { - const t = useT(); - const [tools, setTools] = createSignal([] as string[]); + const t = useT() + const [tools, setTools] = createSignal([] as string[]) const [toolsLoading, reqTool] = useFetch((): PResp => { - return r.get("/public/offline_download_tools"); - }); - const [tool, setTool] = createSignal(""); + return r.get("/public/offline_download_tools") + }) + const [tool, setTool] = createSignal("") const [deletePolicy, setDeletePolicy] = createSignal( - "upload_download_stream", - ); + "upload_download_stream" + ) onMount(async () => { - const resp = await reqTool(); + const resp = await reqTool() handleResp(resp, (data) => { - setTools(data); - setTool(data[0]); - }); - }); + setTools(data) + setTool(data[0]) + }) + }) - const { isOpen, onOpen, onClose } = createDisclosure(); - const [loading, ok] = useFetch(offlineDownload); - const { pathname } = useRouter(); + const { isOpen, onOpen, onClose } = createDisclosure() + const [loading, ok] = useFetch(offlineDownload) + const { pathname } = useRouter() // 监听工具栏事件 const handler = (name: string) => { if (name === "offline_download") { - onOpen(); - setShowTasks(true); + onOpen() + setShowTasks(true) } - }; - bus.on("tool", handler); + } + bus.on("tool", handler) onCleanup(() => { - bus.off("tool", handler); - }); + bus.off("tool", handler) + }) - const { tasks, loading: tasksLoading, fetchTasks } = useTasks(); - const [showTasks, setShowTasks] = createSignal(false); + const { tasks, loading: tasksLoading, fetchTasks } = useTasks() + const [showTasks, setShowTasks] = createSignal(false) const handleSubmit = async ( urls: string, - setValue: (value: string) => void, + setValue: (value: string) => void ) => { - const resp = await ok(pathname(), urls.split("\n"), tool(), deletePolicy()); + const resp = await ok(pathname(), urls.split("\n"), tool(), deletePolicy()) handleRespWithNotifySuccess(resp, () => { - fetchTasks(false); // 不显示 loading - setValue(""); - }); - }; + fetchTasks(false) // 不显示 loading + setValue("") + }) + } // 定时刷新任务进度(仅当显示任务列表时) - let timer: number | undefined; + let timer: number | undefined createEffect(() => { if (showTasks()) { // 打开时立即获取一次最新数据,不显示 loading - fetchTasks(false); + fetchTasks(false) // 启动定时器,每隔3秒刷新(也不显示 loading) - timer = setInterval(() => fetchTasks(false), 3000); + timer = setInterval(() => fetchTasks(false), 3000) } else { - clearInterval(timer); - timer = undefined; + clearInterval(timer) + timer = undefined } - }); + }) onCleanup(() => { - clearInterval(timer); - }); + clearInterval(timer) + }) // 拖拽种子文件转换为磁力链接 const handleTorrentFileDrop = async ( e: DragEvent, - setValue: (value: string) => void, + setValue: (value: string) => void ) => { - e.preventDefault(); + e.preventDefault() if (e.dataTransfer?.files.length) { - const values = []; + const values = [] for (const file of e.dataTransfer.files) { if (file.name.toLowerCase().endsWith(".torrent")) { try { - const buffer = await file.arrayBuffer(); - values.push(toMagnetUrl(new Uint8Array(buffer))); + const buffer = await file.arrayBuffer() + values.push(toMagnetUrl(new Uint8Array(buffer))) } catch (err) { - console.error( - "Failed to convert torrent file to magnet link:", - err, - ); + console.error("Failed to convert torrent file to magnet link:", err) } } } if (values.length) { - setValue(values.join("\n")); + setValue(values.join("\n")) } } - }; + } return ( { type="text" opened={isOpen()} onClose={() => { - onClose(); - setShowTasks(false); + onClose() + setShowTasks(false) }} loading={toolsLoading() || loading()} tips={t("home.toolbar.offline_download-tips")} @@ -182,12 +179,12 @@ export const OfflineDownload = () => { v !== "SimpleHttp" && deletePolicy() === "upload_download_stream" ) { - setDeletePolicy("delete_on_upload_succeed"); + setDeletePolicy("delete_on_upload_succeed") } - setTool(v); + setTool(v) }} options={tools().map((tool) => { - return { value: tool, label: tool }; + return { value: tool, label: tool } })} /> @@ -202,11 +199,11 @@ export const OfflineDownload = () => { .filter((policy) => policy === "upload_download_stream" ? tool() === "SimpleHttp" - : true, + : true ) .map((policy) => ({ value: policy, - label: t(`home.toolbar.delete_policy.${policy}`), + label: t(`home.toolbar.delete_policy.${policy}`) }))} /> @@ -241,5 +238,5 @@ export const OfflineDownload = () => { } onSubmitWithValue={handleSubmit} /> - ); -}; + ) +} From 94c1b8aa359172a5a524c35a22b05051cdb1f80d Mon Sep 17 00:00:00 2001 From: zwzzrl <66906196+zwzzrl@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:05:45 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E8=A7=A6=E5=8F=91Actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tarytoActions.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tarytoActions.txt diff --git a/tarytoActions.txt b/tarytoActions.txt new file mode 100644 index 000000000..e69de29bb From f35d7522fed8fb65423f83e07619cbe6a467cbbd Mon Sep 17 00:00:00 2001 From: zwzzrl <66906196+zwzzrl@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:09:08 +0800 Subject: [PATCH 04/12] =?UTF-8?q?Revert=20"=E8=A7=A6=E5=8F=91Actions"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 94c1b8aa359172a5a524c35a22b05051cdb1f80d. --- tarytoActions.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tarytoActions.txt diff --git a/tarytoActions.txt b/tarytoActions.txt deleted file mode 100644 index e69de29bb..000000000 From 0088ad9291e66b32dc89d4da908ae05e1da62d16 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 11 Mar 2026 10:00:00 +0800 Subject: [PATCH 05/12] pnpm format Signed-off-by: MadDogOwner --- src/components/ModalInput.tsx | 4 ++-- .../home/offlinedownload/TaskProgress.tsx | 14 ++++++------- src/pages/home/offlinedownload/task.ts | 2 +- src/pages/home/toolbar/OfflineDownload.tsx | 20 +++++++++---------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/ModalInput.tsx b/src/components/ModalInput.tsx index 6312d38ba..7365034a1 100644 --- a/src/components/ModalInput.tsx +++ b/src/components/ModalInput.tsx @@ -25,7 +25,7 @@ export type ModalInputProps = { title: string isRenamingFile?: boolean onSubmit?: (text: string) => void - onSubmitWithValue?: (text: string,setValue: (value: string) => void) => void + onSubmitWithValue?: (text: string, setValue: (value: string) => void) => void type?: string defaultValue?: string loading?: boolean @@ -93,7 +93,7 @@ export const ModalInput = (props: ModalInputProps) => { } } props.onSubmit?.(value()) - props.onSubmitWithValue?.(value(),setValue) + props.onSubmitWithValue?.(value(), setValue) } const handleInput = (newValue: string) => { diff --git a/src/pages/home/offlinedownload/TaskProgress.tsx b/src/pages/home/offlinedownload/TaskProgress.tsx index 173d98318..db8b699db 100644 --- a/src/pages/home/offlinedownload/TaskProgress.tsx +++ b/src/pages/home/offlinedownload/TaskProgress.tsx @@ -5,7 +5,7 @@ import { Text, Badge, Progress, - ProgressIndicator + ProgressIndicator, } from "@hope-ui/solid" import { TaskInfo } from "./task" import { getFileSize } from "~/utils" @@ -17,7 +17,7 @@ import { me } from "~/store" const getPath = ( device: string, path: string, - asLink: boolean = true + asLink: boolean = true, ): JSX.Element => { const fullPath = (device === "/" ? "" : device) + path const prefix = me().base_path === "/" ? "" : me().base_path @@ -45,12 +45,12 @@ const parseTaskName = (name: string) => { return { type: "download" as const, fileName: match[1], - path: match[2] + path: match[2], } } // transfer/upload 类型:transfer [设备](路径) to [目标设备](目标路径) 或 upload [文件名](URL) to [目标设备](目标路径) match = name.match( - /^(transfer|upload) \[(.*?)\]\((.*?)\) to \[(.*?)\]\((.*?)\)$/ + /^(transfer|upload) \[(.*?)\]\((.*?)\) to \[(.*?)\]\((.*?)\)$/, ) if (match) { const type = match[1] as "transfer" | "upload" @@ -68,7 +68,7 @@ const parseTaskName = (name: string) => { srcDevice: bracketContent, srcPath: urlOrPath, dstDevice, - dstPath + dstPath, } } else { // upload 类型:文件名直接取自方括号 @@ -78,7 +78,7 @@ const parseTaskName = (name: string) => { srcDevice: "", srcPath: urlOrPath, dstDevice, - dstPath + dstPath, } } } @@ -91,7 +91,7 @@ export const StatusColor = { 2: "warning", 3: "danger", 4: "success", - 5: "info" + 5: "info", } as const export const TaskItem = (props: TaskInfo) => { diff --git a/src/pages/home/offlinedownload/task.ts b/src/pages/home/offlinedownload/task.ts index 6fe0f7d26..294422e7b 100644 --- a/src/pages/home/offlinedownload/task.ts +++ b/src/pages/home/offlinedownload/task.ts @@ -27,7 +27,7 @@ export const fetchTasks = async (showLoading = true) => { r.get("/task/offline_download/undone").catch(() => ({ data: [] })), r .get("/task/offline_download_transfer/undone") - .catch(() => ({ data: [] })) + .catch(() => ({ data: [] })), ]) const taskMap = new Map() diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index 5fb10ae24..dc08b3083 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -5,7 +5,7 @@ import { Heading, Button, VStack, - Text + Text, } from "@hope-ui/solid" import { ModalInput, SelectWrapper } from "~/components" import { useFetch, useRouter, useT } from "~/hooks" @@ -14,7 +14,7 @@ import { bus, handleRespWithNotifySuccess, r, - handleResp + handleResp, } from "~/utils" import { createSignal, @@ -22,7 +22,7 @@ import { onMount, createEffect, For, - Show + Show, } from "solid-js" import { PResp } from "~/types" import bencode from "bencode" @@ -36,7 +36,7 @@ const deletePolicies = [ "delete_on_upload_succeed", "delete_on_upload_failed", "delete_never", - "delete_always" + "delete_always", ] as const type DeletePolicy = (typeof deletePolicies)[number] @@ -65,7 +65,7 @@ function toMagnetUrl(torrentBuffer: Uint8Array) { } return `magnet:?xt=urn:btih:${infoHash}&${new URLSearchParams( - params + params, ).toString()}` } @@ -77,7 +77,7 @@ export const OfflineDownload = () => { }) const [tool, setTool] = createSignal("") const [deletePolicy, setDeletePolicy] = createSignal( - "upload_download_stream" + "upload_download_stream", ) onMount(async () => { const resp = await reqTool() @@ -108,7 +108,7 @@ export const OfflineDownload = () => { const handleSubmit = async ( urls: string, - setValue: (value: string) => void + setValue: (value: string) => void, ) => { const resp = await ok(pathname(), urls.split("\n"), tool(), deletePolicy()) handleRespWithNotifySuccess(resp, () => { @@ -137,7 +137,7 @@ export const OfflineDownload = () => { // 拖拽种子文件转换为磁力链接 const handleTorrentFileDrop = async ( e: DragEvent, - setValue: (value: string) => void + setValue: (value: string) => void, ) => { e.preventDefault() if (e.dataTransfer?.files.length) { @@ -199,11 +199,11 @@ export const OfflineDownload = () => { .filter((policy) => policy === "upload_download_stream" ? tool() === "SimpleHttp" - : true + : true, ) .map((policy) => ({ value: policy, - label: t(`home.toolbar.delete_policy.${policy}`) + label: t(`home.toolbar.delete_policy.${policy}`), }))} /> From 4c2c68d4dda65901d4f1c6296890453e02776ccf Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 11 Mar 2026 10:04:46 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=99=A8=E8=AE=BE=E7=BD=AE=EF=BC=8C=E7=A1=AE=E4=BF=9D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20window.setInterval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MadDogOwner --- src/pages/home/toolbar/OfflineDownload.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index dc08b3083..f29084254 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -124,7 +124,7 @@ export const OfflineDownload = () => { // 打开时立即获取一次最新数据,不显示 loading fetchTasks(false) // 启动定时器,每隔3秒刷新(也不显示 loading) - timer = setInterval(() => fetchTasks(false), 3000) + timer = window.setInterval(() => fetchTasks(false), 3000) } else { clearInterval(timer) timer = undefined From 4e3b20579107ab77faaf323e7f474296476832bf Mon Sep 17 00:00:00 2001 From: zwzzrl <66906196+zwzzrl@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:36:36 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B9=B6=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ModalInput.tsx | 9 ++-- .../home/offlinedownload/TaskProgress.tsx | 31 ++----------- src/pages/home/offlinedownload/task.ts | 43 ++++++++----------- src/pages/home/toolbar/OfflineDownload.tsx | 31 +++++++++---- 4 files changed, 51 insertions(+), 63 deletions(-) diff --git a/src/components/ModalInput.tsx b/src/components/ModalInput.tsx index 7365034a1..4be2f9af1 100644 --- a/src/components/ModalInput.tsx +++ b/src/components/ModalInput.tsx @@ -92,10 +92,13 @@ export const ModalInput = (props: ModalInputProps) => { return } } - props.onSubmit?.(value()) - props.onSubmitWithValue?.(value(), setValue) + if (props.onSubmitWithValue) { + props.onSubmitWithValue(value(), setValue) + } else { + props.onSubmit?.(value()) + } } - + const handleInput = (newValue: string) => { setValue(newValue) if (props.validateFilename) { diff --git a/src/pages/home/offlinedownload/TaskProgress.tsx b/src/pages/home/offlinedownload/TaskProgress.tsx index db8b699db..05c88c3f2 100644 --- a/src/pages/home/offlinedownload/TaskProgress.tsx +++ b/src/pages/home/offlinedownload/TaskProgress.tsx @@ -1,4 +1,3 @@ -// src/pages/home/toolbar/TaskProgress.tsx import { VStack, HStack, @@ -7,35 +6,11 @@ import { Progress, ProgressIndicator, } from "@hope-ui/solid" -import { TaskInfo } from "./task" +import type { TaskInfo } from "~/types" import { getFileSize } from "~/utils" -import { Show, createSignal, JSX } from "solid-js" +import { Show } from "solid-js" import { useT } from "~/hooks" -import { me } from "~/store" - -// 复制 helper.tsx 中的 getPath 函数(用于生成路径链接) -const getPath = ( - device: string, - path: string, - asLink: boolean = true, -): JSX.Element => { - const fullPath = (device === "/" ? "" : device) + path - const prefix = me().base_path === "/" ? "" : me().base_path - const accessible = fullPath.startsWith(prefix) - const [underline, setUnderline] = createSignal(false) - return accessible && asLink ? ( -
setUnderline(true)} - onMouseOut={() => setUnderline(false)} - href={fullPath.slice(prefix.length)} - > - {fullPath} - - ) : ( -

{fullPath}

- ) -} +import { getPath } from "../../manage/tasks/helper" // 解析任务名称,返回文件名和路径信息 const parseTaskName = (name: string) => { diff --git a/src/pages/home/offlinedownload/task.ts b/src/pages/home/offlinedownload/task.ts index 294422e7b..637fc61e8 100644 --- a/src/pages/home/offlinedownload/task.ts +++ b/src/pages/home/offlinedownload/task.ts @@ -1,21 +1,7 @@ -// src/store/task.ts import { createSignal } from "solid-js" import { createStore } from "solid-js/store" import { r } from "~/utils" - -export interface TaskInfo { - id: string - name: string - creator: string - creator_role: number - state: number - status: string - progress: number - start_time: string | null - end_time: string | null - total_bytes: number - error: string -} +import type { TaskInfo } from "~/types" const [tasks, setTasks] = createStore([]) const [loading, setLoading] = createSignal(false) @@ -24,21 +10,30 @@ export const fetchTasks = async (showLoading = true) => { if (showLoading) setLoading(true) try { const [respOld, respNew] = await Promise.all([ - r.get("/task/offline_download/undone").catch(() => ({ data: [] })), - r - .get("/task/offline_download_transfer/undone") - .catch(() => ({ data: [] })), + r.get("/task/offline_download/undone"), + r.get("/task/offline_download_transfer/undone"), ]) - + const getTasksFromResp = (resp: any, label: string): any[] => { + if (!resp || resp.code !== 200) { + const message = + resp && typeof resp.message === "string" + ? resp.message + : "Unknown error" + throw new Error(`Failed to fetch ${label}: ${message}`) + } + const data = resp.data + return Array.isArray(data) ? data : [] + } const taskMap = new Map() - - const oldTasks = respOld.data || [] + const oldTasks = getTasksFromResp(respOld, "offline download tasks") oldTasks.forEach((item: any) => { if (!item.state) item.state = 0 taskMap.set(item.id, item) }) - - const newTasks = respNew.data || [] + const newTasks = getTasksFromResp( + respNew, + "offline download transfer tasks", + ) newTasks.forEach((item: any) => { taskMap.set(item.id, item) }) diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index f29084254..850e285a3 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -3,7 +3,6 @@ import { createDisclosure, HStack, Heading, - Button, VStack, Text, } from "@hope-ui/solid" @@ -96,6 +95,7 @@ export const OfflineDownload = () => { if (name === "offline_download") { onOpen() setShowTasks(true) + fetchTasks(true) } } bus.on("tool", handler) @@ -112,28 +112,43 @@ export const OfflineDownload = () => { ) => { const resp = await ok(pathname(), urls.split("\n"), tool(), deletePolicy()) handleRespWithNotifySuccess(resp, () => { - fetchTasks(false) // 不显示 loading + fetchTasks(true) // 显示 loading setValue("") }) } // 定时刷新任务进度(仅当显示任务列表时) let timer: number | undefined + let isFetching = false createEffect(() => { if (showTasks()) { - // 打开时立即获取一次最新数据,不显示 loading - fetchTasks(false) - // 启动定时器,每隔3秒刷新(也不显示 loading) - timer = window.setInterval(() => fetchTasks(false), 3000) + const poll = async () => { + if (!showTasks()) return + if (isFetching) { + timer = window.setTimeout(poll, 3000) + return + } + isFetching = true + try { + await fetchTasks(false) + } finally { + isFetching = false + } + if (showTasks()) { + timer = window.setTimeout(poll, 3000) + } + } + void poll() } else { - clearInterval(timer) + clearTimeout(timer) timer = undefined } }) onCleanup(() => { - clearInterval(timer) + clearTimeout(timer) }) + // 拖拽种子文件转换为磁力链接 const handleTorrentFileDrop = async ( e: DragEvent, From e6429856203beeea6f6a85458909e1def503152d Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 11 Mar 2026 16:36:52 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MadDogOwner --- src/pages/home/toolbar/OfflineDownload.tsx | 4 ++-- src/pages/home/{offlinedownload => toolbar}/TaskProgress.tsx | 0 src/{pages/home/offlinedownload => store}/task.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/pages/home/{offlinedownload => toolbar}/TaskProgress.tsx (100%) rename src/{pages/home/offlinedownload => store}/task.ts (100%) diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index 850e285a3..0b6f10a0d 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -26,8 +26,8 @@ import { import { PResp } from "~/types" import bencode from "bencode" import crypto from "crypto-js" -import { useTasks } from "../offlinedownload/task" -import TaskItem from "../offlinedownload/TaskProgress" +import { useTasks } from "~/store/task" +import TaskItem from "./TaskProgress" import { FullLoading } from "~/components/FullLoading" const deletePolicies = [ diff --git a/src/pages/home/offlinedownload/TaskProgress.tsx b/src/pages/home/toolbar/TaskProgress.tsx similarity index 100% rename from src/pages/home/offlinedownload/TaskProgress.tsx rename to src/pages/home/toolbar/TaskProgress.tsx diff --git a/src/pages/home/offlinedownload/task.ts b/src/store/task.ts similarity index 100% rename from src/pages/home/offlinedownload/task.ts rename to src/store/task.ts From e3e411ad4dc05a36bb6875ea8cddbbce6c3cbada Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 11 Mar 2026 16:37:24 +0800 Subject: [PATCH 09/12] pnpm format Signed-off-by: MadDogOwner --- src/components/ModalInput.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ModalInput.tsx b/src/components/ModalInput.tsx index 4be2f9af1..7d89e6aad 100644 --- a/src/components/ModalInput.tsx +++ b/src/components/ModalInput.tsx @@ -92,13 +92,13 @@ export const ModalInput = (props: ModalInputProps) => { return } } - if (props.onSubmitWithValue) { + if (props.onSubmitWithValue) { props.onSubmitWithValue(value(), setValue) } else { props.onSubmit?.(value()) - } + } } - + const handleInput = (newValue: string) => { setValue(newValue) if (props.validateFilename) { From 1ab5476db4173119afa3ef144acd84e455b61209 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 11 Mar 2026 17:05:00 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8D=E6=98=BE=E7=A4=BA=E5=92=8C=E7=8A=B6=E6=80=81=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MadDogOwner --- src/pages/home/toolbar/OfflineDownload.tsx | 33 +++++++++++----------- src/pages/home/toolbar/TaskProgress.tsx | 9 ++++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index 0b6f10a0d..0d6b02d4a 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -1,34 +1,33 @@ import { Box, createDisclosure, - HStack, Heading, - VStack, + HStack, Text, + VStack, } from "@hope-ui/solid" -import { ModalInput, SelectWrapper } from "~/components" -import { useFetch, useRouter, useT } from "~/hooks" -import { - offlineDownload, - bus, - handleRespWithNotifySuccess, - r, - handleResp, -} from "~/utils" +import bencode from "bencode" +import crypto from "crypto-js" import { + createEffect, createSignal, + For, onCleanup, onMount, - createEffect, - For, Show, } from "solid-js" -import { PResp } from "~/types" -import bencode from "bencode" -import crypto from "crypto-js" +import { FullLoading, ModalInput, SelectWrapper } from "~/components" +import { useFetch, useRouter, useT } from "~/hooks" import { useTasks } from "~/store/task" +import { PResp } from "~/types" +import { + bus, + handleResp, + handleRespWithNotifySuccess, + offlineDownload, + r, +} from "~/utils" import TaskItem from "./TaskProgress" -import { FullLoading } from "~/components/FullLoading" const deletePolicies = [ "upload_download_stream", diff --git a/src/pages/home/toolbar/TaskProgress.tsx b/src/pages/home/toolbar/TaskProgress.tsx index 05c88c3f2..8fc1d6e6b 100644 --- a/src/pages/home/toolbar/TaskProgress.tsx +++ b/src/pages/home/toolbar/TaskProgress.tsx @@ -10,7 +10,7 @@ import type { TaskInfo } from "~/types" import { getFileSize } from "~/utils" import { Show } from "solid-js" import { useT } from "~/hooks" -import { getPath } from "../../manage/tasks/helper" +import { getPath } from "~/pages/manage/tasks/helper" // 解析任务名称,返回文件名和路径信息 const parseTaskName = (name: string) => { @@ -36,7 +36,8 @@ const parseTaskName = (name: string) => { if (type === "transfer") { // 从路径中提取文件名(最后一段,去除参数) - const fileName = urlOrPath.split("/").pop()?.split("?")[0] || "未知文件" + const fileName = + urlOrPath.split("/").pop()?.split("?")[0] || "Unknown file" return { type, fileName, @@ -110,7 +111,9 @@ export const TaskItem = (props: TaskInfo) => { )} {t("tasks.state." + props.state)} From 5189a48b00c84c2474119e574558ead9cfca3929 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 11 Mar 2026 17:05:08 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=E5=80=BC=E7=9A=84=E8=AE=A1=E7=AE=97=EF=BC=8C=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E4=BD=BF=E7=94=A8=20props.progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MadDogOwner --- src/pages/home/toolbar/TaskProgress.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/toolbar/TaskProgress.tsx b/src/pages/home/toolbar/TaskProgress.tsx index 8fc1d6e6b..7e910a591 100644 --- a/src/pages/home/toolbar/TaskProgress.tsx +++ b/src/pages/home/toolbar/TaskProgress.tsx @@ -123,7 +123,7 @@ export const TaskItem = (props: TaskInfo) => { w="$full" trackColor="$info3" rounded="$full" - value={props.progress * 100} + value={props.progress} size="sm" > From 13d40d3128930007aaf878fccd30dda2e543614a Mon Sep 17 00:00:00 2001 From: zwzzrl <66906196+zwzzrl@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:54:39 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/toolbar/OfflineDownload.tsx | 12 ++++++------ src/pages/home/toolbar/TaskProgress.tsx | 4 ++-- src/store/task.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/home/toolbar/OfflineDownload.tsx b/src/pages/home/toolbar/OfflineDownload.tsx index 0d6b02d4a..55f1a1287 100644 --- a/src/pages/home/toolbar/OfflineDownload.tsx +++ b/src/pages/home/toolbar/OfflineDownload.tsx @@ -27,7 +27,7 @@ import { offlineDownload, r, } from "~/utils" -import TaskItem from "./TaskProgress" +import OfflineDownloadTaskItem from "./TaskProgress" const deletePolicies = [ "upload_download_stream", @@ -94,7 +94,7 @@ export const OfflineDownload = () => { if (name === "offline_download") { onOpen() setShowTasks(true) - fetchTasks(true) + fetchOfflineDownloadTasks(true) } } bus.on("tool", handler) @@ -102,7 +102,7 @@ export const OfflineDownload = () => { bus.off("tool", handler) }) - const { tasks, loading: tasksLoading, fetchTasks } = useTasks() + const { tasks, loading: tasksLoading, fetchOfflineDownloadTasks } = useTasks() const [showTasks, setShowTasks] = createSignal(false) const handleSubmit = async ( @@ -111,7 +111,7 @@ export const OfflineDownload = () => { ) => { const resp = await ok(pathname(), urls.split("\n"), tool(), deletePolicy()) handleRespWithNotifySuccess(resp, () => { - fetchTasks(true) // 显示 loading + fetchOfflineDownloadTasks(true) // 显示 loading setValue("") }) } @@ -129,7 +129,7 @@ export const OfflineDownload = () => { } isFetching = true try { - await fetchTasks(false) + await fetchOfflineDownloadTasks(false) } finally { isFetching = false } @@ -238,7 +238,7 @@ export const OfflineDownload = () => { }> - {(task) => } + {(task) => } {t("tasks.attr.offline_download.no_tasks")} diff --git a/src/pages/home/toolbar/TaskProgress.tsx b/src/pages/home/toolbar/TaskProgress.tsx index 7e910a591..11878b2ad 100644 --- a/src/pages/home/toolbar/TaskProgress.tsx +++ b/src/pages/home/toolbar/TaskProgress.tsx @@ -70,7 +70,7 @@ export const StatusColor = { 5: "info", } as const -export const TaskItem = (props: TaskInfo) => { +export const OfflineDownloadTaskItem = (props: TaskInfo) => { const t = useT() const parsed = parseTaskName(props.name) @@ -137,4 +137,4 @@ export const TaskItem = (props: TaskInfo) => { ) } -export default TaskItem +export default OfflineDownloadTaskItem diff --git a/src/store/task.ts b/src/store/task.ts index 637fc61e8..7c02b8d96 100644 --- a/src/store/task.ts +++ b/src/store/task.ts @@ -6,7 +6,7 @@ import type { TaskInfo } from "~/types" const [tasks, setTasks] = createStore([]) const [loading, setLoading] = createSignal(false) -export const fetchTasks = async (showLoading = true) => { +export const fetchOfflineDownloadTasks = async (showLoading = true) => { if (showLoading) setLoading(true) try { const [respOld, respNew] = await Promise.all([ @@ -56,4 +56,4 @@ export const fetchTasks = async (showLoading = true) => { } } -export const useTasks = () => ({ tasks, loading, fetchTasks }) +export const useTasks = () => ({ tasks, loading, fetchOfflineDownloadTasks })