增加了前后端打包验证功能

This commit is contained in:
root 2025-08-15 10:12:38 +08:00
parent c9c2e6cd16
commit aaf6047b81
23 changed files with 15563 additions and 8163 deletions

10097
.gitignore vendored

File diff suppressed because it is too large Load Diff

View File

@ -85,7 +85,7 @@ exe = EXE(
a.zipfiles, a.zipfiles,
a.datas, a.datas,
[], [],
name='BodyBalanceBackend_Full', name='BodyBalanceBackend',
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,

View File

@ -113,7 +113,7 @@ exe = EXE(
a.zipfiles, a.zipfiles,
a.datas, a.datas,
[], [],
name='BodyBalanceBackend_Full', name='BodyBalanceBackend',
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
@ -171,7 +171,7 @@ echo - 管理界面: http://localhost:5000
echo. echo.
echo 按Ctrl+C停止服务 echo 按Ctrl+C停止服务
echo. echo.
"BodyBalanceBackend_Full.exe" "BodyBalanceBackend.exe"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
echo. echo.
echo 服务启动失败请检查错误信息 echo 服务启动失败请检查错误信息
@ -183,10 +183,10 @@ if %errorlevel% neq 0 (
if not os.path.exists(dist_dir): if not os.path.exists(dist_dir):
os.makedirs(dist_dir) os.makedirs(dist_dir)
with open(os.path.join(dist_dir, 'start_backend_full.bat'), 'w', encoding='utf-8') as f: with open(os.path.join(dist_dir, 'start_backend.bat'), 'w', encoding='utf-8') as f:
f.write(startup_script) f.write(startup_script)
print("✓ 创建启动脚本: dist/start_backend_full.bat") print("✓ 创建启动脚本: dist/start_backend.bat")
def create_directories(): def create_directories():
"""创建必要的目录结构""" """创建必要的目录结构"""
@ -215,30 +215,28 @@ def copy_dll_files():
# 查找所有DLL文件 # 查找所有DLL文件
dll_files = [] dll_files = []
# 从当前目录查找DLL文件 # 复制dll目录下的所有文件夹和文件到目标目录的dll目录下
for file in os.listdir('.'): source_dll_dir = 'dll'
if file.endswith('.dll'): if not os.path.exists(source_dll_dir):
dll_files.append(file) print("⚠️ 未找到dll目录")
# 从子目录查找DLL文件
for root, dirs, files in os.walk('.'):
if 'dist' in root or 'build' in root or '__pycache__' in root:
continue
for file in files:
if file.endswith('.dll'):
dll_files.append(os.path.join(root, file))
if not dll_files:
print("⚠️ 未找到DLL文件")
return return
for dll_file in dll_files: try:
if os.path.exists(dll_file): # 遍历dll目录下的所有内容
try: for item in os.listdir(source_dll_dir):
shutil.copy2(dll_file, dll_dir) source_path = os.path.join(source_dll_dir, item)
print(f"✓ 已复制 {os.path.basename(dll_file)}") target_path = os.path.join(dll_dir, item)
except Exception as e:
print(f"⚠️ 复制 {dll_file} 失败: {e}") if os.path.isdir(source_path):
# 如果是目录,递归复制整个目录
shutil.copytree(source_path, target_path, dirs_exist_ok=True)
print(f"✓ 已复制目录 {item}")
else:
# 如果是文件,直接复制
shutil.copy2(source_path, target_path)
print(f"✓ 已复制文件 {item}")
except Exception as e:
print(f"⚠️ 复制dll目录内容失败: {e}")
def copy_data_files(): def copy_data_files():
"""复制数据库文件到data目录""" """复制数据库文件到data目录"""
@ -309,7 +307,7 @@ def main():
print("后处理...") print("后处理...")
# 检查生成的exe文件 # 检查生成的exe文件
exe_path = 'dist/BodyBalanceBackend_Full.exe' exe_path = 'dist/BodyBalanceBackend.exe'
if os.path.exists(exe_path): if os.path.exists(exe_path):
print(f"✓ exe文件位置: {exe_path}") print(f"✓ exe文件位置: {exe_path}")
@ -337,7 +335,7 @@ def main():
print() print()
print("输出文件:") print("输出文件:")
print(f"- 可执行文件: {exe_path}") print(f"- 可执行文件: {exe_path}")
print("- 启动脚本: dist/start_backend_full.bat") print("- 启动脚本: dist/start_backend.bat")
print("- 配置文件: dist/config.ini") print("- 配置文件: dist/config.ini")
print() print()
print("目录结构:") print("目录结构:")
@ -346,8 +344,8 @@ def main():
print("- logs/ - 日志文件") print("- logs/ - 日志文件")
print() print()
print("使用方式:") print("使用方式:")
print("1. 直接运行: dist/BodyBalanceBackend_Full.exe") print("1. 直接运行: dist/BodyBalanceBackend.exe")
print("2. 使用脚本: dist/start_backend_full.bat") print("2. 使用脚本: dist/start_backend.bat")
print() print()
print("服务地址: http://localhost:5000") print("服务地址: http://localhost:5000")
print("SocketIO: ws://localhost:5000") print("SocketIO: ws://localhost:5000")

View File

@ -1,41 +0,0 @@
[APP]
name = Body Balance Evaluation System
version = 1.0.0
debug = false
log_level = INFO
[SERVER]
host = 0.0.0.0
port = 5000
cors_origins = *
[DATABASE]
path = backend/data/body_balance.db
backup_interval = 24
max_backups = 7
[DEVICES]
camera_index = 0
camera_width = 640
camera_height = 480
camera_fps = 30
imu_port = COM3
pressure_port = COM4
[DETECTION]
default_duration = 60
sampling_rate = 30
balance_threshold = 0.2
posture_threshold = 5.0
[DATA_PROCESSING]
filter_window = 5
outlier_threshold = 2.0
chart_dpi = 300
export_format = csv
[SECURITY]
secret_key = 4bfd3d60ddecef23973b64d761371610130034f23ce90dd6f405756ad2f89495
session_timeout = 3600
max_login_attempts = 5

View File

@ -1,169 +0,0 @@
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const axios = require('axios');
const log = require('electron-log');
let mainWindow;
let pythonProcess;
const BACKEND_PORT = 5000;
const BACKEND_HOST = process.env.BACKEND_HOST || '127.0.0.1';
const BACKEND_URL = `http://${BACKEND_HOST}:${BACKEND_PORT}`;
// 配置日志
log.transports.file.resolvePathFn = () => path.join(app.getPath('userData'), 'logs', 'main.log');
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, '../../assets/icon.ico'),
show: false
});
// 开发环境加载本地服务器,生产环境加载打包后的文件
const isDev = process.env.NODE_ENV === 'development';
if (isDev) {
mainWindow.loadURL('http://localhost:3000');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/dist/index.html'));
}
mainWindow.once('ready-to-show', () => {
mainWindow.show();
startPythonBackend();
});
mainWindow.on('closed', () => {
mainWindow = null;
stopPythonBackend();
});
}
function startPythonBackend() {
try {
const isDev = process.env.NODE_ENV === 'development';
let scriptPath;
if (isDev) {
// 开发环境直接运行Python脚本
scriptPath = path.join(__dirname, '../../backend/app.py');
pythonProcess = spawn('python', [scriptPath], {
stdio: ['pipe', 'pipe', 'pipe']
});
} else {
// 生产环境运行打包后的exe
scriptPath = path.join(process.resourcesPath, 'backend', 'app.exe');
pythonProcess = spawn(scriptPath, {
stdio: ['pipe', 'pipe', 'pipe']
});
}
log.info('启动Python后端服务:', scriptPath);
pythonProcess.stdout.on('data', (data) => {
log.info('Python输出:', data.toString());
});
pythonProcess.stderr.on('data', (data) => {
log.error('Python错误:', data.toString());
});
pythonProcess.on('close', (code) => {
log.info(`Python进程退出代码: ${code}`);
});
// 等待后端启动
setTimeout(() => {
checkBackendHealth();
}, 3000);
} catch (error) {
log.error('启动Python后端失败:', error);
dialog.showErrorBox('启动失败', '无法启动后端服务,请检查安装是否完整');
}
}
function stopPythonBackend() {
if (pythonProcess) {
pythonProcess.kill();
pythonProcess = null;
log.info('Python后端服务已停止');
}
}
async function checkBackendHealth() {
try {
const response = await axios.get(`${BACKEND_URL}/health`, { timeout: 5000 });
if (response.status === 200) {
log.info('后端服务健康检查通过');
mainWindow.webContents.send('backend-ready', true);
}
} catch (error) {
log.error('后端服务健康检查失败:', error.message);
mainWindow.webContents.send('backend-ready', false);
// 尝试重启后端
setTimeout(() => {
log.info('尝试重启后端服务');
stopPythonBackend();
startPythonBackend();
}, 5000);
}
}
// IPC 通信处理
ipcMain.handle('get-backend-url', () => {
return BACKEND_URL;
});
ipcMain.handle('check-backend-health', async () => {
try {
const response = await axios.get(`${BACKEND_URL}/health`, { timeout: 3000 });
return response.status === 200;
} catch (error) {
return false;
}
});
ipcMain.handle('restart-backend', () => {
stopPythonBackend();
setTimeout(() => {
startPythonBackend();
}, 1000);
});
// 应用事件处理
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
stopPythonBackend();
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
app.on('before-quit', () => {
stopPythonBackend();
});
// 处理未捕获的异常
process.on('uncaughtException', (error) => {
log.error('未捕获的异常:', error);
});
process.on('unhandledRejection', (reason, promise) => {
log.error('未处理的Promise拒绝:', reason);
});

View File

@ -1,32 +0,0 @@
const { contextBridge, ipcRenderer } = require('electron');
// 向渲染进程暴露安全的API
contextBridge.exposeInMainWorld('electronAPI', {
// 获取后端服务URL
getBackendUrl: () => ipcRenderer.invoke('get-backend-url'),
// 检查后端服务健康状态
checkBackendHealth: () => ipcRenderer.invoke('check-backend-health'),
// 重启后端服务
restartBackend: () => ipcRenderer.invoke('restart-backend'),
// 监听后端就绪状态
onBackendReady: (callback) => {
ipcRenderer.on('backend-ready', (event, isReady) => {
callback(isReady);
});
},
// 移除监听器
removeAllListeners: (channel) => {
ipcRenderer.removeAllListeners(channel);
}
});
// 暴露日志API
contextBridge.exposeInMainWorld('logAPI', {
info: (message) => console.log('[INFO]', message),
error: (message) => console.error('[ERROR]', message),
warn: (message) => console.warn('[WARN]', message)
});

View File

@ -0,0 +1,79 @@
#!/usr/bin/env node
/**
* Electron开发调试启动脚本
* 用于在renderer目录下直接启动electron开发环境
*/
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
// 检查main.js是否存在
const mainPath = path.join(__dirname, '../main/main.js');
if (!fs.existsSync(mainPath)) {
console.error('❌ main.js文件不存在:', mainPath);
console.log('请确保main进程文件存在于 ../main/main.js');
process.exit(1);
}
// 检查preload.js是否存在
const preloadPath = path.join(__dirname, '../main/preload.js');
if (!fs.existsSync(preloadPath)) {
console.error('❌ preload.js文件不存在:', preloadPath);
console.log('请确保preload文件存在于 ../main/preload.js');
process.exit(1);
}
console.log('🚀 启动Electron开发环境...');
console.log('📁 Main进程文件:', mainPath);
console.log('📁 Preload文件:', preloadPath);
// 设置环境变量
process.env.NODE_ENV = 'development';
process.env.ELECTRON_IS_DEV = '1';
// 启动Vite开发服务器
console.log('\n🔧 启动Vite开发服务器...');
const viteProcess = spawn('npm', ['run', 'dev'], {
stdio: 'inherit',
shell: true,
cwd: __dirname
});
// 等待Vite服务器启动后启动Electron
setTimeout(() => {
console.log('\n⚡ 启动Electron应用...');
const electronProcess = spawn('npx', ['electron', mainPath], {
stdio: 'inherit',
shell: true,
cwd: __dirname
});
// 处理Electron进程退出
electronProcess.on('close', (code) => {
console.log(`\n🔚 Electron进程退出代码: ${code}`);
viteProcess.kill();
process.exit(code);
});
// 处理Vite进程退出
viteProcess.on('close', (code) => {
console.log(`\n🔚 Vite进程退出代码: ${code}`);
electronProcess.kill();
process.exit(code);
});
}, 3000); // 等待3秒让Vite服务器启动
// 处理进程信号
process.on('SIGINT', () => {
console.log('\n🛑 收到中断信号,正在关闭...');
viteProcess.kill();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\n🛑 收到终止信号,正在关闭...');
viteProcess.kill();
process.exit(0);
});

View File

@ -0,0 +1,237 @@
const { app, BrowserWindow } = require('electron');
const path = require('path');
const http = require('http');
const fs = require('fs');
const url = require('url');
const { spawn } = require('child_process');
let mainWindow;
let localServer;
let backendProcess;
function startBackendService() {
// 在打包后的应用中使用process.resourcesPath获取resources目录
const resourcesPath = process.resourcesPath || path.join(__dirname, '../..');
const backendPath = path.join(resourcesPath, 'backend/BodyBalanceBackend.exe');
const backendDir = path.join(resourcesPath, 'backend/');
console.log('Resources path:', resourcesPath);
console.log('Backend path:', backendPath);
console.log('Backend directory:', backendDir);
// 检查后端可执行文件是否存在
if (!fs.existsSync(backendPath)) {
console.error('Backend executable not found:', backendPath);
// 尝试备用路径
const fallbackPath = path.join(__dirname, 'resources/backend/BodyBalanceBackend.exe');
console.log('Trying fallback path:', fallbackPath);
if (!fs.existsSync(fallbackPath)) {
console.error('Fallback backend executable not found:', fallbackPath);
return;
}
// 使用备用路径
const fallbackDir = path.join(__dirname, 'resources/backend/');
backendProcess = spawn(fallbackPath, [], {
cwd: fallbackDir,
stdio: ['ignore', 'pipe', 'pipe']
});
console.log('Starting backend service with fallback path:', fallbackPath);
} else {
console.log('Starting backend service:', backendPath);
// 启动后端进程
backendProcess = spawn(backendPath, [], {
cwd: backendDir,
stdio: ['ignore', 'pipe', 'pipe']
});
}
backendProcess.stdout.on('data', (data) => {
console.log('Backend stdout:', data.toString());
});
backendProcess.stderr.on('data', (data) => {
console.error('Backend stderr:', data.toString());
});
backendProcess.on('close', (code) => {
console.log('Backend process exited with code:', code);
backendProcess = null;
});
backendProcess.on('error', (err) => {
console.error('Failed to start backend process:', err);
backendProcess = null;
});
}
function stopBackendService() {
if (backendProcess) {
console.log('Stopping backend service...');
backendProcess.kill('SIGTERM');
backendProcess = null;
}
// 强制杀死所有BodyBalanceBackend.exe进程
try {
const { exec } = require('child_process');
exec('taskkill /f /im BodyBalanceBackend.exe', (error, stdout, stderr) => {
if (error) {
// 如果没有找到进程taskkill会返回错误这是正常的
if (!error.message.includes('not found')) {
console.error('Error killing BodyBalanceBackend processes:', error.message);
}
} else {
console.log('Successfully killed all BodyBalanceBackend processes:', stdout);
}
});
} catch (err) {
console.error('Failed to execute taskkill command:', err);
}
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, '../../assets/icon.ico'),
show: false,
fullscreen: false,
frame: true,
autoHideMenuBar: true,
backgroundColor: '#000000'
});
// 开发环境加载本地服务器,生产环境加载打包后的文件
const isDev = process.env.NODE_ENV === 'development';
if (isDev) {
mainWindow.loadURL('http://localhost:3000');
// mainWindow.webContents.openDevTools(); // 如需调试,请手动打开或在 did-finish-load 之后打开
} else {
// 启动后端服务
startBackendService();
// 延迟1秒后启动本地HTTP服务器
setTimeout(() => {
startLocalServer(() => {
mainWindow.loadURL('http://localhost:3000');
});
}, 1000);
}
// 窗口就绪后再显示,避免白屏/闪烁
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// 监听页面加载完成事件
mainWindow.webContents.once('did-finish-load', () => {
console.log('Page loaded completely');
});
// 添加加载失败的处理
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
console.log('Failed to load:', errorDescription, 'URL:', validatedURL);
mainWindow.show(); // 即使加载失败也显示窗口
});
mainWindow.on('closed', () => {
mainWindow = null;
if (localServer) {
localServer.close();
}
// 关闭后端服务
stopBackendService();
});
}
function startLocalServer(callback) {
const staticPath = path.join(__dirname, '../dist/');
localServer = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url);
let pathname = parsedUrl.pathname;
// 默认加载index.html
if (pathname === '/') {
pathname = '/index.html';
}
const filePath = path.join(staticPath, pathname);
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('File not found');
return;
}
// 设置正确的Content-Type
const ext = path.extname(filePath);
let contentType = 'text/html';
switch (ext) {
case '.js':
contentType = 'application/javascript';
break;
case '.css':
contentType = 'text/css';
break;
case '.json':
contentType = 'application/json';
break;
case '.png':
contentType = 'image/png';
break;
case '.jpg':
case '.jpeg':
contentType = 'image/jpeg';
break;
case '.svg':
contentType = 'image/svg+xml';
break;
}
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
});
localServer.listen(3000, 'localhost', () => {
console.log('Local server started on http://localhost:3000');
callback();
});
}
// 应用事件处理
// 关闭硬件加速以规避 GPU 进程异常导致的闪烁
app.disableHardwareAcceleration();
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
if (localServer) {
localServer.close();
}
// 关闭后端服务
stopBackendService();
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// 应用退出前清理资源
app.on('before-quit', () => {
stopBackendService();
});

View File

@ -0,0 +1,2 @@
const { contextBridge } = require('electron');

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,23 @@
"name": "body-balance-renderer", "name": "body-balance-renderer",
"version": "1.0.0", "version": "1.0.0",
"description": "平衡体态检测系统前端界面", "description": "平衡体态检测系统前端界面",
"main": "main/main.js",
"scripts": { "scripts": {
"dev": "vite", "dev": "concurrently \"npm run dev:renderer\" \"wait-on http://localhost:3000 && npm run dev:electron\"",
"build": "vite build", "dev:renderer": "vite",
"dev:electron": "electron .",
"build": "npm run build:renderer && npm run build:electron",
"build:renderer": "vite build",
"build:electron": "electron-builder",
"pack": "electron-packager . electron-browser --platform=win32 --arch=x64 --out=dist --overwrite",
"preview": "vite preview" "preview": "vite preview"
}, },
"author": "Zhengsl",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"axios": "^1.5.0", "axios": "^1.5.0",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"element-plus": "^2.3.9", "element-plus": "^2.3.9",
"html2canvas": "^1.4.1",
"pinia": "^2.1.6", "pinia": "^2.1.6",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.2",
"vue": "^3.3.4", "vue": "^3.3.4",
@ -20,8 +26,45 @@
"vue-router": "^4.2.4" "vue-router": "^4.2.4"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.3.4", "@vitejs/plugin-vue": "^4.4.0",
"sass": "^1.66.1", "concurrently": "^8.2.2",
"vite": "^4.4.9" "electron": "^27.3.11",
"electron-builder": "^24.13.3",
"electron-packager": "^17.1.2",
"vite": "^4.4.9",
"wait-on": "^7.0.1"
},
"build": {
"appId": "com.bodybalance.evaluation",
"productName": "平衡体态检测系统",
"electronVersion": "27.3.11",
"electronDownload": {
"mirror": "https://npmmirror.com/mirrors/electron/"
},
"directories": {
"output": "dist-electron"
},
"files": [
"dist/**/*",
"main/**/*",
"package.json"
],
"extraFiles": [
{
"from": "../../../backend/dist",
"to": "resources/backend",
"filter": ["**/*"]
}
],
"win": {
"target": "nsis",
"icon": "../../../install/assets/icon.ico"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
} }
} }

View File

@ -17,7 +17,6 @@ api.interceptors.request.use(
config.baseURL = window.electronAPI.getBackendUrl() config.baseURL = window.electronAPI.getBackendUrl()
} else { } else {
config.baseURL = 'http://localhost:5000' config.baseURL = 'http://localhost:5000'
} }
// 添加时间戳防止缓存 // 添加时间戳防止缓存
@ -73,7 +72,7 @@ api.interceptors.response.use(
message = error.message || '未知错误' message = error.message || '未知错误'
} }
ElMessage.error(message) // ElMessage.error(message)
return Promise.reject(error) return Promise.reject(error)
} }
) )

View File

@ -33,6 +33,7 @@
:type="passwordVisible ? 'text' : 'password'" :type="passwordVisible ? 'text' : 'password'"
placeholder="请输入密码" placeholder="请输入密码"
class="custom-input" class="custom-input"
@keyup.enter="handleLogin"
> >
<template #suffix> <template #suffix>
<el-icon <el-icon
@ -414,11 +415,12 @@ const handleLogin = async () => {
router.push('/') router.push('/')
} else { } else {
// //
if (result.error && result.error.includes('用户不存在')) { const errorMsg = String(result.error || '')
if (errorMsg.includes('用户不存在')) {
showError('用户名或密码错误,请重新输入!') showError('用户名或密码错误,请重新输入!')
} else if (result.error && result.error.includes('密码错误')) { } else if (errorMsg.includes('密码错误')) {
showError('用户名或密码错误,请重新输入!') showError('用户名或密码错误,请重新输入!')
} else if (result.error && result.error.includes('用户未激活')) { } else if (errorMsg.includes('用户未激活')) {
showError('账户未激活,请联系管理员激活后再登录') showError('账户未激活,请联系管理员激活后再登录')
} else { } else {
showError('用户名或密码错误,请重新输入!') showError('用户名或密码错误,请重新输入!')

View File

@ -0,0 +1,117 @@
#!/usr/bin/env node
/**
* Electron功能测试脚本
* 用于验证electron环境是否正确配置
*/
const fs = require('fs');
const path = require('path');
console.log('🔍 Electron环境检测...');
console.log('='.repeat(50));
// 检查package.json配置
const packagePath = path.join(__dirname, 'package.json');
if (fs.existsSync(packagePath)) {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
console.log('✅ package.json 存在');
console.log('📦 项目名称:', pkg.name);
console.log('📦 版本:', pkg.version);
// 检查electron相关脚本
if (pkg.scripts && pkg.scripts['electron:dev']) {
console.log('✅ electron:dev 脚本已配置');
} else {
console.log('❌ electron:dev 脚本未配置');
}
// 检查electron依赖
const hasElectron = pkg.devDependencies && pkg.devDependencies.electron;
if (hasElectron) {
console.log('✅ electron 依赖已配置:', pkg.devDependencies.electron);
} else {
console.log('❌ electron 依赖未配置');
}
// 检查electron-builder
const hasBuilder = pkg.devDependencies && pkg.devDependencies['electron-builder'];
if (hasBuilder) {
console.log('✅ electron-builder 依赖已配置:', pkg.devDependencies['electron-builder']);
} else {
console.log('❌ electron-builder 依赖未配置');
}
} else {
console.log('❌ package.json 不存在');
}
// 检查main进程文件
const mainPath = path.join(__dirname, '../main/main.js');
if (fs.existsSync(mainPath)) {
console.log('✅ main.js 存在:', mainPath);
} else {
console.log('❌ main.js 不存在:', mainPath);
}
// 检查preload文件
const preloadPath = path.join(__dirname, '../main/preload.js');
if (fs.existsSync(preloadPath)) {
console.log('✅ preload.js 存在:', preloadPath);
} else {
console.log('❌ preload.js 不存在:', preloadPath);
}
// 检查vite配置
const viteConfigPath = path.join(__dirname, 'vite.config.js');
if (fs.existsSync(viteConfigPath)) {
console.log('✅ vite.config.js 存在');
const viteConfig = fs.readFileSync(viteConfigPath, 'utf8');
if (viteConfig.includes('electron-renderer')) {
console.log('✅ vite配置包含electron-renderer目标');
} else {
console.log('⚠️ vite配置可能需要electron-renderer目标');
}
} else {
console.log('❌ vite.config.js 不存在');
}
// 检查node_modules
const nodeModulesPath = path.join(__dirname, 'node_modules');
if (fs.existsSync(nodeModulesPath)) {
console.log('✅ node_modules 存在');
// 检查electron是否安装
const electronPath = path.join(nodeModulesPath, 'electron');
if (fs.existsSync(electronPath)) {
console.log('✅ electron 已安装');
} else {
console.log('❌ electron 未安装,请运行 npm install');
}
} else {
console.log('❌ node_modules 不存在,请运行 npm install');
}
// 检查启动脚本
const startScriptPath = path.join(__dirname, 'start-electron.bat');
if (fs.existsSync(startScriptPath)) {
console.log('✅ start-electron.bat 启动脚本存在');
} else {
console.log('❌ start-electron.bat 启动脚本不存在');
}
const devScriptPath = path.join(__dirname, 'electron-dev.js');
if (fs.existsSync(devScriptPath)) {
console.log('✅ electron-dev.js 开发脚本存在');
} else {
console.log('❌ electron-dev.js 开发脚本不存在');
}
console.log('='.repeat(50));
console.log('🎯 检测完成!');
console.log('');
console.log('📋 使用说明:');
console.log('1. 确保依赖已安装: npm install');
console.log('2. 启动开发环境: npm run electron:dev');
console.log('3. 或使用批处理: start-electron.bat');
console.log('4. 或使用脚本: node electron-dev.js');

View File

@ -12,7 +12,10 @@ export default defineConfig({
input: { input: {
main: resolve(__dirname, 'index.html') main: resolve(__dirname, 'index.html')
} }
} },
// Electron环境优化
target: 'esnext',
minify: false
}, },
resolve: { resolve: {
alias: { alias: {
@ -21,6 +24,13 @@ export default defineConfig({
}, },
server: { server: {
port: 3000, port: 3000,
host: '0.0.0.0' host: '0.0.0.0',
// 开发服务器配置
cors: true,
strictPort: true
},
// Electron环境变量
define: {
__IS_ELECTRON__: 'true'
} }
}); });

View File

@ -1,200 +1,21 @@
# 最小功能测试框架 # 平衡体态检测系统安装包
## 概述 ## 目录结构
- `backend/` - 后端程序文件
- `frontend/` - 前端Electron应用
- `启动系统.bat` - 系统启动脚本
这是一个最小功能测试框架,用于验证 Flask + SocketIO + threading 技术栈在打包成 exe 后的可用性。 ## 使用方法
1. 双击 `启动系统.bat` 启动系统
2. 系统会自动启动后端服务和前端界面
3. 如果需要单独启动可以直接运行前端exe文件
## 功能特性 ## 系统要求
- Windows 10 或更高版本
- 至少4GB内存
- 支持USB设备连接
- ✅ Flask HTTP 服务 ## 注意事项
- ✅ SocketIO WebSocket 服务 - 首次启动可能需要较长时间
- ✅ 强制使用 threading 异步模式 - 确保防火墙允许程序访问网络
- ✅ 完整的前端测试界面 - 如遇问题请查看日志文件
- ✅ HTTP API 测试
- ✅ WebSocket 连接测试
- ✅ 实时消息收发测试
- ✅ 系统信息显示
## 文件结构
```
install/
├── minimal_test_app.py # 主应用文件
├── build_minimal.py # 打包脚本
├── requirements_minimal.txt # 最小依赖列表
└── README.md # 说明文档
```
## 快速开始
### 1. 安装依赖
```bash
pip install -r requirements_minimal.txt
```
### 2. 测试运行
```bash
python minimal_test_app.py
```
然后在浏览器中访问 http://localhost:5000 进行测试。
### 3. 打包成 exe
```bash
python build_minimal.py
```
### 4. 测试 exe
打包完成后,运行:
```bash
# 方式1直接运行
dist/MinimalTestApp.exe
# 方式2使用脚本
dist/start_test.bat
```
## 测试步骤
### HTTP API 测试
1. 点击「测试 HTTP API」按钮
2. 确认返回成功响应
3. 检查响应数据包含服务器信息
### WebSocket 测试
1. 页面加载时会自动连接 WebSocket
2. 确认连接状态显示为「已连接」
3. 点击「发送测试消息」
4. 确认能收到服务器响应
5. 测试断开和重连功能
### 系统信息检查
- 服务器时间:确认时间正确
- SocketIO模式确认显示为 "threading"
- Flask版本确认版本信息
## 技术要点
### 异步模式选择
```python
# 强制使用 threading 模式,避免 eventlet/gevent 依赖问题
socketio = SocketIO(
app,
cors_allowed_origins='*',
async_mode='threading', # 关键配置
logger=False,
engineio_logger=False
)
```
### 打包配置
```python
# PyInstaller 隐藏导入配置
hiddenimports=[
'flask',
'flask_socketio',
'socketio',
'engineio',
'engineio.async_drivers.threading', # 关键threading 驱动
'socketio.namespace',
'dns',
'dns.resolver',
'dns.asyncresolver'
],
# 排除不需要的异步模式
excludes=[
'eventlet',
'gevent',
'gevent_uwsgi'
]
```
## 故障排除
### 常见问题
1. **"Invalid async_mode specified" 错误**
- 确认已安装所有依赖
- 检查 PyInstaller 隐藏导入配置
- 确认排除了不需要的异步模式
2. **WebSocket 连接失败**
- 检查防火墙设置
- 确认端口 5000 未被占用
- 查看控制台错误信息
3. **打包失败**
- 确认 PyInstaller 版本兼容
- 检查依赖版本冲突
- 查看详细错误输出
### 调试模式
如需调试,可以修改 `minimal_test_app.py`
```python
# 启用调试模式
socketio.run(
app,
host='0.0.0.0',
port=5000,
debug=True, # 启用调试
allow_unsafe_werkzeug=True
)
```
## 扩展指南
### 添加新功能
1. **新增 HTTP 路由**
```python
@app.route('/api/new-endpoint')
def new_endpoint():
return jsonify({'message': 'New endpoint'})
```
2. **新增 SocketIO 事件**
```python
@socketio.on('new_event')
def handle_new_event(data):
emit('response', {'status': 'received'})
```
3. **添加依赖**
- 更新 `requirements_minimal.txt`
- 更新 `build_minimal.py` 中的 `hiddenimports`
- 重新测试打包
### 渐进式集成
按照以下顺序逐步添加业务功能:
1. ✅ 基础 Flask + SocketIO当前阶段
2. 🔄 添加数据库支持
3. 🔄 添加文件操作
4. 🔄 添加外部库依赖
5. 🔄 添加硬件设备支持
每个阶段都要确保打包和运行正常,出现问题时更容易定位。
## 版本信息
- Flask: 2.3.3
- Flask-SocketIO: 5.3.6
- PyInstaller: 6.1.0
- Python: 3.8+
## 许可证
MIT License

View File

@ -1,224 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
最小功能测试框架打包脚本
用于将Flask + SocketIO应用打包成exe文件
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
def check_dependencies():
"""检查必需的依赖"""
print("检查依赖模块...")
required_modules = [
'flask',
'flask_socketio',
'socketio',
'engineio'
]
missing_modules = []
for module in required_modules:
try:
__import__(module)
print(f"{module}")
except ImportError:
print(f"{module} (缺失)")
missing_modules.append(module)
if missing_modules:
print(f"\n缺失模块: {', '.join(missing_modules)}")
print("请运行: pip install -r requirements_minimal.txt")
return False
print("✓ 所有依赖模块检查通过")
return True
def create_spec_file():
"""创建PyInstaller spec文件"""
spec_content = '''
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['minimal_test_app.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[
'flask',
'flask_socketio',
'socketio',
'engineio',
'engineio.async_drivers.threading',
'socketio.namespace',
'dns',
'dns.resolver',
'dns.asyncresolver'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[
'eventlet',
'gevent',
'gevent_uwsgi'
],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='MinimalTestApp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None
)
'''
with open('minimal_test.spec', 'w', encoding='utf-8') as f:
f.write(spec_content)
print("✓ 已创建 minimal_test.spec 文件")
def clean_build_dirs():
"""清理构建目录"""
dirs_to_clean = ['build', 'dist', '__pycache__']
for dir_name in dirs_to_clean:
if os.path.exists(dir_name):
try:
shutil.rmtree(dir_name)
print(f"✓ 清理目录: {dir_name}")
except Exception as e:
print(f"警告: 无法清理目录 {dir_name}: {e}")
def build_exe():
"""构建exe文件"""
print("\n开始构建exe文件...")
try:
# 使用PyInstaller构建
cmd = [sys.executable, '-m', 'PyInstaller', 'minimal_test.spec', '--clean']
print(f"执行命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("✓ 构建成功!")
return True
else:
print(f"✗ 构建失败")
print(f"错误输出: {result.stderr}")
return False
except Exception as e:
print(f"✗ 构建过程出错: {e}")
return False
def create_test_script():
"""创建测试脚本"""
test_script = '''
@echo off
echo 启动最小功能测试应用...
echo.
echo 测试说明:
echo 1. 应用启动后在浏览器中访问 http://localhost:5000
echo 2. 测试HTTP API和WebSocket功能
echo 3. 按Ctrl+C停止应用
echo.
"MinimalTestApp.exe"
pause
'''
with open('dist/start_test.bat', 'w', encoding='utf-8') as f:
f.write(test_script)
print("✓ 创建测试脚本: dist/start_test.bat")
def main():
"""主函数"""
print("="*60)
print("最小功能测试框架 - 打包工具")
print("="*60)
print()
# 检查当前目录
if not os.path.exists('minimal_test_app.py'):
print("✗ 错误: 找不到 minimal_test_app.py 文件")
print("请确保在正确的目录下运行此脚本")
input("按回车键退出...")
return
# 检查依赖
if not check_dependencies():
input("按回车键退出...")
return
print()
# 清理构建目录
print("清理构建目录...")
clean_build_dirs()
print()
# 创建spec文件
print("创建PyInstaller配置...")
create_spec_file()
print()
# 构建exe
if build_exe():
print()
print("后处理...")
# 检查生成的exe文件
exe_path = 'dist/MinimalTestApp.exe'
if os.path.exists(exe_path):
print(f"✓ exe文件位置: {exe_path}")
# 创建测试脚本
create_test_script()
print()
print("🎉 打包完成!")
print()
print("测试方式:")
print("1. 直接运行: dist/MinimalTestApp.exe")
print("2. 使用脚本: dist/start_test.bat")
print()
print("测试步骤:")
print("1. 启动应用")
print("2. 浏览器访问 http://localhost:5000")
print("3. 测试HTTP API和WebSocket功能")
print("4. 确认所有功能正常工作")
else:
print("✗ 错误: 未找到生成的exe文件")
else:
print("\n✗ 打包失败")
print()
input("按回车键退出...")
if __name__ == '__main__':
main()

View File

@ -1,53 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['minimal_test_app.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[
'flask',
'flask_socketio',
'socketio',
'engineio',
'engineio.async_drivers.threading',
'socketio.namespace',
'dns',
'dns.resolver',
'dns.asyncresolver'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[
'eventlet',
'gevent',
'gevent_uwsgi'
],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='MinimalTestApp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None
)

View File

@ -1,274 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
最小功能测试框架 - Flask + SocketIO + threading
用于验证打包exe后HTTP和WebSocket服务的可用性
"""
import os
import sys
from flask import Flask, render_template_string, jsonify
from flask_socketio import SocketIO, emit
import logging
from datetime import datetime
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 创建Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'minimal-test-secret-key-2024'
# 初始化SocketIO强制使用threading模式
try:
logger.info("初始化SocketIOthreading模式...")
socketio = SocketIO(
app,
cors_allowed_origins='*',
async_mode='threading',
logger=False,
engineio_logger=False
)
logger.info(f"SocketIO初始化成功异步模式: {socketio.async_mode}")
except Exception as e:
logger.error(f"SocketIO初始化失败: {e}")
sys.exit(1)
# HTML测试页面模板
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>最小功能测试 - Flask + SocketIO</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: 0 auto; }
.section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.success { color: green; }
.error { color: red; }
.info { color: blue; }
button { padding: 10px 20px; margin: 5px; cursor: pointer; }
#messages { height: 200px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; background: #f9f9f9; }
.message { margin: 5px 0; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
</head>
<body>
<div class="container">
<h1>最小功能测试框架</h1>
<p class="info">测试Flask HTTP服务和SocketIO WebSocket服务</p>
<!-- HTTP API测试 -->
<div class="section">
<h2>HTTP API 测试</h2>
<button onclick="testHttpApi()">测试 HTTP API</button>
<div id="http-result"></div>
</div>
<!-- WebSocket测试 -->
<div class="section">
<h2>WebSocket 测试</h2>
<button onclick="connectSocket()">连接 WebSocket</button>
<button onclick="disconnectSocket()">断开连接</button>
<button onclick="sendTestMessage()">发送测试消息</button>
<div>连接状态: <span id="connection-status" class="error">未连接</span></div>
<div id="messages"></div>
</div>
<!-- 系统信息 -->
<div class="section">
<h2>系统信息</h2>
<div id="system-info">
<p>服务器时间: {{ server_time }}</p>
<p>SocketIO模式: {{ socketio_mode }}</p>
<p>Flask版本: {{ flask_version }}</p>
</div>
</div>
</div>
<script>
let socket = null;
function addMessage(message, type = 'info') {
const messages = document.getElementById('messages');
const div = document.createElement('div');
div.className = `message ${type}`;
div.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`;
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}
function testHttpApi() {
const resultDiv = document.getElementById('http-result');
resultDiv.innerHTML = '测试中...';
fetch('/api/test')
.then(response => response.json())
.then(data => {
resultDiv.innerHTML = `<span class="success"> HTTP API 正常</span><br>响应: ${JSON.stringify(data, null, 2)}`;
})
.catch(error => {
resultDiv.innerHTML = `<span class="error"> HTTP API 错误: ${error}</span>`;
});
}
function connectSocket() {
if (socket && socket.connected) {
addMessage('WebSocket已连接', 'info');
return;
}
socket = io();
socket.on('connect', function() {
document.getElementById('connection-status').innerHTML = '<span class="success">已连接</span>';
addMessage('WebSocket连接成功', 'success');
});
socket.on('disconnect', function() {
document.getElementById('connection-status').innerHTML = '<span class="error">未连接</span>';
addMessage('WebSocket连接断开', 'error');
});
socket.on('test_response', function(data) {
addMessage(`收到服务器响应: ${JSON.stringify(data)}`, 'success');
});
socket.on('server_message', function(data) {
addMessage(`服务器消息: ${data.message}`, 'info');
});
}
function disconnectSocket() {
if (socket) {
socket.disconnect();
socket = null;
}
}
function sendTestMessage() {
if (!socket || !socket.connected) {
addMessage('请先连接WebSocket', 'error');
return;
}
const testData = {
message: 'Hello from client',
timestamp: new Date().toISOString()
};
socket.emit('test_message', testData);
addMessage(`发送消息: ${JSON.stringify(testData)}`, 'info');
}
// 页面加载时自动连接
window.onload = function() {
connectSocket();
};
</script>
</body>
</html>
"""
# HTTP路由
@app.route('/')
def index():
"""主页面"""
import flask
return render_template_string(HTML_TEMPLATE,
server_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
socketio_mode=socketio.async_mode,
flask_version=flask.__version__
)
@app.route('/api/test')
def api_test():
"""HTTP API测试接口"""
return jsonify({
'status': 'success',
'message': 'HTTP API工作正常',
'timestamp': datetime.now().isoformat(),
'server_info': {
'socketio_mode': socketio.async_mode,
'working_directory': os.getcwd()
}
})
@app.route('/health')
def health_check():
"""健康检查接口"""
return jsonify({
'status': 'healthy',
'services': {
'http': 'running',
'websocket': 'running',
'socketio_mode': socketio.async_mode
},
'timestamp': datetime.now().isoformat()
})
# SocketIO事件处理
@socketio.on('connect')
def handle_connect():
"""客户端连接事件"""
logger.info(f"客户端连接: {request.sid if 'request' in globals() else 'unknown'}")
emit('server_message', {
'message': 'WebSocket连接成功',
'timestamp': datetime.now().isoformat(),
'socketio_mode': socketio.async_mode
})
@socketio.on('disconnect')
def handle_disconnect():
"""客户端断开连接事件"""
logger.info(f"客户端断开连接: {request.sid if 'request' in globals() else 'unknown'}")
@socketio.on('test_message')
def handle_test_message(data):
"""处理测试消息"""
logger.info(f"收到测试消息: {data}")
# 回复客户端
emit('test_response', {
'status': 'received',
'original_message': data,
'server_timestamp': datetime.now().isoformat(),
'socketio_mode': socketio.async_mode
})
def main():
"""主函数"""
logger.info("="*50)
logger.info("最小功能测试框架启动")
logger.info("="*50)
logger.info(f"工作目录: {os.getcwd()}")
logger.info(f"Python版本: {sys.version}")
logger.info(f"SocketIO异步模式: {socketio.async_mode}")
try:
# 启动服务器
logger.info("启动服务器 http://localhost:5000")
logger.info("按 Ctrl+C 停止服务器")
socketio.run(
app,
host='0.0.0.0',
port=5000,
debug=False,
allow_unsafe_werkzeug=True
)
except KeyboardInterrupt:
logger.info("用户中断,正在关闭服务器...")
except Exception as e:
logger.error(f"服务器启动失败: {e}")
input("按回车键退出...")
sys.exit(1)
if __name__ == '__main__':
main()

View File

@ -1,110 +0,0 @@
@echo off
echo ========================================
echo Minimal Test Framework - Quick Start
echo ========================================
echo.
:menu
echo Please select an option:
echo 1. Install dependencies
echo 2. Test run (development mode)
echo 3. Build exe
echo 4. Run built exe
echo 5. Show help
echo 0. Exit
echo.
set /p choice=Enter option (0-5):
if "%choice%"=="1" goto install_deps
if "%choice%"=="2" goto test_run
if "%choice%"=="3" goto build_exe
if "%choice%"=="4" goto run_exe
if "%choice%"=="5" goto show_help
if "%choice%"=="0" goto exit
echo Invalid option, please try again
echo.
goto menu
:install_deps
echo.
echo Installing dependencies...
pip install -r requirements_minimal.txt
if %errorlevel% equ 0 (
echo Dependencies installed successfully
) else (
echo Failed to install dependencies
)
echo.
pause
goto menu
:test_run
echo.
echo Starting test server...
echo After server starts, visit http://localhost:5000 in your browser
echo Press Ctrl+C to stop the server
echo.
python minimal_test_app.py
echo.
pause
goto menu
:build_exe
echo.
echo Starting build...
python build_minimal.py
echo.
pause
goto menu
:run_exe
echo.
if exist "dist\MinimalTestApp.exe" (
echo Starting built application...
echo After app starts, visit http://localhost:5000 in your browser
echo.
cd dist
MinimalTestApp.exe
cd ..
) else (
echo dist\MinimalTestApp.exe not found
echo Please run option 3 to build first
)
echo.
pause
goto menu
:show_help
echo.
echo ========================================
echo Usage Instructions
echo ========================================
echo.
echo 1. First time: select "1. Install dependencies"
echo 2. After installation: select "2. Test run" to verify
echo 3. After testing: select "3. Build exe"
echo 4. After building: select "4. Run built exe" to verify
echo.
echo Test Steps:
echo - HTTP API test: Click "Test HTTP API" button on page
echo - WebSocket test: Page auto-connects, can send test messages
echo - System info: Check if SocketIO mode shows "threading"
echo.
echo Files:
echo - minimal_test_app.py: Main application
echo - build_minimal.py: Build script
echo - requirements_minimal.txt: Dependencies
echo - README.md: Detailed documentation
echo.
echo Troubleshooting:
echo - If "Invalid async_mode specified" error occurs
echo check if dependencies are fully installed
echo - If WebSocket connection fails, check firewall settings
echo - See README.md for detailed instructions
echo.
pause
goto menu
:exit
echo Thank you for using!
exit /b 0

View File

@ -1,11 +0,0 @@
# 最小功能测试框架依赖
# 只包含Flask + SocketIO + threading的核心依赖
Flask==2.3.3
Flask-SocketIO==5.3.6
Werkzeug==2.3.7
python-socketio==5.8.0
python-engineio==4.7.1
# 打包工具
PyInstaller>=6.10.0

6334
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,122 +0,0 @@
{
"name": "body-balance-evaluation",
"version": "1.0.0",
"description": "身体平衡评估系统 - 基于多传感器融合技术的专业平衡能力评估与分析系统",
"main": "src/main/main.js",
"scripts": {
"dev": "electron .",
"build": "npm run build:renderer && npm run build:main",
"build:renderer": "cd src/renderer && npm run build",
"build:main": "electron-builder",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"postinstall": "electron-builder install-app-deps",
"start": "electron .",
"start:dev": "electron . --mode development",
"start:prod": "electron . --mode production",
"install:backend": "pip install -r backend/requirements.txt",
"install:frontend": "cd src/renderer && npm install",
"install:all": "npm run install:backend && npm run install:frontend",
"test:backend": "cd backend && python -m pytest tests/ -v",
"test:frontend": "cd src/renderer && npm run test",
"lint:backend": "cd backend && flake8 . --max-line-length=88 --exclude=venv,__pycache__",
"lint:frontend": "cd src/renderer && npm run lint",
"format:backend": "cd backend && black . --line-length=88",
"format:frontend": "cd src/renderer && npm run format",
"clean": "node -e \"const fs = require('fs'); const path = require('path'); ['logs', 'temp', 'node_modules/.cache'].forEach(d => { try { fs.rmSync(d, {recursive: true, force: true}); } catch(e) {} });\"",
"clean:data": "node -e \"const fs = require('fs'); try { fs.rmSync('data', {recursive: true, force: true}); } catch(e) {}\"",
"backup": "node -e \"const fs = require('fs'); const path = require('path'); const archiver = require('archiver'); const date = new Date().toISOString().slice(0,19).replace(/:/g,'-'); const output = fs.createWriteStream(`backup_${date}_data.zip`); const archive = archiver('zip'); archive.pipe(output); archive.directory('data/', false); archive.finalize();\"",
"setup": "npm run install:all && node -e \"const fs = require('fs'); ['data', 'data/patients', 'data/sessions', 'data/exports', 'data/backups', 'logs', 'temp'].forEach(d => fs.mkdirSync(d, {recursive: true}));\"",
"check": "node --version && npm --version && electron --version",
"test": "npm run test:backend && npm run test:frontend"
},
"keywords": [
"balance",
"posture",
"assessment",
"healthcare",
"rehabilitation",
"sensors",
"computer-vision",
"medical-device",
"electron",
"vue",
"mediapipe"
],
"author": {
"name": "zheng shunli",
"email": "dev@example.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/example/body-balance-evaluation.git"
},
"bugs": {
"url": "https://github.com/example/body-balance-evaluation/issues"
},
"homepage": "https://github.com/example/body-balance-evaluation#readme",
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"devDependencies": {
"concurrently": "^7.6.0",
"cross-env": "^7.0.3",
"electron": "^27.0.0",
"electron-builder": "latest",
"electron-packager": "^17.1.2"
},
"dependencies": {
"axios": "^1.5.0",
"electron-log": "^4.4.8",
"element-plus": "^2.10.4",
"html2canvas": "^1.4.1",
"socket.io-client": "^4.8.1",
"vue-router": "^4.5.1"
},
"config": {
"backend_host": "0.0.0.0",
"backend_port": "5000",
"frontend_port": "5173"
},
"build": {
"appId": "com.bodybalance.evaluation",
"productName": "平衡体态检测系统",
"electronDist": "D:\\electron-v36.4.0-win32-x64",
"electronVersion": "36.4.0",
"directories": {
"output": "dist"
},
"files": [
"src/main/**/*",
"src/renderer/dist/**/*",
"backend/dist/**/*",
"ffmpeg/**/*",
"node_modules/**/*"
],
"extraResources": [
{
"from": "backend/dist",
"to": "backend"
},
{
"from": "ffmpeg",
"to": "ffmpeg"
}
],
"win": {
"target": "nsis",
"icon": "assets/icon.ico",
"arch": [
"x64"
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
}
}