Compare commits
No commits in common. "1d29874c07a274c991a1c1a70baf6c4648a0e126" and "275ad1a7201f0e3de0f5779561da402ffa8f7365" have entirely different histories.
1d29874c07
...
275ad1a720
195
CLAUDE.md
195
CLAUDE.md
@ -1,195 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**腾智微丝产线包装系统** is a PyQt6-based industrial automation control system for managing and monitoring the packing process of fine wire production lines. It integrates camera monitoring, weighing, barcode scanning, PLC communication, and multiple serial device support.
|
||||
|
||||
**Key Operating Modes:**
|
||||
- **standalone**: Complete independent system
|
||||
- **api**: Acts as an interface component for other systems
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Setup Development Environment
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run the application
|
||||
python main.py
|
||||
```
|
||||
|
||||
### Key Configuration
|
||||
- Main config file: `config/app_config.json`
|
||||
- Database: SQLite (default at `db/jtDB.db`), also supports PostgreSQL and MySQL
|
||||
- Logs: `logs/app_YYYY-MM-DD.log` (auto-rotating daily, kept for 30 days)
|
||||
|
||||
### Database Initialization
|
||||
If the database doesn't exist, it's automatically created on first run. The database schema is initialized via `utils/init_db.py`.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This project follows an **MVC architecture** with clear separation of concerns:
|
||||
|
||||
### Layer Structure
|
||||
|
||||
1. **Model (DAO Layer)** - `dao/` directory
|
||||
- Data Access Objects for database operations
|
||||
- Supports SQLite, PostgreSQL, MySQL via `utils/sql_utils.py`
|
||||
- Key DAOs: `inspection_dao.py`, `user_dao.py`, `electricity_dao.py`, `pallet_type_dao.py`
|
||||
|
||||
2. **View Layer** - `ui/` and `widgets/` directories
|
||||
- **UI Definitions** (`ui/*.py`): Generated or hand-written UI class definitions
|
||||
- **Controllers** (`widgets/*.py`): Business logic and signal/slot handling for UI components
|
||||
- Built with **PySide6** (Qt for Python)
|
||||
- Key components:
|
||||
- `widgets/main_window.py`: Core application window
|
||||
- `widgets/login_widget.py`: User authentication
|
||||
- `widgets/camera_widget.py`: Camera management
|
||||
- `widgets/inspection_settings_widget.py`: Inspection configuration
|
||||
|
||||
3. **Utilities Layer** - `utils/` directory
|
||||
- **Configuration**: `config_loader.py` (singleton pattern, loads from JSON)
|
||||
- **Serial Communication**: `serial_manager.py` (manages multiple serial ports)
|
||||
- **Modbus Protocol**: `modbus_utils.py`, `modbus_monitor.py` (PLC communication)
|
||||
- **Hardware**:
|
||||
- `electricity_monitor.py` (power consumption tracking, singleton)
|
||||
- `keyboard_listener.py` (hardware input)
|
||||
- **Image Processing**: `local_image_player.py` (offline camera simulation)
|
||||
- **Database**: `sql_utils.py` (connection pooling, multi-database support)
|
||||
|
||||
4. **Hardware Integration** - `camera/` directory
|
||||
- Hikvision camera SDK integration
|
||||
- Supports both real camera devices and local image sequence playback
|
||||
|
||||
5. **API Layer** - `apis/` directory
|
||||
- RESTful API endpoints for api mode: `tary_api.py`, `gc_api.py`
|
||||
|
||||
## Key Design Patterns and Conventions
|
||||
|
||||
### Singleton Pattern
|
||||
Used for global resources and services:
|
||||
- `ConfigLoader`: Configuration management
|
||||
- `ElectricityMonitor`: Power monitoring
|
||||
- `SerialManager`: Serial port management (when instantiated)
|
||||
|
||||
**Access pattern:**
|
||||
```python
|
||||
from utils.config_loader import ConfigLoader
|
||||
config = ConfigLoader.get_instance()
|
||||
```
|
||||
|
||||
### Qt Signal/Slot Communication
|
||||
- Preferred method for inter-component communication
|
||||
- Asynchronous event handling
|
||||
- Avoid direct method calls between widgets
|
||||
|
||||
### Configuration Management
|
||||
- JSON-based configuration (`config/app_config.json`)
|
||||
- Accessed via `ConfigLoader.get_value(key_path, default_value)`
|
||||
- Supports nested keys with dot notation: `'database.sources.sqlite.path'`
|
||||
|
||||
### Database Access
|
||||
- Use `SQLUtils` for connection management
|
||||
- All DAOs inherit pattern from existing implementations
|
||||
- Connection pooling is automatically managed
|
||||
- Always call `SQLUtils.close_all_connections()` on application exit
|
||||
|
||||
### Error Handling
|
||||
- Global exception handler in `main.py` catches unhandled exceptions
|
||||
- Log all errors with context (function name, line number)
|
||||
- Use try/except in main threads, avoid silent failures
|
||||
|
||||
## Important Implementation Details
|
||||
|
||||
### Serial Port Configuration
|
||||
- Defined in `config/app_config.json` under `serial.*`
|
||||
- Each device (cz=weighing, xj=scanner, mdz=moisture sensor) has own config
|
||||
- `serial_manager.py` handles connections and data parsing
|
||||
- Note: Weighing device (cz) now retrieves data via Modbus register D11, not serial
|
||||
|
||||
### Modbus Communication
|
||||
- TCP protocol to PLC at `modbus.host:modbus.port` (default: localhost:5020)
|
||||
- `modbus_monitor.py` provides continuous monitoring of specific registers
|
||||
- Test/development: Use `modbus_server.py` to simulate PLC
|
||||
|
||||
### Application Mode Detection
|
||||
- Check `app.mode` from config to determine standalone vs api mode
|
||||
- API mode disables certain UI elements and uses REST endpoints instead
|
||||
|
||||
### Threading Considerations
|
||||
- Use `QThread` for long-running operations (serial reading, Modbus polling)
|
||||
- Always use signals to communicate between threads
|
||||
- Avoid blocking the GUI thread
|
||||
|
||||
## File Organization
|
||||
|
||||
```
|
||||
.
|
||||
├── main.py # Application entry point
|
||||
├── config/app_config.json # Main configuration file
|
||||
├── requirements.txt # Python dependencies
|
||||
├── widgets/ # View controllers (UI logic)
|
||||
├── ui/ # View definitions (UI layouts)
|
||||
├── dao/ # Data access objects
|
||||
├── utils/ # Utility modules
|
||||
│ ├── config_loader.py # Configuration singleton
|
||||
│ ├── serial_manager.py # Serial port handling
|
||||
│ ├── modbus_utils.py # Modbus communication
|
||||
│ ├── sql_utils.py # Database utilities
|
||||
│ └── ... (other utilities)
|
||||
├── camera/ # Camera SDK integration
|
||||
├── apis/ # API endpoints (api mode)
|
||||
├── db/ # Database files
|
||||
├── logs/ # Application logs
|
||||
└── inspection/ # Inspection business logic
|
||||
```
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding a New Widget/Dialog
|
||||
1. Create UI definition in `ui/` with suffix `_ui.py`
|
||||
2. Create controller in `widgets/` with corresponding name
|
||||
3. Use signals for communication with parent widgets
|
||||
4. Register in config if it needs configuration options
|
||||
|
||||
### Modifying Serial Device Handling
|
||||
1. Edit device config in `config/app_config.json`
|
||||
2. Update parser logic in `serial_manager.py` if data format changes
|
||||
3. Test with physical device or mock serial data
|
||||
|
||||
### Adding Database Tables
|
||||
1. Modify schema in `utils/init_db.py`
|
||||
2. Create new DAO class in `dao/` following existing patterns
|
||||
3. Add migration if needed for existing databases
|
||||
|
||||
### Debugging Modbus Communication
|
||||
```bash
|
||||
# Start Modbus test server
|
||||
python modbus_server.py
|
||||
|
||||
# Check register values and communication
|
||||
# Monitor output from modbus_monitor.py in logs
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
See `requirements.txt` for complete list. Key dependencies:
|
||||
- **PySide6**: GUI framework
|
||||
- **pymodbus**: Modbus TCP communication
|
||||
- **pyserial**: Serial port communication
|
||||
- **pandas**: Data processing
|
||||
- **opencv-python**: Image processing (for local image playback)
|
||||
- **pillow**: Image handling
|
||||
- **pynput**: Keyboard monitoring
|
||||
|
||||
## Notes for Future Development
|
||||
|
||||
1. **Database Migrations**: When modifying schemas, provide backwards compatibility or migration scripts
|
||||
2. **Configuration Changes**: Maintain JSON schema compatibility with existing configs
|
||||
3. **Testing**: Tests directory exists at `tests/` but currently minimal
|
||||
4. **Logging**: Application logs are verbose by design - check logs when debugging issues
|
||||
5. **Resource Cleanup**: Always stop services (ElectricityMonitor, SerialManager) on application exit
|
||||
@ -94,11 +94,10 @@
|
||||
"data_bits": 8,
|
||||
"parity": "N",
|
||||
"port": "9600",
|
||||
"ser": "",
|
||||
"ser": "COM2",
|
||||
"stable_threshold": 10,
|
||||
"stop_bits": 1,
|
||||
"timeout": 1,
|
||||
"comment": "称重通过寄存器D11获取,无需串口"
|
||||
"timeout": 1
|
||||
},
|
||||
"xj": {
|
||||
"bit": 10,
|
||||
@ -117,10 +116,10 @@
|
||||
"code": "scanner",
|
||||
"data_bits": 8,
|
||||
"parity": "N",
|
||||
"port": "115200",
|
||||
"ser": "COM1",
|
||||
"port": "9600",
|
||||
"ser": "COM3",
|
||||
"stop_bits": 1,
|
||||
"timeout": 0.5
|
||||
"timeout": 1
|
||||
}
|
||||
},
|
||||
"electricity": {
|
||||
|
||||
BIN
db/jtDB.db
BIN
db/jtDB.db
Binary file not shown.
@ -51,12 +51,6 @@ class SerialManager:
|
||||
# 稳定性时间跟踪
|
||||
self.stability_start_time = 0 # 开始检测稳定性的时间
|
||||
|
||||
# 串口失败跟踪
|
||||
self.port_failure_count: Dict[str, int] = {} # 记录每个串口的连续失败次数
|
||||
self.port_last_failure_time: Dict[str, float] = {} # 记录每个串口的最后失败时间
|
||||
self.max_failures = 5 # 最大连续失败次数
|
||||
self.failure_cooldown = 300 # 失败冷却时间(秒),5分钟
|
||||
|
||||
# 数据存储
|
||||
self.data = {
|
||||
'mdz': 0,
|
||||
@ -1002,26 +996,20 @@ class SerialManager:
|
||||
port_name = self.cz_config['ser']
|
||||
baud_rate = self.cz_config.get('port', 9600)
|
||||
|
||||
# 检查串口是否在冷却期内
|
||||
if self._should_skip_port_attempt(port_name):
|
||||
logging.info(f"称重串口 {port_name} 在冷却期内,跳过重试(失败{self.port_failure_count.get(port_name, 0)}次)")
|
||||
elif not self.is_port_open(port_name):
|
||||
if not self.is_port_open(port_name):
|
||||
try:
|
||||
if self.open_port(port_name, 'cz', baud_rate):
|
||||
logging.info(f"自动打开称重串口 {port_name} 成功")
|
||||
self._reset_port_failure(port_name) # 成功后重置失败计数
|
||||
else:
|
||||
logging.error(f"自动打开称重串口 {port_name} 失败")
|
||||
self._record_port_failure(port_name)
|
||||
success = False
|
||||
except Exception as e:
|
||||
logging.error(f"自动打开称重串口 {port_name} 时发生异常: {e}")
|
||||
self._record_port_failure(port_name)
|
||||
success = False
|
||||
else:
|
||||
logging.info(f"称重串口 {port_name} 已经打开,无需重新打开")
|
||||
else:
|
||||
logging.info("称重串口未配置或设置为不使用,跳过自动打开")
|
||||
logging.warning("称重串口未配置或设置为不使用,跳过自动打开")
|
||||
|
||||
# 尝试打开线径串口
|
||||
if self.xj_config and 'ser' in self.xj_config and self.xj_config['ser'] and self.xj_config['ser'].strip():
|
||||
@ -1337,46 +1325,4 @@ class SerialManager:
|
||||
logging.error(f"线程监控主循环异常: {e}")
|
||||
# 尝试重新启动监控
|
||||
time.sleep(10)
|
||||
self._start_thread_monitor()
|
||||
|
||||
def _should_skip_port_attempt(self, port_name: str) -> bool:
|
||||
"""检查是否应该跳过串口重试(基于失败次数和冷却时间)"""
|
||||
current_time = time.time()
|
||||
|
||||
# 如果没有失败记录,允许尝试
|
||||
if port_name not in self.port_failure_count:
|
||||
return False
|
||||
|
||||
failure_count = self.port_failure_count[port_name]
|
||||
last_failure_time = self.port_last_failure_time.get(port_name, 0)
|
||||
|
||||
# 如果失败次数超过最大值且在冷却期内,跳过
|
||||
if failure_count >= self.max_failures:
|
||||
time_since_failure = current_time - last_failure_time
|
||||
if time_since_failure < self.failure_cooldown:
|
||||
return True
|
||||
else:
|
||||
# 冷却期结束,重置失败计数给一次机会
|
||||
logging.info(f"串口 {port_name} 冷却期结束,重置失败计数")
|
||||
self._reset_port_failure(port_name)
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def _record_port_failure(self, port_name: str):
|
||||
"""记录串口打开失败"""
|
||||
current_time = time.time()
|
||||
self.port_failure_count[port_name] = self.port_failure_count.get(port_name, 0) + 1
|
||||
self.port_last_failure_time[port_name] = current_time
|
||||
|
||||
failure_count = self.port_failure_count[port_name]
|
||||
if failure_count >= self.max_failures:
|
||||
cooldown_minutes = self.failure_cooldown // 60
|
||||
logging.warning(f"串口 {port_name} 连续失败{failure_count}次,将在{cooldown_minutes}分钟内暂停重试")
|
||||
|
||||
def _reset_port_failure(self, port_name: str):
|
||||
"""重置串口失败计数"""
|
||||
if port_name in self.port_failure_count:
|
||||
del self.port_failure_count[port_name]
|
||||
if port_name in self.port_last_failure_time:
|
||||
del self.port_last_failure_time[port_name]
|
||||
self._start_thread_monitor()
|
||||
@ -65,9 +65,6 @@ class MainWindow(MainWindowUI):
|
||||
# 新增的重量警告弹框信号 - 参数:值, 最小值, 最大值
|
||||
weight_alert_signal = Signal(float, float, float)
|
||||
|
||||
# 新增的炉号警告弹框信号 - 无参数
|
||||
luno_alert_signal = Signal()
|
||||
|
||||
# 不需要单独的spack信号,我们直接从loading_data中获取spack
|
||||
|
||||
def __init__(self, user_id=None, user_name=None, corp_name=None, corp_id=None):
|
||||
@ -386,9 +383,6 @@ class MainWindow(MainWindowUI):
|
||||
|
||||
# 连接重量警告弹框信号
|
||||
self.weight_alert_signal.connect(self.show_weight_alert)
|
||||
|
||||
# 连接炉号警告弹框信号
|
||||
self.luno_alert_signal.connect(self.show_luno_alert)
|
||||
|
||||
def update_inspection_columns(self):
|
||||
"""更新检验列配置 - 使用检验配置管理器获取启用的列数和标题"""
|
||||
@ -670,9 +664,6 @@ class MainWindow(MainWindowUI):
|
||||
# 启动串口监听
|
||||
self.serial_manager.auto_open_configured_ports()
|
||||
|
||||
# 重新注册串口回调函数(修复切换订单后扫码失效的问题)
|
||||
self.register_serial_callbacks()
|
||||
|
||||
# 启动键盘监听器
|
||||
self.serial_manager.start_keyboard_listener()
|
||||
logging.info("已在上料操作前启动键盘监听器")
|
||||
@ -770,10 +761,6 @@ class MainWindow(MainWindowUI):
|
||||
if not hasattr(self, 'modbus_monitor') or not self.modbus_monitor.is_running():
|
||||
self.setup_modbus_monitor()
|
||||
self.serial_manager.auto_open_configured_ports()
|
||||
|
||||
# 重新注册串口回调函数(修复切换订单后扫码失效的问题)
|
||||
self.register_serial_callbacks()
|
||||
|
||||
self.serial_manager.start_keyboard_listener()
|
||||
|
||||
self.unloading_dialog = UnloadingDialog(self, self.user_id)
|
||||
@ -2632,40 +2619,6 @@ class MainWindow(MainWindowUI):
|
||||
|
||||
# 更新处理键
|
||||
self._last_weight_process_key = process_key
|
||||
|
||||
# 检查炉号是否为空
|
||||
luno_value = None
|
||||
luno_container = self.info_values.get("炉号")
|
||||
logging.debug(f"获取对象数据为:{self.info_values}")
|
||||
if luno_container:
|
||||
# 查找容器中的QLineEdit
|
||||
for child in luno_container.children():
|
||||
if hasattr(child, 'text') and hasattr(child, 'setText'):
|
||||
luno_value = child.text().strip()
|
||||
break
|
||||
|
||||
# 如果炉号为空或不存在,阻止继续执行
|
||||
if not luno_value:
|
||||
# 写入寄存器D10 给值 2 表示炉号为空
|
||||
try:
|
||||
modbus = ModbusUtils()
|
||||
client = modbus.get_client()
|
||||
modbus.write_register_until_success(client, 10, 2)
|
||||
modbus.close_client(client)
|
||||
logging.info("已写入D10=2,通知PLC炉号为空")
|
||||
except Exception as e:
|
||||
logging.error(f"写入Modbus寄存器D10=2失败: {str(e)}")
|
||||
|
||||
# 使用信号触发弹框显示 - 避免主线程阻塞
|
||||
logging.warning(f"炉号为空或未设置,发送信号显示警告")
|
||||
try:
|
||||
self.luno_alert_signal.emit()
|
||||
except Exception as e:
|
||||
logging.error(f"发送炉号警告信号失败: {str(e)}", exc_info=True)
|
||||
|
||||
# 阻止继续执行,等待用户处理
|
||||
logging.warning(f"炉号为空或未设置,已阻止保存称重数据")
|
||||
return
|
||||
|
||||
# 保存净重到数据库(毛重-工字轮重量,单位都是千克)
|
||||
from dao.inspection_dao import InspectionDAO
|
||||
@ -3955,40 +3908,6 @@ class MainWindow(MainWindowUI):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def handle_activate_scanner(self):
|
||||
"""激活扫码器:向扫码串口发送指令 16 4D 0D"""
|
||||
try:
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
# 获取扫码器配置
|
||||
scanner_config = getattr(self.serial_manager, 'scanner_config', None)
|
||||
if not scanner_config or not scanner_config.get('ser'):
|
||||
logging.warning("扫码器串口未配置,无法发送激活指令")
|
||||
QMessageBox.warning(self, "提示", "未配置扫码器串口,请先在参数设置中配置扫码器串口。")
|
||||
return
|
||||
|
||||
port_name = scanner_config.get('ser')
|
||||
baud_rate = scanner_config.get('port', 115200)
|
||||
|
||||
# 确保串口已打开
|
||||
if not self.serial_manager.is_port_open(port_name):
|
||||
logging.info(f"扫码器串口 {port_name} 未打开,尝试自动打开...")
|
||||
if not self.serial_manager.open_port(port_name, 'scanner', baud_rate):
|
||||
logging.error(f"打开扫码器串口 {port_name} 失败,无法发送激活指令")
|
||||
QMessageBox.critical(self, "错误", f"打开扫码器串口失败: {port_name}")
|
||||
return
|
||||
|
||||
# 发送激活指令 16 4D 0D
|
||||
cmd_hex = "16 4D 0D"
|
||||
cmd_bytes = bytes.fromhex(cmd_hex.replace(" ", ""))
|
||||
self.serial_manager.serial_ports[port_name].write(cmd_bytes)
|
||||
logging.info(f"已向扫码器串口 {port_name} 发送激活指令: {cmd_hex}")
|
||||
except Exception as e:
|
||||
logging.error(f"激活扫码器失败: {str(e)}", exc_info=True)
|
||||
try:
|
||||
QMessageBox.critical(self, "错误", f"激活扫码器失败: {str(e)}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_inspection_value(self, data_type, config, value):
|
||||
"""设置检验项目值到表格中
|
||||
|
||||
@ -4435,21 +4354,6 @@ class MainWindow(MainWindowUI):
|
||||
""")
|
||||
self.print_tray_button.clicked.connect(lambda: self.handle_tray_complete(ismt=False))
|
||||
|
||||
# 激活扫码按钮
|
||||
self.activate_scanner_button = QPushButton("激活扫码")
|
||||
self.activate_scanner_button.setFont(self.normal_font)
|
||||
self.activate_scanner_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
padding: 5px 8px;
|
||||
background-color: #fff3e0;
|
||||
border: 1px solid #ff9800;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #ffe0b2;
|
||||
}
|
||||
""")
|
||||
self.activate_scanner_button.clicked.connect(self.handle_activate_scanner)
|
||||
|
||||
# 打印选中行按钮
|
||||
self.print_row_button = QPushButton("打印")
|
||||
self.print_row_button.setFont(self.normal_font)
|
||||
@ -4468,7 +4372,6 @@ class MainWindow(MainWindowUI):
|
||||
# 将标题和按钮添加到布局中
|
||||
title_layout.addWidget(self.process_title)
|
||||
title_layout.addStretch()
|
||||
title_layout.addWidget(self.activate_scanner_button)
|
||||
title_layout.addWidget(self.print_row_button)
|
||||
title_layout.addWidget(self.delete_row_button)
|
||||
title_layout.addWidget(self.print_tray_button)
|
||||
@ -4874,23 +4777,10 @@ class MainWindow(MainWindowUI):
|
||||
else:
|
||||
new_value = ""
|
||||
|
||||
# 智能更新逻辑:
|
||||
# - 强度字段允许空值覆盖
|
||||
# - 炉号字段:如果接口返回为空/None,则强制清空,不能沿用上一个订单的炉号
|
||||
# - 其他字段:空值不覆盖
|
||||
# 智能更新逻辑:强度字段允许空值覆盖,其他字段空值不覆盖
|
||||
should_update = False
|
||||
if field_name == "强度": # 强度字段允许空值覆盖
|
||||
should_update = new_value != current_value
|
||||
elif field_name == "炉号":
|
||||
# 对炉号字段进行专门处理,避免沿用上一个订单的炉号
|
||||
raw_luno = order_info.get(field_key)
|
||||
if raw_luno is None or str(raw_luno).strip() == "":
|
||||
# 接口未提供炉号,确保清空当前值
|
||||
new_value = ""
|
||||
should_update = current_value != ""
|
||||
else:
|
||||
new_value = str(raw_luno).strip()
|
||||
should_update = new_value != current_value
|
||||
else: # 其他字段:只有新值不为空且与当前值不同时才更新
|
||||
should_update = new_value and new_value != current_value
|
||||
|
||||
@ -5375,80 +5265,6 @@ class MainWindow(MainWindowUI):
|
||||
except Exception as e:
|
||||
logging.error(f"显示重量警告弹框失败: {str(e)}", exc_info=True)
|
||||
|
||||
@Slot()
|
||||
def show_luno_alert(self):
|
||||
"""显示炉号为空警告 - 通过信号触发,使用exec_强制显示"""
|
||||
try:
|
||||
# 使用更强制的方式显示警告对话框
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
# 记录当前时间,用于跟踪弹框显示时间
|
||||
start_time = time.time()
|
||||
logging.info(f"开始创建炉号警告弹框,时间: {start_time}")
|
||||
|
||||
# 创建一个模态对话框 - 使用exec_方式显示
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Critical) # 使用Critical图标更明显
|
||||
msg.setWindowTitle('警告:炉号未设置!')
|
||||
msg.setText(f"<b>炉号为空或未设置!</b><br><br>请先设置炉号后再进行称重")
|
||||
msg.setStandardButtons(QMessageBox.Ok)
|
||||
|
||||
# 设置样式,使其更显眼
|
||||
msg.setStyleSheet("""
|
||||
QMessageBox {
|
||||
background-color: #ffeeee;
|
||||
border: 3px solid #ff0000;
|
||||
font-size: 14px;
|
||||
}
|
||||
QLabel {
|
||||
color: #ff0000;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
min-width: 250px;
|
||||
min-height: 80px;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #ff6666;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
min-width: 80px;
|
||||
min-height: 30px;
|
||||
}
|
||||
""")
|
||||
|
||||
# 确保对话框显示在所有窗口之上
|
||||
msg.setWindowFlags(Qt.WindowStaysOnTopHint)
|
||||
|
||||
# 强制处理事件,确保UI更新
|
||||
QApplication.processEvents()
|
||||
|
||||
# 使用定时器在2秒后自动点击确定按钮
|
||||
def auto_close():
|
||||
try:
|
||||
# 查找确定按钮并点击
|
||||
for button in msg.buttons():
|
||||
if msg.buttonRole(button) == QMessageBox.AcceptRole:
|
||||
button.click()
|
||||
logging.info(f"已自动点击确定按钮,弹框显示时长: {time.time() - start_time:.2f}秒")
|
||||
return
|
||||
# 如果没有找到确定按钮,直接关闭
|
||||
msg.done(QMessageBox.Ok)
|
||||
logging.info(f"已自动关闭弹框,弹框显示时长: {time.time() - start_time:.2f}秒")
|
||||
except Exception as e:
|
||||
logging.error(f"自动关闭弹框失败: {str(e)}")
|
||||
|
||||
# 设置自动关闭定时器
|
||||
QTimer.singleShot(2000, auto_close)
|
||||
|
||||
# 显示弹框并阻塞直到用户关闭或自动关闭
|
||||
logging.info(f"即将显示炉号警告弹框...")
|
||||
msg.exec_() # 使用exec_而不是show,确保弹框显示
|
||||
|
||||
# 记录日志
|
||||
logging.info(f"炉号警告弹框已关闭,总显示时长: {time.time() - start_time:.2f}秒")
|
||||
except Exception as e:
|
||||
logging.error(f"显示炉号警告弹框失败: {str(e)}", exc_info=True)
|
||||
|
||||
# 不需要单独的handle_spack_received方法,我们直接从loading_data中获取spack
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user