mirror of
https://gitee.com/viarotel-org/escrcpy.git
synced 2024-11-30 02:08:59 +08:00
feat: 🚀 Initially add a new file manager
This commit is contained in:
parent
d9be5d5fe3
commit
94ee0070ef
@ -7,6 +7,7 @@ import { Adb } from '@devicefarmer/adbkit'
|
||||
import { uniq } from 'lodash-es'
|
||||
import appStore from '$electron/helpers/store.js'
|
||||
import { adbPath } from '$electron/configs/index.js'
|
||||
import { formatFileSize } from '$renderer/utils/index'
|
||||
|
||||
const exec = util.promisify(_exec)
|
||||
|
||||
@ -232,6 +233,18 @@ const watch = async (callback) => {
|
||||
return close
|
||||
}
|
||||
|
||||
async function getFiles(id, path) {
|
||||
const value = await client.getDevice(id).readdir(path)
|
||||
|
||||
return value.map(item => ({
|
||||
...item,
|
||||
type: item.isFile() ? 'file' : 'directory',
|
||||
name: item.name,
|
||||
size: formatFileSize(item.size),
|
||||
updateTime: dayjs(item.mtime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
}))
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const binPath = appStore.get('common.adbPath') || adbPath
|
||||
|
||||
@ -257,5 +270,6 @@ export default () => {
|
||||
clearOverlayDisplayDevices,
|
||||
push,
|
||||
watch,
|
||||
getFiles,
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@
|
||||
"electron-log": "^5.1.2",
|
||||
"electron-store": "^9.0.0",
|
||||
"electron-updater": "^6.1.8",
|
||||
"element-plus": "^2.7.2",
|
||||
"element-plus": "^2.8.1",
|
||||
"eslint": "^8.57.0",
|
||||
"fix-path": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
|
@ -1,18 +1,7 @@
|
||||
<template>
|
||||
<el-dropdown :hide-on-click="false" :disabled="loading">
|
||||
<div class="">
|
||||
<slot :loading="loading" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="handlePush(devices)">
|
||||
<span class="" title="/sdcard/Download/">
|
||||
{{ $t('device.control.file.push') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="" @click="handlePush(devices)">
|
||||
<slot v-bind="{ loading }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
@ -3,7 +3,7 @@
|
||||
v-model="visible"
|
||||
:title="$t('device.task.name')"
|
||||
width="70%"
|
||||
class="el-dialog-beautify"
|
||||
class="el-dialog--beautify"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@closed="onClosed"
|
||||
|
@ -63,7 +63,7 @@ const actionModel = [
|
||||
component: Application,
|
||||
},
|
||||
{
|
||||
label: 'device.control.file.name',
|
||||
label: 'device.control.file.push',
|
||||
svgIcon: 'file-send',
|
||||
component: FileManage,
|
||||
},
|
||||
|
@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="$t('device.control.file.name')"
|
||||
width="97%"
|
||||
append-to-body
|
||||
class="el-dialog--beautify"
|
||||
@closed="onClosed"
|
||||
>
|
||||
<div
|
||||
class="flex items-center mb-4 bg-gray-100 dark:bg-gray-800 rounded-full p-1"
|
||||
>
|
||||
<el-button
|
||||
text
|
||||
icon="Top"
|
||||
circle
|
||||
class="mr-2"
|
||||
@click="handlePrev"
|
||||
></el-button>
|
||||
|
||||
<el-breadcrumb separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item>
|
||||
<el-button
|
||||
text
|
||||
icon="Iphone"
|
||||
class="!px-2"
|
||||
@click="handleBreadcrumb(breadcrumbModel[0])"
|
||||
></el-button>
|
||||
</el-breadcrumb-item>
|
||||
|
||||
<el-breadcrumb-item
|
||||
v-for="item of breadcrumbModel"
|
||||
:key="item.value"
|
||||
@click="handleBreadcrumb(item)"
|
||||
>
|
||||
<el-button text class="!px-2">
|
||||
{{ item.label || item.value }}
|
||||
</el-button>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
|
||||
<div class="ml-auto">
|
||||
<el-button text icon="Refresh" circle></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 -ml-px">
|
||||
<el-button type="default" icon="FolderAdd">
|
||||
新建文件夹
|
||||
</el-button>
|
||||
<el-button type="default" icon="DocumentAdd">
|
||||
上传文件
|
||||
</el-button>
|
||||
<el-button type="default" icon="Download">
|
||||
下载文件
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
stripe
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="50"
|
||||
align="left"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column prop="name" label="名称" sortable>
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<el-button
|
||||
v-if="row.type === 'directory'"
|
||||
text
|
||||
icon="Folder"
|
||||
class="!p-0 !bg-transparent"
|
||||
@click="handleDirectory(row)"
|
||||
>
|
||||
{{ row.name }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
text
|
||||
icon="Document"
|
||||
class="!p-0 !bg-transparent"
|
||||
@click="handleFile(row)"
|
||||
>
|
||||
{{ row.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="size"
|
||||
label="大小"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="updateTime"
|
||||
label="修改时间"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="">
|
||||
<EleTooltipButton
|
||||
v-if="['file'].includes(row.type)"
|
||||
effect="light"
|
||||
placement="top"
|
||||
:offset="2"
|
||||
:content="$t('common.download')"
|
||||
text
|
||||
type="primary"
|
||||
icon="Download"
|
||||
circle
|
||||
@click="handleFile(row)"
|
||||
>
|
||||
</EleTooltipButton>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<template #footer></template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const visible = ref(false)
|
||||
|
||||
const device = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const tableData = ref([])
|
||||
|
||||
const currentPath = ref('sdcard')
|
||||
|
||||
const breadcrumbModel = computed(() => {
|
||||
const pathList = currentPath.value.split('/')
|
||||
|
||||
const value = pathList.map(item => ({
|
||||
label: item === 'sdcard' ? '内部存储空间' : void 0,
|
||||
value: item,
|
||||
}))
|
||||
|
||||
return value
|
||||
})
|
||||
|
||||
function open(args) {
|
||||
visible.value = true
|
||||
device.value = args
|
||||
getTableData()
|
||||
}
|
||||
|
||||
function onClosed() {}
|
||||
|
||||
async function getTableData() {
|
||||
loading.value = true
|
||||
|
||||
const data = await window.adbkit.getFiles(device.value.id, currentPath.value)
|
||||
|
||||
loading.value = false
|
||||
|
||||
tableData.value = data
|
||||
}
|
||||
|
||||
const selectionRows = ref([])
|
||||
|
||||
function onSelectionChange(selection) {
|
||||
selectionRows.value = selection
|
||||
}
|
||||
|
||||
function handleFile(row) {}
|
||||
|
||||
function handleDirectory(row) {
|
||||
currentPath.value += `/${row.name}`
|
||||
getTableData()
|
||||
}
|
||||
|
||||
function handleBreadcrumb(data) {
|
||||
const index = currentPath.value.indexOf(data.value)
|
||||
|
||||
currentPath.value = currentPath.value.slice(0, index + data.value.length)
|
||||
|
||||
getTableData()
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
if (breadcrumbModel.value.length <= 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
const value = breadcrumbModel.value
|
||||
.slice(0, -1)
|
||||
.map(item => item.value)
|
||||
.join('/')
|
||||
|
||||
currentPath.value = value
|
||||
|
||||
getTableData()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,22 +1,12 @@
|
||||
<template>
|
||||
<el-dropdown :hide-on-click="false" :disabled="loading">
|
||||
<div class="">
|
||||
<slot :loading="loading" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="handlePush(device)">
|
||||
<span class="" title="/sdcard/Download/">
|
||||
{{ $t('device.control.file.push') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="" @click="handleFile(device)">
|
||||
<slot />
|
||||
<FileDialog ref="fileDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useFileActions } from '$/composables/useFileActions/index.js'
|
||||
import FileDialog from './FileDialog/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
device: {
|
||||
@ -25,7 +15,11 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { loading, send: handlePush } = useFileActions()
|
||||
const fileDialogRef = ref()
|
||||
|
||||
function handleFile(device) {
|
||||
fileDialogRef.value.open(device)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="$t('device.actions.more.custom.name')"
|
||||
class="!w-[98%] el-dialog-flex el-dialog-beautify"
|
||||
class="!w-[98%] el-dialog-flex el-dialog--beautify"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@closed="onClosed"
|
||||
|
@ -3,7 +3,7 @@
|
||||
v-model="visible"
|
||||
:title="$t('device.task.list')"
|
||||
width="98%"
|
||||
class="el-dialog-beautify"
|
||||
class="el-dialog--beautify"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@closed="onClosed"
|
||||
@ -108,6 +108,8 @@
|
||||
>
|
||||
<EleTooltipButton
|
||||
v-if="['progress'].includes(taskStatus)"
|
||||
placement="top"
|
||||
:offset="2"
|
||||
text
|
||||
type="warning"
|
||||
effect="light"
|
||||
@ -120,6 +122,8 @@
|
||||
|
||||
<EleTooltipButton
|
||||
v-if="['finished'].includes(taskStatus)"
|
||||
placement="top"
|
||||
:offset="2"
|
||||
text
|
||||
type="primary"
|
||||
effect="light"
|
||||
@ -132,6 +136,8 @@
|
||||
</EleTooltipButton>
|
||||
|
||||
<EleTooltipButton
|
||||
placement="top"
|
||||
:offset="2"
|
||||
text
|
||||
type="danger"
|
||||
effect="light"
|
||||
@ -145,9 +151,7 @@
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="h-4"></div>
|
||||
</template>
|
||||
<template #footer></template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
"common.default": "默认",
|
||||
"common.tips": "提示",
|
||||
"common.open": "打开",
|
||||
"common.download": "下载",
|
||||
"common.input.placeholder": "请填写",
|
||||
"common.success": "操作成功",
|
||||
"common.success.batch": "批量操作成功",
|
||||
|
@ -1,14 +1,26 @@
|
||||
<template>
|
||||
<el-tooltip>
|
||||
<el-tooltip v-bind="{ offset: 8, ...$attrs }">
|
||||
<ElButton
|
||||
v-bind="{ ...$props }"
|
||||
:class="[{ '!border-none': borderless }, buttonClass]"
|
||||
:class="[
|
||||
{
|
||||
'!border-none': borderless,
|
||||
'el-button-space-none': !$slots.default,
|
||||
},
|
||||
buttonClass,
|
||||
]"
|
||||
@click="emit('click', $event)"
|
||||
>
|
||||
<slot name="icon"></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
|
||||
<slot></slot>
|
||||
</ElButton>
|
||||
<slot name="content"></slot>
|
||||
|
||||
<template #content>
|
||||
<slot name="content"></slot>
|
||||
</template>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
@ -22,6 +34,7 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
buttonClass: {
|
||||
type: [String, Array, Object],
|
||||
default: '',
|
||||
@ -31,4 +44,10 @@ const props = defineProps({
|
||||
const emit = defineEmits(['click'])
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style lang="postcss">
|
||||
.el-button.el-button-space-none {
|
||||
[class*='el-icon'] + span {
|
||||
@apply ml-0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -57,7 +57,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog-beautify {
|
||||
.el-dialog--beautify {
|
||||
@apply !rounded-lg;
|
||||
|
||||
.el-dialog__title {
|
||||
@ -68,4 +68,8 @@
|
||||
@apply absolute inset-x-0 bottom-0 h-2 bg-primary-500/30;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
@apply min-h-8;
|
||||
}
|
||||
}
|
||||
|
@ -123,3 +123,24 @@ export function clearTimer(type, ...args) {
|
||||
const method = camelCase(`clear-${type}`)
|
||||
return globalThis[method](...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件大小(字节)格式化为易读的字符串。
|
||||
* @function
|
||||
* @param {number} bytes - 文件大小(字节)。
|
||||
* @returns {string} 表示文件大小的易读字符串,包含适当的单位。
|
||||
* @example
|
||||
* formatFileSize(1024); // 返回 "1.00 KB"
|
||||
* formatFileSize(1234567); // 返回 "1.18 MB"
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (bytes === 0)
|
||||
return '0 Bytes'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user