feat: 🚀 新增支持导入及导出配置

This commit is contained in:
viarotel 2023-10-18 19:23:00 +08:00
parent 7a59329ebb
commit 326a133460
21 changed files with 589 additions and 235 deletions

View File

@ -58,9 +58,15 @@
- 截取屏幕
- 安装应用
## 高级配
## 偏好设
> 持续完善中 目前支持 Scrcpy 中以下常用配置
> 持续完善中 目前支持以下常用配置
### 自定义
- Adb 路径
- Scrcpy 路径
- 文件存储路径(音视频及截图保存在这里)
### 视频控制

View File

@ -1,21 +1,33 @@
import fs from 'fs-extra'
import { dialog, ipcMain, shell } from 'electron'
export default () => {
ipcMain.handle('show-open-dialog', async (event, params) => {
// console.log('params', params)
try {
const res = await dialog.showOpenDialog(params)
// console.log('showOpenDialog.res', res)
if (res.canceled) {
ipcMain.handle(
'show-open-dialog',
async (event, { preset = '', ...options } = {}) => {
// console.log('options', options)
try {
const res = await dialog.showOpenDialog(options)
// console.log('showOpenDialog.res', res)
if (res.canceled) {
return false
}
const filePaths = res.filePaths
switch (preset) {
case 'replaceFile':
await fs.copy(filePaths[0], options.filePath, { overwrite: true })
break
}
return filePaths
}
catch (error) {
console.warn(error?.message || error)
return false
}
return res.filePaths
}
catch (error) {
console.warn(error?.message || error)
return false
}
})
},
)
ipcMain.handle('open-path', async (event, pathValue) => {
try {
@ -38,4 +50,28 @@ export default () => {
return false
}
})
ipcMain.handle(
'show-save-dialog',
async (event, { filePath = '', ...options } = {}) => {
try {
const result = await dialog.showSaveDialog({
...options,
})
if (!result || result.canceled || !result.filePath) {
return false
}
const destinationPath = result.filePath
await fs.copy(filePath, destinationPath)
return true
}
catch (error) {
console.error(error?.message || error)
return false
}
},
)
}

View File

@ -1,4 +1,7 @@
import path from 'node:path'
import * as configs from '@electron/configs/index.js'
import store from './store/index.js'
import electron from './electron/index.js'
import adbkit from './adbkit/index.js'
import scrcpy from './scrcpy/index.js'
@ -6,7 +9,14 @@ import scrcpy from './scrcpy/index.js'
export default {
init(expose) {
expose('nodePath', path)
expose('electron', electron())
expose('appStore', store())
expose('electron', {
...electron(),
configs,
})
expose('adbkit', adbkit())
expose('scrcpy', scrcpy())
},

View File

@ -0,0 +1,27 @@
import Store from 'electron-store'
import { createProxy } from '@electron/helpers/index'
export default () => {
const appStore = new Store()
appStore.onDidAnyChange(() => {
console.log('reload.appStore.onDidAnyChange', appStore.store)
})
return {
...createProxy(appStore, [
'set',
'get',
'delete',
'clear',
'reset',
'has',
'onDidChange',
'onDidAnyChange',
'openInEditor',
]),
...appStore,
getAll: () => appStore.store,
setAll: value => (appStore.store = value),
}
}

View File

@ -25,3 +25,18 @@ export function exposeContext(key, value) {
window[key] = value
}
}
/**
* 创建一个代理对象将目标对象的指定方法转发并执行
*
* @param {object} targetObject - 目标对象包含要代理的方法
* @param {string[]} methodNames - 要代理的方法名称数组
* @returns {object} - 代理对象包含转发的方法
*/
export function createProxy(targetObject, methodNames) {
return methodNames.reduce((proxyObj, methodName) => {
proxyObj[methodName] = (...args) => targetObject[methodName](...args)
return proxyObj
}, {})
}

View File

@ -1,5 +1,6 @@
import path from 'node:path'
import { BrowserWindow, app, shell } from 'electron'
import Store from 'electron-store'
import { electronApp, optimizer } from '@electron-toolkit/utils'
// packaged.js 必须位于非依赖项的顶部
@ -9,6 +10,8 @@ import { icnsLogoPath, icoLogoPath, logoPath } from './configs/index.js'
import events from './events/index.js'
const appStore = new Store()
// The built directory structure
//
// ├─┬─┬ dist

View File

@ -32,8 +32,10 @@
"dayjs": "^1.11.10",
"electron": "^26.1.0",
"electron-builder": "^24.6.4",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.4",
"element-plus": "^2.4.0",
"fs-extra": "^11.1.1",
"lodash-es": "^4.17.21",
"pinia": "^2.1.7",
"typescript": "^5.2.2",

View File

@ -43,12 +43,18 @@ devDependencies:
electron-builder:
specifier: ^24.6.4
version: 24.6.4
electron-store:
specifier: ^8.1.0
version: 8.1.0
electron-updater:
specifier: ^6.1.4
version: 6.1.4
element-plus:
specifier: ^2.4.0
version: 2.4.0(vue@3.3.4)
fs-extra:
specifier: ^11.1.1
version: 11.1.1
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@ -1566,6 +1572,17 @@ packages:
- supports-color
dev: true
/ajv-formats@2.1.1(ajv@8.12.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
dependencies:
ajv: 8.12.0
dev: true
/ajv-keywords@3.5.2(ajv@6.12.6):
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
peerDependencies:
@ -1583,6 +1600,15 @@ packages:
uri-js: 4.4.1
dev: true
/ajv@8.12.0:
resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
dev: true
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@ -1703,6 +1729,11 @@ packages:
engines: {node: '>= 4.0.0'}
dev: true
/atomically@1.7.0:
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
engines: {node: '>=10.12.0'}
dev: true
/autoprefixer@10.4.16(postcss@8.4.31):
resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==}
engines: {node: ^10 || ^12 || >=14}
@ -2062,6 +2093,22 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/conf@10.2.0:
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
engines: {node: '>=12'}
dependencies:
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
atomically: 1.7.0
debounce-fn: 4.0.0
dot-prop: 6.0.1
env-paths: 2.2.1
json-schema-typed: 7.0.3
onetime: 5.1.2
pkg-up: 3.1.0
semver: 7.5.4
dev: true
/config-file-ts@0.2.4:
resolution: {integrity: sha512-cKSW0BfrSaAUnxpgvpXPLaaW/umg4bqg4k3GO1JqlRfpx+d5W0GDXznCMkWotJQek5Mmz1MJVChQnz3IVaeMZQ==}
dependencies:
@ -2172,6 +2219,13 @@ packages:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
dev: true
/debounce-fn@4.0.0:
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
engines: {node: '>=10'}
dependencies:
mimic-fn: 3.1.0
dev: true
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@ -2348,6 +2402,13 @@ packages:
domhandler: 5.0.3
dev: true
/dot-prop@6.0.1:
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
engines: {node: '>=10'}
dependencies:
is-obj: 2.0.0
dev: true
/dotenv-expand@5.1.0:
resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==}
dev: true
@ -2403,6 +2464,13 @@ packages:
- supports-color
dev: true
/electron-store@8.1.0:
resolution: {integrity: sha512-2clHg/juMjOH0GT9cQ6qtmIvK183B39ZXR0bUoPwKwYHJsEF3quqyDzMFUAu+0OP8ijmN2CbPRAelhNbWUbzwA==}
dependencies:
conf: 10.2.0
type-fest: 2.19.0
dev: true
/electron-to-chromium@1.4.554:
resolution: {integrity: sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==}
dev: true
@ -3014,6 +3082,13 @@ packages:
to-regex-range: 5.0.1
dev: true
/find-up@3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
dependencies:
locate-path: 3.0.0
dev: true
/find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@ -3065,6 +3140,15 @@ packages:
universalify: 2.0.0
dev: true
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
/fs-extra@8.1.0:
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
engines: {node: '>=6 <7 || >=8'}
@ -3495,6 +3579,11 @@ packages:
engines: {node: '>=0.12.0'}
dev: true
/is-obj@2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
dev: true
/is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
@ -3570,6 +3659,14 @@ packages:
requiresBuild: true
dev: true
/json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
dev: true
/json-schema-typed@7.0.3:
resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
dev: true
/json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true
@ -3650,6 +3747,14 @@ packages:
engines: {node: '>=14'}
dev: true
/locate-path@3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
dependencies:
p-locate: 3.0.0
path-exists: 3.0.0
dev: true
/locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@ -3800,6 +3905,11 @@ packages:
engines: {node: '>=6'}
dev: true
/mimic-fn@3.1.0:
resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
engines: {node: '>=8'}
dev: true
/mimic-response@1.0.1:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'}
@ -4037,6 +4147,13 @@ packages:
yocto-queue: 0.1.0
dev: true
/p-locate@3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
dependencies:
p-limit: 2.3.0
dev: true
/p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
@ -4088,6 +4205,11 @@ packages:
resolution: {integrity: sha512-D66DG2nKx4Yoq66TMEyCUHlR2STGqO7vsBrX7tgyS9cfQyO6XD5JyzOiflwmWN6a4wbUAqpmHqmrxlTQVGZcbA==}
dev: true
/path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
dev: true
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@ -4168,6 +4290,13 @@ packages:
pathe: 1.1.1
dev: true
/pkg-up@3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
dependencies:
find-up: 3.0.0
dev: true
/plist@3.1.0:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'}
@ -4372,6 +4501,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
dev: true
/resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
dev: true
@ -4870,6 +5004,11 @@ packages:
engines: {node: '>=8'}
dev: true
/type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
dev: true
/typescript@4.9.5:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'}

View File

@ -15,33 +15,33 @@
</template>
<script>
import Devices from './components/Devices/index.vue'
import Advanced from './components/Advanced/index.vue'
import AboutUs from './components/AboutUs/index.vue'
import Device from './components/Device/index.vue'
import Preference from './components/Preference/index.vue'
import About from './components/About/index.vue'
export default {
components: {
Devices,
Advanced,
AboutUs,
Device,
Preference,
About,
},
data() {
return {
tabsModel: [
{
label: '设备列表',
prop: 'Devices',
prop: 'Device',
},
{
label: '高级配置',
prop: 'Advanced',
label: '偏好设置',
prop: 'Preference',
},
{
label: '关于',
prop: 'AboutUs',
prop: 'About',
},
],
activeTab: 'Devices',
activeTab: 'Device',
}
},
created() {

View File

@ -1,189 +0,0 @@
<template>
<div class="grid gap-6 pr-2">
<el-card v-for="(item, index) of scrcpyModel" :key="index" shadow="hover" class="">
<template #header>
<div class="flex items-center">
<div class="flex-1 w-0 truncate pl-2 text-base">
{{ item.label }}
</div>
<div class="flex-none pl-4">
<el-button type="primary" text @click="handleReset(item.type)">
恢复默认值
</el-button>
</div>
</div>
</template>
<div class="">
<el-form ref="elForm" :model="scrcpyForm" label-width="135px" class="pr-8 pt-4">
<el-row :gutter="20">
<el-col
v-for="(item_1, index_1) of getSubModel(item.type)"
:key="index_1"
:span="12"
:offset="0"
>
<el-form-item :label="item_1.label" :prop="item_1.field">
<template #label>
<div class="flex items-center">
<el-tooltip
v-if="item_1.tips"
class=""
effect="dark"
:content="item_1.tips"
placement="bottom"
>
<el-link
class="mr-1 !text-base"
icon="InfoFilled"
type="warning"
:underline="false"
>
</el-link>
</el-tooltip>
<span class="" :title="item_1.placeholder">{{ item_1.label }}</span>
</div>
</template>
<el-input
v-if="item_1.type === 'input'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
class="!w-full"
:title="item_1.placeholder"
:placeholder="item_1.placeholder"
clearable
></el-input>
<el-input
v-if="item_1.type === 'input.number'"
v-bind="item_1.props || {}"
v-model.number="scrcpyForm[item_1.field]"
class="!w-full"
:title="item_1.placeholder"
:placeholder="item_1.placeholder"
clearable
></el-input>
<el-input
v-if="item_1.type === 'input.directory'"
v-bind="item_1.props || {}"
:value="scrcpyForm[item_1.field]"
readonly
class="!w-full"
clearable
:placeholder="item_1.placeholder"
:title="item_1.placeholder"
@click="handleDirectory(item_1)"
></el-input>
<el-switch
v-if="item_1.type === 'switch'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
class="!w-full"
clearable
:title="item_1.placeholder"
></el-switch>
<el-select
v-if="item_1.type === 'select'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
:placeholder="item_1.placeholder"
class="!w-full"
clearable
:title="item_1.placeholder"
>
<el-option
v-for="(item_2, index_2) in item_1.options"
:key="index_2"
:label="item_2.label"
:value="item_2.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</div>
</template>
<script>
import { debounce } from 'lodash-es'
import { useScrcpyStore } from '@/store/index.js'
export default {
data() {
const scrcpyStore = useScrcpyStore()
return {
scrcpyModel: [
{
label: '视频控制',
type: 'video',
},
{
label: '设备控制',
type: 'device',
},
{
label: '窗口控制',
type: 'window',
},
{
label: '音视频录制',
type: 'record',
},
{
label: '音频控制',
type: 'audio',
},
],
scrcpyForm: { ...scrcpyStore.config },
}
},
watch: {
scrcpyForm: {
handler() {
this.handleSave()
},
deep: true,
},
},
created() {
this.handleSave = debounce(this.handleSave, 1000, { leading: false, trailing: true })
},
methods: {
async handleDirectory({ field }) {
const res = await this.$electron.ipcRenderer.invoke('show-open-dialog', {
properties: ['openDirectory'],
defaultPath: this.scrcpyForm[field],
})
if (!res) {
return false
}
const value = this.$path.normalize(res[0])
this.scrcpyForm[field] = value
},
handleSave() {
this.$store.scrcpy.updateConfig(this.scrcpyForm)
this.$message.success('保存配置成功,将在下一次控制设备时生效')
},
getSubModel(type) {
const value = this.$store.scrcpy.getModel(type)
return value
},
handleReset(type) {
this.scrcpyForm = { ...this.scrcpyForm, ...this.$store.scrcpy.getDefaultConfig(type) }
this.$store.scrcpy.updateConfig(this.scrcpyForm)
},
},
}
</script>
<style scoped lang="postcss">
:deep(.el-card) {
--el-card-padding: 8px;
}
</style>

View File

@ -152,10 +152,7 @@ export default {
const fileName = `${device.name}-screencap-${dayjs().format(
'YYYY-MM-DD-HH-mm-ss',
)}.png`
const savePath = this.$path.resolve(
this.scrcpyConfig['--record'],
fileName,
)
const savePath = this.$path.resolve(this.scrcpyConfig.savePath, fileName)
try {
await this.$adb.screencap(device.id, { savePath })

View File

@ -213,7 +213,7 @@ export default {
this.$refs.elTable.toggleRowExpansion(...params)
},
getRecordPath(row) {
const basePath = this.scrcpyConfig['--record']
const basePath = this.scrcpyConfig.savePath
const recordFormat = this.scrcpyConfig['--record-format']
const fileName = `${row.name || row.id}-recording-${dayjs().format(
'YYYY-MM-DD-HH-mm-ss',

View File

@ -0,0 +1,278 @@
<template>
<div class="">
<div class="pb-4 pr-2 flex items-center">
<div class="">
<el-button type="" plain @click="handleImport">
导入配置
</el-button>
<el-button type="" plain @click="handleExport">
导出配置
</el-button>
<el-button type="" plain @click="handleEdit">
编辑配置
</el-button>
</div>
</div>
<div class="grid gap-6 pr-2">
<el-card
v-for="(item, index) of scrcpyModel"
:key="index"
shadow="hover"
class=""
>
<template #header>
<div class="flex items-center">
<div class="flex-1 w-0 truncate pl-2 text-base">
{{ item.label }}
</div>
<div class="flex-none pl-4">
<el-button type="primary" text @click="handleReset(item.type)">
恢复默认值
</el-button>
</div>
</div>
</template>
<div class="">
<el-form
ref="elForm"
:model="scrcpyForm"
label-width="135px"
class="pr-8 pt-4"
>
<el-row :gutter="20">
<el-col
v-for="(item_1, index_1) of getSubModel(item.type)"
:key="index_1"
:span="12"
:offset="0"
>
<el-form-item :label="item_1.label" :prop="item_1.field">
<template #label>
<div class="flex items-center">
<el-tooltip
v-if="item_1.tips"
class=""
effect="dark"
:content="item_1.tips"
placement="bottom"
>
<el-link
class="mr-1 !text-base"
icon="InfoFilled"
type="warning"
:underline="false"
>
</el-link>
</el-tooltip>
<span class="" :title="item_1.placeholder">{{
item_1.label
}}</span>
</div>
</template>
<el-input
v-if="item_1.type === 'input'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
class="!w-full"
:title="item_1.placeholder"
:placeholder="item_1.placeholder"
clearable
></el-input>
<el-input
v-if="item_1.type === 'input.number'"
v-bind="item_1.props || {}"
v-model.number="scrcpyForm[item_1.field]"
class="!w-full"
:title="item_1.placeholder"
:placeholder="item_1.placeholder"
clearable
></el-input>
<el-input
v-if="item_1.type === 'input.path'"
v-bind="item_1.props || {}"
:value="scrcpyForm[item_1.field]"
readonly
class="!w-full"
clearable
:placeholder="item_1.placeholder"
:title="item_1.placeholder"
@click="
handleSelect(item_1, {
properties: item_1.properties,
filters: item_1.filters,
})
"
></el-input>
<el-switch
v-if="item_1.type === 'switch'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
class="!w-full"
clearable
:title="item_1.placeholder"
></el-switch>
<el-select
v-if="item_1.type === 'select'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
:placeholder="item_1.placeholder"
class="!w-full"
clearable
:title="item_1.placeholder"
>
<el-option
v-for="(item_2, index_2) in item_1.options"
:key="index_2"
:label="item_2.label"
:value="item_2.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</div>
</div>
</template>
<script>
import { debounce } from 'lodash-es'
import { useScrcpyStore } from '@/store/index.js'
import LoadingIcon from '@/components/Device/ControlBar/LoadingIcon/index.vue'
export default {
data() {
const scrcpyStore = useScrcpyStore()
return {
scrcpyModel: [
{
label: '自定义',
type: 'custom',
},
{
label: '视频控制',
type: 'video',
},
{
label: '设备控制',
type: 'device',
},
{
label: '窗口控制',
type: 'window',
},
{
label: '音视频录制',
type: 'record',
},
{
label: '音频控制',
type: 'audio',
},
],
scrcpyForm: { ...scrcpyStore.config },
}
},
watch: {
scrcpyForm: {
handler() {
this.handleSave()
},
deep: true,
},
},
created() {
this.handleSave = debounce(this.handleSave, 1000, {
leading: false,
trailing: true,
})
},
methods: {
async handleImport() {
const result = await this.$electron.ipcRenderer.invoke(
'show-open-dialog',
{
preset: 'replaceFile',
filePath: this.$appStore.path,
filters: [{ name: '请选择要导入的配置文件', extensions: ['json'] }],
},
)
if (!result) {
this.$message.warning('导入偏好配置失败')
return
}
this.$message.success('导入偏好配置成功')
this.scrcpyForm = this.$store.scrcpy.init()
},
handleEdit() {
this.$appStore.openInEditor()
},
async handleExport() {
const messageEl = this.$message({
message: ' 正在导出偏好配置中...',
icon: LoadingIcon,
duration: 0,
})
const result = await this.$electron.ipcRenderer.invoke(
'show-save-dialog',
{
defaultPath: 'escrcpy-configs.json',
filePath: this.$appStore.path,
filters: [
{ name: '请选择配置文件要保存的位置', extensions: ['json'] },
],
},
)
messageEl.close()
if (result) {
this.$message.success('导出偏好配置成功')
}
},
async handleSelect({ field }, { properties, filters } = {}) {
const res = await this.$electron.ipcRenderer.invoke('show-open-dialog', {
properties,
filters,
defaultPath: this.scrcpyForm[field],
})
if (!res) {
return false
}
const value = res[0]
this.scrcpyForm[field] = value
},
handleSave() {
this.$store.scrcpy.updateConfig(this.scrcpyForm)
this.$message.success('保存配置成功,将在下一次控制设备时生效')
},
getSubModel(type) {
const value = this.$store.scrcpy.getModel(type)
return value
},
handleReset(type) {
this.scrcpyForm = {
...this.scrcpyForm,
...this.$store.scrcpy.getDefaultConfig(type),
}
this.$store.scrcpy.updateConfig(this.scrcpyForm)
},
},
}
</script>
<style scoped lang="postcss">
:deep(.el-card) {
--el-card-padding: 8px;
}
</style>

View File

@ -20,6 +20,7 @@ app.config.globalProperties.$electron = window.electron
app.config.globalProperties.$adb = window.adbkit
app.config.globalProperties.$scrcpy = window.scrcpy
app.config.globalProperties.$path = window.nodePath
app.config.globalProperties.$appStore = window.appStore
app.mount('#app').$nextTick(() => {
// Remove Preload scripts loading

View File

@ -1,7 +1,8 @@
import { defineStore } from 'pinia'
import { pickBy } from 'lodash-es'
import * as scrcpyModel from './model/index.js'
import storage from '@/utils/storages/index.js'
const $appStore = window.appStore
/**
* 获取 Scrcpy 默认配置
@ -34,7 +35,7 @@ export const useScrcpyStore = defineStore({
model: scrcpyModel,
defaultConfig: getDefaultConfig(),
config: {},
excludeKeys: ['--record', '--record-format'],
excludeKeys: ['--record-format', 'savePath', 'adbPath', 'scrcpyPath'],
}
},
getters: {
@ -73,7 +74,7 @@ export const useScrcpyStore = defineStore({
init() {
this.config = {
...this.defaultConfig,
...(storage.get('scrcpyConfig') || {}),
...($appStore.get('scrcpy') || {}),
}
return this.config
@ -83,7 +84,7 @@ export const useScrcpyStore = defineStore({
console.log('pickConfig', pickConfig)
storage.set('scrcpyConfig', pickConfig)
$appStore.set('scrcpy', pickConfig)
this.init()
},

View File

@ -0,0 +1,37 @@
export default () => {
const $path = window.nodePath
const { adbPath, scrcpyPath } = window?.electron?.configs || {}
return [
{
label: '文件存储路径',
type: 'input.path',
field: 'savePath',
value: $path.resolve('../'),
placeholder: '默认值为执行应用的同级目录',
tips: '截图和录制的音视频都存在这里',
properties: ['openDirectory'],
},
{
label: 'Adb 路径',
field: 'adbPath',
type: 'input.path',
value: adbPath,
tips: '用于连接设备的 adb.exe 的地址,注意:改变此选项时需要重启服务',
placeholder: '请选择 Adb.exe',
properties: ['openFile'],
filters: [{ name: '请选择 Adb.exe', extensions: ['exe'] }],
},
{
label: 'Scrcpy 路径',
field: 'scrcpyPath',
type: 'input.path',
value: scrcpyPath,
tips: '用于控制设备的 Scrcpy.exe 的地址',
placeholder: '请选择 Scrcpy.exe',
properties: ['openFile'],
filters: [{ name: '请选择 Scrcpy.exe', extensions: ['exe'] }],
},
]
}

View File

@ -1,3 +1,4 @@
export { default as custom } from './custom/index.js'
export { default as video } from './video/index.js'
export { default as device } from './device/index.js'
export { default as window } from './window/index.js'

View File

@ -1,15 +1,5 @@
export default () => {
const $path = window.nodePath
return [
{
label: '文件保存路径',
type: 'input.directory',
field: '--record',
value: $path.resolve('../'),
placeholder: '默认值为执行应用的同级目录',
tips: '截图和录制的音视频都存在这里',
},
{
label: '录制视频格式',
type: 'select',