yichael 3 هفته پیش
والد
کامیت
83dade2f76

+ 30 - 25
.cursorrules

@@ -1,27 +1,19 @@
-# Project Coding Standards
+# Coding Standards
 
-Follow these coding standards strictly for all code in this project.
-
-## 1. Variable Naming
-
-Meaningful variable names, file names, folder names. Use abbreviations for words over ten characters. Abbreviations must be over three characters.
+## 1. Meaningful variable names, file names, folder names. Use abbreviations for words over ten characters. Abbreviations must be over three characters.
 
 - ✅ `update-btn` (refresh button), `device-list`, `user-profile-settings` (profile = abbreviation)
 - ❌ `btn` (too vague), `d1` (meaningless), `temp` (unclear), `us` (abbreviation too short)
 
-## 2. Word Separation: Kebab-Case
-
-Use hyphens (`-`) for multi-word names.
-
-## 3. Comments: One Block, One Comment, English Only
+## 2. Use hyphens (`-`) for multi-word names.
 
-## 4. No Try-Catch
+## 3. Comments: One Function, One Comment, English Only
 
-Never use try-catch. Let errors crash.
+## 4. Use functions to separate different logic. Avoid writing too much logic in a single function.
 
-## 5. GUI File Structure: Separate JSX, JS, SCSS
+## 5. Never use try-catch. Let errors crash.
 
-GUI components must split into three files:
+## 6. GUI components must split into three files:
 
 - `.jsx`: Layout only (no logic, no styles)
 - `.js`: Logic only (functions, state, business logic)
@@ -29,22 +21,35 @@ GUI components must split into three files:
 
 **Never write logic or inline styles in `.jsx` files.**
 
-## 6. Code Simplicity: Minimal Code
+## 7. Use the least code to implement functionality. 
 
-Use the least code to implement functionality.
+## 8. Do not add any `console.log` statements in production code.
 
-## 7. No Console.log
+## 9. Do not create script files unnecessarily. If you can call PowerShell directly, use PowerShell instead of creating a separate script file.
 
-Do not add any `console.log` statements in production code.
+## 10. If you need to create any new files, you must ask first.
 
-## 8. Avoid Creating Script Files
+## 11. Use `switch` statements instead of multiple `if-else` chains when checking the same variable against multiple values.
 
-Do not create script files unnecessarily. If you can call PowerShell directly, use PowerShell instead of creating a separate script file.
+## 12. Prefer Early Return Over Else Blocks
 
-## 9. Ask Before Creating New Files
+Prefer early return pattern:
+```javascript
+if (condition) { 
+    ...
+    return;
+}
+```
 
-If you need to create any new files, you must ask first.
+Instead of:
+```javascript
+if (condition) {
+    ...
+} else {
+    ...
+}
+```
 
-## 10. Prefer Switch Statements Over If-Else Chains
+## 13. No Existence Checks
 
-Use `switch` statements instead of multiple `if-else` chains when checking the same variable against multiple values.
+Do not add if statements to check if directories exist, files exist, or if parsing succeeded. Let errors crash.

+ 29 - 22
doc/CODING_STANDARDS.md

@@ -1,25 +1,19 @@
 # Coding Standards
 
-## 1. Variable Naming
-
-Meaningful variable names, file names, folder names. Use abbreviations for words over ten characters. Abbreviations must be over three characters.
+## 1. Meaningful variable names, file names, folder names. Use abbreviations for words over ten characters. Abbreviations must be over three characters.
 
 - ✅ `update-btn` (refresh button), `device-list`, `user-profile-settings` (profile = abbreviation)
 - ❌ `btn` (too vague), `d1` (meaningless), `temp` (unclear), `us` (abbreviation too short)
 
-## 2. Word Separation: Kebab-Case
-
-Use hyphens (`-`) for multi-word names.
-
-## 3. Comments: One Block, One Comment, English Only
+## 2. Use hyphens (`-`) for multi-word names.
 
-## 4. No Try-Catch
+## 3. Comments: One Function, One Comment, English Only
 
-Never use try-catch. Let errors crash.
+## 4. Use functions to separate different logic. Avoid writing too much logic in a single function.
 
-## 5. GUI File Structure: Separate JSX, JS, SCSS
+## 5. Never use try-catch. Let errors crash.
 
-GUI components must split into three files:
+## 6. GUI components must split into three files:
 
 - `.jsx`: Layout only (no logic, no styles)
 - `.js`: Logic only (functions, state, business logic)
@@ -27,22 +21,35 @@ GUI components must split into three files:
 
 **Never write logic or inline styles in `.jsx` files.**
 
-## 6. Code Simplicity: Minimal Code
+## 7. Use the least code to implement functionality. 
 
-Use the least code to implement functionality. 
+## 8. Do not add any `console.log` statements in production code.
 
-## 7. No Console.log
+## 9. Do not create script files unnecessarily. If you can call PowerShell directly, use PowerShell instead of creating a separate script file.
 
-Do not add any `console.log` statements in production code.
+## 10. If you need to create any new files, you must ask first.
 
-## 8. Avoid Creating Script Files
+## 11. Use `switch` statements instead of multiple `if-else` chains when checking the same variable against multiple values.
 
-Do not create script files unnecessarily. If you can call PowerShell directly, use PowerShell instead of creating a separate script file.
+## 12. Prefer Early Return Over Else Blocks
 
-## 9. Ask Before Creating New Files
+Prefer early return pattern:
+```javascript
+if (condition) { 
+    ...
+    return;
+}
+```
 
-If you need to create any new files, you must ask first.
+Instead of:
+```javascript
+if (condition) {
+    ...
+} else {
+    ...
+}
+```
 
-## 10. Prefer Switch Statements Over If-Else Chains
+## 13. No Existence Checks
 
-Use `switch` statements instead of multiple `if-else` chains when checking the same variable against multiple values.
+Do not add if statements to check if directories exist, files exist, or if parsing succeeded. Let errors crash.

+ 85 - 0
nodejs/directory-parser.js

@@ -0,0 +1,85 @@
+#!/usr/bin/env node
+// Iterate through all folders and files in a directory, return directory structure array
+const fs = require('fs')
+const path = require('path')
+
+const operation = process.argv[2]
+const relativePath = process.argv[3]
+
+if (!operation || !relativePath) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Missing required parameters: operation and directory path'
+  }) + '\n')
+  process.exit(1)
+}
+
+const staticDir = path.resolve(__dirname, '..', 'static')
+const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/')
+
+if (normalizedPath.includes('..') || normalizedPath.startsWith('/')) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Invalid directory path'
+  }) + '\n')
+  process.exit(1)
+}
+
+const targetDir = path.resolve(staticDir, normalizedPath)
+
+if (!targetDir.startsWith(staticDir)) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Directory path must be within static directory'
+  }) + '\n')
+  process.exit(1)
+}
+
+if (!fs.existsSync(targetDir)) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Directory does not exist'
+  }) + '\n')
+  process.exit(1)
+}
+
+const stats = fs.statSync(targetDir)
+if (!stats.isDirectory()) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Path is not a directory'
+  }) + '\n')
+  process.exit(1)
+}
+
+// Read only first level directories and files
+function readFirstLevel(dirPath) {
+  const forders = []
+  const files = []
+  const entries = fs.readdirSync(dirPath, { withFileTypes: true })
+
+  for (const entry of entries) {
+    if (entry.isDirectory()) {
+      forders.push(entry.name)
+    } else {
+      files.push(entry.name)
+    }
+  }
+
+  return {
+    forders: forders,
+    files: files
+  }
+}
+
+if (operation === 'read') {
+  const result = readFirstLevel(targetDir)
+  process.stdout.write(JSON.stringify(result) + '\n')
+  process.exit(0)
+} else {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: `Unknown operation: ${operation}. Supported: read`
+  }) + '\n')
+  process.exit(1)
+}

+ 67 - 0
nodejs/list-folders.js

@@ -0,0 +1,67 @@
+#!/usr/bin/env node
+// List folder names in a directory
+const fs = require('fs')
+const path = require('path')
+
+const relativePath = process.argv[2]
+
+if (!relativePath) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Missing required parameter: directory path'
+  }) + '\n')
+  process.exit(1)
+}
+
+const staticDir = path.resolve(__dirname, '..', 'static')
+const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/')
+
+if (normalizedPath.includes('..') || normalizedPath.startsWith('/')) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Invalid file path'
+  }) + '\n')
+  process.exit(1)
+}
+
+const targetDir = path.resolve(staticDir, normalizedPath)
+
+if (!targetDir.startsWith(staticDir)) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Path must be within static directory'
+  }) + '\n')
+  process.exit(1)
+}
+
+if (!fs.existsSync(targetDir)) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Directory does not exist'
+  }) + '\n')
+  process.exit(1)
+}
+
+const stats = fs.statSync(targetDir)
+if (!stats.isDirectory()) {
+  process.stdout.write(JSON.stringify({
+    success: false,
+    error: 'Path is not a directory'
+  }) + '\n')
+  process.exit(1)
+}
+
+const entries = fs.readdirSync(targetDir, { withFileTypes: true })
+const folderNames = []
+
+for (const entry of entries) {
+  if (entry.isDirectory()) {
+    folderNames.push(entry.name)
+  }
+}
+
+process.stdout.write(JSON.stringify({
+  success: true,
+  data: folderNames
+}) + '\n')
+process.exit(0)

+ 3 - 4
src/page/device/device.js

@@ -4,14 +4,13 @@ import alertView from '../public/alert-view/alert-view.js'
 
 // 设备管理类,所有方法都可以通过 this. 访问属性
 class DeviceClass {
-    constructor(setDeviceList) {
+    constructor() {}
+
+    async init(setDeviceList) {
         this.setDeviceList = setDeviceList
         this.count_ip_x = 0
         this.count_ip_y = 0
-    }
 
-    async init() {
- 
         let readResult = await window.electronAPI.runNodejsScript('json-parser', 'read', 'device_list.json')
 
         if (readResult.stdout === '') {

+ 7 - 4
src/page/device/device.jsx

@@ -1,4 +1,4 @@
-import React, { useRef, useState } from 'react'
+import React, { useState, useRef, useEffect } from 'react'
 import './device.scss'
 import UpdateBtn from './update-btn/update-btn.jsx'
 import ConnectItem from './connect-item/connect-item.jsx'
@@ -15,9 +15,12 @@ function Device({ show }) {
     return null
   }
 
-  if (!deviceClass.current) {
-    deviceClass.current = new DeviceClass(setDeviceList)
-  }
+  useEffect(() => {
+    if (!deviceClass.current) {
+      deviceClass.current = new DeviceClass()
+      deviceClass.current.init(setDeviceList);
+    }
+  }, [])
 
   return (
     <>

+ 0 - 0
src/page/process/process-item/process-item.css


+ 18 - 0
src/page/process/process-item/process-item.js

@@ -0,0 +1,18 @@
+class ProcessItemClass {
+  constructor() {
+  }
+
+  async init(processInfo) {
+    console.log(processInfo)
+  }
+
+  start() {
+
+  }
+
+  delete() {
+    
+  }
+}
+
+export { ProcessItemClass }

+ 29 - 0
src/page/process/process-item/process-item.jsx

@@ -0,0 +1,29 @@
+import React, { useRef, useEffect } from 'react'
+import './process-item.scss'
+import { ProcessItemClass } from './process-item.js'
+
+function ProcessItem({ processInfo, onRemove }) {
+  const processItemClass = useRef(null)
+
+  useEffect(() => {
+    if (!processItemClass.current) {
+      processItemClass.current = new ProcessItemClass()
+      processItemClass.current.init(processInfo)
+    }
+  }, [])
+
+  return (
+    <div className="process-item-container">
+      <div className="process-item-content-container">
+        <div className="process-item-title">{processInfo.name}</div>
+        <div className="process-item-content">{processInfo.description}</div>
+      </div>
+      <div className="btn-area-container">
+        <div className="start-btn" onClick={() => processItemClass.current?.start()}>Start</div>
+        <div className="delete-btn" onClick={() => processItemClass.current?.delete()}>delete</div>
+      </div>
+    </div>
+  )
+}
+
+export default ProcessItem

+ 98 - 0
src/page/process/process-item/process-item.scss

@@ -0,0 +1,98 @@
+@use '../../public/style/style.scss' as *;
+
+$font-size-scale: 1.5;  // 字体缩放系数,调整此值可改变字体大小(数值越大字体越大,越小字体越小)
+
+.process-item-container {
+    width: 80%;
+    height: 20%;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+
+    box-sizing: border-box;
+    padding: 0;
+    margin: 5%;
+
+    border-radius: 10px;
+    /* 计算过程:
+    应为process的宽高为20vw × 100vh 
+    process-item-container: 90% × 10% of process-container
+    宽度:90% × 20vw = 18vw
+    高度:10% × 100vh = 10vh
+    */
+   font-size: min(calc(18vw * 0.06 * $font-size-scale), calc(10vh * 0.12 * $font-size-scale));
+
+   user-select: none;
+   border: 1px solid rgb(0, 0, 0);
+}
+
+.process-item-content-container {
+    width: 70%;
+    height: 100%;
+
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    margin: 1%;
+
+    .process-item-title {
+        @include flex-center;
+        width: 100%;
+        height: 50%;
+    
+        font-size: 1.0em;
+        font-weight: bold;
+        word-break: break-word;
+    }
+    .process-item-content {
+        display: flex;
+        align-items: flex-start;
+        justify-content: center;
+
+        width: 100%;
+        height: 50%;   
+        
+        font-size: 1.0em;
+        word-break: break-word;
+    }
+}
+
+.btn-area-container {
+    width: 30%;
+    height: 80%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: space-around;
+
+    margin: 1%;
+
+    
+    .start-btn{
+        @include highlight-btn(#004ef6);
+        @include flex-center;
+    
+        width: 100%;
+        height: 30%;
+    
+        border-radius: 10px;
+       
+        font-size: 1.2em;
+    }
+
+    .delete-btn {
+        @include highlight-btn(#f60000);
+        @include flex-center;
+
+        width: 100%;
+        height: 50%;
+
+        border-radius: 10px;
+   
+        font-size: 1.2em;
+    }
+
+}

+ 28 - 0
src/page/process/process.js

@@ -0,0 +1,28 @@
+class ProcessClass {
+  constructor() {}
+
+  async init(setProcessItemList) {
+    this.setProcessItemList = setProcessItemList;
+    const result = await window.electronAPI.runNodejsScript('directory-parser', 'read', 'process')
+    const directoryData = JSON.parse(result.stdout)
+    const forders = directoryData.forders
+
+    const processItemList = []
+
+    for (const folderName of forders) {
+      const directoryPath = `process/${folderName}/process.json`
+      const jsonResult = await window.electronAPI.runNodejsScript('json-parser', 'read', directoryPath)
+      const jsonResponse = JSON.parse(jsonResult.stdout)
+      const jsonData = jsonResponse.data
+
+      processItemList.push({
+        name: jsonData.name,
+        description: jsonData.description
+      })
+    }
+
+    this.setProcessItemList(processItemList)
+  }
+}
+
+export { ProcessClass }

+ 20 - 4
src/page/process/process.jsx

@@ -1,16 +1,32 @@
-import React from 'react'
+import React, { useState, useRef, useEffect } from 'react'
 import './process.scss'
+import { ProcessClass } from './process.js'
+import ProcessItem from './process-item/process-item.jsx'
 
 function Process({ show }) {
+  const [processItemList, setProcessItemList] = useState([])
+  const processClassRef = useRef(null)
+
   if (!show) {
     return null
   }
 
+  useEffect(() => {
+    if (!processClassRef.current) {
+      processClassRef.current = new ProcessClass()
+      processClassRef.current.init(setProcessItemList)
+    }
+  }, [])
+  
   return (
     <div className="process-container">
-      <div className="process-content">
-        
-      </div>
+       {processItemList.map((item, index) => (
+            <ProcessItem 
+              key={index} 
+              processInfo={item}
+              onRemove={() => processClassRef.current?.removeProcessItem()}
+            />
+          ))}
     </div>
   )
 }

+ 11 - 19
src/page/process/process.scss

@@ -1,22 +1,14 @@
-// ========== 颜色配置 ==========
-$process-bg: #07fb40;
-
-// ========== 尺寸配置 ==========
-$full-size: 100%;
+.process-container {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: flex-start;
 
-// ========== Mixin 配置 ==========
-@mixin flex-center {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
+    box-sizing: border-box;
+    padding: 0;
+    margin: 0;
 
-// ========== 样式定义 ==========
-.process-container {
-  width: $full-size;
-  height: $full-size;
-  background-color: $process-bg;
-  @include flex-center;
-  margin: 0;
-  padding: 0;
+    // border: 1px solid red;
 }