Compare commits

...

62 Commits

Author SHA1 Message Date
zhu-mengmeng
1d29874c07 feat: 更新串口配置,新增炉号警告弹框,优化扫码器激活逻辑 2025-12-16 13:24:31 +08:00
zhu-mengmeng
b0115059e5 feat: 添加串口失败跟踪和冷却机制,优化串口重试逻辑 2025-10-14 15:53:50 +08:00
zhu-mengmeng
275ad1a720 调整 2025-09-24 14:26:29 +08:00
zhu-mengmeng
b5420880f7 添加报表功能 2025-08-16 13:37:42 +08:00
zhu-mengmeng
fe60d656b1 fix:修改强度获取方式 2025-08-16 11:50:44 +08:00
zhu-mengmeng
6c33eeb3bf feat: 更新托盘包装统计数据缓存逻辑,新增清除缓存功能,优化主窗口和API接口的交互 2025-07-26 13:31:15 +08:00
zhu-mengmeng
252c2b882d feat: 优化加载对话框的spack处理逻辑,新增从订单数据获取spack的支持 2025-07-26 10:25:13 +08:00
zhu-mengmeng
d10c51edca feat: 新增获取托盘包装统计数据接口,优化加载对话框和主窗口的spack处理逻辑 2025-07-26 10:13:35 +08:00
zhu-mengmeng
e3ab0502be feat: 更新检验数据和包装记录逻辑,新增箱号支持,优化数据库查询和保存功能 2025-07-25 17:15:07 +08:00
zhu-mengmeng
23734541d1 feat: 更新Modbus写入值,修改应用配置的基础URL,优化加载对话框的数据存储和异常处理逻辑 2025-07-25 15:44:23 +08:00
zhu-mengmeng
5669bdc8a7 feat:修复箱号生成错误 2025-07-25 14:42:21 +08:00
zhu-mengmeng
53cd8f60b5 feat: 将托盘号输入框从QComboBox更改为QLineEdit,优化托盘号设置和处理逻辑 2025-07-25 11:49:50 +08:00
zhu-mengmeng
7d7fbcaa0e feat: 修改Modbus写入值,优化检验数据删除逻辑,移除冗余字段 2025-07-24 17:42:23 +08:00
zhu-mengmeng
61fd6cd78b feat: 修改电源自动启动设置为false,新增上料和下料对话框引用,优化主窗口托盘号设置逻辑 2025-07-24 09:23:58 +08:00
zhu-mengmeng
0878b05033 feat: 新增托盘包装统计数据接口,优化主窗口统计数据更新逻辑及字段名称 2025-07-24 00:25:12 +08:00
zhu-mengmeng
d40083b096 feat: 新增获取订单包装统计数据接口,优化主窗口统计数据更新逻辑及字段名称 2025-07-23 15:15:38 +08:00
zhu-mengmeng
2e5a31e904 feat: 优化主窗口字段处理逻辑,跳过线径公差、材质和打印材质字段的日志记录,移除无用的IP地址赋值和加载信息清空操作 2025-07-23 14:40:48 +08:00
zhu-mengmeng
69c8626888 feat: 新增检查包装记录是否存在的功能,优化主窗口操作逻辑及字段名称 2025-07-23 14:16:22 +08:00
zhu-mengmeng
5d2f0099ac feat: 修改Modbus写入值,新增线径和重量警告信号及弹框逻辑,优化警告提示处理 2025-07-23 13:22:53 +08:00
zhu-mengmeng
23823d08f8 feat:修复线径测量问题 2025-07-23 10:46:39 +08:00
zhu-mengmeng
268a1cc16b feat: 新增检验数据查询接口,优化主窗口防抖机制及数据处理逻辑 2025-07-22 18:34:18 +08:00
zhu-mengmeng
95ba01e152 feat: 新增防抖机制以提升用户体验 2025-07-22 14:30:18 +08:00
zhu-mengmeng
b61f2cc70b feat: 修改电源自动启动设置为false,优化主窗口输入框处理逻辑,调整重量计算精度及警告提示信息 2025-07-22 11:27:03 +08:00
zhu-mengmeng
cbdc25545e feat: 更新订单查询和主窗口逻辑,调整字段名称为srch_spack,优化净重计算及警告提示逻辑 2025-07-21 15:51:47 +08:00
zhu-mengmeng
0d216d3067 feat: 更新加载对话框和主窗口逻辑,调整订单号输入框回显字段为mo,优化信息表格更新逻辑 2025-07-21 14:19:48 +08:00
zhu-mengmeng
e75761bc39 feat: 新增通过托盘号获取订单信息的接口及相关逻辑,优化托盘号查询功能 2025-07-21 13:34:27 +08:00
zhu-mengmeng
382062239f feat: 修改Modbus写入值,新增圆形标签checkbox及其逻辑处理,优化线径公差检查提示框 2025-07-21 12:11:19 +08:00
zhu-mengmeng
1568d4c5ee feat: 调整主窗口微丝产线表格字体和行高,以提升可读性 2025-07-21 08:37:05 +08:00
zhu-mengmeng
5be57a052b feat: 更新主窗口逻辑,修复光标定位问题并调整数据库字段处理 2025-07-21 08:33:12 +08:00
zhu-mengmeng
bc2ca6919e feat: 修改Modbus写入值,更新SQL查询以处理空值,添加安全字符串转换函数 2025-07-20 23:01:27 +08:00
zhu-mengmeng
3246e8981f feat:新增一键生成虚拟订单号功能 2025-07-20 18:15:31 +08:00
zhu-mengmeng
d81765e7d2 feat: 添加线径警告信号机制,优化警告提示框的显示与关闭逻辑 2025-07-20 17:39:48 +08:00
zhu-mengmeng
fa36263c61 feat: 修改轴重和线径超出范围时的警告提示逻辑,添加自动关闭功能 2025-07-20 16:44:20 +08:00
zhu-mengmeng
7dbf1c1efc feat:实现接口删除功能 2025-07-20 15:34:17 +08:00
zhu-mengmeng
fe1df2e3e2 feat:添加删除包装记录功能 2025-07-20 15:03:08 +08:00
zhu-mengmeng
f4b11085aa feat:新增自定义轴号操作 2025-07-20 14:17:56 +08:00
zhu-mengmeng
dc92fb6eab feat: 更新下料标签的属性检查,添加客户扩展信息到订单查询 2025-07-20 14:08:49 +08:00
zhu-mengmeng
516d7cb99b feat:解决没有正常保存线径问题 2025-07-20 12:22:52 +08:00
zhu-mengmeng
dcc778ede6 feat:当下料完成后,自增,不清空 2025-07-20 08:57:15 +08:00
zhu-mengmeng
67374c5513 feat:修复数据展示问题 2025-07-19 21:30:51 +08:00
zhu-mengmeng
668700353f feat:确保扫码数据永远在最后一个 2025-07-19 17:20:09 +08:00
zhu-mengmeng
e36d5475dd feat: 修改数据库操作,将更新逻辑改为删除记录 2025-07-19 16:57:45 +08:00
zhu-mengmeng
aa532ef4ec feat: 修复按钮监听 2025-07-19 16:51:27 +08:00
zhu-mengmeng
387fc11796 feat: 优化获取本机IP地址的逻辑,处理异常情况并使用动态获取的IP 2025-07-19 14:36:32 +08:00
zhu-mengmeng
66344011f4 feat:修改上下料区域 2025-07-19 14:21:37 +08:00
zhu-mengmeng
976080683f feat: 添加上工程支持 2025-07-19 14:06:39 +08:00
zhu-mengmeng
ef77566b1d feat: 新增数量和机台 2025-07-19 13:59:27 +08:00
zhu-mengmeng
b05dc327af feat: 添加炉号选择的支持 2025-07-19 13:17:24 +08:00
zhu-mengmeng
ba40f97ab6 feat: 调整备注,变为多行显示 2025-07-19 12:22:21 +08:00
zhu-mengmeng
c40698c58b feat: 新增线材类型参数获取功能及界面支持 2025-07-19 12:00:39 +08:00
zhu-mengmeng
a626aafbf3 feat:处理 None 值回显问题 2025-07-19 10:37:08 +08:00
zhu-mengmeng
e6c7c3a46e feat: 新增库房功能 2025-07-19 10:29:10 +08:00
zhu-mengmeng
083cc3f675 feat:新增当前处理行跟踪 2025-07-19 09:03:48 +08:00
zhu-mengmeng
744bc4eb2f feat:修复不同输入框获取值方式不一致问题 2025-07-19 08:19:19 +08:00
zhu-mengmeng
8b8df295f1 feat: 新增状态吗判断 2025-07-19 02:00:22 +08:00
zhu-mengmeng
df842cd83c feat: 更新寄存器地址和按钮样式恢复逻辑 2025-07-18 15:35:28 +08:00
zhu-mengmeng
63233f1de6 feat: 调整卸载对话框宽度以适应较长托盘类型选项 2025-07-18 15:20:46 +08:00
zhu-mengmeng
470c58ace0 feat: 在主窗口中新增打印托盘号功能,并优化托盘完成处理逻辑 2025-07-18 15:17:59 +08:00
zhu-mengmeng
1af12e3cf2 feat: 调整强度回显功能 2025-07-18 14:56:31 +08:00
zhu-mengmeng
63edb27353 feat: 选择订单的时候同步生成托盘号 2025-07-18 14:49:46 +08:00
zhu-mengmeng
9fa3ed66f7 feat: 添加强度 2025-07-18 14:35:38 +08:00
zhu-mengmeng
83a3426219 feat: 在GcApi中新增获取订单列表功能,并在InspectionDAO中更新订单信息以支持新字段,同时优化加载对话框的订单查询逻辑 2025-07-18 13:46:11 +08:00
32 changed files with 7290 additions and 2506 deletions

195
CLAUDE.md Normal file
View File

@ -0,0 +1,195 @@
# 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

View File

@ -1,6 +1,10 @@
from utils.api_utils import ApiUtils
import logging
import requests
import json
import logging
import time
from utils.config_loader import ConfigLoader
from utils.api_utils import ApiUtils
class GcApi:
"""工程API接口类"""
@ -8,6 +12,11 @@ class GcApi:
"""初始化API接口"""
self.api_utils = ApiUtils()
# 添加缓存相关属性
self._tray_stats_cache = {} # 缓存数据
self._tray_stats_cache_time = {} # 缓存时间
self._cache_ttl = 5 # 缓存有效期(秒)
def ismt_option(self, params):
"""
@ -100,6 +109,28 @@ class GcApi:
except Exception as e:
logging.error(f"获取订单信息失败: {str(e)}")
return None
def get_spakc_info(self, order_code,corp_id,tray_id):
"""
获取订单信息
"""
try:
# API 配置中的键名
api_key = "get_spack_info"
# 构建 form-data 格式的数据
order_dict = {"orderId":order_code,"data_corp":corp_id,"trayId":tray_id}
data = {
"parms": json.dumps(order_dict), # 必须将数据序列化为JSON字符串
"pageIndex": 0,
"pageSize": 10,
"sortField": "",
"sortOrder": ""
}
# 将工程号作为参数传递,使用 data 参数传递 form-data 格式数据
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"获取订单信息失败: {str(e)}")
return None
def add_order_info(self, info):
"""
添加订单信息
@ -122,6 +153,34 @@ class GcApi:
logging.error(f"添加订单信息失败: {str(e)}")
return None
def remove_order_info(self, xpack, ddmo, sgc):
"""
删除订单信息
Args:
xpack: 托盘号
ddmo: 订单号
sgc: 工程号
Returns:
dict: 接口响应结果
"""
try:
# API 配置中的键名
api_key = "remove_order_info"
# 构建 form-data 格式的数据
data = {
"xpack": xpack,
"ddmo": ddmo,
"gch": sgc
}
# 调用接口
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"删除订单信息失败: {str(e)}")
return {"status": False, "message": f"删除订单信息失败: {str(e)}"}
def get_xpack(self, order_id,corp_id):
"""
获取包装号
@ -140,3 +199,312 @@ class GcApi:
except Exception as e:
logging.error(f"获取包装号失败: {str(e)}")
return None
def get_order_list(self, params):
"""
获取订单列表
Args:
params: 查询参数字典
Returns:
dict: 订单列表
"""
try:
# API 配置中的键名
api_key = "get_order_info"
# 构建查询参数
order_dict = {
"srch_mo": params.get("srch_mo", ""),
"srch_note": params.get("srch_note", ""),
"srch_rq1": params.get("srch_rq1", ""),
"srch_rq2": params.get("srch_rq2", ""),
"srch_cz": params.get("srch_cz", ""),
"srch_size": params.get("srch_size", ""),
"data_corp": params.get("corp_id", "")
}
# 构建 form-data 格式的数据
data = {
"parms": json.dumps(order_dict), # 必须将数据序列化为JSON字符串
"pageIndex": 0,
"pageSize": 50, # 获取更多数据
"sortField": "create_time",
"sortOrder": "desc"
}
# 调用API
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"获取订单列表失败: {str(e)}")
return {"status": False, "message": f"获取订单列表失败: {str(e)}"}
def get_params(self, stype, main, corp_id):
"""
获取指定参数信息
Args:
stype: 参数类型"库房档案"
main: 主参数"XC"
corp_id: 公司ID
Returns:
dict: 参数信息列表
"""
try:
# API 配置中的键名
api_key = "get_params"
# 构建GET请求参数
params = {
"stype": stype,
"main": main,
"corp_id": corp_id
}
# 发送GET请求
response = self.api_utils.get(api_key, params=params)
# 检查响应状态
if response.get("success", False):
return {
"status": True,
"data": response.get("data", []),
"message": "获取参数信息成功"
}
else:
return {
"status": False,
"data": [],
"message": response.get("message", "获取参数信息失败")
}
except Exception as e:
logging.error(f"获取参数信息失败: {str(e)}")
return {
"status": False,
"data": [],
"message": f"获取参数信息失败: {str(e)}"
}
def get_wire_type_params(self, corp_id):
"""
获取线材类型参数信息
Args:
corp_id: 公司ID
Returns:
dict: 线材类型信息列表
"""
try:
# 使用get_params方法传入线材类型参数
return self.get_params("线材类型", "", corp_id)
except Exception as e:
logging.error(f"获取线材类型参数失败: {str(e)}")
return {
"status": False,
"data": [],
"message": f"获取线材类型参数失败: {str(e)}"
}
def get_luno_list(self, params):
"""
获取炉号列表
Args:
params: 查询参数字典包含
- srch_rq1: 开始日期
- srch_rq2: 结束日期
- luono: 炉号
- cz: 材质
- gg: 规格
- gc: 钢厂
- corp_id: 公司ID
Returns:
dict: 炉号列表
"""
try:
# API 配置中的键名
api_key = "get_luno"
# 构建查询参数
luno_dict = {
"srch_rq1": params.get("srch_rq1", ""),
"srch_rq2": params.get("srch_rq2", ""),
"luono": params.get("luono", ""),
"cz": params.get("cz", ""),
"gg": params.get("gg", ""),
"gc": params.get("gc", ""),
"data_corp": params.get("corp_id", "")
}
# 构建 form-data 格式的数据
data = {
"parms": json.dumps(luno_dict), # 必须将数据序列化为JSON字符串
"pageIndex": 0,
"pageSize": 50, # 获取更多数据
"sortField": "create_time",
"sortOrder": "desc"
}
# 调用API
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"获取炉号列表失败: {str(e)}")
return {"status": False, "message": f"获取炉号列表失败: {str(e)}"}
def get_order_info_by_xpack(self, xpack, corp_id):
"""
通过托盘号获取订单信息
"""
try:
# API 配置中的键名
api_key = "get_order_info_by_xpack"
order_dict = {
"srch_spack": xpack,
"data_corp": corp_id
}
data = {
"parms": json.dumps(order_dict),
"pageIndex": 0,
"pageSize": 10,
"sortField": "",
"sortOrder": ""
}
response = self.api_utils.post(api_key, data=data)
return response
except Exception as e:
logging.error(f"通过托盘号获取订单信息失败: {str(e)}")
return None
def get_package_statistics(self, order_id, corp_id):
"""
获取订单包装统计数据
"""
try:
# API 配置中的键名
api_key = "get_package_statistics"
# 构建URL参数
params = {
"orderId": order_id,
"data_corp": corp_id
}
response = self.api_utils.get(api_key, params=params)
return response
except Exception as e:
logging.error(f"获取订单包装统计数据失败: {str(e)}")
return {"status": False, "message": str(e)}
def get_tray_package_statistics(self, order_id, tray_id, corp_id):
"""获取托盘包装统计信息
Args:
order_id: 订单号
tray_id: 托盘号
corp_id: 公司ID
Returns:
dict: 托盘包装统计信息
"""
# 生成缓存键
cache_key = f"{order_id}_{tray_id}_{corp_id}"
# 检查缓存是否有效
current_time = time.time()
if (cache_key in self._tray_stats_cache and
current_time - self._tray_stats_cache_time.get(cache_key, 0) < self._cache_ttl):
logging.info(f"使用缓存数据: getBzNumByTrayWszb.do, 参数={order_id}, {tray_id}")
return self._tray_stats_cache[cache_key]
# 原有API调用逻辑
logging.info(f"调用API: getBzNumByTrayWszb.do, 参数={order_id}, {tray_id}")
try:
# API 配置中的键名
api_key = "get_tray_package_statistics"
# 构建URL参数
params = {
"orderId": order_id,
"trayId": tray_id,
"data_corp": corp_id
}
response = self.api_utils.get(api_key, params=params)
if response and response.get('status', False):
# 更新缓存
self._tray_stats_cache[cache_key] = response
self._tray_stats_cache_time[cache_key] = current_time
return response
except Exception as e:
logging.error(f"获取托盘包装统计数据失败: {str(e)}")
return {"status": False, "message": str(e)}
def get_spack_info(self, order_id, tray_id, corp_id):
"""
获取spack信息
Args:
order_id: 订单号
tray_id: 托盘号
corp_id: 公司ID
Returns:
dict: 包含spack信息的字典
"""
try:
# API 配置中的键名
api_key = "get_spack_info"
# 构建URL参数
params = {
"orderId": order_id,
"trayId": tray_id,
"data_corp": corp_id
}
response = self.api_utils.get(api_key, params=params)
return response
except Exception as e:
logging.error(f"获取spack信息失败: {str(e)}")
return {"status": False, "message": str(e)}
def clear_tray_stats_cache(self, order_id=None, tray_id=None, corp_id=None):
"""清除托盘统计数据缓存
Args:
order_id: 订单号如果为None则清除所有缓存
tray_id: 托盘号如果为None则清除该订单的所有缓存
corp_id: 公司ID如果为None则清除该订单和托盘的所有缓存
"""
if order_id is None:
# 清除所有缓存
self._tray_stats_cache = {}
self._tray_stats_cache_time = {}
logging.info("已清除所有托盘统计数据缓存")
return
# 清除特定缓存
keys_to_remove = []
prefix = f"{order_id}_"
if tray_id:
prefix += f"{tray_id}_"
if corp_id:
prefix += f"{corp_id}"
for key in list(self._tray_stats_cache.keys()):
if key.startswith(prefix):
keys_to_remove.append(key)
for key in keys_to_remove:
if key in self._tray_stats_cache:
del self._tray_stats_cache[key]
if key in self._tray_stats_cache_time:
del self._tray_stats_cache_time[key]
logging.info(f"已清除托盘统计数据缓存: 前缀={prefix}, 清除数量={len(keys_to_remove)}")

View File

@ -7,7 +7,7 @@
"enable_keyboard_listener": false,
"enable_camera": false
},
"base_url": "http://localhost:8084",
"base_url": "https://jsjtnew.tengzhicn.com",
"mode": "api"
},
"apis": {
@ -15,8 +15,15 @@
"get_gc_info": "/jsjt/xcsc/tprk/getBZGCInfoToWsbz.do",
"get_order_info": "/jsjt/xcsc/tprk/getXsddBzrkGridListToWsbz.do",
"add_order_info": "/jsjt/xcsc/tprk/bzrkAdd01.do",
"remove_order_info": "/jsjt/xcsc/tprk/removeByOrder.do",
"get_xpack": "/jsjt/xcsc/tprk/getXpackToWsbz.do",
"ismt_option": "/jsjt/xcsc/tprk/ismtOptioTonWsbz.do"
"ismt_option": "/jsjt/xcsc/tprk/ismtOptioTonWsbz.do",
"get_params": "/select/getcombcodeWsbz.do",
"get_luno": "/common/luno/getLunoListWsbz.do",
"get_order_info_by_xpack": "/jsjt/xcsc/tprk/getXsddBzrkGridListByXpackToWsbz.do",
"get_package_statistics": "/jsjt/xcsc/tprk/getBzNumByOrderidWszb.do",
"get_tray_package_statistics": "/jsjt/xcsc/tprk/getBzNumByTrayWszb.do",
"get_spack_info": "/jsjt/xcsc/tprk/getSpackWszb.do"
},
"database": {
"default": "sqlite",
@ -87,10 +94,11 @@
"data_bits": 8,
"parity": "N",
"port": "9600",
"ser": "COM2",
"ser": "",
"stable_threshold": 10,
"stop_bits": 1,
"timeout": 1
"timeout": 1,
"comment": "称重通过寄存器D11获取无需串口"
},
"xj": {
"bit": 10,
@ -99,7 +107,7 @@
"parity": "N",
"port": "19200",
"query_cmd": "01 41 0d",
"query_interval": 5,
"query_interval": 1,
"auto_query": true,
"ser": "COM3",
"stop_bits": 1,
@ -109,10 +117,10 @@
"code": "scanner",
"data_bits": 8,
"parity": "N",
"port": "9600",
"ser": "COM3",
"port": "115200",
"ser": "COM1",
"stop_bits": 1,
"timeout": 1
"timeout": 0.5
}
},
"electricity": {

View File

@ -180,7 +180,7 @@ class InspectionDAO:
# 构建SQL
sql = f"""
UPDATE inspection_config
UPDATE wsbz_inspection_config
SET {', '.join(update_fields)}
WHERE id = ?
"""
@ -253,7 +253,8 @@ class InspectionDAO:
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 = ?,bz_tqd = ?,type_name = ?,remarks_hb=?
bccd = ? ,tccd = ?, zzyq = ?, customer = ?,customerexp = ?,
bz_bqd = ?,bz_tqd = ?,type_name = ?,remarks_hb=?,khno=?
WHERE ddmo = ?
"""
params = (
@ -295,6 +296,7 @@ class InspectionDAO:
data.get("bz_tqd", ""),
data.get("type_name", ""),
data.get("remarks_hb", ""),
data.get("khno", ""),
data.get("mo", "") # WHERE 条件参数
)
logging.info(f"更新订单信息: ddmo={data.get('mo', '')}")
@ -305,10 +307,12 @@ 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,customer,customerexp,bz_bqd,bz_tqd,type_name,remarks_hb
pono, xj, ysl, dycz, zx_code, edit_id, remarks,zx_name,
bccd,tccd,zzyq,customer,customerexp,bz_bqd,bz_tqd,type_name,
remarks_hb,khno
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
"""
params = (
@ -350,7 +354,8 @@ class InspectionDAO:
data.get("bz_bqd", ""),
data.get("bz_tqd", ""),
data.get("type_name", ""),
data.get("remarks_hb", "")
data.get("remarks_hb", ""),
data.get("khno", "")
)
logging.info(f"插入新订单信息: ddmo={data.get('mo', '')}")
@ -389,14 +394,14 @@ class InspectionDAO:
status = item.get('status', '')
remark = item.get('remark', '')
tray_id = item.get('tray_id', '')
package_id = item.get('package_id', '')
# 获取新游标执行查询,避免递归使用
check_cursor = db.get_new_cursor()
check_sql = """
SELECT id FROM wsbz_inspection_data
WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ?
WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ? AND package_id = ?
"""
check_params = (order_id, gc_note, position, tray_id)
check_params = (order_id, gc_note, position, tray_id, package_id)
check_cursor.execute(check_sql, check_params)
existing_record = check_cursor.fetchone()
@ -408,12 +413,12 @@ class InspectionDAO:
UPDATE wsbz_inspection_data
SET config_id = ?, value = ?, status = ?, remark = ?,
update_time = ?, update_by = ?
WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ?
WHERE order_id = ? AND gc_note = ? AND position = ? AND tray_id = ? AND package_id = ?
"""
update_params = (
config_id, value, status, remark,
current_time, username,
order_id, gc_note, position, tray_id
order_id, gc_note, position, tray_id, package_id
)
db.execute_update(update_sql, update_params)
else:
@ -421,12 +426,12 @@ class InspectionDAO:
insert_sql = """
INSERT INTO wsbz_inspection_data (
order_id, gc_note, position, config_id, value, status, remark,
create_time, create_by, update_time, update_by, tray_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
create_time, create_by, update_time, update_by, tray_id, package_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
insert_params = (
order_id, gc_note, position, config_id, value, status, remark,
current_time, username, current_time, username, tray_id
current_time, username, current_time, username, tray_id, package_id
)
db.execute_update(insert_sql, insert_params)
@ -436,19 +441,34 @@ class InspectionDAO:
except Exception as e:
logging.error(f"保存检验数据失败: {str(e)}")
return False
def get_inspection_data_unfinished(self, tray_id):
def get_inspection_data_unfinished(self, tray_id, package_id=None):
"""获取未完成的检验数据,通过是否贴标来判断
Args:
tray_id: 托盘号
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
list: 未完成的检验数据列表
"""
try:
# 先获取所有没有贴标的工程号
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql_orders = """
SELECT DISTINCT d.gc_note
FROM wsbz_inspection_data d
WHERE d.is_deleted = FALSE AND d.tray_id = ? AND d.package_id = ?
AND status != 'labeled'
"""
params = (tray_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
sql_orders = """
SELECT DISTINCT d.gc_note
FROM wsbz_inspection_data d
WHERE d.is_deleted = FALSE AND d.tray_id = ?
AND d.position = 11 AND COALESCE(d.value,'') = ''
AND status != 'labeled'
"""
params = (tray_id,)
with SQLUtils('sqlite', database='db/jtDB.db') as db:
@ -463,9 +483,24 @@ class InspectionDAO:
placeholders = ','.join(['?' for _ in gc_notes])
# 获取这些工程号的所有检验数据
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql = f"""
SELECT d.id, d.gc_note, d.position, d.config_id, d.value, d.status, d.remark,
c.name, c.display_name, c.data_type, c.unit
c.name, c.display_name, c.data_type, c.unit, d.package_id
FROM wsbz_inspection_data d
LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id
WHERE d.is_deleted = FALSE AND d.tray_id = ? AND d.package_id = ?
AND d.gc_note IN ({placeholders})
ORDER BY d.create_time
"""
params = [tray_id, package_id] + gc_notes
else:
# 如果没有提供package_id则只使用tray_id查询
sql = f"""
SELECT d.id, d.gc_note, d.position, d.config_id, d.value, d.status, d.remark,
c.name, c.display_name, c.data_type, c.unit, d.package_id
FROM wsbz_inspection_data d
LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id
WHERE d.is_deleted = FALSE AND d.tray_id = ?
@ -491,7 +526,8 @@ class InspectionDAO:
'name': row[7],
'display_name': row[8],
'data_type': row[9],
'unit': row[10]
'unit': row[10],
'package_id': row[11] if len(row) > 11 else ''
}
data_list.append(data)
@ -499,20 +535,34 @@ class InspectionDAO:
except Exception as e:
logging.error(f"获取未完成的检验数据失败: {str(e)}")
return []
def get_inspection_data_by_order(self, order_id,gc_note, tray_id):
def get_inspection_data_by_order(self, order_id, gc_note, tray_id, package_id=None):
"""根据工程号获取检验数据
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
list: 检验数据列表
"""
try:
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql = """
SELECT d.id, d.position, d.config_id, d.value, d.status, d.remark,
c.name, c.display_name, c.data_type, c.unit
c.name, c.display_name, c.data_type, c.unit, d.package_id
FROM wsbz_inspection_data d
LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id
WHERE d.order_id = ? AND d.gc_note = ? AND d.is_deleted = FALSE AND d.tray_id = ? AND d.package_id = ?
ORDER BY d.create_time, d.order_id, d.position
"""
params = (order_id, gc_note, tray_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
sql = """
SELECT d.id, d.position, d.config_id, d.value, d.status, d.remark,
c.name, c.display_name, c.data_type, c.unit, d.package_id
FROM wsbz_inspection_data d
LEFT JOIN wsbz_inspection_config c ON d.config_id = c.id
WHERE d.order_id = ? AND d.gc_note = ? AND d.is_deleted = FALSE AND d.tray_id = ?
@ -536,7 +586,8 @@ class InspectionDAO:
'name': row[6],
'display_name': row[7],
'data_type': row[8],
'unit': row[9]
'unit': row[9],
'package_id': row[10] if len(row) > 10 else ''
}
data_list.append(data)
@ -545,11 +596,12 @@ class InspectionDAO:
logging.error(f"获取检验数据失败: {str(e)}")
return []
def get_package_record(self, tray_id):
def get_package_record(self, tray_id, package_id=None):
"""根据托盘号获取包装记录
Args:
tray_id: 托盘号
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
list: 包装记录列表
"""
@ -578,23 +630,33 @@ class InspectionDAO:
except Exception as e:
logging.error(f"获取包装记录失败: {str(e)}")
return []
def save_package_record(self, order_id, tray_id, label_value, weight_value,net_weight_value, finish_time,gc_note):
def save_package_record(self, order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, gc_note=None, package_id=None):
"""保存包装记录
Args:
order_id: 工程
order_id: 订单
tray_id: 托盘号
label_value: 标签值
weight_value: 重量值
label_value: 贴标值
weight_value: 称重值
net_weight_value: 净重值
finish_time: 完成时间
gc_note: 工程号
package_id: 箱号(spack)如果为None则使用tray_id
Returns:
bool: 保存是否成功
"""
# TODO调用接口获取到工程号对应的其他信息比如材质规格后续完成
try:
# 如果没有提供package_id则使用tray_id
if package_id is None:
package_id = tray_id
sql = """
INSERT INTO wsbz_inspection_pack_data (order_id, tray_id, axis_package_id, weight, net_weight, pack_time, create_time, create_by, update_time, update_by, is_deleted,gc_note)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
INSERT INTO wsbz_inspection_pack_data (order_id, tray_id, axis_package_id, weight, net_weight, pack_time, create_time, create_by, update_time, update_by, is_deleted, gc_note, package_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False,gc_note)
params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False, gc_note, package_id)
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.begin_transaction()
@ -605,21 +667,136 @@ class InspectionDAO:
except Exception as e:
logging.error(f"保存包装记录失败: {str(e)}")
return False
def delete_inspection_data(self, order_id, gc_note, tray_id):
def get_product_status(self, order_id, gc_note, tray_id):
"""获取产品的当前状态
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
Returns:
str: 产品状态如果没有找到则返回'init'
"""
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
sql = """
SELECT status FROM wsbz_inspection_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
ORDER BY id ASC LIMIT 1
"""
params = (order_id, gc_note, tray_id)
db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return result[0] if result and result[0] else 'init' # 默认为init状态
except Exception as e:
logging.error(f"获取产品状态失败: {str(e)}")
return 'init' # 出错时返回默认状态
def check_package_record_exists(self, order_id, gc_note, tray_id, package_id=None):
"""检查指定工程号和托盘号的包装记录是否已存在
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
bool: 记录是否存在
"""
try:
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql = """
SELECT COUNT(*) FROM wsbz_inspection_pack_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND package_id = ? AND is_deleted = FALSE
"""
params = (order_id, gc_note, tray_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
sql = """
SELECT COUNT(*) FROM wsbz_inspection_pack_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND is_deleted = FALSE
"""
params = (order_id, gc_note, tray_id)
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return result[0] > 0 if result else False
except Exception as e:
logging.error(f"检查包装记录是否存在失败: {str(e)}")
return False
def update_product_status(self, order_id, gc_note, tray_id, new_status, package_id=None):
"""更新产品的状态
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
new_status: 新状态
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
bool: 更新是否成功
"""
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
# 更新该产品所有记录的状态字段
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
update_sql = """
UPDATE wsbz_inspection_data SET status = ?, update_time = ?
WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND package_id = ?
"""
update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
order_id, gc_note, tray_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
update_sql = """
UPDATE wsbz_inspection_data SET status = ?, update_time = ?
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
"""
update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
order_id, gc_note, tray_id)
db.execute_update(update_sql, update_params)
if package_id:
logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 箱号={package_id}, 新状态={new_status}")
else:
logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 新状态={new_status}")
return True
except Exception as e:
logging.error(f"更新产品状态失败: {str(e)}")
return False
def delete_inspection_data(self, order_id, gc_note, tray_id, package_id=None):
"""删除检验数据
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
package_id: 箱号(spack)如果为None则只使用tray_id查询
"""
try:
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql = """
UPDATE wsbz_inspection_data
SET is_deleted = TRUE
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
DELETE FROM wsbz_inspection_data
WHERE gc_note = ? AND tray_id = ? AND package_id = ?
"""
params = (order_id, gc_note, tray_id)
params = (gc_note, tray_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
sql = """
DELETE FROM wsbz_inspection_data
WHERE gc_note = ? AND tray_id = ?
"""
params = (gc_note, tray_id)
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.begin_transaction()
@ -684,7 +861,7 @@ class InspectionDAO:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.cursor.execute(sql, params)
result = db.cursor.fetchone()
return result[0],result[1] if result else None,None
return (result[0],result[1]) if result else (None,None)
except Exception as e:
logging.error(f"获取线径范围失败: {str(e)}")
return None,None
@ -764,7 +941,7 @@ class InspectionDAO:
try:
sql = """
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
coalesce(qfqd,'') as 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,)
@ -800,7 +977,7 @@ class InspectionDAO:
"""
try:
sql = """
SELECT t1.order_id, CASE WHEN t1.position = 12 THEN 'mzl' ELSE name END AS name, value
SELECT t1.order_id, CASE t1.position WHEN 12 THEN 'mzl' when 2 then 'xj' ELSE name END AS name, value
FROM wsbz_inspection_data t1
LEFT JOIN main.wsbz_inspection_config wic ON t1.config_id = wic.id
WHERE gc_note = ? AND t1.order_id = ?
@ -907,3 +1084,342 @@ class InspectionDAO:
'order_num_year': 0,
'order_num_all': 0
}
def save_luno_info(self, luno_code, luno_data):
"""保存炉号信息到数据库
Args:
luno_code: 炉号编码
luno_data: 炉号数据字典
Returns:
bool: 保存是否成功
"""
try:
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 检查是否已存在该炉号
check_sql = "SELECT id FROM wsbz_luno_info WHERE luono = ?"
with SQLUtils('sqlite') as db:
db.cursor.execute(check_sql, (luno_code,))
existing = db.cursor.fetchone()
if existing:
# 更新现有记录
update_sql = """
UPDATE wsbz_luno_info SET
cz = ?, gg = ?, gc = ?, bz = ?, data_corp = ?, data_corp_name = ?,
c = ?, si = ?, mn = ?, p = ?, s = ?, ni = ?, cr = ?, ti = ?, mo = ?, cu = ?,
others = ?, klqd = ?, ysl = ?, rq = ?, update_time = ?, update_by = ?
WHERE luono = ?
"""
params = (
luno_data.get('cz', ''),
luno_data.get('gg', ''),
luno_data.get('gc', ''),
luno_data.get('bz', ''),
luno_data.get('data_corp', ''),
luno_data.get('data_corp_name', ''),
luno_data.get('c', ''),
luno_data.get('si', ''),
luno_data.get('mn', ''),
luno_data.get('p', ''),
luno_data.get('s', ''),
luno_data.get('ni', ''),
luno_data.get('cr', ''),
luno_data.get('ti', ''),
luno_data.get('mo', ''),
luno_data.get('cu', ''),
luno_data.get('others', ''),
luno_data.get('klqd', ''),
luno_data.get('ysl', ''),
luno_data.get('rq', ''),
current_time,
luno_data.get('user_name', 'system'),
luno_code
)
db.cursor.execute(update_sql, params)
else:
# 插入新记录
insert_sql = """
INSERT INTO wsbz_luno_info (
luono, cz, gg, gc, bz, data_corp, data_corp_name,
c, si, mn, p, s, ni, cr, ti, mo, cu, others, klqd, ysl, rq,
create_time, create_by, update_time, update_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
params = (
luno_code,
luno_data.get('cz', ''),
luno_data.get('gg', ''),
luno_data.get('gc', ''),
luno_data.get('bz', ''),
luno_data.get('data_corp', ''),
luno_data.get('data_corp_name', ''),
luno_data.get('c', ''),
luno_data.get('si', ''),
luno_data.get('mn', ''),
luno_data.get('p', ''),
luno_data.get('s', ''),
luno_data.get('ni', ''),
luno_data.get('cr', ''),
luno_data.get('ti', ''),
luno_data.get('mo', ''),
luno_data.get('cu', ''),
luno_data.get('others', ''),
luno_data.get('klqd', ''),
luno_data.get('ysl', ''),
luno_data.get('rq', ''),
current_time,
luno_data.get('user_name', 'system'),
current_time,
luno_data.get('user_name', 'system')
)
db.cursor.execute(insert_sql, params)
db.connection.commit()
logging.info(f"成功保存炉号信息: {luno_code}")
return True
except Exception as e:
logging.error(f"保存炉号信息失败: {str(e)}")
return False
def get_luno_info(self, luno_code):
"""根据炉号获取炉号信息
Args:
luno_code: 炉号编码
Returns:
dict: 炉号信息字典未找到返回None
"""
try:
sql = """
SELECT luono, cz, gg, gc, bz, data_corp, data_corp_name,
c, si, mn, p, s, ni, cr, ti, mo, cu, others, klqd, ysl, rq,
create_time, create_by, update_time, update_by
FROM wsbz_luno_info
WHERE luono = ? AND is_deleted = FALSE
"""
with SQLUtils('sqlite') as db:
db.cursor.execute(sql, (luno_code,))
row = db.cursor.fetchone()
if row:
# 获取列名
column_names = [desc[0] for desc in db.cursor.description]
# 转换为字典
luno_info = {}
for i, value in enumerate(row):
if i < len(column_names):
luno_info[column_names[i]] = value
return luno_info
else:
return None
except Exception as e:
logging.error(f"获取炉号信息失败: {str(e)}")
return None
def delete_package_record(self, order_id, gc_note, tray_id, package_id=None):
"""删除包装记录
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
bool: 删除是否成功
"""
try:
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql = """
DELETE FROM wsbz_inspection_pack_data
WHERE gc_note = ? AND tray_id = ? AND package_id = ?
"""
params = (gc_note, tray_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
sql = """
DELETE FROM wsbz_inspection_pack_data
WHERE gc_note = ? AND tray_id = ?
"""
params = (gc_note, tray_id)
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.begin_transaction()
db.execute_update(sql, params)
db.commit_transaction()
if package_id:
logging.info(f"已删除包装记录: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 箱号={package_id}")
else:
logging.info(f"已删除包装记录: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}")
return True
except Exception as e:
logging.error(f"删除包装记录失败: {str(e)}")
return False
def get_inspection_data_by_config(self, order_id, gc_note, tray_id, position, config_id, package_id=None):
"""根据工程号、托盘号、位置和配置ID查询检验数据
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
position: 位置序号
config_id: 配置ID
package_id: 箱号(spack)如果为None则只使用tray_id查询
Returns:
dict: 检验数据记录如果不存在则返回None
"""
try:
# 使用SQLUtils获取数据库连接
if package_id:
# 如果提供了package_id则同时使用tray_id和package_id查询
sql = """
SELECT id, order_id, gc_note, position, config_id, value, status, remark, tray_id, package_id, create_time, update_time
FROM wsbz_inspection_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND position = ? AND config_id = ? AND package_id = ?
ORDER BY update_time DESC
LIMIT 1
"""
params = (order_id, gc_note, tray_id, position, config_id, package_id)
else:
# 如果没有提供package_id则只使用tray_id查询
sql = """
SELECT id, order_id, gc_note, position, config_id, value, status, remark, tray_id, package_id, create_time, update_time
FROM wsbz_inspection_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ? AND position = ? AND config_id = ?
ORDER BY update_time DESC
LIMIT 1
"""
params = (order_id, gc_note, tray_id, position, config_id)
with SQLUtils('sqlite', database='db/jtDB.db') as db:
# 执行查询
db.cursor.execute(sql, params)
# 获取结果
row = db.cursor.fetchone()
# 如果有结果,转换为字典
if row:
return {
'id': row[0],
'order_id': row[1],
'gc_note': row[2],
'position': row[3],
'config_id': row[4],
'value': row[5],
'status': row[6],
'remark': row[7],
'tray_id': row[8],
'package_id': row[9],
'create_time': row[10],
'update_time': row[11]
}
else:
return None
except Exception as e:
logging.error(f"查询检验数据失败: {str(e)}")
return None
# def get_package_statistics(self, order_id=None):
# """获取包装记录的统计数据
# Args:
# order_id: 订单号如果为None则获取所有订单的统计
# Returns:
# dict: 包含当前订单和所有订单的统计数据
# {
# 'count': 当前订单的记录数量,
# 'weight': 当前订单的总重量,
# 'count_all': 所有订单的记录数量,
# 'weight_all': 所有订单的总重量
# }
# """
# try:
# # 构建SQL查询
# sql = """
# SELECT SUM(weight) weight,
# SUM(count) count,
# SUM(count_all) count_all,
# SUM(weight_all) weight_all
# FROM (
# SELECT COUNT(gc_note) AS count,
# SUM(weight) AS weight,
# '' AS count_all,
# '' AS weight_all
# FROM wsbz_inspection_pack_data
# WHERE order_id = ?
# UNION ALL
# SELECT '', '',
# COUNT(gc_note) AS count_all,
# SUM(weight) AS weight_all
# FROM wsbz_inspection_pack_data
# ) a
# """
# with SQLUtils('sqlite', database='db/jtDB.db') as db:
# # 执行查询
# db.cursor.execute(sql, (order_id or '',))
# # 获取结果
# row = db.cursor.fetchone()
# # 如果有结果,转换为字典
# if row:
# return {
# 'weight': float(row[0] or 0),
# 'count': int(row[1] or 0),
# 'count_all': int(row[2] or 0),
# 'weight_all': float(row[3] or 0)
# }
# else:
# return {
# 'weight': 0,
# 'count': 0,
# 'count_all': 0,
# 'weight_all': 0
# }
# except Exception as e:
# logging.error(f"获取包装记录统计数据失败: {str(e)}")
# return {
# 'weight': 0,
# 'count': 0,
# 'count_all': 0,
# 'weight_all': 0
# }
def get_spack_by_order_id(self, order_id,tray_id):
"""根据订单号和托盘号获取箱号(spack)
Args:
order_id: 订单号
tray_id: 托盘号
Returns:
str: 箱号(spack)如果不存在则返回空字符串
"""
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.cursor.execute("SELECT package_id FROM wsbz_inspection_data WHERE order_id = ? AND tray_id = ? LIMIT 1", (order_id, tray_id))
row = db.cursor.fetchone()
if row:
return row[0]
else:
return None
except Exception as e:
logging.error(f"获取箱号(spack)失败: {str(e)}")
return None

View File

@ -367,8 +367,8 @@ class PalletTypeDAO:
update_time, update_by, enabled, is_deleted
) VALUES (?, ?, ?, ?, ?, ?, TRUE, FALSE)
"""
params = (pallet_code, tier, current_time, user_id,
current_time, user_id)
params = (pallet_code, tier, current_time, user_id[0],
current_time, user_id[0])
self.db.execute_update(insert_sql, params)
logging.info(f"创建新托盘档案记录:包装号={pallet_code}, 层数={tier}")

170
dao/report_dao.py Normal file
View File

@ -0,0 +1,170 @@
import logging
from datetime import datetime
from utils.sql_utils import SQLUtils
class ReportDAO:
"""报表数据访问对象,处理报表相关的数据库操作"""
def __init__(self):
"""初始化数据访问对象"""
# 不再在初始化时创建数据库连接,而是在需要时创建
pass
def get_production_report(self, start_date, end_date, customer=None, material=None, spec=None):
"""获取生产报表数据
Args:
start_date: 开始日期格式为 'YYYY-MM-DD'
end_date: 结束日期格式为 'YYYY-MM-DD'
customer: 客户名称用于模糊查询可选
material: 材质用于模糊查询可选
spec: 规格用于模糊查询可选
Returns:
list: 包含报表数据的字典列表
"""
try:
# 构建SQL查询
sql = """
SELECT DATE(pack_time) AS 日期
, customerexp AS 客户
, t1.order_id AS 订单号
, COUNT(DISTINCT t1.gc_note) AS '轴数'
, t2.cz AS '材质'
, t2.size AS '规格'
, ROUND(SUM(t1.net_weight), 2) AS '净重'
FROM wsbz_inspection_pack_data t1
LEFT JOIN wsbz_order_info t2 ON t1.order_id = t2.ddmo
WHERE pack_time BETWEEN ? AND ?
"""
params = [f"{start_date} 00:00:00", f"{end_date} 23:59:59"]
# 添加可选的筛选条件
if customer:
sql += " AND customerexp LIKE ?"
params.append(f"%{customer}%")
if material:
sql += " AND t2.cz LIKE ?"
params.append(f"%{material}%")
if spec:
sql += " AND t2.size LIKE ?"
params.append(f"%{spec}%")
# 添加分组条件
sql += " GROUP BY DATE(pack_time), t2.cz, t2.size, customerexp"
# 执行查询
with SQLUtils('sqlite', database='db/jtDB.db') as db:
db.cursor.execute(sql, params)
results = db.cursor.fetchall()
# 获取列名
columns = [desc[0] for desc in db.cursor.description]
# 转换为字典列表
data = []
for row in results:
row_dict = {}
for i, col in enumerate(columns):
row_dict[col] = row[i]
data.append(row_dict)
return data
except Exception as e:
logging.error(f"获取生产报表数据失败: {str(e)}")
raise e
def get_daily_report(self, date):
"""获取指定日期的日报表
Args:
date: 日期格式为 'YYYY-MM-DD'
Returns:
list: 包含报表数据的字典列表
"""
return self.get_production_report(date, date)
def get_monthly_report(self, year, month):
"""获取指定月份的月报表
Args:
year: 年份 2024
month: 月份 8
Returns:
list: 包含报表数据的字典列表
"""
# 计算月初和月末
start_date = f"{year}-{month:02d}-01"
# 计算月末日期
if month == 12:
next_year = year + 1
next_month = 1
else:
next_year = year
next_month = month + 1
end_date = f"{next_year}-{next_month:02d}-01"
# 使用日期范围查询
return self.get_production_report(start_date, end_date)
def get_yearly_report(self, year):
"""获取指定年份的年报表
Args:
year: 年份 2024
Returns:
list: 包含报表数据的字典列表
"""
start_date = f"{year}-01-01"
end_date = f"{year+1}-01-01"
# 使用日期范围查询
return self.get_production_report(start_date, end_date)
def get_customer_report(self, start_date, end_date, customer):
"""获取指定客户的报表
Args:
start_date: 开始日期格式为 'YYYY-MM-DD'
end_date: 结束日期格式为 'YYYY-MM-DD'
customer: 客户名称
Returns:
list: 包含报表数据的字典列表
"""
return self.get_production_report(start_date, end_date, customer=customer)
def get_material_report(self, start_date, end_date, material):
"""获取指定材质的报表
Args:
start_date: 开始日期格式为 'YYYY-MM-DD'
end_date: 结束日期格式为 'YYYY-MM-DD'
material: 材质
Returns:
list: 包含报表数据的字典列表
"""
return self.get_production_report(start_date, end_date, material=material)
def get_spec_report(self, start_date, end_date, spec):
"""获取指定规格的报表
Args:
start_date: 开始日期格式为 'YYYY-MM-DD'
end_date: 结束日期格式为 'YYYY-MM-DD'
spec: 规格
Returns:
list: 包含报表数据的字典列表
"""
return self.get_production_report(start_date, end_date, spec=spec)

BIN
data/inspection.db Normal file

Binary file not shown.

Binary file not shown.

View File

@ -162,3 +162,35 @@ CREATE TABLE IF NOT EXISTS packaging_record (
update_time TIMESTAMP,
update_by VARCHAR(50)
);
-- 创建炉号信息表
CREATE TABLE IF NOT EXISTS wsbz_luno_info (
id INTEGER PRIMARY KEY AUTOINCREMENT,
luono VARCHAR(50) NOT NULL, -- 炉号
cz VARCHAR(50), -- 材质
gg VARCHAR(50), -- 规格
gc VARCHAR(50), -- 钢厂
bz VARCHAR(50), -- 标准
data_corp VARCHAR(50), -- 公司账套
data_corp_name VARCHAR(100), -- 公司账套名称
c VARCHAR(20), -- C含量
si VARCHAR(20), -- Si含量
mn VARCHAR(20), -- Mn含量
p VARCHAR(20), -- P含量
s VARCHAR(20), -- S含量
ni VARCHAR(20), -- Ni含量
cr VARCHAR(20), -- Cr含量
ti VARCHAR(20), -- Ti含量
mo VARCHAR(20), -- Mo含量
cu VARCHAR(20), -- Cu含量
others VARCHAR(100), -- 其他元素
klqd VARCHAR(50), -- 抗拉强度
ysl VARCHAR(50), -- 延伸率
rq VARCHAR(50), -- 日期
create_time TIMESTAMP NOT NULL,
create_by VARCHAR(50) NOT NULL,
update_time TIMESTAMP,
update_by VARCHAR(50),
is_deleted BOOLEAN DEFAULT FALSE,
UNIQUE(luono, is_deleted) -- 确保炉号唯一
);

367
fix_status_management.py Normal file
View File

@ -0,0 +1,367 @@
#!/usr/bin/env python3
"""
修复状态管理功能的脚本
"""
import os
import re
# 1. 在InspectionDAO类中添加状态管理方法
DAO_METHODS = """
def get_product_status(self, order_id, gc_note, tray_id):
\"\"\"获取产品的当前状态
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
Returns:
str: 产品状态如果没有找到则返回'init'
\"\"\"
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
sql = \"\"\"
SELECT status FROM wsbz_inspection_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
ORDER BY id ASC LIMIT 1
\"\"\"
params = (order_id, gc_note, tray_id)
result = db.query_one(sql, params)
return result[0] if result and result[0] else 'init' # 默认为init状态
except Exception as e:
logging.error(f"获取产品状态失败: {str(e)}")
return 'init' # 出错时返回默认状态
def update_product_status(self, order_id, gc_note, tray_id, new_status):
\"\"\"更新产品的状态
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
new_status: 新状态
Returns:
bool: 更新是否成功
\"\"\"
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
# 更新该产品所有记录的状态字段
update_sql = \"\"\"
UPDATE wsbz_inspection_data SET status = ?, update_time = ?
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
\"\"\"
update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
order_id, gc_note, tray_id)
db.execute(update_sql, update_params)
logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 新状态={new_status}")
return True
except Exception as e:
logging.error(f"更新产品状态失败: {str(e)}")
return False
"""
# 2. 检查检验完成的方法
CHECK_INSPECTION_METHOD = """
def check_inspection_completed(self, row):
\"\"\"检查行是否有至少一个检验项已完成如果是则更新状态为inspected
Args:
row: 行索引
Returns:
bool: 是否有至少一个检验项已完成
\"\"\"
try:
# 获取工程号
gc_note_item = self.process_table.item(row, 1)
if not gc_note_item:
return False
gc_note = gc_note_item.text().strip()
tray_id = self.tray_edit.text()
# 获取启用的检验配置
enabled_configs = self.inspection_manager.get_enabled_configs()
# 检查是否有至少一个检验项有值
has_any_value = False
for i, config in enumerate(enabled_configs):
col_index = 2 + i
item = self.process_table.item(row, col_index)
if item and item.text().strip():
has_any_value = True
break
# 如果有至少一个检验项有值更新状态为inspected
if has_any_value:
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'inspected')
logging.info(f"工程号 {gc_note} 的检验已完成状态更新为inspected")
return has_any_value
except Exception as e:
logging.error(f"检查检验完成状态失败: {str(e)}")
return False
"""
# 3. 修改的save_inspection_data方法
SAVE_INSPECTION_DATA_METHOD = """
def save_inspection_data(self, order_id, gc_note, tray_id, position, config_id, value, status):
\"\"\"保存检验数据到数据库
Args:
order_id: 订单号
gc_note: 工程号
position: 位置序号
config_id: 配置ID
value: 检验值
status: 状态
\"\"\"
try:
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
modbus = ModbusUtils()
client = modbus.get_client()
# 获取当前产品状态,优先使用产品状态管理中的状态
current_status = inspection_dao.get_product_status(order_id, gc_note, tray_id)
# 如果当前状态不是初始状态则使用当前状态而不是传入的status
if current_status not in ['', 'init']:
status = current_status
# 记录保存前的详细日志
logging.info(f"正在保存检验数据: 工程号={gc_note}, 托盘号={tray_id}, 位置={position}, 配置ID={config_id}, 值={value}, 状态={status}")
# 构建数据
data = [{
'position': position,
'config_id': config_id,
'value': value,
'status': status,
'remark': '',
'tray_id': tray_id
}]
# 保存到数据库
inspection_dao.save_inspection_data(order_id, gc_note, data)
"""
# 4. 修改_process_stable_weight方法中的查找行逻辑
PROCESS_STABLE_WEIGHT_FIND_ROW = """
# 基于状态查找行优先查找状态为inspected的行
data_row = None
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 首先查找状态为inspected的行
for row in range(2, self.process_table.rowCount()):
gc_note_item = self.process_table.item(row, 1)
if gc_note_item:
row_gc_note = gc_note_item.text().strip()
tray_id = self.tray_edit.text()
status = inspection_dao.get_product_status(self._current_order_code, row_gc_note, tray_id)
if status == 'inspected':
data_row = row
logging.info(f"找到状态为inspected的行: {data_row}, 工程号: {row_gc_note}")
break
# 如果没有找到inspected状态的行回退到原有逻辑
if data_row is None:
# 查找第一个没有称重数据的行
for row in range(2, self.process_table.rowCount()):
weight_item = self.process_table.item(row, weight_col)
if not weight_item or not weight_item.text().strip():
data_row = row
break
# 如果仍然没有找到,使用当前选中行或第一个数据行
if data_row is None:
current_row = self.process_table.currentRow()
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行索引为2
logging.info(f"未找到状态为inspected的行或没有称重数据的行使用当前选中行或第一个数据行: {data_row}")
else:
logging.info(f"找到没有称重数据的行: {data_row}")
else:
logging.info(f"将使用状态为inspected的行: {data_row}")
"""
# 5. 添加称重完成后的状态更新代码
PROCESS_STABLE_WEIGHT_UPDATE_STATUS = """
# 更新产品状态为weighed
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'weighed')
logging.info(f"工程号 {gc_note} 的称重已完成状态更新为weighed")
"""
# 6. 修改handle_label_signal方法中的查找行逻辑
HANDLE_LABEL_SIGNAL_FIND_ROW = """
# 基于状态查找行优先查找状态为weighed的行
data_row = None
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 首先查找状态为weighed的行
for row in range(2, self.process_table.rowCount()):
gc_note_item = self.process_table.item(row, 1)
if gc_note_item:
row_gc_note = gc_note_item.text().strip()
tray_id = self.tray_edit.text()
status = inspection_dao.get_product_status(self._current_order_code, row_gc_note, tray_id)
if status == 'weighed':
data_row = row
logging.info(f"找到状态为weighed的行: {data_row}, 工程号: {row_gc_note}")
break
# 如果没有找到weighed状态的行回退到原有逻辑
if data_row is None:
# 使用当前选中的行或第一个数据行
current_row = self.process_table.currentRow()
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行索引为2
logging.info(f"未找到状态为weighed的行使用当前选中行或第一个数据行: {data_row}")
else:
logging.info(f"将使用状态为weighed的行: {data_row}")
"""
# 7. 添加贴标完成后的状态更新代码
HANDLE_LABEL_SIGNAL_UPDATE_STATUS = """
# 更新产品状态为labeled
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'labeled')
logging.info(f"工程号 {gc_note} 的贴标已完成状态更新为labeled")
"""
# 8. 在handle_inspection_cell_changed方法末尾添加调用check_inspection_completed
HANDLE_INSPECTION_CELL_CHANGED_CALL = """
# 检查是否完成检验并更新状态
self.check_inspection_completed(row)
"""
# 9. 在add_new_inspection_row方法末尾添加初始化状态代码
ADD_NEW_INSPECTION_ROW_INIT_STATUS = """
# 初始化产品状态为init
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'init')
logging.info(f"已添加工程号 {gc_note} 的新记录,显示在第{new_seq}初始状态为init")
"""
# 10. 移除数据校验逻辑,替换为简单的单元格颜色设置
REMOVE_VALIDATION_LOGIC = """
# 设置单元格颜色为浅绿色,表示已填写
cell_item.setBackground(QBrush(QColor("#c8e6c9")))
"""
# 主函数:应用所有修改
def apply_fixes():
print("开始应用状态管理功能修复...")
# 备份文件
os.system("cp dao/inspection_dao.py dao/inspection_dao.py.bak")
os.system("cp widgets/main_window.py widgets/main_window.py.bak")
# 1. 添加DAO方法
with open("dao/inspection_dao.py", "r") as f:
dao_content = f.read()
# 检查方法是否已存在
if "def get_product_status" not in dao_content:
# 找到合适的位置插入新方法
pattern = r'def save_package_record.*?return False'
match = re.search(pattern, dao_content, re.DOTALL)
if match:
insert_pos = match.end()
new_content = dao_content[:insert_pos] + DAO_METHODS + dao_content[insert_pos:]
with open("dao/inspection_dao.py", "w") as f:
f.write(new_content)
print("1. 已成功添加状态管理方法到 dao/inspection_dao.py")
else:
print("无法找到合适的位置插入DAO方法")
else:
print("1. 状态管理方法已存在,跳过添加")
# 读取main_window.py
with open("widgets/main_window.py", "r") as f:
main_window_content = f.read()
# 2. 添加check_inspection_completed方法
if "def check_inspection_completed" not in main_window_content:
# 找到合适的位置插入新方法
pattern = r'def validate_inspection_value.*?return False'
match = re.search(pattern, main_window_content, re.DOTALL)
if match:
insert_pos = match.end()
new_content = main_window_content[:insert_pos] + "\n" + CHECK_INSPECTION_METHOD + main_window_content[insert_pos:]
main_window_content = new_content
print("2. 已成功添加check_inspection_completed方法")
else:
print("无法找到合适的位置插入check_inspection_completed方法")
else:
print("2. check_inspection_completed方法已存在跳过添加")
# 3. 修改save_inspection_data方法
pattern = r'def save_inspection_data.*?inspection_dao\.save_inspection_data\(order_id, gc_note, data\)'
replacement = SAVE_INSPECTION_DATA_METHOD
main_window_content = re.sub(pattern, replacement, main_window_content, flags=re.DOTALL)
print("3. 已成功修改save_inspection_data方法")
# 4. 修改_process_stable_weight方法中的查找行逻辑
pattern = r'# 查找第一个没有称重数据的行\s*data_row = None\s*for row in range.*?if data_row is None:.*?else:\s*logging\.info\(f"找到没有称重数据的行: \{data_row\}"\)'
replacement = PROCESS_STABLE_WEIGHT_FIND_ROW
main_window_content = re.sub(pattern, replacement, main_window_content, flags=re.DOTALL)
print("4. 已成功修改_process_stable_weight方法中的查找行逻辑")
# 5. 添加称重完成后的状态更新代码
pattern = r'(logging\.info\(f"已将稳定的称重数据 \{weight_kg\}kg 写入行 \{data_row\}, 列 \{weight_col\}"\))\s*\n\s*except'
replacement = r'\1\n\n' + PROCESS_STABLE_WEIGHT_UPDATE_STATUS + r'\n except'
main_window_content = re.sub(pattern, replacement, main_window_content)
print("5. 已成功添加称重完成后的状态更新代码")
# 6. 修改handle_label_signal方法中的查找行逻辑
pattern = r'# 获取当前选中的行或第一个数据行\s*current_row = self\.process_table\.currentRow\(\)\s*data_row = current_row if current_row >= 2 else 2'
replacement = HANDLE_LABEL_SIGNAL_FIND_ROW
main_window_content = re.sub(pattern, replacement, main_window_content)
print("6. 已成功修改handle_label_signal方法中的查找行逻辑")
# 7. 添加贴标完成后的状态更新代码
pattern = r'(logging\.info\(f"已将贴标数据 \{axios_num\} 保存到数据库"\))\s*\n\s*# 调用加载到包装记录的方法'
replacement = r'\1\n\n' + HANDLE_LABEL_SIGNAL_UPDATE_STATUS + r'\n \n # 调用加载到包装记录的方法'
main_window_content = re.sub(pattern, replacement, main_window_content)
print("7. 已成功添加贴标完成后的状态更新代码")
# 8. 在handle_inspection_cell_changed方法末尾添加调用check_inspection_completed
pattern = r'(logging\.info\(f"处理单元格变更: 行=\{row\}, 列=\{column\}, 类型=\{data_type\}, 工程号=\{gc_note\}, 值=\{value\}, 状态=\{status\}"\))\s*\n\s*except'
replacement = r'\1\n\n' + HANDLE_INSPECTION_CELL_CHANGED_CALL + r'\n except'
main_window_content = re.sub(pattern, replacement, main_window_content)
print("8. 已成功在handle_inspection_cell_changed方法末尾添加调用check_inspection_completed")
# 9. 修改add_new_inspection_row方法设置初始状态
# 9.1 修改检验项的status为init
pattern = r"'status': '',\s*# 默认设置为通过状态"
replacement = "'status': 'init', # 设置初始状态"
main_window_content = re.sub(pattern, replacement, main_window_content)
# 9.2 修改贴标和称重项的status为init
pattern = r"'status': 'pass',\s*# 默认设置为通过状态"
replacement = "'status': 'init', # 设置初始状态"
main_window_content = re.sub(pattern, replacement, main_window_content)
# 9.3 添加状态初始化代码
pattern = r'(logging\.info\(f"已添加工程号 \{gc_note\} 的新记录,显示在第\{new_seq\}条"\))\s*\n\s*except'
replacement = ADD_NEW_INSPECTION_ROW_INIT_STATUS + r'\n except'
main_window_content = re.sub(pattern, replacement, main_window_content)
print("9. 已成功修改add_new_inspection_row方法设置初始状态")
# 10. 移除数据校验逻辑
pattern = r'# 验证数据有效性\s*if self\.validate_inspection_value\(config, value\):.*?status = \'warning\''
replacement = REMOVE_VALIDATION_LOGIC
main_window_content = re.sub(pattern, replacement, main_window_content, flags=re.DOTALL)
print("10. 已成功移除数据校验逻辑")
# 保存修改后的main_window.py
with open("widgets/main_window.py", "w") as f:
f.write(main_window_content)
print("状态管理功能修复完成!")
if __name__ == "__main__":
apply_fixes()

View File

@ -2,19 +2,28 @@ from pymodbus.client import ModbusTcpClient
import time
client = ModbusTcpClient('localhost', port=5020)
client.connect()
client.write_registers(address=11, values=[212])
client.write_registers(address=11, values=[19200])
# client.write_registers(address=3, values=[0])
time.sleep(2)
client.write_registers(address=0, values=[0])
client.write_registers(address=4, values=[0])
# time.sleep(2)
# client.write_registers(address=0, values=[0])
# 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])
client.write_registers(address=13, values=[1])
# client.write_registers(address=2, values=[0])
client.write_registers(address=13, values=[0])
# time.sleep(2)
# client.write_registers(address=20, values=[0])
# time.sleep(3)
# client.write_registers(address=20, values=[1])
# time.sleep(3)
# client.write_registers(address=20, values=[0])
result1 = client.read_holding_registers(address=3, count=1)
print(result1.registers[0],"123===")
result = client.read_holding_registers(address=0, count=1)
print(result.registers[0],"123===")
result2 = client.read_holding_registers(address=2, count=1)
print(result2.registers[0],"123===")
client.close()

378
status_management_patch.py Executable file
View File

@ -0,0 +1,378 @@
#!/usr/bin/env python3
"""
状态管理功能补丁文件
用于修复产品状态管理相关的代码
"""
import os
import re
# 定义要修改的文件
DAO_FILE = "dao/inspection_dao.py"
MAIN_WINDOW_FILE = "widgets/main_window.py"
# 1. 在InspectionDAO类中添加状态管理方法
def add_dao_methods():
with open(DAO_FILE, "r") as f:
content = f.read()
# 检查方法是否已存在
if "def get_product_status" in content:
print("状态管理方法已存在,跳过添加")
return
# 找到合适的位置插入新方法
pattern = r'(def save_package_record.*?\n\s*return False\n)'
new_methods = r"""\1
def get_product_status(self, order_id, gc_note, tray_id):
"""获取产品的当前状态
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
Returns:
str: 产品状态如果没有找到则返回'init'
"""
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
sql = """
SELECT status FROM wsbz_inspection_data
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
ORDER BY id ASC LIMIT 1
"""
params = (order_id, gc_note, tray_id)
result = db.query_one(sql, params)
return result[0] if result and result[0] else 'init' # 默认为init状态
except Exception as e:
logging.error(f"获取产品状态失败: {str(e)}")
return 'init' # 出错时返回默认状态
def update_product_status(self, order_id, gc_note, tray_id, new_status):
"""更新产品的状态
Args:
order_id: 订单号
gc_note: 工程号
tray_id: 托盘号
new_status: 新状态
Returns:
bool: 更新是否成功
"""
try:
with SQLUtils('sqlite', database='db/jtDB.db') as db:
# 更新该产品所有记录的状态字段
update_sql = """
UPDATE wsbz_inspection_data SET status = ?, update_time = ?
WHERE order_id = ? AND gc_note = ? AND tray_id = ?
"""
update_params = (new_status, datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
order_id, gc_note, tray_id)
db.execute(update_sql, update_params)
logging.info(f"已更新产品状态: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}, 新状态={new_status}")
return True
except Exception as e:
logging.error(f"更新产品状态失败: {str(e)}")
return False"""
modified_content = re.sub(pattern, new_methods, content, flags=re.DOTALL)
with open(DAO_FILE, "w") as f:
f.write(modified_content)
print(f"已成功添加状态管理方法到 {DAO_FILE}")
# 2. 修改add_new_inspection_row方法设置初始状态
def update_add_new_inspection_row():
with open(MAIN_WINDOW_FILE, "r") as f:
content = f.read()
# 修改检验项的status为init
pattern1 = r"'status': '',\s*# 默认设置为通过状态"
replacement1 = "'status': 'init', # 设置初始状态"
content = re.sub(pattern1, replacement1, content)
# 修改贴标和称重项的status为init
pattern2 = r"'status': 'pass',\s*# 默认设置为通过状态"
replacement2 = "'status': 'init', # 设置初始状态"
content = re.sub(pattern2, replacement2, content)
# 添加状态初始化代码
pattern3 = r"(logging\.info\(f\"已添加工程号 \{gc_note\} 的新记录,显示在第\{new_seq\}条\"\))"
replacement3 = r"""# 初始化产品状态为init
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'init')
logging.info(f"已添加工程号 {gc_note} 的新记录,显示在第{new_seq}初始状态为init")"""
content = re.sub(pattern3, replacement3, content)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功更新add_new_inspection_row方法")
# 3. 添加check_inspection_completed方法
def add_check_inspection_completed():
with open(MAIN_WINDOW_FILE, "r") as f:
content = f.read()
# 检查方法是否已存在
if "def check_inspection_completed" in content:
print("check_inspection_completed方法已存在跳过添加")
else:
# 找到合适的位置插入新方法
pattern = r'(def validate_inspection_value.*?)(\n\s*def )'
new_method = r"""\1
def check_inspection_completed(self, row):
"""检查行是否有至少一个检验项已完成如果是则更新状态为inspected
Args:
row: 行索引
Returns:
bool: 是否有至少一个检验项已完成
"""
try:
# 获取工程号
gc_note_item = self.process_table.item(row, 1)
if not gc_note_item:
return False
gc_note = gc_note_item.text().strip()
tray_id = self.tray_edit.text()
# 获取启用的检验配置
enabled_configs = self.inspection_manager.get_enabled_configs()
# 检查是否有至少一个检验项有值
has_any_value = False
for i, config in enumerate(enabled_configs):
col_index = 2 + i
item = self.process_table.item(row, col_index)
if item and item.text().strip():
has_any_value = True
break
# 如果有至少一个检验项有值更新状态为inspected
if has_any_value:
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'inspected')
logging.info(f"工程号 {gc_note} 的检验已完成状态更新为inspected")
return has_any_value
except Exception as e:
logging.error(f"检查检验完成状态失败: {str(e)}")
return False\2"""
content = re.sub(pattern, new_method, content, flags=re.DOTALL)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功添加check_inspection_completed方法")
# 在handle_inspection_cell_changed方法末尾添加调用
pattern = r'(logging\.info\(f"处理单元格变更: 行=\{row\}, 列=\{column\}, 类型=\{data_type\}, 工程号=\{gc_note\}, 值=\{value\}, 状态=\{status\}"\))\n(\s*except)'
replacement = r"""\1
# 检查是否完成检验并更新状态
self.check_inspection_completed(row)
\2"""
content = re.sub(pattern, replacement, content)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功在handle_inspection_cell_changed方法末尾添加调用check_inspection_completed")
# 4. 修改_process_stable_weight方法
def update_process_stable_weight():
with open(MAIN_WINDOW_FILE, "r") as f:
content = f.read()
# 修改查找行的逻辑
pattern1 = r'# 查找第一个没有称重数据的行\s*data_row = None\s*for row in range.*?if data_row is None:.*?else:\s*logging\.info\(f"找到没有称重数据的行: \{data_row\}"\)'
replacement1 = r"""# 基于状态查找行优先查找状态为inspected的行
data_row = None
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 首先查找状态为inspected的行
for row in range(2, self.process_table.rowCount()):
gc_note_item = self.process_table.item(row, 1)
if gc_note_item:
row_gc_note = gc_note_item.text().strip()
tray_id = self.tray_edit.text()
status = inspection_dao.get_product_status(self._current_order_code, row_gc_note, tray_id)
if status == 'inspected':
data_row = row
logging.info(f"找到状态为inspected的行: {data_row}, 工程号: {row_gc_note}")
break
# 如果没有找到inspected状态的行回退到原有逻辑
if data_row is None:
# 查找第一个没有称重数据的行
for row in range(2, self.process_table.rowCount()):
weight_item = self.process_table.item(row, weight_col)
if not weight_item or not weight_item.text().strip():
data_row = row
break
# 如果仍然没有找到,使用当前选中行或第一个数据行
if data_row is None:
current_row = self.process_table.currentRow()
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行索引为2
logging.info(f"未找到状态为inspected的行或没有称重数据的行使用当前选中行或第一个数据行: {data_row}")
else:
logging.info(f"找到没有称重数据的行: {data_row}")
else:
logging.info(f"将使用状态为inspected的行: {data_row}")"""
content = re.sub(pattern1, replacement1, content, flags=re.DOTALL)
# 添加状态更新代码
pattern2 = r'(logging\.info\(f"已将稳定的称重数据 \{weight_kg\}kg 写入行 \{data_row\}, 列 \{weight_col\}"\))\n\s*except'
replacement2 = r"""\1
# 更新产品状态为weighed
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'weighed')
logging.info(f"工程号 {gc_note} 的称重已完成状态更新为weighed")
except"""
content = re.sub(pattern2, replacement2, content)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功更新_process_stable_weight方法")
# 5. 修改handle_label_signal方法
def update_handle_label_signal():
with open(MAIN_WINDOW_FILE, "r") as f:
content = f.read()
# 修改查找行的逻辑
pattern1 = r'# 获取当前选中的行或第一个数据行\s*current_row = self\.process_table\.currentRow\(\)\s*data_row = current_row if current_row >= 2 else 2'
replacement1 = r"""# 基于状态查找行优先查找状态为weighed的行
data_row = None
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 首先查找状态为weighed的行
for row in range(2, self.process_table.rowCount()):
gc_note_item = self.process_table.item(row, 1)
if gc_note_item:
row_gc_note = gc_note_item.text().strip()
tray_id = self.tray_edit.text()
status = inspection_dao.get_product_status(self._current_order_code, row_gc_note, tray_id)
if status == 'weighed':
data_row = row
logging.info(f"找到状态为weighed的行: {data_row}, 工程号: {row_gc_note}")
break
# 如果没有找到weighed状态的行回退到原有逻辑
if data_row is None:
# 使用当前选中的行或第一个数据行
current_row = self.process_table.currentRow()
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行索引为2
logging.info(f"未找到状态为weighed的行使用当前选中行或第一个数据行: {data_row}")
else:
logging.info(f"将使用状态为weighed的行: {data_row}")"""
content = re.sub(pattern1, replacement1, content)
# 添加状态更新代码
pattern2 = r'(logging\.info\(f"已将贴标数据 \{axios_num\} 保存到数据库"\))\n\s*# 调用加载到包装记录的方法'
replacement2 = r"""\1
# 更新产品状态为labeled
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'labeled')
logging.info(f"工程号 {gc_note} 的贴标已完成状态更新为labeled")
# 调用加载到包装记录的方法"""
content = re.sub(pattern2, replacement2, content)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功更新handle_label_signal方法")
# 6. 修改save_inspection_data方法
def update_save_inspection_data():
with open(MAIN_WINDOW_FILE, "r") as f:
content = f.read()
# 查找save_inspection_data方法
pattern = r'def save_inspection_data.*?try:.*?inspection_dao = InspectionDAO\(\).*?# 记录保存前的详细日志'
# 修改方法,添加状态获取逻辑
replacement = r"""def save_inspection_data(self, order_id, gc_note, tray_id, position, config_id, value, status):
"""保存检验数据到数据库
Args:
order_id: 订单号
gc_note: 工程号
position: 位置序号
config_id: 配置ID
value: 检验值
status: 状态
"""
try:
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
modbus = ModbusUtils()
client = modbus.get_client()
# 获取当前产品状态,优先使用产品状态管理中的状态
current_status = inspection_dao.get_product_status(order_id, gc_note, tray_id)
# 如果当前状态不是初始状态则使用当前状态而不是传入的status
if current_status not in ['', 'init']:
status = current_status
# 记录保存前的详细日志"""
content = re.sub(pattern, replacement, content, flags=re.DOTALL)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功更新save_inspection_data方法")
# 7. 移除数据校验逻辑
def remove_validation_logic():
with open(MAIN_WINDOW_FILE, "r") as f:
content = f.read()
# 第一处移除handle_inspection_cell_changed中的数据校验逻辑
pattern1 = r'# 验证数据有效性\s*if self\.validate_inspection_value\(config, value\):.*?status = \'warning\''
replacement1 = r"""# 设置单元格颜色为浅绿色,表示已填写
cell_item.setBackground(QBrush(QColor("#c8e6c9")))"""
content = re.sub(pattern1, replacement1, content, flags=re.DOTALL)
with open(MAIN_WINDOW_FILE, "w") as f:
f.write(content)
print(f"已成功移除数据校验逻辑")
# 执行所有修改
def apply_all_changes():
print("开始应用状态管理功能补丁...")
add_dao_methods()
update_add_new_inspection_row()
add_check_inspection_completed()
update_process_stable_weight()
update_handle_label_signal()
update_save_inspection_data()
remove_validation_logic()
print("状态管理功能补丁应用完成!")
if __name__ == "__main__":
apply_all_changes()

7
temp_fix.py Normal file
View File

@ -0,0 +1,7 @@
# 设置单元格颜色为浅绿色,表示已填写
cell_item.setBackground(QBrush(QColor("#c8e6c9")))
# 保持当前状态不变,由状态管理逻辑处理
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
status = inspection_dao.get_product_status(self._current_order_code, gc_note, tray_id)

View File

@ -0,0 +1,39 @@
def save_inspection_data(self, order_id, gc_note, tray_id, position, config_id, value, status):
"""保存检验数据到数据库
Args:
order_id: 订单号
gc_note: 工程号
position: 位置序号
config_id: 配置ID
value: 检验值
status: 状态
"""
try:
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
modbus = ModbusUtils()
client = modbus.get_client()
# 获取当前产品状态,优先使用产品状态管理中的状态
current_status = inspection_dao.get_product_status(order_id, gc_note, tray_id)
# 如果当前状态不是初始状态则使用当前状态而不是传入的status
if current_status not in ['', 'init']:
status = current_status
# 记录保存前的详细日志
logging.info(f"正在保存检验数据: 工程号={gc_note}, 托盘号={tray_id}, 位置={position}, 配置ID={config_id}, 值={value}, 状态={status}")
# 构建数据
data = [{
'position': position,
'config_id': config_id,
'value': value,
'status': status,
'remark': '',
'tray_id': tray_id
}]
# 保存到数据库
inspection_dao.save_inspection_data(order_id, gc_note, data)

1
test_register_monitor.py Normal file
View File

@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
import sys
import os
import logging
from pathlib import Path
# 添加项目根目录到系统路径,以便导入模块
project_root = str(Path(__file__).parent.parent)
sys.path.insert(0, project_root)
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s:%(lineno)d] - %(message)s')
# 导入需要测试的模块
from apis.gc_api import GcApi
def test_gc_api():
"""测试 GcApi 的 get_gc_info 方法是否能正确处理 form-data 格式的请求"""
print("开始测试 GcApi.get_gc_info 方法...")
# 创建 GcApi 实例
gc_api = GcApi()
# 测试工程号
test_gc_code = "JTPD25060003"
# 调用方法
print(f"使用工程号 {test_gc_code} 调用 get_gc_info...")
response = gc_api.get_gc_info(test_gc_code)
# 打印结果
print(f"API 响应: {response}")
if response:
print("测试成功: API 返回了有效响应")
else:
print("测试失败: API 返回了空响应")
# 检查响应格式
if isinstance(response, dict) and "status" in response:
print(f"响应状态: {response.get('status', False)}")
print(f"响应消息: {response.get('message', '')}")
print(f"响应数据: {response.get('data', None)}")
else:
print(f"响应格式不符合预期: {response}")
if __name__ == "__main__":
test_gc_api()

View File

@ -92,13 +92,44 @@ class LoadingDialogUI(QDialog):
self.order_label.setFixedWidth(100)
self.order_label.setFixedHeight(45)
# 创建一个水平布局,包含输入框和查询按钮
order_input_layout = QHBoxLayout()
order_input_layout.setSpacing(0)
order_input_layout.setContentsMargins(0, 0, 0, 0)
self.order_input = QLineEdit()
self.order_input.setFont(self.normal_font)
self.order_input.setPlaceholderText("请扫描订单号")
self.order_input.setStyleSheet(input_style)
self.order_input.setFixedHeight(45)
# 添加查询按钮
self.order_query_btn = QPushButton("...")
self.order_query_btn.setFont(self.normal_font)
self.order_query_btn.setFixedSize(45, 45)
self.order_query_btn.setStyleSheet("""
QPushButton {
border: none;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
background-color: #f5f5f5;
color: #333333;
font-weight: bold;
}
QPushButton:hover {
background-color: #e0e0e0;
}
QPushButton:pressed {
background-color: #d0d0d0;
}
""")
# 添加到水平布局
order_input_layout.addWidget(self.order_input)
order_input_layout.addWidget(self.order_query_btn)
row1.addWidget(self.order_label)
row1.addWidget(self.order_input, 1)
row1.addLayout(order_input_layout, 1)
container_layout.addLayout(row1)
# 添加水平分隔布局

347
ui/luno_query_dialog_ui.py Normal file
View File

@ -0,0 +1,347 @@
from PySide6.QtWidgets import (
QDialog, QLabel, QLineEdit, QComboBox, QPushButton,
QVBoxLayout, QHBoxLayout, QFrame, QTableWidget, QTableWidgetItem,
QHeaderView, QDateEdit, QApplication
)
from PySide6.QtCore import Qt, QDate
from PySide6.QtGui import QFont
class LunoQueryDialogUI(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("炉号查询")
self.resize(1200, 600) # 调整宽度和高度
# 设置字体
self.normal_font = QFont("微软雅黑", 10)
self.title_font = QFont("微软雅黑", 10, QFont.Bold)
# 初始化UI
self.init_ui()
def init_ui(self):
"""初始化UI"""
# 主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(10)
# 创建查询条件区域
self.create_query_frame()
# 创建结果表格
self.create_result_table()
# 创建按钮
self.create_buttons()
def create_query_frame(self):
"""创建查询条件区域"""
# 创建一个带边框的容器
query_frame = QFrame()
query_frame.setStyleSheet("""
QFrame {
border: 1px solid #e0e0e0;
background-color: white;
border-radius: 4px;
}
""")
# 容器的垂直布局
query_layout = QVBoxLayout(query_frame)
query_layout.setContentsMargins(15, 15, 15, 15)
query_layout.setSpacing(10)
# 第一行:日期范围和炉号
row1 = QHBoxLayout()
# 日期标签
date_label = QLabel("日期:")
date_label.setFont(self.normal_font)
date_label.setFixedWidth(40)
# 开始日期
self.start_date = QDateEdit()
self.start_date.setFont(self.normal_font)
self.start_date.setCalendarPopup(True)
self.start_date.setDate(QDate.currentDate().addDays(-7))
self.start_date.setFixedWidth(120)
self.start_date.setStyleSheet("""
QDateEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
""")
# 至标签
to_label = QLabel("至:")
to_label.setFont(self.normal_font)
to_label.setFixedWidth(20)
# 结束日期
self.end_date = QDateEdit()
self.end_date.setFont(self.normal_font)
self.end_date.setCalendarPopup(True)
self.end_date.setDate(QDate.currentDate())
self.end_date.setFixedWidth(120)
self.end_date.setStyleSheet("""
QDateEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
""")
# 炉号标签
luno_label = QLabel("炉号:")
luno_label.setFont(self.normal_font)
luno_label.setFixedWidth(50)
# 炉号输入框
self.luno_input = QLineEdit()
self.luno_input.setFont(self.normal_font)
self.luno_input.setStyleSheet("""
QLineEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QLineEdit:focus {
border: 1px solid #66afe9;
}
""")
# 材质标签
material_label = QLabel("材质")
material_label.setFont(self.normal_font)
material_label.setFixedWidth(40)
# 材质下拉框
self.material_combo = QComboBox()
self.material_combo.setFont(self.normal_font)
self.material_combo.addItem("全部")
self.material_combo.setStyleSheet("""
QComboBox {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 20px;
border-left: 1px solid #e0e0e0;
}
""")
# 规格标签
spec_label = QLabel("规格")
spec_label.setFont(self.normal_font)
spec_label.setFixedWidth(40)
# 规格输入框
self.spec_input = QLineEdit()
self.spec_input.setFont(self.normal_font)
self.spec_input.setStyleSheet("""
QLineEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QLineEdit:focus {
border: 1px solid #66afe9;
}
""")
# 钢厂标签
steel_label = QLabel("钢厂")
steel_label.setFont(self.normal_font)
steel_label.setFixedWidth(40)
# 钢厂输入框
self.steel_input = QLineEdit()
self.steel_input.setFont(self.normal_font)
self.steel_input.setStyleSheet("""
QLineEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QLineEdit:focus {
border: 1px solid #66afe9;
}
""")
# 查询按钮
self.query_button = QPushButton("查询")
self.query_button.setFont(self.normal_font)
self.query_button.setStyleSheet("""
QPushButton {
background-color: #0078d4;
color: white;
border: none;
border-radius: 4px;
padding: 6px 16px;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
""")
self.query_button.setFixedWidth(80)
# 添加组件到第一行布局
row1.addWidget(date_label)
row1.addWidget(self.start_date)
row1.addWidget(to_label)
row1.addWidget(self.end_date)
row1.addSpacing(20)
row1.addWidget(luno_label)
row1.addWidget(self.luno_input, 1)
row1.addSpacing(20)
row1.addWidget(material_label)
row1.addWidget(self.material_combo, 1)
row1.addSpacing(20)
row1.addWidget(spec_label)
row1.addWidget(self.spec_input, 1)
row1.addSpacing(20)
row1.addWidget(steel_label)
row1.addWidget(self.steel_input, 1)
row1.addSpacing(20)
row1.addWidget(self.query_button)
# 添加第一行到查询布局
query_layout.addLayout(row1)
# 将查询框架添加到主布局
self.main_layout.addWidget(query_frame)
def create_result_table(self):
"""创建结果表格"""
# 创建表格
self.result_table = QTableWidget()
self.result_table.setFont(self.normal_font)
# 设置表头样式
header = self.result_table.horizontalHeader()
header.setStyleSheet("""
QHeaderView::section {
background-color: #f5f5f5;
color: #333333;
padding: 5px;
border: 1px solid #e0e0e0;
font-weight: bold;
}
""")
# 设置表格样式
self.result_table.setStyleSheet("""
QTableWidget {
border: 1px solid #e0e0e0;
gridline-color: #e0e0e0;
selection-background-color: #0078d4;
selection-color: white;
}
QTableWidget::item {
padding: 5px;
border-bottom: 1px solid #e0e0e0;
}
""")
# 启用水平滚动条
self.result_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# 设置表头固定宽度模式,确保所有列都能显示
header.setSectionResizeMode(QHeaderView.Fixed)
# 设置表格最小宽度,确保有足够空间显示滚动条
self.result_table.setMinimumWidth(800)
# 设置默认列宽
self.result_table.setColumnWidth(0, 60) # 序号
self.result_table.setColumnWidth(1, 100) # 日期
self.result_table.setColumnWidth(2, 120) # 炉号
self.result_table.setColumnWidth(3, 100) # 材质
self.result_table.setColumnWidth(4, 100) # 规格
self.result_table.setColumnWidth(5, 100) # 钢厂
self.result_table.setColumnWidth(6, 80) # 标准
self.result_table.setColumnWidth(7, 100) # 公司账套
self.result_table.setColumnWidth(8, 60) # C
self.result_table.setColumnWidth(9, 60) # Si
self.result_table.setColumnWidth(10, 60) # Mn
self.result_table.setColumnWidth(11, 60) # P
self.result_table.setColumnWidth(12, 60) # S
self.result_table.setColumnWidth(13, 60) # Ni
self.result_table.setColumnWidth(14, 60) # Cr
self.result_table.setColumnWidth(15, 60) # Ti
self.result_table.setColumnWidth(16, 60) # Mo
self.result_table.setColumnWidth(17, 60) # Cu
self.result_table.setColumnWidth(18, 80) # 其他
self.result_table.setColumnWidth(19, 100) # 抗拉强度
self.result_table.setColumnWidth(20, 80) # 延伸率
# 设置表格可以选择整行
self.result_table.setSelectionBehavior(QTableWidget.SelectRows)
# 设置表格只能单选
self.result_table.setSelectionMode(QTableWidget.SingleSelection)
# 添加表格到主布局
self.main_layout.addWidget(self.result_table, 1) # 1表示拉伸因子让表格占据更多空间
def create_buttons(self):
"""创建底部按钮"""
button_layout = QHBoxLayout()
# 确认和取消按钮
self.confirm_button = QPushButton("确认")
self.confirm_button.setFont(self.normal_font)
self.confirm_button.setStyleSheet("""
QPushButton {
background-color: #0078d4;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-weight: bold;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
""")
self.confirm_button.setFixedSize(100, 35)
self.cancel_button = QPushButton("取消")
self.cancel_button.setFont(self.normal_font)
self.cancel_button.setStyleSheet("""
QPushButton {
background-color: white;
color: #333333;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 8px 16px;
font-weight: bold;
}
QPushButton:hover {
background-color: #f5f5f5;
}
QPushButton:pressed {
background-color: #e0e0e0;
}
""")
self.cancel_button.setFixedSize(100, 35)
# 添加按钮到布局
button_layout.addStretch()
button_layout.addWidget(self.confirm_button)
button_layout.addSpacing(30) # 添加30px的间距
button_layout.addWidget(self.cancel_button)
# 添加按钮布局到主布局
self.main_layout.addLayout(button_layout)

View File

@ -1,7 +1,7 @@
from PySide6.QtWidgets import (
QMainWindow, QWidget, QLabel, QGridLayout, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QHeaderView, QFrame, QSplitter,
QPushButton, QLineEdit, QAbstractItemView, QComboBox, QSizePolicy
QPushButton, QLineEdit, QAbstractItemView, QComboBox, QSizePolicy, QTextEdit, QCheckBox
)
from PySide6.QtGui import QFont, QAction, QBrush, QColor
from PySide6.QtCore import Qt, QDateTime, QTimer
@ -9,7 +9,10 @@ from PySide6.QtCore import Qt, QDateTime, QTimer
class MainWindowUI(QMainWindow):
# 定义字段映射为类属性,方便外部引用
FIELD_MAPPING = {
"库房": "lib",
"机台": "jt",
"客户": "customerexp",
"订单数量": "sl",
"规格": "size",
"材质": "cz",
"种类": "type_name",
@ -17,14 +20,15 @@ class MainWindowUI(QMainWindow):
"炉号": "luno",
"轴型": "zx_name",
"标签": "template_name",
"打印材质": "cz",
"打印材质": "dycz",
"底托类型": "spack_type",
"强度范围": "qx",
"强度": "qd",
"延伸要求": "ysl",
"线材类型": "jz",
"线材类型": "xclx",
"包装方式": "bzfs",
"轴重要求": "zzyq",
"线径公差": "xj",
"线径公差": "xjgc",
"备注": "remarks_hb"
}
@ -183,6 +187,35 @@ class MainWindowUI(QMainWindow):
self.order_layout.addWidget(self.order_label)
self.order_layout.addWidget(self.order_edit)
# 添加虚拟工程号按钮
self.virtual_order_button = QPushButton("虚拟工程号")
self.virtual_order_button.setFixedHeight(30)
self.virtual_order_button.setFixedWidth(100)
self.virtual_order_button.setFont(QFont("微软雅黑", 10))
self.virtual_order_button.setStyleSheet("""
QPushButton {
background-color: #e3f2fd;
border: 1px solid #2196f3;
border-radius: 3px;
padding: 2px 5px;
color: #1976d2;
}
QPushButton:hover {
background-color: #bbdefb;
}
QPushButton:pressed {
background-color: #90caf9;
}
""")
self.order_layout.addWidget(self.virtual_order_button)
# 添加圆形标签checkbox
self.round_label_checkbox = QCheckBox("圆形标签")
self.round_label_checkbox.setFont(QFont("微软雅黑", 10))
self.round_label_checkbox.setFixedHeight(30)
self.order_layout.addWidget(self.round_label_checkbox)
self.order_layout.addStretch() # 添加弹性空间,将组件推到左侧
self.task_layout.addLayout(self.order_layout)
@ -196,22 +229,22 @@ class MainWindowUI(QMainWindow):
# 设置列宽均等
self.task_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# 第一行:订单量和完成量 (一级标题)
self.task_table.setSpan(0, 0, 1, 2) # 订单量跨2列
self.task_table.setSpan(0, 2, 1, 2) # 完成跨2列
# 第一行:总完成和已完成 (一级标题)
self.task_table.setSpan(0, 0, 1, 2) # 总完成跨2列
self.task_table.setSpan(0, 2, 1, 2) # 完成跨2列
order_item = QTableWidgetItem("订单")
order_item = QTableWidgetItem("订单完成")
order_item.setTextAlignment(Qt.AlignCenter)
order_item.setFont(self.normal_font)
self.task_table.setItem(0, 0, order_item)
completed_item = QTableWidgetItem("完成")
completed_item = QTableWidgetItem("托盘完成")
completed_item.setTextAlignment(Qt.AlignCenter)
completed_item.setFont(self.normal_font)
self.task_table.setItem(0, 2, completed_item)
# 第二行:二级标题
headers = ["总生产数量", "总生产公斤", "已完成数量", "已完成公斤"]
headers = ["订单完成轴数", "订单完成数量", "托盘完成轴数", "托盘完成数量"]
for col, header in enumerate(headers):
item = QTableWidgetItem(header)
item.setTextAlignment(Qt.AlignCenter)
@ -251,6 +284,8 @@ class MainWindowUI(QMainWindow):
self.material_content_layout = QVBoxLayout(self.material_content)
self.material_content_layout.setContentsMargins(10, 10, 10, 10)
# 移除滚动区域,直接使用表格
# 创建订单号输入框
self.order_info_layout = QHBoxLayout()
self.order_no_label = QLabel("订单号:")
@ -285,7 +320,9 @@ class MainWindowUI(QMainWindow):
# 创建信息表格 - 使用QTableWidget实现
self.info_table = QTableWidget()
self.info_table.setRowCount(9) # 8行常规字段 + 1行备注
# 计算需要的行数21个字段每行2个字段最后两行是备注跨列
# 前20个字段需要10行备注占用2行总共12行
self.info_table.setRowCount(12) # 10行常规字段 + 2行备注
self.info_table.setColumnCount(4) # 4列标签1, 值1, 标签2, 值2
self.info_table.setShowGrid(True) # 显示网格线
self.info_table.horizontalHeader().setVisible(False) # 隐藏水平表头
@ -320,20 +357,28 @@ class MainWindowUI(QMainWindow):
label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
label.setStyleSheet("background-color: #FAFAFA; padding: 5px;")
# 创建值
value = QLabel("")
# 创建值改为QTextEdit
value = QTextEdit("")
value.setFont(self.normal_font)
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
value.setStyleSheet("background-color: white; padding: 5px;")
value.setStyleSheet("background-color: white; padding: 5px; border: 1px solid #cccccc;")
value.setFixedHeight(70) # 增加高度为两行
value.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
value.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
value.setFrameStyle(QFrame.NoFrame)
value.setLineWrapMode(QTextEdit.WidgetWidth) # 允许自动换行
value.setContentsMargins(0, 0, 0, 0)
value.setAcceptRichText(False)
value.setTabChangesFocus(True)
# 保存引用
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列
self.info_table.setSpan(row, 0, 2, 1) # 标签跨越2行1列
self.info_table.setSpan(row, 1, 2, 3) # 值跨越2行3列
else:
# 创建标签
label = QLabel(field_name)
@ -341,11 +386,113 @@ class MainWindowUI(QMainWindow):
label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
label.setStyleSheet("background-color: #FAFAFA; padding: 5px;")
# 创建值
value = QLabel("")
# 根据字段类型创建不同的值组件
if field_name == "库房":
# 库房字段使用QComboBox完全复刻托盘号组件的配置
value = QComboBox()
value.setFixedHeight(35)
value.setStyleSheet("QComboBox { border: 1px solid #cccccc; padding: 3px; background-color: white; } QComboBox::drop-down { border: none; width: 20px; }")
value.setFont(QFont("微软雅黑", 12))
value.setEditable(False) # 设置为不可编辑,确保是纯下拉选择
value.setInsertPolicy(QComboBox.NoInsert) # 不自动插入用户输入到列表中
value.setMaxVisibleItems(10) # 设置下拉框最多显示10个项目
# 添加默认选项
value.addItem("成品库")
value.addItem("退回仓")
value.addItem("散装库")
value.addItem("不合格库(线材)")
value.addItem("废丝库")
value.setCurrentIndex(0) # 默认选择第一个
elif field_name == "机台":
# 机台字段使用QLineEdit与其他字段保持一致
value = QLineEdit("")
value.setFont(self.normal_font)
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
value.setStyleSheet("background-color: white; padding: 5px;")
value.setStyleSheet("background-color: white; padding: 5px; border: 1px solid #cccccc;")
value.setFrame(False)
value.setContentsMargins(0, 0, 0, 0)
value.setFixedHeight(35)
elif field_name == "订单数量":
# 订单数量字段使用QLineEdit与其他字段保持一致
value = QLineEdit("")
value.setFont(self.normal_font)
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
value.setStyleSheet("background-color: white; padding: 5px; border: 1px solid #cccccc;")
value.setFrame(False)
value.setContentsMargins(0, 0, 0, 0)
value.setFixedHeight(35)
elif field_name == "线材类型":
# 线材类型字段使用QComboBox与库房组件配置相同
value = QComboBox()
value.setFixedHeight(35)
value.setStyleSheet("QComboBox { border: 1px solid #cccccc; padding: 3px; background-color: white; } QComboBox::drop-down { border: none; width: 20px; }")
value.setFont(QFont("微软雅黑", 12))
value.setEditable(False) # 设置为不可编辑,确保是纯下拉选择
value.setInsertPolicy(QComboBox.NoInsert) # 不自动插入用户输入到列表中
value.setMaxVisibleItems(10) # 设置下拉框最多显示10个项目
# 添加默认选项后续会从API动态加载
value.addItem("请选择")
value.setCurrentIndex(0) # 默认选择第一个
elif field_name == "炉号":
# 炉号字段使用QLineEdit + 查询按钮,复刻订单号的逻辑
# 创建水平布局容器
value_container = QWidget()
value_layout = QHBoxLayout(value_container)
value_layout.setContentsMargins(0, 0, 0, 0)
value_layout.setSpacing(0)
# 创建输入框
value = QLineEdit("")
value.setFont(self.normal_font)
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
value.setStyleSheet("background-color: white; padding: 5px; border: 1px solid #cccccc; border-right: none;")
value.setFixedHeight(35)
# 创建查询按钮
query_button = QPushButton("...")
query_button.setFont(self.normal_font)
query_button.setFixedSize(35, 35)
query_button.setStyleSheet("""
QPushButton {
border: 1px solid #cccccc;
border-left: none;
background-color: #f5f5f5;
color: #333333;
font-weight: bold;
}
QPushButton:hover {
background-color: #e0e0e0;
}
QPushButton:pressed {
background-color: #d0d0d0;
}
""")
# 添加到布局
value_layout.addWidget(value)
value_layout.addWidget(query_button)
# 保存按钮引用,以便后续连接信号
self.luno_query_button = query_button
# 将容器作为值组件
value = value_container
elif field_name == "包装方式":
# 包装方式字段使用QLineEdit与其他字段保持一致
value = QLineEdit("")
value.setFont(self.normal_font)
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
value.setStyleSheet("background-color: white; padding: 5px; border: 1px solid #cccccc;")
else:
# 其他字段使用QLineEdit
value = QLineEdit("")
value.setFont(self.normal_font)
value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
value.setStyleSheet("background-color: white; padding: 5px; border: 1px solid #cccccc;")
value.setFrame(False)
value.setContentsMargins(0, 0, 0, 0)
value.setFixedHeight(35)
# 保存引用
self.info_labels[field_name] = label
@ -363,19 +510,21 @@ class MainWindowUI(QMainWindow):
# 设置行高
for i in range(self.info_table.rowCount()):
self.info_table.setRowHeight(i, 35) # 普通行高度调高
if i == 10: # 备注行第11行索引为10
self.info_table.setRowHeight(i, 35) # 备注行第一行高度
self.info_table.setRowHeight(i + 1, 35) # 备注行第二行高度
break
else:
self.info_table.setRowHeight(i, 35) # 普通行高度
# 设置表格自适应
self.info_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # 列宽自适应
# 设置表格自适应宽度
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)
@ -400,46 +549,81 @@ class MainWindowUI(QMainWindow):
self.tray_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
self.tray_layout.addWidget(self.tray_label)
self.tray_edit = QComboBox()
# 将 QComboBox 改为 QLineEdit
self.tray_edit = QLineEdit()
self.tray_edit.setFixedHeight(40) # 设置固定高度与父容器相同
self.tray_edit.setStyleSheet("QComboBox { border: none; padding: 0px 10px; background-color: white; } QComboBox::drop-down { border: none; width: 20px; }")
self.tray_edit.setStyleSheet("QLineEdit { border: none; padding: 0px 10px; background-color: white; }")
self.tray_edit.setFont(QFont("微软雅黑", 12))
self.tray_edit.setEditable(True) # 允许手动输入
self.tray_edit.setInsertPolicy(QComboBox.NoInsert) # 不自动插入用户输入到列表中
self.tray_edit.setMaxVisibleItems(10) # 设置下拉框最多显示10个项目
self.tray_edit.completer().setCaseSensitivity(Qt.CaseInsensitive) # 设置补全不区分大小写
self.tray_edit.completer().setFilterMode(Qt.MatchContains) # 设置模糊匹配模式
# 允许清空选择
self.tray_edit.setCurrentText("")
self.tray_edit.setReadOnly(True) # 设置为只读
self.tray_edit.setFocusPolicy(Qt.NoFocus) # 禁止获取焦点
self.tray_edit.setText("") # 设置初始值为空
self.tray_layout.addWidget(self.tray_edit)
self.left_layout.addWidget(self.tray_frame)
# 下料区 - 使用QFrame包裹添加边框
# 上下料区 - 使用QFrame包裹添加边框
self.output_frame = QFrame()
self.output_frame.setFrameShape(QFrame.StyledPanel)
self.output_frame.setLineWidth(1)
self.output_frame.setFixedHeight(100) # 压缩下料区域的高度从原来的150减少到100
self.output_frame.setFixedHeight(100) # 保持高度不变
self.output_frame.setStyleSheet("QFrame { background-color: #f8f8f8; }")
self.output_layout = QHBoxLayout(self.output_frame)
self.output_layout.setContentsMargins(0, 0, 0, 0)
self.output_layout.setSpacing(0)
# 下料区标签
self.output_label = QLabel("下料")
self.output_label.setFont(self.normal_font)
self.output_label.setAlignment(Qt.AlignCenter)
self.output_label.setFixedWidth(100) # 设置固定宽度
self.output_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
self.output_layout.addWidget(self.output_label)
# 上料区(拆垛层数)
self.input_area = QWidget()
self.input_area.setFixedWidth(200) # 设置固定宽度
self.input_area.setStyleSheet("background-color: white;")
self.input_area_layout = QVBoxLayout(self.input_area)
self.input_area_layout.setContentsMargins(5, 5, 5, 5)
self.input_area_layout.setSpacing(5)
# 下料区内容 - 这里可以添加更多控件
self.output_content = QWidget()
self.output_content.setStyleSheet("background-color: white;")
self.output_content_layout = QVBoxLayout(self.output_content)
self.output_content_layout.setContentsMargins(5, 5, 5, 5) # 减小内部边距
self.output_layout.addWidget(self.output_content)
# 上料区标签
self.input_area_label = QLabel("上料区")
self.input_area_label.setFont(self.normal_font)
self.input_area_label.setAlignment(Qt.AlignCenter)
self.input_area_label.setStyleSheet("font-weight: bold; color: #333333;")
self.input_area_layout.addWidget(self.input_area_label)
# 拆垛层数显示
self.stow_level_label = QLabel("拆垛层数: 1")
self.stow_level_label.setFont(self.normal_font)
self.stow_level_label.setAlignment(Qt.AlignCenter)
self.stow_level_label.setStyleSheet("color: #333333; background-color: #f5f5f5; padding: 5px; border: 1px solid #cccccc; border-radius: 3px;")
self.input_area_layout.addWidget(self.stow_level_label)
self.output_layout.addWidget(self.input_area)
# 下料区(下料层数、下料位置)
self.output_area = QWidget()
self.output_area.setStyleSheet("background-color: white;")
self.output_area_layout = QVBoxLayout(self.output_area)
self.output_area_layout.setContentsMargins(5, 5, 5, 5)
self.output_area_layout.setSpacing(5)
# 下料区标签
self.output_area_label = QLabel("下料区")
self.output_area_label.setFont(self.normal_font)
self.output_area_label.setAlignment(Qt.AlignCenter)
self.output_area_label.setStyleSheet("font-weight: bold; color: #333333;")
self.output_area_layout.addWidget(self.output_area_label)
# 下料层数显示
self.unload_level_label = QLabel("下料层数: 0/0")
self.unload_level_label.setFont(self.normal_font)
self.unload_level_label.setAlignment(Qt.AlignCenter)
self.unload_level_label.setStyleSheet("color: #333333; background-color: #f5f5f5; padding: 5px; border: 1px solid #cccccc; border-radius: 3px;")
self.output_area_layout.addWidget(self.unload_level_label)
# 下料位置显示
self.unload_position_label = QLabel("下料位置: 0")
self.unload_position_label.setFont(self.normal_font)
self.unload_position_label.setAlignment(Qt.AlignCenter)
self.unload_position_label.setStyleSheet("color: #333333; background-color: #f5f5f5; padding: 5px; border: 1px solid #cccccc; border-radius: 3px;")
self.output_area_layout.addWidget(self.unload_position_label)
self.output_layout.addWidget(self.output_area)
self.left_layout.addWidget(self.output_frame)
@ -650,16 +834,20 @@ class MainWindowUI(QMainWindow):
self.process_table = QTableWidget(8, total_columns) # 8行1行标题区域 + 1行列标题 + 6行数据
# 为微丝产线表格设置更大的字体
process_font = QFont("微软雅黑", 16) # 比normal_font(12)大2个字号
self.process_table.setFont(process_font)
# 应用通用表格设置
self.setup_table_common(self.process_table)
# 设置行高
self.process_table.setRowHeight(0, 30) # 标题区域行高
self.process_table.setRowHeight(1, 30) # 列标题行高
# 设置行高 - 适当增加以适应新字号
self.process_table.setRowHeight(0, 40) # 标题区域行高从30增加到35
self.process_table.setRowHeight(1, 40) # 列标题行高从30增加到35
# 设置数据行的行高
for row in range(2, 8): # 工序行
self.process_table.setRowHeight(row, 35)
self.process_table.setRowHeight(row, 45) # 从35增加到40
# 设置列宽
self.set_process_table_column_widths()

375
ui/order_query_dialog_ui.py Normal file
View File

@ -0,0 +1,375 @@
from PySide6.QtWidgets import (
QDialog, QLabel, QLineEdit, QComboBox, QPushButton,
QVBoxLayout, QHBoxLayout, QFrame, QTableWidget, QTableWidgetItem,
QHeaderView, QDateEdit, QApplication
)
from PySide6.QtCore import Qt, QDate
from PySide6.QtGui import QFont
class OrderQueryDialogUI(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("订单查询")
self.resize(1400, 700) # 增加宽度和高度
# 设置字体
self.normal_font = QFont("微软雅黑", 10)
self.title_font = QFont("微软雅黑", 10, QFont.Bold)
# 初始化UI
self.init_ui()
def init_ui(self):
"""初始化UI"""
# 主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(10)
# 创建查询条件区域
self.create_query_frame()
# 创建结果表格
self.create_result_table()
# 创建按钮
self.create_buttons()
def create_query_frame(self):
"""创建查询条件区域"""
# 创建一个带边框的容器
query_frame = QFrame()
query_frame.setStyleSheet("""
QFrame {
border: 1px solid #e0e0e0;
background-color: white;
border-radius: 4px;
}
""")
# 容器的垂直布局
query_layout = QVBoxLayout(query_frame)
query_layout.setContentsMargins(15, 15, 15, 15)
query_layout.setSpacing(10)
# 第一行日期范围和订单Mo
row1 = QHBoxLayout()
# 日期标签
date_label = QLabel("日期:")
date_label.setFont(self.normal_font)
date_label.setFixedWidth(40)
# 开始日期
self.start_date = QDateEdit()
self.start_date.setFont(self.normal_font)
self.start_date.setCalendarPopup(True)
self.start_date.setDate(QDate.currentDate().addDays(-7))
self.start_date.setFixedWidth(120)
self.start_date.setStyleSheet("""
QDateEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
""")
# 至标签
to_label = QLabel("至:")
to_label.setFont(self.normal_font)
to_label.setFixedWidth(20)
# 结束日期
self.end_date = QDateEdit()
self.end_date.setFont(self.normal_font)
self.end_date.setCalendarPopup(True)
self.end_date.setDate(QDate.currentDate())
self.end_date.setFixedWidth(120)
self.end_date.setStyleSheet("""
QDateEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
""")
# 订单Mo标签
order_mo_label = QLabel("订单Mo:")
order_mo_label.setFont(self.normal_font)
order_mo_label.setFixedWidth(60)
# 订单Mo输入框
self.order_mo_input = QLineEdit()
self.order_mo_input.setFont(self.normal_font)
self.order_mo_input.setStyleSheet("""
QLineEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QLineEdit:focus {
border: 1px solid #66afe9;
}
""")
# 材质标签
material_label = QLabel("材质")
material_label.setFont(self.normal_font)
material_label.setFixedWidth(40)
# 材质下拉框
self.material_combo = QComboBox()
self.material_combo.setFont(self.normal_font)
self.material_combo.addItem("全部")
self.material_combo.setStyleSheet("""
QComboBox {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 20px;
border-left: 1px solid #e0e0e0;
}
""")
# 规格标签
spec_label = QLabel("规格")
spec_label.setFont(self.normal_font)
spec_label.setFixedWidth(40)
# 规格输入框
self.spec_input = QLineEdit()
self.spec_input.setFont(self.normal_font)
self.spec_input.setStyleSheet("""
QLineEdit {
border: 1px solid #e0e0e0;
padding: 4px;
border-radius: 4px;
}
QLineEdit:focus {
border: 1px solid #66afe9;
}
""")
# 查询按钮
self.query_button = QPushButton("查询")
self.query_button.setFont(self.normal_font)
self.query_button.setStyleSheet("""
QPushButton {
background-color: #0078d4;
color: white;
border: none;
border-radius: 4px;
padding: 6px 16px;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
""")
self.query_button.setFixedWidth(80)
# 添加组件到第一行布局
row1.addWidget(date_label)
row1.addWidget(self.start_date)
row1.addWidget(to_label)
row1.addWidget(self.end_date)
row1.addSpacing(20)
row1.addWidget(order_mo_label)
row1.addWidget(self.order_mo_input, 1)
row1.addSpacing(20)
row1.addWidget(material_label)
row1.addWidget(self.material_combo, 1)
row1.addSpacing(20)
row1.addWidget(spec_label)
row1.addWidget(self.spec_input, 1)
row1.addSpacing(20)
row1.addWidget(self.query_button)
# 添加第一行到查询布局
query_layout.addLayout(row1)
# 将查询框架添加到主布局
self.main_layout.addWidget(query_frame)
def create_result_table(self):
"""创建结果表格"""
# 创建表格
self.result_table = QTableWidget()
self.result_table.setFont(self.normal_font)
# 设置表头样式
header = self.result_table.horizontalHeader()
header.setStyleSheet("""
QHeaderView::section {
background-color: #f5f5f5;
color: #333333;
padding: 5px;
border: 1px solid #e0e0e0;
font-weight: bold;
}
""")
# 设置表格样式
self.result_table.setStyleSheet("""
QTableWidget {
border: 1px solid #e0e0e0;
gridline-color: #e0e0e0;
selection-background-color: #0078d4;
selection-color: white;
}
QTableWidget::item {
padding: 5px;
border-bottom: 1px solid #e0e0e0;
}
""")
# 启用水平滚动条
self.result_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# 设置表头固定宽度模式,确保所有列都能显示
header.setSectionResizeMode(QHeaderView.Fixed)
# 设置表格最小宽度,确保有足够空间显示滚动条
self.result_table.setMinimumWidth(800)
# 设置默认列宽 - 常用列宽一些,不常用列窄一些
self.result_table.setColumnWidth(0, 60) # 序号
self.result_table.setColumnWidth(1, 100) # 日期
self.result_table.setColumnWidth(2, 120) # 订单号
self.result_table.setColumnWidth(3, 120) # 订单明细
self.result_table.setColumnWidth(4, 120) # 客户
self.result_table.setColumnWidth(5, 120) # 客户订单号
self.result_table.setColumnWidth(6, 100) # 订单类别
self.result_table.setColumnWidth(7, 100) # 客户交期
self.result_table.setColumnWidth(8, 100) # 编码
self.result_table.setColumnWidth(9, 100) # 产品类别
self.result_table.setColumnWidth(10, 80) # 材质
self.result_table.setColumnWidth(11, 80) # 规格
self.result_table.setColumnWidth(12, 80) # 产地
self.result_table.setColumnWidth(13, 80) # 最大入库量
self.result_table.setColumnWidth(14, 100) # 托盘号
self.result_table.setColumnWidth(15, 100) # 轴型
self.result_table.setColumnWidth(16, 80) # 轴型code
self.result_table.setColumnWidth(17, 80) # 轴型重量
self.result_table.setColumnWidth(18, 100) # 标签类别
self.result_table.setColumnWidth(19, 100) # 标签类别code
self.result_table.setColumnWidth(20, 100) # 打印材质
self.result_table.setColumnWidth(21, 100) # 炉号
self.result_table.setColumnWidth(22, 80) # 公司
self.result_table.setColumnWidth(23, 80) # 数量
self.result_table.setColumnWidth(24, 80) # 上公差
self.result_table.setColumnWidth(25, 80) # 下公差
self.result_table.setColumnWidth(26, 80) # 延伸率
self.result_table.setColumnWidth(27, 80) # 屈服强度
self.result_table.setColumnWidth(28, 80) # 英制规格
self.result_table.setColumnWidth(29, 80) # 强度上限
self.result_table.setColumnWidth(30, 80) # 强度下限
self.result_table.setColumnWidth(31, 100) # 包装方式
self.result_table.setColumnWidth(32, 120) # 订单要求
self.result_table.setColumnWidth(33, 150) # 备注
self.result_table.setColumnWidth(34, 100) # 包装强度上限
self.result_table.setColumnWidth(35, 100) # 包装强度下限
self.result_table.setColumnWidth(36, 100) # 轴重要求
# 设置表格可以选择整行
self.result_table.setSelectionBehavior(QTableWidget.SelectRows)
# 设置表格只能单选
self.result_table.setSelectionMode(QTableWidget.SingleSelection)
# 添加表格到主布局
self.main_layout.addWidget(self.result_table, 1) # 1表示拉伸因子让表格占据更多空间
def create_buttons(self):
"""创建底部按钮"""
button_layout = QHBoxLayout()
# 分页控件(示例)
page_layout = QHBoxLayout()
# 添加分页按钮(示例)
self.first_page_btn = QPushButton("<<")
self.prev_page_btn = QPushButton("<")
self.page_label = QLabel("1 / 1")
self.next_page_btn = QPushButton(">")
self.last_page_btn = QPushButton(">>")
page_style = """
QPushButton {
border: 1px solid #e0e0e0;
background-color: white;
padding: 4px 8px;
}
QPushButton:hover {
background-color: #f5f5f5;
}
QLabel {
padding: 0 10px;
}
"""
self.first_page_btn.setStyleSheet(page_style)
self.prev_page_btn.setStyleSheet(page_style)
self.next_page_btn.setStyleSheet(page_style)
self.last_page_btn.setStyleSheet(page_style)
page_layout.addWidget(self.first_page_btn)
page_layout.addWidget(self.prev_page_btn)
page_layout.addWidget(self.page_label)
page_layout.addWidget(self.next_page_btn)
page_layout.addWidget(self.last_page_btn)
# 确认和取消按钮
self.confirm_button = QPushButton("确认")
self.confirm_button.setFont(self.normal_font)
self.confirm_button.setStyleSheet("""
QPushButton {
background-color: #0078d4;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
""")
self.confirm_button.setFixedSize(100, 35)
self.cancel_button = QPushButton("取消")
self.cancel_button.setFont(self.normal_font)
self.cancel_button.setStyleSheet("""
QPushButton {
background-color: white;
color: #333333;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 8px 16px;
}
QPushButton:hover {
background-color: #f5f5f5;
}
QPushButton:pressed {
background-color: #e0e0e0;
}
""")
self.cancel_button.setFixedSize(100, 35)
# 添加分页控件和按钮到布局
button_layout.addLayout(page_layout)
button_layout.addStretch()
button_layout.addWidget(self.confirm_button)
button_layout.addSpacing(20)
button_layout.addWidget(self.cancel_button)
# 添加按钮布局到主布局
self.main_layout.addLayout(button_layout)

View File

@ -1,7 +1,8 @@
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QTableWidget, QTableWidgetItem, QHeaderView,
QPushButton, QComboBox, QFrame, QDateEdit
QPushButton, QComboBox, QFrame, QDateEdit,
QLineEdit
)
from PySide6.QtCore import Qt, QDate
from PySide6.QtGui import QFont
@ -37,27 +38,67 @@ class ReportDialogUI(QDialog):
# 筛选条件容器
self.filter_frame = QFrame()
self.filter_frame.setFrameShape(QFrame.StyledPanel)
self.filter_layout = QHBoxLayout(self.filter_frame)
self.filter_layout = QVBoxLayout(self.filter_frame)
# 日期选择
# 第一行:日期选择
self.date_row = QHBoxLayout()
self.date_label = QLabel("日期范围:")
self.date_label.setFont(self.normal_font)
self.date_label.setFixedWidth(80)
self.start_date = QDateEdit()
self.start_date.setFont(self.normal_font)
self.start_date.setCalendarPopup(True)
self.start_date.setDate(QDate.currentDate())
self.start_date.setDate(QDate.currentDate().addMonths(-1)) # 默认开始日期为一个月前
self.date_separator = QLabel("-")
self.end_date = QDateEdit()
self.end_date.setFont(self.normal_font)
self.end_date.setCalendarPopup(True)
self.end_date.setDate(QDate.currentDate())
# 报表类型选择
self.type_label = QLabel("报表类型:")
self.type_label.setFont(self.normal_font)
self.type_combo = QComboBox()
self.type_combo.setFont(self.normal_font)
self.type_combo.addItems(["日报表", "月报表", "年报表"])
self.date_row.addWidget(self.date_label)
self.date_row.addWidget(self.start_date)
self.date_row.addWidget(self.date_separator)
self.date_row.addWidget(self.end_date)
self.date_row.addStretch()
# 第二行:客户、材质、规格
self.filter_row = QHBoxLayout()
# 客户输入
self.customer_label = QLabel("客户:")
self.customer_label.setFont(self.normal_font)
self.customer_label.setFixedWidth(80)
self.customer_edit = QLineEdit()
self.customer_edit.setFont(self.normal_font)
self.customer_edit.setPlaceholderText("输入客户名称")
# 材质输入
self.material_label = QLabel("材质:")
self.material_label.setFont(self.normal_font)
self.material_label.setFixedWidth(80)
self.material_edit = QLineEdit()
self.material_edit.setFont(self.normal_font)
self.material_edit.setPlaceholderText("输入材质")
# 规格输入
self.spec_label = QLabel("规格:")
self.spec_label.setFont(self.normal_font)
self.spec_label.setFixedWidth(80)
self.spec_edit = QLineEdit()
self.spec_edit.setFont(self.normal_font)
self.spec_edit.setPlaceholderText("输入规格")
self.filter_row.addWidget(self.customer_label)
self.filter_row.addWidget(self.customer_edit)
self.filter_row.addSpacing(10)
self.filter_row.addWidget(self.material_label)
self.filter_row.addWidget(self.material_edit)
self.filter_row.addSpacing(10)
self.filter_row.addWidget(self.spec_label)
self.filter_row.addWidget(self.spec_edit)
# 第三行:查询按钮
self.button_row = QHBoxLayout()
# 查询按钮
self.query_button = QPushButton("查询")
@ -74,17 +115,29 @@ class ReportDialogUI(QDialog):
}
""")
# 添加组件到筛选布局
self.filter_layout.addWidget(self.date_label)
self.filter_layout.addWidget(self.start_date)
self.filter_layout.addWidget(self.date_separator)
self.filter_layout.addWidget(self.end_date)
self.filter_layout.addSpacing(20)
self.filter_layout.addWidget(self.type_label)
self.filter_layout.addWidget(self.type_combo)
self.filter_layout.addSpacing(20)
self.filter_layout.addWidget(self.query_button)
self.filter_layout.addStretch()
# 清空按钮
self.clear_button = QPushButton("清空条件")
self.clear_button.setFont(self.normal_font)
self.clear_button.setStyleSheet("""
QPushButton {
padding: 8px 16px;
background-color: #757575;
color: white;
border-radius: 4px;
}
QPushButton:hover {
background-color: #616161;
}
""")
self.button_row.addStretch()
self.button_row.addWidget(self.clear_button)
self.button_row.addWidget(self.query_button)
# 添加所有行到筛选布局
self.filter_layout.addLayout(self.date_row)
self.filter_layout.addLayout(self.filter_row)
self.filter_layout.addLayout(self.button_row)
# 添加到主布局
self.main_layout.addWidget(self.filter_frame)
@ -95,11 +148,10 @@ class ReportDialogUI(QDialog):
self.report_table = QTableWidget()
self.report_table.setFont(self.normal_font)
# 设置列
self.report_table.setColumnCount(8)
# 设置列 - 根据SQL查询结果设置列
self.report_table.setColumnCount(7)
self.report_table.setHorizontalHeaderLabels([
"日期", "工程号", "品名", "规格",
"生产数量", "合格数量", "不合格数量", "合格率"
"日期", "客户", "订单号", "轴数", "材质", "规格", "净重"
])
# 设置表格样式
@ -118,12 +170,79 @@ class ReportDialogUI(QDialog):
# 调整列宽
header = self.report_table.horizontalHeader()
for i in range(8):
for i in range(7):
header.setSectionResizeMode(i, QHeaderView.Stretch)
# 添加到主布局
self.main_layout.addWidget(self.report_table)
# 创建汇总区域
self.create_summary_section()
def create_summary_section(self):
"""创建汇总区域"""
# 汇总容器
self.summary_frame = QFrame()
self.summary_frame.setFrameShape(QFrame.StyledPanel)
self.summary_frame.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
}
""")
self.summary_layout = QHBoxLayout(self.summary_frame)
self.summary_layout.setContentsMargins(15, 10, 15, 10)
self.summary_layout.setSpacing(20)
# 汇总标题
self.summary_title = QLabel("汇总统计")
self.summary_title.setFont(self.title_font)
self.summary_title.setStyleSheet("color: #495057; font-weight: bold;")
# 总轴数
self.total_axes_label = QLabel("总轴数:")
self.total_axes_label.setFont(self.normal_font)
self.total_axes_label.setStyleSheet("color: #495057; font-weight: bold;")
self.total_axes_value = QLabel("0")
self.total_axes_value.setFont(self.normal_font)
self.total_axes_value.setStyleSheet("color: #007bff; font-weight: bold;")
self.total_axes_value.setMinimumWidth(80)
# 总净重
self.total_weight_label = QLabel("总净重:")
self.total_weight_label.setFont(self.normal_font)
self.total_weight_label.setStyleSheet("color: #495057; font-weight: bold;")
self.total_weight_value = QLabel("0.00 ")
self.total_weight_value.setFont(self.normal_font)
self.total_weight_value.setStyleSheet("color: #28a745; font-weight: bold;")
self.total_weight_value.setMinimumWidth(100)
# 记录数
self.record_count_label = QLabel("记录数:")
self.record_count_label.setFont(self.normal_font)
self.record_count_label.setStyleSheet("color: #495057; font-weight: bold;")
self.record_count_value = QLabel("0")
self.record_count_value.setFont(self.normal_font)
self.record_count_value.setStyleSheet("color: #6c757d; font-weight: bold;")
self.record_count_value.setMinimumWidth(60)
# 添加到汇总布局
self.summary_layout.addWidget(self.summary_title)
self.summary_layout.addStretch()
self.summary_layout.addWidget(self.total_axes_label)
self.summary_layout.addWidget(self.total_axes_value)
self.summary_layout.addSpacing(20)
self.summary_layout.addWidget(self.total_weight_label)
self.summary_layout.addWidget(self.total_weight_value)
self.summary_layout.addSpacing(20)
self.summary_layout.addWidget(self.record_count_label)
self.summary_layout.addWidget(self.record_count_value)
# 添加到主布局
self.main_layout.addWidget(self.summary_frame)
def create_button_section(self):
"""创建按钮区域"""
# 按钮容器

View File

@ -9,7 +9,7 @@ class UnloadingDialogUI(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("下料操作")
self.setFixedSize(600, 250) # 减小高度,因为移除了第一行
self.setFixedSize(700, 250) # 增加宽度以适应较长的托盘类型选项
# 设置字体
self.normal_font = QFont("微软雅黑", 12)
@ -98,7 +98,9 @@ class UnloadingDialogUI(QDialog):
self.tray_input.setPlaceholderText("请扫描托盘号")
self.tray_input.setStyleSheet(input_style)
self.tray_input.setFixedHeight(45)
# 禁止用户修改
self.tray_input.setReadOnly(True)
self.tray_input.setFocusPolicy(Qt.NoFocus)
row1.addWidget(self.tray_label)
row1.addWidget(self.tray_input, 1)
container_layout.addLayout(row1)

View File

@ -48,7 +48,7 @@ class ModbusMonitor(QObject):
register_error = Signal(int, str)
monitor_status_changed = Signal(bool, str)
def __init__(self, polling_interval=1.0, max_errors=3, retry_interval=5.0):
def __init__(self, polling_interval=0.5, max_errors=3, retry_interval=5.0):
"""
初始化Modbus监控器
@ -77,7 +77,7 @@ class ModbusMonitor(QObject):
def _initialize_registers(self):
"""初始化要监控的寄存器列表"""
# 默认监控的寄存器地址
register_addresses = [0, 4, 5, 6, 11, 13, 20, 21, 22, 23, 24, 25, 30]
register_addresses = [0, 2, 3, 4, 5, 6, 11, 13, 20, 21, 22, 23, 24, 25, 30]
for address in register_addresses:
self.registers[address] = RegisterValue(address)

View File

@ -302,3 +302,16 @@ class EmergencyStopHandler:
if changed:
logging.info(f"急停状态变化: {desc} (值={value})")
return changed, desc
class RegisterChangeHandler(RegisterHandler):
"""通用寄存器变化处理器用于处理寄存器2和3等"""
def __init__(self, callback=None, address=None):
super().__init__()
self.callback = callback
self.address = address
def handle_change(self, value):
logging.info(f"寄存器D{self.address}变化: {value}")
if self.callback:
self.callback(self.address, value)

View File

@ -51,6 +51,12 @@ 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,
@ -996,20 +1002,26 @@ class SerialManager:
port_name = self.cz_config['ser']
baud_rate = self.cz_config.get('port', 9600)
if not self.is_port_open(port_name):
# 检查串口是否在冷却期内
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):
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.warning("称重串口未配置或设置为不使用,跳过自动打开")
logging.info("称重串口未配置或设置为不使用,跳过自动打开")
# 尝试打开线径串口
if self.xj_config and 'ser' in self.xj_config and self.xj_config['ser'] and self.xj_config['ser'].strip():
@ -1117,8 +1129,8 @@ class SerialManager:
byte_data = bytes.fromhex(query_cmd.replace(' ', ''))
self.serial_ports[port_name].write(byte_data)
# 等待响应
time.sleep(0.5)
# 等待响应 - 减少等待时间以加快数据获取
time.sleep(0.2)
if self.serial_ports[port_name].in_waiting > 0:
response = self.serial_ports[port_name].read(self.serial_ports[port_name].in_waiting)
@ -1128,14 +1140,14 @@ class SerialManager:
logging.error(f"线径数据处理异常: {e}")
# 查询间隔,从配置中获取或使用默认值
query_interval = self.xj_config.get('query_interval', 5) if self.xj_config else 5
wait_cycles = int(query_interval * 10) # 转换为0.1秒的周期数
query_interval = self.xj_config.get('query_interval', 1) if self.xj_config else 1
# 每隔query_interval秒查询一次
for i in range(wait_cycles):
if not self.running_flags.get(port_name, False):
break
time.sleep(0.1)
# 确保查询间隔不小于0.2秒
if query_interval < 0.2:
query_interval = 0.2
# 每隔query_interval秒查询一次使用更简单的等待方式
time.sleep(query_interval)
except Exception as e:
logging.error(f"线径串口 {port_name} 读取线程异常: {e}")
@ -1326,3 +1338,45 @@ class SerialManager:
# 尝试重新启动监控
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]

View File

@ -1,25 +1,30 @@
from ui.loading_dialog_ui import LoadingDialogUI
from apis.tary_api import TaryApi
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QMessageBox, QDialog
import logging
from PySide6.QtWidgets import QMessageBox
from PySide6.QtCore import Qt, Signal
from ui.loading_dialog_ui import LoadingDialogUI
from widgets.order_query_dialog import OrderQueryDialog
from utils.app_mode import AppMode
from utils.pallet_type_manager import PalletTypeManager
from apis.gc_api import GcApi
class LoadingDialog(LoadingDialogUI):
# 定义一个信号,用于向主窗口传递托盘号
class LoadingDialog(LoadingDialogUI):
"""上料操作对话框"""
# 定义信号,用于向主窗口传递托盘号
tray_code_signal = Signal(str, str, str, str)
# 定义一个信号,用于向主窗口传递订单号
# 定义信号,用于向主窗口传递订单号
order_code_signal = Signal(str)
def __init__(self, parent=None,user_id=None,user_name=None,corp_id=None):
"""初始化加载对话框"""
def __init__(self, parent=None, user_id=None, user_name=None, corp_id=None):
super().__init__()
self.parent = parent
self.user_id = user_id
self.user_id = user_id,
self.user_name = user_name
self.corp_id = corp_id
# 存储订单数据
self.order_data = None
# 彻底禁用对话框的回车键关闭功能
self.setModal(True)
# 禁用所有按钮的默认行为
@ -35,11 +40,14 @@ class LoadingDialog(LoadingDialogUI):
from utils.focus_tracker import FocusTracker
self.focus_tracker = FocusTracker.get_instance()
# 绑定事件
self.setup_connections()
# 连接信号和槽
self.connect_signals()
def connect_signals(self):
"""连接信号和槽"""
# 订单查询按钮点击事件
self.order_query_btn.clicked.connect(self.show_order_query_dialog)
def setup_connections(self):
"""设置事件连接"""
# 订单号输入框回车事件触发查询
self.order_input.returnPressed.connect(self.handle_order_return_pressed)
@ -48,6 +56,10 @@ class LoadingDialog(LoadingDialogUI):
# 取消按钮点击事件
self.cancel_button.clicked.connect(self.reject)
# 托盘号输入框回车事件
self.tray_input.returnPressed.connect(self.on_tray_entered)
def handle_order_return_pressed(self):
"""处理订单输入框的回车事件"""
logging.info("订单输入框回车事件触发")
@ -64,66 +76,198 @@ class LoadingDialog(LoadingDialogUI):
logging.info(f"发送订单号到主窗口: {order_code}")
self.order_code_signal.emit(order_code)
#判断是否是接口,如果不是接口直接添加如果是则走接口
# 如果开启接口模式,则需要调用接口同步到业务库
order_info = None
# 查询订单信息
self.on_order_query(order_code)
# 阻止事件继续传播
return True
def on_order_query(self,order_code):
def show_order_query_dialog(self):
"""显示订单查询对话框"""
try:
# 创建订单查询对话框
dialog = OrderQueryDialog(self)
# 连接订单选择信号
dialog.order_selected.connect(self.on_order_selected)
# 显示对话框
dialog.exec()
except Exception as e:
logging.error(f"显示订单查询对话框失败: {e}")
QMessageBox.critical(self, "错误", f"显示订单查询对话框失败: {str(e)}")
def on_order_selected(self, order_data):
"""处理订单选择事件
Args:
order_data: 订单数据字典
"""
try:
# 存储订单数据
self.order_data = order_data
logging.info(f"已存储订单数据: {order_data.get('mo', '')}")
# 更新UI
# 注意此时order_data["note"]已经被修改为order_data["mo"]的值
self.order_input.setText(order_data.get("mo", ""))
# 更新轴型
self.axis_value.setText(order_data.get("zx_name", "--"))
# 更新数量
self.quantity_value.setText(str(order_data.get("sl", "--")))
# 更新重量
self.weight_value.setText(str(order_data.get("zx_zl", "--")))
# 自动填充托盘号
if "xpack" in order_data:
self.tray_input.setText(order_data["xpack"])
# 发送订单号到主窗口
from widgets.main_window import MainWindow
main_window = self.parent
if main_window and isinstance(main_window, MainWindow):
# 使用note字段作为订单号已经被修改为mo的值
order_code = order_data.get("mo", "")
logging.info(f"发送订单号到主窗口: {order_code}")
self.order_code_signal.emit(order_code)
# 设置焦点到托盘号输入框
self.tray_input.setFocus()
except Exception as e:
logging.error(f"处理订单选择事件失败: {e}")
QMessageBox.critical(self, "错误", f"处理订单选择事件失败: {str(e)}")
def on_order_query(self, order_code):
"""查询订单信息,同时生成托盘号,并且回显到页面上"""
try:
if AppMode.is_api():
# 构建查询参数
query_params = {
"srch_mo": order_code,
"srch_note": order_code,
"corp_id": self.corp_id
}
# 创建订单查询对话框(仅用于执行查询)
dialog = OrderQueryDialog(self)
# 执行查询
results = dialog.query_orders(query_params)
# 处理查询结果
if results:
# 如果只有一个结果,直接使用
if len(results) == 1:
self.on_order_selected(results[0])
return
else:
# 如果有多个结果,显示查询对话框让用户选择
dialog.update_result_table(results)
dialog.order_selected.connect(self.on_order_selected)
dialog.exec()
return
# 如果没有结果并且不是API模式显示提示
if not AppMode.is_api():
QMessageBox.warning(self, "提示", f"未找到订单: {order_code}")
return
# 如果是API模式尝试使用旧的API方式查询
# 调用接口
gc_api = GcApi()
# 防止response为None导致异常
# 获取工程号信息,并且初始化数据
order_response = gc_api.get_order_info(order_code,self.corp_id)
order_response = gc_api.get_order_info(order_code, self.corp_id)
# 生成托盘号
if(order_response.get("status",False)):
if order_response and order_response.get("status", False):
# 将接口数据保存到数据库,用于后续的关联使用
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
order_info = order_response.get("data", {})[0]
# 设置轴数
order_info['user_id'] = self.user_id
order_info['user_id'] = self.user_id[0]
order_info['user_name'] = self.user_name
order_info['data_corp'] = self.corp_id
inspection_dao.save_order_info(order_code,order_info)
inspection_dao.save_order_info(order_code, order_info)
self.axis_value.setText(order_info['zx_name'])
self.quantity_value.setText(str(order_info['sl']))
self.weight_value.setText(str(order_info['sl']))
xpack_response = gc_api.get_xpack(order_code,self.corp_id)
if(xpack_response.get("status",False)):
self.weight_value.setText(str(order_info['zx_zl']))
# 存储订单数据
self.order_data = order_info
logging.info(f"已存储订单数据: {order_code}")
else:
QMessageBox.warning(self, "提示", f"未找到订单: {order_code}")
return
xpack_response = gc_api.get_xpack(order_code, self.corp_id)
if xpack_response and xpack_response.get("status", False):
xpack = xpack_response['xpack']
spack = xpack_response['spack']
self.tray_input.setText(xpack)
self.pallet_tier_value.setText('3')
# 发送托盘号到主窗
# 将spack保存到order_data中以便后续使用
if hasattr(self, 'order_data') and self.order_data:
self.order_data['spack'] = spack
logging.info(f"已将spack: {spack}保存到order_data中")
# 发送托盘号到主窗口
from widgets.main_window import MainWindow
main_window = self.parent
if main_window and isinstance(main_window, MainWindow):
# 检查托盘号是否已存在
existed = False
for i in range(main_window.tray_edit.count()):
if main_window.tray_edit.itemText(i) == xpack:
existed = True
break
# 如果不存在,则添加
if not existed:
logging.info(f"添加托盘号到主窗口: {xpack}")
main_window.tray_edit.addItem(xpack)
# 设置当前选中的托盘号
main_window.tray_edit.setCurrentText(xpack)
# 由于 tray_edit 现在是 QLineEdit不再需要检查是否存在
# 直接设置文本
logging.info(f"设置主窗口当前托盘号: {xpack}")
main_window.tray_edit.setText(xpack)
# 手动触发托盘号变更事件
main_window.handle_tray_changed(xpack)
# spack值已经获取可以在后续处理中使用
logging.info(f"已获取spack: {spack}将与xpack一起使用")
return order_info
except Exception as e:
logging.error(f"查询订单信息异常: {str(e)}")
return None
logging.error(f"查询订单信息失败: {e}")
QMessageBox.critical(self, "错误", f"查询订单信息失败: {str(e)}")
def on_xpack_query(self, tray_id):
"""通过托盘号查询订单信息并回显到页面完全仿照on_order_query"""
try:
query_params = {
"srch_spack": tray_id,
"corp_id": self.corp_id
}
dialog = OrderQueryDialog(self)
results = dialog.query_orders_xpack(query_params)
if results:
if len(results) == 1:
self.on_order_selected(results[0])
return
else:
dialog.update_result_table(results)
dialog.order_selected.connect(self.on_order_selected)
dialog.exec()
return
QMessageBox.warning(self, "提示", "未找到该托盘号对应的订单信息")
except Exception as e:
logging.error(f"托盘号查订单接口异常: {e}")
QMessageBox.warning(self, "错误", f"托盘号查订单接口异常: {str(e)}")
def on_tray_entered(self):
tray_id = self.tray_input.text().strip()
if not tray_id:
QMessageBox.warning(self, "提示", "请输入托盘号")
return
self.on_xpack_query(tray_id)
self.pallet_tier_value.setFocus()
def keyPressEvent(self, event):
"""重写键盘事件处理,防止回车关闭对话框"""
@ -132,9 +276,7 @@ class LoadingDialog(LoadingDialogUI):
logging.info(f"捕获到回车键事件,当前焦点部件: {self.focusWidget()}")
# 如果焦点在托盘输入框上,触发查询
# 注释掉此处的on_tray_query调用因为已经通过returnPressed信号处理了
if self.focusWidget() == self.tray_input:
# self.on_tray_query() - 通过returnPressed信号已经处理这里不需要再调用
event.accept() # 消费掉这个事件
return
@ -165,14 +307,24 @@ class LoadingDialog(LoadingDialogUI):
QMessageBox.warning(self, "提示", "请输入托盘号")
return
# 检查订单号
order_id = self.order_input.text().strip()
if not order_id:
QMessageBox.warning(self, "提示", "请输入订单号")
self.order_input.setFocus()
return
# if not self.order_data:
# QMessageBox.warning(self, "提示", "请先查询订单信息")
# return
try:
# 保存托盘档案信息
from utils.pallet_type_manager import PalletTypeManager
pallet_manager = PalletTypeManager.get_instance()
success = pallet_manager.save_pallet_archives(
pallet_code=pallet_code,
tier=int(tier_value),
user_id=self.user_id,
user_id=self.user_id[0],
user_name=self.user_name
)
@ -213,3 +365,30 @@ class LoadingDialog(LoadingDialogUI):
logging.info("取消按钮被点击或ESC触发取消")
# 调用父类的reject方法关闭对话框
super().reject()
def get_loading_data(self):
"""获取上料数据
Returns:
dict: 上料数据字典
"""
order_id = self.order_input.text().strip()
tray_id = self.tray_input.text().strip()
spack = ""
gc_api = GcApi()
response = gc_api.get_spack_info(order_id, tray_id, self.corp_id)
if response and response.get("status", False) and response.get("data", {}).get("spack", ""):
spack = response.get("data", {}).get("spack", "")
elif self.order_data and isinstance(self.order_data, dict) and 'spack' in self.order_data:
spack = self.order_data['spack']
else:
spack = ""
return {
"order_id": order_id,
"tray_id": tray_id,
"spack": spack, # 添加spack字段
"pallet_tier": self.pallet_tier_value.text().strip(),
"order_data": self.order_data if self.order_data else {}
}

View File

@ -0,0 +1,413 @@
import logging
from datetime import datetime
from PySide6.QtWidgets import QTableWidgetItem, QMessageBox
from PySide6.QtCore import Qt, Signal
from ui.luno_query_dialog_ui import LunoQueryDialogUI
from utils.sql_utils import SQLUtils
from utils.app_mode import AppMode
from apis.gc_api import GcApi
class LunoQueryDialog(LunoQueryDialogUI):
"""炉号查询对话框,用于查询炉号信息"""
# 定义信号,用于向上传递选中的炉号信息
luno_selected = Signal(dict)
def __init__(self, parent=None):
super().__init__()
# 存储查询结果
self.query_results = []
# 获取父窗口的用户信息
self.corp_id = None
self.user_id = None
self.user_name = None
if parent:
if hasattr(parent, 'corp_id'):
self.corp_id = parent.corp_id
if hasattr(parent, 'user_id'):
self.user_id = parent.user_id[0] if isinstance(parent.user_id, tuple) else parent.user_id
if hasattr(parent, 'user_name'):
self.user_name = parent.user_name
# 连接信号和槽
self.connect_signals()
def connect_signals(self):
"""连接信号和槽"""
# 查询按钮点击事件
self.query_button.clicked.connect(self.on_luno_query)
# 确认按钮点击事件
self.confirm_button.clicked.connect(self.on_confirm)
# 取消按钮点击事件
self.cancel_button.clicked.connect(self.reject)
# 表格双击事件
self.result_table.cellDoubleClicked.connect(self.on_table_double_clicked)
def on_luno_query(self):
"""执行炉号查询"""
try:
# 获取查询参数
start_date = self.start_date.date().toString("yyyy-MM-dd")
end_date = self.end_date.date().toString("yyyy-MM-dd")
luno = self.luno_input.text().strip()
material = self.material_combo.currentText()
if material == "全部":
material = ""
spec = self.spec_input.text().strip()
steel = self.steel_input.text().strip()
# 构建查询参数
query_params = {
"srch_rq1": start_date,
"srch_rq2": end_date,
"luono": luno,
"cz": material,
"gg": spec,
"gc": steel,
"corp_id": self.corp_id
}
# 执行查询
results = self.query_lunos(query_params)
# 更新表格
self.update_result_table(results)
except Exception as e:
logging.error(f"炉号查询失败: {e}")
QMessageBox.critical(self, "查询错误", f"查询炉号时发生错误: {str(e)}")
def query_lunos(self, params):
"""查询炉号数据
Args:
params: 查询参数字典
Returns:
list: 炉号数据列表
"""
try:
# 如果是API模式优先从接口获取数据
if AppMode.is_api():
return self._query_lunos_from_api(params)
else:
return self._query_lunos_from_db(params)
except Exception as e:
logging.error(f"查询炉号数据失败: {e}")
raise
def _query_lunos_from_api(self, params):
"""从API获取炉号数据
Args:
params: 查询参数字典
Returns:
list: 炉号数据列表
"""
try:
# 调用接口
gc_api = GcApi()
# 调用接口查询炉号列表
response = gc_api.get_luno_list(params)
# 处理接口返回结果 - 接口直接返回数据列表
if response and isinstance(response, list):
# 接口直接返回炉号数据列表
lunos_data = response
results = []
for luno_data in lunos_data:
# 转换为统一格式的字典,处理接口返回的实际字段
luno_dict = {
"luono": luno_data.get("luono", ""),
"cz": luno_data.get("cz", ""),
"gg": luno_data.get("gg", ""),
"gc": luno_data.get("gc", ""),
"bz": luno_data.get("bz", ""),
"data_corp": luno_data.get("data_corp", ""),
"data_corp_name": luno_data.get("data_corp_name", ""),
"c": luno_data.get("c", ""),
"si": luno_data.get("si", ""),
"mn": luno_data.get("mn", ""),
"p": luno_data.get("p", ""),
"s": luno_data.get("s", ""),
"ni": luno_data.get("ni", ""),
"cr": luno_data.get("cr", ""),
"ti": luno_data.get("ti", ""),
"mo": luno_data.get("mo", ""),
"cu": luno_data.get("cu", ""),
"others": luno_data.get("others", ""),
"klqd": luno_data.get("klqd", ""),
"ysl": luno_data.get("ysl", ""),
"rq": luno_data.get("rq", "")
}
# 处理null值转换为空字符串
for key, value in luno_dict.items():
if value is None:
luno_dict[key] = ""
results.append(luno_dict)
# 保存查询结果
self.query_results = results
# 如果接口查询到数据,则保存到数据库中
if results:
self._save_lunos_to_db(results)
return results
else:
# 如果接口返回的不是列表或为空,则尝试从数据库查询
logging.warning("从API获取炉号数据失败或返回格式不正确尝试从数据库查询")
return self._query_lunos_from_db(params)
except Exception as e:
logging.error(f"从API获取炉号数据失败: {e}")
# 如果接口查询失败,则尝试从数据库查询
logging.warning("从API获取炉号数据失败尝试从数据库查询")
return self._query_lunos_from_db(params)
def _save_lunos_to_db(self, lunos):
"""将炉号数据保存到数据库
Args:
lunos: 炉号数据列表
"""
try:
# 导入DAO
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 遍历炉号数据,保存到数据库
for luno_data in lunos:
luno_code = luno_data.get("luono", "")
if luno_code:
# 设置用户信息
if self.user_id:
luno_data['user_id'] = self.user_id
if self.user_name:
luno_data['user_name'] = self.user_name
if self.corp_id:
luno_data['data_corp'] = self.corp_id
# 保存炉号信息
inspection_dao.save_luno_info(luno_code, luno_data)
logging.info(f"成功保存{len(lunos)}条炉号数据到数据库")
except Exception as e:
logging.error(f"保存炉号数据到数据库失败: {e}")
def _query_lunos_from_db(self, params):
"""从数据库获取炉号数据
Args:
params: 查询参数字典
Returns:
list: 炉号数据列表
"""
# 构建SQL查询
sql = """
SELECT
l.luono, l.cz, l.gg, l.gc, l.bz, l.data_corp, l.data_corp_name,
l.c, l.si, l.mn, l.p, l.s, l.ni, l.cr, l.ti, l.mo, l.cu, l.others,
l.klqd, l.ysl, l.create_time as rq
FROM wsbz_luno_info l
WHERE 1=1
"""
query_params = []
# 添加查询条件
if params.get("srch_rq1") and params.get("srch_rq2"):
sql += " AND l.create_time BETWEEN ? AND ?"
query_params.append(params["srch_rq1"] + " 00:00:00")
query_params.append(params["srch_rq2"] + " 23:59:59")
if params.get("luono"):
sql += " AND l.luono LIKE ?"
query_params.append(f"%{params['luono']}%")
if params.get("cz"):
sql += " AND l.cz = ?"
query_params.append(params["cz"])
if params.get("gg"):
sql += " AND l.gg LIKE ?"
query_params.append(f"%{params['gg']}%")
if params.get("gc"):
sql += " AND l.gc LIKE ?"
query_params.append(f"%{params['gc']}%")
# 添加排序
sql += " ORDER BY l.create_time DESC"
# 执行查询
with SQLUtils('sqlite') as db:
db.execute_query(sql, query_params)
rows = db.fetchall()
# 处理查询结果
results = []
for row in rows:
# 将元组转换为字典
luno_data = {
"luono": row[0],
"cz": row[1],
"gg": row[2],
"gc": row[3],
"bz": row[4],
"data_corp": row[5],
"data_corp_name": row[6],
"c": row[7],
"si": row[8],
"mn": row[9],
"p": row[10],
"s": row[11],
"ni": row[12],
"cr": row[13],
"ti": row[14],
"mo": row[15],
"cu": row[16],
"others": row[17],
"klqd": row[18],
"ysl": row[19],
"rq": row[20]
}
results.append(luno_data)
# 保存查询结果
self.query_results = results
return results
def update_result_table(self, results):
"""更新结果表格
Args:
results: 炉号数据列表
"""
# 清空表格
self.result_table.setRowCount(0)
if not results:
return
# 定义表头和对应的字段名
columns = [
{"title": "序号", "field": None},
{"title": "日期", "field": "rq"},
{"title": "炉号", "field": "luono"},
{"title": "材质", "field": "cz"},
{"title": "规格", "field": "gg"},
{"title": "钢厂", "field": "gc"},
{"title": "标准", "field": "bz"},
{"title": "公司账套", "field": "data_corp_name"},
{"title": "C", "field": "c"},
{"title": "Si", "field": "si"},
{"title": "Mn", "field": "mn"},
{"title": "P", "field": "p"},
{"title": "S", "field": "s"},
{"title": "Ni", "field": "ni"},
{"title": "Cr", "field": "cr"},
{"title": "Ti", "field": "ti"},
{"title": "Mo", "field": "mo"},
{"title": "Cu", "field": "cu"},
{"title": "其他", "field": "others"},
{"title": "抗拉强度", "field": "klqd"},
{"title": "延伸率", "field": "ysl"}
]
# 设置表头
self.result_table.setColumnCount(len(columns))
header_labels = [col["title"] for col in columns]
self.result_table.setHorizontalHeaderLabels(header_labels)
# 设置行数
self.result_table.setRowCount(len(results))
# 填充数据
for row, luno_data in enumerate(results):
# 序号
item_index = QTableWidgetItem(str(row + 1))
item_index.setTextAlignment(Qt.AlignCenter)
self.result_table.setItem(row, 0, item_index)
# 遍历列,填充数据
for col_idx, column in enumerate(columns[1:], 1): # 从1开始跳过序号列
field = column["field"]
if field:
value = ""
# 特殊处理某些字段
if field == "rq":
create_time = luno_data.get(field, "")
if create_time:
try:
# 尝试解析日期时间字符串,支持多种格式
if " " in create_time:
# 包含时间的格式2025-07-09 10:30:00
dt = datetime.strptime(create_time, "%Y-%m-%d %H:%M:%S")
value = dt.strftime("%Y-%m-%d")
else:
# 只有日期的格式2025-07-09
dt = datetime.strptime(create_time, "%Y-%m-%d")
value = dt.strftime("%Y-%m-%d")
except:
# 如果解析失败,直接使用原值
value = create_time
else:
# 其他普通字段
value = str(luno_data.get(field, ""))
# 创建表格项并设置文本
item = QTableWidgetItem(value)
# 居中对齐特定的列
if field in ["rq", "c", "si", "mn", "p", "s", "ni", "cr", "ti", "mo", "cu", "klqd", "ysl"]:
item.setTextAlignment(Qt.AlignCenter)
# 设置表格项
self.result_table.setItem(row, col_idx, item)
# 存储原始数据到第一列的item中
item_index.setData(Qt.UserRole, luno_data)
def on_table_double_clicked(self, row, column):
"""表格双击事件处理"""
self.select_current_row(row)
def on_confirm(self):
"""确认按钮点击事件处理"""
# 获取当前选中行
selected_rows = self.result_table.selectionModel().selectedRows()
if not selected_rows:
QMessageBox.warning(self, "提示", "请选择一个炉号")
return
# 获取选中行的索引
row_index = selected_rows[0].row()
self.select_current_row(row_index)
def select_current_row(self, row):
"""选择当前行并返回数据"""
if 0 <= row < self.result_table.rowCount():
# 获取存储在item中的原始数据
item = self.result_table.item(row, 0)
if item:
luno_data = item.data(Qt.UserRole)
if luno_data:
# 发出信号
self.luno_selected.emit(luno_data)
self.accept()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,490 @@
import logging
from datetime import datetime
from PySide6.QtWidgets import QTableWidgetItem, QMessageBox
from PySide6.QtCore import Qt, Signal
from ui.order_query_dialog_ui import OrderQueryDialogUI
from utils.sql_utils import SQLUtils
from utils.app_mode import AppMode
from apis.gc_api import GcApi
class OrderQueryDialog(OrderQueryDialogUI):
"""订单查询对话框,用于查询订单信息"""
# 定义信号,用于向上传递选中的订单信息
order_selected = Signal(dict)
def __init__(self, parent=None):
super().__init__()
# 存储查询结果
self.query_results = []
# 获取父窗口的用户信息
self.corp_id = None
self.user_id = None
self.user_name = None
if parent:
if hasattr(parent, 'corp_id'):
self.corp_id = parent.corp_id
if hasattr(parent, 'user_id'):
self.user_id = parent.user_id[0]
if hasattr(parent, 'user_name'):
self.user_name = parent.user_name
# 连接信号和槽
self.connect_signals()
def connect_signals(self):
"""连接信号和槽"""
# 查询按钮点击事件
self.query_button.clicked.connect(self.on_order_query)
# 确认按钮点击事件
self.confirm_button.clicked.connect(self.on_confirm)
# 取消按钮点击事件
self.cancel_button.clicked.connect(self.reject)
# 表格双击事件
self.result_table.cellDoubleClicked.connect(self.on_table_double_clicked)
def on_order_query(self):
"""执行订单查询"""
try:
# 获取查询参数
start_date = self.start_date.date().toString("yyyy-MM-dd")
end_date = self.end_date.date().toString("yyyy-MM-dd")
order_mo = self.order_mo_input.text().strip()
material = self.material_combo.currentText()
if material == "全部":
material = ""
spec = self.spec_input.text().strip()
# 构建查询参数
query_params = {
"srch_rq1": start_date,
"srch_rq2": end_date,
"srch_mo": order_mo,
"srch_note": order_mo,
"srch_cz": material,
"srch_size": spec,
"corp_id": self.corp_id
}
# 执行查询
results = self.query_orders(query_params)
# 更新表格
self.update_result_table(results)
except Exception as e:
logging.error(f"订单查询失败: {e}")
QMessageBox.critical(self, "查询错误", f"查询订单时发生错误: {str(e)}")
def query_orders(self, params):
"""查询订单数据
Args:
params: 查询参数字典
Returns:
list: 订单数据列表
"""
try:
# 如果是API模式优先从接口获取数据
if AppMode.is_api():
return self._query_orders_from_api(params)
else:
return self._query_orders_from_db(params)
except Exception as e:
logging.error(f"查询订单数据失败: {e}")
raise
def _query_orders_from_api(self, params):
"""从API获取订单数据
Args:
params: 查询参数字典
Returns:
list: 订单数据列表
"""
try:
# 调用接口
gc_api = GcApi()
# 调用接口查询订单列表
response = gc_api.get_order_list(params)
# 处理接口返回结果
if response and response.get("status", False):
# 将接口返回的数据转换为标准格式
orders_data = response.get("data", [])
results = []
for order_data in orders_data:
# 转换为统一格式的字典
order_dict = {
"note": order_data.get("note", ""),
"mo": order_data.get("mo", ""),
"customer": order_data.get("customer", ""),
"customerexp": order_data.get("customerexp", ""),
"khno": order_data.get("khno", ""),
"ddzl": order_data.get("ddzl", ""),
"khjq": order_data.get("khjq", ""),
"code": order_data.get("code", ""),
"type": order_data.get("type", ""),
"cz": order_data.get("cz", ""),
"size": order_data.get("size", ""),
"cd": order_data.get("cd", ""),
"maxsl": order_data.get("maxsl", ""),
"spack": order_data.get("spack", ""),
"zx_name": order_data.get("zx_name", ""),
"zx_code": order_data.get("zx_code", ""),
"zx_zl": order_data.get("zx_zl", ""),
"template_name": order_data.get("template_name", ""),
"bqlb": order_data.get("bqlb", ""),
# 删除重复的khno字段
"dycz": order_data.get("dycz", ""),
"luno": order_data.get("luno", ""),
"corp": order_data.get("corp", ""),
"sl": order_data.get("sl", ""),
"tccd": order_data.get("tccd", ""),
"bccd": order_data.get("bccd", ""),
"ysl": order_data.get("ysl", ""),
"qfqd": order_data.get("qfqd", ""),
"yzgg": order_data.get("yzgg", ""),
"tqd": order_data.get("tqd", ""),
"bqd": order_data.get("bqd", ""),
"bzfs": order_data.get("bzfs", ""),
"ddyq": order_data.get("ddyq", ""),
"remarks_hb": order_data.get("remarks_hb", ""),
"bz_tqd": order_data.get("bz_tqd", ""),
"bz_bqd": order_data.get("bz_bqd", ""),
"zzyq": order_data.get("zzyq", ""),
"rq": order_data.get("rq", "")
}
results.append(order_dict)
# 保存查询结果
self.query_results = results
# 如果接口查询到数据,则保存到数据库中
if results:
self._save_orders_to_db(results)
return results
else:
# 如果接口查询失败,则尝试从数据库查询
logging.warning("从API获取订单数据失败尝试从数据库查询")
return self._query_orders_from_db(params)
except Exception as e:
logging.error(f"从API获取订单数据失败: {e}")
# 如果接口查询失败,则尝试从数据库查询
logging.warning("从API获取订单数据失败尝试从数据库查询")
return self._query_orders_from_db(params)
def _save_orders_to_db(self, orders):
"""将订单数据保存到数据库
Args:
orders: 订单数据列表
"""
try:
# 导入DAO
from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO()
# 遍历订单数据,保存到数据库
for order_data in orders:
order_code = order_data.get("note", "")
if order_code:
# 设置用户信息
if self.user_id:
order_data['user_id'] = self.user_id
if self.user_name:
order_data['user_name'] = self.user_name
if self.corp_id:
order_data['data_corp'] = self.corp_id
# 保存订单信息
inspection_dao.save_order_info(order_code, order_data)
logging.info(f"成功保存{len(orders)}条订单数据到数据库")
except Exception as e:
logging.error(f"保存订单数据到数据库失败: {e}")
def _query_orders_from_db(self, params):
"""从数据库获取订单数据
Args:
params: 查询参数字典
Returns:
list: 订单数据列表
"""
# 构建SQL查询
sql = """
SELECT
o.note, o.mo, o.customer, o.customerexp, o.khno, o.ddzl, o.khjq, o.code, o.type,
o.cz, o.size, o.cd, o.maxsl, o.spack, o.zx_name, o.zx_code, o.zx_zl,
o.template_name, o.bqlb, o.dycz, o.luno, o.corp, o.sl, o.tccd, o.bccd,
o.ysl, o.qfqd, o.yzgg, o.tqd, o.bqd, o.bzfs, o.ddyq, o.remarks_hb,
o.bz_tqd, o.bz_bqd, o.zzyq, o.create_time as rq
FROM wsbz_order_info o
WHERE 1=1
"""
query_params = []
# 添加查询条件
if params.get("srch_rq1") and params.get("srch_rq2"):
sql += " AND o.create_time BETWEEN ? AND ?"
query_params.append(params["srch_rq1"] + " 00:00:00")
query_params.append(params["srch_rq2"] + " 23:59:59")
if params.get("srch_mo"):
sql += " AND (o.mo LIKE ? OR o.note LIKE ?)"
query_params.append(f"%{params['srch_mo']}%")
query_params.append(f"%{params['srch_mo']}%")
if params.get("srch_cz"):
sql += " AND o.cz = ?"
query_params.append(params["srch_cz"])
if params.get("srch_size"):
sql += " AND o.size LIKE ?"
query_params.append(f"%{params['srch_size']}%")
# 添加排序
sql += " ORDER BY o.create_time DESC"
# 执行查询
with SQLUtils('sqlite') as db:
db.execute_query(sql, query_params)
rows = db.fetchall()
# 处理查询结果
results = []
for row in rows:
# 将元组转换为字典,使用安全的索引访问
try:
order_data = {
"note": row[0] if len(row) > 0 else "",
"mo": row[1] if len(row) > 1 else "",
"customer": row[2] if len(row) > 2 else "",
"customerexp": row[3] if len(row) > 3 else "",
"khno": row[4] if len(row) > 4 else "",
"ddzl": row[5] if len(row) > 5 else "",
"khjq": row[6] if len(row) > 6 else "",
"code": row[7] if len(row) > 7 else "",
"type": row[8] if len(row) > 8 else "",
"cz": row[9] if len(row) > 9 else "",
"size": row[10] if len(row) > 10 else "",
"cd": row[11] if len(row) > 11 else "",
"maxsl": row[12] if len(row) > 12 else "",
"spack": row[13] if len(row) > 13 else "",
"zx_name": row[14] if len(row) > 14 else "",
"zx_code": row[15] if len(row) > 15 else "",
"zx_zl": row[16] if len(row) > 16 else "",
"template_name": row[17] if len(row) > 17 else "",
"bqlb": row[18] if len(row) > 18 else "",
"dycz": row[19] if len(row) > 19 else "",
"luno": row[20] if len(row) > 20 else "",
"corp": row[21] if len(row) > 21 else "",
"sl": row[22] if len(row) > 22 else "",
"tccd": row[23] if len(row) > 23 else "",
"bccd": row[24] if len(row) > 24 else "",
"ysl": row[25] if len(row) > 25 else "",
"qfqd": row[26] if len(row) > 26 else "",
"yzgg": row[27] if len(row) > 27 else "",
"tqd": row[28] if len(row) > 28 else "",
"bqd": row[29] if len(row) > 29 else "",
"bzfs": row[30] if len(row) > 30 else "",
"ddyq": row[31] if len(row) > 31 else "",
"remarks_hb": row[32] if len(row) > 32 else "",
"bz_tqd": row[33] if len(row) > 33 else "",
"bz_bqd": row[34] if len(row) > 34 else "",
"zzyq": row[35] if len(row) > 35 else "",
"rq": row[36] if len(row) > 36 else ""
}
results.append(order_data)
except Exception as e:
logging.error(f"处理查询结果行时出错: {str(e)}, 行数据: {row}")
continue
# 保存查询结果
self.query_results = results
return results
def query_orders_xpack(self, params):
"""通过托盘号查订单数据,返回列表"""
try:
gc_api = GcApi()
response = gc_api.get_order_info_by_xpack(params.get("srch_spack"), params.get("corp_id"))
if response and response.get("status", False):
orders_data = response.get("data", [])
results = []
for order_data in orders_data:
results.append(order_data)
self.query_results = results
return results
else:
return []
except Exception as e:
logging.error(f"通过托盘号查订单数据失败: {e}")
return []
def update_result_table(self, results):
"""更新结果表格
Args:
results: 订单数据列表
"""
# 清空表格
self.result_table.setRowCount(0)
if not results:
return
# 定义表头和对应的字段名(完整列表)
columns = [
{"title": "序号", "field": None},
{"title": "日期", "field": "rq"},
{"title": "订单号", "field": "note"},
{"title": "订单明细", "field": "mo"},
{"title": "客户", "field": "customer"},
{"title": "客户名称", "field": "customerexp"},
{"title": "客户订单号", "field": "khno"}, # 修改标题以区分
{"title": "订单类别", "field": "ddzl"},
{"title": "客户交期", "field": "khjq"},
{"title": "编码", "field": "code"},
{"title": "产品类别", "field": "type"},
{"title": "材质", "field": "cz"},
{"title": "规格", "field": "size"},
{"title": "产地", "field": "cd"},
{"title": "最大入库量", "field": "maxsl"},
{"title": "托盘号", "field": "spack"},
{"title": "轴型", "field": "zx_name"},
{"title": "轴型code", "field": "zx_code"},
{"title": "轴型重量", "field": "zx_zl"},
{"title": "标签类别", "field": "template_name"},
{"title": "标签类别code", "field": "bqlb"},
# 删除重复的"客户实际订单号"列
{"title": "打印材质", "field": "dycz"},
{"title": "炉号", "field": "luno"},
{"title": "公司", "field": "corp"},
{"title": "数量", "field": "sl"},
{"title": "上公差", "field": "tccd"},
{"title": "下公差", "field": "bccd"},
{"title": "延伸率", "field": "ysl"},
{"title": "屈服强度", "field": "qfqd"},
{"title": "英制规格", "field": "yzgg"},
{"title": "强度上限", "field": "tqd"},
{"title": "强度下限", "field": "bqd"},
{"title": "包装方式", "field": "bzfs"},
{"title": "订单要求", "field": "ddyq"},
{"title": "备注", "field": "remarks_hb"},
{"title": "包装强度上限", "field": "bz_tqd"},
{"title": "包装强度下限", "field": "bz_bqd"},
{"title": "轴重要求", "field": "zzyq"}
]
# 设置表头
self.result_table.setColumnCount(len(columns))
header_labels = [col["title"] for col in columns]
self.result_table.setHorizontalHeaderLabels(header_labels)
# 设置行数
self.result_table.setRowCount(len(results))
# 填充数据
for row, order_data in enumerate(results):
# 序号
item_index = QTableWidgetItem(str(row + 1))
item_index.setTextAlignment(Qt.AlignCenter)
self.result_table.setItem(row, 0, item_index)
# 遍历列,填充数据
for col_idx, column in enumerate(columns[1:], 1): # 从1开始跳过序号列
field = column["field"]
if field:
value = ""
# 特殊处理某些字段
if field == "rq":
create_time = order_data.get(field, "")
if create_time:
try:
# 尝试解析日期时间字符串
dt = datetime.strptime(create_time, "%Y-%m-%d %H:%M:%S")
value = dt.strftime("%Y-%m-%d")
except:
value = create_time
else:
# 其他普通字段
value = str(order_data.get(field, ""))
# 创建表格项并设置文本
item = QTableWidgetItem(value)
# 居中对齐特定的列
if field in ["rq", "sl", "maxsl", "zx_zl"]:
item.setTextAlignment(Qt.AlignCenter)
# 设置表格项
self.result_table.setItem(row, col_idx, item)
# 存储原始数据到第一列的item中
item_index.setData(Qt.UserRole, order_data)
def on_table_double_clicked(self, row, column):
"""表格双击事件处理"""
self.select_current_row(row)
def on_confirm(self):
"""确认按钮点击事件处理"""
# 获取当前选中行
selected_rows = self.result_table.selectionModel().selectedRows()
if not selected_rows:
QMessageBox.warning(self, "提示", "请选择一个订单")
return
# 获取选中行的索引
row_index = selected_rows[0].row()
self.select_current_row(row_index)
def select_current_row(self, row):
"""选择当前行并返回数据
将订单明细(mo)作为订单号传递给上层组件并自动获取托盘号
"""
if 0 <= row < self.result_table.rowCount():
# 获取存储在item中的原始数据
item = self.result_table.item(row, 0)
if item:
order_data = item.data(Qt.UserRole)
if order_data:
# 修改订单数据将mo字段作为note字段的值
mo_value = order_data.get("mo", "")
if mo_value:
order_data["note"] = mo_value
# 自动获取托盘号
order_code = mo_value
if order_code and self.corp_id:
gc_api = GcApi()
xpack_response = gc_api.get_xpack(order_code, self.corp_id)
if xpack_response and xpack_response.get("status", False):
xpack = xpack_response.get("xpack", "")
spack = xpack_response.get("spack", "")
order_data["xpack"] = xpack
order_data["spack"] = spack
logging.info(f"已获取spack: {spack}已添加到order_data中")
# 发出信号
self.order_selected.emit(order_data)
self.accept()

View File

@ -1,14 +1,17 @@
from PySide6.QtWidgets import QMessageBox
from PySide6.QtWidgets import QMessageBox, QFileDialog, QLineEdit, QTableWidgetItem
from PySide6.QtCore import Qt
from ui.report_dialog_ui import ReportDialogUI
import pandas as pd
from datetime import datetime
import logging
import os
from dao.report_dao import ReportDAO
class ReportDialog(ReportDialogUI):
def __init__(self, parent=None):
super().__init__(parent)
self.init_signals()
self.current_data = [] # 存储当前查询的数据
def init_signals(self):
"""初始化信号连接"""
@ -21,34 +24,59 @@ class ReportDialog(ReportDialogUI):
# 关闭按钮点击事件
self.close_button.clicked.connect(self.close)
# 清空条件按钮点击事件
self.clear_button.clicked.connect(self.on_clear)
def on_clear(self):
"""清空条件按钮点击处理"""
# 重置日期为默认值
self.start_date.setDate(datetime.now().date().addMonths(-1))
self.end_date.setDate(datetime.now().date())
# 清空其他输入框
self.customer_edit.clear()
self.material_edit.clear()
self.spec_edit.clear()
# 清空表格和汇总信息
self.report_table.setRowCount(0)
self.update_summary([])
def on_query(self):
"""查询按钮点击处理"""
try:
# 获取查询条件
start_date = self.start_date.date().toString(Qt.ISODate)
end_date = self.end_date.date().toString(Qt.ISODate)
report_type = self.type_combo.currentText()
start_date = self.start_date.date().toString("yyyy-MM-dd")
end_date = self.end_date.date().toString("yyyy-MM-dd")
customer = self.customer_edit.text().strip()
material = self.material_edit.text().strip()
spec = self.spec_edit.text().strip()
# TODO: 根据条件从数据库查询数据
# 这里需要实现具体的查询逻辑
# 使用ReportDAO获取数据
report_dao = ReportDAO()
data = report_dao.get_production_report(
start_date=start_date,
end_date=end_date,
customer=customer if customer else None,
material=material if material else None,
spec=spec if spec else None
)
# 示例数据
data = [
{
"日期": "2024-03-20",
"工程号": "GC001",
"品名": "产品A",
"规格": "规格1",
"生产数量": 100,
"合格数量": 95,
"不合格数量": 5,
"合格率": "95%"
}
]
# 保存当前数据用于导出
self.current_data = data
# 更新表格显示
self.update_table(data)
# 显示查询结果数量
if data:
if self.statusBar():
self.statusBar().showMessage(f"查询到 {len(data)} 条记录")
else:
if self.statusBar():
self.statusBar().showMessage("未查询到符合条件的记录")
QMessageBox.information(self, "提示", "未查询到符合条件的记录")
except Exception as e:
logging.error(f"查询报表数据失败: {str(e)}")
QMessageBox.warning(self, "错误", f"查询数据失败: {str(e)}")
@ -62,44 +90,91 @@ class ReportDialog(ReportDialogUI):
# 清空表格
self.report_table.setRowCount(0)
if not data:
# 如果没有数据,清空汇总信息
self.update_summary([])
return
# 添加数据行
for row_idx, row_data in enumerate(data):
self.report_table.insertRow(row_idx)
for col_idx, (key, value) in enumerate(row_data.items()):
# 按照表头顺序添加数据
headers = ["日期", "客户", "订单号", "轴数", "材质", "规格", "净重"]
for col_idx, header in enumerate(headers):
value = row_data.get(header, "")
item = QTableWidgetItem(str(value))
item.setTextAlignment(Qt.AlignCenter)
self.report_table.setItem(row_idx, col_idx, item)
# 更新汇总信息
self.update_summary(data)
def update_summary(self, data):
"""更新汇总信息
Args:
data: 包含报表数据的列表
"""
if not data:
# 重置汇总信息
self.total_axes_value.setText("0")
self.total_weight_value.setText("0.00 kg")
self.record_count_value.setText("0")
return
# 计算总轴数
total_axes = sum(row.get("轴数", 0) for row in data)
# 计算总净重
total_weight = sum(float(row.get("净重", 0)) for row in data)
# 计算记录数
record_count = len(data)
# 更新显示
self.total_axes_value.setText(str(total_axes))
self.total_weight_value.setText(f"{total_weight:.2f} kg")
self.record_count_value.setText(str(record_count))
def on_export(self):
"""导出按钮点击处理"""
try:
# 获取表格数据
data = []
for row in range(self.report_table.rowCount()):
row_data = {}
for col in range(self.report_table.columnCount()):
header = self.report_table.horizontalHeaderItem(col).text()
item = self.report_table.item(row, col)
value = item.text() if item else ""
row_data[header] = value
data.append(row_data)
if not data:
if not self.current_data:
QMessageBox.warning(self, "警告", "没有数据可以导出")
return
# 创建DataFrame
df = pd.DataFrame(data)
df = pd.DataFrame(self.current_data)
# 生成文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"统计报表_{timestamp}.xlsx"
# 让用户选择保存路径
default_name = f"统计报表_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
filename, _ = QFileDialog.getSaveFileName(
self, "保存报表", default_name, "Excel 文件 (*.xlsx);;所有文件 (*)"
)
if not filename: # 用户取消了保存
return
# 确保文件名有正确的扩展名
if not filename.endswith('.xlsx'):
filename += '.xlsx'
# 导出到Excel
df.to_excel(filename, index=False, engine='openpyxl')
# 显示成功消息并打开文件所在目录
QMessageBox.information(self, "成功", f"报表已导出到: {filename}")
# 打开文件所在目录
os.system(f'explorer /select,"{os.path.normpath(filename)}"')
except Exception as e:
logging.error(f"导出报表失败: {str(e)}")
QMessageBox.warning(self, "错误", f"导出报表失败: {str(e)}")
def statusBar(self):
"""获取状态栏,如果父窗口有状态栏则使用父窗口的状态栏"""
if self.parent() and hasattr(self.parent(), 'statusBar'):
return self.parent().statusBar()
return None

View File

@ -178,20 +178,13 @@ class UnloadingDialog(UnloadingDialogUI):
main_window = self.parent
if main_window and isinstance(main_window, MainWindow):
# 检查托盘号是否已存在
existed = False
for i in range(main_window.tray_edit.count()):
if main_window.tray_edit.itemText(i) == tray_code:
existed = True
break
# 如果不存在,则添加
if not existed:
logging.info(f"添加托盘号到主窗口: {tray_code}")
main_window.tray_edit.addItem(tray_code)
# 设置当前选中的托盘号
main_window.tray_edit.setCurrentText(tray_code)
# 由于 tray_edit 现在是 QLineEdit不再需要检查是否存在
# 直接设置文本
logging.info(f"设置主窗口当前托盘号: {tray_code}")
main_window.tray_edit.setText(tray_code)
# 手动触发托盘号变更事件
main_window.handle_tray_changed(tray_code)
except Exception as e:
logging.error(f"查询托盘信息异常: {str(e)}")