mirror of
https://github.com/putyy/res-downloader.git
synced 2026-01-12 14:14:55 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
820a2671cf | ||
|
|
3b4443110e | ||
|
|
ffd5b29030 | ||
|
|
779f56dd91 | ||
|
|
2beecdade2 |
@@ -14,7 +14,7 @@
|
||||
!define INFO_PRODUCTNAME "res-downloader"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "3.1.0"
|
||||
!define INFO_PRODUCTVERSION "3.1.1"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "Copyright © 2023"
|
||||
|
||||
21
core/http.go
21
core/http.go
@@ -84,8 +84,6 @@ func (h *HttpServer) preview(w http.ResponseWriter, r *http.Request) {
|
||||
request.Header.Set("Range", rangeHeader)
|
||||
}
|
||||
|
||||
//request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36")
|
||||
//request.Header.Set("Referer", parsedURL.Scheme+"://"+parsedURL.Host+"/")
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch the resource", http.StatusInternalServerError)
|
||||
@@ -93,12 +91,15 @@ func (h *HttpServer) preview(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
if contentRange := resp.Header.Get("Content-Range"); contentRange != "" {
|
||||
w.Header().Set("Content-Range", contentRange)
|
||||
for k, v := range resp.Header {
|
||||
if strings.ToLower(k) == "access-control-allow-origin" {
|
||||
continue
|
||||
}
|
||||
for _, vv := range v {
|
||||
w.Header().Add(k, vv)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
@@ -334,7 +335,7 @@ func (h *HttpServer) delete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
MediaInfo
|
||||
shared.MediaInfo
|
||||
DecodeStr string `json:"decodeStr"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
@@ -347,7 +348,7 @@ func (h *HttpServer) download(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *HttpServer) cancel(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
MediaInfo
|
||||
shared.MediaInfo
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
@@ -365,7 +366,7 @@ func (h *HttpServer) cancel(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *HttpServer) wxFileDecode(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
MediaInfo
|
||||
shared.MediaInfo
|
||||
Filename string `json:"filename"`
|
||||
DecodeStr string `json:"decodeStr"`
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ func Middleware(next http.Handler) http.Handler {
|
||||
func HandleApi(w http.ResponseWriter, r *http.Request) bool {
|
||||
if strings.HasPrefix(r.URL.Path, "/api") {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
if r.URL.Path != "/api/preview" {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
}
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return true
|
||||
|
||||
@@ -51,7 +51,7 @@ func (p *DefaultPlugin) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *
|
||||
Url: rawUrl,
|
||||
UrlSign: urlSign,
|
||||
CoverUrl: "",
|
||||
Size: shared.FormatSize(value),
|
||||
Size: value,
|
||||
Domain: shared.GetTopLevelDomain(rawUrl),
|
||||
Classify: classify,
|
||||
Suffix: suffix,
|
||||
|
||||
@@ -166,7 +166,7 @@ func (p *QqPlugin) handleMedia(body []byte) {
|
||||
Url: rawUrl,
|
||||
UrlSign: urlSign,
|
||||
CoverUrl: "",
|
||||
Size: "0",
|
||||
Size: 0,
|
||||
Domain: shared.GetTopLevelDomain(rawUrl),
|
||||
Classify: "video",
|
||||
Suffix: ".mp4",
|
||||
@@ -201,10 +201,10 @@ func (p *QqPlugin) handleMedia(body []byte) {
|
||||
|
||||
switch size := firstMedia["fileSize"].(type) {
|
||||
case float64:
|
||||
res.Size = shared.FormatSize(size)
|
||||
res.Size = size
|
||||
case string:
|
||||
if value, err := strconv.ParseFloat(size, 64); err == nil {
|
||||
res.Size = shared.FormatSize(value)
|
||||
res.Size = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,23 +21,6 @@ type Proxy struct {
|
||||
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)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -97,11 +97,11 @@ func (r *Resource) cancel(id string) error {
|
||||
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 == "" {
|
||||
return
|
||||
}
|
||||
go func(mediaInfo MediaInfo) {
|
||||
go func(mediaInfo shared.MediaInfo) {
|
||||
rawUrl := mediaInfo.Url
|
||||
fileName := shared.Md5(rawUrl)
|
||||
|
||||
@@ -180,7 +180,7 @@ func (r *Resource) download(mediaInfo MediaInfo, decodeStr string) {
|
||||
}(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)
|
||||
|
||||
if hh, ok := mediaInfo.OtherData["headers"]; ok {
|
||||
@@ -199,7 +199,7 @@ func (r *Resource) parseHeaders(mediaInfo MediaInfo) (map[string]string, error)
|
||||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -224,7 +224,7 @@ func (r *Resource) wxFileDecode(mediaInfo MediaInfo, fileName, decodeStr string)
|
||||
return mediaInfo.SavePath, nil
|
||||
}
|
||||
|
||||
func (r *Resource) progressEventsEmit(mediaInfo MediaInfo, args ...string) {
|
||||
func (r *Resource) progressEventsEmit(mediaInfo shared.MediaInfo, args ...string) {
|
||||
Status := shared.DownloadStatusError
|
||||
Message := "ok"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ type MediaInfo struct {
|
||||
Url string
|
||||
UrlSign string
|
||||
CoverUrl string
|
||||
Size string
|
||||
Size float64
|
||||
Domain string
|
||||
Classify string
|
||||
Suffix string
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
sysRuntime "runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -66,10 +67,41 @@ func IsDevelopment() bool {
|
||||
|
||||
func GetFileNameFromURL(rawUrl string) string {
|
||||
parsedURL, err := url.Parse(rawUrl)
|
||||
if err == nil {
|
||||
return path.Base(parsedURL.Path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
|
||||
fileName := path.Base(parsedURL.Path)
|
||||
if fileName == "" || fileName == "/" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if decoded, err := url.QueryUnescape(fileName); err == nil {
|
||||
fileName = decoded
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`[<>:"/\\|?*]`)
|
||||
fileName = re.ReplaceAllString(fileName, "_")
|
||||
|
||||
fileName = strings.TrimRightFunc(fileName, func(r rune) bool {
|
||||
return r == '.' || r == ' '
|
||||
})
|
||||
|
||||
const maxFileNameLen = 255
|
||||
runes := []rune(fileName)
|
||||
if len(runes) > maxFileNameLen {
|
||||
ext := path.Ext(fileName)
|
||||
name := strings.TrimSuffix(fileName, ext)
|
||||
|
||||
runes = []rune(name)
|
||||
if len(runes) > maxFileNameLen-len(ext) {
|
||||
runes = runes[:maxFileNameLen-len(ext)]
|
||||
}
|
||||
name = string(runes)
|
||||
fileName = name + ext
|
||||
}
|
||||
|
||||
return fileName
|
||||
}
|
||||
|
||||
func GetCurrentDateTimeFormatted() string {
|
||||
|
||||
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -15,6 +15,7 @@ declare module 'vue' {
|
||||
NaiveProvider: typeof import('./src/components/NaiveProvider.vue')['default']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</NIcon>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center justify-start p-1.5 cursor-pointer" @click="action('cancel')">
|
||||
<div class="flex items-center justify-start p-1.5 cursor-pointer" v-if="row.Status === 'running'" @click="action('cancel')">
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-red-500 dark:text-red-300 bg-red-500/20 dark:bg-red-500/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-red-500/40 transition-colors"
|
||||
|
||||
@@ -84,7 +84,7 @@ const playFlvStream = () => {
|
||||
try {
|
||||
if (!flvjs.isSupported() || !videoPlayer.value) return
|
||||
|
||||
flvPlayer = flvjs.createPlayer({ type: "flv", url: props.previewRow.Url })
|
||||
flvPlayer = flvjs.createPlayer({ type: "flv", url: window?.$baseUrl + "/api/preview?url=" + encodeURIComponent(props.previewRow.Url) })
|
||||
flvPlayer.attachMediaElement(videoPlayer.value)
|
||||
flvPlayer.load()
|
||||
flvPlayer.play()
|
||||
@@ -105,7 +105,7 @@ const setupVideoJsPlayer = () => {
|
||||
}
|
||||
|
||||
player.src({
|
||||
src: props.previewRow.Url,
|
||||
src: window?.$baseUrl + "/api/preview?url=" + encodeURIComponent(props.previewRow.Url),
|
||||
type: props.previewRow.ContentType,
|
||||
withCredentials: true,
|
||||
})
|
||||
@@ -113,7 +113,7 @@ const setupVideoJsPlayer = () => {
|
||||
}
|
||||
|
||||
const playVideoWithoutTotalLength = () => {
|
||||
rowUrl = buildUrlWithParams(props.previewRow.Url)
|
||||
rowUrl = window?.$baseUrl + "/api/preview?url=" + encodeURIComponent(buildUrlWithParams(props.previewRow.Url))
|
||||
mediaSource = new MediaSource()
|
||||
videoPlayer.value.src = URL.createObjectURL(mediaSource)
|
||||
videoPlayer.value.play()
|
||||
|
||||
@@ -26,4 +26,15 @@ export const isValidHost = (host: string) => {
|
||||
export const isValidPort = (port: number) => {
|
||||
const portNumber = Number(port)
|
||||
return Number.isInteger(portNumber) && portNumber > 1024 && portNumber < 65535
|
||||
}
|
||||
|
||||
export const formatSize = (size: number | string) => {
|
||||
if (typeof size === "string") return size
|
||||
if (size > 1048576) {
|
||||
return (size / 1048576).toFixed(2) + 'MB';
|
||||
}
|
||||
if (size > 1024) {
|
||||
return (size / 1024).toFixed(2) + 'KB';
|
||||
}
|
||||
return Math.floor(size) + 'b';
|
||||
}
|
||||
@@ -34,11 +34,13 @@
|
||||
"grab_type": "Grab Type",
|
||||
"clear_list": "Clear List",
|
||||
"clear_list_tip": "Clear all records?",
|
||||
"remember_clear_choice": "Remember this selection and clear it next time",
|
||||
"batch_download": "Batch Download",
|
||||
"batch_export": "Batch Export",
|
||||
"batch_import": "Batch Import",
|
||||
"export_url": "Export Url",
|
||||
"import_success": "Export Success",
|
||||
"total_resources": "total of {count} resources",
|
||||
"all": "All",
|
||||
"image": "Image",
|
||||
"audio": "Audio",
|
||||
@@ -50,6 +52,7 @@
|
||||
"pdf": "PDF",
|
||||
"font": "Font",
|
||||
"domain": "Domain",
|
||||
"choice": "choice",
|
||||
"type": "Type",
|
||||
"preview": "Preview",
|
||||
"preview_tip": "Preview not supported",
|
||||
@@ -60,6 +63,7 @@
|
||||
"save_path_empty": "Please set save location",
|
||||
"operation": "Operation",
|
||||
"ready": "Ready",
|
||||
"pending": "Pending",
|
||||
"running": "Running",
|
||||
"error": "Error",
|
||||
"done": "Done",
|
||||
@@ -82,13 +86,14 @@
|
||||
"import_placeholder": "When adding multiple items, ensure each line contains only one (each link on a new line)",
|
||||
"import_empty": "Please enter data to import",
|
||||
"win_install_tip": "For the first time using this software, please right-click and select 'Run as administrator'",
|
||||
"download_queued": "Download has been added to the queue, current queue length:{count}",
|
||||
"download_queued": "has been added to the queue, current queue length:{count}",
|
||||
"search": "Search",
|
||||
"search_description": "Keyword Search...",
|
||||
"start_err_tip": "Error Message",
|
||||
"start_err_content": "The current startup process has encountered an issue. Do you want to reset the application?",
|
||||
"start_err_positiveText": "Clear cache and restart",
|
||||
"start_err_negativeText": "Close the software"
|
||||
"start_err_negativeText": "Close the software",
|
||||
"reset_app_tip": "This operation will delete intercepted data and data related to this application. Please proceed with caution!"
|
||||
},
|
||||
"setting": {
|
||||
"restart_tip": "Keep default if unsure, please restart software after modification",
|
||||
|
||||
@@ -34,11 +34,13 @@
|
||||
"grab_type": "抓取类型",
|
||||
"clear_list": "清空列表",
|
||||
"clear_list_tip": "清空所有记录?",
|
||||
"remember_clear_choice": "记住此选择,下次直接清除",
|
||||
"batch_download": "批量下载",
|
||||
"batch_export": "批量导出",
|
||||
"batch_import": "批量导入",
|
||||
"export_url": "导出链接",
|
||||
"import_success": "导出成功",
|
||||
"total_resources": "共{count}个资源",
|
||||
"all": "全部",
|
||||
"image": "图片",
|
||||
"audio": "音频",
|
||||
@@ -50,6 +52,7 @@
|
||||
"pdf": "pdf",
|
||||
"font": "字体",
|
||||
"domain": "域",
|
||||
"choice": "已选",
|
||||
"type": "类型",
|
||||
"preview": "预览",
|
||||
"preview_tip": "暂不支持预览",
|
||||
@@ -60,6 +63,7 @@
|
||||
"save_path_empty": "请设置保存位置",
|
||||
"operation": "操作",
|
||||
"ready": "就绪",
|
||||
"pending": "待处理",
|
||||
"running": "运行中",
|
||||
"error": "错误",
|
||||
"done": "完成",
|
||||
@@ -82,13 +86,14 @@
|
||||
"import_placeholder": "添加多个时,请确保每行只有一个(每个链接回车换行)",
|
||||
"import_empty": "请输入需要导入的数据",
|
||||
"win_install_tip": "首次启用本软件,请使用鼠标右键选择以管理员身份运行",
|
||||
"download_queued": "下载已加入队列,当前队列长度:{count}",
|
||||
"download_queued": "已加入队列,当前队列长度:{count}",
|
||||
"search": "搜索",
|
||||
"search_description": "关键字搜索...",
|
||||
"start_err_tip": "错误提示",
|
||||
"start_err_content": "当前启动过程遇到了问题,是否重置应用?",
|
||||
"start_err_positiveText": "清理缓存并重启",
|
||||
"start_err_negativeText": "关闭软件"
|
||||
"start_err_negativeText": "关闭软件",
|
||||
"reset_app_tip": "此操作会删除已拦截数据以及本应用相关数据,请谨慎操作!"
|
||||
},
|
||||
"setting": {
|
||||
"restart_tip": "如果不清楚保持默认就行,修改后请重启软件",
|
||||
|
||||
2
frontend/src/types/app.d.ts
vendored
2
frontend/src/types/app.d.ts
vendored
@@ -38,7 +38,7 @@ export namespace appType {
|
||||
Url: string
|
||||
UrlSign: string
|
||||
CoverUrl: string
|
||||
Size: string
|
||||
Size: number
|
||||
Domain: string
|
||||
Classify: string
|
||||
Suffix: string
|
||||
|
||||
@@ -3,16 +3,28 @@
|
||||
<div class="pb-2 z-40" id="header">
|
||||
<NSpace>
|
||||
<NButton v-if="isProxy" secondary type="primary" @click.stop="close" style="--wails-draggable:no-drag">
|
||||
{{ t("index.close_grab") }}
|
||||
<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 ? ` ${t('index.total_resources', {count: data.length})}` : '' }}
|
||||
</NButton>
|
||||
<NButton v-else tertiary type="tertiary" @click.stop="open" style="--wails-draggable:no-drag">
|
||||
{{ t("index.open_grab") }}
|
||||
{{ t("index.open_grab") }}{{ data.length > 0 ? ` ${t('index.total_resources', {count: data.length})}` : '' }}
|
||||
</NButton>
|
||||
<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>
|
||||
<NButtonGroup style="--wails-draggable:no-drag">
|
||||
|
||||
<NButton v-if="rememberChoice" tertiary type="error" @click.stop="clear" style="--wails-draggable:no-drag">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<TrashOutline/>
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ t("index.clear_list") }}
|
||||
</NButton>
|
||||
<n-popconfirm
|
||||
@positive-click="clear"
|
||||
v-else
|
||||
@positive-click="()=>{rememberChoice=rememberChoiceTmp;clear()}"
|
||||
:show-icon="false"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton tertiary type="error" style="--wails-draggable:no-drag">
|
||||
@@ -24,7 +36,19 @@
|
||||
{{ t("index.clear_list") }}
|
||||
</NButton>
|
||||
</template>
|
||||
{{ t("index.clear_list_tip") }}
|
||||
<div>
|
||||
<div class="flex flex-row items-center text-red-700 my-2 text-base">
|
||||
<n-icon>
|
||||
<TrashOutline/>
|
||||
</n-icon>
|
||||
<p class="ml-1">{{ t("index.clear_list_tip") }}</p>
|
||||
</div>
|
||||
<NCheckbox
|
||||
v-model:checked="rememberChoiceTmp"
|
||||
>
|
||||
<span class="text-gray-400">{{ t('index.remember_clear_choice') }}</span>
|
||||
</NCheckbox>
|
||||
</div>
|
||||
</n-popconfirm>
|
||||
|
||||
<NButton tertiary type="primary" @click.stop="batchDown">
|
||||
@@ -111,10 +135,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {NButton, NIcon, NImage, NInput, NSpace, NTooltip, NPopover} from "naive-ui"
|
||||
import {NButton, NIcon, NImage, NInput, NSpace, NTooltip, NPopover, NGradientText} from "naive-ui"
|
||||
import {computed, h, onMounted, ref, watch} from "vue"
|
||||
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 ShowLoading from "@/components/ShowLoading.vue"
|
||||
// @ts-ignore
|
||||
@@ -136,10 +160,11 @@ import {
|
||||
Apps,
|
||||
TrashOutline, CloseOutline
|
||||
} from "@vicons/ionicons5"
|
||||
import { useDialog } from 'naive-ui'
|
||||
import {useDialog} from 'naive-ui'
|
||||
import * as bind from "../../wailsjs/go/core/Bind"
|
||||
import {Quit} from "../../wailsjs/runtime"
|
||||
import {DialogOptions} from "naive-ui/es/dialog/src/DialogProvider"
|
||||
import {formatSize} from "@/func"
|
||||
|
||||
const {t} = useI18n()
|
||||
const eventStore = useEventStore()
|
||||
@@ -185,6 +210,7 @@ const classifyAlias: { [key: string]: any } = {
|
||||
const dwStatus = computed<any>(() => {
|
||||
return {
|
||||
ready: t("index.ready"),
|
||||
pending: t("index.pending"),
|
||||
running: t("index.running"),
|
||||
error: t("index.error"),
|
||||
done: t("index.done"),
|
||||
@@ -204,13 +230,17 @@ const classify = ref([
|
||||
])
|
||||
|
||||
const descriptionSearchValue = ref("")
|
||||
const rememberChoice = ref(false)
|
||||
const rememberChoiceTmp = ref(false)
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{
|
||||
type: "selection",
|
||||
},
|
||||
{
|
||||
title: computed(() => t("index.domain")),
|
||||
title: computed(() => {
|
||||
return checkedRowKeysValue.value.length > 0 ? h(NGradientText, {type: "success"}, t("index.choice") + `(${checkedRowKeysValue.value.length})`) : t("index.domain")
|
||||
}),
|
||||
key: "Domain",
|
||||
width: 90,
|
||||
},
|
||||
@@ -278,11 +308,18 @@ const columns = ref<any[]>([
|
||||
key: "Status",
|
||||
width: 80,
|
||||
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(
|
||||
NButton,
|
||||
{
|
||||
tertiary: true,
|
||||
type: row.Status === "done" ? "success" : "info",
|
||||
type: status as any,
|
||||
size: "small",
|
||||
style: {
|
||||
margin: "2px"
|
||||
@@ -307,7 +344,7 @@ const columns = ref<any[]>([
|
||||
title: () => h('div', {class: 'flex items-center'}, [
|
||||
t('index.description'),
|
||||
h(NPopover, {
|
||||
style:"--wails-draggable:no-drag",
|
||||
style: "--wails-draggable:no-drag",
|
||||
trigger: 'click',
|
||||
placement: 'bottom',
|
||||
showArrow: true,
|
||||
@@ -343,6 +380,10 @@ const columns = ref<any[]>([
|
||||
title: computed(() => t("index.resource_size")),
|
||||
key: "Size",
|
||||
width: 120,
|
||||
sorter: (row1: appType.MediaInfo, row2: appType.MediaInfo) => row1.Size - row2.Size,
|
||||
render(row: appType.MediaInfo, index: number) {
|
||||
return formatSize(row.Size)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: computed(() => t("index.save_path")),
|
||||
@@ -401,8 +442,8 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
checkLoading()
|
||||
watch(showPassword, ()=>{
|
||||
if (!showPassword.value){
|
||||
watch(showPassword, () => {
|
||||
if (!showPassword.value) {
|
||||
checkLoading()
|
||||
}
|
||||
})
|
||||
@@ -423,7 +464,22 @@ onMounted(() => {
|
||||
if (cache) {
|
||||
data.value = JSON.parse(cache)
|
||||
}
|
||||
|
||||
const choiceCache = localStorage.getItem("remember-clear-choice")
|
||||
if (choiceCache === "1") {
|
||||
rememberChoice.value = true
|
||||
}
|
||||
|
||||
watch(rememberChoice, (n, o) => {
|
||||
if (rememberChoice.value) {
|
||||
localStorage.setItem("remember-clear-choice", "1")
|
||||
} else {
|
||||
localStorage.removeItem("remember-clear-choice")
|
||||
}
|
||||
})
|
||||
|
||||
resetTableHeight()
|
||||
|
||||
eventStore.addHandle({
|
||||
type: "newResources",
|
||||
event: (res: appType.MediaInfo) => {
|
||||
@@ -451,6 +507,9 @@ onMounted(() => {
|
||||
item.SavePath = res.SavePath
|
||||
item.Status = 'done'
|
||||
})
|
||||
if (activeDownloads > 0) {
|
||||
activeDownloads--
|
||||
}
|
||||
cacheData()
|
||||
checkQueue()
|
||||
break
|
||||
@@ -459,6 +518,9 @@ onMounted(() => {
|
||||
item.SavePath = res.Message
|
||||
item.Status = 'error'
|
||||
})
|
||||
if (activeDownloads > 0) {
|
||||
activeDownloads--
|
||||
}
|
||||
cacheData()
|
||||
checkQueue()
|
||||
break
|
||||
@@ -478,7 +540,7 @@ watch(resourcesType, (n, o) => {
|
||||
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)
|
||||
if (item) updater(item)
|
||||
}
|
||||
@@ -525,11 +587,14 @@ const dataAction = (row: appType.MediaInfo, index: number, type: string) => {
|
||||
break
|
||||
case "cancel":
|
||||
if (row.Status === "running") {
|
||||
appApi.cancel({id: row.Id}).then((res)=>{
|
||||
appApi.cancel({id: row.Id}).then((res) => {
|
||||
updateItem(row.Id, item => {
|
||||
item.Status = 'ready'
|
||||
item.SavePath = ''
|
||||
})
|
||||
if (activeDownloads > 0) {
|
||||
activeDownloads--
|
||||
}
|
||||
cacheData()
|
||||
checkQueue()
|
||||
if (res.code === 0) {
|
||||
@@ -591,7 +656,7 @@ const handleCheck = (rowKeys: DataTableRowKey[]) => {
|
||||
checkedRowKeysValue.value = rowKeys
|
||||
}
|
||||
|
||||
const updateFilters = (filters: DataTableFilterState, initiatorColumn: DataTableBaseColumn)=>{
|
||||
const updateFilters = (filters: DataTableFilterState, initiatorColumn: DataTableBaseColumn) => {
|
||||
filterClassify.value = filters.Classify as string[]
|
||||
}
|
||||
|
||||
@@ -615,22 +680,29 @@ const batchDown = async () => {
|
||||
checkedRowKeysValue.value = []
|
||||
}
|
||||
|
||||
const batchCancel = () =>{
|
||||
const batchCancel = async () => {
|
||||
if (checkedRowKeysValue.value.length <= 0) {
|
||||
window?.$message?.error(t("index.use_data"))
|
||||
return
|
||||
}
|
||||
|
||||
data.value.forEach(async (item, index) => {
|
||||
loading.value = true
|
||||
const cancelTasks: Promise<any>[] = []
|
||||
data.value.forEach((item, index) => {
|
||||
if (checkedRowKeysValue.value.includes(item.Id) && item.Status === "running") {
|
||||
appApi.cancel({id: item.Id})
|
||||
data.value[index].Status = 'ready'
|
||||
data.value[index].SavePath = ''
|
||||
if (activeDownloads > 0) {
|
||||
activeDownloads--
|
||||
}
|
||||
cancelTasks.push(appApi.cancel({id: item.Id}).then(() => {
|
||||
item.Status = 'ready'
|
||||
item.SavePath = ''
|
||||
checkQueue()
|
||||
}))
|
||||
}
|
||||
})
|
||||
await Promise.allSettled(cancelTasks)
|
||||
loading.value = false
|
||||
checkedRowKeysValue.value = []
|
||||
cacheData()
|
||||
checkQueue()
|
||||
}
|
||||
|
||||
const batchExport = (type?: string) => {
|
||||
@@ -649,9 +721,9 @@ const batchExport = (type?: string) => {
|
||||
|
||||
let jsonData = data.value.filter(item => checkedRowKeysValue.value.includes(item.Id))
|
||||
|
||||
if (type === "url"){
|
||||
if (type === "url") {
|
||||
jsonData = jsonData.map(item => item.Url)
|
||||
} else{
|
||||
} else {
|
||||
jsonData = jsonData.map(item => encodeURIComponent(JSON.stringify(item)))
|
||||
}
|
||||
|
||||
@@ -687,9 +759,9 @@ const download = (row: appType.MediaInfo, index: number) => {
|
||||
}
|
||||
|
||||
if (activeDownloads >= maxConcurrentDownloads.value) {
|
||||
row.Status = "pending"
|
||||
downloadQueue.value.push(row)
|
||||
window?.$message?.info((row.Description ? `「${row.Description}」` : "")
|
||||
+ t("index.download_queued", {count: downloadQueue.value.length}))
|
||||
window?.$message?.info(t("index.download_queued", {count: downloadQueue.value.length}))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -707,9 +779,6 @@ const startDownload = (row: appType.MediaInfo, index: number) => {
|
||||
if (res.code === 0) {
|
||||
window?.$message?.error(res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
activeDownloads--
|
||||
checkQueue()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -861,8 +930,8 @@ const handleInstall = async () => {
|
||||
return false
|
||||
}
|
||||
|
||||
const checkLoading = ()=>{
|
||||
setTimeout(()=>{
|
||||
const checkLoading = () => {
|
||||
setTimeout(() => {
|
||||
if (loading.value && !isInstall && !showPassword.value) {
|
||||
dialog.warning({
|
||||
title: t("index.start_err_tip"),
|
||||
|
||||
@@ -76,6 +76,17 @@
|
||||
{{ t("setting.insert_tail_tip") }}
|
||||
</NTooltip>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem >
|
||||
<n-popconfirm @positive-click="resetHandle">
|
||||
<template #trigger>
|
||||
<NButton tertiary type="error" style="--wails-draggable:no-drag">
|
||||
{{ t("index.start_err_positiveText") }}
|
||||
</NButton>
|
||||
</template>
|
||||
{{t("index.reset_app_tip")}}
|
||||
</n-popconfirm>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</NTabPane>
|
||||
|
||||
@@ -217,6 +228,8 @@ import appApi from "@/api/app"
|
||||
import {computed} from "vue"
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {isValidHost, isValidPort} from '@/func'
|
||||
import {NButton, NIcon} from "naive-ui"
|
||||
import * as bind from "../../wailsjs/go/core/Bind"
|
||||
|
||||
const {t} = useI18n()
|
||||
const store = useIndexStore()
|
||||
@@ -281,6 +294,11 @@ const selectDir = () => {
|
||||
window?.$message?.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
const resetHandle = ()=>{
|
||||
localStorage.clear()
|
||||
bind.ResetApp()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.n-tabs-nav--top{
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"info": {
|
||||
"companyName": "res-downloader",
|
||||
"productName": "res-downloader",
|
||||
"productVersion": "3.1.1",
|
||||
"productVersion": "3.1.2",
|
||||
"copyright": "Copyright © 2023",
|
||||
"comments": "This is a high-value high-performance and diverse resource downloader called res-downloader."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user