yichael 2 недель назад
Родитель
Сommit
3d2e59a748

+ 1 - 1
bat-tool/adb-connect-test/adb-connect.bat

@@ -2,7 +2,7 @@
 chcp 65001 >nul
 title ADB Connect Test
 cd /d "%~dp0"
-node adb-connect.js 192.168.2.1 5555
+node adb-connect.js 192.168.2.5 5555
 if errorlevel 1 (
     echo.
     pause

+ 34 - 0
doc/node说明.txt

@@ -0,0 +1,34 @@
+
+Echo
+┌────────────────────────────────────┐
+│                                    │
+│ ► [执行输入]           [执行输出] ►        
+│                                    │
+│                                    │
+│	○ [    ]						 │			 
+│   ● turn    						 │ 
+│  							         │
+│	+								 │
+└────────────────────────────────────┘
+
+
+If
+┌────────────────────────────────────┐
+│                                    │
+│ ► [执行输入]           [true] ►      
+│                                    │
+│ 									 │
+│ ● condition    					 │ 
+│  						[false] ► 	 │
+│									 │
+└────────────────────────────────────┘
+
+
+
+If
+┌─────────────────────────────────────────┐
+│ ►                           [True] ►    │
+│                                         │
+│ ● condition                             │
+│                            [False] ►    │
+└─────────────────────────────────────────┘

+ 234 - 0
doc/工作流语法.md

@@ -0,0 +1,234 @@
+# 工作流语法
+
+## 基本结构
+
+```json
+{
+  "variables": {},
+  "execute": []
+}
+```
+
+## 语法分层
+
+- **基础语法**:`schedule`、`if`、`while`
+- **基础 action**:`adb`(通过 method 区分)
+- **扩展标签(Func)**:`src/pages/processing/func/` 目录下的脚本文件名即为标签名
+
+## 定时执行(schedule)
+
+```json
+{
+  "type": "schedule",
+  "condition": {
+    "interval": "1s",
+    "repeat": -1
+  },
+  "interval": []
+}
+```
+
+- `interval`: 执行间隔("1s", "2m", "3h")
+- `repeat`: 重复次数(`-1` 表示无限循环)
+
+## ADB操作(adb)
+
+统一格式:
+```json
+{
+  "type": "adb",
+  "method": "input|click|locate|swipe|scroll|press|string-press",
+  "inVars": [],
+  "outVars": []
+}
+```
+
+| method | 说明 | inVars |
+|--------|------|--------|
+| `input` | 输入文本 | `[0]`: 文本内容 |
+| `click` | 点击 | `[0]`: 位置坐标(字符串格式:`"{\"x\":123,\"y\":456}"` 或 `"123,456"`) |
+| `locate` | 定位 | `[0]`: 图片/文字,`outVars[0]`: 保存位置 |
+| `swipe` | 滑动 | `[0]`: 方向(up-down/down-up/left-right/right-left) |
+| `scroll` | 滚动 | `[0]`: 方向(up/down) |
+| `press` | 图像匹配并点击 | `[0]`: 图片路径 |
+| `string-press` | 文字识别并点击 | `[0]`: 文字内容 |
+
+## 条件判断(if)
+
+```json
+{
+  "type": "if",
+  "condition": "{变量} == '值'",
+  "ture": [],
+  "false": []
+}
+```
+
+支持操作符:`==` `!=` `>` `<` `>=` `<=`
+
+### 条件判断示例
+
+**数值比较:**
+```json
+{
+  "type": "if",
+  "condition": "{count} > 5",
+  "ture": [{"type": "echo", "value": "count大于5"}],
+  "false": []
+}
+```
+
+**字符串比较:**
+```json
+{
+  "type": "if",
+  "condition": "{message} == \"hello\"",
+  "ture": [{"type": "echo", "value": "消息是hello"}],
+  "false": []
+}
+```
+
+**布尔值判断:**
+```json
+{
+  "type": "if",
+  "condition": "{isReady} == true",
+  "ture": [{"type": "echo", "value": "已准备好"}],
+  "false": [{"type": "echo", "value": "未准备好"}]
+}
+```
+
+或直接使用布尔变量:
+```json
+{
+  "type": "if",
+  "condition": "{isReady}",
+  "ture": [{"type": "echo", "value": "已准备好"}],
+  "false": []
+}
+```
+
+## 循环(while)
+
+```json
+{
+  "type": "while",
+  "condition": "{变量} > 0",
+  "ture": []
+}
+```
+
+> 注意:`ture` 是 `body` 的别名。
+
+## 内置操作
+
+### 延迟(delay)
+```json
+{ "type": "delay", "value": "2s" }
+```
+
+### 设置变量(set)
+```json
+{ "type": "set", "variable": "{name}", "value": "value" }
+```
+
+### 打印信息(echo)
+```json
+{ "type": "echo", "value": "当前消息: {{lastMessage}}" }
+```
+
+或使用 `inVars`:
+```json
+{ "type": "echo", "inVars": ["{lastMessage}", "{lastRole}"] }
+```
+
+- `value`: 直接文本,支持 `{{variable}}` 格式的变量替换(双花括号用于字符串拼接)
+- `inVars`: 变量名数组,输出所有变量的值
+
+> 注意:`log` 标签已废弃,请使用 `echo` 标签。
+
+### 随机数(random)
+```json
+{
+  "type": "random",
+  "inVars": ["1", "100"],
+  "outVars": ["{randomNum}"]
+}
+```
+
+- `inVars[0]`: 最小值(字符串格式的数字或变量引用)
+- `inVars[1]`: 最大值(字符串格式的数字或变量引用)
+- `outVars[0]`: 输出变量(**`number` 类型**,可以是整数或小数)
+
+**传统格式(已过时,建议使用上述格式):**
+```json
+{ 
+  "type": "random", 
+  "variable": "num", 
+  "min": 1, 
+  "max": 100, 
+  "integer": true 
+}
+```
+
+## 变量
+
+- 定义:`"variables": {"name": "value"}`
+- 使用:`"{name}"` 在字段中引用
+- 保存:`"outVars": ["{name}"]` 或 `"variable": "{name}"`
+- 类型:支持三种数据类型:
+  - **`number`**:数值类型(整数或小数),例如:`1`、`3.14`、`-5.2`
+  - **`string`**:字符串类型,例如:`"hello"`、`""`
+  - **`bool`**:布尔类型,只能是 `true` 或 `false`(不是字符串)
+
+### 变量类型说明
+- 变量的类型由其初始值自动推断:
+  - 如果初始值是数字(如 `1`、`3.14`),类型为 `number`
+  - 如果初始值是字符串(如 `"text"`、`""`),类型为 `string`
+  - 如果初始值是布尔值(如 `true`、`false`),类型为 `bool`
+- 示例:
+  ```json
+  {
+    "variables": {
+      "count": 0,           // number 类型
+      "message": "",        // string 类型
+      "isReady": false      // bool 类型
+    }
+  }
+  ```
+
+## 时间格式
+
+- 间隔:`"1s"`, `"2m"`, `"3h"`
+- 日期:`"2026/1/14 01:21"`
+- 时间:`"09:00"`
+
+## 扩展标签
+
+扩展标签由 `src/pages/processing/func/` 目录下的脚本文件决定,每个脚本文件名即为标签名。
+
+常用标签:
+- `ocr-chat`: OCR识别对话内容
+  - `inVars`: `[好友RGB颜色, 我的RGB颜色, 区域坐标]`(RGB格式:`"(r,g,b)"`,区域坐标:JSON字符串)
+  - `outVars`: `[聊天记录变量]`(输出 chat-history.txt 格式的JSON字符串)
+- `read-last-message`: 读取最后一条消息(输出文本和发送者角色)
+- `smart-chat-append`: 智能合并历史聊天记录和当前聊天记录,自动检测并去除连续重合部分后返回新的聊天记录字符串
+  - `inVars`: `[历史记录, 当前记录]`
+  - `outVars`: `[合并后的记录]`
+- `read-txt`: 读取文本文件内容
+  - `inVars`: `[文件路径]`(相对于工作流目录,如 `"history/chat-history.txt"`)
+  - `outVars`: `[文件内容变量]`
+- `save-txt`: 保存字符串为文本文件
+  - `inVars`: `[内容, 文件路径]`(路径相对于工作流目录)
+  - `outVars`: `[]`
+- `image-center-location`: 图像中心点定位
+  - `inVars`: `[模板图片路径]`(相对于工作流目录的 resources 文件夹)
+  - `outVars`: `[位置坐标变量]`(输出JSON字符串格式:`{"x":123,"y":456}`)
+- `image-region-location`: 图像区域定位(在完整截图中查找区域图片的位置)
+  - `inVars`: `[截图路径, 区域图片路径]`(都相对于工作流目录的 resources 文件夹,如 `"ScreenShot.jpg"` 和 `"ChatArea.png"`)
+  - `outVars`: `[区域坐标变量]`(返回四个顶点坐标:`{topLeft: {x, y}, topRight: {x, y}, bottomLeft: {x, y}, bottomRight: {x, y}}`)
+  - 注意:此标签**不主动截图**,使用 `resources/` 文件夹下已有的截图文件进行匹配
+- `image-area-cropping`: 图像区域裁剪(从当前屏幕截图裁剪指定区域)
+  - `inVars`: `[区域坐标, 保存路径]`(区域坐标:JSON字符串格式,保存路径相对于工作流目录的 history 文件夹)
+  - `outVars`: `[]`
+  - 功能:从主进程缓存获取最新截图,保存到 `history/ScreenShot.jpg`,然后根据区域坐标裁剪并保存到指定路径

+ 32 - 102
electron/main.js

@@ -1,6 +1,5 @@
 const { app, BrowserWindow ,ipcMain} = require('electron')
 const path = require('path')
-const http = require('http')
 const os = require('os')
 const fs = require('fs')
 const config = require('../configs/config.js')
@@ -43,72 +42,10 @@ if (process.platform === 'win32') {
   }
 }
 
-/**
- * 检测 Vite 开发服务器实际使用的端口
- * 如果配置的端口被占用,Vite 会自动尝试下一个端口
- * 需要确认是 Vite 服务器,而不仅仅是端口响应
- */
-async function findVitePort(startPort, maxAttempts = 10) {
-  const viteHost = config.vite?.host || 'localhost'
-  
-  for (let offset = 0; offset < maxAttempts; offset++) {
-    const port = startPort + offset
-    const isViteServer = await new Promise((resolve) => {
-      const req = http.get(`http://${viteHost}:${port}`, (res) => {
-        // 检查状态码
-        if (res.statusCode !== 200) {
-          resolve(false)
-          return
-        }
-        
-        // 读取响应数据确认是否是 Vite 服务器
-        let data = ''
-        let resolved = false
-        
-        res.on('data', (chunk) => {
-          if (resolved) return
-          data += chunk.toString()
-          // 如果响应包含 Vite 特征,立即确认
-          if (data.length > 100 && (data.includes('/vite') || data.includes('Vite') || data.includes('vite/client'))) {
-            resolved = true
-            resolve(true)
-          }
-        })
-        
-        res.on('end', () => {
-          if (resolved) return
-          // 检查响应内容是否包含 Vite 特征
-          if (data.includes('/vite') || data.includes('Vite') || data.includes('vite/client')) {
-            resolve(true)
-          } else {
-            resolve(false)
-          }
-        })
-      })
-      
-      req.on('error', () => {
-        resolve(false)
-      })
-      
-      req.setTimeout(2000, () => {
-        req.destroy()
-        resolve(false)
-      })
-    })
-    
-    if (isViteServer) {
-      return port
-    }
-  }
-  
-  // 如果找不到,返回配置的端口
-  return startPort
-}
-
 // 保存主窗口引用,用于推送消息
 let mainWindowInstance = null
 
-async function createWindow() {
+function createWindow() {
   const mainWindow = new BrowserWindow({
     width: config.window.width,
     height: config.window.height,
@@ -124,12 +61,8 @@ async function createWindow() {
   mainWindowInstance = mainWindow
 
   if (isDev) {
-    // 从配置文件读取 Vite 开发服务器端口
-    const configPort = config.vite?.port || 5173
-    // 检测实际使用的端口(如果配置端口被占用,Vite 会自动尝试下一个)
-    const vitePort = await findVitePort(configPort)
+    const vitePort = config.vite?.port || 5173
     const viteHost = config.vite?.host || 'localhost'
-    
     console.log(`Loading Vite dev server at http://${viteHost}:${vitePort}`)
     mainWindow.loadURL(`http://${viteHost}:${vitePort}`)
     
@@ -167,69 +100,66 @@ ipcMain.handle('run-nodejs-script', async (event, scriptName, ...parameters) =>
     let stderr = ''
     let resolved = false
     
+    const finish = (result) => {
+      if (resolved) return
+      resolved = true
+      resolve(result)
+    }
+    
     nodeProcess.stdout.on('data', (data) => {
       const dataStr = data.toString()
       stdout += dataStr
-      
-      // 检查是否是 JSON 格式的成功消息(用于持续运行的脚本)
+      const isLongRunning = scriptName.includes('screenshot') || scriptName.includes('adb/')
       try {
         const lines = dataStr.trim().split('\n')
         for (const line of lines) {
           if (line.trim().startsWith('{')) {
             const json = JSON.parse(line.trim())
-            if (json.success && !resolved) {
-              resolved = true
-              // 对于持续运行的脚本,收到成功消息后立即返回,不等待进程结束
-              resolve({
-                success: true,
-                stdout: line.trim(),
-                stderr: stderr.trim(),
-                exitCode: null
-              })
+            if (json.success && isLongRunning) {
+              finish({ success: true, stdout: line.trim(), stderr: stderr.trim(), exitCode: null })
+              return
             }
           }
         }
-      } catch (e) {
-        // 不是 JSON,继续收集输出
-      }
+      } catch (e) {}
     })
     
     nodeProcess.stderr.on('data', (data) => {
       stderr += data.toString()
     })
     
-    nodeProcess.on('close', (code) => {
-      runningProcesses.delete(processKey)
+    const timeoutId = setTimeout(() => {
       if (!resolved) {
-        resolve({
-          success: code === 0,
+        finish({
+          success: false,
           stdout: stdout.trim(),
           stderr: stderr.trim(),
-          exitCode: code
+          exitCode: 1,
+          message: 'Script is running in background'
         })
       }
+    }, 5000)
+
+    nodeProcess.on('close', (code) => {
+      clearTimeout(timeoutId)
+      runningProcesses.delete(processKey)
+      const exitCode = (code !== null && code !== undefined) ? code : 1
+      finish({
+        success: exitCode === 0,
+        stdout: stdout.trim(),
+        stderr: stderr.trim(),
+        exitCode
+      })
     })
     
     nodeProcess.on('error', (error) => {
+      clearTimeout(timeoutId)
       runningProcesses.delete(processKey)
       if (!resolved) {
+        resolved = true
         reject(error)
       }
     })
-    
-    // 对于持续运行的脚本,设置超时,如果 5 秒内没有收到成功消息,也返回
-    setTimeout(() => {
-      if (!resolved) {
-        resolved = true
-        resolve({
-          success: true,
-          stdout: stdout.trim(),
-          stderr: stderr.trim(),
-          exitCode: null,
-          message: 'Script is running in background'
-        })
-      }
-    }, 5000)
   })
 })
 

+ 3 - 5
nodejs/adb/adb-connect.js

@@ -20,10 +20,8 @@ const connectCommand = `"${adbPath}" connect ${deviceIp}:${devicePort}`
 const connectOutput = execSync(connectCommand, { encoding: 'utf-8', timeout: 500 })
 const connectSuccess = connectOutput.trim().includes('connected') || connectOutput.trim().includes('already connected')
 
-if (!connectSuccess) {
-  process.exit(1)
-}
-else
-{
+if (connectSuccess) {
   process.exit(0)
+} else {
+  process.exit(1)
 }

+ 9 - 2
src/page/device/connect-item/connect-item.js

@@ -2,7 +2,7 @@ class ConnectItemClass {
     constructor() {
     }
 
-    async init(ipAddress, isConnected, setIsConnected, isPreviewing, setIsPreviewing, onConnect, onPreview, onRemove) {
+    async init(ipAddress, isConnected, setIsConnected, isPreviewing, setIsPreviewing, onConnect, onPreview, onRemove, setIsSelected, isSelected) {
         this.ipAddress = ipAddress
         this.is_connected = isConnected || false
         this.is_previewing = isPreviewing || false
@@ -11,6 +11,13 @@ class ConnectItemClass {
         this.onRemoveCallback = onRemove
         this.onConnectCallback = onConnect
         this.onPreviewCallback = onPreview
+        this.setIsSelected = setIsSelected
+        this.isSelected = isSelected ?? false
+    }
+
+    // 单选框点击:切换选中/未选中(点击一下选中,再点击一下取消)
+    onSelect() {
+        this.setIsSelected(!this.isSelected)
     }
 
     async onConnect() {
@@ -87,7 +94,7 @@ class ConnectItemClass {
 
     // 连接设备
     async connect() {
-        const result = await window.electronAPI.runNodejsScript('adb-connect', this.ipAddress, '5555')
+        const result = await window.electronAPI.runNodejsScript('adb/adb-connect', this.ipAddress, '5555')
         if (result.exitCode === 0) {
             this.is_connected = true
             this.startConnectionCheck()

+ 49 - 13
src/page/device/connect-item/connect-item.jsx

@@ -2,11 +2,12 @@ import React, { useState, useRef, useEffect } from 'react'
 import './connect-item.scss'
 import { ConnectItemClass } from './connect-item.js'
 
-function ConnectItem({ ipAddress, isConnected=false, onRemove, onConnect, onPreview }) {
+function ConnectItem({ ipAddress, isConnected=false, selected=false, onRemove, onConnect, onPreview }) {
   const connectItemClassRef = useRef(null)
   const [isConnectedState, setIsConnectedState] = useState(isConnected)
   const [isPreviewing, setIsPreviewing] = useState(false)
-  
+  const [isSelected, setIsSelected] = useState(selected)
+
   useEffect(() => {
     if (!connectItemClassRef.current) {
       connectItemClassRef.current = new ConnectItemClass()
@@ -18,32 +19,67 @@ function ConnectItem({ ipAddress, isConnected=false, onRemove, onConnect, onPrev
         setIsPreviewing,
         onConnect,
         onPreview,
-        onRemove
+        onRemove,
+        setIsSelected,
+        isSelected
       )
     }
-    
     return () => {
       if (connectItemClassRef.current) {
         connectItemClassRef.current.stopConnectionCheck()
       }
     }
   }, [])
+
+  // 把最新的 isSelected 同步到 Class,这样 Class 里 this.isSelected 始终是当前值
+  useEffect(() => {
+    if (connectItemClassRef.current) {
+      connectItemClassRef.current.isSelected = isSelected
+    }
+  }, [isSelected])
   
   return (
     <div className="connect-item-container">
-      <div className="ip-address">{ipAddress}</div>
-      <div className="connect-btn" onClick={() => connectItemClassRef.current?.onConnect()}>
-        {isConnectedState ? '断开' : '连接'}
+      <div
+          className={`select-btn ${isSelected ? 'select-btn--selected' : ''}`}
+          onClick={() => connectItemClassRef.current?.onSelect()}
+          role="radio"
+          aria-checked={isSelected}
+        >
+          {isSelected ? (
+            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
+              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none"/>
+              <path d="M6 12l4 4 8-10" stroke="#00c853" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
+            </svg>
+          ) : (
+            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
+              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none"/>
+            </svg>
+          )}
       </div>
-      <div className="preview-btn" onClick={() => connectItemClassRef.current?.onPreview()}>
-        {isPreviewing ? '停止预览' : '预览'}
+      <div className="item-container">
+        
+        <div className="ip-address">{ipAddress}</div>
+      
+        <div className="btn-area-container">
+          <div className="connect-btn" onClick={() => connectItemClassRef.current?.onConnect()}>
+            {isConnectedState ? '断开' : '连接'}
+          </div>
+          <div className="preview-btn" onClick={() => connectItemClassRef.current?.onPreview()}>
+            {isPreviewing ? '停止预览' : '预览'}
+          </div>
+          
+        </div>
+
       </div>
+
       <div className="remove-btn" onClick={() => connectItemClassRef.current?.onRemove()}>
-        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-          <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none"/>
-          <path d="M8 12H16" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
-        </svg>
+          <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none"/>
+              <path d="M8 12H16" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
+          </svg>
       </div>
+
     </div>
   )
 }

+ 70 - 32
src/page/device/connect-item/connect-item.scss

@@ -9,8 +9,8 @@ $font-size-scale: 1.5;  // 字体缩放系数,调整此值可改变字体大
   }
 
 .connect-item-container {
-  width: 100%;
-  height: 100%;
+  width: 90%;
+  height: 10%;
 
   display: flex;
   flex-direction: row;
@@ -18,7 +18,7 @@ $font-size-scale: 1.5;  // 字体缩放系数,调整此值可改变字体大
   justify-content: center;
 
   margin: 5%;
-  border-radius: 10px;
+  
   
     /* 计算过程:
      * home-bg grid: Device占第一列20%
@@ -37,49 +37,87 @@ $font-size-scale: 1.5;  // 字体缩放系数,调整此值可改变字体大
   font-size: min(calc(16vw * 0.06 * $font-size-scale), calc(8vh * 0.12 * $font-size-scale));
 
   user-select: none;
-  border: 1px solid #000000;
-}
-
-.ip-address {
-    width: 100%;
+  
+  .select-btn {
+    width: 10%;
     height: 100%;
-
-    margin: 1%;
+    cursor: pointer;
     @include flex-center;
-}
 
-.connect-btn {
-    width: 100%;
-    height: 60%;
+    &:hover {
+      opacity: 0.85;
+    }
+    &--selected svg circle {
+      stroke: #00c853;
+    }
+  }
 
-    margin: 1%;
-    @include flex-center;
+  .item-container {
+    width: 80%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
 
     border-radius: 10px;
-    @include highlight-btn(#004ef6);
-}
+    border: 1px solid #000000;
+
+    .ip-address {
+        width: 100%;
+        height: 30%;
+    
+        margin: 1%;
+        @include flex-center;
+    }
 
-.preview-btn {
-    width: 100%;
-    height: 60%;
+    .btn-area-container {
+        width: 100%;
+        height: 70%;
 
-    margin: 1%;
-    @include flex-center;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
 
-    border-radius: 10px;
-    @include highlight-btn(#00fb43);
-}
+        .connect-btn {
+            width: 100%;
+            height: 100%;
+
+            margin: 1%;
+            @include flex-center;
+
+            border-radius: 10px;
+            @include highlight-btn(#004ef6);
+        }
 
-.remove-btn {
-    width: 40%;
-    height: 40%;
+        .preview-btn {
+            width: 100%;
+            height: 100%;
+
+            margin: 1%;
+            @include flex-center;
+
+            border-radius: 10px;
+            @include highlight-btn(#00fb43);
+        }     
+    }
+  }
+
+  .remove-btn {
+    width: 10%;
+    height: 100%;
 
     margin: 1%;
     @include div-btn-hover-effect;
     @include div-btn-pressed-effect;
 
+    color: #f60000;
     svg {
-        width: 100%;
-        height: 100%;
+      width: 100%;
+      height: 100%;
     }
-}
+  }
+}
+
+

+ 24 - 15
src/page/device/device.js

@@ -6,10 +6,12 @@ import alertView from '../public/alert-view/alert-view.js'
 class DeviceClass {
     constructor() {}
 
-    async init(setDeviceList,inputValue)
+    async init(setDeviceList, inputValue, setSelectedDevice, setInputValue)
     {
         this.setDeviceList = setDeviceList
         this.inputValue = inputValue
+        this.setSelectedDevice = setSelectedDevice
+        this.setInputValue = setInputValue
         this.count_ip_x = 0
         this.count_ip_y = 0
 
@@ -44,7 +46,7 @@ class DeviceClass {
         }
 
         const ip = this.inputValue 
-        const result = await window.electronAPI.runNodejsScript('adb-connect', ip, '5555')
+        const result = await window.electronAPI.runNodejsScript('adb/adb-connect', ip, '5555')
 
         if (result.exitCode === 0) {
             that.setDeviceList(prev => [...prev, ip])
@@ -60,28 +62,35 @@ class DeviceClass {
 
     async onAddDevice() {
         const ip = this.inputValue; 
-        const result = await window.electronAPI.runNodejsScript('adb-connect', ip, '5555')
+        const ipList = await window.electronAPI.runNodejsScript('json-parser', 'read', 'device_list.json')
+
+        const jsonData = JSON.parse(ipList.stdout)
+        const devices = jsonData.data.devices
+        if (devices.includes(ip)) {
+      
+            hintView.setContent('设备已存在')
+            hintView.show()
+            return
+        }     
+
+        const result = await window.electronAPI.runNodejsScript('adb/adb-connect', ip, '5555')
     
-        if (result.exitCode === 1) {
+        if (result.exitCode === 0) {
             hintView.setContent('设备添加成功')
             hintView.show()
-
-            await this.addDevice(ip)
+            this.setSelectedDevice(ip)
+            await this.addDevice(ip, this.currentDeviceList || [])
+            this.setInputValue?.('192.168.')
             return
         }
         alertView.show()
         alertView.setContent('无法检测到连接设备')
     }
 
-
-
-    async addDevice(ip) {
-        let newArr = null
-        this.setDeviceList(prev => {
-            newArr = [...prev, ip]
-            return newArr
-        })
-        await window.electronAPI.runNodejsScript('json-parser', 'update', 'device_list.json', JSON.stringify({devices: newArr}))
+    async addDevice(ip, currentList = []) {
+        const newArr = [...currentList, ip]
+        this.setDeviceList(newArr)
+        await window.electronAPI.runNodejsScript('json-parser', 'update', 'device_list.json', JSON.stringify({ devices: newArr }))
     }
 
     async onRemoveDevice(ip) {

+ 14 - 2
src/page/device/device.jsx

@@ -11,6 +11,7 @@ function Device({ show }) {
   const [deviceList, setDeviceList] = useState([])
   const deviceClass = useRef(null)
   const [inputValue, setInputValue] = useState('192.168.')
+  const [selectedDevice, setSelectedDevice] = useState(null)
   if (!show) {
     return null
   }
@@ -18,10 +19,18 @@ function Device({ show }) {
   useEffect(() => {
     if (!deviceClass.current) {
       deviceClass.current = new DeviceClass()
-      deviceClass.current.init(setDeviceList,inputValue)
+      deviceClass.current.init(setDeviceList, inputValue, setSelectedDevice, setInputValue)
     }
   }, [])
 
+  useEffect(() => {
+    if (deviceClass.current) deviceClass.current.inputValue = inputValue
+  }, [inputValue])
+
+  useEffect(() => {
+    if (deviceClass.current) deviceClass.current.currentDeviceList = deviceList
+  }, [deviceList])
+
   return (
     <>
       <div className="device-container">
@@ -42,6 +51,8 @@ function Device({ show }) {
             <ConnectItem 
               key={index} 
               ipAddress={deviceList[index]} 
+              selected={selectedDevice === deviceList[index]}
+              onSelect={(ip) => setSelectedDevice(ip)}
               onRemove={(ip) => deviceClass.current?.onRemoveDevice(ip)}
             />
           ))}
@@ -53,7 +64,8 @@ function Device({ show }) {
             <input 
               type="text" 
               placeholder="请输入设备IP" 
-              defaultValue={inputValue}
+              value={inputValue}
+              onChange={(e) => setInputValue(e.target.value)}
               onKeyDown={(e) => { if (e.key === 'Enter') { deviceClass.current?.onAddDevice() } }}
             />
           </div>

+ 0 - 6
src/page/device/device.scss

@@ -42,12 +42,6 @@
 
     @include flex-column-start;
     border: 1px solid #000000;
-    .connect-item-container {
-      width: 90%;
-      height: 8%;
-      @include flex-row-between;
-      // border: 1px solid #000000;
-    }
   }
 
   // 添加设备

+ 1 - 1
static/device_list.json

@@ -1,5 +1,5 @@
 {
   "devices": [
-    "192.168."
+    "192.168.2.5"
   ]
 }