Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99bb8e27b6 | ||
|
|
5a05bfe9a2 | ||
|
|
383e6d3aaa | ||
|
|
dce6f4a14a | ||
|
|
a8c4a929ff | ||
|
|
3be6b964b5 | ||
|
|
38f0062308 | ||
|
|
397c7398c8 | ||
|
|
94f8ae054c | ||
|
|
f1540ec0c3 | ||
|
|
4495b1683d |
240
README.md
240
README.md
@ -4,118 +4,136 @@
|
||||
|
||||
腾智微丝产线包装系统是一个基于PySide6(Qt for Python)开发的工业自动化控制系统,用于管理和监控微丝产线的包装过程。该系统集成了相机监控、称重、条码扫描、PLC通信等功能,为微丝产品的质量控制与包装提供全面的解决方案。
|
||||
|
||||
## 项目架构
|
||||
## 系统架构
|
||||
|
||||
系统采用典型的MVC(模型-视图-控制器)架构:
|
||||
系统采用MVC(模型-视图-控制器)架构,并集成了多种工业通信协议:
|
||||
|
||||
1. **模型层(Model)**:
|
||||
- 采用DAO(数据访问对象)模式访问数据库
|
||||
- 支持多种数据库(SQLite、PostgreSQL、MySQL)
|
||||
- 主要数据表包括用户表、检验配置表、检验数据表、电力消耗表等
|
||||
- 核心数据表包括用户表、检验配置表、检验数据表、电力消耗表、托盘类型表等
|
||||
|
||||
2. **视图层(View)**:
|
||||
- 基于PySide6构建UI界面
|
||||
- 主要界面包括登录界面、主窗口、设置界面等
|
||||
- 采用分离的UI类设计,如LoginUI、MainWindowUI等
|
||||
- 界面包括登录界面、主窗口、设置界面、相机配置界面、检验设置等
|
||||
- 支持多语言界面(中文简体为主)
|
||||
|
||||
3. **控制器层(Controller)**:
|
||||
- 主要控制逻辑在widgets目录下的类中实现
|
||||
- 使用信号槽机制进行组件间通信
|
||||
- 采用单例模式管理全局资源
|
||||
- 主要控制逻辑在widgets目录下实现
|
||||
- 使用Qt信号槽机制进行组件间通信
|
||||
- 采用单例模式管理全局资源和服务
|
||||
|
||||
4. **工具层(Utils)**:
|
||||
- 提供各种工具类,如配置加载器、Modbus通信、串口管理等
|
||||
- 采用单例模式确保资源共享
|
||||
- 配置加载器(config_loader.py)
|
||||
- 本地图像播放器(local_image_player.py)
|
||||
- 串口管理器(serial_manager.py)
|
||||
- Modbus通信工具(modbus_utils.py和modbus_monitor.py)
|
||||
- 电力监控(electricity_monitor.py)
|
||||
- 键盘监听器(keyboard_listener.py)
|
||||
- 数据库工具(sql_utils.py)
|
||||
- 检验配置管理器(inspection_config_manager.py)
|
||||
|
||||
5. **硬件集成层**:
|
||||
- **相机子系统**:基于海康威视SDK进行集成
|
||||
- **串口通信**:与称重设备、扫描器等外设通信
|
||||
5. **硬件集成**:
|
||||
- **相机子系统**:基于海康威视SDK集成(camera目录)
|
||||
- **串口通信**:支持多种设备(称重设备、扫描器等)
|
||||
- **Modbus通信**:与PLC设备通信
|
||||
- **本地图像模式**:支持离线使用本地图像序列进行模拟测试
|
||||
- **本地图像模式**:支持离线使用本地图像序列
|
||||
|
||||
## 技术栈
|
||||
|
||||
1. **前端技术**:
|
||||
- PySide6(Qt for Python)用于GUI开发
|
||||
- 使用Qt的信号槽机制实现组件间通信
|
||||
- Qt样式表(QSS)用于界面美化
|
||||
|
||||
2. **后端技术**:
|
||||
- Python 3.7+作为主要开发语言
|
||||
- 支持SQLite、PostgreSQL和MySQL数据库
|
||||
- Modbus协议用于与PLC设备通信
|
||||
- 多线程处理耗时操作
|
||||
- Python 3.7+
|
||||
- 支持多种数据库(SQLite、PostgreSQL、MySQL)
|
||||
- 多线程处理模型
|
||||
- 进程间通信
|
||||
|
||||
3. **通信技术**:
|
||||
- Modbus TCP用于与PLC设备通信
|
||||
- 串口通信用于与称重设备、条码扫描器等外设通信
|
||||
- 支持键盘监听用于扫码枪等输入设备
|
||||
- Modbus TCP协议(与PLC设备通信)
|
||||
- 串口通信(RS-232/485协议)
|
||||
- HTTP RESTful API(API模式)
|
||||
|
||||
4. **设计模式**:
|
||||
- 单例模式(配置加载器、电力监控器、相机管理器等)
|
||||
- DAO模式(数据访问)
|
||||
- 观察者模式(信号槽)
|
||||
- 单例模式(ConfigLoader、ElectricityMonitor等)
|
||||
- DAO模式(数据访问层)
|
||||
- 观察者模式(基于Qt信号槽)
|
||||
- 工厂模式(数据库连接)
|
||||
- 策略模式(不同通信协议的实现)
|
||||
|
||||
## 代码结构
|
||||
|
||||
1. **主要目录**:
|
||||
- `widgets/`:包含所有窗口控制器类
|
||||
- `ui/`:包含所有UI定义类
|
||||
- `utils/`:包含工具类
|
||||
- `dao/`:包含数据访问对象
|
||||
- `db/`:包含数据库文件
|
||||
- `config/`:包含配置文件
|
||||
- `logs/`:包含日志文件
|
||||
- `camera/`:包含相机模块和SDK接口类
|
||||
- `apis/`:API接口(用于接口模式)
|
||||
- `main.py`:程序入口,初始化应用
|
||||
- `widgets/`:窗口控制器类(包含主窗口、设置窗口、相机窗口等)
|
||||
- `ui/`:UI定义类(使用PySide6的界面描述)
|
||||
- `utils/`:工具类(配置、串口、Modbus、电力监控等)
|
||||
- `dao/`:数据访问对象(用户、检验、电力数据等)
|
||||
- `db/`:数据库文件和工具
|
||||
- `config/`:配置文件目录
|
||||
- `logs/`:日志文件存储目录
|
||||
- `camera/`:相机模块和SDK集成
|
||||
- `apis/`:API接口定义(用于接口模式)
|
||||
- `inspection/`:检验相关业务逻辑
|
||||
|
||||
2. **核心文件**:
|
||||
- `main.py`:程序入口点
|
||||
- `modbus_server.py`:Modbus服务器模拟
|
||||
- `utils/config_loader.py`:配置加载器
|
||||
- `utils/local_image_player.py`:本地图像序列播放器
|
||||
- `widgets/camera_manager.py`:相机管理器
|
||||
- `dao/login_dao.py`:用户认证数据访问
|
||||
2. **关键组件**:
|
||||
- **主窗口**(`widgets/main_window.py`):系统核心界面,集成所有功能
|
||||
- **相机管理器**(`widgets/camera_manager.py`):管理相机设备连接与图像获取
|
||||
- **串口管理器**(`utils/serial_manager.py`):管理多个串口设备连接
|
||||
- **Modbus工具**(`utils/modbus_utils.py`):Modbus协议通信
|
||||
- **电力监控**(`utils/electricity_monitor.py`):监控电力消耗
|
||||
- **检验DAO**(`dao/inspection_dao.py`):处理检验数据存储与检索
|
||||
- **本地图像播放器**(`utils/local_image_player.py`):模拟相机图像源
|
||||
|
||||
## 系统特点与功能
|
||||
|
||||
1. **两种运行模式**:
|
||||
- 单机模式(standalone):完整的独立系统
|
||||
- 接口模式(api):作为其他系统的接口组件运行
|
||||
1. **运行模式**:
|
||||
- **单机模式**(standalone):完整独立系统
|
||||
- **API模式**(api):作为其他系统的接口组件运行
|
||||
- **开发/测试模式**:支持模拟器和测试工具
|
||||
|
||||
2. **本地图像模式**:
|
||||
- 支持本地图像序列播放,模拟相机实时画面
|
||||
- 适用于开发测试和演示场景,无需连接实际相机设备
|
||||
- 可调整播放帧率、设置循环播放等参数
|
||||
- 支持从本地图像序列模拟相机画面
|
||||
- 可配置播放速率、循环模式
|
||||
- 支持多种图像格式(JPG、PNG、BMP等)
|
||||
|
||||
3. **相机管理**:
|
||||
- 支持海康威视相机设备管理
|
||||
- 自动发现和枚举可用相机设备
|
||||
- 相机参数设置和保存(曝光、增益、帧率等)
|
||||
- 支持海康威视相机设备
|
||||
- 相机参数配置(曝光、增益、帧率等)
|
||||
- 实时图像显示和处理
|
||||
|
||||
4. **Modbus通信**:
|
||||
- 与PLC设备进行Modbus TCP通信
|
||||
- 支持自动模式和手动模式切换
|
||||
- 内置Modbus服务器模拟功能,便于开发测试
|
||||
4. **通信功能**:
|
||||
- **Modbus通信**:支持读写PLC寄存器
|
||||
- **串口通信**:支持多种设备协议(称重设备、扫描器等)
|
||||
- **键盘监听**:支持扫码枪等输入设备
|
||||
|
||||
5. **电力监控**:
|
||||
- 实时监控和记录电力消耗数据
|
||||
- 数据可视化和统计分析
|
||||
5. **检验管理**:
|
||||
- 检验配置管理
|
||||
- 检验数据记录和分析
|
||||
- 检验报告生成
|
||||
|
||||
6. **数据管理**:
|
||||
- 支持用户认证和权限控制
|
||||
- 检验数据的采集、存储和分析
|
||||
- 托盘类型管理
|
||||
6. **电力监控**:
|
||||
- 定时记录电力消耗数据
|
||||
- 电力数据分析和报表
|
||||
|
||||
7. **日志系统**:
|
||||
- 详细的日志记录
|
||||
7. **安全与用户管理**:
|
||||
- 用户认证系统
|
||||
- 操作日志记录
|
||||
- 系统异常处理和恢复
|
||||
|
||||
8. **日志系统**:
|
||||
- 多级日志记录
|
||||
- 自动日志轮换
|
||||
- 支持多级日志级别
|
||||
- 异常信息捕获与记录
|
||||
|
||||
## 安装与运行
|
||||
|
||||
### 系统要求
|
||||
- Python 3.7+
|
||||
- PySide6 6.2.0+
|
||||
- 相关依赖库(见requirements.txt)
|
||||
- 支持Windows、macOS、Linux操作系统
|
||||
|
||||
### 安装步骤
|
||||
@ -128,7 +146,7 @@
|
||||
2. 配置应用:
|
||||
- 配置文件位于`config/app_config.json`
|
||||
- 默认使用SQLite数据库,位于`db/jtDB.db`
|
||||
- 可配置数据库连接、相机参数、Modbus设置等
|
||||
- 可配置数据库连接、相机参数、串口设置等
|
||||
|
||||
3. 运行应用:
|
||||
```
|
||||
@ -137,7 +155,21 @@
|
||||
|
||||
### 配置说明
|
||||
|
||||
1. **数据库配置**:
|
||||
1. **应用配置**:
|
||||
```json
|
||||
"app": {
|
||||
"name": "腾智微丝产线包装系统",
|
||||
"version": "1.0.0",
|
||||
"features": {
|
||||
"enable_serial_ports": true,
|
||||
"enable_keyboard_listener": false,
|
||||
"enable_camera": false
|
||||
},
|
||||
"mode": "api" // 或 "standalone"
|
||||
}
|
||||
```
|
||||
|
||||
2. **数据库配置**:
|
||||
```json
|
||||
"database": {
|
||||
"default": "sqlite",
|
||||
@ -157,43 +189,64 @@
|
||||
}
|
||||
```
|
||||
|
||||
2. **本地图像模式配置**:
|
||||
3. **相机配置**:
|
||||
```json
|
||||
"local_image_mode": {
|
||||
"camera": {
|
||||
"enabled": false,
|
||||
"default_exposure": 20000,
|
||||
"default_gain": 10,
|
||||
"default_framerate": 30,
|
||||
"local_mode": {
|
||||
"enabled": true,
|
||||
"folder_path": "/path/to/images",
|
||||
"framerate": 15,
|
||||
"loop": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **应用模式配置**:
|
||||
4. **Modbus配置**:
|
||||
```json
|
||||
"app": {
|
||||
"mode": "standalone", // 或 "api"
|
||||
"features": {
|
||||
"enable_serial_ports": false,
|
||||
"enable_keyboard_listener": false,
|
||||
"enable_camera": false
|
||||
"modbus": {
|
||||
"host": "localhost",
|
||||
"port": "5020"
|
||||
}
|
||||
```
|
||||
|
||||
5. **串口配置**:
|
||||
```json
|
||||
"serial": {
|
||||
"cz": { // 称重设备
|
||||
"code": "cz",
|
||||
"ser": "COM2",
|
||||
"port": "9600",
|
||||
"data_bits": 8,
|
||||
"stop_bits": 1,
|
||||
"parity": "N",
|
||||
"timeout": 1
|
||||
},
|
||||
// 其他串口设备配置...
|
||||
}
|
||||
```
|
||||
|
||||
## 开发与扩展
|
||||
|
||||
系统采用模块化设计,可以方便地进行功能扩展:
|
||||
系统设计采用模块化和松耦合原则,便于扩展:
|
||||
|
||||
1. **添加新的数据源**:扩展DAO层,实现对应的数据访问对象
|
||||
1. **添加新的数据源**:
|
||||
- 在`dao/`目录下创建新的DAO类
|
||||
- 遵循现有DAO模式和接口约定
|
||||
|
||||
2. **添加新的硬件支持**:
|
||||
- 参考`widgets/camera_manager.py`添加新的硬件管理器
|
||||
- 参考`utils/local_image_player.py`添加新的设备模拟器
|
||||
- 在`utils/`目录下创建新的硬件管理类
|
||||
- 实现相应的信号机制与主程序通信
|
||||
|
||||
3. **扩展UI界面**:
|
||||
- 在`ui/`目录下创建新的UI类
|
||||
- 在`widgets/`目录下创建对应的控制器类
|
||||
- 在`ui/`目录下创建新的UI定义类
|
||||
- 在`widgets/`目录下创建对应控制器类
|
||||
|
||||
4. **添加新的通信协议**:参考`modbus_server.py`实现新的通信接口
|
||||
4. **添加新的通信协议**:
|
||||
- 参考`utils/modbus_utils.py`和`utils/serial_manager.py`实现
|
||||
|
||||
## 注意事项
|
||||
|
||||
@ -201,24 +254,29 @@
|
||||
|
||||
2. 日志文件位于`logs/`目录,按日期自动轮换
|
||||
|
||||
3. 如果使用相机功能,需确保已安装海康威视SDK
|
||||
3. 使用相机功能前,确保已安装相应的SDK
|
||||
|
||||
4. 在测试环境中,可使用本地图像模式替代实际相机
|
||||
4. 在开发/测试环境中,可使用本地图像模式和Modbus服务器模拟
|
||||
|
||||
5. 系统支持热插拔设备,但建议在操作前确认设备状态
|
||||
|
||||
## 故障排除
|
||||
|
||||
1. **日志查看**:
|
||||
- 检查`logs/`目录下的日志文件
|
||||
- 日志格式为`app_YYYY-MM-DD.log`
|
||||
- 检查`logs/`目录下的日志文件(`app_YYYY-MM-DD.log`格式)
|
||||
- 查看控制台输出信息
|
||||
|
||||
2. **数据库问题**:
|
||||
- 确认数据库配置正确
|
||||
- 查看数据库连接错误日志
|
||||
- 检查数据库文件权限
|
||||
- 可使用SQLite浏览工具查看数据库内容
|
||||
|
||||
3. **相机连接问题**:
|
||||
- 确认相机驱动已正确安装
|
||||
- 尝试启用本地图像模式进行测试
|
||||
3. **设备连接问题**:
|
||||
- 检查设备是否正确连接
|
||||
- 确认设备驱动已安装
|
||||
- 验证设备配置参数(串口、波特率等)
|
||||
|
||||
4. **Modbus通信问题**:
|
||||
- 检查PLC设备IP和端口配置
|
||||
- 使用`modbus_server.py`进行通信测试
|
||||
- 检查网络连接和防火墙设置
|
||||
- 验证Modbus寄存器地址配置
|
||||
@ -2,10 +2,50 @@ from utils.api_utils import ApiUtils
|
||||
import logging
|
||||
import json
|
||||
class GcApi:
|
||||
"""工程API接口类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化托盘API工具类"""
|
||||
"""初始化API接口"""
|
||||
self.api_utils = ApiUtils()
|
||||
|
||||
|
||||
def ismt_option(self, params):
|
||||
"""
|
||||
标记是否满托
|
||||
|
||||
Args:
|
||||
ismt: 是否满托
|
||||
corp_id: 公司ID
|
||||
tray_id: 托盘ID
|
||||
ip: 机器IP
|
||||
|
||||
Returns:
|
||||
dict: 返回结果
|
||||
"""
|
||||
try:
|
||||
# API 配置中的键名
|
||||
api_key = "ismt_option"
|
||||
# 构建 form-data 格式的数据
|
||||
data = {
|
||||
"ismt": params.get("ismt", False),
|
||||
"xpack":params.get("tray_id", ""),
|
||||
"nw_ip":params.get("ip", ""),
|
||||
"data_corp":params.get("corp_id", ""),
|
||||
}
|
||||
# 将工程号作为参数传递,使用 data 参数传递 form-data 格式数据
|
||||
response = self.api_utils.post(api_key, data=data)
|
||||
|
||||
# 请求失败时返回空数据
|
||||
if not response.get("status", False):
|
||||
return {
|
||||
"success": False,
|
||||
"message": "标记是否满托失败",
|
||||
"data": None
|
||||
}
|
||||
return response
|
||||
except Exception as e:
|
||||
logging.error(f"标记是否满托失败: {str(e)}")
|
||||
return None
|
||||
def get_gc_info(self, gc_code):
|
||||
"""
|
||||
获取GC信息
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
"get_gc_info": "/jsjt/xcsc/tprk/getBZGCInfoToWsbz.do",
|
||||
"get_order_info": "/jsjt/xcsc/tprk/getXsddBzrkGridListToWsbz.do",
|
||||
"add_order_info": "/jsjt/xcsc/tprk/bzrkAdd01.do",
|
||||
"get_xpack": "/jsjt/xcsc/tprk/getXpackToWsbz.do"
|
||||
"get_xpack": "/jsjt/xcsc/tprk/getXpackToWsbz.do",
|
||||
"ismt_option": "/jsjt/xcsc/tprk/ismtOptioTonWsbz.do"
|
||||
},
|
||||
"database": {
|
||||
"default": "sqlite",
|
||||
@ -115,6 +116,7 @@
|
||||
}
|
||||
},
|
||||
"electricity": {
|
||||
"auto_start": true
|
||||
"auto_start": true,
|
||||
"interval_minutes": 30
|
||||
}
|
||||
}
|
||||
@ -122,3 +122,55 @@ class ElectricityDAO:
|
||||
except Exception as e:
|
||||
logging.error(f"获取电力消耗数据失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_electricity_statistics(self):
|
||||
"""获取电力消耗统计数据(日/月/年/累计)
|
||||
|
||||
Returns:
|
||||
dict: 包含日、月、年、累计用电量的字典
|
||||
"""
|
||||
try:
|
||||
# 使用提供的SQL查询
|
||||
sql = """
|
||||
SELECT CASE
|
||||
WHEN sync_time >= DATE('now') AND sync_time < DATE('now', '+1 day')
|
||||
THEN MAX(electricity_number) - MIN(electricity_number)
|
||||
ELSE 0 END AS electricity_number_day,
|
||||
CASE
|
||||
WHEN sync_time >= DATE('now', 'start of month') AND sync_time < DATE('now', 'start of month', '+1 month')
|
||||
THEN MAX(electricity_number) - MIN(electricity_number)
|
||||
ELSE 0 END AS electricity_number_month,
|
||||
CASE
|
||||
WHEN sync_time >= DATE('now', 'start of year') AND sync_time < DATE('now', 'start of year', '+1 year')
|
||||
THEN MAX(electricity_number) - MIN(electricity_number)
|
||||
ELSE 0 END AS electricity_number_year,
|
||||
MAX(electricity_number) - MIN(electricity_number) AS electricity_number_all
|
||||
FROM wsbz_electricity_consumption
|
||||
"""
|
||||
|
||||
self.db.cursor.execute(sql)
|
||||
row = self.db.cursor.fetchone()
|
||||
|
||||
if row:
|
||||
data = {
|
||||
'day': row[0] if row[0] is not None else 0,
|
||||
'month': row[1] if row[1] is not None else 0,
|
||||
'year': row[2] if row[2] is not None else 0,
|
||||
'all': row[3] if row[3] is not None else 0
|
||||
}
|
||||
return data
|
||||
else:
|
||||
return {
|
||||
'day': 0,
|
||||
'month': 0,
|
||||
'year': 0,
|
||||
'all': 0
|
||||
}
|
||||
except Exception as e:
|
||||
logging.error(f"获取电力消耗统计数据失败: {str(e)}")
|
||||
return {
|
||||
'day': 0,
|
||||
'month': 0,
|
||||
'year': 0,
|
||||
'all': 0
|
||||
}
|
||||
@ -253,7 +253,7 @@ class InspectionDAO:
|
||||
gzl = ?, maxsl = ?, cz = ?, size = ?, cd = ?, luno = ?,
|
||||
qfqd = ?, pono = ?, xj = ?, ysl = ?, dycz = ?,
|
||||
zx_code = ?, edit_id = ?, remarks = ?, zx_name = ?,
|
||||
bccd = ? ,tccd = ?, zzyq = ?
|
||||
bccd = ? ,tccd = ?, zzyq = ?, customer = ?,customerexp = ?,bz_bqd = ?,bz_tqd = ?,type_name = ?,remarks_hb=?
|
||||
WHERE ddmo = ?
|
||||
"""
|
||||
params = (
|
||||
@ -289,6 +289,12 @@ class InspectionDAO:
|
||||
data.get("bccd", ""),
|
||||
data.get("tccd", ""),
|
||||
data.get("zzyq", ""),
|
||||
data.get("customer", ""),
|
||||
data.get("customerexp", ""),
|
||||
data.get("bz_bqd", ""),
|
||||
data.get("bz_tqd", ""),
|
||||
data.get("type_name", ""),
|
||||
data.get("remarks_hb", ""),
|
||||
data.get("mo", "") # WHERE 条件参数
|
||||
)
|
||||
logging.info(f"更新订单信息: ddmo={data.get('mo', '')}")
|
||||
@ -299,10 +305,10 @@ class InspectionDAO:
|
||||
data_corp, user_id, user_name, gzl_zl, ddmo, xpack,
|
||||
qd, spack_type, mxzs, jt, ddnote, code, type,
|
||||
lable, lib, gzl, maxsl, cz, size, cd, luno, qfqd,
|
||||
pono, xj, ysl, dycz, zx_code, edit_id, remarks,zx_name,bccd,tccd,zzyq
|
||||
pono, xj, ysl, dycz, zx_code, edit_id, remarks,zx_name,bccd,tccd,zzyq,customer,customerexp,bz_bqd,bz_tqd,type_name,remarks_hb
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
"""
|
||||
params = (
|
||||
@ -338,7 +344,13 @@ class InspectionDAO:
|
||||
data.get("zx_name", ""),
|
||||
data.get("bccd", ""),
|
||||
data.get("tccd", ""),
|
||||
data.get("zzyq", "")
|
||||
data.get("zzyq", ""),
|
||||
data.get("customer", ""),
|
||||
data.get("customerexp", ""),
|
||||
data.get("bz_bqd", ""),
|
||||
data.get("bz_tqd", ""),
|
||||
data.get("type_name", ""),
|
||||
data.get("remarks_hb", "")
|
||||
)
|
||||
logging.info(f"插入新订单信息: ddmo={data.get('mo', '')}")
|
||||
|
||||
@ -751,8 +763,8 @@ class InspectionDAO:
|
||||
"""
|
||||
try:
|
||||
sql = """
|
||||
SELECT distinct data_corp,user_id,user_name,gzl_zl,mzl,ddmo,qd,spack_type,mxzs,jt,ddnote,code,
|
||||
type,lable,lib,gzl,maxsl,cz,size,cd,luno,qfqd,pono,xj,ysl,dycz,edit_id,remarks,zx_name
|
||||
SELECT DISTINCT data_corp, user_id, user_name, gzl_zl, mzl, ddmo, xpack, qd, spack_type, mxzs, jt, ddnote, code, type, lable, lib, gzl, maxsl, cz, size, cd, luno,
|
||||
qfqd, pono, xj, ysl, dycz, zx_code, edit_id, remarks, zx_name, bccd, tccd, zzyq, customer, customerexp, bz_bqd as bqd, bz_tqd as tqd, type_name, remarks_hb
|
||||
FROM wsbz_order_info WHERE ddmo = ?
|
||||
"""
|
||||
params = (order_id,)
|
||||
@ -816,3 +828,82 @@ class InspectionDAO:
|
||||
except Exception as e:
|
||||
logging.error(f"获取订单其他信息失败: {str(e)}")
|
||||
return {}
|
||||
def get_order_statistics(self):
|
||||
"""获取订单数量和产量统计数据(日/月/年/累计)
|
||||
|
||||
Returns:
|
||||
dict: 包含日、月、年、累计订单数量和产量的字典
|
||||
"""
|
||||
try:
|
||||
# 使用提供的SQL查询
|
||||
sql = """
|
||||
SELECT CASE
|
||||
WHEN create_time >= DATE('now') AND create_time < DATE('now', '+1 day')
|
||||
THEN COUNT(DISTINCT order_id)
|
||||
ELSE 0 END AS order_cnt_day,
|
||||
CASE
|
||||
WHEN create_time >= DATE('now', 'start of month') AND create_time < DATE('now', 'start of month', '+1 month')
|
||||
THEN COUNT(DISTINCT order_id)
|
||||
ELSE 0 END AS order_cnt_month,
|
||||
CASE
|
||||
WHEN create_time >= DATE('now', 'start of year') AND create_time < DATE('now', 'start of year', '+1 year')
|
||||
THEN COUNT(DISTINCT order_id)
|
||||
ELSE 0 END AS order_cnt_year,
|
||||
COUNT(DISTINCT order_id) AS order_cnt_all,
|
||||
CASE
|
||||
WHEN create_time >= DATE('now') AND create_time < DATE('now', '+1 day')
|
||||
THEN SUM(value)
|
||||
ELSE 0 END AS order_num_day,
|
||||
CASE
|
||||
WHEN create_time >= DATE('now', 'start of month') AND
|
||||
create_time < DATE('now', 'start of month', '+1 month')
|
||||
THEN SUM(value)
|
||||
ELSE 0 END AS order_num_month,
|
||||
CASE
|
||||
WHEN create_time >= DATE('now', 'start of year') AND
|
||||
create_time < DATE('now', 'start of year', '+1 year')
|
||||
THEN SUM(value)
|
||||
ELSE 0 END AS order_num_year,
|
||||
CASE WHEN position = 12 THEN SUM(value) ELSE 0 END AS order_num_all
|
||||
FROM wsbz_inspection_data WHERE position = 12
|
||||
"""
|
||||
|
||||
with SQLUtils('sqlite', database='db/jtDB.db') as db:
|
||||
db.cursor.execute(sql)
|
||||
row = db.cursor.fetchone()
|
||||
|
||||
if row:
|
||||
data = {
|
||||
'order_cnt_day': row[0] if row[0] is not None else 0,
|
||||
'order_cnt_month': row[1] if row[1] is not None else 0,
|
||||
'order_cnt_year': row[2] if row[2] is not None else 0,
|
||||
'order_cnt_all': row[3] if row[3] is not None else 0,
|
||||
'order_num_day': float(row[4]) if row[4] is not None else 0,
|
||||
'order_num_month': float(row[5]) if row[5] is not None else 0,
|
||||
'order_num_year': float(row[6]) if row[6] is not None else 0,
|
||||
'order_num_all': float(row[7]) if row[7] is not None else 0
|
||||
}
|
||||
return data
|
||||
else:
|
||||
return {
|
||||
'order_cnt_day': 0,
|
||||
'order_cnt_month': 0,
|
||||
'order_cnt_year': 0,
|
||||
'order_cnt_all': 0,
|
||||
'order_num_day': 0,
|
||||
'order_num_month': 0,
|
||||
'order_num_year': 0,
|
||||
'order_num_all': 0
|
||||
}
|
||||
except Exception as e:
|
||||
logging.error(f"获取订单数量和产量统计数据失败: {str(e)}")
|
||||
return {
|
||||
'order_cnt_day': 0,
|
||||
'order_cnt_month': 0,
|
||||
'order_cnt_year': 0,
|
||||
'order_cnt_all': 0,
|
||||
'order_num_day': 0,
|
||||
'order_num_month': 0,
|
||||
'order_num_year': 0,
|
||||
'order_num_all': 0
|
||||
}
|
||||
BIN
db/jtDB.db
BIN
db/jtDB.db
Binary file not shown.
@ -2,7 +2,7 @@ from pymodbus.client import ModbusTcpClient
|
||||
import time
|
||||
client = ModbusTcpClient('localhost', port=5020)
|
||||
client.connect()
|
||||
# client.write_registers(address=11, values=[2247])
|
||||
client.write_registers(address=11, values=[212])
|
||||
# client.write_registers(address=3, values=[0])
|
||||
time.sleep(2)
|
||||
client.write_registers(address=0, values=[0])
|
||||
@ -11,8 +11,8 @@ client.write_registers(address=4, values=[0])
|
||||
# client.write_registers(address=30, values=[25])
|
||||
# client.write_registers(address=5, values=[16])
|
||||
# 贴标完成
|
||||
# client.write_registers(address=24, values=[1])s
|
||||
# client.write_registers(address=13, values=[1])
|
||||
# client.write_registers(address=24, values=[1])
|
||||
client.write_registers(address=13, values=[1])
|
||||
|
||||
|
||||
result = client.read_holding_registers(address=0, count=1)
|
||||
|
||||
@ -1,12 +1,33 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QMainWindow, QWidget, QLabel, QGridLayout, QVBoxLayout, QHBoxLayout,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView, QFrame, QSplitter,
|
||||
QPushButton, QLineEdit, QAbstractItemView, QComboBox
|
||||
QPushButton, QLineEdit, QAbstractItemView, QComboBox, QSizePolicy
|
||||
)
|
||||
from PySide6.QtGui import QFont, QAction, QBrush, QColor
|
||||
from PySide6.QtCore import Qt, QDateTime, QTimer
|
||||
|
||||
class MainWindowUI(QMainWindow):
|
||||
# 定义字段映射为类属性,方便外部引用
|
||||
FIELD_MAPPING = {
|
||||
"客户": "customerexp",
|
||||
"规格": "size",
|
||||
"材质": "cz",
|
||||
"种类": "type_name",
|
||||
"钢厂": "cd",
|
||||
"炉号": "luno",
|
||||
"轴型": "zx_name",
|
||||
"标签": "template_name",
|
||||
"打印材质": "cz",
|
||||
"底托类型": "spack_type",
|
||||
"强度范围": "qx",
|
||||
"强度": "qd",
|
||||
"延伸要求": "ysl",
|
||||
"线材类型": "jz",
|
||||
"轴重要求": "zzyq",
|
||||
"线径公差": "xj",
|
||||
"备注": "remarks_hb"
|
||||
}
|
||||
|
||||
def __init__(self,username):
|
||||
super().__init__()
|
||||
self.username = username
|
||||
@ -217,19 +238,146 @@ class MainWindowUI(QMainWindow):
|
||||
self.material_layout.setSpacing(0)
|
||||
|
||||
# 上料区标签
|
||||
self.material_label = QLabel("上料区")
|
||||
self.material_label = QLabel("托盘入库")
|
||||
self.material_label.setFont(self.normal_font)
|
||||
self.material_label.setAlignment(Qt.AlignCenter)
|
||||
self.material_label.setFixedWidth(100) # 设置固定宽度
|
||||
self.material_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
|
||||
self.material_layout.addWidget(self.material_label)
|
||||
|
||||
# 上料区内容 - 这里可以添加更多控件
|
||||
# 上料区内容 - 订单信息表格
|
||||
self.material_content = QWidget()
|
||||
# 使用透明背景,让相机画面可以正常显示
|
||||
self.material_content.setStyleSheet("background-color: transparent;")
|
||||
self.material_content.setStyleSheet("background-color: white;")
|
||||
self.material_content_layout = QVBoxLayout(self.material_content)
|
||||
self.material_content_layout.setContentsMargins(0, 0, 0, 0) # 移除内边距以便相机画面填满
|
||||
self.material_content_layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
# 创建订单号输入框
|
||||
self.order_info_layout = QHBoxLayout()
|
||||
self.order_no_label = QLabel("订单号:")
|
||||
self.order_no_label.setFont(self.normal_font)
|
||||
self.order_no_label.setFixedWidth(60)
|
||||
|
||||
self.order_no_input = QLineEdit()
|
||||
self.order_no_input.setFont(self.normal_font)
|
||||
self.order_no_input.setReadOnly(True)
|
||||
self.order_no_input.setStyleSheet("background-color: #f5f5f5; border: 1px solid #cccccc; padding: 3px;")
|
||||
|
||||
self.order_info_layout.addWidget(self.order_no_label)
|
||||
self.order_info_layout.addWidget(self.order_no_input)
|
||||
self.order_info_layout.addStretch()
|
||||
|
||||
# 添加托盘完成按钮
|
||||
self.tray_complete_button = QPushButton("托盘完成")
|
||||
self.tray_complete_button.setFont(self.normal_font)
|
||||
self.tray_complete_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
padding: 5px 8px;
|
||||
background-color: #e8f5e9;
|
||||
border: 1px solid #4caf50;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #c8e6c9;
|
||||
}
|
||||
""")
|
||||
self.order_info_layout.addWidget(self.tray_complete_button)
|
||||
|
||||
self.material_content_layout.addLayout(self.order_info_layout)
|
||||
|
||||
# 创建信息表格 - 使用QTableWidget实现
|
||||
self.info_table = QTableWidget()
|
||||
self.info_table.setRowCount(9) # 8行常规字段 + 1行备注
|
||||
self.info_table.setColumnCount(4) # 4列:标签1, 值1, 标签2, 值2
|
||||
self.info_table.setShowGrid(True) # 显示网格线
|
||||
self.info_table.horizontalHeader().setVisible(False) # 隐藏水平表头
|
||||
self.info_table.verticalHeader().setVisible(False) # 隐藏垂直表头
|
||||
|
||||
# 设置表格样式
|
||||
self.info_table.setStyleSheet("""
|
||||
QTableWidget {
|
||||
border-top: 1px solid #cccccc;
|
||||
border-left: 1px solid #cccccc;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
gridline-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
|
||||
# 定义字段和对应的数据键
|
||||
field_mapping = self.FIELD_MAPPING
|
||||
|
||||
# 创建标签和值的字典,用于后续更新
|
||||
self.info_labels = {}
|
||||
self.info_values = {}
|
||||
|
||||
# 填充表格内容
|
||||
row = 0
|
||||
col = 0
|
||||
for i, field_name in enumerate(field_mapping.keys()):
|
||||
if field_name == "备注": # 备注行特殊处理
|
||||
# 创建标签
|
||||
label = QLabel("备注")
|
||||
label.setFont(self.normal_font)
|
||||
label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
label.setStyleSheet("background-color: #FAFAFA; padding: 5px;")
|
||||
|
||||
# 创建值
|
||||
value = QLabel("")
|
||||
value.setFont(self.normal_font)
|
||||
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
value.setStyleSheet("background-color: white; padding: 5px;")
|
||||
|
||||
# 保存引用
|
||||
self.info_labels["备注"] = label
|
||||
self.info_values["备注"] = value
|
||||
|
||||
# 添加到表格
|
||||
self.info_table.setCellWidget(row, 0, label)
|
||||
self.info_table.setCellWidget(row, 1, value)
|
||||
self.info_table.setSpan(row, 1, 1, 3) # 值跨越3列
|
||||
else:
|
||||
# 创建标签
|
||||
label = QLabel(field_name)
|
||||
label.setFont(self.normal_font)
|
||||
label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
label.setStyleSheet("background-color: #FAFAFA; padding: 5px;")
|
||||
|
||||
# 创建值
|
||||
value = QLabel("")
|
||||
value.setFont(self.normal_font)
|
||||
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
value.setStyleSheet("background-color: white; padding: 5px;")
|
||||
|
||||
# 保存引用
|
||||
self.info_labels[field_name] = label
|
||||
self.info_values[field_name] = value
|
||||
|
||||
# 添加到表格
|
||||
self.info_table.setCellWidget(row, col * 2, label)
|
||||
self.info_table.setCellWidget(row, col * 2 + 1, value)
|
||||
|
||||
# 更新行列索引
|
||||
col += 1
|
||||
if col >= 2: # 每行两组数据
|
||||
col = 0
|
||||
row += 1
|
||||
|
||||
# 设置行高
|
||||
for i in range(self.info_table.rowCount()):
|
||||
self.info_table.setRowHeight(i, 35) # 普通行高度调高
|
||||
|
||||
# 设置表格自适应
|
||||
self.info_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # 列宽自适应
|
||||
|
||||
# 设置表格填充整个容器
|
||||
self.info_table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
# 禁用滚动条,避免用户误触
|
||||
self.info_table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.info_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
|
||||
# 添加表格到主布局
|
||||
self.material_content_layout.addWidget(self.info_table)
|
||||
|
||||
self.material_layout.addWidget(self.material_content)
|
||||
|
||||
self.left_layout.addWidget(self.material_frame)
|
||||
@ -472,9 +620,9 @@ class MainWindowUI(QMainWindow):
|
||||
"""创建微丝产线表格,包含上料、检验、包装部分"""
|
||||
# 创建微丝产线框架
|
||||
self.process_frame = QFrame()
|
||||
self.process_frame.setFrameShape(QFrame.Box)
|
||||
self.process_frame.setLineWidth(1)
|
||||
self.process_frame.setStyleSheet("QFrame { border: 1px solid #dddddd; }")
|
||||
self.process_frame.setFrameShape(QFrame.NoFrame) # 移除边框
|
||||
self.process_frame.setLineWidth(0)
|
||||
self.process_frame.setStyleSheet("QFrame { background-color: #f5f5f5; }") # 与其他标题背景色一致
|
||||
self.process_layout = QVBoxLayout(self.process_frame)
|
||||
self.process_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.process_layout.setSpacing(0)
|
||||
@ -483,9 +631,8 @@ class MainWindowUI(QMainWindow):
|
||||
self.process_title = QLabel("微丝产线")
|
||||
self.process_title.setFont(self.second_title_font)
|
||||
self.process_title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
# 调整行高
|
||||
self.process_title.setFixedHeight(40)
|
||||
self.process_title.setStyleSheet("background-color: #f8f8f8; padding: 5px; border-bottom: 1px solid #dddddd;")
|
||||
self.process_title.setStyleSheet("background-color: #f5f5f5; padding: 5px;") # 移除底部边框
|
||||
self.process_layout.addWidget(self.process_title)
|
||||
|
||||
|
||||
@ -529,9 +676,9 @@ class MainWindowUI(QMainWindow):
|
||||
"""创建包装记录表格"""
|
||||
# 创建包装记录框架
|
||||
self.record_frame = QFrame()
|
||||
self.record_frame.setFrameShape(QFrame.Box)
|
||||
self.record_frame.setLineWidth(1)
|
||||
self.record_frame.setStyleSheet("QFrame { border: 1px solid #dddddd; }") # 移除 border-top: none;
|
||||
self.record_frame.setFrameShape(QFrame.NoFrame) # 移除边框
|
||||
self.record_frame.setLineWidth(0)
|
||||
self.record_frame.setStyleSheet("QFrame { background-color: #f5f5f5; }") # 与其他标题背景色一致
|
||||
self.record_layout = QVBoxLayout(self.record_frame)
|
||||
self.record_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.record_layout.setSpacing(0)
|
||||
@ -540,10 +687,23 @@ class MainWindowUI(QMainWindow):
|
||||
self.record_title = QLabel("包装记录")
|
||||
self.record_title.setFont(self.second_title_font)
|
||||
self.record_title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
# 调整行高
|
||||
self.record_title.setFixedHeight(40)
|
||||
self.record_title.setStyleSheet("background-color: #f8f8f8; padding: 5px; border-bottom: 1px solid #dddddd;")
|
||||
self.record_layout.addWidget(self.record_title)
|
||||
self.record_title.setStyleSheet("background-color: #f5f5f5; padding: 5px;") # 移除底部边框
|
||||
|
||||
# 创建标题容器,用于水平布局标题和按钮
|
||||
self.record_title_container = QWidget()
|
||||
self.record_title_layout = QHBoxLayout(self.record_title_container)
|
||||
self.record_title_layout.setContentsMargins(5, 0, 5, 0)
|
||||
self.record_title_layout.setSpacing(10)
|
||||
|
||||
# 添加标题到布局
|
||||
self.record_title_layout.addWidget(self.record_title)
|
||||
|
||||
# 添加弹性空间,确保标题居左
|
||||
self.record_title_layout.addStretch() # 添加弹性空间
|
||||
|
||||
# 将标题容器添加到记录布局
|
||||
self.record_layout.addWidget(self.record_title_container)
|
||||
|
||||
# 创建表格
|
||||
self.record_table = QTableWidget(13, 10) # 13行10列:序号、订单、工程号、品名、规格、托号、轴包装号、重量、净重、完成时间
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import logging
|
||||
import time
|
||||
from threading import Thread, Event
|
||||
from PySide6.QtCore import QTimer
|
||||
from PySide6.QtCore import QTimer, QObject, Signal
|
||||
from utils.modbus_utils import ModbusUtils
|
||||
from utils.modbus_monitor import RegisterHandler
|
||||
from dao.electricity_dao import ElectricityDAO
|
||||
from utils.config_loader import ConfigLoader
|
||||
|
||||
class ElectricityHandler(RegisterHandler):
|
||||
class ElectricityHandler(RegisterHandler, QObject):
|
||||
"""电力消耗寄存器处理器"""
|
||||
|
||||
# 定义电力数据变化信号
|
||||
electricity_data_changed = Signal(float)
|
||||
|
||||
def __init__(self):
|
||||
"""初始化处理器"""
|
||||
QObject.__init__(self)
|
||||
self.dao = ElectricityDAO()
|
||||
# 确保表已创建
|
||||
self.dao.create_table_if_not_exists()
|
||||
@ -26,6 +30,9 @@ class ElectricityHandler(RegisterHandler):
|
||||
# 保存电力消耗数据
|
||||
self.dao.save_electricity_data(value)
|
||||
logging.info(f"已记录电力消耗数据: {value}")
|
||||
|
||||
# 发射电力数据变化信号
|
||||
self.electricity_data_changed.emit(value)
|
||||
except Exception as e:
|
||||
logging.error(f"处理电力消耗数据时发生错误: {str(e)}")
|
||||
|
||||
|
||||
111
utils/focus_tracker.py
Normal file
111
utils/focus_tracker.py
Normal file
@ -0,0 +1,111 @@
|
||||
from PySide6.QtWidgets import QApplication, QLineEdit, QComboBox
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
import logging
|
||||
|
||||
class FocusTracker(QObject):
|
||||
"""焦点跟踪器,用于跟踪当前焦点所在的输入框"""
|
||||
|
||||
# 定义单例
|
||||
_instance = None
|
||||
|
||||
# 信号:当焦点变化时触发
|
||||
focus_changed = Signal(object)
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
"""获取单例实例"""
|
||||
if cls._instance is None:
|
||||
cls._instance = FocusTracker()
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""初始化焦点跟踪器"""
|
||||
if FocusTracker._instance is not None:
|
||||
raise Exception("FocusTracker是单例类,请使用get_instance()方法获取实例")
|
||||
|
||||
super().__init__()
|
||||
self._current_focus = None
|
||||
self._initialized = False
|
||||
|
||||
def initialize(self):
|
||||
"""初始化焦点跟踪"""
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
# 连接应用程序的焦点变化信号
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
app.focusChanged.connect(self._on_focus_changed)
|
||||
self._initialized = True
|
||||
logging.info("焦点跟踪器已初始化")
|
||||
else:
|
||||
logging.error("无法获取QApplication实例,焦点跟踪器初始化失败")
|
||||
|
||||
def _on_focus_changed(self, old, now):
|
||||
"""焦点变化处理"""
|
||||
# 关注QLineEdit和QComboBox控件
|
||||
if isinstance(now, (QLineEdit, QComboBox)):
|
||||
self._current_focus = now
|
||||
self.focus_changed.emit(now)
|
||||
widget_type = "输入框" if isinstance(now, QLineEdit) else "下拉框"
|
||||
logging.debug(f"焦点变化到{widget_type}: {now.objectName()}")
|
||||
|
||||
def get_current_focus(self):
|
||||
"""获取当前焦点所在的控件"""
|
||||
return self._current_focus
|
||||
|
||||
def set_text_and_trigger_enter(self, text):
|
||||
"""设置当前焦点控件的文本并触发回车事件"""
|
||||
if not self._current_focus:
|
||||
logging.warning("当前没有焦点控件,无法设置文本和触发回车事件")
|
||||
return False
|
||||
|
||||
# 创建回车键事件
|
||||
from PySide6.QtGui import QKeyEvent
|
||||
from PySide6.QtCore import Qt, QEvent
|
||||
key_event = QKeyEvent(QEvent.KeyPress, Qt.Key_Return, Qt.NoModifier)
|
||||
|
||||
# 根据控件类型处理
|
||||
if isinstance(self._current_focus, QLineEdit):
|
||||
# 设置文本
|
||||
self._current_focus.setText(text)
|
||||
|
||||
# 发送事件到当前焦点控件
|
||||
QApplication.sendEvent(self._current_focus, key_event)
|
||||
|
||||
logging.info(f"已将文本 '{text}' 设置到输入框并触发回车事件")
|
||||
return True
|
||||
|
||||
elif isinstance(self._current_focus, QComboBox):
|
||||
# 对于ComboBox,先查找匹配的项
|
||||
combo_box = self._current_focus
|
||||
|
||||
# 查找完全匹配的项
|
||||
index = combo_box.findText(text)
|
||||
if index >= 0:
|
||||
combo_box.setCurrentIndex(index)
|
||||
logging.info(f"已在下拉框中选择匹配项: '{text}'")
|
||||
else:
|
||||
# 如果没有完全匹配的项,设置为编辑文本(如果ComboBox是可编辑的)
|
||||
if combo_box.isEditable():
|
||||
combo_box.setEditText(text)
|
||||
logging.info(f"已将文本 '{text}' 设置到可编辑下拉框")
|
||||
else:
|
||||
logging.warning(f"下拉框中没有匹配项 '{text}',且下拉框不可编辑")
|
||||
return False
|
||||
|
||||
# 发送事件到当前焦点控件
|
||||
QApplication.sendEvent(combo_box, key_event)
|
||||
|
||||
# 如果ComboBox有activated信号,手动触发
|
||||
try:
|
||||
if hasattr(combo_box, 'activated'):
|
||||
combo_box.activated.emit(combo_box.currentIndex())
|
||||
logging.debug("已触发下拉框的activated信号")
|
||||
except Exception as e:
|
||||
logging.error(f"触发下拉框activated信号失败: {e}")
|
||||
|
||||
return True
|
||||
else:
|
||||
logging.warning(f"不支持的控件类型: {type(self._current_focus)}")
|
||||
return False
|
||||
@ -1160,6 +1160,9 @@ class SerialManager:
|
||||
# 转换为浮点数
|
||||
xj_value = float(number_str)
|
||||
logging.info(f"线径数据: {xj_value}")
|
||||
# 过滤无效值:>10 或 =0 的值直接跳过
|
||||
if xj_value/10000 > 10 or xj_value/10000 == 0:
|
||||
return False
|
||||
|
||||
# 更新数据
|
||||
self.data['xj'] = xj_value
|
||||
|
||||
@ -31,6 +31,10 @@ class LoadingDialog(LoadingDialogUI):
|
||||
# 设置对话框特性,按下Escape键才能关闭
|
||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
|
||||
# 确保焦点跟踪器能够跟踪此对话框中的输入框
|
||||
from utils.focus_tracker import FocusTracker
|
||||
self.focus_tracker = FocusTracker.get_instance()
|
||||
|
||||
# 绑定事件
|
||||
self.setup_connections()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,10 @@ class UnloadingDialog(UnloadingDialogUI):
|
||||
# 设置对话框特性,按下Escape键才能关闭
|
||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
|
||||
# 确保焦点跟踪器能够跟踪此对话框中的输入框
|
||||
from utils.focus_tracker import FocusTracker
|
||||
self.focus_tracker = FocusTracker.get_instance()
|
||||
|
||||
# 初始化托盘类型下拉列表
|
||||
self.init_pallet_types()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user