mirror of
https://gitee.com/viarotel-org/escrcpy.git
synced 2024-11-29 18:01:34 +08:00
perf: ♻️ Optimize the performance of equipment interaction column and file manager
This commit is contained in:
parent
3b1fbbf8a0
commit
41ffcf5660
@ -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',
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
188
src/plugins/scrollable/components/Scrollable/index.vue
Normal file
188
src/plugins/scrollable/components/Scrollable/index.vue
Normal 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>
|
7
src/plugins/scrollable/index.js
Normal file
7
src/plugins/scrollable/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
import Scrollable from './components/Scrollable/index.vue'
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.component('Scrollable', Scrollable)
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue
Block a user