perf: ♻️ Optimize the performance of equipment interaction column and file manager

This commit is contained in:
viarotel 2024-09-08 23:36:34 +08:00
parent 3b1fbbf8a0
commit 41ffcf5660
7 changed files with 306 additions and 123 deletions

View File

@ -39,7 +39,7 @@
<script setup>
import Application from './Application/index.vue'
import FileManage from './FileManage/index.vue'
import FilePush from './FilePush/index.vue'
import Screenshot from './Screenshot/index.vue'
import Shell from './Shell/index.vue'
import Tasks from './Tasks/index.vue'
@ -65,7 +65,7 @@ const actionModel = [
{
label: 'device.control.file.push',
svgIcon: 'file-send',
component: FileManage,
component: FilePush,
},
{
label: 'device.control.shell.name',

View File

@ -5,6 +5,7 @@
width="97%"
append-to-body
destroy-on-close
:close-on-click-modal="false"
class="el-dialog--beautify"
@closed="onClosed"
>
@ -15,32 +16,26 @@
text
icon="Top"
circle
class="mr-2"
class="mr-2 flex-none"
@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>
<Scrollable ref="scrollableRef" class="flex-1 w-0 flex items-center">
<el-breadcrumb separator-icon="ArrowRight" class="!flex">
<el-breadcrumb-item
v-for="item of breadcrumbModel"
:key="item.value"
class="!flex-none"
@click="handleBreadcrumb(item)"
>
<el-button text class="!px-2" :icon="item.icon" :title="item.label">
{{ item.label }}
</el-button>
</el-breadcrumb-item>
</el-breadcrumb>
</Scrollable>
<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">
<div class="flex-none">
<el-button text icon="Refresh" circle @click="getTableData"></el-button>
</div>
</div>
@ -88,28 +83,31 @@
:selectable="(row) => ['file'].includes(row.type)"
></el-table-column>
<el-table-column prop="name" :label="$t('common.name')" sortable>
<el-table-column
prop="name"
:label="$t('common.name')"
sortable
show-overflow-tooltip
>
<template #default="{ row }">
<div class="flex items-center">
<el-link
v-if="row.type === 'directory'"
type="default"
icon="Folder"
class="!space-x-2"
@click="handleDirectory(row)"
>
{{ row.name }}
</el-link>
<el-link
v-else
type="default"
icon="Document"
class="!space-x-2"
@click="handleDownload(row)"
>
{{ row.name }}
</el-link>
</div>
<el-link
v-if="row.type === 'directory'"
type="default"
icon="Folder"
class="!space-x-2"
@click="handleDirectory(row)"
>
{{ row.name }}
</el-link>
<el-link
v-else
type="default"
icon="Document"
class="!space-x-2"
@click="handleDownload(row)"
>
{{ row.name }}
</el-link>
</template>
</el-table-column>
@ -185,15 +183,21 @@ const tableData = ref([])
const currentPath = ref('sdcard')
const breadcrumbModel = computed(() => {
const pathList = currentPath.value.split('/')
const presetMap = {
sdcard: {
icon: 'Iphone',
label: window.t('device.control.file.manager.storage'),
value: 'sdcard',
},
}
const value = pathList.map(item => ({
label:
item === 'sdcard'
? window.t('device.control.file.manager.storage')
: void 0,
const breadcrumbModel = computed(() => {
const list = currentPath.value.split('/')
const value = list.map(item => ({
label: item,
value: item,
...(presetMap[item] || {}),
}))
return value
@ -226,9 +230,14 @@ function onSelectionChange(selection) {
selectionRows.value = selection
}
function handleDirectory(row) {
const scrollableRef = ref()
async function handleDirectory(row) {
currentPath.value += `/${row.name}`
getTableData()
await nextTick()
scrollableRef.value.scrollToEnd()
}
function handleBreadcrumb(data) {

View File

@ -1,11 +1,10 @@
<template>
<div
ref="wheelContainer"
class="bg-primary-100 dark:bg-gray-800 -my-[8px] flex flex-nowrap overflow-hidden scroll-smooth px-4 group"
class="bg-primary-100 dark:bg-gray-800 flex items-center group -my-[8px] h-9 overflow-hidden"
>
<el-button
type="primary"
class="el-button-nav prev"
class="el-button-nav"
title="Prev"
@click="handlePrev"
>
@ -13,9 +12,50 @@
<CaretLeft />
</el-icon>
</el-button>
<Scrollable ref="scrollableRef" class="flex-1 w-0 flex items-center">
<component
:is="item.component || 'div'"
v-for="(item, index) in controlModel"
:key="index"
class="flex-none"
v-bind="{
device,
...(item.command
? {
onClick: () => handleShell(item),
}
: {}),
}"
>
<template #default="{ loading = false } = {}">
<el-button
type="primary"
plain
class="!border-none !mx-0 bg-transparent !rounded-0"
:disabled="device.$unauthorized"
:title="$t(item.tips || item.label)"
:loading="loading"
>
<template #icon>
<svg-icon
v-if="item.svgIcon"
:name="item.svgIcon"
:class="item.iconClass"
></svg-icon>
<el-icon v-else-if="item.elIcon" :class="item.iconClass">
<component :is="item.elIcon" />
</el-icon>
</template>
{{ $t(item.label) }}
</el-button>
</template>
</component>
</Scrollable>
<el-button
type="primary"
class="el-button-nav next"
class="el-button-nav"
title="Next"
@click="handleNext"
>
@ -23,44 +63,6 @@
<CaretRight />
</el-icon>
</el-button>
<component
:is="item.component || 'div'"
v-for="(item, index) in controlModel"
:key="index"
class="flex-none"
v-bind="{
device,
...(item.command
? {
onClick: () => handleShell(item),
}
: {}),
}"
>
<template #default="{ loading = false } = {}">
<el-button
type="primary"
plain
class="!border-none !mx-0 bg-transparent !rounded-0"
:disabled="device.$unauthorized"
:title="$t(item.tips || item.label)"
:loading="loading"
@wheel.prevent="onWheel"
>
<template #icon>
<svg-icon
v-if="item.svgIcon"
:name="item.svgIcon"
:class="item.iconClass"
></svg-icon>
<el-icon v-else-if="item.elIcon" :class="item.iconClass">
<component :is="item.elIcon" />
</el-icon>
</template>
{{ $t(item.label) }}
</el-button>
</template>
</component>
</div>
</template>
@ -182,30 +184,11 @@ export default {
},
computed: {},
methods: {
onWheel(event) {
const container = this.$refs.wheelContainer
container.scrollLeft += event.deltaY
},
handlePrev() {
const container = this.$refs.wheelContainer
if (container.scrollLeft <= 0) {
return false
}
container.scrollLeft -= 100
this.$refs.scrollableRef.scrollBackward()
},
handleNext() {
const container = this.$refs.wheelContainer
if (
container.scrollLeft
>= container.scrollWidth - container.clientWidth
) {
return false
}
container.scrollLeft += 100
this.$refs.scrollableRef.scrollForward()
},
handleShell(row) {
this.$adb.deviceShell(this.device.id, row.command)
@ -220,12 +203,6 @@ export default {
}
.el-button.el-button-nav {
@apply p-0 rounded-none border-0 absolute z-10 inset-y-0 flex items-center justify-center opacity-0 bg-primary-100 dark:bg-gray-800 !hover:bg-primary-300 active:bg-primary-500 text-primary-600 hover:text-white w-4 group-hover:opacity-100 transition-opacity;
&.prev {
@apply left-0;
}
&.next {
@apply right-0;
}
@apply flex-none p-0 rounded-none border-0 h-full flex items-center justify-center opacity-0 bg-primary-100 dark:bg-gray-800 !hover:bg-primary-300 active:bg-primary-500 text-primary-600 hover:text-white w-4 group-hover:opacity-100 transition-opacity;
}
</style>

View File

@ -1,7 +1,9 @@
import ElementPlus from './element-plus/index.js'
import Scrollable from './scrollable/index.js'
export default {
install(app) {
app.use(ElementPlus)
app.use(Scrollable)
},
}

View File

@ -0,0 +1,188 @@
<template>
<div
ref="container"
class="overflow-hidden"
:class="{ 'cursor-grab': !isDragging, 'cursor-grabbing': isDragging }"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="endDrag"
@mouseleave="endDrag"
@wheel="onWheel"
>
<div
ref="content"
class="inline-flex"
:class="{ 'flex-col': direction === 'vertical' }"
:style="contentStyle"
>
<slot></slot>
</div>
</div>
</template>
<script setup>
defineOptions({
name: 'Scrollable',
})
const props = defineProps({
direction: {
type: String,
default: 'horizontal',
validator: value => ['horizontal', 'vertical'].includes(value),
},
speed: {
type: Number,
default: 1,
},
})
const container = ref(null)
const content = ref(null)
const isDragging = ref(false)
const startX = ref(0)
const startY = ref(0)
const scrollLeft = ref(0)
const scrollTop = ref(0)
const contentStyle = computed(() => ({
transform:
props.direction === 'horizontal'
? `translateX(${-scrollLeft.value}px)`
: `translateY(${-scrollTop.value}px)`,
transition: isDragging.value ? 'none' : 'transform 0.3s ease-out',
}))
const startDrag = (e) => {
isDragging.value = true
startX.value = e.pageX - container.value.offsetLeft
startY.value = e.pageY - container.value.offsetTop
container.value.style.cursor = 'grabbing'
}
const onDrag = (e) => {
if (!isDragging.value)
return
e.preventDefault()
const x = e.pageX - container.value.offsetLeft
const y = e.pageY - container.value.offsetTop
const walkX = (x - startX.value) * props.speed
const walkY = (y - startY.value) * props.speed
if (props.direction === 'horizontal') {
scrollLeft.value = Math.max(
0,
Math.min(
scrollLeft.value - walkX,
content.value.offsetWidth - container.value.offsetWidth,
),
)
}
else {
scrollTop.value = Math.max(
0,
Math.min(
scrollTop.value - walkY,
content.value.offsetHeight - container.value.offsetHeight,
),
)
}
startX.value = x
startY.value = y
}
const endDrag = () => {
isDragging.value = false
container.value.style.cursor = 'grab'
}
const onWheel = (e) => {
e.preventDefault()
const delta
= props.direction === 'horizontal' ? e.deltaX || e.deltaY : e.deltaY
const newScroll
= (props.direction === 'horizontal' ? scrollLeft.value : scrollTop.value)
+ delta * props.speed
if (props.direction === 'horizontal') {
scrollLeft.value = Math.max(
0,
Math.min(
newScroll,
content.value.offsetWidth - container.value.offsetWidth,
),
)
}
else {
scrollTop.value = Math.max(
0,
Math.min(
newScroll,
content.value.offsetHeight - container.value.offsetHeight,
),
)
}
}
const getIncrement = () => {
return 100 * props.speed
}
const scrollToEnd = () => {
if (props.direction === 'horizontal') {
const maxScroll = Math.max(
0,
content.value.offsetWidth - container.value.offsetWidth,
)
scrollLeft.value = maxScroll
}
else {
const maxScroll = Math.max(
0,
content.value.offsetHeight - container.value.offsetHeight,
)
scrollTop.value = maxScroll
}
}
const scrollForward = () => {
const increment = getIncrement()
if (props.direction === 'horizontal') {
scrollLeft.value = Math.min(
scrollLeft.value + increment,
content.value.offsetWidth - container.value.offsetWidth,
)
}
else {
scrollTop.value = Math.min(
scrollTop.value + increment,
content.value.offsetHeight - container.value.offsetHeight,
)
}
}
const scrollBackward = () => {
const increment = getIncrement()
if (props.direction === 'horizontal') {
scrollLeft.value = Math.max(scrollLeft.value - increment, 0)
}
else {
scrollTop.value = Math.max(scrollTop.value - increment, 0)
}
}
onMounted(() => {
window.addEventListener('mouseup', endDrag)
})
onUnmounted(() => {
window.removeEventListener('mouseup', endDrag)
})
defineExpose({
scrollToEnd,
scrollForward,
scrollBackward,
})
</script>

View File

@ -0,0 +1,7 @@
import Scrollable from './components/Scrollable/index.vue'
export default {
install(app) {
app.component('Scrollable', Scrollable)
},
}