From 93075e8c8ec21427c41a42e8d61bff3e6918c533 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:07:25 +0000 Subject: [PATCH 01/38] Initial plan From 6ef06729f499eb4ac05ea6b23db708a525fae0b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:14:41 +0000 Subject: [PATCH 02/38] feat: add MyClient torrent management page under Overview Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/c9f44fb7-e62c-4922-a360-7bca711c815c Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- src/entries/messages.ts | 7 +- src/entries/offscreen/utils/download.ts | 28 ++ src/entries/options/plugins/router.ts | 6 + src/entries/options/stores/config.ts | 4 + .../options/views/Overview/MyClient/Index.vue | 360 ++++++++++++++++++ src/locales/en.json | 40 +- src/locales/zh_CN.json | 45 ++- 7 files changed, 486 insertions(+), 4 deletions(-) create mode 100644 src/entries/options/views/Overview/MyClient/Index.vue diff --git a/src/entries/messages.ts b/src/entries/messages.ts index cef7523b5..6a72ccf9d 100644 --- a/src/entries/messages.ts +++ b/src/entries/messages.ts @@ -11,7 +11,7 @@ import type { import type { ISocialInformation, TSupportSocialSite$1 } from "@ptd/social"; import type { IMediaServerId, IMediaServerSearchOptions, IMediaServerSearchResult } from "@ptd/mediaServer"; import type { IBackupData, IBackupFileInfo } from "@ptd/backupServer"; -import type { TorrentClientStatus } from "@ptd/downloader"; +import type { CTorrent, TorrentClientStatus } from "@ptd/downloader"; // 可序列化的种子信息,用于辅种检测 export interface ITorrentInfoForVerification { @@ -113,6 +113,11 @@ interface ProtocolMap extends TMessageMap { getTorrentDownloadLink(torrent: ITorrent): string; getTorrentInfoForVerification(torrent: ITorrent): ITorrentInfoForVerification; + getClientTorrents(downloaderId: string): CTorrent[]; + deleteClientTorrent(data: { downloaderId: string; id: any; removeData?: boolean }): boolean; + pauseClientTorrent(data: { downloaderId: string; id: any }): boolean; + resumeClientTorrent(data: { downloaderId: string; id: any }): boolean; + downloadTorrent(data: IDownloadTorrentOption): IDownloadTorrentResult; getDownloadHistory(): ITorrentDownloadMetadata[]; diff --git a/src/entries/offscreen/utils/download.ts b/src/entries/offscreen/utils/download.ts index 8b3f7863c..724733712 100644 --- a/src/entries/offscreen/utils/download.ts +++ b/src/entries/offscreen/utils/download.ts @@ -108,6 +108,34 @@ export async function getTorrentInfoForVerification(torrent: ITorrent) { onMessage("getTorrentInfoForVerification", async ({ data: torrent }) => await getTorrentInfoForVerification(torrent)); +onMessage("getClientTorrents", async ({ data: downloaderId }) => { + const downloaderConfig = await getDownloaderConfig(downloaderId); + if (!downloaderConfig.id) return []; + const downloaderInstance = await getDownloader(downloaderConfig); + return await downloaderInstance.getAllTorrents(); +}); + +onMessage("deleteClientTorrent", async ({ data: { downloaderId, id, removeData } }) => { + const downloaderConfig = await getDownloaderConfig(downloaderId); + if (!downloaderConfig.id) return false; + const downloaderInstance = await getDownloader(downloaderConfig); + return await downloaderInstance.removeTorrent(id, removeData ?? false); +}); + +onMessage("pauseClientTorrent", async ({ data: { downloaderId, id } }) => { + const downloaderConfig = await getDownloaderConfig(downloaderId); + if (!downloaderConfig.id) return false; + const downloaderInstance = await getDownloader(downloaderConfig); + return await downloaderInstance.pauseTorrent(id); +}); + +onMessage("resumeClientTorrent", async ({ data: { downloaderId, id } }) => { + const downloaderConfig = await getDownloaderConfig(downloaderId); + if (!downloaderConfig.id) return false; + const downloaderInstance = await getDownloader(downloaderConfig); + return await downloaderInstance.resumeTorrent(id); +}); + function buildDownloadHistory(downloadOption: IDownloadTorrentOption): ITorrentDownloadMetadata { const { torrent = {}, downloaderId = "local" } = downloadOption; return { diff --git a/src/entries/options/plugins/router.ts b/src/entries/options/plugins/router.ts index bef5b13ce..a213c663c 100644 --- a/src/entries/options/plugins/router.ts +++ b/src/entries/options/plugins/router.ts @@ -77,6 +77,12 @@ export const routes: RouteRecordRaw[] = [ meta: { icon: "mdi-play-network" }, component: () => import("../views/Overview/MediaServerEntity/Index.vue"), }, + { + path: "/my-client", + name: "MyClient", + meta: { icon: "mdi-download-network" }, + component: () => import("../views/Overview/MyClient/Index.vue"), + }, { path: "/download-history", name: "DownloadHistory", diff --git a/src/entries/options/stores/config.ts b/src/entries/options/stores/config.ts index 1a8deecde..48e285c7f 100644 --- a/src/entries/options/stores/config.ts +++ b/src/entries/options/stores/config.ts @@ -139,6 +139,10 @@ export const useConfigStore = defineStore("config", { itemsPerPage: 10, sortBy: [{ key: "enabled", order: "desc" }], }, + MyClient: { + itemsPerPage: 25, + sortBy: [{ key: "dateAdded", order: "desc" }], + }, SetSearchSolution: { itemsPerPage: 10, }, diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue new file mode 100644 index 000000000..ae0f7b34f --- /dev/null +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -0,0 +1,360 @@ + + + + + diff --git a/src/locales/en.json b/src/locales/en.json index 7bd205dc2..39de0ef7d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1135,5 +1135,43 @@ "clearSearchSnapshot": "Clear Search Snapshots", "clearSearchSnapshotDesc": "Clear all search snapshot data" } + }, + "MyClient": { + "refresh": "Refresh", + "deleteSelected": "Delete Selected", + "deleteSelectedWithData": "Delete Selected (with data)", + "searchPlaceholder": "Search by name, hash, label, or path", + "emptyNotice": "No torrents found. Please add a downloader or click Refresh.", + "table": { + "client": "Client", + "name": "Torrent Name", + "size": "Size", + "progress": "Progress", + "status": "Status", + "ratio": "Ratio", + "upSpeed": "Upload Speed", + "dlSpeed": "Download Speed", + "savePath": "Save Path", + "addedAt": "Added At" + }, + "state": { + "downloading": "Downloading", + "seeding": "Seeding", + "paused": "Paused", + "queued": "Queued", + "checking": "Checking", + "error": "Error", + "unknown": "Unknown" + }, + "action": { + "pause": "Pause", + "resume": "Resume", + "delete": "Delete", + "deleteWithData": "Delete (with data)", + "pauseSuccess": "Paused successfully", + "pauseError": "Failed to pause", + "resumeSuccess": "Resumed successfully", + "resumeError": "Failed to resume" + } } -} +} \ No newline at end of file diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index ff9d6f045..7a6b60f11 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -878,7 +878,10 @@ "searchEntries": "搜索入口", "flushFavicon": "刷新站点图标" }, - "settingNote": ["只有配置过的站点才会显示插件图标及相应的功能;", "已离线的站点不再参与搜索和信息获取;"], + "settingNote": [ + "只有配置过的站点才会显示插件图标及相应的功能;", + "已离线的站点不再参与搜索和信息获取;" + ], "oneClickImport": "一键导入站点", "reBuildMap": "重建站点映射" }, @@ -1132,5 +1135,43 @@ "clearSearchSnapshot": "清空搜索快照", "clearSearchSnapshotDesc": "清空所有搜索快照数据" } + }, + "MyClient": { + "refresh": "刷新", + "deleteSelected": "删除所选", + "deleteSelectedWithData": "删除所选(含数据)", + "searchPlaceholder": "搜索种子名称、Hash、标签、路径", + "emptyNotice": "当前没有种子,请先添加下载器或刷新", + "table": { + "client": "下载器", + "name": "种子名称", + "size": "大小", + "progress": "进度", + "status": "状态", + "ratio": "分享率", + "upSpeed": "上传速度", + "dlSpeed": "下载速度", + "savePath": "保存路径", + "addedAt": "添加时间" + }, + "state": { + "downloading": "下载中", + "seeding": "做种中", + "paused": "已暂停", + "queued": "排队中", + "checking": "检查中", + "error": "错误", + "unknown": "未知" + }, + "action": { + "pause": "暂停", + "resume": "继续", + "delete": "删除", + "deleteWithData": "删除(含数据)", + "pauseSuccess": "暂停成功", + "pauseError": "暂停失败", + "resumeSuccess": "继续成功", + "resumeError": "继续失败" + } } -} +} \ No newline at end of file From 017bf0a6179aaf994607f6f9b0bbccb73018b80a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:16:54 +0000 Subject: [PATCH 03/38] fix: address code review feedback - extract helper, fix v-if placement, improve types Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/c9f44fb7-e62c-4922-a360-7bca711c815c Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../options/views/Overview/MyClient/Index.vue | 22 +++++++++++-------- src/entries/shared/types/storages/config.ts | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index ae0f7b34f..d0b4c53e6 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -126,13 +126,13 @@ function openDeleteDialog(torrentList: CTorrent[], withData = false) { } function openDeleteSelected(withData = false) { - const selected = torrents.value.filter((t) => tableSelected.value.includes(String(t.id) + t.clientId)); + const selected = torrents.value.filter((t) => tableSelected.value.includes(torrentKey(t))); openDeleteDialog(selected, withData); } // Called per-item by DeleteDialog -async function confirmDeleteTorrent(torrentKey: string): Promise { - const torrent = toDeleteTorrents.value.find((t) => String(t.id) + t.clientId === torrentKey); +async function confirmDeleteTorrent(torrentKey_: string): Promise { + const torrent = toDeleteTorrents.value.find((t) => torrentKey(t) === torrentKey_); if (!torrent) return; await sendMessage("deleteClientTorrent", { downloaderId: torrent.clientId, @@ -149,6 +149,10 @@ function clientIcon(clientId: string) { const type = metadataStore.downloaders[clientId]?.type; return type ? getDownloaderIcon(type) : undefined; } + +function torrentKey(torrent: CTorrent) { + return String(torrent.id) + torrent.clientId; +} - From f2e4e468c914d489abf65aa445d001dc2e18c103 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:04:45 +0000 Subject: [PATCH 12/38] feat: add Push-to-Downloader button and dialog to MyClient nav Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/ceb4d923-570a-4463-b2e6-f7127abc2d54 Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../options/views/Overview/MyClient/Index.vue | 15 ++ .../MyClient/PushToDownloaderDialog.vue | 144 ++++++++++++++++++ src/locales/en.json | 10 ++ src/locales/zh_CN.json | 10 ++ 4 files changed, 179 insertions(+) create mode 100644 src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index 54f35ca2f..90af56fd5 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -12,6 +12,7 @@ import { useConfigStore } from "@/options/stores/config.ts"; import NavButton from "@/options/components/NavButton.vue"; import DeleteDialog from "./DeleteDialog.vue"; +import PushToDownloaderDialog from "./PushToDownloaderDialog.vue"; const { t } = useI18n(); const metadataStore = useMetadataStore(); @@ -31,6 +32,9 @@ const selectedDownloaderIds = ref([]); const showDeleteDialog = ref(false); const toDeleteTorrents = ref([]); +// push to downloader dialog +const showPushToDownloaderDialog = ref(false); + // ── auto-refresh ─────────────────────────────────────────────────────────── /** * Global refresh interval in seconds (0 = off). @@ -274,6 +278,15 @@ function torrentKey(torrent: CTorrent) { + + + + + + diff --git a/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue b/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue new file mode 100644 index 000000000..7920fdfec --- /dev/null +++ b/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/locales/en.json b/src/locales/en.json index ebba38aa8..83881aa52 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1186,6 +1186,16 @@ "resumeSelected": "Resume selected", "dialog": { "removeData": "Also remove data" + }, + "pushToDownloader": { + "navBtn": "Push to Downloader", + "title": "Push to Downloader", + "modeUrl": "Link / Magnet", + "modeFile": "Torrent File", + "urlInputLabel": "URLs / Magnet Links", + "urlInputHint": "One URL or magnet link per line", + "fileInputLabel": "Torrent Files", + "fileInputHint": "Select one or more .torrent files to push" } } } \ No newline at end of file diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index 7b2da8a75..4ed2dcc99 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -1186,6 +1186,16 @@ "resumeSelected": "继续所选", "dialog": { "removeData": "同时删除数据" + }, + "pushToDownloader": { + "navBtn": "推送到下载器", + "title": "推送到下载器", + "modeUrl": "链接 / 磁力", + "modeFile": "种子文件", + "urlInputLabel": "下载链接 / 磁力链接", + "urlInputHint": "每行一个链接或磁力链接", + "fileInputLabel": "种子文件", + "fileInputHint": "选择一个或多个 .torrent 文件进行推送" } } } \ No newline at end of file From be052b5fb5d4289b9f4dac20adcfef2b1750c056 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:06:18 +0000 Subject: [PATCH 13/38] perf: use FileReader.readAsDataURL for torrent file base64 encoding Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/ceb4d923-570a-4463-b2e6-f7127abc2d54 Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../Overview/MyClient/PushToDownloaderDialog.vue | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue b/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue index 7920fdfec..1c28aead3 100644 --- a/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue +++ b/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue @@ -49,14 +49,12 @@ async function submit() { } } else { for (const file of torrentFiles.value) { - const arrayBuffer = await file.arrayBuffer(); - const bytes = new Uint8Array(arrayBuffer); - let binary = ""; - for (let i = 0; i < bytes.byteLength; i++) { - binary += String.fromCharCode(bytes[i]); - } - const base64 = btoa(binary); - const dataUri = `data:application/x-bittorrent;base64,${base64}`; + const dataUri = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = reject; + reader.readAsDataURL(file); + }); torrentItems.push({ link: dataUri, title: file.name.replace(/\.torrent$/i, ""), From d07bacf1b1750f245f1e96cb495307b6ef5f0e61 Mon Sep 17 00:00:00 2001 From: Rhilip Date: Fri, 10 Apr 2026 23:58:49 +0800 Subject: [PATCH 14/38] refactor(qBittorrent): update torrent management requests to use POST method with data payload --- src/packages/downloader/entity/qBittorrent.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/packages/downloader/entity/qBittorrent.ts b/src/packages/downloader/entity/qBittorrent.ts index a651febcc..1d2bda44c 100644 --- a/src/packages/downloader/entity/qBittorrent.ts +++ b/src/packages/downloader/entity/qBittorrent.ts @@ -444,29 +444,29 @@ export default class QBittorrent extends AbstractBittorrentClient { - const params = { + const data = { hashes: hashes === "all" ? "all" : normalizePieces(hashes), }; - await this.request("/torrents/pause", { params }); + await this.request("/torrents/pause", { method: "post", data }); return true; } // 注意方法虽然支持一次对多个种子进行操作,但仍建议每次均只操作一个种子 async removeTorrent(hashes: string | string[] | "all", removeData: boolean = false): Promise { - const params = { + const data = { hashes: hashes === "all" ? "all" : normalizePieces(hashes), - removeData, + deleteFiles: removeData, }; - await this.request("/torrents/delete", { params }); + await this.request("/torrents/delete", { method: "post", data }); return true; } // 注意方法虽然支持一次对多个种子进行操作,但仍建议每次均只操作一个种子 async resumeTorrent(hashes: string | string[] | "all"): Promise { - const params = { + const data = { hashes: hashes === "all" ? "all" : normalizePieces(hashes), }; - await this.request("/torrents/resume", { params }); + await this.request("/torrents/resume", { method: "post", data }); return true; } From c5d01f3cdb33afac7592db72aef87b21b58854bb Mon Sep 17 00:00:00 2001 From: Rhilip Date: Sat, 11 Apr 2026 00:05:48 +0800 Subject: [PATCH 15/38] feat: enhance downloader settings with new options and improve UI components --- .../options/components/DeleteDialog.vue | 6 +++-- .../SentToDownloaderDialog/utils.ts | 7 ++++-- src/entries/options/stores/config.ts | 1 + .../views/Overview/MyClient/DeleteDialog.vue | 18 +++++++-------- .../options/views/Overview/MyClient/Index.vue | 10 ++++++--- .../MyClient/PushToDownloaderDialog.vue | 22 ++++++++++--------- .../views/Settings/SetBase/DownloadWindow.vue | 12 ++++++++++ src/entries/shared/types/storages/config.ts | 8 ++++--- src/locales/en.json | 9 ++++---- src/locales/zh_CN.json | 22 +++++++++---------- src/packages/downloader/entity/qBittorrent.ts | 7 ++++++ 11 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/entries/options/components/DeleteDialog.vue b/src/entries/options/components/DeleteDialog.vue index 6d09811a0..32d229f36 100644 --- a/src/entries/options/components/DeleteDialog.vue +++ b/src/entries/options/components/DeleteDialog.vue @@ -37,11 +37,13 @@ async function dialogEnter() { {{ t("common.dialog.title.confirmAction") }} - + {{ t("common.dialog.deleteText", [toDeleteIds!.length]) }} + + - + diff --git a/src/entries/options/components/SentToDownloaderDialog/utils.ts b/src/entries/options/components/SentToDownloaderDialog/utils.ts index 2d0362db4..82e000025 100644 --- a/src/entries/options/components/SentToDownloaderDialog/utils.ts +++ b/src/entries/options/components/SentToDownloaderDialog/utils.ts @@ -54,12 +54,15 @@ export function sendTorrentToDownloader( const replaceMap: Record = { "torrent.title": torrent.title ?? "", "torrent.subTitle": torrent.subTitle ?? "", - "torrent.site": torrent.site, - "torrent.siteName": await metadataStore.getSiteName(torrent.site), "torrent.category": (torrent.category as string) ?? "", ...baseReplaceMap, }; + if (torrent.site) { + replaceMap["torrent.site"] = torrent.site; + replaceMap["torrent.siteName"] = await metadataStore.getSiteName(torrent.site); + } + for (const key of ["savePath", "label"] as (keyof typeof realAddTorrentOptions)[]) { if (realAddTorrentOptions[key]) { if (realAddTorrentOptions[key] === "") { diff --git a/src/entries/options/stores/config.ts b/src/entries/options/stores/config.ts index 48e285c7f..1b3818ca7 100644 --- a/src/entries/options/stores/config.ts +++ b/src/entries/options/stores/config.ts @@ -248,6 +248,7 @@ export const useConfigStore = defineStore("config", { download: { saveDownloadHistory: true, startupAutoFetchDownloaderStatus: false, + initDownloaderTorrentOnEnter: false, saveLastDownloader: false, allowDirectSendToClient: false, localDownloadMethod: "browser", diff --git a/src/entries/options/views/Overview/MyClient/DeleteDialog.vue b/src/entries/options/views/Overview/MyClient/DeleteDialog.vue index 694c4445e..f38bfae9e 100644 --- a/src/entries/options/views/Overview/MyClient/DeleteDialog.vue +++ b/src/entries/options/views/Overview/MyClient/DeleteDialog.vue @@ -33,16 +33,14 @@ function wrappedConfirmDelete(id: string) { :confirm-delete="wrappedConfirmDelete" @all-delete="emits('allDelete')" > - diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index 90af56fd5..c9cbecf65 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -47,7 +47,7 @@ const globalRefreshInterval = ref(0); const autoRefreshRunning = ref(false); /** Per-downloader setTimeout IDs */ -const refreshTimers = new Map>(); +const refreshTimers = new Map(); /** Per-downloader consecutive failure counts */ const failCounts = new Map(); @@ -86,7 +86,7 @@ const tableHeader = computed( { title: t("MyClient.table.ratio"), key: "ratio", align: "end", width: "80" }, { title: t("MyClient.table.upSpeed"), key: "uploadSpeed", align: "end", width: "100" }, { title: t("MyClient.table.dlSpeed"), key: "downloadSpeed", align: "end", width: "100" }, - { title: t("MyClient.table.savePath"), key: "savePath", align: "start" }, + // { title: t("MyClient.table.savePath"), key: "savePath", align: "start" }, { title: t("MyClient.table.addedAt"), key: "dateAdded", align: "center", width: "160" }, { title: t("common.action"), key: "action", align: "center", sortable: false, width: "120" }, ] as DataTableHeader[], @@ -213,7 +213,11 @@ function resumeDownloaderRefresh(id: string) { } } -onMounted(loadTorrents); +onMounted(() => { + if (configStore.download.initDownloaderTorrentOnEnter) { + loadTorrents(); + } +}); onUnmounted(() => { stopAllTimers(); diff --git a/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue b/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue index 1c28aead3..08df5d9e8 100644 --- a/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue +++ b/src/entries/options/views/Overview/MyClient/PushToDownloaderDialog.vue @@ -1,5 +1,5 @@ diff --git a/src/entries/options/views/Settings/SetBase/DownloadWindow.vue b/src/entries/options/views/Settings/SetBase/DownloadWindow.vue index ce7b4730d..62cdb8930 100644 --- a/src/entries/options/views/Settings/SetBase/DownloadWindow.vue +++ b/src/entries/options/views/Settings/SetBase/DownloadWindow.vue @@ -26,12 +26,24 @@ async function clearLastDownloader(v: boolean) { false-icon="mdi-alert-octagon" hide-details /> + + + + + + {{ t("SetBase.download.localDownloadTitle") }} + diff --git a/src/entries/shared/types/storages/config.ts b/src/entries/shared/types/storages/config.ts index a47f1b201..9099b4f7c 100644 --- a/src/entries/shared/types/storages/config.ts +++ b/src/entries/shared/types/storages/config.ts @@ -183,14 +183,16 @@ export interface IConfigPiniaStorageSchema { // 是否保存下载记录 saveDownloadHistory: boolean; - // 在下载器页面,进入时自动获取下载器状态(如果下载器支持获取状态的话) - startupAutoFetchDownloaderStatus: boolean; - // 当使用本地方法下载时,如何下载种子 localDownloadMethod: TLocalDownloadMethod; // 当使用本地方法下载时,是否忽略站点的下载间隔设置; ignoreSiteDownloadIntervalWhenLocalDownload: boolean; + // 在下载器页面,进入时自动获取下载器状态(如果下载器支持获取状态的话) + startupAutoFetchDownloaderStatus: boolean; + // 在我的下载器页面,进入时即刷新下载器 + initDownloaderTorrentOnEnter: boolean; + // 是否保存上一次使用的下载器 saveLastDownloader: boolean; // 是否允许直接将链接(而不是种子文件)发送到客户端 diff --git a/src/locales/en.json b/src/locales/en.json index 83881aa52..36d7d59e1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -659,6 +659,7 @@ }, "saveDownloadHistory": "Save download history (Please do not disable this function unless necessary)", "startupAutoFetchDownloaderStatus": "Automatically load the downloader status when entering the downloader settings page", + "initDownloaderTorrentOnEnter": "Automatically load torrents from the downloader when entering the My Downloader page", "localDownloadTitle": "Local Download", "localDownloadIgnoreInterval": "Ignore site download interval limit for local downloads", "pushDownloadServerTitle": "Push to Download Server", @@ -1138,7 +1139,7 @@ }, "MyClient": { "refresh": "Refresh", - "deleteSelected": "Delete Selected", + "deleteSelected": "Delete", "searchPlaceholder": "Search by name, hash, label, or path", "emptyNotice": "No torrents found. Please add a downloader or click Refresh.", "table": { @@ -1182,8 +1183,8 @@ "clientSuspended": "{name} failed 3 times in a row. Auto-refresh suspended. Click the chip to resume.", "suspendedTip": "This client has been suspended due to 3 consecutive failures. Click the alert icon to resume." }, - "pauseSelected": "Pause selected", - "resumeSelected": "Resume selected", + "pauseSelected": "Pause", + "resumeSelected": "Resume", "dialog": { "removeData": "Also remove data" }, @@ -1198,4 +1199,4 @@ "fileInputHint": "Select one or more .torrent files to push" } } -} \ No newline at end of file +} diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index 4ed2dcc99..c7ff707af 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -659,6 +659,7 @@ }, "saveDownloadHistory": "是否保存下载历史(如非必要请勿关闭此功能)", "startupAutoFetchDownloaderStatus": "进入下载器设置页面时自动加载下载器状态", + "initDownloaderTorrentOnEnter": "进入 我的下载器 页面时,自动加载下载器中的种子信息", "localDownloadTitle": "本地下载", "localDownloadIgnoreInterval": "本地下载时忽略站点下载间隔限制", "pushDownloadServerTitle": "下载服务器推送", @@ -878,10 +879,7 @@ "searchEntries": "搜索入口", "flushFavicon": "刷新站点图标" }, - "settingNote": [ - "只有配置过的站点才会显示插件图标及相应的功能;", - "已离线的站点不再参与搜索和信息获取;" - ], + "settingNote": ["只有配置过的站点才会显示插件图标及相应的功能;", "已离线的站点不再参与搜索和信息获取;"], "oneClickImport": "一键导入站点", "reBuildMap": "重建站点映射" }, @@ -1138,7 +1136,7 @@ }, "MyClient": { "refresh": "刷新", - "deleteSelected": "删除所选", + "deleteSelected": "删除", "searchPlaceholder": "搜索种子名称、Hash、标签、路径", "emptyNotice": "当前没有种子,请先添加下载器或刷新", "table": { @@ -1164,14 +1162,14 @@ }, "action": { "pause": "暂停", - "resume": "继续", + "resume": "开始", "delete": "删除", "pauseSuccess": "暂停成功", "pauseError": "暂停失败", - "resumeSuccess": "继续成功", - "resumeError": "继续失败", + "resumeSuccess": "开始成功", + "resumeError": "开始失败", "pauseSelectedSuccess": "已暂停 {count} 个种子", - "resumeSelectedSuccess": "已继续 {count} 个种子" + "resumeSelectedSuccess": "已开始 {count} 个种子" }, "autoRefresh": { "btnTitle": "自动刷新", @@ -1182,8 +1180,8 @@ "clientSuspended": "{name} 刷新失败 3 次,已暂停自动刷新。点击下载器标签可恢复。", "suspendedTip": "该下载器已因连续失败 3 次而被暂停。点击感叹号图标可恢复。" }, - "pauseSelected": "暂停所选", - "resumeSelected": "继续所选", + "pauseSelected": "暂停", + "resumeSelected": "开始", "dialog": { "removeData": "同时删除数据" }, @@ -1198,4 +1196,4 @@ "fileInputHint": "选择一个或多个 .torrent 文件进行推送" } } -} \ No newline at end of file +} diff --git a/src/packages/downloader/entity/qBittorrent.ts b/src/packages/downloader/entity/qBittorrent.ts index 1d2bda44c..a3883c0d7 100644 --- a/src/packages/downloader/entity/qBittorrent.ts +++ b/src/packages/downloader/entity/qBittorrent.ts @@ -257,6 +257,13 @@ export default class QBittorrent extends AbstractBittorrentClient({ baseURL: this.config.address, url: urlJoin("/api/v2", path), From 9bc097eefba15d261264f00eece51b46e2421a2f Mon Sep 17 00:00:00 2001 From: Rhilip Date: Sat, 11 Apr 2026 17:20:50 +0800 Subject: [PATCH 16/38] feat: add TorrentStateTd component for improved state display in torrent management --- .../options/views/Overview/MyClient/Index.vue | 25 +++---------- .../Overview/MyClient/TorrentStateTd.vue | 35 +++++++++++++++++++ .../views/Settings/SetBase/DownloadWindow.vue | 2 +- src/locales/en.json | 1 + src/locales/zh_CN.json | 1 + 5 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 src/entries/options/views/Overview/MyClient/TorrentStateTd.vue diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index c9cbecf65..624ca6845 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -13,6 +13,7 @@ import { useConfigStore } from "@/options/stores/config.ts"; import NavButton from "@/options/components/NavButton.vue"; import DeleteDialog from "./DeleteDialog.vue"; import PushToDownloaderDialog from "./PushToDownloaderDialog.vue"; +import TorrentStateTd from "./TorrentStateTd.vue"; const { t } = useI18n(); const metadataStore = useMetadataStore(); @@ -92,17 +93,6 @@ const tableHeader = computed( ] as DataTableHeader[], ); -// ── state chip display map ──────────────────────────────────────────────── -const stateDisplay: Record = { - [CTorrentState.downloading]: { color: "blue", icon: "mdi-download", label: "MyClient.state.downloading" }, - [CTorrentState.seeding]: { color: "green", icon: "mdi-upload", label: "MyClient.state.seeding" }, - [CTorrentState.paused]: { color: "grey", icon: "mdi-pause", label: "MyClient.state.paused" }, - [CTorrentState.queued]: { color: "orange", icon: "mdi-clock-outline", label: "MyClient.state.queued" }, - [CTorrentState.checking]: { color: "cyan", icon: "mdi-refresh", label: "MyClient.state.checking" }, - [CTorrentState.error]: { color: "red", icon: "mdi-alert-circle", label: "MyClient.state.error" }, - [CTorrentState.unknown]: { color: "grey", icon: "mdi-help-circle", label: "MyClient.state.unknown" }, -}; - // ── per-downloader helpers ──────────────────────────────────────────────── /** Fetch torrents for a single downloader and merge into `torrents`. */ async function loadSingleDownloader(id: string): Promise { @@ -409,13 +399,13 @@ function torrentKey(torrent: CTorrent) { v-model="tableSelected" :headers="tableHeader" :items="filteredTorrents" - :loading="loading" :items-per-page="configStore.tableBehavior['MyClient']?.itemsPerPage ?? 25" + :loading="loading" :multi-sort="configStore.enableTableMultiSort" :sort-by="configStore.tableBehavior['MyClient']?.sortBy" - return-object class="table-stripe table-header-no-wrap" hover + return-object show-select @update:itemsPerPage="(v) => configStore.updateTableBehavior('MyClient', 'itemsPerPage', v)" @update:sortBy="(v) => configStore.updateTableBehavior('MyClient', 'sortBy', v)" @@ -457,14 +447,7 @@ function torrentKey(torrent: CTorrent) { diff --git a/src/entries/options/views/Overview/MyClient/TorrentStateTd.vue b/src/entries/options/views/Overview/MyClient/TorrentStateTd.vue new file mode 100644 index 000000000..a1467b99c --- /dev/null +++ b/src/entries/options/views/Overview/MyClient/TorrentStateTd.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/entries/options/views/Settings/SetBase/DownloadWindow.vue b/src/entries/options/views/Settings/SetBase/DownloadWindow.vue index 62cdb8930..091a8f5f7 100644 --- a/src/entries/options/views/Settings/SetBase/DownloadWindow.vue +++ b/src/entries/options/views/Settings/SetBase/DownloadWindow.vue @@ -31,7 +31,7 @@ async function clearLastDownloader(v: boolean) { - {{ t("SetBase.download.localDownloadTitle") }} + {{ t("SetBase.download.myClientTitle") }} Date: Sat, 11 Apr 2026 09:27:19 +0000 Subject: [PATCH 17/38] feat(MyClient): add totalUploaded/totalDownloaded columns, column selector, and raw JSON viewer Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/4eade5c8-2ffd-49e7-bcc5-80cfcaca58ac Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- src/entries/options/stores/config.ts | 1 + .../options/views/Overview/MyClient/Index.vue | 88 +++++++++++++++++-- src/locales/en.json | 12 ++- src/locales/zh_CN.json | 17 ++-- 4 files changed, 104 insertions(+), 14 deletions(-) diff --git a/src/entries/options/stores/config.ts b/src/entries/options/stores/config.ts index 1b3818ca7..2d16e5a39 100644 --- a/src/entries/options/stores/config.ts +++ b/src/entries/options/stores/config.ts @@ -141,6 +141,7 @@ export const useConfigStore = defineStore("config", { }, MyClient: { itemsPerPage: 25, + columns: ["clientId", "name", "totalSize", "progress", "state", "ratio", "uploadSpeed", "downloadSpeed", "dateAdded", "action"], sortBy: [{ key: "dateAdded", order: "desc" }], }, SetSearchSolution: { diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index 624ca6845..40cdd4193 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -36,6 +36,15 @@ const toDeleteTorrents = ref([]); // push to downloader dialog const showPushToDownloaderDialog = ref(false); +// raw JSON dialog +const showRawDialog = ref(false); +const rawTorrent = ref(null); + +function openRawDialog(item: CTorrent) { + rawTorrent.value = item; + showRawDialog.value = true; +} + // ── auto-refresh ─────────────────────────────────────────────────────────── /** * Global refresh interval in seconds (0 = off). @@ -76,10 +85,10 @@ const filteredTorrents = computed(() => { }); // ── table headers ───────────────────────────────────────────────────────── -const tableHeader = computed( +const fullTableHeader = computed( () => [ - { title: t("MyClient.table.client"), key: "clientId", align: "center", width: "120" }, + { title: t("MyClient.table.client"), key: "clientId", align: "center", width: "120", props: { disabled: true } }, { title: t("MyClient.table.name"), key: "name", align: "start", minWidth: "20rem" }, { title: t("MyClient.table.size"), key: "totalSize", align: "end", width: "110" }, { title: t("MyClient.table.progress"), key: "progress", align: "end", width: "90" }, @@ -87,10 +96,18 @@ const tableHeader = computed( { title: t("MyClient.table.ratio"), key: "ratio", align: "end", width: "80" }, { title: t("MyClient.table.upSpeed"), key: "uploadSpeed", align: "end", width: "100" }, { title: t("MyClient.table.dlSpeed"), key: "downloadSpeed", align: "end", width: "100" }, - // { title: t("MyClient.table.savePath"), key: "savePath", align: "start" }, + { title: t("MyClient.table.totalUploaded"), key: "totalUploaded", align: "end", width: "120" }, + { title: t("MyClient.table.totalDownloaded"), key: "totalDownloaded", align: "end", width: "140" }, + { title: t("MyClient.table.savePath"), key: "savePath", align: "start" }, { title: t("MyClient.table.addedAt"), key: "dateAdded", align: "center", width: "160" }, - { title: t("common.action"), key: "action", align: "center", sortable: false, width: "120" }, - ] as DataTableHeader[], + { title: t("common.action"), key: "action", align: "center", sortable: false, width: "120", props: { disabled: true } }, + ] as (DataTableHeader & { props?: any })[], +); + +const tableHeader = computed(() => + fullTableHeader.value.filter( + (item) => item?.props?.disabled || (configStore.tableBehavior["MyClient"] as any)?.columns?.includes(item.key), + ) as DataTableHeader[], ); // ── per-downloader helpers ──────────────────────────────────────────────── @@ -381,6 +398,32 @@ function torrentKey(torrent: CTorrent) { + + + + + - + + + + + + @@ -528,6 +589,23 @@ function torrentKey(torrent: CTorrent) { /> + + + + + + + {{ t("MyClient.action.viewRaw") }} + + + + +
{{ rawTorrent ? JSON.stringify(rawTorrent, null, 2) : "" }}
+
+
+
diff --git a/src/locales/en.json b/src/locales/en.json index 3c98fe23e..428376eb5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1153,7 +1153,9 @@ "upSpeed": "Upload Speed", "dlSpeed": "Download Speed", "savePath": "Save Path", - "addedAt": "Added At" + "addedAt": "Added At", + "totalUploaded": "Total Upload", + "totalDownloaded": "Total Download" }, "state": { "downloading": "Downloading", @@ -1173,7 +1175,8 @@ "resumeSuccess": "Resumed successfully", "resumeError": "Failed to resume", "pauseSelectedSuccess": "Paused {count} torrent(s)", - "resumeSelectedSuccess": "Resumed {count} torrent(s)" + "resumeSelectedSuccess": "Resumed {count} torrent(s)", + "viewRaw": "View Raw" }, "autoRefresh": { "btnTitle": "Auto-Refresh", @@ -1198,6 +1201,7 @@ "urlInputHint": "One URL or magnet link per line", "fileInputLabel": "Torrent Files", "fileInputHint": "Select one or more .torrent files to push" - } + }, + "columnSelector": "Columns" } -} +} \ No newline at end of file diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index ff5bd275f..5ae950174 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -880,7 +880,10 @@ "searchEntries": "搜索入口", "flushFavicon": "刷新站点图标" }, - "settingNote": ["只有配置过的站点才会显示插件图标及相应的功能;", "已离线的站点不再参与搜索和信息获取;"], + "settingNote": [ + "只有配置过的站点才会显示插件图标及相应的功能;", + "已离线的站点不再参与搜索和信息获取;" + ], "oneClickImport": "一键导入站点", "reBuildMap": "重建站点映射" }, @@ -1150,7 +1153,9 @@ "upSpeed": "上传速度", "dlSpeed": "下载速度", "savePath": "保存路径", - "addedAt": "添加时间" + "addedAt": "添加时间", + "totalUploaded": "总上传量", + "totalDownloaded": "总下载量" }, "state": { "downloading": "下载中", @@ -1170,7 +1175,8 @@ "resumeSuccess": "开始成功", "resumeError": "开始失败", "pauseSelectedSuccess": "已暂停 {count} 个种子", - "resumeSelectedSuccess": "已开始 {count} 个种子" + "resumeSelectedSuccess": "已开始 {count} 个种子", + "viewRaw": "查看原始数据" }, "autoRefresh": { "btnTitle": "自动刷新", @@ -1195,6 +1201,7 @@ "urlInputHint": "每行一个链接或磁力链接", "fileInputLabel": "种子文件", "fileInputHint": "选择一个或多个 .torrent 文件进行推送" - } + }, + "columnSelector": "自定义列" } -} +} \ No newline at end of file From 9e9ce8ef73750d423277ddb07bc7ba3fddd65e7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 09:29:33 +0000 Subject: [PATCH 18/38] refactor: use typed computed for MyClient table behavior, fix Vuetify 3 classes, move style to scoped CSS Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/4eade5c8-2ffd-49e7-bcc5-80cfcaca58ac Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../options/views/Overview/MyClient/Index.vue | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index 40cdd4193..24c39e021 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -84,6 +84,8 @@ const filteredTorrents = computed(() => { ); }); +const myClientTableBehavior = computed(() => configStore.tableBehavior["MyClient"] as { itemsPerPage: number; columns: string[]; sortBy?: any[] }); + // ── table headers ───────────────────────────────────────────────────────── const fullTableHeader = computed( () => @@ -106,7 +108,7 @@ const fullTableHeader = computed( const tableHeader = computed(() => fullTableHeader.value.filter( - (item) => item?.props?.disabled || (configStore.tableBehavior["MyClient"] as any)?.columns?.includes(item.key), + (item) => item?.props?.disabled || myClientTableBehavior.value?.columns?.includes(item.key as string), ) as DataTableHeader[], ); @@ -400,7 +402,7 @@ function torrentKey(torrent: CTorrent) { {{ item.title }} - - (+{{ (configStore.tableBehavior['MyClient'] as any).columns!.length - 1 }}) + + (+{{ myClientTableBehavior.columns!.length - 1 }}) @@ -442,10 +444,10 @@ function torrentKey(torrent: CTorrent) { v-model="tableSelected" :headers="tableHeader" :items="filteredTorrents" - :items-per-page="configStore.tableBehavior['MyClient']?.itemsPerPage ?? 25" + :items-per-page="myClientTableBehavior.itemsPerPage ?? 25" :loading="loading" :multi-sort="configStore.enableTableMultiSort" - :sort-by="configStore.tableBehavior['MyClient']?.sortBy" + :sort-by="myClientTableBehavior.sortBy" class="table-stripe table-header-no-wrap" hover return-object @@ -602,10 +604,15 @@ function torrentKey(torrent: CTorrent) { -
{{ rawTorrent ? JSON.stringify(rawTorrent, null, 2) : "" }}
+
{{ rawTorrent ? JSON.stringify(rawTorrent, null, 2) : "" }}
- + From bf2c08359f60d085b3dfa423ea2933c689713c2c Mon Sep 17 00:00:00 2001 From: Rhilip Date: Sat, 11 Apr 2026 17:50:10 +0800 Subject: [PATCH 19/38] feat: enhance MyClient table with improved column management and UI updates --- .../options/views/Overview/MyClient/Index.vue | 143 +++++++++--------- src/locales/en.json | 5 +- src/locales/zh_CN.json | 8 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index 24c39e021..f6a772baf 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -10,7 +10,6 @@ import { useMetadataStore } from "@/options/stores/metadata.ts"; import { useRuntimeStore } from "@/options/stores/runtime.ts"; import { useConfigStore } from "@/options/stores/config.ts"; -import NavButton from "@/options/components/NavButton.vue"; import DeleteDialog from "./DeleteDialog.vue"; import PushToDownloaderDialog from "./PushToDownloaderDialog.vue"; import TorrentStateTd from "./TorrentStateTd.vue"; @@ -84,32 +83,38 @@ const filteredTorrents = computed(() => { ); }); -const myClientTableBehavior = computed(() => configStore.tableBehavior["MyClient"] as { itemsPerPage: number; columns: string[]; sortBy?: any[] }); - // ── table headers ───────────────────────────────────────────────────────── const fullTableHeader = computed( () => [ { title: t("MyClient.table.client"), key: "clientId", align: "center", width: "120", props: { disabled: true } }, - { title: t("MyClient.table.name"), key: "name", align: "start", minWidth: "20rem" }, + { title: t("MyClient.table.name"), key: "name", align: "start", minWidth: "20rem", props: { disabled: true } }, { title: t("MyClient.table.size"), key: "totalSize", align: "end", width: "110" }, { title: t("MyClient.table.progress"), key: "progress", align: "end", width: "90" }, { title: t("MyClient.table.status"), key: "state", align: "center", width: "110" }, - { title: t("MyClient.table.ratio"), key: "ratio", align: "end", width: "80" }, { title: t("MyClient.table.upSpeed"), key: "uploadSpeed", align: "end", width: "100" }, { title: t("MyClient.table.dlSpeed"), key: "downloadSpeed", align: "end", width: "100" }, - { title: t("MyClient.table.totalUploaded"), key: "totalUploaded", align: "end", width: "120" }, - { title: t("MyClient.table.totalDownloaded"), key: "totalDownloaded", align: "end", width: "140" }, + { title: t("MyClient.table.totalUploaded"), key: "totalUploaded", align: "end", width: "100" }, + { title: t("MyClient.table.totalDownloaded"), key: "totalDownloaded", align: "end", width: "100" }, + { title: t("MyClient.table.ratio"), key: "ratio", align: "end", width: "60" }, { title: t("MyClient.table.savePath"), key: "savePath", align: "start" }, { title: t("MyClient.table.addedAt"), key: "dateAdded", align: "center", width: "160" }, - { title: t("common.action"), key: "action", align: "center", sortable: false, width: "120", props: { disabled: true } }, + { + title: t("common.action"), + key: "action", + align: "center", + sortable: false, + width: "120", + props: { disabled: true }, + }, ] as (DataTableHeader & { props?: any })[], ); -const tableHeader = computed(() => - fullTableHeader.value.filter( - (item) => item?.props?.disabled || myClientTableBehavior.value?.columns?.includes(item.key as string), - ) as DataTableHeader[], +const tableHeader = computed( + () => + fullTableHeader.value.filter( + (item) => item?.props?.disabled || (configStore.tableBehavior["MyClient"] as any)?.columns?.includes(item.key), + ) as DataTableHeader[], ); // ── per-downloader helpers ──────────────────────────────────────────────── @@ -291,44 +296,46 @@ function torrentKey(torrent: CTorrent) { - - - - - - - + + - + @@ -371,6 +378,34 @@ function torrentKey(torrent: CTorrent) { + + + + + + + - - - - - - - - - - diff --git a/src/locales/en.json b/src/locales/en.json index 428376eb5..cdd2e83e8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -658,7 +658,7 @@ "extensionTip": "The extension downloads the torrent in the background and proxies it to @:SetBase.download.localDownloadMethodOptions.browser for downloading" }, "saveDownloadHistory": "Save download history (Please do not disable this function unless necessary)", - "myClientTitle": "My Downloader", + "MyClientTitle": "My Downloader", "startupAutoFetchDownloaderStatus": "Automatically load the downloader status when entering the downloader settings page", "initDownloaderTorrentOnEnter": "Automatically load torrents from the downloader when entering the My Downloader page", "localDownloadTitle": "Local Download", @@ -1142,7 +1142,6 @@ "refresh": "Refresh", "deleteSelected": "Delete", "searchPlaceholder": "Search by name, hash, label, or path", - "emptyNotice": "No torrents found. Please add a downloader or click Refresh.", "table": { "client": "Client", "name": "Torrent Name", @@ -1204,4 +1203,4 @@ }, "columnSelector": "Columns" } -} \ No newline at end of file +} diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index 5ae950174..a051e5ac1 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -880,10 +880,7 @@ "searchEntries": "搜索入口", "flushFavicon": "刷新站点图标" }, - "settingNote": [ - "只有配置过的站点才会显示插件图标及相应的功能;", - "已离线的站点不再参与搜索和信息获取;" - ], + "settingNote": ["只有配置过的站点才会显示插件图标及相应的功能;", "已离线的站点不再参与搜索和信息获取;"], "oneClickImport": "一键导入站点", "reBuildMap": "重建站点映射" }, @@ -1142,7 +1139,6 @@ "refresh": "刷新", "deleteSelected": "删除", "searchPlaceholder": "搜索种子名称、Hash、标签、路径", - "emptyNotice": "当前没有种子,请先添加下载器或刷新", "table": { "client": "下载器", "name": "种子名称", @@ -1204,4 +1200,4 @@ }, "columnSelector": "自定义列" } -} \ No newline at end of file +} From f2a99a6544d2dd5bf5293d87238d20d449e1e81a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 09:54:27 +0000 Subject: [PATCH 20/38] feat(MyClient): move downloader filter chips into auto-refresh menu popup Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/242c1ef6-e139-4755-96af-c3718c580e3f Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../options/views/Overview/MyClient/Index.vue | 58 +++++++++---------- src/locales/en.json | 5 +- src/locales/zh_CN.json | 10 +++- 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index f6a772baf..4ff9a7ffd 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -373,6 +373,35 @@ function torrentKey(torrent: CTorrent) { {{ autoRefreshRunning ? t("MyClient.autoRefresh.stop") : t("MyClient.autoRefresh.start") }} + + + + {{ t("MyClient.autoRefresh.downloaderFilter") }} + + + + {{ d.name }} + + {{ t("MyClient.autoRefresh.suspendedTip") }} + + + + @@ -404,35 +433,6 @@ function torrentKey(torrent: CTorrent) { - - - - - - - {{ d.name }} - - {{ t("MyClient.autoRefresh.suspendedTip") }} - - - - - Date: Sat, 11 Apr 2026 11:36:13 +0000 Subject: [PATCH 21/38] feat(MyClient): move downloader server status from SetDownloader to MyClient Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/bd5b2fe0-e312-49a9-a239-218e3d86ba7f Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../options/views/Overview/MyClient/Index.vue | 13 +++++++++++++ .../options/views/Settings/SetDownloader/Index.vue | 6 ------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index 4ff9a7ffd..6e7020b8e 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -13,6 +13,7 @@ import { useConfigStore } from "@/options/stores/config.ts"; import DeleteDialog from "./DeleteDialog.vue"; import PushToDownloaderDialog from "./PushToDownloaderDialog.vue"; import TorrentStateTd from "./TorrentStateTd.vue"; +import ClientStatusSpan from "@/options/views/Settings/SetDownloader/ClientStatusSpan.vue"; const { t } = useI18n(); const metadataStore = useMetadataStore(); @@ -449,6 +450,18 @@ function torrentKey(torrent: CTorrent) { + +
+ +
+ - - + + + +
{{ t("MyClient.autoRefresh.downloaderFilter") }}
+ + + + {{ d.name }} + + {{ t("MyClient.autoRefresh.suspendedTip") }} + + + +
diff --git a/src/entries/options/views/Overview/MyClient/Index.vue b/src/entries/options/views/Overview/MyClient/Index.vue index f4296cdad..0fe20d945 100644 --- a/src/entries/options/views/Overview/MyClient/Index.vue +++ b/src/entries/options/views/Overview/MyClient/Index.vue @@ -399,35 +399,6 @@ function torrentKey(torrent: CTorrent) { {{ autoRefreshRunning ? t("MyClient.autoRefresh.stop") : t("MyClient.autoRefresh.start") }} - - - - {{ t("MyClient.autoRefresh.downloaderFilter") }} - - - - {{ d.name }} - - {{ t("MyClient.autoRefresh.suspendedTip") }} - - - - @@ -623,7 +594,12 @@ function torrentKey(torrent: CTorrent) { - + From 78bab878ad6272252862997f79e19531c9b38076 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 12:53:40 +0000 Subject: [PATCH 25/38] refactor(MyClient): abstract refresh state to utils.ts, integrate filter/suspend into ClientStatusDialog list items Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/7c0a73a9-773c-46e0-aa49-01b71dcda6ec Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- .../Overview/MyClient/ClientStatusDialog.vue | 93 +++++------ .../options/views/Overview/MyClient/Index.vue | 151 +++--------------- .../options/views/Overview/MyClient/utils.ts | 143 ++++++++++++++++- src/locales/en.json | 1 + src/locales/zh_CN.json | 1 + 5 files changed, 215 insertions(+), 174 deletions(-) diff --git a/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue b/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue index a1cf0d6f5..b494c240e 100644 --- a/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue +++ b/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue @@ -1,34 +1,37 @@ - - - - diff --git a/src/locales/en.json b/src/locales/en.json index 13857d529..70fa7d53f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -798,9 +798,6 @@ "custom": "Custom text" } } - }, - "ClientStatusSpan": { - "flushInterval": "Flush Interval (s)" } }, "SetSearchSolution": { diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index dcd62a673..308e7ced6 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -798,9 +798,6 @@ "custom": "自定义的内容替换" } } - }, - "ClientStatusSpan": { - "flushInterval": "刷新间隔 (s)" } }, "SetSearchSolution": { @@ -880,10 +877,7 @@ "searchEntries": "搜索入口", "flushFavicon": "刷新站点图标" }, - "settingNote": [ - "只有配置过的站点才会显示插件图标及相应的功能;", - "已离线的站点不再参与搜索和信息获取;" - ], + "settingNote": ["只有配置过的站点才会显示插件图标及相应的功能;", "已离线的站点不再参与搜索和信息获取;"], "oneClickImport": "一键导入站点", "reBuildMap": "重建站点映射" }, From f601cd8377e32890622a1d32b59f85c1ad613076 Mon Sep 17 00:00:00 2001 From: Rhilip Date: Fri, 8 May 2026 22:32:31 +0800 Subject: [PATCH 33/38] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/packages/downloader/entity/qBittorrent.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/packages/downloader/entity/qBittorrent.ts b/src/packages/downloader/entity/qBittorrent.ts index 0a86bccb4..9ed10aed5 100644 --- a/src/packages/downloader/entity/qBittorrent.ts +++ b/src/packages/downloader/entity/qBittorrent.ts @@ -553,18 +553,8 @@ export default class QBittorrent extends AbstractBittorrentClient { - try { - // 解码 URL 编码内容 - trackers.push(decodeURIComponent(tr)); - } catch (e) { - trackers.push(tr); // 解码失败保留原值 - } - }); - - return trackers; + // URLSearchParams 已经会对参数值进行解码,这里直接返回即可,避免二次解码导致 tracker URL 被破坏 + return params.getAll("tr"); } // 不然,则从客户端直接请求获取 From 932e3054feeda2f48840498a822896e633a577ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 14:49:46 +0000 Subject: [PATCH 34/38] feat(offscreen): add downloaderInstance cache keyed by config content Agent-Logs-Url: https://github.com/pt-plugins/PT-depiler/sessions/0121aeb3-aa84-4903-af09-b506e949f2df Co-authored-by: Rhilip <13842140+Rhilip@users.noreply.github.com> --- src/entries/offscreen/utils/download.ts | 27 +++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/entries/offscreen/utils/download.ts b/src/entries/offscreen/utils/download.ts index ce20af082..e332fe36e 100644 --- a/src/entries/offscreen/utils/download.ts +++ b/src/entries/offscreen/utils/download.ts @@ -40,10 +40,28 @@ export async function getDownloaderConfig(downloaderId: string) { return metadataStore?.downloaders?.[downloaderId] ?? ({} as IDownloaderMetadata); } -export async function getDownloaderInstance(downloaderId: string) { +type DownloaderInstance = Awaited>; + +const downloaderInstanceCache = new Map(); + +function getDownloaderConfigKey(config: IDownloaderMetadata): string { + const { id, type, address, username, password, timeout } = config; + return JSON.stringify({ id, type, address, username, password, timeout }); +} + +export async function getDownloaderInstance(downloaderId: string): Promise { const downloaderConfig = await getDownloaderConfig(downloaderId); - if (!downloaderConfig.id) return false; - return await getDownloader(downloaderConfig); + if (!downloaderConfig.id) return null; + + const configKey = getDownloaderConfigKey(downloaderConfig); + const cached = downloaderInstanceCache.get(downloaderId); + if (cached && cached.configKey === configKey) { + return cached.instance; + } + + const instance = await getDownloader(downloaderConfig); + downloaderInstanceCache.set(downloaderId, { configKey, instance }); + return instance; } onMessage("getDownloaderConfig", async ({ data: downloaderId }) => await getDownloaderConfig(downloaderId)); @@ -341,7 +359,8 @@ async function downloadTorrentToRemote( const downloaderConfig = await getDownloaderConfig(downloaderId); if (downloaderConfig.id && downloaderConfig.enabled) { - const downloaderInstance = await getDownloader(downloaderConfig); + const downloaderInstance = await getDownloaderInstance(downloaderId); + if (!downloaderInstance) return downloadStatus; if (addTorrentOptions.localDownload) { addTorrentOptions.localDownloadOption = downloadRequestConfig; } From 42450197e97459b4de7b01dd765c7aa41556062c Mon Sep 17 00:00:00 2001 From: Rhilip Date: Fri, 8 May 2026 23:15:10 +0800 Subject: [PATCH 35/38] fix(ClientStatusDialog): disable stop button when auto-refresh is not running --- .../views/Overview/MyClient/ClientStatusDialog.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue b/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue index fb78b7714..c9361afc3 100644 --- a/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue +++ b/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue @@ -6,7 +6,13 @@ import { getDownloaderIcon, type TorrentClientStatus } from "@ptd/downloader"; import { sendMessage } from "@/messages.ts"; import { formatSize } from "@/options/utils.ts"; -import { torrents, selectedDownloaderIds, suspendedDownloaders, useClientRefresh } from "./utils.ts"; +import { + torrents, + selectedDownloaderIds, + suspendedDownloaders, + useClientRefresh, + autoRefreshRunning, +} from "./utils.ts"; const showDialog = defineModel(); @@ -164,6 +170,7 @@ watch(showDialog, (v) => { Date: Fri, 15 May 2026 21:32:00 +0800 Subject: [PATCH 36/38] refactor(core/downloaderSetting): clean up configuration and improve downloader status handling --- src/entries/options/stores/config.ts | 14 ++++++++++++-- .../views/Overview/MyClient/ClientStatusDialog.vue | 10 +++++----- .../views/Settings/SetBase/DownloadWindow.vue | 6 ------ .../options/views/Settings/SetDownloader/Index.vue | 6 +++--- src/entries/shared/types/storages/config.ts | 2 -- src/locales/en.json | 1 - src/locales/zh_CN.json | 1 - 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/entries/options/stores/config.ts b/src/entries/options/stores/config.ts index 2d16e5a39..ae4a7143e 100644 --- a/src/entries/options/stores/config.ts +++ b/src/entries/options/stores/config.ts @@ -141,7 +141,18 @@ export const useConfigStore = defineStore("config", { }, MyClient: { itemsPerPage: 25, - columns: ["clientId", "name", "totalSize", "progress", "state", "ratio", "uploadSpeed", "downloadSpeed", "dateAdded", "action"], + columns: [ + "clientId", + "name", + "totalSize", + "progress", + "state", + "ratio", + "uploadSpeed", + "downloadSpeed", + "dateAdded", + "action", + ], sortBy: [{ key: "dateAdded", order: "desc" }], }, SetSearchSolution: { @@ -248,7 +259,6 @@ export const useConfigStore = defineStore("config", { download: { saveDownloadHistory: true, - startupAutoFetchDownloaderStatus: false, initDownloaderTorrentOnEnter: false, saveLastDownloader: false, allowDirectSendToClient: false, diff --git a/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue b/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue index c9361afc3..59087c32e 100644 --- a/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue +++ b/src/entries/options/views/Overview/MyClient/ClientStatusDialog.vue @@ -1,5 +1,5 @@