feat: 🚀 Initially add a new file manager

This commit is contained in:
viarotel 2024-09-07 19:20:23 +08:00
parent d9be5d5fe3
commit 94ee0070ef
13 changed files with 304 additions and 44 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -63,7 +63,7 @@ const actionModel = [
component: Application,
},
{
label: 'device.control.file.name',
label: 'device.control.file.push',
svgIcon: 'file-send',
component: FileManage,
},

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
"common.default": "默认",
"common.tips": "提示",
"common.open": "打开",
"common.download": "下载",
"common.input.placeholder": "请填写",
"common.success": "操作成功",
"common.success.batch": "批量操作成功",

View File

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

View File

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

View File

@ -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]}`
}