perf: Batch export、operation item style optimization

This commit is contained in:
putyy
2025-07-23 15:34:24 +08:00
parent 31073eb57e
commit 567eb2903d
9 changed files with 149 additions and 96 deletions

View File

@@ -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,
})

View File

@@ -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

View File

@@ -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']

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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",

View File

@@ -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": "无法解密",

View File

@@ -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()