feat: 📝 添加基本的有线连接支持

This commit is contained in:
viarotel 2023-09-15 19:32:13 +08:00
parent a46a5e1154
commit 647a0c5606
41 changed files with 4303 additions and 3073 deletions

View File

@ -6,10 +6,10 @@ module.exports = {
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@electron-toolkit',
'@vue/eslint-config-prettier'
'@vue/eslint-config-prettier',
],
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off'
}
'vue/multi-word-component-names': 'off',
},
}

9
.eslintrc.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
extends: ['@electron-toolkit', '@viarotel-org'],
rules: {
'no-unused-vars': 'off',
'eqeqeq': 'off',
'prefer-promise-reject-errors': 'off',
'antfu/top-level-function': 'off',
},
}

1
.npmrc
View File

@ -1,2 +1,3 @@
registry=https://registry.npmmirror.com/
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
shamefully-hoist=true

4
.yarnrc Normal file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
registry "https://registry.npmmirror.com/"

View File

@ -1,20 +1,26 @@
import { resolve } from 'path'
import { resolve } from 'node:path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import useEslint from 'vite-plugin-eslint'
import useUnoCSS from 'unocss/vite'
import postcssConfig from '@viarotel-org/postcss-config'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
plugins: [externalizeDepsPlugin()],
},
preload: {
plugins: [externalizeDepsPlugin()]
plugins: [externalizeDepsPlugin()],
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src')
}
'@renderer': resolve('src/renderer/src'),
},
},
plugins: [vue()]
}
plugins: [useEslint(), vue(), useUnoCSS()],
css: {
postcss: postcssConfig(),
},
},
})

10
jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"exclude": ["node_modules", "dist", "out"],
"include": ["src/**/*"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@renderer/*": ["src/renderer/src/*"]
}
}
}

View File

@ -7,7 +7,7 @@
"homepage": "https://www.electronjs.org",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"lint": "eslint . --ext .md,.vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .eslintignore --fix",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "electron-vite build",
@ -17,23 +17,31 @@
"build:linux": "npm run build && electron-builder --linux --config"
},
"dependencies": {
"@devicefarmer/adbkit": "^3.2.5",
"@electron-toolkit/preload": "^2.0.0",
"@electron-toolkit/utils": "^2.0.0",
"electron-updater": "^6.1.1"
"@viarotel-org/design": "^0.7.0",
"electron-updater": "^6.1.1",
"element-plus": "^2.3.14"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.1",
"@rushstack/eslint-patch": "^1.3.3",
"@viarotel-org/eslint-config": "^0.7.0",
"@viarotel-org/postcss-config": "^0.7.0",
"@viarotel-org/unocss-config": "^0.7.4",
"@vitejs/plugin-vue": "^4.3.1",
"@vue/eslint-config-prettier": "^8.0.0",
"electron": "^25.6.0",
"electron-builder": "^24.6.3",
"electron-vite": "^1.0.27",
"eslint": "^8.47.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"less": "^4.2.0",
"prettier": "^3.0.2",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-eslint": "^1.8.1",
"vue": "^3.3.4"
}
}

6651
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

BIN
resources/core/SDL2.dll Normal file

Binary file not shown.

BIN
resources/core/adb.exe Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/core/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

View File

@ -0,0 +1 @@
@cmd

View File

@ -0,0 +1,4 @@
@echo off
scrcpy.exe %*
:: if the exit code is >= 1, then pause
if errorlevel 1 pause

View File

@ -0,0 +1,7 @@
strCommand = "cmd /c scrcpy.exe"
For Each Arg In WScript.Arguments
strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """"
Next
CreateObject("Wscript.Shell").Run strCommand, 0, false

Binary file not shown.

BIN
resources/core/scrcpy.exe Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
import { app, shell, BrowserWindow } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { join } from 'node:path'
import { BrowserWindow, app, shell } from 'electron'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow() {
@ -13,8 +13,9 @@ function createWindow() {
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
sandbox: false,
devTools: true,
},
})
mainWindow.on('ready-to-show', () => {
@ -28,9 +29,10 @@ function createWindow() {
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
if (is.dev && process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
}
else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
@ -51,10 +53,11 @@ app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
if (BrowserWindow.getAllWindows().length === 0)
createWindow()
})
})

View File

@ -0,0 +1,15 @@
import { contextBridge } from 'electron'
export function addContext(key, value) {
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld(key, value)
}
catch (error) {
console.error(error)
}
}
else {
window[key] = value
}
}

View File

@ -1,5 +1,11 @@
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import { Adb } from '@devicefarmer/adbkit'
import scrcpyPath from '../../resources/core/scrcpy.exe?asset&asarUnpack'
import { addContext } from './helpers/index.js'
const util = require('node:util')
const exec = util.promisify(require('node:child_process').exec)
// Custom APIs for renderer
const api = {}
@ -7,14 +13,27 @@ const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
addContext('electron', electronAPI)
addContext('api', api)
addContext('adbkit', () => {
const client = Adb.createClient()
console.log('client', client)
const getDevices = async () => await client.listDevicesWithPaths()
const shell = async (id, command) => await client.getDevice(id).shell(command)
const kill = async () => await client.kill()
return {
getDevices,
shell,
kill,
}
} else {
window.electron = electronAPI
window.api = api
}
})
addContext('scrcpy', () => {
const shell = command => exec(`${scrcpyPath} ${command}`)
return {
shell,
}
})

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<title>Escrcpy</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"

View File

@ -1,97 +1,55 @@
<script setup>
import Versions from './components/Versions.vue'
</script>
<template>
<Versions></Versions>
<svg class="hero-logo" viewBox="0 0 900 300">
<use xlink:href="./assets/icons.svg#electron" />
</svg>
<h2 class="hero-text">You've successfully created an Electron project with Vue</h2>
<p class="hero-tagline">Please try pressing <code>F12</code> to open the devTool</p>
<div class="links">
<div class="link-item">
<a target="_blank" href="https://electron-vite.org">Documentation</a>
</div>
<div class="link-item link-dot"></div>
<div class="link-item">
<a target="_blank" href="https://github.com/alex8088/electron-vite">Getting Help</a>
</div>
<div class="link-item link-dot"></div>
<div class="link-item">
<a
target="_blank"
href="https://github.com/alex8088/quick-start/tree/master/packages/create-electron"
<div class="absolute inset-0 px-4 pb-4 h-full overflow-hidden">
<el-tabs v-model="activeTab" class="el-tabs-flex" @tab-click="handleClick">
<el-tab-pane
v-for="(item, index) of tabsModel"
:key="index"
:label="item.label"
:name="item.prop"
>
create-electron
</a>
</div>
</div>
<div class="features">
<div class="feature-item">
<article>
<h2 class="title">Configuring</h2>
<p class="detail">
Config with <span>electron.vite.config.js</span> and refer to the
<a target="_blank" href="https://electron-vite.org/config">config guide</a>.
</p>
</article>
</div>
<div class="feature-item">
<article>
<h2 class="title">HMR</h2>
<p class="detail">
Edit <span>src/renderer</span> files to test HMR. See
<a target="_blank" href="https://electron-vite.org/guide/hmr.html">docs</a>.
</p>
</article>
</div>
<div class="feature-item">
<article>
<h2 class="title">Hot Reloading</h2>
<p class="detail">
Run <span>'electron-vite dev --watch'</span> to enable. See
<a target="_blank" href="https://electron-vite.org/guide/hot-reloading.html">docs</a>.
</p>
</article>
</div>
<div class="feature-item">
<article>
<h2 class="title">Debugging</h2>
<p class="detail">
Check out <span>.vscode/launch.json</span>. See
<a target="_blank" href="https://electron-vite.org/guide/debugging.html">docs</a>.
</p>
</article>
</div>
<div class="feature-item">
<article>
<h2 class="title">Source Code Protection</h2>
<p class="detail">
Supported via built-in plugin <span>bytecodePlugin</span>. See
<a target="_blank" href="https://electron-vite.org/guide/source-code-protection.html">
docs
</a>
.
</p>
</article>
</div>
<div class="feature-item">
<article>
<h2 class="title">Packaging</h2>
<p class="detail">
Use
<a target="_blank" href="https://www.electron.build">electron-builder</a>
and pre-configured to pack your app.
</p>
</article>
</div>
<component :is="item.prop" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<style lang="less">
@import './assets/css/styles.less';
</style>
<script>
import Wired from './components/Wired/index.vue'
import Wireless from './components/Wireless/index.vue'
import Advanced from './components/Advanced/index.vue'
export default {
components: {
Wired,
Wireless,
Advanced,
},
data() {
return {
tabsModel: [
{
label: '有线模式',
prop: 'Wired',
},
{
label: '无线模式',
prop: 'Wireless',
},
{
label: '高级配置',
prop: 'Advanced',
},
],
activeTab: 'Wired',
}
},
mounted() {},
methods: {
getDevices() {
window.adbkit.createClient()
},
},
}
</script>
<style></style>

View File

@ -1,205 +0,0 @@
body {
display: flex;
flex-direction: column;
font-family:
Roboto,
-apple-system,
BlinkMacSystemFont,
'Helvetica Neue',
'Segoe UI',
'Oxygen',
'Ubuntu',
'Cantarell',
'Open Sans',
sans-serif;
color: #86a5b1;
background-color: #2f3241;
}
* {
padding: 0;
margin: 0;
}
ul {
list-style: none;
}
code {
font-weight: 600;
padding: 3px 5px;
border-radius: 2px;
background-color: #26282e;
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-size: 85%;
}
a {
color: #9feaf9;
font-weight: 600;
cursor: pointer;
text-decoration: none;
outline: none;
}
a:hover {
border-bottom: 1px solid;
}
#app {
flex: 1;
display: flex;
flex-direction: column;
max-width: 840px;
margin: 0 auto;
padding: 15px 30px 0 30px;
}
.versions {
margin: 0 auto;
float: none;
clear: both;
overflow: hidden;
font-family: 'Menlo', 'Lucida Console', monospace;
color: #c2f5ff;
line-height: 1;
transition: all 0.3s;
li {
display: block;
float: left;
border-right: 1px solid rgba(194, 245, 255, 0.4);
padding: 0 20px;
font-size: 13px;
opacity: 0.8;
&:last-child {
border: none;
}
}
}
.hero-logo {
margin-top: -0.4rem;
transition: all 0.3s;
}
@media (max-width: 840px) {
.versions {
display: none;
}
.hero-logo {
margin-top: -1.5rem;
}
}
.hero-text {
font-weight: 400;
color: #c2f5ff;
text-align: center;
margin-top: -0.5rem;
margin-bottom: 10px;
}
@media (max-width: 660px) {
.hero-logo {
display: none;
}
.hero-text {
margin-top: 20px;
}
}
.hero-tagline {
text-align: center;
margin-bottom: 14px;
}
.links {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
font-size: 18px;
font-weight: 500;
a {
font-weight: 500;
}
.link-item {
padding: 0 4px;
}
}
.features {
display: flex;
flex-wrap: wrap;
margin: -6px;
.feature-item {
width: 33.33%;
box-sizing: border-box;
padding: 6px;
}
article {
background-color: rgba(194, 245, 255, 0.1);
border-radius: 8px;
box-sizing: border-box;
padding: 12px;
height: 100%;
}
span {
color: #d4e8ef;
word-break: break-all;
}
.title {
font-size: 17px;
font-weight: 500;
color: #c2f5ff;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail {
font-size: 14px;
font-weight: 500;
line-height: 22px;
margin-top: 6px;
}
}
@media (max-width: 660px) {
.features .feature-item {
width: 50%;
}
}
@media (max-width: 480px) {
.links {
flex-direction: column;
line-height: 32px;
.link-dot {
display: none;
}
}
.features .feature-item {
width: 100%;
}
}

View File

@ -0,0 +1,9 @@
<template>
<div class=""></div>
</template>
<script>
export default {}
</script>
<style></style>

View File

@ -1,14 +0,0 @@
<script setup>
import { reactive } from 'vue'
const versions = reactive({ ...window.electron.process.versions })
</script>
<template>
<ul class="versions">
<li class="electron-version">Electron v{{ versions.electron }}</li>
<li class="chrome-version">Chromium v{{ versions.chrome }}</li>
<li class="node-version">Node v{{ versions.node }}</li>
<li class="v8-version">V8 v{{ versions.v8 }}</li>
</ul>
</template>

View File

@ -0,0 +1,102 @@
<template>
<div class="">
<div class="flex">
<el-button type="primary" @click="handleFind">
刷新设备
</el-button>
<el-button type="warning" :loading="stopLoading" @click="handleReset">
{{ stopLoading ? '重置服务中' : '重置服务' }}
</el-button>
</div>
<div class="pt-4">
<el-table v-loading="loading" :data="deviceList" style="width: 100%" border>
<el-table-column prop="id" label="设备 ID" />
<el-table-column prop="name" label="设备名称">
<template #default="{ row }">
<div v-if="row.$unauthorized" class="flex items-center">
<el-tooltip content="请重新插拔设备并点击允许USB调试" placement="top-start">
<el-icon class="mr-1 text-red-600 text-lg">
<WarningFilled />
</el-icon>
</el-tooltip>
设备未授权
</div>
<span v-else class="">{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="{ row }">
<el-button type="primary" :loading="row.$loading" @click="handleStart(row)">
{{ row.$loading ? '运行中' : '连接设备' }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import { sleep } from '@renderer/utils/index.js'
export default {
data() {
return {
loading: false,
stopLoading: false,
deviceList: [],
}
},
created() {
this.getDeviceData()
},
methods: {
async handleStart(row) {
row.$loading = true
try {
await this.$scrcpy.shell(`-s ${row.id}`)
}
catch (error) {
this.$message.error(`${error.message}`)
}
row.$loading = false
},
async getDeviceData() {
this.loading = true
await sleep(500)
try {
const data = await this.$adb.getDevices().catch(e => console.warn(e))
console.log('getDeviceData.data', data)
this.deviceList = data.map(item => ({
...item,
name: item.model ? item.model.split(':')[1] : '未授权设备',
$loading: false,
$unauthorized: item.type === 'unauthorized',
}))
}
catch (error) {
console.warn('error', error.message)
}
this.loading = false
},
handleFind() {
this.getDeviceData()
},
async handleReset() {
this.stopLoading = true
try {
await this.$adb.kill()
await sleep(2000)
}
catch (error) {
console.warn('error', error.message)
}
this.stopLoading = false
this.$message.success('重置服务状态成功')
this.handleFind()
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,9 @@
<template>
<div class=""></div>
</template>
<script>
export default {}
</script>
<style></style>

View File

@ -1,4 +1,16 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
import plugins from './plugins/index.js'
import 'virtual:uno.css'
import './styles/index.js'
const app = createApp(App)
app.use(plugins)
app.config.globalProperties.$adb = window.adbkit()
app.config.globalProperties.$scrcpy = window.scrcpy()
app.mount('#app')

View File

@ -0,0 +1,15 @@
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './restyle.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
export default {
install(app) {
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
},
}

View File

@ -0,0 +1,35 @@
:root {
/* 主题色 */
--el-color-primary: rgba(var(--color-primary), 1);
--el-color-primary-dark-2: rgba(var(--color-primary-60), 1);
--el-color-primary-light-1: rgba(var(--color-primary-400), 1);
--el-color-primary-light-2: rgba(var(--color-primary-400), 1);
--el-color-primary-light-3: rgba(var(--color-primary-300), 1);
--el-color-primary-light-4: rgba(var(--color-primary-300), 1);
--el-color-primary-light-5: rgba(var(--color-primary-200), 1);
--el-color-primary-light-6: rgba(var(--color-primary-200), 1);
--el-color-primary-light-7: rgba(var(--color-primary-100), 1);
--el-color-primary-light-8: rgba(var(--color-primary-100), 1);
--el-color-primary-light-9: rgba(var(--color-primary-50), 1);
/* 字体大小 */
--el-font-size-base: 12px;
--el-font-size-small: 14px;
--el-font-size-large: 16px;
}
.el-tabs-flex {
@apply flex flex-col h-full !important;
.el-tabs__header {
@apply flex-none !important;
}
.el-tabs__content {
@apply flex-grow overflow-hidden h-full !important;
}
.el-tab-pane {
@apply h-full;
}
}

View File

@ -0,0 +1,7 @@
import ElementPlus from './element-plus/index.js'
export default {
install(app) {
app.use(ElementPlus)
},
}

View File

@ -0,0 +1 @@
import '@viarotel-org/design/styles/resets'

View File

@ -0,0 +1 @@
import './css/index.js'

View File

@ -0,0 +1,9 @@
/**
* @desc 使用async await 进项进行延时操作
* @param {*} time
*/
export function sleep(time = 1000) {
return new Promise((resolve) => {
setTimeout(() => resolve(true), time)
})
}

5
unocss.config.js Normal file
View File

@ -0,0 +1,5 @@
import extendConfig from '@viarotel-org/unocss-config/src/desktop/index.js'
export default extendConfig({
shortcuts: {},
})