perf: ♻️ Refactor configuration related code to improve stability

This commit is contained in:
viarotel 2024-12-08 21:18:26 +08:00
parent cef022f171
commit 37bf4382ff
22 changed files with 200 additions and 119 deletions

View File

@ -61,14 +61,17 @@ let mainWindow
function createWindow() {
const bounds = appStore.get('common.bounds') || {}
const baseWidth = 640
const baseHeight = Number((baseWidth / 1.57).toFixed())
mainWindow = new BrowserWindow({
icon: getLogoPath(),
show: false,
width: 768,
minWidth: 768,
height: 600,
minHeight: 450,
width: baseWidth,
minWidth: baseWidth,
height: baseHeight,
minHeight: baseHeight,
...bounds,
show: false,
icon: getLogoPath(),
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.mjs'),
@ -81,21 +84,22 @@ function createWindow() {
remote.enable(mainWindow.webContents)
remote.initialize()
new Edger(mainWindow);
new Edger(mainWindow)
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.on('resize', () => {
if(mainWindow.isMaximized()) {
return false
}
const bounds = mainWindow.getBounds();
appStore.set('common.bounds', {
...bounds,
isMaximized: false
;['resize', 'move'].forEach((eventName) => {
mainWindow.on(eventName, () => {
if(mainWindow.isMaximized()) {
return false
}
const bounds = mainWindow.getBounds()
appStore.set('common.bounds', {
...bounds
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<el-config-provider :locale :size>
<el-config-provider :locale :size="getSize($grid)">
<Layouts />
</el-config-provider>
</template>
@ -12,7 +12,6 @@ import localeModel from '$/plugins/element-plus/locale.js'
import { usePreferenceStore } from '$/store/preference/index.js'
import { useThemeStore } from '$/store/theme/index.js'
import { useGrid } from 'vue-screen'
import Layouts from './layouts/index.vue'
@ -24,16 +23,6 @@ const locale = computed(() => {
return value
})
const grid = useGrid('tailwind')
const size = computed(() => {
if (['sm', 'md'].includes(grid.breakpoint)) {
return 'small'
}
return 'default'
})
const themeStore = useThemeStore()
const preferenceStore = usePreferenceStore()
@ -42,6 +31,12 @@ preferenceStore.init()
showTips()
function getSize(grid) {
const value = ['sm', 'md'].includes(grid.breakpoint) ? 'small' : 'default'
return value
}
async function showTips() {
const { scrcpyPath } = window.electron?.configs || {}

View File

@ -1,5 +1,5 @@
<template>
<el-form ref="elForm" :model="preferenceData" :label-width="grid['2xl'] ? '240px' : '160px'" class="">
<el-form ref="elForm" :model="preferenceData" :label-width="$grid.lg ? '240px' : '140px'" class="">
<el-collapse
v-model="collapseValue"
v-bind="{
@ -58,7 +58,7 @@
>
</el-link>
</el-tooltip>
<div class="truncate max-w-[120px] 2xl:max-w-[200px]" :title="$t(item_1.label)">
<div class="truncate max-w-[100px] lg:max-w-[200px]" :title="$t(item_1.label)">
{{ $t(item_1.label) }}
</div>
</div>
@ -93,8 +93,6 @@ import { omit } from 'lodash-es'
import { inputModel } from './components/index.js'
import { useGrid } from 'vue-screen'
const props = defineProps({
deviceScope: {
type: String,
@ -112,8 +110,6 @@ const props = defineProps({
const locale = computed(() => i18n.global.locale.value)
const grid = useGrid('tailwind')
const preferenceData = defineModel('modelValue', {
type: Object,
default: () => ({}),
@ -176,7 +172,7 @@ defineExpose({
<style scoped lang="postcss">
:deep(.el-collapse-item__header) {
@apply h-13 leading-13;
@apply h-10 leading-10 md:h-12 md:leading-12;
}
:deep(.el-collapse-item__arrow) {

12
src/dicts/device/index.js Normal file
View File

@ -0,0 +1,12 @@
export const deviceStatus = [
{
label: 'device.status.unauthorized',
value: 'unauthorized',
tagType: 'danger',
},
{
label: 'device.status.connected',
value: 'device',
tagType: 'success',
},
]

View File

@ -1,16 +1,18 @@
import * as dicts from '$/dicts/index.js'
export function getDictLabel(dict, value) {
export function getDictLabel(dict, value, options = {}) {
const { labelKey = 'label' } = options
let label = ''
if (typeof dict === 'function') {
label = dict(value)
label = dict(value, options)
}
else if (typeof dict === 'string') {
label = dicts?.[dict]?.find(item => item.value == value)?.label
label = dicts?.[dict]?.find(item => item.value == value)?.[labelKey]
}
else {
label = dict?.find(item => item.value == value)?.label
label = dict?.find(item => item.value == value)?.[labelKey]
}
if (!label) {

View File

@ -1 +1,2 @@
export * from './device/index'
export * from './tasks/index'

View File

@ -62,6 +62,9 @@
"device.remark": "备注",
"device.permission.error": "设备可能未授权成功请重新插拔设备并点击允许USB调试",
"device.terminal.name": "终端调试",
"device.status": "状态",
"device.status.unauthorized": "未授权",
"device.status.connected": "已连接",
"device.task.name": "计划任务",
"device.task.tips": " 注意:请确保你的计算机保持唤醒状态,否则计划任务将无法被正常执行。",

View File

@ -1,19 +1,19 @@
<template>
<div class="h-full">
<div class="h-full flex flex-col items-center justify-center space-y-4 -mt-8">
<div class="h-full flex flex-col items-center justify-center space-y-[4vh] -mt-[4vh]">
<a class="block" :href="escrcpyURL" target="_blank">
<img src="$electron/resources/build/logo.png" class="h-48" alt="" />
<img src="$electron/resources/build/logo.png" class="h-[32vh] max-h-72" alt="" />
</a>
<div class="text-lg lg:text-xl text-center italic text-gray-700 dark:text-white">
<div class="text-lg lg:text-xl xl:text-2xl text-center italic text-gray-700 dark:text-white">
{{ $t("about.description") }}
</div>
<div class="pt-8">
<div class="pt-[4vh]">
<el-button
:loading="loading"
type="primary"
:size="grid.lg ? 'large' : 'default'"
:size="$grid.lg ? 'large' : 'default'"
@click="handleUpdate"
>
{{
@ -23,7 +23,7 @@
}}
</el-button>
<el-button :size="grid.lg ? 'large' : 'default'" class="group" @click="handleSponsor">
<el-button :size="$grid.lg ? 'large' : 'default'" class="group" @click="handleSponsor">
<span class="group-hover:animate-rubber-band text-red-500"></span>
<span class="pl-1">{{ $t('about.sponsor.title') }}</span>
</el-button>
@ -49,19 +49,12 @@
<script>
import { version } from '/package.json'
import SponsorDialog from './components/SponsorDialog/index.vue'
import { useGrid } from 'vue-screen'
export default {
name: 'About',
components: {
SponsorDialog,
},
setup() {
const grid = useGrid('tailwind')
return {
grid,
}
},
data() {
return {
loading: false,

View File

@ -0,0 +1,32 @@
<template>
<el-popover
ref="popoverRef"
placement="right"
:width="300"
trigger="hover"
popper-class="!p-0 !overflow-hidden !rounded-xl"
>
<template #reference>
<el-link type="primary" :underline="false" icon="InfoFilled" class="mr-1"></el-link>
</template>
<el-descriptions class="!w-full" border>
<el-descriptions-item :label="$t('device.id')">
{{ device.id }}
</el-descriptions-item>
</el-descriptions>
</el-popover>
</template>
<script setup>
const props = defineProps({
device: {
type: Object,
default: () => ({}),
},
})
</script>
<style>
</style>

View File

@ -4,8 +4,8 @@
<el-autocomplete
v-if="!showAutocomplete"
ref="elAutocompleteRef"
v-model="formData.host"
placeholder="192.168.0.1"
v-model="fullHost"
placeholder="192.168.0.1:5555"
clearable
:fetch-suggestions="fetchSuggestions"
class="!w-full"
@ -42,20 +42,6 @@
</el-autocomplete>
</div>
<div class="text-gray-500 text-sm flex-none">
:
</div>
<el-input
v-model.number="formData.port"
type="number"
placeholder="5555"
:min="0"
clearable
class="!w-32 flex-none"
>
</el-input>
<el-button-group>
<el-button
type="primary"
@ -114,6 +100,22 @@ export default {
showAutocomplete: false,
}
},
computed: {
fullHost: {
get() {
if (!this.formData.host) {
return ''
}
return [this.formData.host, this.formData.port].join(':')
},
set(value) {
const [host, port] = value.split(':')
this.formData.host = host
this.formData.port = port
},
},
},
async created() {
const autoConnect = this.$store.preference.data.autoConnect
if (autoConnect) {

View File

@ -24,14 +24,6 @@
<el-table-column type="selection"></el-table-column>
<el-table-column
prop="id"
:label="$t('device.id')"
sortable
show-overflow-tooltip
align="left"
min-width="150"
/>
<el-table-column
:label="$t('device.name')"
sortable
@ -41,17 +33,11 @@
>
<template #default="{ row }">
<div class="flex items-center">
<el-tooltip
v-if="row.$unauthorized"
:content="$t('device.permission.error')"
placement="top-start"
>
<el-icon class="mr-1 text-red-600 text-lg">
<WarningFilled />
</el-icon>
</el-tooltip>
<DevicePopover :device="row" />
{{ row.$name }}
<span class="">
{{ row.$name }}
</span>
<div class="ml-2">
<Remark :device="row" class="" />
@ -63,6 +49,33 @@
</div>
</template>
</el-table-column>
<el-table-column
v-slot="{ row }"
:label="$t('device.status')"
prop="status"
align="left"
sortable
show-overflow-tooltip
width="150"
:filters="statusFilters"
:filter-method="filterMethod"
>
<el-tag :type="getDictLabel('deviceStatus', row.status, { labelKey: 'tagType' })">
<div class="flex items-center">
<el-tooltip
v-if="['unauthorized'].includes(row.status)"
:content="$t('device.permission.error')"
placement="top"
>
<el-link type="danger" :underline="false" icon="WarningFilled" class="mr-1 flex-none"></el-link>
</el-tooltip>
<span class="flex-none">{{ $t(getDictLabel('deviceStatus', row.status)) || '-' }}</span>
</div>
</el-tag>
</el-table-column>
<el-table-column
v-slot="{ row, $index }"
:label="$t('device.control.name')"
@ -86,7 +99,7 @@
</template>
<template #default="{ row }">
<ControlBar :device="row" class="-my-[8px]" />
<ControlBar :device="row" class="-my-[4px] lg:-my-[8px]" />
</template>
</el-table-column>
</el-table>
@ -124,6 +137,12 @@ import WirelessAction from './components/WirelessAction/index.vue'
import WirelessGroup from './components/WirelessGroup/index.vue'
import DevicePopover from './components/DevicePopover/index.vue'
import { getDictLabel } from '$/dicts/helper'
import { deviceStatus } from '$/dicts/index.js'
export default {
name: 'Device',
components: {
@ -134,6 +153,7 @@ export default {
MoreDropdown,
WirelessAction,
BatchActions,
DevicePopover,
},
data() {
return {
@ -147,6 +167,14 @@ export default {
isMultipleRow() {
return this.selectionRows.length > 0
},
statusFilters() {
const value = deviceStatus.map(item => ({
text: window.t(item.label),
value: item.value,
}))
return value
},
},
async created() {
this.getDeviceData()
@ -159,6 +187,11 @@ export default {
this.getDeviceData()
},
methods: {
getDictLabel,
filterMethod(value, row, column) {
const property = column.property
return row[property] === value
},
onSelectionChange(rows) {
this.selectionRows = rows
},
@ -294,5 +327,7 @@ export default {
.el-table .el-table__row .cell {
@apply py-1;
}
--el-empty-image-width: 24vh
}
</style>

View File

@ -172,10 +172,6 @@ export default {
</script>
<style scoped lang="postcss">
:deep(.el-collapse-item__header) {
@apply h-13 leading-13;
}
:deep(.el-collapse-item__arrow) {
@apply w-2em;
}

View File

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

View File

@ -0,0 +1,7 @@
import VueScreen from 'vue-screen'
export default {
install(app) {
app.use(VueScreen, 'tailwind')
},
}

View File

@ -82,6 +82,7 @@ export const useDeviceStore = defineStore({
= res?.map(item => ({
...item,
id: item.id,
status: item.type,
$name: item.model ? item.model.split(':')[1] : '未授权设备',
$unauthorized: item.type === 'unauthorized',
$wifi: isIPWithPort(item.id),

View File

@ -1,12 +1,16 @@
import { cloneDeep, keyBy, mergeWith, uniq } from 'lodash-es'
import { cloneDeep, keyBy, mergeWith, pick, uniq } from 'lodash-es'
import model from '../model/index.js'
const topFields = getTopFields()
const modelMap = getModelMap()
const modelEntries = Object.entries(modelMap)
export function getTopFields(data = model) {
return uniq(Object.values(data).map(item => item.field))
}
const topFields = getTopFields()
export function getModelMap(data = model) {
const value = Object.entries(data).reduce((obj, [parentId, parentItem]) => {
const children
@ -31,13 +35,11 @@ export function getModelMap(data = model) {
}
export function getDefaultData(parentId, iteratee) {
const modelMap = getModelMap()
iteratee = iteratee ?? (value => value)
const value = Object.entries(modelMap).reduce((obj, [key, data]) => {
if (!parentId || data.parentId === parentId) {
obj[key] = iteratee(data.value)
const value = modelEntries.reduce((obj, [key, item]) => {
if (!parentId || item.parentId === parentId) {
obj[key] = iteratee(item.value)
}
return obj
}, {})
@ -50,7 +52,8 @@ export const getStoreData = (scope) => {
topFields.forEach((key) => {
const storeValue = window.appStore.get(key) || {}
if (key === 'scrcpy') {
if (['scrcpy'].includes(key)) {
Object.assign(value, storeValue[scope || 'global'])
return
}
@ -58,12 +61,12 @@ export const getStoreData = (scope) => {
Object.assign(value, storeValue)
})
return value
const includeKeys = Object.keys(modelMap)
return pick(value, includeKeys)
}
export function setStoreData(data, scope) {
const modelMap = getModelMap()
const storeModel = topFields.reduce((obj, key) => {
obj[key] = {}
return obj
@ -114,11 +117,10 @@ export function mergeConfig(object, sources) {
return value
}
export const getOtherFields = (excludeKey = '') => {
const modelMap = getModelMap()
const value = Object.values(modelMap).reduce((arr, item) => {
if (item.parentField !== excludeKey) {
arr.push(item.field)
export function getScrcpyExcludeKeys() {
const value = modelEntries.reduce((arr, [key, item]) => {
if (item.customized || ['common'].includes(item.parentId)) {
arr.push(key)
}
return arr
}, [])

View File

@ -5,7 +5,7 @@ import { defineStore } from 'pinia'
import {
getDefaultData,
getOtherFields,
getScrcpyExcludeKeys,
getStoreData,
getTopFields,
mergeConfig,
@ -40,15 +40,8 @@ export const usePreferenceStore = defineStore({
model: cloneDeep(model),
data: { ...getDefaultData() },
deviceScope,
excludeKeys: [
'--display-overlay',
'--camera',
'--video-code',
'--audio-code',
'--keyboard-inject',
'--audio-record-format',
...getOtherFields('scrcpy'),
],
scrcpyExcludeKeys: getScrcpyExcludeKeys(),
recordKeys,
cameraKeys,
otgKeys,
@ -146,7 +139,7 @@ export const usePreferenceStore = defineStore({
const params = Object.entries(data).reduce((obj, [key, value]) => {
const shouldExclude
= (!value && typeof value !== 'number')
|| this.excludeKeys.includes(key)
|| this.scrcpyExcludeKeys.includes(key)
|| (!isRecord && this.recordKeys.includes(key))
|| (!isCamera && this.cameraKeys.includes(key))
|| (!isOtg && this.otgKeys.includes(key))

View File

@ -34,6 +34,7 @@ export default {
audioCode: {
label: 'preferences.audio.audio-code.name',
field: '--audio-code',
customized: true,
type: 'AudioCodecSelect',
value: undefined,
placeholder: 'preferences.audio.audio-code.placeholder',

View File

@ -14,6 +14,7 @@ export default {
overlayDisplay: {
label: 'preferences.device.display-overlay.name',
field: '--display-overlay',
customized: true,
type: 'Input',
value: undefined,
placeholder: 'preferences.device.display-overlay.placeholder',

View File

@ -80,6 +80,7 @@ export default {
keyboardInject: {
label: 'preferences.input.keyboard.inject.name',
field: '--keyboard-inject',
customized: true,
type: 'KeyboardInjectSelect',
value: void 0,
placeholder: 'preferences.input.keyboard.inject.placeholder',

View File

@ -22,6 +22,7 @@ export default {
audioRecordFormat: {
label: 'preferences.record.format.audio.name',
field: '--audio-record-format',
customized: true,
type: 'Select',
value: void 0,
placeholder: 'preferences.record.format.audio.placeholder',

View File

@ -54,6 +54,7 @@ export default {
videoCode: {
label: 'preferences.video.video-code.name',
field: '--video-code',
customized: true,
type: 'VideoCodecSelect',
value: undefined,
placeholder: 'preferences.video.video-code.placeholder',