mirror of
https://github.com/putyy/res-downloader.git
synced 2026-01-12 14:14:55 +08:00
perf: Batch export、operation item style optimization
This commit is contained in:
36
core/http.go
36
core/http.go
@@ -10,10 +10,8 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"res-downloader/core/shared"
|
||||
sysRuntime "runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
@@ -207,42 +205,14 @@ func (h *HttpServer) openFolder(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := data.FilePath
|
||||
var cmd *exec.Cmd
|
||||
|
||||
switch sysRuntime.GOOS {
|
||||
case "darwin":
|
||||
cmd = exec.Command("open", "-R", filePath)
|
||||
case "windows":
|
||||
cmd = exec.Command("explorer", "/select,", filePath)
|
||||
case "linux":
|
||||
cmd = exec.Command("nautilus", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd = exec.Command("thunar", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd = exec.Command("dolphin", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd = exec.Command("pcmanfm", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
globalLogger.Err(err)
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
h.error(w, "unsupported platform")
|
||||
return
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
err = shared.OpenFolder(data.FilePath)
|
||||
if err != nil {
|
||||
globalLogger.Err(err)
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.success(w)
|
||||
return
|
||||
}
|
||||
|
||||
func (h *HttpServer) install(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -409,6 +379,8 @@ func (h *HttpServer) batchExport(w http.ResponseWriter, r *http.Request) {
|
||||
h.error(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_ = shared.OpenFolder(fileName)
|
||||
h.success(w, respData{
|
||||
"file_name": fileName,
|
||||
})
|
||||
|
||||
@@ -3,10 +3,13 @@ package shared
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
sysRuntime "runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -68,3 +71,32 @@ func GetCurrentDateTimeFormatted() string {
|
||||
now.Minute(),
|
||||
now.Second())
|
||||
}
|
||||
|
||||
func OpenFolder(filePath string) error {
|
||||
var cmd *exec.Cmd
|
||||
|
||||
switch sysRuntime.GOOS {
|
||||
case "darwin":
|
||||
cmd = exec.Command("open", "-R", filePath)
|
||||
case "windows":
|
||||
cmd = exec.Command("explorer", "/select,", filePath)
|
||||
case "linux":
|
||||
cmd = exec.Command("nautilus", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd = exec.Command("thunar", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd = exec.Command("dolphin", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd = exec.Command("pcmanfm", filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return errors.New("unsupported platform")
|
||||
}
|
||||
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 30 KiB |
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -35,6 +35,7 @@ declare module 'vue' {
|
||||
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
|
||||
@@ -1,48 +1,13 @@
|
||||
<template>
|
||||
<div style="--wails-draggable:no-drag" class="grid grid-cols-6 gap-1.5">
|
||||
<div style="--wails-draggable:no-drag" class="grid grid-cols-3 gap-1.5">
|
||||
<n-icon
|
||||
v-if="row.Classify !== 'live' && row.Classify !== 'm3u8'"
|
||||
size="28"
|
||||
size="30"
|
||||
class="text-emerald-600 dark:text-emerald-400 bg-emerald-500/20 dark:bg-emerald-500/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-emerald-500/40 transition-colors"
|
||||
@click="action('down')"
|
||||
>
|
||||
<DownloadOutline/>
|
||||
</n-icon>
|
||||
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-blue-600 dark:text-blue-300 bg-blue-500/20 dark:bg-blue-500/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-blue-500/40 transition-colors"
|
||||
@click="action('copy')"
|
||||
>
|
||||
<LinkOutline/>
|
||||
</n-icon>
|
||||
|
||||
<n-icon
|
||||
v-if="row.Classify !== 'live' && row.Classify !== 'm3u8'"
|
||||
size="28"
|
||||
class="text-blue-500 dark:text-blue-200 bg-blue-400/20 dark:bg-blue-400/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-blue-400/40 transition-colors"
|
||||
@click="action('open')"
|
||||
>
|
||||
<GlobeOutline/>
|
||||
</n-icon>
|
||||
|
||||
<n-icon
|
||||
v-if="row.DecodeKey"
|
||||
size="28"
|
||||
class="text-orange-400 dark:text-red-300 bg-orange-500/20 dark:bg-orange-200/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-orange-200/40 transition-colors"
|
||||
@click="action('decode')"
|
||||
>
|
||||
<LockOpenSharp/>
|
||||
</n-icon>
|
||||
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-sky-400 dark:text-sky-200 bg-sky-500/20 dark:bg-sky-500/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-sky-500/40 transition-colors"
|
||||
@click="action('json')"
|
||||
>
|
||||
<CopyOutline/>
|
||||
</n-icon>
|
||||
|
||||
<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"
|
||||
@@ -50,6 +15,55 @@
|
||||
>
|
||||
<TrashOutline/>
|
||||
</n-icon>
|
||||
|
||||
<NPopover placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="30" class="text-sky-500 dark:text-sky-300 bg-sky-500/20 dark:bg-sky-200/30 rounded-full flex items-center justify-center p-2 cursor-pointer hover:bg-sky-200/40 transition-colors">
|
||||
<GridSharp/>
|
||||
</NIcon>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center justify-start p-1.5 cursor-pointer" @click="action('copy')">
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-blue-300 dark:text-blue-300 bg-blue-300/20 dark:bg-blue-500/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-blue-300/40 transition-colors"
|
||||
>
|
||||
<LinkOutline/>
|
||||
</n-icon>
|
||||
<span class="ml-1">{{ t("index.copy_link") }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-start p-1.5 cursor-pointer" v-if="row.Classify !== 'live' && row.Classify !== 'm3u8'" @click="action('open')">
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-blue-500 dark:text-blue-200 bg-blue-400/20 dark:bg-blue-400/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-blue-400/40 transition-colors"
|
||||
>
|
||||
<GlobeOutline/>
|
||||
</n-icon>
|
||||
<span class="ml-1">{{ t("index.open_link") }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-start p-1.5 cursor-pointer" v-if="row.DecodeKey" @click="action('decode')">
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-orange-400 dark:text-red-300 bg-orange-500/20 dark:bg-orange-200/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-orange-200/40 transition-colors"
|
||||
>
|
||||
<LockOpenSharp/>
|
||||
</n-icon>
|
||||
<span class="ml-1">{{ t("index.video_decode") }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-start p-1.5 cursor-pointer" @click="action('json')">
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-sky-400 dark:text-sky-200 bg-sky-500/20 dark:bg-sky-500/30 rounded-full flex items-center justify-center p-1.5 cursor-pointer hover:bg-sky-500/40 transition-colors"
|
||||
>
|
||||
<CopyOutline/>
|
||||
</n-icon>
|
||||
<span class="ml-1">{{ t("index.copy_data") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</NPopover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,6 +75,7 @@ import {
|
||||
GlobeOutline,
|
||||
LockOpenSharp,
|
||||
LinkOutline,
|
||||
GridSharp,
|
||||
TrashOutline
|
||||
} from "@vicons/ionicons5"
|
||||
|
||||
@@ -73,6 +88,10 @@ const props = defineProps<{
|
||||
const emits = defineEmits(["action"])
|
||||
|
||||
const action = (type: string) => {
|
||||
if (props.row.Classify === 'live' || props.row.Classify === 'm3u8') {
|
||||
window?.$message?.error(t("index.download_no_tip"))
|
||||
return
|
||||
}
|
||||
emits('action', props.row, props.index, type)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<span>
|
||||
{{ t('index.operation') }}
|
||||
</span>
|
||||
<NTooltip trigger="hover">
|
||||
<NPopover trigger="hover">
|
||||
<template #trigger>
|
||||
<NIcon size="18" class="ml-1 text-gray-500">
|
||||
<HelpCircleOutline/>
|
||||
@@ -68,8 +68,17 @@
|
||||
<span class="ml-1">{{ t("index.delete_row") }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-start p-1.5">
|
||||
<n-icon
|
||||
size="28"
|
||||
class="text-sky-500 dark:text-sky-300 bg-sky-500/20 dark:bg-sky-200/30 rounded-full flex items-center justify-center p-2 cursor-pointer hover:bg-sky-200/40 transition-colors"
|
||||
>
|
||||
<GridSharp/>
|
||||
</n-icon>
|
||||
<span class="ml-1">{{ t("index.more_operation") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</NTooltip>
|
||||
</NPopover>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -78,7 +87,10 @@ import {
|
||||
CopyOutline,
|
||||
DownloadOutline,
|
||||
GlobeOutline,
|
||||
HelpCircleOutline, LinkOutline, LockOpenSharp,
|
||||
HelpCircleOutline,
|
||||
LinkOutline,
|
||||
LockOpenSharp,
|
||||
GridSharp,
|
||||
TrashOutline
|
||||
} from "@vicons/ionicons5"
|
||||
|
||||
|
||||
@@ -65,11 +65,13 @@
|
||||
"handle": "Post Processing",
|
||||
"direct_download": "Download",
|
||||
"download_success": "Download Success",
|
||||
"download_no_tip": "This type of download is not supported yet. Please copy the link and use other tools to download.",
|
||||
"copy_link": "Copy Link",
|
||||
"copy_data": "Copy Data",
|
||||
"open_link": "Open Link",
|
||||
"open_file": "Open File",
|
||||
"delete_row": "Delete Row",
|
||||
"more_operation": "More Operations",
|
||||
"video_decode": "WxDecrypt",
|
||||
"video_decode_loading": "Decrypting",
|
||||
"video_decode_no": "Cannot Decrypt",
|
||||
|
||||
@@ -65,11 +65,13 @@
|
||||
"handle": "后续处理",
|
||||
"direct_download": "直接下载",
|
||||
"download_success": "下载成功",
|
||||
"download_no_tip": "该类型暂不支持下载,请复制链接后使用其他工具下载",
|
||||
"copy_link": "复制链接",
|
||||
"copy_data": "复制数据",
|
||||
"open_link": "打开链接",
|
||||
"open_file": "打开文件",
|
||||
"delete_row": "删除记录",
|
||||
"more_operation": "更多操作",
|
||||
"video_decode": "视频解密",
|
||||
"video_decode_loading": "解密中",
|
||||
"video_decode_no": "无法解密",
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {NButton, NIcon, NImage, NInput, NSpace, NTooltip} from "naive-ui"
|
||||
import {NButton, NIcon, NImage, NInput, NSpace, NTooltip, NPopover} from "naive-ui"
|
||||
import {computed, h, onMounted, ref, watch} from "vue"
|
||||
import type {appType} from "@/types/app"
|
||||
import type {DataTableRowKey, ImageRenderToolbarProps} from "naive-ui"
|
||||
@@ -112,16 +112,16 @@ const eventStore = useEventStore()
|
||||
const isProxy = computed(() => {
|
||||
return store.isProxy
|
||||
})
|
||||
const certUrl = computed(()=>{
|
||||
const certUrl = computed(() => {
|
||||
return store.baseUrl + "/api/cert"
|
||||
})
|
||||
const data = ref<any[]>([])
|
||||
|
||||
let filterClassify: string[] = []
|
||||
const filteredData = computed(() => {
|
||||
let result = data.value
|
||||
|
||||
if (resourcesType.value.length > 0 && !resourcesType.value.includes("all")) {
|
||||
result = result.filter(item => resourcesType.value.includes(item.Classify))
|
||||
if (filterClassify.length > 0) {
|
||||
result = result.filter(item => filterClassify.includes(item.Classify))
|
||||
}
|
||||
|
||||
if (descriptionSearchValue.value) {
|
||||
@@ -186,6 +186,7 @@ const columns = ref<any[]>([
|
||||
filterOptions: computed(() => Array.from(classify.value).slice(1)),
|
||||
filterMultiple: true,
|
||||
filter: (value: string, row: appType.MediaInfo) => {
|
||||
if (!filterClassify.includes(value)) filterClassify.push(value)
|
||||
return !!~row.Classify.indexOf(String(value))
|
||||
},
|
||||
render: (row: appType.MediaInfo) => {
|
||||
@@ -271,26 +272,26 @@ const columns = ref<any[]>([
|
||||
}
|
||||
},
|
||||
{
|
||||
title: () => h('div', { class: 'flex items-center' }, [
|
||||
title: () => h('div', {class: 'flex items-center'}, [
|
||||
t('index.description'),
|
||||
h(NTooltip, {
|
||||
h(NPopover, {
|
||||
trigger: 'click',
|
||||
placement: 'bottom',
|
||||
showArrow: false,
|
||||
showArrow: true,
|
||||
}, {
|
||||
trigger: () => h(NIcon, {
|
||||
size: "18",
|
||||
class: "ml-1 text-gray-500 cursor-pointer",
|
||||
onClick: (e: MouseEvent) => e.stopPropagation()
|
||||
}, h(SearchOutline)),
|
||||
default: () => h('div', { class: 'p-2 w-64' }, [
|
||||
default: () => h('div', {class: 'p-2 w-64'}, [
|
||||
h(NInput, {
|
||||
value: descriptionSearchValue.value,
|
||||
'onUpdate:value': (val: string) => descriptionSearchValue.value = val,
|
||||
placeholder: t('index.search_description'),
|
||||
clearable: true
|
||||
}, {
|
||||
prefix: () => h(NIcon, { component: SearchOutline })
|
||||
prefix: () => h(NIcon, {component: SearchOutline})
|
||||
})
|
||||
])
|
||||
})
|
||||
@@ -298,15 +299,10 @@ const columns = ref<any[]>([
|
||||
key: "Description",
|
||||
width: 150,
|
||||
render: (row: appType.MediaInfo, index: number) => {
|
||||
const d = h("div", {class: "ellipsis-2",}, row.Description)
|
||||
return h(NTooltip, {trigger: 'hover', placement: 'top'}, {
|
||||
trigger: () => h("div", {}, row.Description.length > 16 ? row.Description.substring(0, 16) + "..." : row.Description),
|
||||
default: () => h("div", {
|
||||
style: {
|
||||
"max-width": " 400px",
|
||||
"white-space": "normal",
|
||||
"word-wrap": "break-word"
|
||||
}
|
||||
}, row.Description)
|
||||
trigger: () => d,
|
||||
default: () => d
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -322,7 +318,7 @@ const columns = ref<any[]>([
|
||||
return h("a",
|
||||
{
|
||||
href: "javascript:;",
|
||||
class:"ellipsis-2",
|
||||
class: "ellipsis-2",
|
||||
style: {
|
||||
color: "#5a95d0"
|
||||
},
|
||||
@@ -338,7 +334,7 @@ const columns = ref<any[]>([
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
width: 210,
|
||||
width: 130,
|
||||
render(row: appType.MediaInfo, index: number) {
|
||||
return h(Action, {key: index, row: row, index: index, onAction: dataAction})
|
||||
},
|
||||
@@ -361,7 +357,7 @@ let isOpenProxy = false
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
window.addEventListener("resize", ()=>{
|
||||
window.addEventListener("resize", () => {
|
||||
resetTableHeight()
|
||||
})
|
||||
loading.value = true
|
||||
@@ -445,7 +441,7 @@ watch(resourcesType, (n, o) => {
|
||||
appApi.setType(resourcesType.value)
|
||||
})
|
||||
|
||||
const resetTableHeight = ()=>{
|
||||
const resetTableHeight = () => {
|
||||
try {
|
||||
const headerHeight = document.getElementById("header")?.offsetHeight || 0
|
||||
const bottomHeight = document.getElementById("bottom")?.offsetHeight || 0
|
||||
@@ -453,7 +449,7 @@ const resetTableHeight = ()=>{
|
||||
const theadHeight = document.getElementsByClassName("n-data-table-thead")[0]?.offsetHeight || 0
|
||||
const height = document.documentElement.clientHeight || window.innerHeight
|
||||
tableHeight.value = height - headerHeight - bottomHeight - theadHeight - 20
|
||||
}catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -537,6 +533,7 @@ const handleCheck = (rowKeys: DataTableRowKey[]) => {
|
||||
|
||||
const batchDown = async () => {
|
||||
if (checkedRowKeysValue.value.length <= 0) {
|
||||
window?.$message?.error(t("index.use_data"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -566,7 +563,9 @@ const batchExport = () => {
|
||||
loading.value = true
|
||||
let jsonData = []
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
jsonData.push(encodeURIComponent(JSON.stringify(data.value[i])))
|
||||
if (checkedRowKeysValue.value.includes(data.value[i].Id)) {
|
||||
jsonData.push(encodeURIComponent(JSON.stringify(data.value[i])))
|
||||
}
|
||||
}
|
||||
appApi.batchExport({content: jsonData.join("\n")}).then((res: appType.Res) => {
|
||||
loading.value = false
|
||||
@@ -663,6 +662,20 @@ const close = () => {
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
if (checkedRowKeysValue.value.length > 0) {
|
||||
let newData = []
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
if (!checkedRowKeysValue.value.includes(data.value[i].Id)) {
|
||||
appApi.delete({sign: data.value[i].UrlSign})
|
||||
newData.push(data.value[i])
|
||||
}
|
||||
}
|
||||
checkedRowKeysValue.value = []
|
||||
data.value = newData
|
||||
localStorage.setItem("resources-data", JSON.stringify(data.value))
|
||||
return
|
||||
}
|
||||
|
||||
data.value = []
|
||||
localStorage.setItem("resources-data", "")
|
||||
appApi.clear()
|
||||
|
||||
Reference in New Issue
Block a user