fix: 🐛 修复安装路径包含空格会导致无法启动服务的问题

This commit is contained in:
viarotel 2023-10-25 18:17:19 +08:00
parent 217d82d03e
commit 29ae786768
27 changed files with 788 additions and 310 deletions

15
.vscode/settings.json vendored
View File

@ -30,9 +30,16 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"i18n-ally.localesPaths": [
"src/locales",
"dist-release/win-arm64-unpacked/locales",
"dist-release/win-unpacked/locales"
"src/locales/index.js",
"src/locales/languages"
],
"i18n-ally.sourceLanguage": "zh"
"i18n-ally.sourceLanguage": "zh",
"i18n-ally.keystyle": "nested",
"i18n-ally.extract.ignored": [
"${item.id}${item.$name}${\r\n item.$remark ? `${item.$remark}` : ''\r\n }",
"${item.$remark}",
"${row.$remark ? `${row.$remark}-` : ''}${\r\n row.$name\r\n }-${this.$replaceIP(row.id)}-recording-${dayjs().format(\r\n 'YYYY-MM-DD-HH-mm-ss',\r\n )}.${recordFormat}",
"--serial=${row.id} --window-title=${\r\n row.$remark ? `${row.$remark}-` : ''\r\n }${row.$name}-${\r\n row.id\r\n }-🎥录制中... --record=${savePath} ${this.scrcpyArgs(row.id)}",
"--serial=${row.id} --window-title=${\r\n row.$remark ? `${row.$remark}-` : ''\r\n }${row.$name}-${row.id} ${this.scrcpyArgs(row.id)}"
]
}

View File

@ -26,5 +26,7 @@ export const scrcpyPath
? extraResolve('core/scrcpy.exe')
: which.sync('scrcpy', { nothrow: true })
export const logPath = process.env.LOG_PATH
// console.log('adbPath', adbPath)
// console.log('scrcpyPath', scrcpyPath)

View File

@ -6,6 +6,7 @@ import dayjs from 'dayjs'
import { Adb } from '@devicefarmer/adbkit'
import appStore from '@electron/helpers/store.js'
import { adbPath } from '@electron/configs/index.js'
import { uniq } from 'lodash-es'
const exec = util.promisify(child_process.exec)
@ -38,7 +39,10 @@ appStore.onDidChange('scrcpy.global.adbPath', async (value, oldValue) => {
const shell = async command => exec(`${adbPath} ${command}`)
const getDevices = async () => client.listDevicesWithPaths()
const deviceShell = async (id, command) => client.getDevice(id).shell(command)
const deviceShell = async (id, command) => {
const res = await client.getDevice(id).shell(command).then(Adb.util.readAll)
return res.toString()
}
const kill = async (...params) => client.kill(...params)
const connect = async (...params) => client.connect(...params)
const disconnect = async (...params) => client.disconnect(...params)
@ -95,6 +99,28 @@ const install = async (id, path) => client.getDevice(id).install(path)
const version = async () => client.version()
const display = async (deviceId) => {
let value = []
try {
const res = await deviceShell(deviceId, 'dumpsys display')
const regex = /Display Id=(\d+)/g
const match = res.match(regex) || []
const mapValue = match.map(item => item.split('=')[1])
value = uniq(mapValue)
}
catch (error) {
console.error(error?.message || error)
}
console.log('display.deviceId.value', value)
return value
}
const watch = async (callback) => {
const tracker = await client.trackDevices()
tracker.on('add', async (ret) => {
@ -140,6 +166,7 @@ export default () => {
screencap,
install,
version,
display,
watch,
}
}

View File

@ -1,7 +1,11 @@
import path from 'node:path'
import log from '@electron/helpers/log.js'
import '@electron/helpers/console.js'
import store from '@electron/helpers/store.js'
import * as configs from '@electron/configs/index.js'
import electron from './electron/index.js'
import adbkit from './adbkit/index.js'
import scrcpy from './scrcpy/index.js'
@ -12,12 +16,14 @@ export default {
expose('appStore', store)
expose('appLog', log)
expose('electron', {
...electron(),
configs,
})
expose('adbkit', adbkit())
expose('scrcpy', scrcpy())
expose('adbkit', adbkit({ log }))
expose('scrcpy', scrcpy({ log }))
},
}

View File

@ -0,0 +1,12 @@
import log from 'electron-log/main'
import { createProxy } from '@electron/helpers/index'
const levels = Object.keys(log.functions)
const functions = () => ({ ...createProxy(log, levels) })
export default {
levels,
functions: functions(),
...functions(),
}

View File

@ -7,9 +7,10 @@ const shell = async (command, { stdout, stderr } = {}) => {
const ADB = appStore.get('scrcpy.global.adbPath') || adbPath
const args = command.split(' ')
const scrcpyProcess = spawn(spawnPath, args, {
env: { ...process.env, ADB },
const scrcpyProcess = spawn(`"${spawnPath}"`, args, {
env: { ...process.env, ADB: `"${ADB}"` },
shell: true,
encoding: 'utf8',
})
scrcpyProcess.stdout.on('data', (data) => {

View File

@ -0,0 +1,8 @@
import log from '@electron/helpers/log.js'
import { createProxy } from './index.js'
Object.assign(console, {
...createProxy(log.functions, log.levels),
raw: console.log,
})

View File

@ -1,5 +1,6 @@
import { resolve } from 'node:path'
import { contextBridge } from 'electron'
import { cloneDeep } from 'lodash-es'
export const isPackaged = process.env.IS_PACKAGED === 'true'
@ -35,7 +36,8 @@ export function exposeContext(key, value) {
*/
export function createProxy(targetObject, methodNames) {
return methodNames.reduce((proxyObj, methodName) => {
proxyObj[methodName] = (...args) => targetObject[methodName](...args)
proxyObj[methodName] = (...args) =>
targetObject[methodName](...cloneDeep(args))
return proxyObj
}, {})

19
electron/helpers/log.js Normal file
View File

@ -0,0 +1,19 @@
import { shell } from 'electron'
import log from 'electron-log/main'
import { createProxy } from '@electron/helpers/index'
log.transports.console.level = false
const levels = Object.keys(log.functions)
const getFilePath = () => log.transports.file.getFile()?.path
console.log('logPath', getFilePath())
export default {
...createProxy(log, ['initialize', ...levels]),
levels,
functions: createProxy(log, levels),
getFilePath,
openInEditor: () => shell.openPath(getFilePath()),
}

View File

@ -1,11 +1,17 @@
import Store from 'electron-store'
import { isEqual } from 'lodash-es'
import { createProxy } from './index.js'
const appStore = new Store()
appStore.onDidAnyChange((value) => {
console.log('appStore.onDidAnyChange.value', value)
})
// appStore.onDidAnyChange((value) => {
// console.log('appStore.onDidAnyChange.value', value)
// })
// 如果没有数据则手动设置值,以保证配置文件生成成功
if (isEqual(appStore.store, {})) {
appStore.store = {}
}
export default {
...createProxy(appStore, [

View File

@ -6,10 +6,17 @@ import { electronApp, optimizer } from '@electron-toolkit/utils'
import './helpers/process.js'
import './helpers/store.js'
import log from './helpers/log.js'
import './helpers/console.js'
import { icnsLogoPath, icoLogoPath, logoPath } from './configs/index.js'
import events from './events/index.js'
log.initialize({ preload: true })
console.log('Successfully initialized the Escrcpy logging system.')
// The built directory structure
//
// ├─┬─┬ dist

View File

@ -33,6 +33,7 @@
"dayjs": "^1.11.10",
"electron": "^27.0.2",
"electron-builder": "^24.6.4",
"electron-log": "^5.0.0",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.4",
"element-plus": "^2.4.0",

View File

@ -30,7 +30,7 @@
:loading="connectLoading"
@click="handleConnect"
>
{{ $t("devices.wireless.connect") }}
{{ $t("devices.wireless.connect.name") }}
</el-button>
<el-button
type="primary"
@ -38,10 +38,13 @@
:loading="loading"
@click="handleRefresh"
>
{{ $t("devices.refresh") }}
{{ $t("devices.refresh.name") }}
</el-button>
<el-button type="warning" icon="RefreshRight" @click="handleRestart">
{{ $t("devices.restart") }}
{{ $t("devices.restart.name") }}
</el-button>
<el-button icon="View" @click="handleLog">
{{ $t("devices.log.name") }}
</el-button>
</div>
<div class="pt-4 flex-1 h-0 overflow-hidden">
@ -74,7 +77,7 @@
<div class="flex items-center">
<el-tooltip
v-if="row.$unauthorized"
content="设备可能未授权成功请重新插拔设备并点击允许USB调试"
:content="$t('devices.device.permission.error')"
placement="top-start"
>
<el-icon class="mr-1 text-red-600 text-lg">
@ -110,8 +113,8 @@
>
{{
row.$loading
? $t("devices.operates.mirroring")
: $t("devices.operates.mirror")
? $t("devices.mirror.progress")
: $t("devices.mirror.start")
}}
</el-button>
@ -125,8 +128,8 @@
>
{{
row.$recordLoading
? $t("devices.operates.recording")
: $t("devices.operates.record")
? $t("devices.record.progress")
: $t("devices.record.start")
}}
</el-button>
@ -142,7 +145,7 @@
<template #icon>
<svg-icon name="wifi"></svg-icon>
</template>
{{ $t("devices.operates.wireless") }}
{{ $t("devices.wireless.mode") }}
</el-button>
<el-button
@ -156,8 +159,8 @@
>
{{
row.$stopLoading
? $t("devices.operates.disconnecting")
: $t("devices.operates.disconnect")
? $t("devices.wireless.disconnect.progress")
: $t("devices.wireless.disconnect.start")
}}
</el-button>
</template>
@ -243,21 +246,24 @@ export default {
try {
await this.$confirm(
`
<div>通常情况下这可能是因为更新 Escrcpy 缓存的依赖配置不兼容所导致的是否重置依赖配置</div>
<div class="text-red-500">注意重置后之前保存的依赖配置将会被清除因此建议在执行重置操作之前备份您的配置</div>
<div>${this.$t('devices.reset.reasons[0]')}</div>
<div class="text-red-500">${this.$t('devices.reset.reasons[1]')}</div>
`,
'操作失败',
this.$t('devices.reset.title'),
{
dangerouslyUseHTMLString: true,
confirmButtonText: '重置依赖配置',
cancelButtonText: '取消',
confirmButtonText: this.$t('devices.reset.confirm'),
cancelButtonText: this.$t('devices.reset.cancel'),
closeOnClickModal: false,
type: 'warning',
},
)
this.$store.scrcpy.resetDeps(depType)
this.$root.reRender('Preference')
this.$message.success('操作成功,请重新尝试。')
this.$message.success(this.$t('devices.reset.success'))
}
catch (error) {
if (error.message) {
@ -294,20 +300,24 @@ export default {
try {
const command = `--serial=${row.id} --window-title=${
row.$remark ? `${row.$remark}-` : ''
}${row.$name}-${
row.id
}-🎥录制中... --record=${savePath} ${this.scrcpyArgs(row.id)}`
}${row.$name}-${row.id}-🎥${this.$t(
'devices.record.progress',
)}... --record=${savePath} ${this.scrcpyArgs(row.id)}`
console.log('handleRecord.command', command)
await this.$scrcpy.shell(command, { stdout: this.onStdout })
await this.$confirm('是否前往录制位置进行查看?', '录制成功', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
type: 'success',
})
await this.$confirm(
this.$t('devices.record.success.message'),
this.$t('devices.record.success.title'),
{
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
closeOnClickModal: false,
type: 'success',
},
)
await this.$electron.ipcRenderer.invoke(
'show-item-in-folder',
@ -365,16 +375,21 @@ export default {
handleRestart() {
this.$electron.ipcRenderer.send('restart-app')
},
handleLog() {
this.$appLog.openInEditor()
},
async handleConnect() {
if (!this.formData.host) {
this.$message.warning('无线调试地址不能为空')
this.$message.warning(
this.$t('devices.wireless.connect.error.no-address'),
)
return false
}
this.connectLoading = true
try {
await this.$adb.connect(this.formData.host, this.formData.port || 5555)
this.$message.success('连接设备成功')
this.$message.success(this.$t('devices.wireless.connect.success'))
storage.set('adbCache', this.formData)
}
catch (error) {
@ -387,21 +402,23 @@ export default {
await this.$confirm(
`
<div class="pb-4 text-sm text-red-500">${this.$t(
'devices.wireless.error.detail',
'devices.wireless.connect.error.detail',
)}${message}</div>
<div>${this.$t('devices.wireless.error.reasons[0]')}</div>
<div>1. ${this.$t('devices.wireless.error.reasons[1]')} </div>
<div>2. ${this.$t('devices.wireless.error.reasons[2]')} </div>
<div>3. ${this.$t('devices.wireless.error.reasons[3]')} </div>
<div>4. ${this.$t('devices.wireless.error.reasons[4]')} </div>
<div>5. ${this.$t('devices.wireless.error.reasons[5]')} </div>
<div>${this.$t('devices.wireless.connect.error.reasons[0]')}</div>
<div>1. ${this.$t('devices.wireless.connect.error.reasons[1]')} </div>
<div>2. ${this.$t('devices.wireless.connect.error.reasons[2]')} </div>
<div>3. ${this.$t('devices.wireless.connect.error.reasons[3]')} </div>
<div>4. ${this.$t('devices.wireless.connect.error.reasons[4]')} </div>
<div>5. ${this.$t('devices.wireless.connect.error.reasons[5]')} </div>
`,
this.$t('devices.wireless.error.title'),
this.$t('devices.wireless.connect.error.title'),
{
dangerouslyUseHTMLString: true,
closeOnClickModal: false,
confirmButtonText: this.$t('devices.wireless.error.confirm'),
cancelButtonText: this.$t('devices.wireless.error.cancel'),
confirmButtonText: this.$t(
'devices.wireless.connect.error.confirm',
),
cancelButtonText: this.$t('devices.wireless.connect.error.cancel'),
type: 'warning',
},
)
@ -417,7 +434,7 @@ export default {
try {
await this.$adb.disconnect(host, port)
await sleep()
this.$message.success('断开连接成功')
this.$message.success(this.$t('devices.wireless.disconnect.success'))
}
catch (error) {
if (error.message)

View File

@ -1,13 +1,13 @@
<template>
<div class="">
<div class="pb-4 pr-2 flex items-center justify-between">
<div label="作用域范围">
<div class="">
<el-select
v-model="scopeValue"
value-key=""
placeholder="偏好设置的作用域范围"
:placeholder="$t('preferences.scope.placeholder')"
filterable
no-data-text="暂无数据"
:no-data-text="$t('preferences.scope.no-data')"
class="!w-90"
@change="onScopeChange"
>
@ -19,13 +19,13 @@
<template #content>
<div class="space-y-1">
<div class="pb-1">
对全局或者单个设备设置不同的偏好配置
{{ $t("preferences.scope.details[0]") }}
</div>
<div class="">
全局将对所有设备生效
{{ $t("preferences.scope.details[1]") }}
</div>
<div class="">
单个设备继承于全局配置并对单个设备进行独立设置仅对此设备生效
{{ $t("preferences.scope.details[2]") }}
</div>
</div>
</template>
@ -42,16 +42,16 @@
</div>
<div class="">
<el-button type="" plain @click="handleImport">
{{ $t("preferences.config.import") }}
{{ $t("preferences.config.import.name") }}
</el-button>
<el-button type="" plain @click="handleExport">
{{ $t("preferences.config.export") }}
{{ $t("preferences.config.export.name") }}
</el-button>
<el-button type="" plain @click="handleEdit">
{{ $t("preferences.config.edit") }}
{{ $t("preferences.config.edit.name") }}
</el-button>
<el-button type="" plain @click="handleResetAll">
{{ $t("preferences.config.reset") }}
{{ $t("preferences.config.reset.name") }}
</el-button>
</div>
</div>
@ -78,7 +78,7 @@
<el-form
ref="elForm"
:model="scrcpyForm"
label-width="135px"
label-width="170px"
class="pr-8 pt-4"
>
<el-row :gutter="20">
@ -158,6 +158,7 @@
clearable
:title="item_1.placeholder"
></el-switch>
<el-select
v-if="item_1.type === 'select'"
v-bind="item_1.props || {}"
@ -238,7 +239,7 @@ export default {
}))
value.unshift({
label: `Global${this.$t('preferences.global')}`,
label: `Global${this.$t('preferences.scope.global')}`,
value: 'global',
})
@ -252,6 +253,15 @@ export default {
},
deep: true,
},
scopeValue: {
handler(value) {
if (value === 'global') {
return
}
this.getDisplay(value)
},
immediate: true,
},
},
created() {
this.handleSave = debounce(this.handleSave, 1000, {
@ -269,15 +279,27 @@ export default {
this.$store.scrcpy.setScope(replaceIPValue)
this.scrcpyForm = this.$store.scrcpy.config
},
async getDisplay(value) {
const display = await this.$adb.display(value)
console.log('display', display)
this.$store.scrcpy.setModel('video', { display })
},
async handleImport() {
try {
await this.$electron.ipcRenderer.invoke('show-open-dialog', {
preset: 'replaceFile',
filePath: this.$appStore.path,
filters: [{ name: '请选择要导入的配置文件', extensions: ['json'] }],
filters: [
{
name: this.$t('preferences.config.import.placeholder'),
extensions: ['json'],
},
],
})
this.$message.success('导入偏好配置成功')
this.$message.success(this.$t('preferences.config.import.success'))
this.scrcpyForm = this.$store.scrcpy.init()
}
@ -293,7 +315,7 @@ export default {
},
async handleExport() {
const messageEl = this.$message({
message: ' 正在导出偏好配置中...',
message: this.$t('preferences.config.export.message'),
icon: LoadingIcon,
duration: 0,
})
@ -303,10 +325,13 @@ export default {
defaultPath: 'escrcpy-configs.json',
filePath: this.$appStore.path,
filters: [
{ name: '请选择配置文件要保存的位置', extensions: ['json'] },
{
name: this.$t('preferences.config.export.placeholder'),
extensions: ['json'],
},
],
})
this.$message.success('导出偏好配置成功')
this.$message.success(this.$t('preferences.config.export.success'))
}
catch (error) {
if (error.message) {
@ -346,9 +371,10 @@ export default {
},
handleSave() {
this.$store.scrcpy.setConfig(this.scrcpyForm)
this.$message.success('保存配置成功,将在下一次控制设备时生效')
this.$message.success(this.$t('preferences.config.save.placeholder'))
},
getSubModel(type) {
console.log('getSubModel')
const value = this.$store.scrcpy.getModel(type)
return value
},

View File

@ -1,8 +1,8 @@
import { createI18n } from 'vue-i18n'
import messages from '@intlify/unplugin-vue-i18n/messages'
const locale = window.electron?.process?.env?.LOCALE
// const locale = 'en_US'
// const locale = window.electron?.process?.env?.LOCALE
const locale = 'en_US'
// console.log('locale', locale)

View File

@ -5,50 +5,77 @@
},
"devices": {
"name": "Devices",
"loading": "In the loading device...",
"loading": "Devices loading...",
"empty": "No device detected",
"wireless": {
"name": "Wireless",
"connect": "Connect",
"error": {
"title": "Failed to Connect Device",
"detail": "Error Details",
"reasons": [
"Possible reasons:",
"Incorrect IP address or port number",
"Device pairing unsuccessful",
"Computer network and provided device network IP are not in the same LAN",
"Incorrect adb dependency path",
"Other unknown errors"
],
"confirm": "Wireless Pairing",
"cancel": "@:common.cancel"
"mode": "Wireless mode",
"connect": {
"name": "Connect",
"error": {
"title": "连接设备失败",
"detail": "错误详情",
"reasons": [
"可能有以下原因",
"IP地址或端口号错误",
"设备未与当前电脑配对成功",
"电脑网络和提供的设备网络IP不在同一个局域网中",
"adb 依赖路径错误",
"其他未知错误"
],
"confirm": "无线配对",
"cancel": "@:common.cancel",
"no-address": "无线调试地址不能为空"
},
"success": "连接设备成功"
},
"success": "Device Connected Successfully"
"disconnect": {
"start": "断开连接",
"progress": "正在断开",
"success": "断开连接成功"
}
},
"reset": {
"title": "操作失败",
"reasons": [
"通常情况下,这可能是因为更新 Escrcpy 后,缓存的依赖配置不兼容所导致的,是否重置依赖配置?",
"注意:重置后,之前保存的依赖配置将会被清除,因此建议在执行重置操作之前备份您的配置。"
],
"confirm": "重置依赖配置",
"cancel": "@:common.cancel",
"success": "操作成功,请重新尝试。"
},
"refresh": {
"name": "Refresh"
},
"restart": {
"name": "Restart"
},
"log": {
"name": "Logs"
},
"refresh": "Refresh",
"restart": "Restart",
"device": {
"id": "ID",
"name": "Name",
"remark": "Remark"
"remark": "Remark",
"permission": {
"error": "设备可能未授权成功请重新插拔设备并点击允许USB调试"
}
},
"mirror": {
"start": "Mirror",
"progress": "Mirroring"
},
"record": {
"start": "Record",
"progress": "Recording",
"success": {
"title": "录制成功",
"message": "是否前往录制位置进行查看?"
}
},
"operates": {
"name": "Operations",
"more": "More",
"mirror": "Mirror",
"mirroring": "Mirroring",
"record": "Record",
"recording": "Recording",
"wireless": "Wireless Mode",
"disconnect": "Disconnect",
"disconnecting": "Disconnecting",
"install": "Install APP",
"capture": "Capture",
"reboot": "Reboot",
@ -61,97 +88,207 @@
},
"preferences": {
"name": "Preferences",
"global": "Global",
"config": {
"import": "Import",
"export": "Export",
"edit": "Edit",
"reset": "Reset"
"reset": "Resets",
"scope": {
"global": "Global",
"placeholder": "偏好设置的作用域范围",
"no-data": "暂无数据",
"details": [
"对全局或者单个设备设置不同的偏好配置",
"全局:将对所有设备生效。",
"单个设备:继承于全局配置,并对单个设备进行独立设置,仅对此设备生效。"
]
},
"config": {
"import": {
"name": "导入配置",
"placeholder": "请选择要导入的配置文件",
"success": "导入成功"
},
"export": {
"name": "导出配置",
"message": "导出配置",
"placeholder": "请选择要导出的位置",
"success": "导出成功"
},
"edit": {
"name": "编辑配置"
},
"reset": {
"name": "重置配置"
},
"save": {
"name": "保存配置",
"placeholder": "保存配置成功,将在下一次控制设备时生效"
}
},
"custom": {
"name": "Custom",
"file-path": {
"file": {
"name": "File Path",
"placeholder": "Put on the user desktop by default",
"tips": "Screenshots and recorded audio and video exist here"
},
"adb-path": {
"adb": {
"name": "ADB Path",
"placeholder": "Please set the ADB path",
"tips": "The address of the ADB used to connect the device. Note that this option is not affected by the configuration of a single device"
},
"scrcpy-path": {
"scrcpy": {
"name": "Scrcpy Path",
"placeholder": "Please set the SCRCPY path",
"tips": "The address of the SCRCPY used to connect the device. Note that this option is not affected by the configuration of a single device"
}
},
"video": {
"name": "Video",
"resolution": "Resolution",
"bit": "Bitrate",
"refresh-rate": "Refresh Rate",
"decoder": "Video Decoder",
"encoder": "Video Encoder",
"screen-rotation": "Screen Rotation",
"screen-cropping": "Screen Cropping",
"multi-display": "Multiple Displays",
"video-buffering": "Video Buffering",
"audio-buffering": "Audio Buffering",
"receiver-buffering": "Receiver Buffering (v412)",
"disable": "Disable Video"
"resolution": {
"name": "Resolution",
"placeholder": "Default device resolution",
"tips": ""
},
"bit": {
"name": "Bitrate",
"placeholder": "Default 4M",
"tips": ""
},
"refresh-rate": {
"name": "Refresh Rate",
"placeholder": "Default 60",
"tips": ""
},
"decoder": {
"name": "Decoder",
"placeholder": "Default h264",
"tips": ""
},
"encoder": {
"name": "Encoder",
"placeholder": "Default device encoder",
"tips": ""
},
"screen-rotation": {
"name": "Rotation",
"placeholder": "Default device rotation",
"tips": ""
},
"screen-cropping": {
"name": "Cropping",
"placeholder": "Default no cropping",
"tips": ""
},
"multi-display": {
"name": "Multi-Display",
"placeholder": "Default 0 (main)",
"tips": ""
},
"video-buffering": {
"name": "Video Buffering",
"placeholder": "Default 0ms",
"tips": ""
},
"audio-buffering": {
"name": "Audio Buffering",
"placeholder": "Default 0ms",
"tips": ""
},
"receiver-buffering": {
"name": "Receiver Buffering",
"placeholder": "Default 0ms",
"tips": ""
},
"disable": {
"name": "Disable Video",
"placeholder": "Disable video if enabled",
"tips": ""
}
},
"device": {
"name": "Device",
"show-touch": "Show Touches",
"stay-awake": "Stay Awake",
"control-in-close-screen": "Turn Off Screen During Control",
"control-end-video": "Turn Off Screen After Control",
"control-in-stop-charging": "Stop Charging During Control"
"show-touch": {
"name": "Show Touches",
"placeholder": "Enable to show tap feedback in developer options",
"tips": "Only on physical devices"
},
"stay-awake": {
"name": "Stay Awake",
"placeholder": "Enable to prevent sleep",
"tips": "Wired connection only"
},
"control-in-close-screen": {
"name": "Turn Off Screen",
"placeholder": "Automatically turn off screen when controlling device",
"tips": ""
},
"control-end-video": {
"name": "Turn Off at End",
"placeholder": "Automatically turn off screen when control stops",
"tips": ""
},
"control-in-stop-charging": {
"name": "Stop Charging",
"placeholder": "Stop charging when controlling device",
"tips": "May not work on some models"
}
},
"window": {
"name": "Window",
"borderless": "Borderless Mode",
"full-screen": "Full Screen Mode",
"always-top": "Always on Top",
"disable-screen-saver": "Disable Screensaver"
"borderless": {
"name": "Borderless",
"placeholder": "Removes window border",
"tips": ""
},
"full-screen": {
"name": "Fullscreen",
"placeholder": "Fullscreen mode",
"tips": ""
},
"always-top": {
"name": "Always on Top",
"placeholder": "Window always on top",
"tips": ""
},
"disable-screen-saver": {
"name": "Disable Screen Saver",
"placeholder": "Disables screen saver",
"tips": ""
}
},
"record": {
"name": "Recording",
"format": "Video Recording Format"
"format": {
"name": "Format",
"placeholder": "Default .mp4 format",
"tips": ""
}
},
"audio": {
"name": "Audio",
"disable": "Disable Audio"
},
"reset": "Restore Default Values"
"disable": {
"name": "Disable Audio",
"placeholder": "Disables audio",
"tips": ""
}
}
},
"about": {
"name": "About",
"description": "📱 Use the graphical Scrcpy powered by Electron to display and control your Android device.",
"update": "Check for Updates",
"updating": "Updating",
"update-not-available": "Already the latest version",
"description": "📱 Display and control your Android device with graphical Scrcpy, powered by Electron",
"update": "Check for updates",
"update-not-available": "Already up to date",
"update-error": {
"title": "Update Check Failed",
"message": "You may need to use a VPN. Do you want to visit the release page and manually download the update?"
"title": "Update check failed",
"message": "You may need a VPN. Go to releases page to download manually?"
},
"update-downloaded": {
"title": "New Version Downloaded",
"message": "Do you want to restart and apply the update now?",
"title": "New version downloaded",
"message": "Restart now to update?",
"confirm": "Update"
},
"update-available": {
"title": "New Version Available",
"title": "New version available",
"confirm": "Update"
}
},
"updating": "Updating..."
}
}

View File

@ -5,50 +5,79 @@
},
"devices": {
"name": "设备列表",
"loading": "努力加载中...",
"empty": "没有检测到设备",
"wireless": {
"name": "无线连接",
"connect": "连接设备",
"error": {
"title": "连接设备失败",
"detail": "错误详情",
"reasons": [
"可能有以下原因",
"IP地址或端口号错误",
"设备未与当前电脑配对成功",
"电脑网络和提供的设备网络IP不在同一个局域网中",
"adb 依赖路径错误",
"其他未知错误"
],
"confirm": "无线配对",
"cancel": "@:common.cancel"
"mode": "无线模式",
"connect": {
"name": "连接设备",
"error": {
"title": "连接设备失败",
"detail": "错误详情",
"reasons": [
"可能有以下原因",
"IP地址或端口号错误",
"设备未与当前电脑配对成功",
"电脑网络和提供的设备网络IP不在同一个局域网中",
"adb 依赖路径错误",
"其他未知错误"
],
"confirm": "无线配对",
"cancel": "@:common.cancel",
"no-address": "无线调试地址不能为空"
},
"success": "连接设备成功"
},
"success": "连接设备成功"
"disconnect": {
"start": "断开连接",
"progress": "正在断开",
"success": "断开连接成功"
}
},
"reset": {
"title": "操作失败",
"reasons": [
"通常情况下,这可能是因为更新 Escrcpy 后,缓存的依赖配置不兼容所导致的,是否重置依赖配置?",
"注意:重置后,之前保存的依赖配置将会被清除,因此建议在执行重置操作之前备份您的配置。"
],
"confirm": "重置依赖配置",
"cancel": "@:common.cancel",
"success": "操作成功,请重新尝试。"
},
"refresh": {
"name": "刷新设备"
},
"restart": {
"name": "重启服务"
},
"log": {
"name": "运行日志"
},
"refresh": "刷新设备",
"restart": "重启服务",
"device": {
"id": "设备 ID",
"name": "设备名称",
"remark": "备注"
"remark": "备注",
"permission": {
"error": "设备可能未授权成功请重新插拔设备并点击允许USB调试"
}
},
"mirror": {
"start": "开始镜像",
"progress": "正在镜像"
},
"record": {
"start": "开始录制",
"progress": "正在录制",
"success": {
"title": "录制成功",
"message": "是否前往录制位置进行查看?"
}
},
"operates": {
"name": "操作",
"more": "设备交互",
"mirror": "开始镜像",
"mirroring": "正在镜像",
"record": "开始录制",
"recording": "正在录制",
"wireless": "无线模式",
"disconnect": "断开连接",
"disconnecting": "正在断开",
"install": "安装应用",
"capture": "截取屏幕",
"reboot": "重启设备",
@ -59,87 +88,195 @@
"switch": "切换键"
}
},
"preferences": {
"name": "偏好设置",
"global": "全局",
"config": {
"import": "导入配置",
"export": "导出配置",
"edit": "编辑配置",
"reset": "重置配置"
"reset": "恢复默认值",
"scope": {
"global": "全局",
"placeholder": "偏好设置的作用域范围",
"no-data": "暂无数据",
"details": [
"对全局或者单个设备设置不同的偏好配置",
"全局:将对所有设备生效。",
"单个设备:继承于全局配置,并对单个设备进行独立设置,仅对此设备生效。"
]
},
"config": {
"import": {
"name": "导入配置",
"placeholder": "请选择要导入的配置文件",
"success": "导入成功"
},
"export": {
"name": "导出配置",
"message": "导出配置",
"placeholder": "请选择要导出的位置",
"success": "导出成功"
},
"edit": {
"name": "编辑配置"
},
"reset": {
"name": "重置配置"
},
"save": {
"name": "保存配置",
"placeholder": "保存配置成功,将在下一次控制设备时生效"
}
},
"custom": {
"name": "自定义",
"file-path": {
"file": {
"name": "文件存储路径",
"placeholder": "默认情况下放在用户桌面上",
"tips": "截图和录制的音视频存放在这里"
},
"adb-path": {
"adb": {
"name": "adb 路径",
"placeholder": "请设置 adb 路径",
"tips": "用于连接设备的 adb 地址。注意:此选项不受单个设备配置的影响"
},
"scrcpy-path": {
"scrcpy": {
"name": "scrcpy 路径",
"placeholder": "请设置 scrcpy 路径",
"tips": "用于连接设备的 scrcpy 地址。注意:此选项不受单个设备配置的影响"
}
},
"video": {
"name": "视频控制",
"resolution": "分辨率",
"bit": "比特率",
"refresh-rate": "刷新率",
"decoder": "视频解码器",
"encoder": "视频编码器",
"screen-rotation": "屏幕旋转",
"screen-cropping": "屏幕裁剪",
"multi-display": "多显示器",
"video-buffering": "视频缓冲",
"audio-buffering": "音频缓冲",
"receiver-buffering": "接收器缓冲(v412)",
"disable": "禁用视频"
"resolution": {
"name": "分辨率",
"placeholder": "默认值为设备分辨率,如 1920",
"tips": ""
},
"bit": {
"name": "比特率",
"placeholder": "默认值为 4M等同于 4000000",
"tips": ""
},
"refresh-rate": {
"name": "刷新率",
"placeholder": "默认值为 60",
"tips": ""
},
"decoder": {
"name": "视频解码器",
"placeholder": "默认值为 h264",
"tips": ""
},
"encoder": {
"name": "视频编码器",
"placeholder": "默认值为设备默认编码器",
"tips": ""
},
"screen-rotation": {
"name": "屏幕旋转",
"placeholder": "默认值为设备屏幕旋转角度",
"tips": ""
},
"screen-cropping": {
"name": "屏幕裁剪",
"placeholder": "默认不裁剪,格式为 1224:1440:0:0",
"tips": ""
},
"multi-display": {
"name": "多显示器",
"placeholder": "默认值为 0主屏幕",
"tips": ""
},
"video-buffering": {
"name": "视频缓冲",
"placeholder": "默认值为 0ms",
"tips": ""
},
"audio-buffering": {
"name": "音频缓冲",
"placeholder": "默认值为 0ms",
"tips": ""
},
"receiver-buffering": {
"name": "接收器缓冲(v412)",
"placeholder": "默认值为 0ms",
"tips": ""
},
"disable": {
"name": "禁用视频",
"placeholder": "开启后将禁用视频",
"tips": ""
}
},
"device": {
"name": "设备控制",
"show-touch": "展示触摸点",
"stay-awake": "保持清醒",
"control-in-close-screen": "控制时关闭屏幕",
"control-end-video": "控制结束关闭屏幕",
"control-in-stop-charging": "控制时停止充电"
"show-touch": {
"name": "展示触摸点",
"placeholder": "开启后将打开开发者选项中的显示点按触摸反馈",
"tips": "仅在物理设备上展示"
},
"stay-awake": {
"name": "保持清醒",
"placeholder": "开启以防止设备进入睡眠状态",
"tips": "仅有线方式连接时有效"
},
"control-in-close-screen": {
"name": "控制时关闭屏幕",
"placeholder": "开启后控制设备时将自动关闭设备屏幕",
"tips": ""
},
"control-end-video": {
"name": "控制结束关闭屏幕",
"placeholder": "开启后停止控制设备将自动关闭设备屏幕",
"tips": ""
},
"control-in-stop-charging": {
"name": "控制时停止充电",
"placeholder": "开启后控制设备时将停止充电",
"tips": "某些机型上似乎不起作用"
}
},
"window": {
"name": "窗口控制",
"borderless": "无边框模式",
"full-screen": "全屏模式",
"always-top": "始终位于顶部",
"disable-screen-saver": "禁用屏幕保护程序"
"borderless": {
"name": "无边框模式",
"placeholder": "开启后控制窗口将变为无边框模式",
"tips": ""
},
"full-screen": {
"name": "全屏模式",
"placeholder": "开启后控制窗口将全屏显示模式",
"tips": ""
},
"always-top": {
"name": "始终位于顶部",
"placeholder": "开启后控制窗口将始终位于顶部",
"tips": ""
},
"disable-screen-saver": {
"name": "禁用屏幕保护程序",
"placeholder": "开启后将禁用计算机屏幕保护程序",
"tips": ""
}
},
"record": {
"name": "音视频录制",
"format": "录制视频格式"
"format": {
"name": "录制视频格式",
"placeholder": "默认为 *.mp4 格式",
"tips": ""
}
},
"audio": {
"name": "音频控制",
"disable": "禁用音频"
},
"reset": "恢复默认值"
"disable": {
"name": "禁用音频",
"placeholder": "开启后将禁用音频功能",
"tips": ""
}
}
},
"about": {
"name": "关于",
"description": "📱 使用图形化的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动",
"update": "版本检测更新",
"update": "检查并更新",
"update-not-available": "已经是最新版本",
"update-error": {
"title": "检查更新失败",
@ -153,6 +290,7 @@
"update-available": {
"title": "发现新版本",
"confirm": "更新"
}
},
"updating": "正在更新中"
}
}

View File

@ -1,3 +1,5 @@
import '@/utils/console.js'
import { createApp } from 'vue'
import App from './App.vue'
@ -29,9 +31,13 @@ app.config.globalProperties.$scrcpy = window.scrcpy
app.config.globalProperties.$path = window.nodePath
app.config.globalProperties.$appStore = window.appStore
app.config.globalProperties.$appLog = window.appLog
app.config.globalProperties.$replaceIP = replaceIP
app.mount('#app').$nextTick(() => {
// Remove Preload scripts loading
postMessage({ payload: 'removeLoading' }, '*')
})
console.log('electron.configs', window.electron.configs)

View File

@ -36,7 +36,7 @@ function getDefaultConfig(type) {
model.push(...handler())
}
else {
// console.log('scrcpyModel', scrcpyModel)
// console.raw('scrcpyModel', scrcpyModel)
const values = Object.values(scrcpyModel)
model.push(...values.flatMap(handler => handler()))
}
@ -55,7 +55,7 @@ export const useScrcpyStore = defineStore({
state() {
return {
scope: $appStore.get('scrcpy.scope') || 'global',
model: scrcpyModel,
model: { ...scrcpyModel },
defaultConfig: getDefaultConfig(),
config: {},
excludeKeys: ['--record-format', 'savePath', 'adbPath', 'scrcpyPath'],
@ -166,8 +166,20 @@ export const useScrcpyStore = defineStore({
return value
},
getModel(key, params) {
const handler = scrcpyModel[key]
return handler(params)
const handler = this.model[key]
const value = handler(params)
// console.log('setModel.value', value)
return value
},
setModel(key, params) {
const handler = this.model[key]
const value = handler(params)
// console.log('setModel.value', value)
this.model[key] = () => value
return this.model
},
},
})

View File

@ -1,3 +1,5 @@
import { t } from '@/locales/index.js'
export default () => {
// "[server] INFO: List of audio encoders:"
// "--audio-codec=opus --audio-encoder='c2.android.opus.encoder'"
@ -5,11 +7,11 @@ export default () => {
// "--audio-codec=aac --audio-encoder='OMX.google.aac.encoder'"
return [
{
label: '禁用音频',
label: t('preferences.audio.disable.name'),
field: '--no-audio',
type: 'switch',
value: false,
placeholder: '开启后将禁用音频功能',
placeholder: t('preferences.audio.disable.placeholder'),
},
]
}

View File

@ -5,36 +5,34 @@ export default () => {
return [
{
label: t('preferences.custom.file-path.name'),
label: t('preferences.custom.file.name'),
type: 'input.path',
field: 'savePath',
value: desktopPath,
placeholder: t('preferences.custom.file-path.placeholder'),
tips: t('preferences.custom.file-path.tips'),
placeholder: t('preferences.custom.file.placeholder'),
tips: t('preferences.custom.file.tips'),
properties: ['openDirectory'],
},
{
label: t('preferences.custom.adb-path.name'),
label: t('preferences.custom.adb.name'),
field: 'adbPath',
type: 'input.path',
value: adbPath,
placeholder: t('preferences.custom.adb-path.placeholder'),
tips: t('preferences.custom.adb-path.tips'),
placeholder: t('preferences.custom.adb.placeholder'),
tips: t('preferences.custom.adb.tips'),
properties: ['openFile'],
filters: [
{ name: t('preferences.custom.adb-path.name'), extensions: ['*'] },
],
filters: [{ name: t('preferences.custom.adb.name'), extensions: ['*'] }],
},
{
label: t('preferences.custom.scrcpy-path.name'),
label: t('preferences.custom.scrcpy.name'),
field: 'scrcpyPath',
type: 'input.path',
value: scrcpyPath,
placeholder: t('preferences.custom.scrcpy-path.placeholder'),
tips: t('preferences.custom.scrcpy-path.tips'),
placeholder: t('preferences.custom.scrcpy.placeholder'),
tips: t('preferences.custom.scrcpy.tips'),
properties: ['openFile'],
filters: [
{ name: t('preferences.custom.scrcpy-path.name'), extensions: ['*'] },
{ name: t('preferences.custom.scrcpy.name'), extensions: ['*'] },
],
},
]

View File

@ -1,42 +1,44 @@
import { t } from '@/locales/index.js'
export default () => {
return [
{
label: '展示触摸点',
label: t('preferences.device.show-touch.name'),
type: 'switch',
field: '--show-touches',
value: false,
placeholder: '开启后将打开开发者选项中的显示点按触摸反馈',
tips: '仅在物理设备上展示',
placeholder: t('preferences.device.show-touch.placeholder'),
tips: t('preferences.device.show-touch.tips'),
},
{
label: '保持清醒',
label: t('preferences.device.stay-awake.name'),
type: 'switch',
field: '--stay-awake',
value: false,
placeholder: '开启以防止设备进入睡眠状态',
tips: '仅有线方式连接时有效',
placeholder: t('preferences.device.stay-awake.placeholder'),
tips: t('preferences.device.stay-awake.tips'),
},
{
label: '控制时关闭屏幕',
label: t('preferences.device.control-in-close-screen.name'),
type: 'switch',
field: '--turn-screen-off',
value: false,
placeholder: '开启后控制设备时将自动关闭设备屏幕',
placeholder: t('preferences.device.control-in-close-screen.placeholder'),
},
{
label: '控制结束关闭屏幕',
label: t('preferences.device.control-end-video.name'),
type: 'switch',
field: '--power-off-on-close',
value: false,
placeholder: '开启后停止控制设备将自动关闭设备屏幕',
placeholder: t('preferences.device.control-end-video.placeholder'),
},
{
label: '控制时停止充电',
label: t('preferences.device.control-in-stop-charging.name'),
type: 'switch',
field: '--no-power-on',
value: false,
placeholder: '开启后控制设备时将停止充电',
tips: '某些机型上似乎不起作用',
placeholder: t('preferences.device.control-in-stop-charging.placeholder'),
tips: t('preferences.device.control-in-stop-charging.tips'),
},
]
}

View File

@ -1,11 +1,13 @@
import { t } from '@/locales/index.js'
export default () => {
return [
{
label: '录制视频格式',
label: t('preferences.record.format.name'),
type: 'select',
field: '--record-format',
value: 'mp4',
placeholder: '默认值为 mp4',
placeholder: t('preferences.record.format.placeholder'),
options: [
{
label: 'mp4',

View File

@ -1,32 +1,45 @@
export default () => {
import { t } from '@/locales/index.js'
const getDisplayOptions = (display = []) =>
display?.map(value => ({ label: value, value })) || []
export default ({ display } = {}) => {
const displayOptions = display?.length
? getDisplayOptions(display)
: [
{ label: '0', value: '0' },
{ label: '1', value: '1' },
{ label: '2', value: '2' },
]
return [
{
label: '分辨率',
label: t('preferences.video.resolution.name'),
type: 'input.number',
field: '--max-size',
value: '',
placeholder: '默认值为设备分辨率,如 1920',
placeholder: t('preferences.video.resolution.placeholder'),
},
{
label: '比特率',
label: t('preferences.video.bit.name'),
type: 'input',
field: '--video-bit-rate',
value: '',
placeholder: '默认值为 4M等同于 4000000',
placeholder: t('preferences.video.bit.placeholder'),
},
{
label: '刷新率',
label: t('preferences.video.refresh-rate.name'),
type: 'input.number',
field: '--max-fps',
value: '',
placeholder: '默认值为 60',
placeholder: t('preferences.video.refresh-rate.placeholder'),
},
{
label: '视频解码器',
label: t('preferences.video.decoder.name'),
type: 'select',
field: '--video-codec',
value: '',
placeholder: '默认值为 h264',
placeholder: t('preferences.video.decoder.placeholder'),
options: [
{
label: 'h264',
@ -43,11 +56,11 @@ export default () => {
],
},
{
label: '视频编码器',
label: t('preferences.video.encoder.name'),
type: 'select',
field: '--video-encoder',
value: '',
placeholder: '默认值为设备默认编码器',
placeholder: t('preferences.video.encoder.placeholder'),
// "[server] INFO: List of video encoders:"
// "--video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'"
// "--video-codec=h264 --video-encoder='c2.android.avc.encoder'"
@ -56,33 +69,33 @@ export default () => {
// "--video-codec=h265 --video-encoder='c2.android.hevc.encoder'"
options: [
{
label: 'Qualcomm AVC(H.264) 视频编码器',
label: 'Android HEVC(H.265) ',
value: 'OMX.qcom.video.encoder.avc',
},
{
label: 'Android AVC(H.264) 视频编码器',
label: 'Qualcomm HEVC(H.265) ',
value: 'c2.android.avc.encoder',
},
{
label: 'Google H.264(AVC) 视频编码器',
label: 'Google H.264(AVC)',
value: 'OMX.google.h264.encoder',
},
{
label: 'Qualcomm HEVC(H.265) 视频编码器',
label: 'Android AVC(H.264) ',
value: 'OMX.qcom.video.encoder.hevc',
},
{
label: 'Android HEVC(H.265) 视频编码器',
label: 'Qualcomm AVC(H.264)',
value: 'c2.android.hevc.encoder',
},
],
},
{
label: '屏幕旋转',
label: t('preferences.video.screen-rotation.name'),
type: 'select',
field: '--rotation',
value: '',
placeholder: '默认值为设备屏幕旋转角度',
placeholder: t('preferences.video.screen-rotation.placeholder'),
options: [
{ label: '0°', value: '0' },
{ label: '-90°', value: '1' },
@ -91,51 +104,51 @@ export default () => {
],
},
{
label: '屏幕裁剪',
label: t('preferences.video.screen-cropping.name'),
type: 'input',
field: '--crop',
value: '',
placeholder: '默认不裁剪,格式为 1224:1440:0:0',
placeholder: t('preferences.video.screen-cropping.placeholder'),
},
{
label: '多显示器',
label: t('preferences.video.multi-display.name'),
type: 'select',
field: '--display',
value: '',
placeholder: '默认值为 0主屏幕',
options: [
{ label: '0', value: '0' },
{ label: '1', value: '1' },
{ label: '2', value: '2' },
],
placeholder: t('preferences.video.multi-display.placeholder'),
options: displayOptions,
props: {
filterable: true,
allowCreate: true,
},
},
{
label: '视频缓冲',
label: t('preferences.video.video-buffering.name'),
type: 'input.number',
field: '--display-buffer',
value: '',
placeholder: '单位为 ms默认值为 0ms',
placeholder: t('preferences.video.video-buffering.placeholder'),
},
{
label: '音频缓冲',
label: t('preferences.video.audio-buffering.name'),
type: 'input.number',
field: '--audio-buffer',
value: '',
placeholder: '单位为 ms默认值为 0ms',
placeholder: t('preferences.video.video-buffering.placeholder'),
},
{
label: '接收器(v4l2)缓冲',
label: t('preferences.video.receiver-buffering.name'),
type: 'input.number',
field: '--v4l2-buffer',
value: '',
placeholder: '单位为 ms默认值为 0ms',
placeholder: t('preferences.video.video-buffering.placeholder'),
},
{
label: '禁用视频',
label: t('preferences.video.disable.name'),
type: 'switch',
field: '--no-video',
value: false,
placeholder: '开启后将禁用视频',
placeholder: t('preferences.video.disable.placeholder'),
},
]
}

View File

@ -1,32 +1,34 @@
import { t } from '@/locales/index.js'
export default () => {
return [
{
label: '无边框模式',
label: t('preferences.window.borderless.name'),
field: '--window-borderless',
type: 'switch',
value: false,
placeholder: '开启后控制窗口将变为无边框模式',
placeholder: t('preferences.window.borderless.placeholder'),
},
{
label: '全屏模式',
label: t('preferences.window.full-screen.name'),
field: '--fullscreen',
type: 'switch',
value: false,
placeholder: '开启后控制窗口将全屏显示模式',
placeholder: t('preferences.window.full-screen.placeholder'),
},
{
label: '始终位于顶部',
label: t('preferences.window.always-top.name'),
field: '--always-on-top',
type: 'switch',
value: false,
placeholder: '开启后控制窗口将始终位于顶部',
placeholder: t('preferences.window.always-top.placeholder'),
},
{
label: '禁用屏幕保护程序',
label: t('preferences.window.disable-screen-saver.name'),
field: '--disable-screensaver',
type: 'switch',
value: false,
placeholder: '开启后将禁用计算机屏幕保护程序',
placeholder: t('preferences.window.disable-screen-saver.placeholder'),
},
]
}

6
src/utils/console.js Normal file
View File

@ -0,0 +1,6 @@
import { createProxy } from './index.js'
Object.assign(console, {
...createProxy(window.appLog.functions, window.appLog.levels),
raw: console.log,
})

View File

@ -1,3 +1,5 @@
import { cloneDeep } from 'lodash-es'
/**
* @desc 使用async await 进项进行延时操作
* @param {*} time
@ -18,3 +20,20 @@ export function isIPWithPort(ip) {
export function replaceIP(value, to = '_') {
return value.replaceAll('.', to).replaceAll(':', to)
}
/**
* 创建一个代理对象将目标对象的指定方法转发并执行
*
* @param {object} targetObject - 目标对象包含要代理的方法
* @param {string[]} methodNames - 要代理的方法名称数组
* @returns {object} - 代理对象包含转发的方法
*/
export function createProxy(targetObject, methodNames) {
return methodNames.reduce((proxyObj, methodName) => {
proxyObj[methodName] = (...args) => {
return targetObject[methodName](...cloneDeep(args))
}
return proxyObj
}, {})
}