fix: batch cancel

This commit is contained in:
putyy
2025-10-16 12:23:48 +08:00
committed by putyy
parent 3b4443110e
commit 820a2671cf
7 changed files with 48 additions and 49 deletions

View File

@@ -335,7 +335,7 @@ func (h *HttpServer) delete(w http.ResponseWriter, r *http.Request) {
func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) { func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
var data struct { var data struct {
MediaInfo shared.MediaInfo
DecodeStr string `json:"decodeStr"` DecodeStr string `json:"decodeStr"`
} }
if err := json.NewDecoder(r.Body).Decode(&data); err != nil { if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
@@ -348,7 +348,7 @@ func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
func (h *HttpServer) cancel(w http.ResponseWriter, r *http.Request) { func (h *HttpServer) cancel(w http.ResponseWriter, r *http.Request) {
var data struct { var data struct {
MediaInfo shared.MediaInfo
} }
if err := json.NewDecoder(r.Body).Decode(&data); err != nil { if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
@@ -366,7 +366,7 @@ func (h *HttpServer) cancel(w http.ResponseWriter, r *http.Request) {
func (h *HttpServer) wxFileDecode(w http.ResponseWriter, r *http.Request) { func (h *HttpServer) wxFileDecode(w http.ResponseWriter, r *http.Request) {
var data struct { var data struct {
MediaInfo shared.MediaInfo
Filename string `json:"filename"` Filename string `json:"filename"`
DecodeStr string `json:"decodeStr"` DecodeStr string `json:"decodeStr"`
} }

View File

@@ -21,23 +21,6 @@ type Proxy struct {
Is bool Is bool
} }
type MediaInfo struct {
Id string
Url string
UrlSign string
CoverUrl string
Size string
Domain string
Classify string
Suffix string
SavePath string
Status string
DecodeKey string
Description string
ContentType string
OtherData map[string]string
}
var pluginRegistry = make(map[string]shared.Plugin) var pluginRegistry = make(map[string]shared.Plugin)
func init() { func init() {

View File

@@ -97,11 +97,11 @@ func (r *Resource) cancel(id string) error {
return errors.New("task not found") return errors.New("task not found")
} }
func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) { func (r *Resource) download(mediaInfo shared.MediaInfo, decodeStr string) {
if globalConfig.SaveDirectory == "" { if globalConfig.SaveDirectory == "" {
return return
} }
go func(mediaInfo MediaInfo) { go func(mediaInfo shared.MediaInfo) {
rawUrl := mediaInfo.Url rawUrl := mediaInfo.Url
fileName := shared.Md5(rawUrl) fileName := shared.Md5(rawUrl)
@@ -180,7 +180,7 @@ func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
}(mediaInfo) }(mediaInfo)
} }
func (r *Resource) parseHeaders(mediaInfo MediaInfo) (map[string]string, error) { func (r *Resource) parseHeaders(mediaInfo shared.MediaInfo) (map[string]string, error) {
headers := make(map[string]string) headers := make(map[string]string)
if hh, ok := mediaInfo.OtherData["headers"]; ok { if hh, ok := mediaInfo.OtherData["headers"]; ok {
@@ -199,7 +199,7 @@ func (r *Resource) parseHeaders(mediaInfo MediaInfo) (map[string]string, error)
return headers, nil return headers, nil
} }
func (r *Resource) wxFileDecode(mediaInfo MediaInfo, fileName, decodeStr string) (string, error) { func (r *Resource) wxFileDecode(mediaInfo shared.MediaInfo, fileName, decodeStr string) (string, error) {
sourceFile, err := os.Open(fileName) sourceFile, err := os.Open(fileName)
if err != nil { if err != nil {
return "", err return "", err
@@ -224,7 +224,7 @@ func (r *Resource) wxFileDecode(mediaInfo MediaInfo, fileName, decodeStr string)
return mediaInfo.SavePath, nil return mediaInfo.SavePath, nil
} }
func (r *Resource) progressEventsEmit(mediaInfo MediaInfo, args ...string) { func (r *Resource) progressEventsEmit(mediaInfo shared.MediaInfo, args ...string) {
Status := shared.DownloadStatusError Status := shared.DownloadStatusError
Message := "ok" Message := "ok"

View File

@@ -15,6 +15,7 @@ declare module 'vue' {
NaiveProvider: typeof import('./src/components/NaiveProvider.vue')['default'] NaiveProvider: typeof import('./src/components/NaiveProvider.vue')['default']
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup'] NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable'] NDataTable: typeof import('naive-ui')['NDataTable']
NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NDialogProvider: typeof import('naive-ui')['NDialogProvider']

View File

@@ -63,6 +63,7 @@
"save_path_empty": "Please set save location", "save_path_empty": "Please set save location",
"operation": "Operation", "operation": "Operation",
"ready": "Ready", "ready": "Ready",
"pending": "Pending",
"running": "Running", "running": "Running",
"error": "Error", "error": "Error",
"done": "Done", "done": "Done",

View File

@@ -63,6 +63,7 @@
"save_path_empty": "请设置保存位置", "save_path_empty": "请设置保存位置",
"operation": "操作", "operation": "操作",
"ready": "就绪", "ready": "就绪",
"pending": "待处理",
"running": "运行中", "running": "运行中",
"error": "错误", "error": "错误",
"done": "完成", "done": "完成",

View File

@@ -4,10 +4,10 @@
<NSpace> <NSpace>
<NButton v-if="isProxy" secondary type="primary" @click.stop="close" style="--wails-draggable:no-drag"> <NButton v-if="isProxy" secondary type="primary" @click.stop="close" style="--wails-draggable:no-drag">
<span class="inline-block w-1.5 h-1.5 bg-red-600 rounded-full mr-1 animate-pulse"></span> <span class="inline-block w-1.5 h-1.5 bg-red-600 rounded-full mr-1 animate-pulse"></span>
{{ t("index.close_grab") }}{{ data.length > 0 ? `&nbsp;${t('index.total_resources', {count:data.length})}` : ''}} {{ t("index.close_grab") }}{{ data.length > 0 ? `&nbsp;${t('index.total_resources', {count: data.length})}` : '' }}
</NButton> </NButton>
<NButton v-else tertiary type="tertiary" @click.stop="open" style="--wails-draggable:no-drag"> <NButton v-else tertiary type="tertiary" @click.stop="open" style="--wails-draggable:no-drag">
{{ t("index.open_grab") }}{{ data.length > 0 ? `&nbsp;${t('index.total_resources', {count:data.length})}` : ''}} {{ t("index.open_grab") }}{{ data.length > 0 ? `&nbsp;${t('index.total_resources', {count: data.length})}` : '' }}
</NButton> </NButton>
<NSelect style="min-width: 100px;--wails-draggable:no-drag" :placeholder="t('index.grab_type')" v-model:value="resourcesType" multiple clearable <NSelect style="min-width: 100px;--wails-draggable:no-drag" :placeholder="t('index.grab_type')" v-model:value="resourcesType" multiple clearable
:max-tag-count="3" :options="classify"></NSelect> :max-tag-count="3" :options="classify"></NSelect>
@@ -46,7 +46,7 @@
<NCheckbox <NCheckbox
v-model:checked="rememberChoiceTmp" v-model:checked="rememberChoiceTmp"
> >
<span class="text-gray-400">{{t('index.remember_clear_choice')}}</span> <span class="text-gray-400">{{ t('index.remember_clear_choice') }}</span>
</NCheckbox> </NCheckbox>
</div> </div>
</n-popconfirm> </n-popconfirm>
@@ -138,7 +138,7 @@
import {NButton, NIcon, NImage, NInput, NSpace, NTooltip, NPopover, NGradientText} from "naive-ui" import {NButton, NIcon, NImage, NInput, NSpace, NTooltip, NPopover, NGradientText} from "naive-ui"
import {computed, h, onMounted, ref, watch} from "vue" import {computed, h, onMounted, ref, watch} from "vue"
import type {appType} from "@/types/app" import type {appType} from "@/types/app"
import type {DataTableRowKey, ImageRenderToolbarProps, DataTableFilterState,DataTableBaseColumn} from "naive-ui" import type {DataTableRowKey, ImageRenderToolbarProps, DataTableFilterState, DataTableBaseColumn} from "naive-ui"
import Preview from "@/components/Preview.vue" import Preview from "@/components/Preview.vue"
import ShowLoading from "@/components/ShowLoading.vue" import ShowLoading from "@/components/ShowLoading.vue"
// @ts-ignore // @ts-ignore
@@ -160,7 +160,7 @@ import {
Apps, Apps,
TrashOutline, CloseOutline TrashOutline, CloseOutline
} from "@vicons/ionicons5" } from "@vicons/ionicons5"
import { useDialog } from 'naive-ui' import {useDialog} from 'naive-ui'
import * as bind from "../../wailsjs/go/core/Bind" import * as bind from "../../wailsjs/go/core/Bind"
import {Quit} from "../../wailsjs/runtime" import {Quit} from "../../wailsjs/runtime"
import {DialogOptions} from "naive-ui/es/dialog/src/DialogProvider" import {DialogOptions} from "naive-ui/es/dialog/src/DialogProvider"
@@ -210,6 +210,7 @@ const classifyAlias: { [key: string]: any } = {
const dwStatus = computed<any>(() => { const dwStatus = computed<any>(() => {
return { return {
ready: t("index.ready"), ready: t("index.ready"),
pending: t("index.pending"),
running: t("index.running"), running: t("index.running"),
error: t("index.error"), error: t("index.error"),
done: t("index.done"), done: t("index.done"),
@@ -238,7 +239,7 @@ const columns = ref<any[]>([
}, },
{ {
title: computed(() => { title: computed(() => {
return checkedRowKeysValue.value.length > 0 ? h(NGradientText, {type:"success"}, t("index.choice") + `(${checkedRowKeysValue.value.length})`) : t("index.domain") return checkedRowKeysValue.value.length > 0 ? h(NGradientText, {type: "success"}, t("index.choice") + `(${checkedRowKeysValue.value.length})`) : t("index.domain")
}), }),
key: "Domain", key: "Domain",
width: 90, width: 90,
@@ -307,11 +308,18 @@ const columns = ref<any[]>([
key: "Status", key: "Status",
width: 80, width: 80,
render: (row: appType.MediaInfo, index: number) => { render: (row: appType.MediaInfo, index: number) => {
let status = "info"
if (row.Status === "done" || row.Status === "running") {
status = "success"
} else if (row.Status === "pending") {
status = "warning"
}
return h( return h(
NButton, NButton,
{ {
tertiary: true, tertiary: true,
type: row.Status === "done" ? "success" : "info", type: status as any,
size: "small", size: "small",
style: { style: {
margin: "2px" margin: "2px"
@@ -336,7 +344,7 @@ const columns = ref<any[]>([
title: () => h('div', {class: 'flex items-center'}, [ title: () => h('div', {class: 'flex items-center'}, [
t('index.description'), t('index.description'),
h(NPopover, { h(NPopover, {
style:"--wails-draggable:no-drag", style: "--wails-draggable:no-drag",
trigger: 'click', trigger: 'click',
placement: 'bottom', placement: 'bottom',
showArrow: true, showArrow: true,
@@ -434,8 +442,8 @@ onMounted(() => {
}) })
checkLoading() checkLoading()
watch(showPassword, ()=>{ watch(showPassword, () => {
if (!showPassword.value){ if (!showPassword.value) {
checkLoading() checkLoading()
} }
}) })
@@ -532,7 +540,7 @@ watch(resourcesType, (n, o) => {
appApi.setType(resourcesType.value) appApi.setType(resourcesType.value)
}) })
const updateItem = (id: string, updater: (item: any) => void)=>{ const updateItem = (id: string, updater: (item: any) => void) => {
const item = data.value.find(i => i.Id === id) const item = data.value.find(i => i.Id === id)
if (item) updater(item) if (item) updater(item)
} }
@@ -579,7 +587,7 @@ const dataAction = (row: appType.MediaInfo, index: number, type: string) => {
break break
case "cancel": case "cancel":
if (row.Status === "running") { if (row.Status === "running") {
appApi.cancel({id: row.Id}).then((res)=>{ appApi.cancel({id: row.Id}).then((res) => {
updateItem(row.Id, item => { updateItem(row.Id, item => {
item.Status = 'ready' item.Status = 'ready'
item.SavePath = '' item.SavePath = ''
@@ -648,7 +656,7 @@ const handleCheck = (rowKeys: DataTableRowKey[]) => {
checkedRowKeysValue.value = rowKeys checkedRowKeysValue.value = rowKeys
} }
const updateFilters = (filters: DataTableFilterState, initiatorColumn: DataTableBaseColumn)=>{ const updateFilters = (filters: DataTableFilterState, initiatorColumn: DataTableBaseColumn) => {
filterClassify.value = filters.Classify as string[] filterClassify.value = filters.Classify as string[]
} }
@@ -672,25 +680,29 @@ const batchDown = async () => {
checkedRowKeysValue.value = [] checkedRowKeysValue.value = []
} }
const batchCancel = () =>{ const batchCancel = async () => {
if (checkedRowKeysValue.value.length <= 0) { if (checkedRowKeysValue.value.length <= 0) {
window?.$message?.error(t("index.use_data")) window?.$message?.error(t("index.use_data"))
return return
} }
loading.value = true
data.value.forEach(async (item, index) => { const cancelTasks: Promise<any>[] = []
data.value.forEach((item, index) => {
if (checkedRowKeysValue.value.includes(item.Id) && item.Status === "running") { if (checkedRowKeysValue.value.includes(item.Id) && item.Status === "running") {
appApi.cancel({id: item.Id})
if (activeDownloads > 0) { if (activeDownloads > 0) {
activeDownloads-- activeDownloads--
} }
data.value[index].Status = 'ready' cancelTasks.push(appApi.cancel({id: item.Id}).then(() => {
data.value[index].SavePath = '' item.Status = 'ready'
item.SavePath = ''
checkQueue()
}))
} }
}) })
await Promise.allSettled(cancelTasks)
loading.value = false
checkedRowKeysValue.value = [] checkedRowKeysValue.value = []
cacheData() cacheData()
checkQueue()
} }
const batchExport = (type?: string) => { const batchExport = (type?: string) => {
@@ -709,9 +721,9 @@ const batchExport = (type?: string) => {
let jsonData = data.value.filter(item => checkedRowKeysValue.value.includes(item.Id)) let jsonData = data.value.filter(item => checkedRowKeysValue.value.includes(item.Id))
if (type === "url"){ if (type === "url") {
jsonData = jsonData.map(item => item.Url) jsonData = jsonData.map(item => item.Url)
} else{ } else {
jsonData = jsonData.map(item => encodeURIComponent(JSON.stringify(item))) jsonData = jsonData.map(item => encodeURIComponent(JSON.stringify(item)))
} }
@@ -747,6 +759,7 @@ const download = (row: appType.MediaInfo, index: number) => {
} }
if (activeDownloads >= maxConcurrentDownloads.value) { if (activeDownloads >= maxConcurrentDownloads.value) {
row.Status = "pending"
downloadQueue.value.push(row) downloadQueue.value.push(row)
window?.$message?.info(t("index.download_queued", {count: downloadQueue.value.length})) window?.$message?.info(t("index.download_queued", {count: downloadQueue.value.length}))
return return
@@ -917,8 +930,8 @@ const handleInstall = async () => {
return false return false
} }
const checkLoading = ()=>{ const checkLoading = () => {
setTimeout(()=>{ setTimeout(() => {
if (loading.value && !isInstall && !showPassword.value) { if (loading.value && !isInstall && !showPassword.value) {
dialog.warning({ dialog.warning({
title: t("index.start_err_tip"), title: t("index.start_err_tip"),