ソースを参照

启动脚本修改完毕 scss都放在自己下面配置

yichael 4 週間 前
コミット
9198bf1f8c

+ 0 - 365
MODERN_CSS_EXAMPLES.md

@@ -1,365 +0,0 @@
-# 现代CSS强大功能示例 (2025)
-
-## 1. CSS 嵌套 (CSS Nesting) ⭐ 最推荐
-让CSS代码更清晰、更易维护
-
-```css
-/* 传统写法 */
-.device-container {
-  width: 100%;
-  height: 100%;
-}
-
-.device-container .device-update {
-  display: flex;
-}
-
-.device-container .device-update-title {
-  color: white;
-}
-
-/* 现代嵌套写法 */
-.device-container {
-  width: 100%;
-  height: 100%;
-  
-  .device-update {
-    display: flex;
-    
-    .device-update-title {
-      color: white;
-    }
-  }
-  
-  /* 使用 & 引用父选择器 */
-  &:hover {
-    background-color: #0550d0;
-  }
-  
-  /* 媒体查询嵌套 */
-  @media (max-width: 768px) {
-    flex-direction: column;
-  }
-}
-```
-
-## 2. CSS 变量 (Custom Properties) ⭐ 最实用
-统一管理颜色、尺寸等
-
-```css
-:root {
-  /* 颜色变量 */
-  --primary-color: #0769fb;
-  --primary-hover: #0550d0;
-  --text-color: #ffffff;
-  --bg-color: #f5f5f5;
-  
-  /* 尺寸变量 */
-  --spacing-sm: 8px;
-  --spacing-md: 16px;
-  --spacing-lg: 24px;
-  
-  /* 动画变量 */
-  --transition-fast: 0.2s ease;
-  --transition-normal: 0.3s ease;
-}
-
-.device-container {
-  background-color: var(--primary-color);
-  padding: var(--spacing-md);
-  transition: background-color var(--transition-normal);
-  
-  &:hover {
-    background-color: var(--primary-hover);
-  }
-}
-```
-
-## 3. 容器查询 (Container Queries) ⭐ 响应式新标准
-基于容器大小而非视口大小
-
-```css
-.device-container {
-  container-type: inline-size;
-  container-name: device;
-}
-
-/* 当容器宽度大于600px时 */
-@container device (min-width: 600px) {
-  .device-list {
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-  }
-}
-
-/* 当容器宽度小于600px时 */
-@container device (max-width: 599px) {
-  .device-list {
-    display: flex;
-    flex-direction: column;
-  }
-}
-```
-
-## 4. :has() 选择器 ⭐ 强大的父选择器
-根据子元素状态改变父元素样式
-
-```css
-/* 当包含特定子元素时 */
-.device-container:has(.device-update-title) {
-  border-top: 2px solid var(--primary-color);
-}
-
-/* 当子元素有焦点时 */
-.device-container:has(input:focus) {
-  box-shadow: 0 0 0 3px rgba(7, 105, 251, 0.3);
-}
-
-/* 当没有子元素时 */
-.device-list:has(:not(.device-item)) {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #999;
-}
-```
-
-## 5. @layer 层叠层
-更好地管理CSS优先级
-
-```css
-@layer base, components, utilities;
-
-@layer base {
-  .device-container {
-    margin: 0;
-    padding: 0;
-  }
-}
-
-@layer components {
-  .device-update {
-    display: flex;
-  }
-}
-
-@layer utilities {
-  .flex-center {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-}
-```
-
-## 6. aspect-ratio
-保持元素宽高比
-
-```css
-.device-item {
-  aspect-ratio: 16 / 9; /* 保持16:9比例 */
-  width: 100%;
-}
-
-.device-card {
-  aspect-ratio: 1; /* 正方形 */
-}
-```
-
-## 7. backdrop-filter
-毛玻璃效果
-
-```css
-.device-overlay {
-  backdrop-filter: blur(10px);
-  background-color: rgba(255, 255, 255, 0.1);
-}
-```
-
-## 8. clip-path 和 mask
-创建复杂形状
-
-```css
-.device-card {
-  clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
-}
-
-.device-icon {
-  mask: url('icon.svg') no-repeat center;
-  mask-size: contain;
-}
-```
-
-## 9. CSS Grid 高级特性
-
-```css
-.device-container {
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
-  gap: var(--spacing-md);
-  
-  /* 子网格 */
-  .device-list {
-    display: grid;
-    grid-template-columns: subgrid;
-  }
-}
-```
-
-## 10. 动画和过渡
-
-```css
-@keyframes slideIn {
-  from {
-    transform: translateX(-100%);
-    opacity: 0;
-  }
-  to {
-    transform: translateX(0);
-    opacity: 1;
-  }
-}
-
-.device-item {
-  animation: slideIn 0.3s ease-out;
-  
-  /* 或者使用 transition */
-  transition: transform var(--transition-normal),
-              opacity var(--transition-normal);
-  
-  &:hover {
-    transform: scale(1.05);
-  }
-}
-```
-
-## 11. 逻辑属性 (Logical Properties)
-更好的国际化支持
-
-```css
-.device-container {
-  /* 传统 */
-  margin-top: 10px;
-  margin-right: 20px;
-  
-  /* 现代逻辑属性 */
-  margin-block-start: 10px;  /* 顶部(垂直方向开始) */
-  margin-inline-end: 20px;   /* 右侧(水平方向结束) */
-}
-```
-
-## 12. color-mix() 颜色混合
-
-```css
-.device-button {
-  background-color: color-mix(in srgb, var(--primary-color) 80%, white);
-  
-  &:hover {
-    background-color: color-mix(in srgb, var(--primary-color) 100%, black 10%);
-  }
-}
-```
-
-## 实际应用示例:优化你的 device.css
-
-```css
-/* 使用CSS变量和嵌套优化后的代码 */
-:root {
-  --device-primary: #0769fb;
-  --device-primary-hover: #0550d0;
-  --device-spacing: 16px;
-  --device-transition: 0.3s ease;
-}
-
-.device-container {
-  width: 100%;
-  height: 100%;
-  background-color: var(--device-primary);
-  display: flex;
-  flex-direction: column;
-  margin: 0;
-  padding: 0;
-  transition: background-color var(--device-transition);
-  
-  &:hover {
-    background-color: var(--device-primary-hover);
-  }
-  
-  .device-update {
-    width: 100%;
-    height: auto;
-    min-height: 60px;
-    background-color: transparent;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: var(--device-spacing);
-    gap: var(--device-spacing);
-    
-    .device-update-title {
-      font-size: 1.2rem;
-      font-weight: 600;
-      color: white;
-      flex: 1;
-    }
-    
-    .device-update-btn {
-      padding: 8px 16px;
-      background-color: rgba(255, 255, 255, 0.2);
-      border-radius: 4px;
-      cursor: pointer;
-      transition: all var(--device-transition);
-      
-      &:hover {
-        background-color: rgba(255, 255, 255, 0.3);
-        transform: scale(1.05);
-      }
-    }
-  }
-  
-  .device-list {
-    flex: 1;
-    overflow-y: auto;
-    padding: var(--device-spacing);
-    
-    /* 容器查询 */
-    container-type: inline-size;
-    
-    @container (min-width: 600px) {
-      display: grid;
-      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
-      gap: var(--device-spacing);
-    }
-  }
-  
-  .device-add {
-    width: 100%;
-    min-height: 50px;
-    background-color: rgba(255, 255, 255, 0.1);
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    transition: all var(--device-transition);
-    
-    &:hover {
-      background-color: rgba(255, 255, 255, 0.2);
-    }
-  }
-}
-```
-
-## 浏览器支持情况
-
-- ✅ CSS 嵌套: Chrome 112+, Firefox 117+, Safari 16.5+
-- ✅ 容器查询: Chrome 105+, Firefox 110+, Safari 16.0+
-- ✅ :has(): Chrome 105+, Firefox 121+, Safari 15.4+
-- ✅ CSS 变量: 所有现代浏览器
-- ✅ @layer: Chrome 99+, Firefox 97+, Safari 15.4+
-- ✅ aspect-ratio: Chrome 88+, Firefox 89+, Safari 15.0+
-
-## 推荐学习资源
-
-1. MDN Web Docs: https://developer.mozilla.org/en-US/docs/Web/CSS
-2. CSS-Tricks: https://css-tricks.com/
-3. web.dev Learn CSS: https://web.dev/learn/css/

+ 12 - 0
bat-tool/git-push.bat

@@ -29,6 +29,18 @@ if "%CONFIG_BRANCH%"=="" (
     set MAIN_BRANCH=%CONFIG_BRANCH%
     set MAIN_BRANCH=%CONFIG_BRANCH%
 )
 )
 
 
+REM Show files to be pushed
+echo.
+echo Files to be pushed:
+echo ========================================
+git diff --name-status origin/%MAIN_BRANCH%..HEAD 2>nul
+if errorlevel 1 (
+    REM If remote branch doesn't exist, show all files
+    git log --name-status --oneline HEAD 2>nul | findstr /V "^[a-f0-9]"
+)
+echo ========================================
+echo.
+
 REM Push to main branch
 REM Push to main branch
 git push origin %MAIN_BRANCH%
 git push origin %MAIN_BRANCH%
 
 

+ 1 - 4
configs/dependencies.txt

@@ -15,7 +15,6 @@
 @babel/parser==7.28.6
 @babel/parser==7.28.6
 @babel/plugin-transform-react-jsx-self==7.27.1
 @babel/plugin-transform-react-jsx-self==7.27.1
 @babel/plugin-transform-react-jsx-source==7.27.1
 @babel/plugin-transform-react-jsx-source==7.27.1
-@babel/runtime==7.28.6
 @babel/template==7.28.6
 @babel/template==7.28.6
 @babel/traverse==7.28.6
 @babel/traverse==7.28.6
 @babel/types==7.28.6
 @babel/types==7.28.6
@@ -117,7 +116,7 @@ commander==5.1.0
 compare-version==0.1.2
 compare-version==0.1.2
 compress-commons==4.1.2
 compress-commons==4.1.2
 concat-map==0.0.1
 concat-map==0.0.1
-concurrently==8.2.2
+concurrently==9.2.1
 config-file-ts==0.2.6
 config-file-ts==0.2.6
 convert-source-map==2.0.0
 convert-source-map==2.0.0
 core-util-is==1.0.2
 core-util-is==1.0.2
@@ -127,7 +126,6 @@ cross-spawn==7.0.6
 css-color-keywords==1.0.0
 css-color-keywords==1.0.0
 css-to-react-native==3.2.0
 css-to-react-native==3.2.0
 csstype==3.2.3
 csstype==3.2.3
-date-fns==2.30.0
 debug==4.4.3
 debug==4.4.3
 decompress-response==6.0.0
 decompress-response==6.0.0
 defer-to-connect==2.0.1
 defer-to-connect==2.0.1
@@ -300,7 +298,6 @@ simple-update-notifier==2.0.0
 source-map-js==1.2.1
 source-map-js==1.2.1
 source-map-support==0.5.21
 source-map-support==0.5.21
 source-map==0.6.1
 source-map==0.6.1
-spawn-command==0.0.2
 sprintf-js==1.1.3
 sprintf-js==1.1.3
 stat-mode==1.0.0
 stat-mode==1.0.0
 string-width==4.2.3
 string-width==4.2.3

+ 118 - 83
configs/nodejs-dependencies-install.js

@@ -58,18 +58,74 @@ if (packageJson.devDependencies) {
     });
     });
 }
 }
 
 
-// 检查缺失的依赖
+// 快速获取已安装的包列表(直接从 node_modules 文件夹读取)
+function getInstalledPackagesFromFilesystem() {
+    const installedPackages = new Set();
+    
+    if (!fs.existsSync(nodeModulesPath)) {
+        return installedPackages;
+    }
+    
+    try {
+        const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true });
+        
+        for (const entry of entries) {
+            if (entry.isDirectory()) {
+                const packageName = entry.name;
+                
+                // 跳过特殊目录
+                if (packageName.startsWith('.') || packageName === 'node_modules') {
+                    continue;
+                }
+                
+                // 检查是否是有效的包(有 package.json)
+                const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
+                if (fs.existsSync(packageJsonPath)) {
+                    installedPackages.add(packageName.toLowerCase());
+                }
+                
+                // 处理 scoped 包(如 @babel/core)
+                if (packageName.startsWith('@')) {
+                    try {
+                        const scopedPath = path.join(nodeModulesPath, packageName);
+                        const scopedEntries = fs.readdirSync(scopedPath, { withFileTypes: true });
+                        for (const scopedEntry of scopedEntries) {
+                            if (scopedEntry.isDirectory()) {
+                                const scopedPackageName = `${packageName}/${scopedEntry.name}`;
+                                const scopedPackageJsonPath = path.join(scopedPath, scopedEntry.name, 'package.json');
+                                if (fs.existsSync(scopedPackageJsonPath)) {
+                                    installedPackages.add(scopedPackageName.toLowerCase());
+                                }
+                            }
+                        }
+                    } catch (error) {
+                        // 忽略错误
+                    }
+                }
+            }
+        }
+    } catch (error) {
+        // 忽略错误
+    }
+    
+    return installedPackages;
+}
+
+// 快速检查缺失的依赖(使用文件系统)
 const missingDependencies = [];
 const missingDependencies = [];
 let installedCount = 0;
 let installedCount = 0;
 let missingCount = 0;
 let missingCount = 0;
 
 
+// 一次性获取所有已安装的包(只检查一次文件系统)
+const installedPackagesSet = getInstalledPackagesFromFilesystem();
+
 const depNames = Object.keys(allDependencies).sort();
 const depNames = Object.keys(allDependencies).sort();
 
 
 for (const depName of depNames) {
 for (const depName of depNames) {
-    const packagePath = path.join(nodeModulesPath, depName);
-    const packageJsonPath = path.join(packagePath, 'package.json');
+    const depNameLower = depName.toLowerCase();
     
     
-    if (fs.existsSync(packageJsonPath)) {
+    // 快速检查(使用已获取的集合)
+    if (installedPackagesSet.has(depNameLower)) {
         installedCount++;
         installedCount++;
     } else {
     } else {
         missingDependencies.push(depName);
         missingDependencies.push(depName);
@@ -105,100 +161,79 @@ if (missingCount > 0) {
     log(`[OK] All dependencies are installed (${Object.keys(allDependencies).length} packages)`, 'green');
     log(`[OK] All dependencies are installed (${Object.keys(allDependencies).length} packages)`, 'green');
 }
 }
 
 
-// 同步所有已安装的依赖到 dependencies.txt
-log('\nSyncing all installed dependencies to configs/dependencies.txt...', 'cyan');
-
-const syncResult = [];
-
-if (fs.existsSync(nodeModulesPath)) {
-    // 遍历 node_modules 目录,查找所有 package.json 文件
-    function findPackageJsonFiles(dir) {
-        const files = [];
-        try {
-            const entries = fs.readdirSync(dir, { withFileTypes: true });
-            
-            for (const entry of entries) {
-                const fullPath = path.join(dir, entry.name);
-                
-                if (entry.isDirectory()) {
-                    // 跳过 .bin 目录和其他特殊目录
-                    if (entry.name.startsWith('.') || entry.name === 'node_modules') {
-                        continue;
-                    }
-                    // 递归查找
-                    files.push(...findPackageJsonFiles(fullPath));
-                } else if (entry.name === 'package.json') {
-                    files.push(fullPath);
-                }
-            }
-        } catch (error) {
-            // 忽略权限错误等
-        }
-        return files;
-    }
-    
-    const packageJsonFiles = findPackageJsonFiles(nodeModulesPath);
+// 快速同步所有已安装的依赖到 dependencies.txt(只读取根级包,静默执行)
+function syncInstalledPackagesToFile() {
+    const syncResult = [];
     
     
-    for (const file of packageJsonFiles) {
-        try {
-            const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));
-            if (pkg.name && pkg.version) {
-                syncResult.push(`${pkg.name}==${pkg.version}`);
-            }
-        } catch (error) {
-            // 忽略解析错误
-        }
+    if (!fs.existsSync(nodeModulesPath)) {
+        return syncResult;
     }
     }
     
     
-    // 如果遍历结果为空,使用 npm list 作为后备方案
-    if (syncResult.length === 0) {
-        try {
-            const npmListOutput = execSync('npm list --depth=0 --json', { 
-                encoding: 'utf-8',
-                cwd: projectRoot,
-                stdio: ['ignore', 'pipe', 'ignore']
-            });
-            const npmList = JSON.parse(npmListOutput);
-            if (npmList.dependencies) {
-                Object.keys(npmList.dependencies).forEach(depName => {
-                    const dep = npmList.dependencies[depName];
-                    if (dep && dep.version) {
-                        syncResult.push(`${depName}==${dep.version}`);
-                    }
-                });
-            }
-        } catch (error) {
-            // 忽略错误
-        }
-    }
-} else {
-    // 如果 node_modules 不存在,使用 npm list
     try {
     try {
-        const npmListOutput = execSync('npm list --depth=0 --json', { 
-            encoding: 'utf-8',
-            cwd: projectRoot,
-            stdio: ['ignore', 'pipe', 'ignore']
-        });
-        const npmList = JSON.parse(npmListOutput);
-        if (npmList.dependencies) {
-            Object.keys(npmList.dependencies).forEach(depName => {
-                const dep = npmList.dependencies[depName];
-                if (dep && dep.version) {
-                    syncResult.push(`${depName}==${dep.version}`);
+        const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true });
+        
+        for (const entry of entries) {
+            if (entry.isDirectory()) {
+                const packageName = entry.name;
+                
+                // 跳过特殊目录
+                if (packageName.startsWith('.') || packageName === 'node_modules') {
+                    continue;
+                }
+                
+                // 处理普通包
+                const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
+                if (fs.existsSync(packageJsonPath)) {
+                    try {
+                        const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
+                        if (pkg.name && pkg.version) {
+                            syncResult.push(`${pkg.name}==${pkg.version}`);
+                        }
+                    } catch (error) {
+                        // 忽略解析错误
+                    }
+                }
+                
+                // 处理 scoped 包(如 @babel/core)
+                if (packageName.startsWith('@')) {
+                    try {
+                        const scopedPath = path.join(nodeModulesPath, packageName);
+                        const scopedEntries = fs.readdirSync(scopedPath, { withFileTypes: true });
+                        for (const scopedEntry of scopedEntries) {
+                            if (scopedEntry.isDirectory()) {
+                                const scopedPackageJsonPath = path.join(scopedPath, scopedEntry.name, 'package.json');
+                                if (fs.existsSync(scopedPackageJsonPath)) {
+                                    try {
+                                        const pkg = JSON.parse(fs.readFileSync(scopedPackageJsonPath, 'utf-8'));
+                                        if (pkg.name && pkg.version) {
+                                            syncResult.push(`${pkg.name}==${pkg.version}`);
+                                        }
+                                    } catch (error) {
+                                        // 忽略解析错误
+                                    }
+                                }
+                            }
+                        }
+                    } catch (error) {
+                        // 忽略错误
+                    }
                 }
                 }
-            });
+            }
         }
         }
     } catch (error) {
     } catch (error) {
         // 忽略错误
         // 忽略错误
     }
     }
+    
+    return syncResult;
 }
 }
 
 
+// 同步所有已安装的依赖到 dependencies.txt(快速方法,静默执行)
+const syncResult = syncInstalledPackagesToFile();
+
 // 去重并排序
 // 去重并排序
 const uniqueResult = [...new Set(syncResult)].sort();
 const uniqueResult = [...new Set(syncResult)].sort();
 
 
 // 写入文件(UTF-8 编码)
 // 写入文件(UTF-8 编码)
 fs.writeFileSync(dependenciesFile, uniqueResult.join('\n') + '\n', 'utf-8');
 fs.writeFileSync(dependenciesFile, uniqueResult.join('\n') + '\n', 'utf-8');
 
 
-log(`[OK] Dependencies synced to ${dependenciesFile} (${uniqueResult.length} packages)`, 'green');
-
 process.exit(0);
 process.exit(0);

+ 30 - 0
configs/update-nodejs-dependencies.bat

@@ -0,0 +1,30 @@
+@echo off
+chcp 65001 >nul
+title Update Node.js Dependencies List
+
+cd /d "%~dp0\.."
+
+REM Check if node_modules exists
+if not exist "node_modules" (
+    echo [ERROR] node_modules not found
+    echo Please run npm install first to install dependencies.
+    echo.
+    pause
+    exit /b 1
+)
+
+REM Run Node.js script to compare and update dependencies.txt
+node "%~dp0update-nodejs-dependencies.js"
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] Update failed
+    echo.
+    pause
+    exit /b 1
+) else (
+    echo.
+    echo [OK] Update completed
+    echo.
+    pause
+)

+ 242 - 0
configs/update-nodejs-dependencies.js

@@ -0,0 +1,242 @@
+#!/usr/bin/env node
+/**
+ * Node.js 依赖列表更新脚本
+ * 功能:对比 node_modules 中已安装的包和 dependencies.txt,如果不一致则更新 dependencies.txt
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// 获取脚本所在目录和项目根目录
+const scriptDir = __dirname;
+const projectRoot = path.dirname(scriptDir);
+const dependenciesFile = path.join(scriptDir, 'dependencies.txt');
+const nodeModulesPath = path.join(projectRoot, 'node_modules');
+
+// 颜色输出函数
+const colors = {
+    reset: '\x1b[0m',
+    red: '\x1b[31m',
+    green: '\x1b[32m',
+    yellow: '\x1b[33m',
+    cyan: '\x1b[36m',
+    white: '\x1b[37m'
+};
+
+function log(message, color = 'reset') {
+    console.log(`${colors[color]}${message}${colors.reset}`);
+}
+
+/**
+ * 获取 node_modules 中已安装的包列表(包含版本号)
+ */
+function getInstalledPackages() {
+    const installedPackages = {};
+    
+    if (!fs.existsSync(nodeModulesPath)) {
+        log('[ERROR] node_modules not found', 'red');
+        return null;
+    }
+    
+    try {
+        const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true });
+        
+        for (const entry of entries) {
+            if (entry.isDirectory()) {
+                const packageName = entry.name;
+                
+                // 跳过特殊目录
+                if (packageName.startsWith('.') || packageName === 'node_modules') {
+                    continue;
+                }
+                
+                // 处理普通包
+                const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
+                if (fs.existsSync(packageJsonPath)) {
+                    try {
+                        const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
+                        if (pkg.name && pkg.version) {
+                            installedPackages[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`;
+                        }
+                    } catch (error) {
+                        // 忽略解析错误
+                    }
+                }
+                
+                // 处理 scoped 包(如 @babel/core)
+                if (packageName.startsWith('@')) {
+                    try {
+                        const scopedPath = path.join(nodeModulesPath, packageName);
+                        const scopedEntries = fs.readdirSync(scopedPath, { withFileTypes: true });
+                        for (const scopedEntry of scopedEntries) {
+                            if (scopedEntry.isDirectory()) {
+                                const scopedPackageJsonPath = path.join(scopedPath, scopedEntry.name, 'package.json');
+                                if (fs.existsSync(scopedPackageJsonPath)) {
+                                    try {
+                                        const pkg = JSON.parse(fs.readFileSync(scopedPackageJsonPath, 'utf-8'));
+                                        if (pkg.name && pkg.version) {
+                                            installedPackages[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`;
+                                        }
+                                    } catch (error) {
+                                        // 忽略解析错误
+                                    }
+                                }
+                            }
+                        }
+                    } catch (error) {
+                        // 忽略错误
+                    }
+                }
+            }
+        }
+    } catch (error) {
+        log(`[ERROR] Failed to read node_modules: ${error.message}`, 'red');
+        return null;
+    }
+    
+    return installedPackages;
+}
+
+/**
+ * 读取 dependencies.txt 中的包列表
+ */
+function readDependenciesFile() {
+    if (!fs.existsSync(dependenciesFile)) {
+        log(`[WARN] ${dependenciesFile} not found, will create new one`, 'yellow');
+        return {};
+    }
+    
+    const packages = {};
+    try {
+        const content = fs.readFileSync(dependenciesFile, 'utf-8');
+        const lines = content.split('\n');
+        
+        for (const line of lines) {
+            const trimmed = line.trim();
+            if (trimmed && !trimmed.startsWith('#')) {
+                // 提取包名(支持 ==, >=, <=, >, <, ~= 等版本操作符)
+                if (trimmed.includes('==')) {
+                    const parts = trimmed.split('==', 2);
+                    packages[parts[0].trim().toLowerCase()] = trimmed;
+                } else {
+                    // 如果没有版本号,使用整行
+                    const pkgName = trimmed.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].trim();
+                    packages[pkgName.toLowerCase()] = trimmed;
+                }
+            }
+        }
+    } catch (error) {
+        log(`[ERROR] Failed to read dependencies.txt: ${error.message}`, 'red');
+        return {};
+    }
+    
+    return packages;
+}
+
+/**
+ * 对比并更新 dependencies.txt
+ */
+function compareAndUpdate() {
+    log('Comparing installed packages with dependencies.txt...', 'cyan');
+    log('='.repeat(60), 'cyan');
+    
+    // 获取已安装的包
+    const installedPackages = getInstalledPackages();
+    if (installedPackages === null) {
+        process.exit(1);
+    }
+    
+    // 读取 dependencies.txt 中的包
+    const filePackages = readDependenciesFile();
+    
+    // 对比差异
+    const installedSet = new Set(Object.keys(installedPackages));
+    const fileSet = new Set(Object.keys(filePackages));
+    
+    const addedPackages = [];
+    const removedPackages = [];
+    const changedPackages = [];
+    
+    // 检查新增的包
+    for (const pkgName of installedSet) {
+        if (!fileSet.has(pkgName)) {
+            addedPackages.push(pkgName);
+        }
+    }
+    
+    // 检查删除的包
+    for (const pkgName of fileSet) {
+        if (!installedSet.has(pkgName)) {
+            removedPackages.push(pkgName);
+        }
+    }
+    
+    // 检查版本变化
+    for (const pkgName of installedSet) {
+        if (fileSet.has(pkgName)) {
+            if (installedPackages[pkgName] !== filePackages[pkgName]) {
+                changedPackages.push(pkgName);
+            }
+        }
+    }
+    
+    // 显示差异
+    if (addedPackages.length > 0) {
+        log(`\n[+] Added packages (${addedPackages.length}):`, 'green');
+        addedPackages.sort().forEach(pkg => {
+            log(`  + ${installedPackages[pkg]}`, 'green');
+        });
+    }
+    
+    if (removedPackages.length > 0) {
+        log(`\n[-] Removed packages (${removedPackages.length}):`, 'red');
+        removedPackages.sort().forEach(pkg => {
+            log(`  - ${filePackages[pkg]}`, 'red');
+        });
+    }
+    
+    if (changedPackages.length > 0) {
+        log(`\n[~] Changed packages (${changedPackages.length}):`, 'yellow');
+        changedPackages.sort().forEach(pkg => {
+            log(`  ~ ${filePackages[pkg]} -> ${installedPackages[pkg]}`, 'yellow');
+        });
+    }
+    
+    // 判断是否需要更新
+    if (addedPackages.length === 0 && removedPackages.length === 0 && changedPackages.length === 0) {
+        log(`\n[OK] dependencies.txt is up to date`, 'green');
+        log(`     Total packages: ${Object.keys(installedPackages).length}`, 'green');
+        return true;
+    }
+    
+    // 更新 dependencies.txt
+    log(`\nUpdating ${dependenciesFile}...`, 'cyan');
+    
+    // 获取所有已安装的包(按名称排序)
+    const allPackages = Object.values(installedPackages).sort();
+    
+    // 写入文件(UTF-8 编码)
+    try {
+        fs.writeFileSync(dependenciesFile, allPackages.join('\n') + '\n', 'utf-8');
+        log(`[OK] ${dependenciesFile} updated successfully`, 'green');
+        log(`     Total packages: ${allPackages.length}`, 'green');
+        return true;
+    } catch (error) {
+        log(`[ERROR] Failed to write dependencies.txt: ${error.message}`, 'red');
+        return false;
+    }
+}
+
+/**
+ * 主函数
+ */
+function main() {
+    if (!compareAndUpdate()) {
+        process.exit(1);
+    }
+    process.exit(0);
+}
+
+if (require.main === module) {
+    main();
+}

+ 124 - 0
configs/wait-for-vite.js

@@ -0,0 +1,124 @@
+#!/usr/bin/env node
+/**
+ * 等待 Vite 开发服务器启动的脚本
+ * 从配置文件读取端口,并等待服务器就绪
+ * 如果配置端口被占用,Vite 会自动尝试下一个端口,此脚本会检测实际使用的端口
+ */
+
+const path = require('path');
+const http = require('http');
+
+// 获取配置文件
+const configPath = path.join(__dirname, 'config.js');
+const config = require(configPath);
+
+const startPort = config.vite?.port || 5173;
+const viteHost = config.vite?.host || 'localhost';
+const maxWaitTime = 20; // 最多等待 20 秒
+const maxPortAttempts = 10; // 最多尝试 10 个连续端口
+const checkInterval = 500; // 每 500ms 检查一次
+
+/**
+ * 检查指定端口是否有 Vite 服务器响应
+ * 不仅检查端口是否响应,还要确认是 Vite 服务器(通过检查响应头)
+ */
+function checkPort(port) {
+  return 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 的特征(如 vite/client 或 Vite),确认是 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 特征
+        // Vite 的 HTML 通常包含 vite/client 或类似的引用
+        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);
+    });
+  });
+}
+
+/**
+ * 查找 Vite 实际使用的端口
+ */
+async function findVitePort() {
+  // 从配置的端口开始,尝试最多 maxPortAttempts 个连续端口
+  for (let offset = 0; offset < maxPortAttempts; offset++) {
+    const port = startPort + offset;
+    const isReady = await checkPort(port);
+    if (isReady) {
+      return port;
+    }
+  }
+  return null;
+}
+
+async function waitForServer() {
+  console.log(`Waiting for Vite dev server (starting from port ${startPort})...`);
+  
+  const startTime = Date.now();
+  let lastFoundPort = null;
+  let consecutiveChecks = 0;
+  
+  while (Date.now() - startTime < maxWaitTime * 1000) {
+    const port = await findVitePort();
+    if (port) {
+      // 如果找到的端口和上次一样,连续检查几次确保稳定
+      if (port === lastFoundPort) {
+        consecutiveChecks++;
+        if (consecutiveChecks >= 3) {
+          // 连续3次检查到同一个端口,确认服务器稳定运行
+          console.log(`\n[OK] Vite dev server is ready at http://${viteHost}:${port}`);
+          process.exit(0);
+        }
+      } else {
+        // 端口变化了,重置计数器
+        lastFoundPort = port;
+        consecutiveChecks = 1;
+      }
+    } else {
+      // 没找到服务器,重置计数器
+      lastFoundPort = null;
+      consecutiveChecks = 0;
+    }
+    
+    // 显示进度
+    process.stdout.write('.');
+    await new Promise(resolve => setTimeout(resolve, checkInterval));
+  }
+  
+  console.log(`\n[ERROR] Vite dev server did not start within ${maxWaitTime} seconds`);
+  process.exit(1);
+}
+
+waitForServer();

+ 0 - 0
README.md → doc/README.md


+ 111 - 3
electron/main.js

@@ -1,9 +1,111 @@
 const { app, BrowserWindow } = require('electron')
 const { app, BrowserWindow } = require('electron')
 const path = require('path')
 const path = require('path')
+const http = require('http')
+const os = require('os')
+const fs = require('fs')
 const config = require('../configs/config.js')
 const config = require('../configs/config.js')
 const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
 const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
 
 
-function createWindow() {
+// 修复缓存权限问题:设置用户数据目录到有权限的位置
+// 必须在 app.whenReady() 之前调用
+if (process.platform === 'win32') {
+  try {
+    // 设置缓存目录到用户临时目录,避免权限问题
+    const userDataPath = path.join(os.tmpdir(), 'electron-react-vite-app')
+    
+    // 确保目录存在
+    if (!fs.existsSync(userDataPath)) {
+      fs.mkdirSync(userDataPath, { recursive: true })
+    }
+    
+    // 创建缓存子目录
+    const cacheDir = path.join(userDataPath, 'cache')
+    const gpuCacheDir = path.join(userDataPath, 'gpu-cache')
+    if (!fs.existsSync(cacheDir)) {
+      fs.mkdirSync(cacheDir, { recursive: true })
+    }
+    if (!fs.existsSync(gpuCacheDir)) {
+      fs.mkdirSync(gpuCacheDir, { recursive: true })
+    }
+    
+    // 设置用户数据路径(必须在 app.whenReady() 之前)
+    app.setPath('userData', userDataPath)
+    
+    // 设置缓存目录到有权限的位置
+    app.commandLine.appendSwitch('disk-cache-dir', cacheDir)
+    app.commandLine.appendSwitch('gpu-disk-cache-dir', gpuCacheDir)
+    
+    console.log(`[OK] Cache directories set to: ${userDataPath}`)
+  } catch (error) {
+    console.warn('[WARN] Failed to set cache directories:', error.message)
+    // 如果设置失败,尝试禁用 GPU 缓存作为备选方案
+    app.commandLine.appendSwitch('disable-gpu-sandbox')
+  }
+}
+
+/**
+ * 检测 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
+}
+
+async function createWindow() {
   const mainWindow = new BrowserWindow({
   const mainWindow = new BrowserWindow({
     width: config.window.width,
     width: config.window.width,
     height: config.window.height,
     height: config.window.height,
@@ -16,8 +118,14 @@ function createWindow() {
 
 
   if (isDev) {
   if (isDev) {
     // 从配置文件读取 Vite 开发服务器端口
     // 从配置文件读取 Vite 开发服务器端口
-    const vitePort = config.vite?.port || 5173
-    mainWindow.loadURL(`http://${config.vite?.host || 'localhost'}:${vitePort}`)
+    const configPort = config.vite?.port || 5173
+    // 检测实际使用的端口(如果配置端口被占用,Vite 会自动尝试下一个)
+    const vitePort = await findVitePort(configPort)
+    const viteHost = config.vite?.host || 'localhost'
+    
+    console.log(`Loading Vite dev server at http://${viteHost}:${vitePort}`)
+    mainWindow.loadURL(`http://${viteHost}:${vitePort}`)
+    
     // 根据配置文件决定是否打开调试侧边栏
     // 根据配置文件决定是否打开调试侧边栏
     if (config.devTools.enabled) {
     if (config.devTools.enabled) {
       mainWindow.webContents.openDevTools()
       mainWindow.webContents.openDevTools()

+ 2 - 2
package.json

@@ -8,7 +8,7 @@
     "build": "vite build",
     "build": "vite build",
     "preview": "vite preview",
     "preview": "vite preview",
     "electron": "electron .",
     "electron": "electron .",
-    "electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
+    "electron:dev": "concurrently \"npm run dev\" \"node configs/wait-for-vite.js && electron .\"",
     "electron:build": "npm run build && electron-builder"
     "electron:build": "npm run build && electron-builder"
   },
   },
   "dependencies": {
   "dependencies": {
@@ -19,7 +19,7 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@vitejs/plugin-react": "^5.1.2",
     "@vitejs/plugin-react": "^5.1.2",
-    "concurrently": "^8.2.2",
+    "concurrently": "^9.2.1",
     "electron": "^28.0.0",
     "electron": "^28.0.0",
     "electron-builder": "^24.9.1",
     "electron-builder": "^24.9.1",
     "sass": "^1.97.3",
     "sass": "^1.97.3",

+ 71 - 7
python/python-enviroment-install.py

@@ -78,14 +78,67 @@ def read_dependencies(source_file):
     return dependencies
     return dependencies
 
 
 
 
-def check_package_installed(package_name, venv_pip):
-    """检查包是否已安装"""
+def get_installed_packages_from_filesystem():
+    """直接从文件系统获取已安装的包列表(快速方法)"""
+    if platform.system() == "Windows":
+        site_packages = VENV_PATH / "Lib" / "site-packages"
+    else:
+        # Linux/Mac: 需要找到 site-packages 路径
+        import sysconfig
+        site_packages = Path(sysconfig.get_path('purelib', vars={'base': str(VENV_PATH)}))
+    
+    installed_packages = set()
+    
+    if site_packages.exists():
+        for item in site_packages.iterdir():
+            if item.is_dir():
+                pkg_name = item.name
+                
+                # 处理 .dist-info 和 .egg-info 文件夹(最准确的包名来源)
+                if pkg_name.endswith('.dist-info'):
+                    # 从 dist-info 文件夹名提取包名(格式:package-name-version.dist-info)
+                    parts = pkg_name.replace('.dist-info', '').rsplit('-', 1)
+                    if len(parts) >= 1:
+                        installed_packages.add(parts[0].lower().replace('_', '-'))
+                elif pkg_name.endswith('.egg-info'):
+                    # 从 egg-info 文件夹名提取包名
+                    parts = pkg_name.replace('.egg-info', '').rsplit('-', 1)
+                    if len(parts) >= 1:
+                        installed_packages.add(parts[0].lower().replace('_', '-'))
+                elif pkg_name not in ['__pycache__', 'dist-info', 'egg-info']:
+                    # 检查是否是 Python 包(有 __init__.py 或 .py 文件)
+                    if (item / "__init__.py").exists() or any(item.glob("*.py")):
+                        pkg_lower = pkg_name.lower()
+                        installed_packages.add(pkg_lower)
+                        # 添加下划线和连字符的变体
+                        installed_packages.add(pkg_lower.replace('_', '-'))
+                        installed_packages.add(pkg_lower.replace('-', '_'))
+        
+        # 特殊映射:opencv-python 安装后显示为 cv2
+        if 'cv2' in installed_packages:
+            installed_packages.add('opencv-python')
+            installed_packages.add('opencv-contrib-python')
+            installed_packages.add('opencv-python-headless')
+    
+    return installed_packages
+
+
+def check_package_installed(package_name, installed_packages_set=None):
+    """检查包是否已安装(使用文件系统快速检查)"""
     # 提取包名(支持 ==, >=, <=, >, <, ~= 等版本操作符)
     # 提取包名(支持 ==, >=, <=, >, <, ~= 等版本操作符)
     pkg_name = package_name.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
     pkg_name = package_name.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+    pkg_name_lower = pkg_name.lower()
+    
+    # 如果没有提供已安装包集合,则获取一次(避免重复调用)
+    if installed_packages_set is None:
+        installed_packages_set = get_installed_packages_from_filesystem()
     
     
-    cmd = f'"{venv_pip}" show {pkg_name}'
-    success, _, _ = run_command(cmd, check=False, capture_output=True)
-    return success
+    # 快速检查(使用已获取的集合)
+    return (
+        pkg_name_lower in installed_packages_set or
+        pkg_name_lower.replace('-', '_') in installed_packages_set or
+        pkg_name_lower.replace('_', '-') in installed_packages_set
+    )
 
 
 
 
 def install_packages(packages, source_file, venv_pip):
 def install_packages(packages, source_file, venv_pip):
@@ -158,11 +211,14 @@ def main():
         print("[OK] No dependencies specified")
         print("[OK] No dependencies specified")
         sys.exit(0)
         sys.exit(0)
     
     
-    # 检查缺失的依赖
+    # 快速检查缺失的依赖(使用文件系统)
     missing_packages = []
     missing_packages = []
     installed_count = 0
     installed_count = 0
     missing_count = 0
     missing_count = 0
     
     
+    # 一次性获取所有已安装的包(只检查一次文件系统)
+    installed_packages_set = get_installed_packages_from_filesystem()
+    
     for package in required_packages:
     for package in required_packages:
         package_line = package.strip()
         package_line = package.strip()
         if not package_line:
         if not package_line:
@@ -170,8 +226,16 @@ def main():
         
         
         # 提取包名
         # 提取包名
         package_name = package_line.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
         package_name = package_line.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+        pkg_name_lower = package_name.lower()
+        
+        # 快速检查(使用已获取的集合)
+        is_installed = (
+            pkg_name_lower in installed_packages_set or
+            pkg_name_lower.replace('-', '_') in installed_packages_set or
+            pkg_name_lower.replace('_', '-') in installed_packages_set
+        )
         
         
-        if check_package_installed(package_name, venv_pip):
+        if is_installed:
             installed_count += 1
             installed_count += 1
         else:
         else:
             missing_packages.append(package_line)
             missing_packages.append(package_line)

+ 30 - 0
python/update-enviroment-list.bat

@@ -0,0 +1,30 @@
+@echo off
+chcp 65001 >nul
+title Update Python Environment List
+
+cd /d "%~dp0"
+
+REM Check if virtual environment exists
+if not exist "env\Scripts\python.exe" (
+    echo [ERROR] Virtual environment not found at: env
+    echo Please run python-enviroment-install.py first to create the virtual environment.
+    echo.
+    pause
+    exit /b 1
+)
+
+REM Run Python script to compare and update environment.txt
+python "%~dp0update-environment-list.py"
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] Update failed
+    echo.
+    pause
+    exit /b 1
+) else (
+    echo.
+    echo [OK] Update completed
+    echo.
+    pause
+)

+ 174 - 0
python/update-environment-list.py

@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Python 环境列表更新脚本
+功能:对比虚拟环境中已安装的包和 environment.txt,如果不一致则更新 environment.txt
+"""
+
+import os
+import sys
+import subprocess
+import platform
+from pathlib import Path
+
+# 获取脚本所在目录
+SCRIPT_DIR = Path(__file__).parent.absolute()
+VENV_PATH = SCRIPT_DIR / "env"
+ENVIRONMENT_FILE = SCRIPT_DIR / "environment.txt"
+
+# 根据操作系统确定虚拟环境的 pip 路径
+if platform.system() == "Windows":
+    VENV_PIP = VENV_PATH / "Scripts" / "pip.exe"
+else:
+    VENV_PIP = VENV_PATH / "bin" / "pip"
+
+
+def run_command(cmd, check=True, capture_output=True):
+    """运行命令并返回结果"""
+    try:
+        result = subprocess.run(
+            cmd,
+            shell=True,
+            check=check,
+            capture_output=capture_output,
+            text=True,
+            encoding='utf-8'
+        )
+        return result.returncode == 0, result.stdout, result.stderr
+    except subprocess.CalledProcessError as e:
+        return False, e.stdout if hasattr(e, 'stdout') else "", str(e)
+
+
+def get_installed_packages():
+    """获取虚拟环境中已安装的包列表(使用 pip freeze)"""
+    if not VENV_PATH.exists():
+        print("[ERROR] Virtual environment not found")
+        return None
+    
+    cmd = f'"{VENV_PIP}" freeze'
+    success, output, error = run_command(cmd, check=False)
+    
+    if not success:
+        print(f"[ERROR] Failed to get installed packages: {error}")
+        return None
+    
+    # 解析输出,提取包名和版本
+    packages = {}
+    for line in output.strip().split('\n'):
+        line = line.strip()
+        if line and not line.startswith('#'):
+            # 格式:package==version 或 package>=version 等
+            if '==' in line:
+                parts = line.split('==', 1)
+                packages[parts[0].strip().lower()] = line.strip()
+            else:
+                # 如果没有版本号,使用整行
+                pkg_name = line.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+                packages[pkg_name.lower()] = line.strip()
+    
+    return packages
+
+
+def read_environment_file():
+    """读取 environment.txt 中的包列表"""
+    if not ENVIRONMENT_FILE.exists():
+        print(f"[WARN] {ENVIRONMENT_FILE} not found, will create new one")
+        return {}
+    
+    packages = {}
+    with open(ENVIRONMENT_FILE, 'r', encoding='utf-8') as f:
+        for line in f:
+            line = line.strip()
+            if line and not line.startswith('#'):
+                # 提取包名(支持各种版本操作符)
+                if '==' in line:
+                    parts = line.split('==', 1)
+                    packages[parts[0].strip().lower()] = line.strip()
+                else:
+                    pkg_name = line.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+                    packages[pkg_name.lower()] = line.strip()
+    
+    return packages
+
+
+def compare_and_update():
+    """对比并更新 environment.txt"""
+    print("Comparing installed packages with environment.txt...")
+    print("=" * 60)
+    
+    # 获取已安装的包
+    installed_packages = get_installed_packages()
+    if installed_packages is None:
+        sys.exit(1)
+    
+    # 读取 environment.txt 中的包
+    file_packages = read_environment_file()
+    
+    # 对比差异
+    installed_set = set(installed_packages.keys())
+    file_set = set(file_packages.keys())
+    
+    added_packages = installed_set - file_set
+    removed_packages = file_set - installed_set
+    changed_packages = []
+    
+    # 检查版本变化
+    for pkg_name in installed_set & file_set:
+        if installed_packages[pkg_name] != file_packages[pkg_name]:
+            changed_packages.append(pkg_name)
+    
+    # 显示差异
+    if added_packages:
+        print(f"\n[+] Added packages ({len(added_packages)}):")
+        for pkg in sorted(added_packages):
+            print(f"  + {installed_packages[pkg]}")
+    
+    if removed_packages:
+        print(f"\n[-] Removed packages ({len(removed_packages)}):")
+        for pkg in sorted(removed_packages):
+            print(f"  - {file_packages[pkg]}")
+    
+    if changed_packages:
+        print(f"\n[~] Changed packages ({len(changed_packages)}):")
+        for pkg in sorted(changed_packages):
+            print(f"  ~ {file_packages[pkg]} -> {installed_packages[pkg]}")
+    
+    # 判断是否需要更新
+    if not added_packages and not removed_packages and not changed_packages:
+        print("\n[OK] environment.txt is up to date")
+        print(f"     Total packages: {len(installed_packages)}")
+        return True
+    
+    # 更新 environment.txt
+    print(f"\nUpdating {ENVIRONMENT_FILE}...")
+    
+    # 使用 pip freeze 获取完整列表(包含所有依赖)
+    cmd = f'"{VENV_PIP}" freeze'
+    success, output, error = run_command(cmd, check=False)
+    
+    if not success:
+        print(f"[ERROR] Failed to get installed packages: {error}")
+        sys.exit(1)
+    
+    # 写入文件(使用 UTF-8 无 BOM 编码)
+    with open(ENVIRONMENT_FILE, 'w', encoding='utf-8', newline='\n') as f:
+        f.write(output)
+    
+    # 统计包数量
+    package_count = len([line for line in output.strip().split('\n') if line.strip()])
+    
+    print(f"[OK] {ENVIRONMENT_FILE} updated successfully")
+    print(f"     Total packages: {package_count}")
+    
+    return True
+
+
+def main():
+    """主函数"""
+    if not compare_and_update():
+        sys.exit(1)
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()

+ 13 - 1
src/page/ai-chat/ai-chat.scss

@@ -1,5 +1,17 @@
-@use '../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$ai-chat-bg: #e0e8f4;
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%;
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
 .ai-chat-container {
 .ai-chat-container {
   width: $full-size;
   width: $full-size;
   height: $full-size;
   height: $full-size;

+ 13 - 1
src/page/ai-chat/dialog/dialog.scss

@@ -1,5 +1,17 @@
-@use '../../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$primary-color: #0769fb;
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%;
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
 .dialog-container {
 .dialog-container {
   width: $full-size;
   width: $full-size;
   height: $full-size;
   height: $full-size;

+ 13 - 1
src/page/ai-chat/input/input.scss

@@ -1,5 +1,17 @@
-@use '../../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$primary-color: #0769fb;
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%;
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
 .input-container {
 .input-container {
   width: $full-size;
   width: $full-size;
   height: $full-size;
   height: $full-size;

+ 12 - 1
src/page/device/device.jsx

@@ -1,16 +1,27 @@
 import React from 'react'
 import React from 'react'
 import './device.scss'
 import './device.scss'
+import UpdateBtn from './update-btn.jsx'
 
 
 function Device({ show }) {
 function Device({ show }) {
   if (!show) {
   if (!show) {
     return null
     return null
   }
   }
 
 
+  const handleRefresh = () => {
+    console.log('Refresh clicked')
+    // Add your refresh logic here
+  }
+
   return (
   return (
     <div className="device-container">
     <div className="device-container">
       <div className="device-update">
       <div className="device-update">
         <div className="device-update-title">设备列表</div>
         <div className="device-update-title">设备列表</div>
-        <div className="device-update-btn"></div>
+        <div className="device-update-btn">
+          <UpdateBtn
+            onClick={handleRefresh}
+            title="Refresh device list"
+          />
+        </div>
       </div>
       </div>
       <div className="device-list">
       <div className="device-list">
 
 

+ 13 - 1
src/page/device/device.scss

@@ -1,5 +1,17 @@
-@use '../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$primary-color: #0769fb; // 主色调,可根据需要修改
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%; // 全尺寸
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
 .device-container {
 .device-container {
   width: $full-size;
   width: $full-size;
   height: $full-size;
   height: $full-size;

+ 70 - 0
src/page/device/update-btn.jsx

@@ -0,0 +1,70 @@
+import React from 'react'
+import DivBtn from '../public/div-btn/div-btn.jsx'
+
+/**
+ * Refresh Button Component
+ * @param {Object} props
+ * @param {Function} props.onClick - Click handler function
+ * @param {boolean} props.disabled - Disable button (default: false)
+ * @param {string} props.title - Tooltip text (default: 'Refresh')
+ * @param {string} props.className - Additional CSS classes
+ */
+function UpdateBtn({
+  onClick,
+  disabled = false,
+  title = 'Refresh',
+  className = ''
+}) {
+  // Default refresh icon
+  const refreshIcon = (
+    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M1 4V10H7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M23 20V14H17" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10M23 14L18.36 18.36A9 9 0 0 1 3.51 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+    </svg>
+  )
+
+  // Hover refresh icon (slightly bolder)
+  const refreshIconHover = (
+    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M1 4V10H7" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M23 20V14H17" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10M23 14L18.36 18.36A9 9 0 0 1 3.51 15" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
+    </svg>
+  )
+
+  // Pressed refresh icon (thicker)
+  const refreshIconPressed = (
+    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M1 4V10H7" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M23 20V14H17" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10M23 14L18.36 18.36A9 9 0 0 1 3.51 15" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
+    </svg>
+  )
+
+  // Disabled refresh icon (grayed out)
+  const refreshIconDisabled = (
+    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" opacity="0.5">
+      <path d="M1 4V10H7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M23 20V14H17" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+      <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10M23 14L18.36 18.36A9 9 0 0 1 3.51 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+    </svg>
+  )
+
+  return (
+    <DivBtn
+      icon={refreshIcon}
+      iconHover={refreshIconHover}
+      iconPressed={refreshIconPressed}
+      iconDisabled={refreshIconDisabled}
+      onClick={onClick}
+      disabled={disabled}
+      title={title}
+      className={className}
+      rotateOnHover={true}
+      rotateDegrees={180}
+    />
+  )
+}
+
+export default UpdateBtn

+ 1 - 2
src/page/home.scss

@@ -1,5 +1,4 @@
-@use '../styles/variables' as *;
-
+// ========== 样式定义 ==========
 .home-container {
 .home-container {
   width: 100vw;
   width: 100vw;
   height: 100vh;
   height: 100vh;

+ 13 - 1
src/page/process/process.scss

@@ -1,5 +1,17 @@
-@use '../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$process-bg: #07fb40;
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%;
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
 .process-container {
 .process-container {
   width: $full-size;
   width: $full-size;
   height: $full-size;
   height: $full-size;

+ 7 - 1
src/page/public/alert-view/alert-view.scss

@@ -1,5 +1,11 @@
-@use '../../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$white: #fff;
+$black: #000;
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%;
+
+// ========== 样式定义 ==========
 .alert-container {
 .alert-container {
   position: fixed;
   position: fixed;
   top: 0;
   top: 0;

+ 101 - 0
src/page/public/div-btn/div-btn.jsx

@@ -0,0 +1,101 @@
+import React, { useState } from 'react'
+import './div-btn.scss'
+
+/**
+ * Icon Button Component
+ * @param {Object} props
+ * @param {React.ReactNode} props.icon - Default icon (SVG or React component)
+ * @param {React.ReactNode} props.iconHover - Hover icon (optional)
+ * @param {React.ReactNode} props.iconPressed - Pressed icon (optional)
+ * @param {React.ReactNode} props.iconDisabled - Disabled icon (optional)
+ * @param {Function} props.onClick - Click handler function
+ * @param {string} props.className - Additional CSS classes
+ * @param {boolean} props.disabled - Disable button (default: false)
+ * @param {string} props.title - Tooltip text
+ * @param {boolean} props.rotateOnHover - Rotate icon on hover (default: false)
+ * @param {number} props.rotateDegrees - Degrees to rotate on hover (default: 180)
+ */
+function DivBtn({
+  icon,
+  iconHover,
+  iconPressed,
+  iconDisabled,
+  onClick,
+  className = '',
+  disabled = false,
+  title = '',
+  rotateOnHover = false,
+  rotateDegrees = 180
+}) {
+  const [isPressed, setIsPressed] = useState(false)
+  const [isHovered, setIsHovered] = useState(false)
+
+  const handleClick = (e) => {
+    if (!disabled && onClick) {
+      onClick(e)
+    }
+  }
+
+  const handleMouseDown = () => {
+    if (!disabled) {
+      setIsPressed(true)
+    }
+  }
+
+  const handleMouseUp = () => {
+    setIsPressed(false)
+  }
+
+  const handleMouseEnter = () => {
+    if (!disabled) {
+      setIsHovered(true)
+    }
+  }
+
+  const handleMouseLeave = () => {
+    setIsHovered(false)
+    setIsPressed(false)
+  }
+
+  return (
+    <div
+      className={`div-btn ${rotateOnHover ? 'div-btn--rotate' : ''} ${disabled ? 'div-btn--disabled' : ''} ${isPressed ? 'div-btn--pressed' : ''} ${className}`}
+      onClick={handleClick}
+      onMouseDown={handleMouseDown}
+      onMouseUp={handleMouseUp}
+      onMouseEnter={handleMouseEnter}
+      onMouseLeave={handleMouseLeave}
+      title={title}
+      role="button"
+      tabIndex={disabled ? -1 : 0}
+      onKeyDown={(e) => {
+        if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
+          e.preventDefault()
+          handleClick(e)
+        }
+      }}
+      style={rotateOnHover ? { '--rotate-degrees': `${rotateDegrees}deg` } : {}}
+    >
+      <div className="div-btn__icon div-btn__icon--default">
+        {icon}
+      </div>
+      {iconHover && (
+        <div className="div-btn__icon div-btn__icon--hover">
+          {iconHover}
+        </div>
+      )}
+      {iconPressed && (
+        <div className="div-btn__icon div-btn__icon--pressed">
+          {iconPressed}
+        </div>
+      )}
+      {iconDisabled && (
+        <div className="div-btn__icon div-btn__icon--disabled">
+          {iconDisabled}
+        </div>
+      )}
+    </div>
+  )
+}
+
+export default DivBtn

+ 116 - 0
src/page/public/div-btn/div-btn.scss

@@ -0,0 +1,116 @@
+// ========== 颜色配置 ==========
+$white: #fff;
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
+.div-btn {
+  @include flex-center;
+  cursor: pointer;
+  user-select: none;
+  transition: all 0.3s ease;
+  border: none;
+  background: transparent;
+  padding: 0.5em;
+  border-radius: 4px;
+  position: relative;
+
+  // Default size (medium)
+  .div-btn__icon {
+    width: 1.5em;
+    height: 1.5em;
+  }
+
+  &:hover:not(.div-btn--disabled) {
+    background-color: rgba(255, 255, 255, 0.1);
+    transform: scale(1.05);
+  }
+
+  &:active:not(.div-btn--disabled),
+  &--pressed:not(.div-btn--disabled) {
+    transform: scale(0.95);
+  }
+
+  &--disabled {
+    cursor: not-allowed;
+    opacity: 0.5;
+  }
+
+  &--rotate {
+    &:hover:not(.div-btn--disabled) {
+      transform: scale(1.05) rotate(var(--rotate-degrees, 180deg));
+    }
+
+    &:active:not(.div-btn--disabled),
+    &--pressed:not(.div-btn--disabled) {
+      transform: scale(0.95) rotate(360deg);
+    }
+  }
+
+  &__icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: $white;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    transition: opacity 0.2s ease;
+
+    &--default {
+      opacity: 1;
+    }
+
+    &--hover {
+      opacity: 0;
+    }
+
+    &--pressed {
+      opacity: 0;
+    }
+
+    &--disabled {
+      opacity: 0;
+    }
+  }
+
+  // Show hover icon on hover
+  &:hover:not(.div-btn--disabled):not(.div-btn--pressed) {
+    .div-btn__icon--default {
+      opacity: 0;
+    }
+    .div-btn__icon--hover {
+      opacity: 1;
+    }
+  }
+
+  // Show pressed icon when pressed
+  &:active:not(.div-btn--disabled),
+  &--pressed:not(.div-btn--disabled) {
+    .div-btn__icon--default,
+    .div-btn__icon--hover {
+      opacity: 0;
+    }
+    .div-btn__icon--pressed {
+      opacity: 1;
+    }
+  }
+
+  // Show disabled icon when disabled
+  &--disabled {
+    .div-btn__icon--default,
+    .div-btn__icon--hover,
+    .div-btn__icon--pressed {
+      opacity: 0;
+    }
+    .div-btn__icon--disabled {
+      opacity: 1;
+    }
+  }
+}

+ 13 - 1
src/page/screenshot/screenshot.scss

@@ -1,5 +1,17 @@
-@use '../../styles/variables' as *;
+// ========== 颜色配置 ==========
+$screenshot-bg: rgba(251, 7, 7, 0.9);
 
 
+// ========== 尺寸配置 ==========
+$full-size: 100%;
+
+// ========== Mixin 配置 ==========
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+// ========== 样式定义 ==========
 .screenshot-container {
 .screenshot-container {
   width: $full-size;
   width: $full-size;
   height: $full-size;
   height: $full-size;

+ 2 - 1
vite.config.mjs

@@ -20,7 +20,8 @@ export default defineConfig({
   server: {
   server: {
     host: viteHost,
     host: viteHost,
     port: vitePort,
     port: vitePort,
-    strictPort: false // 如果端口被占用,自动尝试下一个端口
+    strictPort: false, // 如果端口被占用,自动尝试下一个端口
+    open: false // 不自动打开浏览器,只用于 Electron 桌面应用
   },
   },
   css: {
   css: {
     preprocessorOptions: {
     preprocessorOptions: {