界面调整,功能定稿

main
risingLee 2026-02-26 16:39:29 +08:00
parent e2eaf3cbe5
commit daeef833f8
34 changed files with 1200 additions and 141 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
__pycache__/
backend/build/
backend/dist/

View File

@ -197,3 +197,5 @@ chmod +x start_frontend.sh

View File

@ -111,3 +111,5 @@ simulator = WrenchSimulator(

View File

@ -248,3 +248,5 @@ ssh -p 2222 root@localhost
如有问题,请检查日志或联系技术支持。

44
backend/backend_api.spec Normal file
View File

@ -0,0 +1,44 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['app.py'],
pathex=['..'],
binaries=[],
datas=[],
hiddenimports=['wrench_controller'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='backend_api',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='backend_api',
)

66
backend/build_backend.bat Normal file
View File

@ -0,0 +1,66 @@
@echo off
REM Simple backend build script (ASCII only, avoid encoding issues)
cd /d "%~dp0"
echo ========================================
echo Building backend_api with PyInstaller...
echo ========================================
echo Checking Python environment...
python -c "import PyInstaller" 2>nul
if errorlevel 1 (
echo PyInstaller not found, installing...
pip install --upgrade setuptools
pip install pyinstaller
if errorlevel 1 (
echo Failed to install PyInstaller. Please run: pip install pyinstaller
pause
exit /b 1
)
) else (
echo Checking PyInstaller dependencies...
python -c "import jaraco" 2>nul
if errorlevel 1 (
echo Missing dependencies detected, fixing...
pip install --upgrade setuptools
pip install --upgrade --force-reinstall pyinstaller
)
)
echo Cleaning old build/dist...
if exist build (
echo Removing build directory...
rmdir /s /q build 2>nul
timeout /t 1 /nobreak >nul
)
if exist dist (
echo Removing dist directory...
rmdir /s /q dist 2>nul
timeout /t 1 /nobreak >nul
)
if exist backend_api.spec (
echo Removing old spec file...
del /q backend_api.spec 2>nul
)
echo Running PyInstaller with --clean flag...
python -m PyInstaller --clean --name backend_api --console --paths .. --hidden-import wrench_controller app.py
if errorlevel 1 (
echo Build failed!
pause
exit /b 1
)
echo.
echo Build success!
echo Executable: dist\backend_api\backend_api.exe
echo.
echo If you have an existing wrench.db, copy it to:
echo dist\backend_api\wrench.db
echo.
echo Then run:
echo dist\backend_api\backend_api.exe
echo.
pause

64
backend/build_backend.sh Normal file
View File

@ -0,0 +1,64 @@
#!/bin/bash
# 电动扳手后端 API 打包脚本Linux/Mac
echo "========================================"
echo "电动扳手后端 API 打包脚本"
echo "========================================"
echo ""
# 确保在 backend 目录下执行
cd "$(dirname "$0")"
# 检查 PyInstaller 是否已安装
if ! python3 -c "import PyInstaller" 2>/dev/null; then
echo "[错误] PyInstaller 未安装"
echo "正在安装 PyInstaller..."
pip3 install pyinstaller
if [ $? -ne 0 ]; then
echo "[错误] PyInstaller 安装失败,请手动执行: pip3 install pyinstaller"
exit 1
fi
fi
echo "[信息] PyInstaller 已安装"
echo ""
# 清理之前的构建文件
echo "[信息] 清理之前的构建文件..."
rm -rf build dist
echo ""
echo "[信息] 当前目录: $(pwd)"
echo "[信息] 开始打包后端 API 服务..."
echo ""
# 使用 onedir 模式打包,并显式包含父目录中的 wrench_controller 模块
# --paths .. 把项目根目录加入模块搜索路径
# --hidden-import ... 确保 wrench_controller 被打包进可执行文件
pyinstaller --name backend_api --console --paths .. --hidden-import wrench_controller app.py
if [ $? -ne 0 ]; then
echo ""
echo "[错误] 打包失败!"
exit 1
fi
echo ""
echo "========================================"
echo "后端打包完成!"
echo "========================================"
echo ""
echo "可执行文件位置: dist/backend_api/backend_api"
echo ""
echo "下一步操作:"
echo "1. 如果已有生产用数据库,可将当前目录的 wrench.db 复制到 dist/backend_api/ 目录:"
echo " cp wrench.db dist/backend_api/wrench.db"
echo ""
echo "2. 运行后端服务:"
echo " ./dist/backend_api/backend_api"
echo ""
echo "3. 确保前端或第三方程序访问的地址仍为:"
echo " http://localhost:5000"
echo ""

View File

@ -77,3 +77,5 @@ print("\n" + "="*60)

View File

@ -28,3 +28,5 @@ conn.close()

View File

@ -68,3 +68,5 @@ print("="*60)

View File

@ -113,3 +113,5 @@ if __name__ == "__main__":

View File

@ -55,3 +55,5 @@ print("="*60)

View File

@ -3,3 +3,5 @@ flask-cors==4.0.0

View File

@ -10,3 +10,5 @@ pause

View File

@ -8,3 +8,5 @@ python3 app.py

View File

@ -42,3 +42,5 @@ if __name__ == "__main__":

View File

@ -0,0 +1,176 @@
## 后端Flask API打包与运行说明
### 1. 是否可以用和前端相同的方式打包?
**可以。**
后端同样可以用 **PyInstaller** 打包成一个可执行文件,只是:
- 前端是 **GUI 程序**`console=False`
- 后端是 **控制台服务程序**`console=True`,启动后常驻,监听 5000 端口)。
本项目已经在 `backend` 目录下生成了两个打包脚本:
- `build_backend.bat`Windows
- `build_backend.sh`Linux/Mac
---
### 2. 打包前准备
在打包前,确保:
- 已安装 Python 3 环境
- 已安装后端依赖:
```bash
cd backend
pip install -r requirements.txt
```
(如果使用虚拟环境,请先激活虚拟环境再执行上面的命令)
---
### 3. Windows 下打包后端
1. 打开命令提示符或 PowerShell
2. 进入 `backend` 目录:
```cmd
cd F:\PyPro\TorqueWrench\backend
```
3. 执行打包脚本:
```cmd
build_backend.bat
```
脚本会自动:
- 检查并安装 PyInstaller
- 清理旧的 `build/`、`dist/` 目录
- 调用:
```cmd
pyinstaller --name backend_api --console --paths .. --hidden-import wrench_controller app.py
```
4. 打包完成后,可执行文件位置:
```text
backend\dist\backend_api\backend_api.exe
```
5. 如果你已经有一个正在使用的数据库文件 `wrench.db`,可以复制过去:
```cmd
copy wrench.db dist\backend_api\wrench.db
```
6. 启动后端服务:
```cmd
dist\backend_api\backend_api.exe
```
启动后,后端会监听 `http://localhost:5000`,前端照常访问。
---
### 4. Linux / Mac 下打包后端
1. 打开终端
2. 进入 `backend` 目录:
```bash
cd /path/to/TorqueWrench/backend
```
3. 赋予脚本执行权限(首次):
```bash
chmod +x build_backend.sh
```
4. 执行打包脚本:
```bash
./build_backend.sh
```
脚本会自动:
- 检查并安装 PyInstaller
- 清理旧的 `build/`、`dist/` 目录
- 调用:
```bash
pyinstaller --name backend_api --console --paths .. --hidden-import wrench_controller app.py
```
5. 打包完成后,可执行文件位置:
```text
backend/dist/backend_api/backend_api
```
6. 如果已有生产数据库,复制到打包目录:
```bash
cp wrench.db dist/backend_api/wrench.db
```
7. 启动后端服务:
```bash
./dist/backend_api/backend_api
```
同样会监听 `http://localhost:5000`
---
### 5. 和直接用 Python 运行的对比
- **直接运行(开发环境常用):**
```bash
cd backend
python app.py
```
- **打包运行(部署 / 发给其他机器):**
- 不需要安装源码,只要有打包后的目录和配置/数据库文件即可。
- 更适合发给不熟悉 Python 环境的用户。
两种方式使用的是同一套代码逻辑,差别只是启动方式。
---
### 6. 注意事项
- **端口**:打包后默认仍然是监听 5000 端口,如果需要改端口,需要改 `app.py` 启动代码(或用环境变量/参数)。
- **数据库文件**
- 代码中使用的是:
- `db_path = os.path.join(os.path.dirname(__file__), "wrench.db")`
- 打包后的 `__file__` 会指向打包目录内的脚本位置,所以:
- 你只要把 `wrench.db` 放到 `dist/backend_api/` 目录下,程序就能正常使用。
- **日志输出**:后端是控制台应用,会在命令行窗口打印日志(方便排查问题)。
---
### 7. 简短回答你的问题
- **问**:后端服务要如何打包?也用同样方式可以吗?
- **答**:可以。后端已经提供了和前端类似的一键打包脚本:
- Windows`backend/build_backend.bat`
- Linux/Mac`backend/build_backend.sh`
你只需要:
1. 进入 `backend` 目录
2. 安装依赖:`pip install -r requirements.txt`
3. 运行打包脚本Windows 用 `.bat`Linux/Mac 用 `.sh`
4. 把 `wrench.db` 复制到 `dist/backend_api/`,运行生成的 `backend_api` 即可。

View File

@ -1,4 +1,8 @@
{
"api": {
"base_url": "http://localhost:5000",
"description": "后端API服务地址例如http://localhost:5000 或 http://192.168.1.100:5000"
},
"wrench": {
"host": "127.0.0.1",
"port": 7888,

47
frontend/build_exe.bat Normal file
View File

@ -0,0 +1,47 @@
@echo off
REM Simple frontend build script (ASCII only, avoid encoding issues)
cd /d "%~dp0"
echo ========================================
echo Building wrench_gui with PyInstaller...
echo ========================================
python -c "import PyInstaller" 2>nul
if errorlevel 1 (
echo PyInstaller not found, installing...
pip install pyinstaller
if errorlevel 1 (
echo Failed to install PyInstaller. Please run: pip install pyinstaller
pause
exit /b 1
)
)
echo Cleaning old build/dist...
if exist build rmdir /s /q build
if exist dist rmdir /s /q dist
echo Running PyInstaller...
python -m PyInstaller wrench_gui.spec
if errorlevel 1 (
echo Build failed!
pause
exit /b 1
)
echo.
echo Build success!
echo Executable: dist\wrench_gui\wrench_gui.exe
echo.
echo Next steps:
echo 1. Copy config.json to executable directory:
echo copy ..\config.json dist\wrench_gui\config.json
echo.
echo 2. Make sure backend API is running (http://localhost:5000)
echo.
echo 3. Run the program:
echo dist\wrench_gui\wrench_gui.exe
echo.
pause

60
frontend/build_exe.sh Normal file
View File

@ -0,0 +1,60 @@
#!/bin/bash
# 电动扳手GUI打包脚本Linux/Mac
echo "========================================"
echo "电动扳手GUI打包脚本"
echo "========================================"
echo ""
# 检查 PyInstaller 是否已安装
if ! python3 -c "import PyInstaller" 2>/dev/null; then
echo "[错误] PyInstaller 未安装"
echo "正在安装 PyInstaller..."
pip3 install pyinstaller
if [ $? -ne 0 ]; then
echo "[错误] PyInstaller 安装失败,请手动执行: pip3 install pyinstaller"
exit 1
fi
fi
echo "[信息] PyInstaller 已安装"
echo ""
# 确保在 frontend 目录下执行
cd "$(dirname "$0")"
# 清理之前的构建文件
echo "[信息] 清理之前的构建文件..."
rm -rf build dist
echo ""
echo "[信息] 当前目录: $(pwd)"
echo "[信息] 开始打包..."
echo ""
# 执行打包
pyinstaller wrench_gui.spec
if [ $? -ne 0 ]; then
echo ""
echo "[错误] 打包失败!"
exit 1
fi
echo ""
echo "========================================"
echo "打包完成!"
echo "========================================"
echo ""
echo "可执行文件位置: dist/wrench_gui/wrench_gui"
echo ""
echo "下一步操作:"
echo "1. 复制配置文件到可执行文件目录:"
echo " cp ../config.json dist/wrench_gui/config.json"
echo ""
echo "2. 确保后端API服务已启动http://localhost:5000"
echo ""
echo "3. 运行程序:"
echo " ./dist/wrench_gui/wrench_gui"
echo ""

View File

@ -2,3 +2,5 @@ requests==2.31.0

View File

@ -10,3 +10,5 @@ pause

View File

@ -8,3 +8,5 @@ python3 wrench_gui.py

View File

@ -49,8 +49,8 @@ class WrenchGUI:
screen_height = self.root.winfo_screenheight()
self.root.geometry(f"{screen_width}x{screen_height}")
# API配置
self.api_base_url = "http://localhost:5000/api"
# API配置(从配置文件读取,如果没有则使用默认值)
self.api_base_url = self.load_api_config()
self.poll_interval = 3 # 轮询间隔(秒)
# 数据
@ -82,6 +82,9 @@ class WrenchGUI:
# 检查测试模式
self.check_test_mode()
# 初始化螺栓列表显示(确保始终可见)
self.update_bolt_list()
# 加载设备列表
self.load_wrench_devices()
@ -90,10 +93,21 @@ class WrenchGUI:
def _create_widgets(self):
"""创建界面组件"""
# 标题
# 使用左右布局:左侧工单列表,右侧其他内容
self.root.update_idletasks()
screen_height = self.root.winfo_screenheight()
# 配置列权重左侧40%右侧60%支持1920和1600分辨率
# 使用相对权重,自适应不同分辨率
self.root.grid_columnconfigure(0, weight=2, minsize=350) # 左侧工单列表和日志最小350像素
self.root.grid_columnconfigure(1, weight=3, minsize=450) # 右侧内容区域最小450像素
# 配置行权重从row=2开始的内容区域可扩展
self.root.grid_rowconfigure(2, weight=1)
# 标题(横跨两列)
title_frame = tk.Frame(self.root, bg="#2c3e50", height=60)
title_frame.pack(fill=tk.X)
title_frame.pack_propagate(False)
title_frame.grid(row=0, column=0, columnspan=2, sticky="ew")
title_frame.grid_propagate(False)
title_label = tk.Label(
title_frame,
@ -114,9 +128,9 @@ class WrenchGUI:
)
self.test_mode_label.place(relx=1.0, rely=0.5, anchor=tk.E, x=-20)
# 工单列表状态区域
# 工单列表状态区域(横跨两列)
status_frame = tk.Frame(self.root, padx=10, pady=5)
status_frame.pack(fill=tk.X)
status_frame.grid(row=1, column=0, columnspan=2, sticky="ew")
self.poll_status_label = tk.Label(
status_frame,
@ -146,13 +160,24 @@ class WrenchGUI:
)
self.refresh_button.pack(side=tk.RIGHT, padx=5)
# 工单列表区域
order_list_frame = tk.LabelFrame(self.root, text="可用工单列表", font=("微软雅黑", 12), padx=10, pady=10)
order_list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# ========== 左侧:工单列表和操作控制 ==========
# 创建左侧主容器(上下布局:工单列表 + 操作控制)
left_container = tk.Frame(self.root)
left_container.grid(row=2, column=0, sticky="nsew", padx=(10, 5), pady=5)
# 配置行权重:工单列表可扩展,操作控制固定不压缩
left_container.grid_rowconfigure(0, weight=3) # 工单列表权重较大
left_container.grid_rowconfigure(1, weight=0, minsize=120) # 操作控制固定最小120像素不压缩
left_container.grid_columnconfigure(0, weight=1)
# 创建工单列表表格
# 工单列表区域
order_list_frame = tk.LabelFrame(left_container, text="可用工单列表", font=("微软雅黑", 12), padx=10, pady=10)
order_list_frame.grid(row=0, column=0, sticky="nsew", padx=0, pady=(0, 5))
order_list_frame.grid_rowconfigure(0, weight=1)
order_list_frame.grid_columnconfigure(0, weight=1)
# 创建工单列表表格(使用相对高度,不固定)
order_columns = ("追溯号", "工序号", "工序名称", "产品", "螺栓数", "状态")
self.order_tree = ttk.Treeview(order_list_frame, columns=order_columns, show="headings", height=5)
self.order_tree = ttk.Treeview(order_list_frame, columns=order_columns, show="headings")
for col in order_columns:
self.order_tree.heading(col, text=col)
@ -164,9 +189,119 @@ class WrenchGUI:
self.order_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
order_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 工单信息区域
info_frame = tk.LabelFrame(self.root, text="当前工单信息", font=("微软雅黑", 12), padx=10, pady=10)
info_frame.pack(fill=tk.X, padx=10, pady=10)
# 操作控制区域(移到左侧,放在工单列表下方)
button_frame = tk.LabelFrame(left_container, text="操作控制", font=("微软雅黑", 12), padx=10, pady=8)
button_frame.grid(row=1, column=0, sticky="ew", padx=0, pady=0)
# 使用grid布局将所有按钮放在同一行确保可见
# 第一列:工单操作
col1_frame = tk.Frame(button_frame)
col1_frame.grid(row=0, column=0, padx=(0, 20), sticky=tk.W)
tk.Label(col1_frame, text="工单操作", font=("微软雅黑", 9, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 3))
self.claim_button = tk.Button(
col1_frame,
text="认领工单",
font=("微软雅黑", 10),
bg="#f39c12",
fg="white",
width=10,
height=1,
cursor="hand2",
command=self.claim_work_order
)
self.claim_button.pack()
# 第二列:设备选择
col2_frame = tk.Frame(button_frame)
col2_frame.grid(row=0, column=1, padx=(0, 20), sticky=tk.W)
tk.Label(col2_frame, text="设备选择", font=("微软雅黑", 9, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 3))
device_select_inner = tk.Frame(col2_frame)
device_select_inner.pack()
tk.Label(device_select_inner, text="选择扳手:", font=("微软雅黑", 9)).pack(side=tk.LEFT, padx=(0, 5))
# 设备状态指示器(圆点)
self.device_status_indicator = tk.Label(
device_select_inner,
text="",
font=("微软雅黑", 12),
fg="#7f8c8d" # 默认灰色
)
self.device_status_indicator.pack(side=tk.LEFT, padx=(0, 3))
self.device_combo = ttk.Combobox(device_select_inner, width=18, state="readonly", font=("微软雅黑", 9))
self.device_combo.pack(side=tk.LEFT, padx=(0, 5))
self.device_combo.bind("<<ComboboxSelected>>", self.on_device_selected)
self.device_manage_button = tk.Button(
device_select_inner,
text="设备管理",
font=("微软雅黑", 8),
bg="#3498db",
fg="white",
width=8,
height=1,
command=self.open_device_manager
)
self.device_manage_button.pack(side=tk.LEFT)
# 第三列:执行控制(放在同一行,确保可见)
col3_frame = tk.Frame(button_frame)
col3_frame.grid(row=0, column=2, padx=(0, 0), sticky=tk.W)
tk.Label(col3_frame, text="执行控制", font=("微软雅黑", 9, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 3))
exec_control_inner = tk.Frame(col3_frame)
exec_control_inner.pack()
self.start_button = tk.Button(
exec_control_inner,
text="开始拧紧",
font=("微软雅黑", 10),
bg="#27ae60",
fg="white",
width=10,
height=1,
cursor="hand2",
command=self.start_process,
state=tk.DISABLED
)
self.start_button.pack(side=tk.LEFT, padx=(0, 10))
self.stop_button = tk.Button(
exec_control_inner,
text="停止",
font=("微软雅黑", 10),
bg="#e74c3c",
fg="white",
width=10,
height=1,
cursor="hand2",
state=tk.DISABLED,
command=self.stop_process
)
self.stop_button.pack(side=tk.LEFT)
# 配置grid列权重
button_frame.grid_columnconfigure(0, weight=0)
button_frame.grid_columnconfigure(1, weight=1)
button_frame.grid_columnconfigure(2, weight=0)
# ========== 右侧:内容区域 ==========
# 创建右侧主容器(上下布局)
right_container = tk.Frame(self.root)
right_container.grid(row=2, column=1, sticky="nsew", padx=(5, 10), pady=5)
# 配置行权重:日志区域可压缩,螺栓列表可压缩
right_container.grid_rowconfigure(0, weight=0) # 工单信息不扩展
right_container.grid_rowconfigure(1, weight=0) # 当前螺栓不扩展
right_container.grid_rowconfigure(2, weight=3) # 螺栓列表可扩展可压缩(主要区域)
right_container.grid_rowconfigure(3, weight=1, minsize=100) # 日志区域可压缩最小100像素
right_container.grid_columnconfigure(0, weight=1)
# 工单信息区域(上下布局,第一行)
info_frame = tk.LabelFrame(right_container, text="当前工单信息", font=("微软雅黑", 12), padx=10, pady=10)
info_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
self.trace_id_label = tk.Label(info_frame, text="追溯号: --", font=("微软雅黑", 10))
self.trace_id_label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
@ -186,9 +321,9 @@ class WrenchGUI:
self.device_label = tk.Label(info_frame, text="扳手设备: --", font=("微软雅黑", 10), fg="#2c3e50")
self.device_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
# 当前螺栓信息
current_frame = tk.LabelFrame(self.root, text="当前螺栓", font=("微软雅黑", 12), padx=10, pady=10)
current_frame.pack(fill=tk.X, padx=10, pady=10)
# 当前螺栓信息(上下布局,第二行)
current_frame = tk.LabelFrame(right_container, text="当前螺栓", font=("微软雅黑", 12), padx=10, pady=10)
current_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
self.current_bolt_label = tk.Label(
current_frame,
@ -213,9 +348,9 @@ class WrenchGUI:
)
self.current_status_label.pack(pady=2)
# 进度条
progress_frame = tk.Frame(self.root, padx=10, pady=5)
progress_frame.pack(fill=tk.X, padx=10)
# 进度条放在current_frame内部
progress_frame = tk.Frame(current_frame, padx=10, pady=5)
progress_frame.pack(fill=tk.X, pady=5)
self.progress_label = tk.Label(progress_frame, text="总进度: 0/0", font=("微软雅黑", 10))
self.progress_label.pack(anchor=tk.W)
@ -223,13 +358,29 @@ class WrenchGUI:
self.progress_bar = ttk.Progressbar(progress_frame, mode='determinate', length=400)
self.progress_bar.pack(fill=tk.X, pady=5)
# 螺栓列表
list_frame = tk.LabelFrame(self.root, text="螺栓列表", font=("微软雅黑", 12), padx=10, pady=10)
list_frame.pack(fill=tk.BOTH, expand=False, padx=10, pady=10)
# 螺栓列表(固定在"当前螺栓"下方、"操作控制"上方,可压缩但始终可见)
# 创建螺栓列表框架
list_frame = tk.LabelFrame(right_container, text="螺栓列表", font=("微软雅黑", 12), padx=10, pady=10)
list_frame.grid(row=2, column=0, sticky="nsew", padx=5, pady=5)
# 螺栓列表通过right_container的row=2的weight=1来控制可以压缩
# 设置最小高度支持1920和1600分辨率
self.root.update_idletasks()
screen_height = self.root.winfo_screenheight()
# 根据分辨率动态调整最小高度1920约180像素1600约150像素
if screen_height >= 1920:
min_list_height = max(180, int(screen_height * 0.15))
elif screen_height >= 1600:
min_list_height = max(150, int(screen_height * 0.15))
else:
min_list_height = max(120, int(screen_height * 0.15))
right_container.grid_rowconfigure(2, weight=1, minsize=min_list_height)
# 创建表格(减少高度,确保按钮区域可见)
# 保存list_frame引用确保始终可见
self.list_frame = list_frame
# 创建表格(使用相对布局,不固定高度)
columns = ("序号", "名称", "扭矩(Nm)", "状态", "实际扭矩", "时间")
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=4)
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings")
# 设置列
for col in columns:
@ -255,129 +406,62 @@ class WrenchGUI:
self.tree.tag_configure("success", foreground="#27ae60", font=("微软雅黑", 10, "bold"))
self.tree.tag_configure("failed", foreground="#e74c3c")
# 控制按钮区域(放在螺栓列表之后,确保可见)
button_frame = tk.LabelFrame(self.root, text="操作控制", font=("微软雅黑", 12), padx=15, pady=15)
button_frame.pack(fill=tk.X, padx=10, pady=10)
# 操作日志区域(移到右侧,放在螺栓列表下方)
log_frame = tk.LabelFrame(right_container, text="操作日志", font=("微软雅黑", 9), padx=5, pady=3)
log_frame.grid(row=3, column=0, sticky="nsew", padx=5, pady=(0, 5))
log_frame.grid_rowconfigure(0, weight=1)
log_frame.grid_columnconfigure(0, weight=1)
# 使用grid布局将所有按钮放在同一行确保可见
# 第一列:工单操作
col1_frame = tk.Frame(button_frame)
col1_frame.grid(row=0, column=0, padx=(0, 20), sticky=tk.W)
tk.Label(col1_frame, text="工单操作", font=("微软雅黑", 10, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 5))
self.claim_button = tk.Button(
col1_frame,
text="认领工单",
font=("微软雅黑", 11, "bold"),
bg="#f39c12",
fg="white",
width=12,
height=2,
cursor="hand2",
command=self.claim_work_order
)
self.claim_button.pack()
# 第二列:设备选择
col2_frame = tk.Frame(button_frame)
col2_frame.grid(row=0, column=1, padx=(0, 20), sticky=tk.W)
tk.Label(col2_frame, text="设备选择", font=("微软雅黑", 10, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 5))
device_select_inner = tk.Frame(col2_frame)
device_select_inner.pack()
tk.Label(device_select_inner, text="选择扳手:", font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=(0, 5))
# 设备状态指示器(圆点)
self.device_status_indicator = tk.Label(
device_select_inner,
text="",
font=("微软雅黑", 12),
fg="#7f8c8d" # 默认灰色
)
self.device_status_indicator.pack(side=tk.LEFT, padx=(0, 3))
self.device_combo = ttk.Combobox(device_select_inner, width=20, state="readonly", font=("微软雅黑", 10))
self.device_combo.pack(side=tk.LEFT, padx=(0, 5))
self.device_combo.bind("<<ComboboxSelected>>", self.on_device_selected)
self.device_manage_button = tk.Button(
device_select_inner,
text="设备管理",
font=("微软雅黑", 9),
bg="#3498db",
fg="white",
width=10,
height=1,
command=self.open_device_manager
)
self.device_manage_button.pack(side=tk.LEFT)
# 第三列:执行控制(放在同一行,确保可见)
col3_frame = tk.Frame(button_frame)
col3_frame.grid(row=0, column=2, padx=(0, 0), sticky=tk.W)
tk.Label(col3_frame, text="执行控制", font=("微软雅黑", 10, "bold"), fg="#2c3e50").pack(anchor=tk.W, pady=(0, 5))
exec_control_inner = tk.Frame(col3_frame)
exec_control_inner.pack()
self.start_button = tk.Button(
exec_control_inner,
text="开始拧紧",
font=("微软雅黑", 11, "bold"),
bg="#27ae60",
fg="white",
width=12,
height=2,
cursor="hand2",
command=self.start_process,
state=tk.DISABLED
)
self.start_button.pack(side=tk.LEFT, padx=(0, 10))
self.stop_button = tk.Button(
exec_control_inner,
text="停止",
font=("微软雅黑", 11, "bold"),
bg="#e74c3c",
fg="white",
width=12,
height=2,
cursor="hand2",
state=tk.DISABLED,
command=self.stop_process
)
self.stop_button.pack(side=tk.LEFT)
# 配置grid列权重
button_frame.grid_columnconfigure(0, weight=0)
button_frame.grid_columnconfigure(1, weight=1)
button_frame.grid_columnconfigure(2, weight=0)
# 日志区域
log_frame = tk.LabelFrame(self.root, text="操作日志", font=("微软雅黑", 10), padx=5, pady=5)
log_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
self.log_text = tk.Text(log_frame, height=5, font=("Consolas", 9))
self.log_text = tk.Text(log_frame, font=("Consolas", 9))
self.log_text.pack(fill=tk.BOTH, expand=True)
log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
self.log_text.configure(yscrollcommand=log_scrollbar.set)
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def get_config_path(self):
"""获取配置文件路径(支持开发环境和打包后的环境)"""
# 如果是打包后的可执行文件config.json 应该在可执行文件同一目录
if getattr(sys, 'frozen', False):
# 打包后的环境
base_path = os.path.dirname(sys.executable)
else:
# 开发环境,在项目根目录
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, "config.json")
def load_api_config(self):
"""从配置文件读取API地址如果没有则使用默认值"""
default_url = "http://localhost:5000/api"
try:
config_path = self.get_config_path()
if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
api_config = config.get("api", {})
api_url = api_config.get("base_url", default_url)
# 确保URL以/api结尾
if not api_url.endswith("/api"):
api_url = api_url.rstrip("/") + "/api"
return api_url
except Exception as e:
print(f"Warning: Failed to load API config: {e}, using default: {default_url}")
return default_url
def check_test_mode(self):
"""检查测试模式"""
try:
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.json")
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
self.test_mode = config.get("test_mode", {}).get("enabled", False)
if self.test_mode:
self.test_mode_label.config(text="⚠️ 测试模式")
self.log("⚠️ 测试模式已启用:失败也算成功", "WARN")
else:
self.test_mode_label.config(text="")
config_path = self.get_config_path()
if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
self.test_mode = config.get("test_mode", {}).get("enabled", False)
if self.test_mode:
self.test_mode_label.config(text="⚠️ 测试模式")
self.log("⚠️ 测试模式已启用:失败也算成功", "WARN")
else:
self.test_mode_label.config(text="")
except Exception as e:
self.log(f"读取配置失败: {e}", "ERROR")
@ -605,11 +689,19 @@ class WrenchGUI:
self.device_label.config(text="扳手设备: 未选择", foreground="#2c3e50")
def update_bolt_list(self):
"""更新螺栓列表"""
"""更新螺栓列表(始终显示,即使没有工单)"""
# 确保螺栓列表框架始终显示,无论连接状态如何
if hasattr(self, 'list_frame'):
self.list_frame.grid() # 强制显示,防止被隐藏
# 始终清空表格,确保表格框架始终可见
self.tree.delete(*self.tree.get_children())
if not self.work_order:
# 没有工单时,显示空表格,但表格框架保持可见
return
self.tree.delete(*self.tree.get_children())
# 有工单时,显示螺栓数据
bolts = self.work_order.get('bolts', [])
for bolt in bolts:
self.tree.insert("", tk.END, values=(

75
frontend/wrench_gui.spec Normal file
View File

@ -0,0 +1,75 @@
# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller 打包配置文件
用于打包 wrench_gui.py 为可执行文件
"""
block_cipher = None
import os
# 获取当前脚本所在目录的父目录(项目根目录)
# 这样 PyInstaller 可以找到 wrench_controller.py
current_dir = os.path.dirname(os.path.abspath('wrench_gui.spec'))
parent_dir = os.path.dirname(current_dir)
a = Analysis(
['wrench_gui.py'],
pathex=[
parent_dir, # 添加父目录到搜索路径,让 PyInstaller 能找到 wrench_controller.py
current_dir, # 当前目录
],
binaries=[],
datas=[
# 包含配置文件(如果程序需要读取)
(os.path.join(parent_dir, 'config.json'), '.'), # 将 config.json 复制到可执行文件目录
],
hiddenimports=[
'wrench_controller',
'device_manager',
'tkinter',
'tkinter.ttk',
'tkinter.messagebox',
'requests',
'json',
'threading',
'socket',
'struct',
'datetime',
'pathlib',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='wrench_gui',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False, # 不显示控制台窗口GUI应用
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None, # 可以添加图标文件路径,如 'icon.ico'
)

View File

@ -0,0 +1,87 @@
========================================
wrench_gui.py 快速打包指南
========================================
【Windows 系统】
1. 打开命令提示符CMD或 PowerShell
2. 进入 frontend 目录:
cd F:\PyPro\TorqueWrench\frontend
3. 执行打包脚本:
build_exe.bat
4. 等待打包完成(约 1-3 分钟)
5. 打包完成后,可执行文件在:
dist\wrench_gui\wrench_gui.exe
6. 复制配置文件:
copy ..\config.json dist\wrench_gui\config.json
7. 运行程序:
dist\wrench_gui\wrench_gui.exe
【Linux/Mac 系统】
1. 打开终端
2. 进入 frontend 目录:
cd /path/to/TorqueWrench/frontend
3. 给脚本添加执行权限:
chmod +x build_exe.sh
4. 执行打包脚本:
./build_exe.sh
5. 等待打包完成(约 1-3 分钟)
6. 打包完成后,可执行文件在:
dist/wrench_gui/wrench_gui
7. 复制配置文件:
cp ../config.json dist/wrench_gui/config.json
8. 运行程序:
./dist/wrench_gui/wrench_gui
【注意事项】
1. 首次打包前,确保已安装 PyInstaller
pip install pyinstaller
2. 确保已安装所有依赖:
pip install -r requirements.txt
3. 打包前确保后端服务可以正常启动
4. 打包后的程序需要 config.json 配置文件才能运行
5. 如果打包失败,查看错误信息,通常是缺少依赖包
【常见问题】
Q: 打包失败,提示找不到模块?
A: 检查是否已安装所有依赖pip install -r requirements.txt
Q: 打包后的程序无法启动?
A: 确保 config.json 在可执行文件同一目录下
Q: 打包后的程序很大?
A: 这是正常的PyInstaller 会打包所有依赖,通常 15-30MB
Q: 如何减小打包后的文件大小?
A: 使用虚拟环境,只安装必需的包,或在 spec 文件中排除不需要的模块
【技术支持】
详细说明请查看:打包说明.md

298
frontend/打包说明.md Normal file
View File

@ -0,0 +1,298 @@
# wrench_gui.py 打包说明
## 一、环境准备
### 1. 安装 Python 依赖
确保已安装所有必需的 Python 包:
```bash
# 进入 frontend 目录
cd frontend
# 安装依赖
pip install -r requirements.txt
# 安装 PyInstaller如果未安装
pip install pyinstaller
```
### 2. 检查文件结构
确保以下文件存在:
```
TorqueWrench/
├── frontend/
│ ├── wrench_gui.py # 主程序
│ ├── device_manager.py # 设备管理模块
│ ├── wrench_gui.spec # PyInstaller 配置文件
│ ├── build_exe.bat # Windows 打包脚本
│ └── build_exe.sh # Linux/Mac 打包脚本
├── wrench_controller.py # 扳手控制模块(父目录)
└── config.json # 配置文件(父目录)
```
---
## 二、打包步骤
### Windows 系统
1. **打开命令提示符或 PowerShell**
2. **进入 frontend 目录**
```cmd
cd F:\PyPro\TorqueWrench\frontend
```
3. **执行打包脚本**
```cmd
build_exe.bat
```
或者手动执行:
```cmd
pyinstaller wrench_gui.spec
```
### Linux/Mac 系统
1. **打开终端**
2. **进入 frontend 目录**
```bash
cd /path/to/TorqueWrench/frontend
```
3. **给脚本添加执行权限**
```bash
chmod +x build_exe.sh
```
4. **执行打包脚本**
```bash
./build_exe.sh
```
或者手动执行:
```bash
pyinstaller wrench_gui.spec
```
---
## 三、打包输出
打包完成后,会在 `frontend/dist/` 目录下生成可执行文件:
### Windows
- **位置**: `frontend/dist/wrench_gui/wrench_gui.exe`
- **大小**: 约 15-30 MB取决于依赖
### Linux/Mac
- **位置**: `frontend/dist/wrench_gui/wrench_gui`
- **大小**: 约 15-30 MB
---
## 四、运行打包后的程序
### 1. 准备配置文件
`config.json` 复制到可执行文件所在目录:
**Windows:**
```cmd
copy ..\config.json dist\wrench_gui\config.json
```
**Linux/Mac:**
```bash
cp ../config.json dist/wrench_gui/config.json
```
### 2. 启动后端服务
确保后端 API 服务已启动:
```bash
# 进入 backend 目录
cd ../backend
# 启动后端Windows
start_backend.bat
# 或启动后端Linux/Mac
./start_backend.sh
```
### 3. 运行可执行文件
**Windows:**
- 双击 `dist/wrench_gui/wrench_gui.exe`
- 或在命令行执行:
```cmd
dist\wrench_gui\wrench_gui.exe
```
**Linux/Mac:**
```bash
./dist/wrench_gui/wrench_gui
```
---
## 五、常见问题
### 1. 打包失败:找不到模块
**问题**: `ModuleNotFoundError: No module named 'xxx'`
**解决**:
- 检查是否已安装所有依赖:`pip install -r requirements.txt`
- 检查 `wrench_gui.spec` 中的 `hiddenimports` 是否包含缺失的模块
### 2. 运行时报错:找不到 config.json
**问题**: 程序启动时报错找不到配置文件
**解决**:
- 确保 `config.json` 在可执行文件同一目录下
- 检查 `wrench_gui.spec` 中的 `datas` 配置是否正确
### 3. 打包后的程序很大(>50MB
**原因**: PyInstaller 会打包所有依赖库
**优化**:
- 使用虚拟环境,只安装必需的包
- 在 `wrench_gui.spec``excludes` 中添加不需要的模块
### 4. 控制台窗口也显示Windows
**问题**: 打包后的程序同时显示 GUI 和控制台窗口
**解决**:
- 检查 `wrench_gui.spec``console=False` 是否设置正确
### 5. 图标设置
如果需要自定义程序图标:
1. 准备图标文件(`.ico` 格式Windows
2. 修改 `wrench_gui.spec`
```python
icon='icon.ico', # 图标文件路径
```
3. 重新打包
---
## 六、打包选项说明
### wrench_gui.spec 关键配置
- **`console=False`**: 不显示控制台窗口GUI应用
- **`upx=True`**: 使用 UPX 压缩(减小文件大小)
- **`datas`**: 包含的数据文件(如配置文件)
- **`hiddenimports`**: 需要显式导入的隐藏模块
### 单文件打包(可选)
如果需要打包成单个 `.exe` 文件,修改 `wrench_gui.spec`
```python
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True, # 改为 True
name='wrench_gui',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
)
```
然后添加 `COLLECT` 部分PyInstaller 会自动处理)。
---
## 七、分发打包后的程序
### 1. 最小分发包
需要包含的文件:
```
wrench_gui/
├── wrench_gui.exe # 可执行文件
├── config.json # 配置文件
└── README.txt # 使用说明(可选)
```
### 2. 完整分发包(推荐)
```
wrench_gui/
├── wrench_gui.exe
├── config.json
├── README.txt
└── 使用说明.pdf # 用户手册
```
### 3. 注意事项
- 确保目标机器已安装必要的运行时库(如 Visual C++ RedistributableWindows
- 如果使用了 UPX 压缩,某些杀毒软件可能会误报,建议关闭 UPX 或添加白名单
---
## 八、快速打包命令(一键打包)
### Windows
```cmd
cd frontend && build_exe.bat
```
### Linux/Mac
```bash
cd frontend && chmod +x build_exe.sh && ./build_exe.sh
```
---
## 九、验证打包结果
打包完成后,建议测试:
1. **运行可执行文件**
- 检查是否能正常启动
- 检查是否能连接到后端 API
- 检查设备管理功能是否正常
2. **检查文件大小**
- 正常大小15-30 MB
- 如果过大(>50MB检查依赖
3. **测试功能**
- 工单列表加载
- 设备选择
- 拧紧操作
- 数据提交
---
## 十、技术支持
如果遇到打包问题:
1. 查看 PyInstaller 官方文档https://pyinstaller.org/
2. 检查错误日志:`build/wrench_gui/warn-wrench_gui.txt`
3. 使用调试模式:在 `wrench_gui.spec` 中设置 `debug=True`

View File

@ -79,3 +79,5 @@ while true; do
done

View File

@ -72,3 +72,5 @@ echo " 查看日志: sudo journalctl -u ssh-tunnel -f"
echo " 禁用自启: sudo systemctl disable ssh-tunnel"

View File

@ -105,3 +105,5 @@ echo ""
echo " 或者使用自动重连脚本(见下方)"

View File

@ -9,3 +9,5 @@ pause

View File

@ -7,3 +7,5 @@ python3 wrench_simulator.py

View File

@ -81,3 +81,5 @@ if __name__ == "__main__":

View File

@ -72,3 +72,5 @@ if __name__ == "__main__":