mirror of
https://github.com/viarotel-org/escrcpy.git
synced 2024-11-14 18:57:40 +01:00
feat: 🚀 Add basic timing task function
This commit is contained in:
parent
8e6af2087e
commit
04a760897e
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -13,6 +13,8 @@ declare module 'vue' {
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
|
@ -23,6 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-in-page-search": "^1.3.2",
|
||||
"nanoid": "^5.0.7",
|
||||
"vue": "^3.4.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
61
src/App.vue
61
src/App.vue
@ -1,29 +1,31 @@
|
||||
<template>
|
||||
<div class="absolute inset-0 px-4 pb-4 h-full">
|
||||
<el-tabs
|
||||
v-model="activeTab"
|
||||
class="el-tabs-flex"
|
||||
addable
|
||||
@tab-change="onTabChange"
|
||||
>
|
||||
<template #add-icon>
|
||||
<AppSearch />
|
||||
</template>
|
||||
<el-tab-pane
|
||||
v-for="(item, index) of tabsModel"
|
||||
:key="index"
|
||||
:label="$t(item.label)"
|
||||
:name="item.prop"
|
||||
lazy
|
||||
<el-config-provider :locale="locale">
|
||||
<div class="absolute inset-0 px-4 pb-4 h-full">
|
||||
<el-tabs
|
||||
v-model="activeTab"
|
||||
class="el-tabs-flex"
|
||||
addable
|
||||
@tab-change="onTabChange"
|
||||
>
|
||||
<component
|
||||
:is="item.component"
|
||||
v-if="isRender(item)"
|
||||
:ref="item.prop"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #add-icon>
|
||||
<AppSearch />
|
||||
</template>
|
||||
<el-tab-pane
|
||||
v-for="(item, index) of tabsModel"
|
||||
:key="index"
|
||||
:label="$t(item.label)"
|
||||
:name="item.prop"
|
||||
lazy
|
||||
>
|
||||
<component
|
||||
:is="item.component"
|
||||
v-if="isRender(item)"
|
||||
:ref="item.prop"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -34,9 +36,20 @@ import Preference from './components/Preference/index.vue'
|
||||
import About from './components/About/index.vue'
|
||||
import AppSearch from './components/Search/index.vue'
|
||||
|
||||
import { i18n } from '$/locales/index.js'
|
||||
import localeModel from '$/plugins/element-plus/locale.js'
|
||||
|
||||
import { useThemeStore } from '$/store/theme/index.js'
|
||||
import { usePreferenceStore } from '$/store/preference/index.js'
|
||||
|
||||
const locale = computed(() => {
|
||||
const i18nLocale = i18n.global.locale.value
|
||||
|
||||
const value = localeModel[i18nLocale]
|
||||
|
||||
return value
|
||||
})
|
||||
|
||||
const tabsModel = ref([
|
||||
{
|
||||
label: 'device.list',
|
||||
|
@ -1,76 +1,31 @@
|
||||
<template>
|
||||
<div class="" @click="handleClick">
|
||||
<div class="" @click="handleClick(devices)">
|
||||
<slot v-bind="{ loading }" />
|
||||
<ApplicationProxy ref="applicationProxyRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ApplicationProxy from '$/components/Device/components/ControlBar/Application/index.vue'
|
||||
import { allSettledWrapper } from '$/utils'
|
||||
<script setup>
|
||||
import { useInstallAction } from '$/composables/useInstallAction/index.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ApplicationProxy,
|
||||
import { useTaskStore } from '$/store/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
devices: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
props: {
|
||||
devices: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleClick() {
|
||||
let files = null
|
||||
})
|
||||
|
||||
try {
|
||||
files = await this.$electron.ipcRenderer.invoke('show-open-dialog', {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{
|
||||
name: this.$t('device.control.install.placeholder'),
|
||||
extensions: ['apk'],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
const message
|
||||
= error.message?.match(/Error: (.*)/)?.[1] || error.message
|
||||
this.$message.warning(message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
const { loading, invoke: handleClick } = useInstallAction()
|
||||
|
||||
this.loading = true
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
const closeMessage = this.$message.loading(
|
||||
this.$t('device.control.install.progress', {
|
||||
deviceName: window.t('common.device'),
|
||||
}),
|
||||
).close
|
||||
|
||||
await allSettledWrapper(this.devices, (item) => {
|
||||
return this.$refs.applicationProxyRef.invoke(item, {
|
||||
files,
|
||||
silent: true,
|
||||
})
|
||||
})
|
||||
|
||||
closeMessage()
|
||||
|
||||
ElMessage.success(window.t('common.success.batch'))
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
}
|
||||
taskStore.on('install', (task) => {
|
||||
taskStore.start({
|
||||
task,
|
||||
handler: handleClick,
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -2,7 +2,6 @@
|
||||
<el-dropdown :hide-on-click="false" :disabled="loading">
|
||||
<div class="">
|
||||
<slot :loading="loading" />
|
||||
<FileManageProxy ref="fileManageProxyRef" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
@ -17,11 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import FileManageProxy from '$/components/Device/components/ControlBar/FileManage/index.vue'
|
||||
|
||||
import { selectAndSendFileToDevice } from '$/utils/device/index.js'
|
||||
import { allSettledWrapper } from '$/utils'
|
||||
import { useFileActions } from '$/composables/useFileActions/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
devices: {
|
||||
@ -30,48 +25,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const fileManageProxyRef = ref(null)
|
||||
|
||||
async function handlePush(devices) {
|
||||
let files = null
|
||||
|
||||
try {
|
||||
files = await window.electron.ipcRenderer.invoke('show-open-dialog', {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{
|
||||
name: window.t('device.control.file.push.placeholder'),
|
||||
extensions: ['*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
const message = error.message?.match(/Error: (.*)/)?.[1] || error.message
|
||||
ElMessage.warning(message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const closeMessage = ElMessage.loading(
|
||||
window.t('device.control.file.push.loading'),
|
||||
).close
|
||||
|
||||
await allSettledWrapper(devices, (item) => {
|
||||
return fileManageProxyRef.value.handlePush(item, { files, silent: true })
|
||||
})
|
||||
|
||||
closeMessage()
|
||||
|
||||
ElMessage.success(window.t('common.success.batch'))
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
const { loading, send: handlePush } = useFileActions()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,51 +1,31 @@
|
||||
<template>
|
||||
<div class="" @click="handleClick">
|
||||
<div class="" @click="handleClick(devices)">
|
||||
<slot v-bind="{ loading }" />
|
||||
<ScreenshotProxy ref="screenshotProxyRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScreenshotProxy from '$/components/Device/components/ControlBar/Screenshot/index.vue'
|
||||
import { allSettledWrapper, sleep } from '$/utils'
|
||||
<script setup>
|
||||
import { useTaskStore } from '$/store/index.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ScreenshotProxy,
|
||||
import { useScreenshotAction } from '$/composables/useScreenshotAction/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
devices: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
props: {
|
||||
devices: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleClick() {
|
||||
this.loading = true
|
||||
})
|
||||
|
||||
const closeMessage = this.$message.loading(
|
||||
window.t('device.control.capture.progress', {
|
||||
deviceName: window.t('common.device'),
|
||||
}),
|
||||
).close
|
||||
const { loading, invoke: handleClick } = useScreenshotAction()
|
||||
|
||||
await allSettledWrapper(this.devices, (item) => {
|
||||
return this.$refs.screenshotProxyRef.invoke(item, { silent: true })
|
||||
})
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
closeMessage()
|
||||
|
||||
ElMessage.success(window.t('common.success.batch'))
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
}
|
||||
taskStore.on('screenshot', (task) => {
|
||||
taskStore.start({
|
||||
task,
|
||||
handler: handleClick,
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -5,9 +5,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { selectAndSendFileToDevice } from '$/utils/device/index.js'
|
||||
import { allSettledWrapper } from '$/utils'
|
||||
import { useShellAction } from '$/composables/useShellAction/index.js'
|
||||
import { useTaskStore } from '$/store/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
devices: {
|
||||
@ -16,73 +15,16 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const { loading, invoke: handleClick } = useShellAction()
|
||||
|
||||
async function handleClick(devices) {
|
||||
let files = null
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
try {
|
||||
files = await window.electron.ipcRenderer.invoke('show-open-dialog', {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: window.t('device.control.shell.select'),
|
||||
extensions: ['sh'],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
const message = error.message?.match(/Error: (.*)/)?.[1]
|
||||
ElMessage.warning(message || error.message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const closeLoading = ElMessage.loading(
|
||||
window.t('device.control.shell.push.loading'),
|
||||
).close
|
||||
|
||||
const failFiles = []
|
||||
|
||||
await allSettledWrapper(devices, async (device) => {
|
||||
const successFiles = await selectAndSendFileToDevice(device.id, {
|
||||
files,
|
||||
silent: true,
|
||||
}).catch((e) => {
|
||||
console.warn(e.message)
|
||||
failFiles.push(e.message)
|
||||
})
|
||||
|
||||
const filePath = successFiles?.[0]
|
||||
|
||||
if (filePath) {
|
||||
window.adbkit.deviceShell(device.id, `sh ${filePath}`)
|
||||
}
|
||||
taskStore.on('shell', (task) => {
|
||||
taskStore.start({
|
||||
task,
|
||||
handler: handleClick,
|
||||
})
|
||||
|
||||
if (failFiles.length) {
|
||||
ElMessageBox.alert(
|
||||
`<div>${failFiles.map(text => `${text}<br/>`).join('')}</div>`,
|
||||
window.t('common.tips'),
|
||||
{
|
||||
type: 'warning',
|
||||
dangerouslyUseHTMLString: true,
|
||||
},
|
||||
)
|
||||
loading.value = false
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
|
||||
await ElMessage.success(window.t('device.control.shell.success'))
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="定时任务"
|
||||
width="60%"
|
||||
class="el-dialog-beautify"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@closed="onClosed"
|
||||
>
|
||||
<ele-form-row
|
||||
ref="formRef"
|
||||
:model="model"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
class="!pr-[120px] !pt-4"
|
||||
>
|
||||
<ele-form-item-col label="任务类型" :span="24" prop="taskType">
|
||||
<el-select
|
||||
v-model="model.taskType"
|
||||
placeholder="请选择任务类型"
|
||||
clearable
|
||||
filterable
|
||||
@change="onTaskChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in taskModel"
|
||||
:key="item.value"
|
||||
:label="$t(item.label)"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</ele-form-item-col>
|
||||
<ele-form-item-col label="执行频率" :span="24" prop="timerType">
|
||||
<el-radio-group v-model="model.timerType">
|
||||
<el-radio
|
||||
v-for="(item, index) of timerModel"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</ele-form-item-col>
|
||||
|
||||
<ele-form-item-col
|
||||
v-if="['timeout'].includes(model.timerType)"
|
||||
label="执行时间"
|
||||
:span="24"
|
||||
prop="timeout"
|
||||
>
|
||||
<el-date-picker
|
||||
v-model="model.timeout"
|
||||
type="datetime"
|
||||
placeholder="0000-00-00 00:00:00"
|
||||
clearable
|
||||
v-bind="{ disabledDate, defaultTime }"
|
||||
></el-date-picker>
|
||||
</ele-form-item-col>
|
||||
|
||||
<ele-form-item-col
|
||||
v-if="['interval'].includes(model.timerType)"
|
||||
label="重复规则"
|
||||
:span="24"
|
||||
prop="interval"
|
||||
>
|
||||
<el-input
|
||||
v-model="model.interval"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
clearable
|
||||
>
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model="model.intervalType"
|
||||
placeholder="请选择时间单位"
|
||||
filterable
|
||||
class="!w-24"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) of intervalModel"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</ele-form-item-col>
|
||||
|
||||
<ele-form-item-col
|
||||
v-if="['install'].includes(model.taskType)"
|
||||
label="选择应用"
|
||||
:span="24"
|
||||
prop="extra"
|
||||
>
|
||||
<InputPath
|
||||
v-model="model.extra"
|
||||
:placeholder="$t('device.control.install.placeholder')"
|
||||
:data="{
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{
|
||||
name: $t('device.control.install.placeholder'),
|
||||
extensions: ['apk'],
|
||||
},
|
||||
],
|
||||
}"
|
||||
/>
|
||||
</ele-form-item-col>
|
||||
|
||||
<ele-form-item-col
|
||||
v-if="['shell'].includes(model.taskType)"
|
||||
label="选择脚本"
|
||||
:span="24"
|
||||
prop="extra"
|
||||
>
|
||||
<InputPath
|
||||
v-model="model.extra"
|
||||
:placeholder="$t('device.control.shell.select')"
|
||||
:data="{
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: $t('device.control.shell.select'),
|
||||
extensions: ['sh'],
|
||||
},
|
||||
],
|
||||
}"
|
||||
/>
|
||||
</ele-form-item-col>
|
||||
</ele-form-row>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button :loading type="primary" @click="submit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import {
|
||||
timeUnit as intervalModel,
|
||||
timerType as timerModel,
|
||||
} from '$/dicts/index.js'
|
||||
|
||||
import { useTaskStore } from '$/store/index.js'
|
||||
import { sleep } from '$/utils'
|
||||
|
||||
import InputPath from '$/components/Preference/components/PreferenceForm/components/InputPath/index.vue'
|
||||
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const model = ref({
|
||||
taskType: void 0,
|
||||
timerType: 'timeout',
|
||||
timeout: void 0,
|
||||
interval: void 0,
|
||||
intervalType: 'second',
|
||||
extra: void 0,
|
||||
})
|
||||
|
||||
const rules = computed(() =>
|
||||
Object.keys(model.value).reduce((obj, item) => {
|
||||
obj[item] = [{ required: true, message: '该选项不能为空', trigger: 'blur' }]
|
||||
|
||||
if (item === 'timeout') {
|
||||
obj[item].push({
|
||||
trigger: 'blur',
|
||||
validator: (rule, value, callback) => {
|
||||
if (value.getTime() <= Date.now()) {
|
||||
callback(new Error('不能小于当前时间'))
|
||||
}
|
||||
else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return obj
|
||||
}, {}),
|
||||
)
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const taskModel = computed(() => taskStore.model)
|
||||
|
||||
const devices = ref(null)
|
||||
|
||||
const defaultTime = ref(null)
|
||||
|
||||
function open(args) {
|
||||
visible.value = true
|
||||
|
||||
if (args.devices) {
|
||||
devices.value = args.devices
|
||||
}
|
||||
else if (args.device) {
|
||||
devices.value = [args.device]
|
||||
}
|
||||
|
||||
defaultTime.value = new Date()
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
}
|
||||
catch (error) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
await taskStore.add({ ...model.value, devices: devices.value })
|
||||
await sleep()
|
||||
await ElMessage.success(window.t('common.success'))
|
||||
|
||||
loading.value = false
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
async function onClosed() {
|
||||
devices.value = null
|
||||
formRef.value.resetFields()
|
||||
formRef.value.clearValidate()
|
||||
}
|
||||
|
||||
function disabledDate(time) {
|
||||
return time.getTime() < Date.now() - 24 * 60 * 60 * 1000
|
||||
}
|
||||
|
||||
function onTaskChange() {
|
||||
model.value.extra = void 0
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<div class="" @click="handleClick(devices)">
|
||||
<slot v-bind="{ loading }" />
|
||||
|
||||
<TaskDialog ref="taskDialogRef"></TaskDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TaskDialog from './components/TaskDialog/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
devices: {
|
||||
type: Array,
|
||||
@ -13,6 +17,12 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const taskDialogRef = ref(null)
|
||||
|
||||
function handleClick(devices) {
|
||||
taskDialogRef.value.open({ devices })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-dropdown :hide-on-click="false">
|
||||
<el-dropdown :hide-on-click="false" :disabled="loading">
|
||||
<div class="">
|
||||
<slot v-bind="{ loading }" />
|
||||
<slot :loading="loading" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
@ -16,10 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { selectAndSendFileToDevice } from '$/utils/device/index.js'
|
||||
import { useDeviceStore } from '$/store'
|
||||
import { allSettledWrapper } from '$/utils'
|
||||
import { useFileActions } from '$/composables/useFileActions/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
device: {
|
||||
@ -28,94 +25,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function handlePush(device, { files, silent = false } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await window.electron.ipcRenderer.invoke('show-open-dialog', {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{
|
||||
name: window.t('device.control.file.push.placeholder'),
|
||||
extensions: ['*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
const message
|
||||
= error.message?.match(/Error: (.*)/)?.[1] || error.message
|
||||
ElMessage.warning(message)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
let closeLoading
|
||||
|
||||
if (!silent) {
|
||||
closeLoading = ElMessage.loading(
|
||||
`${deviceStore.getLabel(device)}: ${window.t(
|
||||
'device.control.file.push.loading',
|
||||
)}`,
|
||||
).close
|
||||
}
|
||||
|
||||
let failCount = 0
|
||||
|
||||
await allSettledWrapper(files, (item) => {
|
||||
return window.adbkit.push(device.id, item).catch(() => {
|
||||
++failCount
|
||||
})
|
||||
})
|
||||
|
||||
loading.value = false
|
||||
|
||||
if (silent) {
|
||||
return false
|
||||
}
|
||||
|
||||
const totalCount = files.length
|
||||
const successCount = totalCount - failCount
|
||||
|
||||
if (successCount) {
|
||||
closeLoading()
|
||||
|
||||
if (totalCount > 1) {
|
||||
ElMessage.success(
|
||||
window.t('device.control.file.push.success', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
totalCount,
|
||||
successCount,
|
||||
failCount,
|
||||
}),
|
||||
)
|
||||
}
|
||||
else {
|
||||
ElMessage.success(
|
||||
window.t('device.control.file.push.success.single', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
ElMessage.warning(window.t('device.control.file.push.error'))
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handlePush,
|
||||
})
|
||||
const { loading, send: handlePush } = useFileActions()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,69 +1,20 @@
|
||||
<template>
|
||||
<div class="" @click="handleCapture(device)">
|
||||
<slot />
|
||||
<div class="" @click="handleClick(device)">
|
||||
<slot v-bind="{ loading }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
device: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
<script setup>
|
||||
import { useScreenshotAction } from '$/composables/useScreenshotAction/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
device: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
invoke(...args) {
|
||||
return this.handleCapture(...args)
|
||||
},
|
||||
preferenceData(...args) {
|
||||
return this.$store.preference.getData(...args)
|
||||
},
|
||||
async handleCapture(device, { silent = false } = {}) {
|
||||
let closeLoading
|
||||
if (!silent) {
|
||||
closeLoading = this.$message.loading(
|
||||
this.$t('device.control.capture.progress', {
|
||||
deviceName: this.$store.device.getLabel(device),
|
||||
}),
|
||||
).close
|
||||
}
|
||||
})
|
||||
|
||||
const fileName = this.$store.device.getLabel(
|
||||
device,
|
||||
({ time }) => `screenshot-${time}.jpg`,
|
||||
)
|
||||
|
||||
const deviceConfig = this.preferenceData(device.id)
|
||||
const savePath = this.$path.resolve(deviceConfig.savePath, fileName)
|
||||
|
||||
try {
|
||||
await this.$adb.screencap(device.id, { savePath })
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
this.$message.warning(error.message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (silent) {
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
|
||||
this.$message.success(
|
||||
`${this.$t(
|
||||
'device.control.capture.success.message.title',
|
||||
)}: ${savePath}`,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
const { loading, invoke: handleClick } = useScreenshotAction()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -5,8 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { selectAndSendFileToDevice } from '$/utils/device/index.js'
|
||||
import { useShellAction } from '$/composables/useShellAction/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
device: {
|
||||
@ -15,37 +14,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const invokeTerminal = inject('invokeTerminal')
|
||||
|
||||
async function handleClick(device) {
|
||||
let files = null
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
files = await selectAndSendFileToDevice(device.id, {
|
||||
extensions: ['sh'],
|
||||
selectText: window.t('device.control.shell.select'),
|
||||
loadingText: window.t('device.control.shell.push.loading'),
|
||||
successText: window.t('device.control.shell.push.success'),
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
loading.value = false
|
||||
ElMessage.warning(error.message)
|
||||
return false
|
||||
}
|
||||
|
||||
const filePath = files[0]
|
||||
|
||||
const command = `adb -s ${device.id} shell sh ${filePath}`
|
||||
|
||||
invokeTerminal(command)
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
const { loading, invoke: handleClick } = useShellAction()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="定时任务"
|
||||
width="60%"
|
||||
class="el-dialog-beautify"
|
||||
append-to-body
|
||||
@closed="onClosed"
|
||||
>
|
||||
<ele-form-row :model="model" label-width="120px" class="!pr-[120px] !pt-4">
|
||||
<ele-form-item-col label="任务类型" :span="24">
|
||||
<el-select v-model="model.taskType" placeholder="请选择任务类型">
|
||||
<el-option
|
||||
v-for="item in taskModel"
|
||||
:key="item.value"
|
||||
:label="$t(item.label)"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</ele-form-item-col>
|
||||
<ele-form-item-col label="定时器类型" :span="24">
|
||||
<el-radio-group v-model="model.timerType">
|
||||
<el-radio
|
||||
v-for="(item, index) of timerModel"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</ele-form-item-col>
|
||||
</ele-form-row>
|
||||
<template #footer>
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const visible = ref(false)
|
||||
|
||||
const model = ref({
|
||||
taskType: '',
|
||||
timerType: 'timeout',
|
||||
})
|
||||
|
||||
const taskModel = [
|
||||
{
|
||||
label: 'device.control.install',
|
||||
value: 'install',
|
||||
},
|
||||
{
|
||||
label: 'device.control.capture',
|
||||
value: 'screenshot',
|
||||
},
|
||||
{
|
||||
label: 'device.control.shell.name',
|
||||
value: 'shell',
|
||||
},
|
||||
]
|
||||
|
||||
const timerModel = [
|
||||
{
|
||||
label: '单次',
|
||||
value: 'timeout',
|
||||
},
|
||||
{
|
||||
label: '周期',
|
||||
value: 'interval',
|
||||
},
|
||||
]
|
||||
|
||||
function open() {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function submit() {}
|
||||
|
||||
function onClosed() {}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -7,12 +7,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TaskDialog from './components/TaskDialog/index.vue'
|
||||
import TaskDialog from '$/components/Device/components/BatchActions/Tasks/components/TaskDialog/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
device: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
@ -21,7 +21,7 @@ const loading = ref(false)
|
||||
const taskDialogRef = ref(null)
|
||||
|
||||
function handleClick(device) {
|
||||
taskDialogRef.value.open(device)
|
||||
taskDialogRef.value.open({ devices: [device] })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -48,9 +48,10 @@ import { useAdb } from './composables/adb-async.js'
|
||||
import { useScrcpy } from './composables/scrcpy.js'
|
||||
import { useGnirehtet } from './composables/gnirehtet.js'
|
||||
import { sleep } from '$/utils/index.js'
|
||||
import { useThemeStore } from '$/store/index.js'
|
||||
import { useTaskStore, useThemeStore } from '$/store/index.js'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const visible = ref(false)
|
||||
@ -166,6 +167,10 @@ function onClosed() {
|
||||
history.value = [createQuery()]
|
||||
}
|
||||
|
||||
taskStore.on('terminal', (task) => {
|
||||
invoke(task.command)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
|
@ -150,11 +150,6 @@ export default {
|
||||
WirelessAction,
|
||||
BatchActions,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
invokeTerminal: (...args) => this.$refs.terminalActionRef.invoke(...args),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
@ -70,7 +70,7 @@ export default {
|
||||
},
|
||||
)
|
||||
|
||||
const value = files[0]
|
||||
const value = files.join(',')
|
||||
|
||||
this.pathValue = value
|
||||
}
|
||||
|
149
src/composables/useFileActions/index.js
Normal file
149
src/composables/useFileActions/index.js
Normal file
@ -0,0 +1,149 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { allSettledWrapper } from '$/utils'
|
||||
import { useDeviceStore } from '$/store'
|
||||
|
||||
export function useFileActions() {
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
function send(...args) {
|
||||
const [devices] = args
|
||||
|
||||
if (Array.isArray(devices)) {
|
||||
return multipleSend(...args)
|
||||
}
|
||||
|
||||
return singleSend(...args)
|
||||
}
|
||||
|
||||
async function selectFiles() {
|
||||
try {
|
||||
const files = await window.electron.ipcRenderer.invoke(
|
||||
'show-open-dialog',
|
||||
{
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{
|
||||
name: window.t('device.control.file.push.placeholder'),
|
||||
extensions: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
return files
|
||||
}
|
||||
catch (error) {
|
||||
const message = error.message?.match(/Error: (.*)/)?.[1] || error.message
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function singleSend(device, { files, silent = false } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await selectFiles()
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.warning(error.message)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
let closeLoading
|
||||
|
||||
if (!silent) {
|
||||
closeLoading = ElMessage.loading(
|
||||
`${deviceStore.getLabel(device)}: ${window.t(
|
||||
'device.control.file.push.loading',
|
||||
)}`,
|
||||
).close
|
||||
}
|
||||
|
||||
let failCount = 0
|
||||
|
||||
await allSettledWrapper(files, (item) => {
|
||||
return window.adbkit.push(device.id, item).catch(() => {
|
||||
++failCount
|
||||
})
|
||||
})
|
||||
|
||||
loading.value = false
|
||||
|
||||
if (silent) {
|
||||
return false
|
||||
}
|
||||
|
||||
const totalCount = files.length
|
||||
const successCount = totalCount - failCount
|
||||
|
||||
if (successCount) {
|
||||
closeLoading()
|
||||
|
||||
if (totalCount > 1) {
|
||||
ElMessage.success(
|
||||
window.t('device.control.file.push.success', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
totalCount,
|
||||
successCount,
|
||||
failCount,
|
||||
}),
|
||||
)
|
||||
}
|
||||
else {
|
||||
ElMessage.success(
|
||||
window.t('device.control.file.push.success.single', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
ElMessage.warning(window.t('device.control.file.push.error'))
|
||||
}
|
||||
|
||||
async function multipleSend(devices, { files } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await selectFiles()
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.warning(error.message)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const closeMessage = ElMessage.loading(
|
||||
window.t('device.control.file.push.loading'),
|
||||
).close
|
||||
|
||||
await allSettledWrapper(devices, (item) => {
|
||||
return singleSend(item, { files, silent: true })
|
||||
})
|
||||
|
||||
closeMessage()
|
||||
|
||||
ElMessage.success(window.t('common.success.batch'))
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
send,
|
||||
selectFiles,
|
||||
singleSend,
|
||||
multipleSend,
|
||||
}
|
||||
}
|
150
src/composables/useInstallAction/index.js
Normal file
150
src/composables/useInstallAction/index.js
Normal file
@ -0,0 +1,150 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { allSettledWrapper } from '$/utils'
|
||||
|
||||
import { useDeviceStore } from '$/store'
|
||||
|
||||
export function useInstallAction() {
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
function invoke(...args) {
|
||||
const [devices] = args
|
||||
|
||||
if (Array.isArray(devices)) {
|
||||
return multipleInvoke(...args)
|
||||
}
|
||||
|
||||
return singleInvoke(...args)
|
||||
}
|
||||
|
||||
async function selectFiles() {
|
||||
try {
|
||||
const files = await window.electron.ipcRenderer.invoke(
|
||||
'show-open-dialog',
|
||||
{
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{
|
||||
name: window.t('device.control.install.placeholder'),
|
||||
extensions: ['apk'],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
return files
|
||||
}
|
||||
catch (error) {
|
||||
const message = error.message?.match(/Error: (.*)/)?.[1] || error.message
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function singleInvoke(device, { files, silent = false } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await selectFiles()
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.warning(error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let closeLoading = null
|
||||
if (!silent) {
|
||||
closeLoading = ElMessage.loading(
|
||||
window.t('device.control.install.progress', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
}),
|
||||
).close
|
||||
}
|
||||
|
||||
let failCount = 0
|
||||
|
||||
await allSettledWrapper(files, (item) => {
|
||||
return window.adbkit.install(device.id, item).catch((e) => {
|
||||
console.warn(e)
|
||||
++failCount
|
||||
})
|
||||
})
|
||||
|
||||
if (silent) {
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
|
||||
const totalCount = files.length
|
||||
const successCount = totalCount - failCount
|
||||
|
||||
if (successCount) {
|
||||
if (totalCount > 1) {
|
||||
ElMessage.success(
|
||||
window.t('device.control.install.success', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
totalCount,
|
||||
successCount,
|
||||
failCount,
|
||||
}),
|
||||
)
|
||||
}
|
||||
else {
|
||||
ElMessage.success(
|
||||
window.t('device.control.install.success.single', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
}),
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ElMessage.warning(window.t('device.control.install.error'))
|
||||
}
|
||||
|
||||
async function multipleInvoke(devices, { files } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await selectFiles()
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.warning(error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const closeMessage = ElMessage.loading(
|
||||
window.t('device.control.install.progress', {
|
||||
deviceName: window.t('common.device'),
|
||||
}),
|
||||
).close
|
||||
|
||||
await allSettledWrapper(devices, (item) => {
|
||||
return singleInvoke(item, {
|
||||
files,
|
||||
silent: true,
|
||||
})
|
||||
})
|
||||
|
||||
closeMessage()
|
||||
|
||||
ElMessage.success(window.t('common.success.batch'))
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
invoke,
|
||||
loading,
|
||||
deviceStore,
|
||||
selectFiles,
|
||||
multipleInvoke,
|
||||
singleInvoke,
|
||||
}
|
||||
}
|
||||
|
||||
export default useInstallAction
|
90
src/composables/useScreenshotAction/index.js
Normal file
90
src/composables/useScreenshotAction/index.js
Normal file
@ -0,0 +1,90 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { allSettledWrapper } from '$/utils/index.js'
|
||||
|
||||
import { useDeviceStore, usePreferenceStore } from '$/store'
|
||||
|
||||
export function useScreenshotAction() {
|
||||
const deviceStore = useDeviceStore()
|
||||
const preferenceStore = usePreferenceStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
function invoke(...args) {
|
||||
const [devices] = args
|
||||
|
||||
if (Array.isArray(devices)) {
|
||||
return multipleInvoke(...args)
|
||||
}
|
||||
|
||||
return singleInvoke(...args)
|
||||
}
|
||||
|
||||
async function singleInvoke(device, { silent = false } = {}) {
|
||||
let closeLoading
|
||||
|
||||
if (!silent) {
|
||||
closeLoading = ElMessage.loading(
|
||||
window.t('device.control.capture.progress', {
|
||||
deviceName: deviceStore.getLabel(device),
|
||||
}),
|
||||
).close
|
||||
}
|
||||
|
||||
const fileName = deviceStore.getLabel(
|
||||
device,
|
||||
({ time }) => `screenshot-${time}.jpg`,
|
||||
)
|
||||
|
||||
const deviceConfig = preferenceStore.getData(device.id)
|
||||
const savePath = window.nodePath.resolve(deviceConfig.savePath, fileName)
|
||||
|
||||
try {
|
||||
await window.adbkit.screencap(device.id, { savePath })
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
ElMessage.warning(error.message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (silent) {
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
|
||||
ElMessage.success(
|
||||
`${window.t('device.control.capture.success.message.title')}: ${savePath}`,
|
||||
)
|
||||
}
|
||||
async function multipleInvoke(devices) {
|
||||
loading.value = true
|
||||
|
||||
const closeMessage = ElMessage.loading(
|
||||
window.t('device.control.capture.progress', {
|
||||
deviceName: window.t('common.device'),
|
||||
}),
|
||||
).close
|
||||
|
||||
await allSettledWrapper(devices, (item) => {
|
||||
return singleInvoke(item, { silent: true })
|
||||
})
|
||||
|
||||
closeMessage()
|
||||
|
||||
ElMessage.success(window.t('common.success.batch'))
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
invoke,
|
||||
loading,
|
||||
singleInvoke,
|
||||
multipleInvoke,
|
||||
}
|
||||
}
|
||||
|
||||
export default useScreenshotAction
|
124
src/composables/useShellAction/index.js
Normal file
124
src/composables/useShellAction/index.js
Normal file
@ -0,0 +1,124 @@
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import { allSettledWrapper } from '$/utils/index.js'
|
||||
import { selectAndSendFileToDevice } from '$/utils/device/index.js'
|
||||
import { useTaskStore } from '$/store'
|
||||
|
||||
export function useShellAction() {
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function invoke(...args) {
|
||||
const [devices] = args
|
||||
|
||||
if (Array.isArray(devices)) {
|
||||
return multipleInvoke(...args)
|
||||
}
|
||||
|
||||
return singleInvoke(...args)
|
||||
}
|
||||
|
||||
async function singleInvoke(device, { files } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await selectAndSendFileToDevice(device.id, {
|
||||
extensions: ['sh'],
|
||||
selectText: window.t('device.control.shell.select'),
|
||||
loadingText: window.t('device.control.shell.push.loading'),
|
||||
successText: window.t('device.control.shell.push.success'),
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
loading.value = false
|
||||
ElMessage.warning(error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const filePath = files[0]
|
||||
|
||||
const command = `adb -s ${device.id} shell sh ${filePath}`
|
||||
|
||||
taskStore.emit('terminal', { command })
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function multipleInvoke(devices, { files } = {}) {
|
||||
if (!files) {
|
||||
try {
|
||||
files = await window.electron.ipcRenderer.invoke('show-open-dialog', {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: window.t('device.control.shell.select'),
|
||||
extensions: ['sh'],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message) {
|
||||
const message = error.message?.match(/Error: (.*)/)?.[1]
|
||||
ElMessage.warning(message || error.message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const closeLoading = ElMessage.loading(
|
||||
window.t('device.control.shell.push.loading'),
|
||||
).close
|
||||
|
||||
const failFiles = []
|
||||
|
||||
await allSettledWrapper(devices, async (device) => {
|
||||
const successFiles = await selectAndSendFileToDevice(device.id, {
|
||||
files,
|
||||
silent: true,
|
||||
}).catch((e) => {
|
||||
console.warn(e.message)
|
||||
failFiles.push(e.message)
|
||||
})
|
||||
|
||||
const filePath = successFiles?.[0]
|
||||
|
||||
if (filePath) {
|
||||
window.adbkit.deviceShell(device.id, `sh ${filePath}`)
|
||||
}
|
||||
})
|
||||
|
||||
if (failFiles.length) {
|
||||
ElMessageBox.alert(
|
||||
`<div>${failFiles.map(text => `${text}<br/>`).join('')}</div>`,
|
||||
window.t('common.tips'),
|
||||
{
|
||||
type: 'warning',
|
||||
dangerouslyUseHTMLString: true,
|
||||
},
|
||||
)
|
||||
loading.value = false
|
||||
return false
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
|
||||
await ElMessage.success(window.t('device.control.shell.success'))
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
invoke,
|
||||
loading,
|
||||
singleInvoke,
|
||||
multipleInvoke,
|
||||
}
|
||||
}
|
||||
|
||||
export default useShellAction
|
1
src/dicts/index.js
Normal file
1
src/dicts/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './tasks/index'
|
41
src/dicts/tasks/index.js
Normal file
41
src/dicts/tasks/index.js
Normal file
@ -0,0 +1,41 @@
|
||||
export const timerType = [
|
||||
{
|
||||
label: '单次执行',
|
||||
value: 'timeout',
|
||||
},
|
||||
{
|
||||
label: '周期重复',
|
||||
value: 'interval',
|
||||
},
|
||||
]
|
||||
|
||||
export const timeUnit = [
|
||||
{
|
||||
label: '月',
|
||||
value: 'month',
|
||||
},
|
||||
{
|
||||
label: '周',
|
||||
value: 'week',
|
||||
},
|
||||
{
|
||||
label: '天',
|
||||
value: 'day',
|
||||
},
|
||||
{
|
||||
label: '小时',
|
||||
value: 'hour',
|
||||
},
|
||||
{
|
||||
label: '分钟',
|
||||
value: 'minute',
|
||||
},
|
||||
{
|
||||
label: '秒',
|
||||
value: 'second',
|
||||
},
|
||||
{
|
||||
label: '毫秒',
|
||||
value: 'millisecond',
|
||||
},
|
||||
]
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-form v-bind="{ ...$props }">
|
||||
<el-form ref="formRef" v-bind="{ ...$props }">
|
||||
<el-row v-bind="{ ...$attrs, size: $props.size }">
|
||||
<slot />
|
||||
</el-row>
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
<script>
|
||||
import { ElForm } from 'element-plus'
|
||||
import { inheritComponentMethods } from '$/utils/index.js'
|
||||
|
||||
export default {
|
||||
name: 'ElFormRow',
|
||||
@ -15,6 +16,16 @@ export default {
|
||||
props: {
|
||||
...ElForm.props,
|
||||
},
|
||||
methods: {
|
||||
...inheritComponentMethods('formRef', [
|
||||
'validate',
|
||||
'validateField',
|
||||
'resetFields',
|
||||
'scrollToField',
|
||||
'clearValidate',
|
||||
'fields',
|
||||
]),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
9
src/plugins/element-plus/locale.js
Normal file
9
src/plugins/element-plus/locale.js
Normal file
@ -0,0 +1,9 @@
|
||||
import enUs from 'element-plus/es/locale/lang/en'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import zhTw from 'element-plus/es/locale/lang/zh-tw'
|
||||
|
||||
export default {
|
||||
'en-Us': enUs,
|
||||
'zh-CN': zhCn,
|
||||
'zh-TW': zhTw,
|
||||
}
|
@ -2,8 +2,9 @@ import { createPinia } from 'pinia'
|
||||
import { useDeviceStore } from './device/index.js'
|
||||
import { usePreferenceStore } from './preference/index.js'
|
||||
import { useThemeStore } from './theme/index.js'
|
||||
import { useTaskStore } from './task/index.js'
|
||||
|
||||
export { useDeviceStore, usePreferenceStore, useThemeStore }
|
||||
export { useDeviceStore, usePreferenceStore, useThemeStore, useTaskStore }
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
|
109
src/store/task/index.js
Normal file
109
src/store/task/index.js
Normal file
@ -0,0 +1,109 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
import { useEventBus } from '@vueuse/core'
|
||||
|
||||
import { clearTimer, isIPWithPort, replaceIP, setTimer } from '$/utils/index.js'
|
||||
|
||||
dayjs.extend(duration)
|
||||
|
||||
export const useTaskStore = defineStore('app-task', () => {
|
||||
const event = useEventBus('app-task')
|
||||
|
||||
const model = ref([
|
||||
{
|
||||
label: 'device.control.install',
|
||||
value: 'install',
|
||||
},
|
||||
{
|
||||
label: 'device.control.capture',
|
||||
value: 'screenshot',
|
||||
},
|
||||
{
|
||||
label: 'device.control.shell.name',
|
||||
value: 'shell',
|
||||
},
|
||||
])
|
||||
|
||||
const list = ref([])
|
||||
|
||||
function add(form) {
|
||||
const task = {
|
||||
...form,
|
||||
timerId: void 0,
|
||||
id: nanoid(),
|
||||
}
|
||||
|
||||
event.emit(task)
|
||||
|
||||
list.value.push(task)
|
||||
}
|
||||
|
||||
function getTimeout(task) {
|
||||
let value = 0
|
||||
|
||||
const { timerType } = task
|
||||
|
||||
if (timerType === 'timeout') {
|
||||
value = dayjs(task.timeout).diff(dayjs())
|
||||
}
|
||||
else if (timerType === 'interval') {
|
||||
value = dayjs.duration(task.interval, task.intervalType).asMilliseconds()
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function start({ task, handler }) {
|
||||
const { timerType, devices } = task
|
||||
|
||||
const files = task.extra ? task.extra.split(',') : void 0
|
||||
|
||||
const timeout = getTimeout(task)
|
||||
|
||||
task.timerId = setTimer(
|
||||
timerType,
|
||||
() => {
|
||||
handler(devices, { files })
|
||||
|
||||
if (['timeout'].includes(timerType)) {
|
||||
clear(task)
|
||||
}
|
||||
},
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
|
||||
function clear(task) {
|
||||
const { timerType, timerId } = task
|
||||
if (timerId) {
|
||||
clearTimer(timerType, timerId)
|
||||
list.value = list.value.filter(item => item.id !== task.id)
|
||||
}
|
||||
}
|
||||
|
||||
function on(name, callback) {
|
||||
event.on((...args) => {
|
||||
const [{ taskType }] = args
|
||||
|
||||
if (taskType !== name) {
|
||||
return false
|
||||
}
|
||||
|
||||
callback(...args)
|
||||
})
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
function emit(name, args) {
|
||||
event.emit({ taskType: name, ...args })
|
||||
return event
|
||||
}
|
||||
|
||||
return { event, on, emit, list, model, add, start, clear }
|
||||
})
|
@ -1,4 +1,4 @@
|
||||
import { cloneDeep, keyBy } from 'lodash-es'
|
||||
import { camelCase, cloneDeep, keyBy } from 'lodash-es'
|
||||
|
||||
/**
|
||||
* @desc 使用async await 进项进行延时操作
|
||||
@ -89,3 +89,37 @@ export function allSettledWrapper(list = [], iterator) {
|
||||
|
||||
return Promise.allSettled(promises)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 继承组件方法
|
||||
* @param {*} refName ref名称
|
||||
* @param {*} methodNames 需要继承的方法名列表
|
||||
* @returns
|
||||
*/
|
||||
export function inheritComponentMethods(refName, methodNames) {
|
||||
const methods = {}
|
||||
methodNames.forEach((name) => {
|
||||
methods[name] = function (...params) {
|
||||
return this.$refs[refName][name](...params)
|
||||
}
|
||||
})
|
||||
return methods
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用定时器
|
||||
* @param {string} type
|
||||
*/
|
||||
export function setTimer(type, ...args) {
|
||||
const method = camelCase(`set-${type}`)
|
||||
return globalThis[method](...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用清除定时器
|
||||
* @param {string} type
|
||||
*/
|
||||
export function clearTimer(type, ...args) {
|
||||
const method = camelCase(`clear-${type}`)
|
||||
return globalThis[method](...args)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user