Merge pull request #2 from zanllp/feature/path-refresh-and-sorting
支持刷新当前路径下的文件,支持多种排序方法pull/3/head
commit
e825f7ec50
|
|
@ -6,8 +6,8 @@
|
|||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-f6acfa37.js"></script>
|
||||
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-2ba4dd52.css">
|
||||
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-3277bb8e.js"></script>
|
||||
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-241ec83f.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="zanllp_dev_gradio_fe"></div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import time
|
||||
from scripts.tool import human_readable_size
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
|
@ -25,7 +26,7 @@ from scripts.bin import (
|
|||
bin_file_name,
|
||||
is_win,
|
||||
)
|
||||
from scripts.tool import get_windows_drives
|
||||
from scripts.tool import get_windows_drives, convert_to_bytes
|
||||
import functools
|
||||
from scripts.logger import logger
|
||||
|
||||
|
|
@ -78,11 +79,14 @@ def list_file(cwd="/"):
|
|||
match = re.match(pattern, line)
|
||||
if match:
|
||||
name = match.group(4).strip()
|
||||
f_type = "dir" if name.endswith("/") else "file"
|
||||
size = match.group(2)
|
||||
file_info = {
|
||||
"size": match.group(2),
|
||||
# "date": match.group(3),
|
||||
"size": size,
|
||||
"date": match.group(3),
|
||||
"name": name.strip("/"),
|
||||
"type": "dir" if name.endswith("/") else "file",
|
||||
"type": f_type,
|
||||
"bytes": convert_to_bytes(size) if size != "-" else size
|
||||
}
|
||||
files.append(file_info)
|
||||
return files
|
||||
|
|
@ -292,11 +296,26 @@ def baidu_netdisk_api(_: gr.Blocks, app: FastAPI):
|
|||
else:
|
||||
for item in os.listdir(folder_path):
|
||||
path = os.path.join(folder_path, item)
|
||||
mod_time = os.path.getmtime(path)
|
||||
date = time.strftime(
|
||||
"%Y-%m-%d %H:%M:%S", time.localtime(mod_time)
|
||||
)
|
||||
if os.path.isfile(path):
|
||||
size = human_readable_size(os.path.getsize(path))
|
||||
files.append({"type": "file", "size": size, "name": item})
|
||||
bytes = os.path.getsize(path)
|
||||
size = human_readable_size(bytes)
|
||||
files.append(
|
||||
{
|
||||
"type": "file",
|
||||
"date": date,
|
||||
"size": size,
|
||||
"name": item,
|
||||
"bytes": bytes,
|
||||
}
|
||||
)
|
||||
elif os.path.isdir(path):
|
||||
files.append({"type": "dir", "size": "-", "name": item})
|
||||
files.append(
|
||||
{"type": "dir", "date": date, "size": "-", "name": item}
|
||||
)
|
||||
else:
|
||||
files = list_file(folder_path)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
|
||||
def human_readable_size(size_bytes):
|
||||
|
|
@ -26,4 +27,23 @@ def get_windows_drives():
|
|||
drives.append(drive_name)
|
||||
return drives
|
||||
|
||||
pattern = re.compile(r'(\d+\.?\d*)([KMGT]?B)', re.IGNORECASE)
|
||||
def convert_to_bytes(file_size_str):
|
||||
match = re.match(pattern, file_size_str)
|
||||
if match:
|
||||
size_str, unit_str = match.groups()
|
||||
size = float(size_str)
|
||||
unit = unit_str.upper()
|
||||
if unit == "KB":
|
||||
size *= 1024
|
||||
elif unit == "MB":
|
||||
size *= 1024**2
|
||||
elif unit == "GB":
|
||||
size *= 1024**3
|
||||
elif unit == "TB":
|
||||
size *= 1024**4
|
||||
return int(size)
|
||||
else:
|
||||
raise ValueError(f"Invalid file size string '{file_size_str}'")
|
||||
|
||||
is_dev = "APP_ENV" in os.environ and os.environ["APP_ENV"] == "dev"
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,8 +5,8 @@
|
|||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-f6acfa37.js"></script>
|
||||
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-2ba4dd52.css">
|
||||
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-3277bb8e.js"></script>
|
||||
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-241ec83f.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="zanllp_dev_gradio_fe"></div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import { axiosInst } from '.'
|
|||
export interface FileNodeInfo {
|
||||
size: string
|
||||
type: 'file' | 'dir'
|
||||
name: string
|
||||
name: string,
|
||||
date: string,
|
||||
bytes: number
|
||||
}
|
||||
|
||||
export const getTargetFolderFiles = async (target: 'local' | 'netdisk' , folder_path: string) => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ref, computed, onMounted, toRaw } from 'vue'
|
|||
import { FileOutlined, FolderOpenOutlined, DownOutlined } from '@/icon'
|
||||
import path from 'path-browserify'
|
||||
import { useGlobalStore } from '@/store/useGlobalStore'
|
||||
import { copy2clipboard, ok } from 'vue3-ts-util'
|
||||
import { copy2clipboard, ok, type SearchSelectConv, SearchSelect } from 'vue3-ts-util'
|
||||
// @ts-ignore
|
||||
import NProgress from 'multi-nprogress'
|
||||
import 'multi-nprogress/nprogress.css'
|
||||
|
|
@ -19,34 +19,66 @@ const props = defineProps<{
|
|||
target: 'local' | 'netdisk'
|
||||
}>()
|
||||
interface Page {
|
||||
files: FileNodeInfo[],
|
||||
files: FileNodeInfo[]
|
||||
curr: string
|
||||
}
|
||||
const stack = ref<Page[]>([])
|
||||
const global = useGlobalStore()
|
||||
const currPage = computed(() => last(stack.value))
|
||||
type SortMethod = 'date-asc' | 'date-desc' | 'name-asc' | 'name-desc' | 'size-asc' | 'size-desc'
|
||||
|
||||
const sortFile = (files: FileNodeInfo[]) => {
|
||||
return files.sort((a, b) => {
|
||||
const sa = a.type === 'dir' ? 1 : 0
|
||||
const sb = b.type === 'dir' ? 1 : 0
|
||||
return sb - sa
|
||||
})
|
||||
const sortMethodMap: Record<SortMethod, string> = {
|
||||
'date-asc': '日期升序',
|
||||
'date-desc': '日期降序',
|
||||
'name-asc': '名称升序',
|
||||
'name-desc': '名称降序',
|
||||
'size-asc': '大小升序',
|
||||
'size-desc': '大小降序'
|
||||
}
|
||||
const sortMethodConv: SearchSelectConv<SortMethod> = {
|
||||
value: (v) => v,
|
||||
text: (v) => '按' + sortMethodMap[v]
|
||||
}
|
||||
const sortMethod = ref<SortMethod>('date-desc')
|
||||
|
||||
onMounted(async () => {
|
||||
const resp = await getTargetFolderFiles(props.target, '/')
|
||||
stack.value.push({
|
||||
files: sortFile(resp.files),
|
||||
files: resp.files,
|
||||
curr: '/'
|
||||
})
|
||||
np.value = new NProgress()
|
||||
np.value!.configure({ parent: el.value as any })
|
||||
})
|
||||
const getBasePath = () => stack.value.map(v => v.curr).slice(global.conf?.is_win && props.target === 'local' ? 1 : 0)
|
||||
|
||||
const getBasePath = () =>
|
||||
stack.value.map((v) => v.curr).slice(global.conf?.is_win && props.target === 'local' ? 1 : 0)
|
||||
|
||||
const copyLocation = () => copy2clipboard(path.join(...getBasePath()))
|
||||
|
||||
const filesSort = (files: FileNodeInfo[]) => {
|
||||
return files.slice().sort((a, b) => {
|
||||
const method = sortMethod.value
|
||||
const sa = a.type === 'dir' ? 1 : 0
|
||||
const sb = b.type === 'dir' ? 1 : 0
|
||||
const typeCompare = sb - sa
|
||||
if (typeCompare !== 0) {
|
||||
return typeCompare
|
||||
}
|
||||
if (method === 'date-asc' || method === 'date-desc') {
|
||||
const da = Date.parse(a.date)
|
||||
const db = Date.parse(b.date)
|
||||
return method === 'date-asc' ? da - db : db - da
|
||||
} else if (method === 'name-asc' || method == 'name-desc') {
|
||||
const an = a.name.toLowerCase()
|
||||
const bn = b.name.toLowerCase()
|
||||
return method === 'name-asc' ? an.localeCompare(bn) : bn.localeCompare(an)
|
||||
} else {
|
||||
return method === 'size-asc' ? a.bytes - b.bytes : b.bytes - a.bytes
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openNext = async (file: FileNodeInfo) => {
|
||||
if (file.type !== 'dir') {
|
||||
return
|
||||
|
|
@ -54,9 +86,12 @@ const openNext = async (file: FileNodeInfo) => {
|
|||
try {
|
||||
np.value?.start()
|
||||
const prev = getBasePath()
|
||||
const { files } = await getTargetFolderFiles(props.target, path.normalize(path.join(...prev, file.name)))
|
||||
const { files } = await getTargetFolderFiles(
|
||||
props.target,
|
||||
path.normalize(path.join(...prev, file.name))
|
||||
)
|
||||
stack.value.push({
|
||||
files: sortFile(files),
|
||||
files,
|
||||
curr: file.name
|
||||
})
|
||||
} finally {
|
||||
|
|
@ -71,7 +106,8 @@ const back = (idx: number) => {
|
|||
}
|
||||
|
||||
const to = async (dir: string) => {
|
||||
if (!/^((\w:)|\/)/.test(dir)) { // 相对路径
|
||||
if (!/^((\w:)|\/)/.test(dir)) {
|
||||
// 相对路径
|
||||
dir = path.join(global.conf?.sd_cwd ?? '/', dir)
|
||||
}
|
||||
const frags = dir.split(/\\|\//)
|
||||
|
|
@ -82,14 +118,17 @@ const to = async (dir: string) => {
|
|||
}
|
||||
back(0) // 回到栈底
|
||||
for (const frag of frags) {
|
||||
const target = currPage.value?.files.find(v => v.name === frag)
|
||||
const target = currPage.value?.files.find((v) => v.name === frag)
|
||||
ok(target)
|
||||
await openNext(target)
|
||||
}
|
||||
}
|
||||
|
||||
const onDrop = async (e: DragEvent) => {
|
||||
const data = JSON.parse(e.dataTransfer?.getData("text") || '{}') as FileNodeInfo & { from: typeof props.target, path: string }
|
||||
const data = JSON.parse(e.dataTransfer?.getData('text') || '{}') as FileNodeInfo & {
|
||||
from: typeof props.target
|
||||
path: string
|
||||
}
|
||||
if (data.from && data.path && data.type) {
|
||||
if (data.from === props.target) {
|
||||
return
|
||||
|
|
@ -99,22 +138,42 @@ const onDrop = async (e: DragEvent) => {
|
|||
const toPath = path.join(...getBasePath())
|
||||
Modal.confirm({
|
||||
title: `确定创建${typeZH}任务${data.type === 'dir' ? ', 这是文件夹!' : ''}`,
|
||||
content: `从 ${props.target !== 'local' ? '本地' : '云盘'} ${data.path} ${typeZH} ${props.target === 'local' ? '本地' : '云盘'} ${toPath}`,
|
||||
content: `从 ${props.target !== 'local' ? '本地' : '云盘'} ${data.path} ${typeZH} ${props.target === 'local' ? '本地' : '云盘'
|
||||
} ${toPath}`,
|
||||
maskClosable: true,
|
||||
async onOk () {
|
||||
global.eventEmitter.emit('createNewTask', { send_dirs: data.path, recv_dir: toPath, type })
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
if (stack.value.length === 1) {
|
||||
const resp = await getTargetFolderFiles(props.target, '/')
|
||||
stack.value = [
|
||||
{
|
||||
files: resp.files,
|
||||
curr: '/'
|
||||
}
|
||||
]
|
||||
} else {
|
||||
const last = currPage.value
|
||||
stack.value.pop()
|
||||
await openNext(currPage.value?.files.find((v) => v.name === last?.curr)!)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div ref="el" @dragover.prevent @drop.prevent="onDrop($event)" class="container">
|
||||
<div class="location">
|
||||
<a-breadcrumb style="flex: 1;">
|
||||
<a-breadcrumb-item v-for="item, idx in stack" :key="idx"><a @click.prevent="back(idx)">{{ item.curr === "/" ? "根"
|
||||
: item.curr.replace(/:\/$/, '盘') }}</a></a-breadcrumb-item>
|
||||
<a-breadcrumb style="flex: 1">
|
||||
<a-breadcrumb-item v-for="(item, idx) in stack" :key="idx"><a @click.prevent="back(idx)">{{
|
||||
item.curr === '/' ? '根' : item.curr.replace(/:\/$/, '盘')
|
||||
}}</a></a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
<SearchSelect v-model:value="sortMethod" :conv="sortMethodConv" :options="Object.keys(sortMethodMap)" />
|
||||
<a class="opt" @click.prevent="refresh"> 刷新 </a>
|
||||
<a-dropdown v-if="props.target === 'local'">
|
||||
<a class="ant-dropdown-link opt" @click.prevent>
|
||||
快速移动
|
||||
|
|
@ -132,17 +191,29 @@ const onDrop = async (e: DragEvent) => {
|
|||
</div>
|
||||
<div v-if="currPage" class="view">
|
||||
<ul class="file-list">
|
||||
<li class="file" v-for="file in currPage.files" :class="{ 'clickable': file.type === 'dir' }" :key="file.name"
|
||||
draggable="true"
|
||||
@dragstart="$event.dataTransfer!.setData('text/plain', JSON.stringify({ from: props.target, path: path.join(...getBasePath(), file.name), ...toRaw(file) }))"
|
||||
@click="openNext(file)">
|
||||
<li class="file" v-for="file in filesSort(currPage.files)" :class="{ clickable: file.type === 'dir' }"
|
||||
:key="file.name" draggable="true" @dragstart="
|
||||
$event.dataTransfer!.setData(
|
||||
'text/plain',
|
||||
JSON.stringify({
|
||||
from: props.target,
|
||||
path: path.join(...getBasePath(), file.name),
|
||||
...toRaw(file)
|
||||
})
|
||||
)
|
||||
" @click="openNext(file)">
|
||||
<file-outlined v-if="file.type === 'file'" />
|
||||
<folder-open-outlined v-else />
|
||||
<div class="name">
|
||||
{{ file.name }}
|
||||
</div>
|
||||
<div class="size">
|
||||
{{ file.size }}
|
||||
<div class="basic-info">
|
||||
<div>
|
||||
{{ file.size }}
|
||||
</div>
|
||||
<div>
|
||||
{{ file.date }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -168,15 +239,12 @@ const onDrop = async (e: DragEvent) => {
|
|||
.view {
|
||||
padding: 8px;
|
||||
|
||||
|
||||
.file-list {
|
||||
list-style: none;
|
||||
padding: 8px;
|
||||
height: 900px;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
|
||||
.file {
|
||||
padding: 8px 16px;
|
||||
margin: 8px;
|
||||
|
|
@ -195,9 +263,12 @@ const onDrop = async (e: DragEvent) => {
|
|||
padding: 8px;
|
||||
}
|
||||
|
||||
.size {}
|
||||
|
||||
.basic-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue