Compare commits

...

6 Commits

Author SHA1 Message Date
zhu-mengmeng
cc33d9318d Merge branch 'dev' 2025-06-16 09:16:55 +08:00
zhu-mengmeng
e638b7395d 删除__pycache__文件夹并更新.gitignore以忽略这些目录 2025-06-16 09:10:36 +08:00
zhu-mengmeng
a1f9a56781 Merge branch '多数据源配置' into dev 2025-06-16 08:59:17 +08:00
zhu-mengmeng
7b0d0a28f2 完成净重计算 2025-06-14 00:34:40 +08:00
zhu-mengmeng
e9f94db1d6 多数据源配置 2025-06-13 17:14:03 +08:00
zhu-mengmeng
935fd44f78 新增上料下料按钮以及对应功能 2025-06-13 15:57:26 +08:00
27 changed files with 579 additions and 127 deletions

5
.gitignore vendored
View File

@ -11,6 +11,11 @@ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
# 确保所有层级的__pycache__目录都被忽略
**/__pycache__/
*/__pycache__/
*/**/__pycache__/
# IDE相关 # IDE相关
.idea/ .idea/
.vscode/ .vscode/

View File

@ -4,18 +4,34 @@
"version": "1.0.0", "version": "1.0.0",
"features": { "features": {
"enable_serial_ports": false, "enable_serial_ports": false,
"enable_keyboard_listener": true, "enable_keyboard_listener": false,
"enable_camera": false "enable_camera": false
} }
}, },
"database": { "database": {
"type": "sqlite", "default": "sqlite",
"path": "db/jtDB.db", "sources": {
"host": "", "sqlite": {
"port": "", "path": "db/jtDB.db",
"user": "", "description": "默认SQLite数据库"
"password": "", },
"name": "" "postgresql": {
"host": "localhost",
"port": "5432",
"user": "postgres",
"password": "",
"name": "jtDB",
"description": "PostgreSQL数据库"
},
"mysql": {
"host": "localhost",
"port": "3306",
"user": "root",
"password": "",
"name": "jtDB",
"description": "MySQL数据库"
}
}
}, },
"camera": { "camera": {
"enabled": false, "enabled": false,

View File

@ -403,6 +403,7 @@ class InspectionDAO:
tray_id, tray_id,
COALESCE(axis_package_id, '') as axis_package_id, COALESCE(axis_package_id, '') as axis_package_id,
COALESCE(weight, 0) as weight, COALESCE(weight, 0) as weight,
COALESCE(net_weight, 0) as net_weight,
STRFTIME('%Y-%m-%d %H:%M:%S', pack_time) as pack_time STRFTIME('%Y-%m-%d %H:%M:%S', pack_time) as pack_time
FROM inspection_pack_data FROM inspection_pack_data
WHERE tray_id = ? WHERE tray_id = ?
@ -416,7 +417,7 @@ class InspectionDAO:
except Exception as e: except Exception as e:
logging.error(f"获取包装记录失败: {str(e)}") logging.error(f"获取包装记录失败: {str(e)}")
return [] return []
def save_package_record(self, order_id, tray_id, label_value, weight_value, finish_time): def save_package_record(self, order_id, tray_id, label_value, weight_value,net_weight_value, finish_time):
"""保存包装记录 """保存包装记录
Args: Args:
@ -429,10 +430,10 @@ class InspectionDAO:
# TODO调用接口获取到工程号对应的其他信息比如材质规格后续完成 # TODO调用接口获取到工程号对应的其他信息比如材质规格后续完成
try: try:
sql = """ sql = """
INSERT INTO inspection_pack_data (order_id, tray_id, axis_package_id, weight, pack_time, create_time, create_by, update_time, update_by, is_deleted) INSERT INTO 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)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""" """
params = (order_id, tray_id, label_value, weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False) params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False)
self.db.cursor.execute(sql, params) self.db.cursor.execute(sql, params)
self.db.conn.commit() self.db.conn.commit()
except Exception as e: except Exception as e:

Binary file not shown.

View File

@ -2,12 +2,12 @@ from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('localhost', port=5020) client = ModbusTcpClient('localhost', port=5020)
client.connect() client.connect()
client.write_registers(address=11, values=[112]) # client.write_registers(address=11, values=[110])
# client.write_registers(address=6, values=[1]) # client.write_registers(address=6, values=[1])
# client.write_registers(address=5, values=[16]) # client.write_registers(address=5, values=[16])
# client.write_registers(address=13, values=[1]) client.write_registers(address=13, values=[1])
result = client.read_holding_registers(address=4, count=1) result = client.read_holding_registers(address=13, count=1)
print(result.registers[0],"123===") print(result.registers[0],"123===")
client.close() client.close()

12
main.py
View File

@ -179,8 +179,13 @@ def main():
# 创建db目录如果不存在 # 创建db目录如果不存在
os.makedirs('db', exist_ok=True) os.makedirs('db', exist_ok=True)
# 从配置获取SQLite数据库路径
config_loader = ConfigLoader.get_instance()
sqlite_config = config_loader.get_database_config('sqlite')
sqlite_db_path = sqlite_config.get('path', 'db/jtDB.db')
# 检查数据库是否存在,如果不存在则初始化 # 检查数据库是否存在,如果不存在则初始化
if not os.path.exists('db/jtDB.db'): if not os.path.exists(sqlite_db_path):
from utils.init_db import init_database from utils.init_db import init_database
init_database() init_database()
logging.info("初始化数据库完成") logging.info("初始化数据库完成")
@ -190,6 +195,11 @@ def main():
exit_code = app.exec() exit_code = app.exec()
logging.info(f"应用程序退出,退出码: {exit_code}") logging.info(f"应用程序退出,退出码: {exit_code}")
# 关闭所有数据库连接
from utils.sql_utils import SQLUtils
SQLUtils.close_all_connections()
sys.exit(exit_code) sys.exit(exit_code)
except Exception as e: except Exception as e:

View File

@ -285,13 +285,13 @@ class MainWindowUI(QMainWindow):
} }
""" """
# self.input_button = QPushButton("上料") self.input_button = QPushButton("上料")
# self.input_button.setFont(self.normal_font) self.input_button.setFont(self.normal_font)
# self.input_button.setStyleSheet(button_style + "background-color: #e3f2fd; border: 1px solid #2196f3;") self.input_button.setStyleSheet(button_style + "background-color: #e3f2fd; border: 1px solid #2196f3;")
# self.output_button = QPushButton("下料") self.output_button = QPushButton("下料")
# self.output_button.setFont(self.normal_font) self.output_button.setFont(self.normal_font)
# self.output_button.setStyleSheet(button_style + "background-color: #fff8e1; border: 1px solid #ffc107;") self.output_button.setStyleSheet(button_style + "background-color: #fff8e1; border: 1px solid #ffc107;")
self.start_button = QPushButton("开始") self.start_button = QPushButton("开始")
self.start_button.setFont(self.normal_font) self.start_button.setFont(self.normal_font)
@ -302,10 +302,10 @@ class MainWindowUI(QMainWindow):
self.stop_button.setStyleSheet(button_style + "background-color: #ffebee; border: 1px solid #f44336;") self.stop_button.setStyleSheet(button_style + "background-color: #ffebee; border: 1px solid #f44336;")
# 使用网格布局排列按钮 # 使用网格布局排列按钮
# self.button_layout.addWidget(self.input_button, 0, 0) self.button_layout.addWidget(self.input_button, 0, 0)
# self.button_layout.addWidget(self.output_button, 0, 1) self.button_layout.addWidget(self.output_button, 0, 1)
self.button_layout.addWidget(self.start_button, 0, 1) self.button_layout.addWidget(self.start_button, 0, 2)
self.button_layout.addWidget(self.stop_button, 0, 2) self.button_layout.addWidget(self.stop_button, 0, 3)
self.control_layout.addWidget(self.button_container) self.control_layout.addWidget(self.button_container)
@ -501,13 +501,13 @@ class MainWindowUI(QMainWindow):
self.record_layout.addWidget(self.record_title) self.record_layout.addWidget(self.record_title)
# 创建表格 # 创建表格
self.record_table = QTableWidget(14, 8) # 14行7列:序号、订单、品名、规格、托号、轴包装号、重量 self.record_table = QTableWidget(14, 9) # 14行9列:序号、订单、品名、规格、托号、轴包装号、重量、净重、完成时间
# 应用通用表格设置 # 应用通用表格设置
self.setup_table_common(self.record_table) self.setup_table_common(self.record_table)
# 设置列标题 # 设置列标题
record_headers = ["序号", "订单", "品名", "规格", "托号", "轴包装号", "重量", "完成时间"] record_headers = ["序号", "订单", "品名", "规格", "托号", "轴包装号", "毛重", "净重", "完成时间"]
for col, header in enumerate(record_headers): for col, header in enumerate(record_headers):
self.record_table.setItem(0, col, self.create_header_item(header)) self.record_table.setItem(0, col, self.create_header_item(header))
@ -520,7 +520,7 @@ class MainWindowUI(QMainWindow):
self.record_table.setRowHeight(row, 35) self.record_table.setRowHeight(row, 35)
# 设置列宽 # 设置列宽
column_widths = [70, 220, 160, 160, 160, 160, 160, 190] # 各列的默认宽度 column_widths = [70, 200, 130, 130, 160, 120, 120, 120, 160] # 各列的默认宽度
for col, width in enumerate(column_widths): for col, width in enumerate(column_widths):
self.record_table.setColumnWidth(col, width) self.record_table.setColumnWidth(col, width)
@ -593,7 +593,7 @@ class MainWindowUI(QMainWindow):
self.inspection_headers = self.inspection_headers[:columns] self.inspection_headers = self.inspection_headers[:columns]
# 计算总列数 # 计算总列数
total_columns = 2 + self.inspection_columns + 2 # 上料2列 + 检验N列 + 包装2 total_columns = 2 + self.inspection_columns + 3 # 上料2列 + 检验N列 + 包装3
self.process_table.setColumnCount(total_columns) self.process_table.setColumnCount(total_columns)
# 重新设置列宽 # 重新设置列宽
@ -614,9 +614,9 @@ class MainWindowUI(QMainWindow):
self.process_table.setSpan(0, 2, 1, self.inspection_columns) self.process_table.setSpan(0, 2, 1, self.inspection_columns)
self.process_table.setItem(0, 2, self.create_header_item("检验")) self.process_table.setItem(0, 2, self.create_header_item("检验"))
# 包装区域(2列) # 包装区域(3列)
packaging_start_col = 2 + self.inspection_columns packaging_start_col = 2 + self.inspection_columns
self.process_table.setSpan(0, packaging_start_col, 1, 2) self.process_table.setSpan(0, packaging_start_col, 1, 3)
self.process_table.setItem(0, packaging_start_col, self.create_header_item("包装")) self.process_table.setItem(0, packaging_start_col, self.create_header_item("包装"))
# 第二行:列标题 # 第二行:列标题
@ -636,7 +636,7 @@ class MainWindowUI(QMainWindow):
self.process_table.setItem(1, 2 + i, self.create_header_item(header_text)) self.process_table.setItem(1, 2 + i, self.create_header_item(header_text))
# 包装区域列标题 # 包装区域列标题
packaging_headers = ["贴标", ""] packaging_headers = ["贴标", "毛重", ""]
for i, header in enumerate(packaging_headers): for i, header in enumerate(packaging_headers):
self.process_table.setItem(1, packaging_start_col + i, self.create_header_item(header)) self.process_table.setItem(1, packaging_start_col + i, self.create_header_item(header))
@ -653,4 +653,5 @@ class MainWindowUI(QMainWindow):
# 包装区域列宽 # 包装区域列宽
packaging_start_col = 2 + self.inspection_columns packaging_start_col = 2 + self.inspection_columns
self.process_table.setColumnWidth(packaging_start_col, 140) # 贴标 self.process_table.setColumnWidth(packaging_start_col, 140) # 贴标
self.process_table.setColumnWidth(packaging_start_col + 1, 140) # 称重 self.process_table.setColumnWidth(packaging_start_col + 1, 140) # 毛重
self.process_table.setColumnWidth(packaging_start_col + 2, 140) # 净重

View File

@ -205,25 +205,31 @@ class SettingsUI(QWidget):
self.database_layout = QVBoxLayout(self.database_tab) self.database_layout = QVBoxLayout(self.database_tab)
self.database_layout.setContentsMargins(20, 20, 20, 20) self.database_layout.setContentsMargins(20, 20, 20, 20)
# 数据类型选择 # 数据类型选择
self.db_type_group = QGroupBox("数据类型") self.db_type_group = QGroupBox("数据类型")
self.db_type_group.setFont(self.normal_font) self.db_type_group.setFont(self.normal_font)
self.db_type_layout = QHBoxLayout() self.db_type_layout = QHBoxLayout()
self.sqlite_radio = QCheckBox("SQLite") self.db_type_combo = QComboBox()
self.sqlite_radio.setFont(self.normal_font) self.db_type_combo.setFont(self.normal_font)
self.sqlite_radio.setChecked(True) self.db_type_combo.addItem("SQLite")
self.db_type_combo.addItem("PostgreSQL")
self.db_type_combo.addItem("MySQL")
self.pgsql_radio = QCheckBox("PostgreSQL") self.db_type_layout.addWidget(QLabel("当前配置类型:"))
self.pgsql_radio.setFont(self.normal_font) self.db_type_layout.addWidget(self.db_type_combo)
self.mysql_radio = QCheckBox("MySQL")
self.mysql_radio.setFont(self.normal_font)
self.db_type_layout.addWidget(self.sqlite_radio)
self.db_type_layout.addWidget(self.pgsql_radio)
self.db_type_layout.addWidget(self.mysql_radio)
self.db_type_layout.addStretch(1) self.db_type_layout.addStretch(1)
# 添加当前使用的数据源选择
self.current_source_label = QLabel("当前使用的数据源:")
self.current_source_label.setFont(self.normal_font)
self.current_source_combo = QComboBox()
self.current_source_combo.setFont(self.normal_font)
self.current_source_combo.addItems(["SQLite", "PostgreSQL", "MySQL"])
self.db_type_layout.addWidget(self.current_source_label)
self.db_type_layout.addWidget(self.current_source_combo)
self.db_type_group.setLayout(self.db_type_layout) self.db_type_group.setLayout(self.db_type_layout)
self.database_layout.addWidget(self.db_type_group) self.database_layout.addWidget(self.db_type_group)

View File

@ -32,6 +32,9 @@ class ConfigLoader:
with open(self.config_file, 'r', encoding='utf-8') as f: with open(self.config_file, 'r', encoding='utf-8') as f:
self.config = json.load(f) self.config = json.load(f)
logging.info(f"已加载配置文件: {self.config_file}") logging.info(f"已加载配置文件: {self.config_file}")
# 检查并升级配置文件结构(兼容旧版本的配置)
self._upgrade_config_if_needed()
else: else:
# 创建默认配置 # 创建默认配置
self.config = { self.config = {
@ -40,17 +43,50 @@ class ConfigLoader:
"version": "1.0.0", "version": "1.0.0",
"features": { "features": {
"enable_serial_ports": False, "enable_serial_ports": False,
"enable_keyboard_listener": False "enable_keyboard_listener": False,
"enable_camera": False
} }
}, },
"database": { "database": {
"type": "sqlite", "current": "sqlite",
"path": "db/jtDB.db", "sources": {
"host": "", "sqlite": {
"port": "", "path": "db/jtDB.db",
"user": "", "description": "默认SQLite数据库"
"password": "", },
"name": "" "postgresql": {
"host": "localhost",
"port": "5432",
"user": "postgres",
"password": "",
"name": "jtDB",
"description": ""
},
"mysql": {
"host": "localhost",
"port": "3306",
"user": "root",
"password": "",
"name": "jtDB",
"description": ""
}
}
},
"camera": {
"enabled": False,
"default_exposure": 20000,
"default_gain": 10,
"default_framerate": 30
},
"modbus": {
"host": "localhost",
"port": "5020"
},
"serial": {
"keyboard": {
"trigger_key": "Key.page_up",
"enabled": False
}
} }
} }
@ -66,11 +102,87 @@ class ConfigLoader:
"version": "1.0.0", "version": "1.0.0",
"features": { "features": {
"enable_serial_ports": False, "enable_serial_ports": False,
"enable_keyboard_listener": False "enable_keyboard_listener": False,
"enable_camera": False
}
},
"database": {
"current": "sqlite",
"sources": {
"sqlite": {
"path": "db/jtDB.db",
"description": "默认SQLite数据库"
}
} }
} }
} }
def _upgrade_config_if_needed(self):
"""升级配置文件结构(兼容旧版本的配置)"""
try:
# 检查数据库配置是否需要升级
if 'database' in self.config:
db_config = self.config['database']
# 旧版本配置结构检查
if 'type' in db_config and 'sources' not in db_config:
logging.info("检测到旧版本的数据库配置,正在升级...")
# 获取旧配置
db_type = db_config.get('type', 'sqlite')
# 创建新的配置结构
new_db_config = {
"default": db_type,
"sources": {}
}
# 转移旧配置到新结构
if db_type == 'sqlite':
new_db_config['sources']['sqlite'] = {
"path": db_config.get('path', 'db/jtDB.db'),
"description": "从旧版本升级的SQLite数据库"
}
else:
new_db_config['sources'][db_type] = {
"host": db_config.get('host', 'localhost'),
"port": db_config.get('port', '5432' if db_type == 'postgresql' else '3306'),
"user": db_config.get('user', ''),
"password": db_config.get('password', ''),
"name": db_config.get('name', 'jtDB'),
"description": f"从旧版本升级的{db_type}数据库"
}
# 确保至少有一个sqlite配置
if 'sqlite' not in new_db_config['sources']:
new_db_config['sources']['sqlite'] = {
"path": "db/jtDB.db",
"description": "默认SQLite数据库"
}
# 更新配置
self.config['database'] = new_db_config
self.save_config()
logging.info("数据库配置已成功升级")
# 将current改为default
if 'current' in db_config and 'default' not in db_config:
logging.info("将数据库配置中的'current'改为'default'...")
db_config['default'] = db_config.pop('current')
self.save_config()
# 将pgsql改为postgresql
if 'sources' in db_config and 'pgsql' in db_config['sources'] and 'postgresql' not in db_config['sources']:
logging.info("将数据库配置中的'pgsql'改为'postgresql'...")
db_config['sources']['postgresql'] = db_config['sources'].pop('pgsql')
# 如果默认值是pgsql也更新
if db_config.get('default') == 'pgsql':
db_config['default'] = 'postgresql'
self.save_config()
except Exception as e:
logging.error(f"升级配置文件结构失败: {e}")
def save_config(self): def save_config(self):
"""保存配置到文件""" """保存配置到文件"""
try: try:
@ -163,4 +275,25 @@ class ConfigLoader:
self.config['serial'][key] = config_data self.config['serial'][key] = config_data
# 这里不保存配置等待调用save_config方法时一并保存 # 这里不保存配置等待调用save_config方法时一并保存
return True return True
def get_database_config(self, db_type=None):
"""
获取指定类型的数据库配置
Args:
db_type: 数据库类型'sqlite', 'postgresql', 'mysql'不指定则使用默认配置
Returns:
dict: 数据库配置
"""
# 如果未指定数据库类型,使用当前设置的类型
if db_type is None:
db_type = self.get_value('database.default', 'sqlite')
# 处理pgsql和postgresql兼容
if db_type == 'pgsql':
db_type = 'postgresql'
# 获取数据库配置
return self.get_value(f'database.sources.{db_type}', {})

View File

@ -2,9 +2,20 @@ from utils.sql_utils import SQLUtils
import datetime import datetime
import os import os
import logging import logging
from utils.config_loader import ConfigLoader
def init_database(): def init_database():
db = SQLUtils('sqlite', database='db/jtDB.db') # 获取SQLite数据源的路径
config_loader = ConfigLoader.get_instance()
sqlite_config = config_loader.get_database_config('sqlite')
db_path = sqlite_config.get('path', 'db/jtDB.db')
# 确保db目录存在
db_dir = os.path.dirname(db_path)
os.makedirs(db_dir, exist_ok=True)
logging.info(f"初始化数据库: {db_path}")
db = SQLUtils('sqlite', database=db_path)
try: try:
db.begin_transaction() db.begin_transaction()

View File

@ -1,4 +1,6 @@
import sys import sys
import logging
from utils.config_loader import ConfigLoader
try: try:
import psycopg2 import psycopg2
@ -10,27 +12,127 @@ try:
except ImportError: except ImportError:
sqlite3 = None sqlite3 = None
try:
import mysql.connector
except ImportError:
mysql = None
class SQLUtils: class SQLUtils:
def __init__(self, db_type, **kwargs): # 存储连接池,避免重复创建连接
self.db_type = db_type.lower() _connection_pool = {}
def __init__(self, db_type=None, source_name=None, **kwargs):
"""初始化SQLUtils对象
Args:
db_type: 数据库类型 'sqlite', 'postgresql', 'mysql' 之一如果为None则使用配置文件中的默认数据源
source_name: 数据源名称用于从配置中获取特定的数据源'sqlite', 'postgresql', 'mysql'
**kwargs: 连接参数如果没有提供则使用配置文件中的参数
"""
self.conn = None self.conn = None
self.cursor = None self.cursor = None
# 如果指定了source_name直接使用该名称的数据源配置
if source_name:
config_loader = ConfigLoader.get_instance()
db_config = config_loader.get_value(f'database.sources.{source_name}', {})
if not db_config:
raise ValueError(f"未找到数据源配置: {source_name}")
db_type = source_name # 数据源名称同时也是类型
if not kwargs: # 如果没有提供连接参数,则使用配置中的参数
if source_name == 'sqlite':
kwargs = {'database': db_config.get('path', 'db/jtDB.db')}
else:
kwargs = {
'host': db_config.get('host', 'localhost'),
'user': db_config.get('user', ''),
'password': db_config.get('password', ''),
'database': db_config.get('name', 'jtDB')
}
if 'port' in db_config and db_config['port']:
kwargs['port'] = int(db_config['port'])
# 如果没有指定数据库类型和数据源名称,则使用配置中的默认数据源
elif db_type is None:
config_loader = ConfigLoader.get_instance()
default_source = config_loader.get_value('database.default', 'sqlite')
# 如果没有提供连接参数,则从配置文件获取
if not kwargs:
db_config = config_loader.get_database_config(default_source)
if default_source == 'sqlite':
kwargs = {'database': db_config.get('path', 'db/jtDB.db')}
else:
kwargs = {
'host': db_config.get('host', 'localhost'),
'user': db_config.get('user', ''),
'password': db_config.get('password', ''),
'database': db_config.get('name', 'jtDB')
}
if 'port' in db_config and db_config['port']:
kwargs['port'] = int(db_config['port'])
db_type = default_source
self.db_type = db_type.lower()
self.kwargs = kwargs self.kwargs = kwargs
self.source_name = source_name or self.db_type
# 尝试从连接池获取连接,如果没有则创建新连接
self._get_connection()
def _get_connection(self):
"""从连接池获取连接,如果没有则创建新连接"""
# 创建连接键,包含数据库类型和连接参数
conn_key = f"{self.db_type}:{str(self.kwargs)}"
# 检查连接池中是否已有此连接
if conn_key in SQLUtils._connection_pool:
try:
# 尝试执行简单查询,确认连接有效
conn, cursor = SQLUtils._connection_pool[conn_key]
cursor.execute("SELECT 1")
# 连接有效,直接使用
self.conn = conn
self.cursor = cursor
return
except Exception:
# 连接已失效,从连接池移除
del SQLUtils._connection_pool[conn_key]
# 创建新连接
self.connect() self.connect()
# 将新连接添加到连接池
if self.conn and self.cursor:
SQLUtils._connection_pool[conn_key] = (self.conn, self.cursor)
def connect(self): def connect(self):
if self.db_type == 'pgsql' or self.db_type == 'postgresql': """连接到数据库"""
if not psycopg2: try:
raise ImportError('psycopg2 is not installed') if self.db_type in ['pgsql', 'postgresql']:
self.conn = psycopg2.connect(**self.kwargs) if not psycopg2:
elif self.db_type == 'sqlite' or self.db_type == 'sqlite3': raise ImportError('psycopg2 is not installed')
if not sqlite3: self.conn = psycopg2.connect(**self.kwargs)
raise ImportError('sqlite3 is not installed') elif self.db_type in ['sqlite', 'sqlite3']:
self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:')) if not sqlite3:
else: raise ImportError('sqlite3 is not installed')
raise ValueError(f'Unsupported db_type: {self.db_type}') self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:'))
self.cursor = self.conn.cursor() elif self.db_type == 'mysql':
if not mysql:
raise ImportError('mysql.connector is not installed')
self.conn = mysql.connector.connect(**self.kwargs)
else:
raise ValueError(f'不支持的数据库类型: {self.db_type}')
self.cursor = self.conn.cursor()
logging.debug(f"成功连接到数据库: {self.db_type}")
except Exception as e:
logging.error(f"连接数据库失败: {e}")
raise
def execute_query(self, sql, params=None): def execute_query(self, sql, params=None):
if params is None: if params is None:
@ -72,7 +174,36 @@ class SQLUtils:
return self.cursor.fetchall() return self.cursor.fetchall()
def close(self): def close(self):
if self.cursor: """关闭连接(实际上是将连接返回到连接池)"""
self.cursor.close() # 这里不再实际关闭连接,让连接池管理连接生命周期
if self.conn: pass
self.conn.close()
@staticmethod
def close_all_connections():
"""关闭所有连接池中的连接"""
for conn, cursor in SQLUtils._connection_pool.values():
try:
if cursor:
cursor.close()
if conn:
conn.close()
except Exception as e:
logging.error(f"关闭数据库连接失败: {e}")
SQLUtils._connection_pool.clear()
logging.info("已关闭所有数据库连接")
@staticmethod
def get_sqlite_connection():
"""获取SQLite连接"""
return SQLUtils(source_name='sqlite')
@staticmethod
def get_postgresql_connection():
"""获取PostgreSQL连接"""
return SQLUtils(source_name='postgresql')
@staticmethod
def get_mysql_connection():
"""获取MySQL连接"""
return SQLUtils(source_name='mysql')

View File

@ -10,7 +10,8 @@ import threading
def check_user_login(user_id, password): def check_user_login(user_id, password):
"""验证用户登录""" """验证用户登录"""
try: try:
db = SQLUtils('sqlite', database='db/jtDB.db') # 始终使用SQLite数据源验证登录
db = SQLUtils(source_name='sqlite')
db.execute_query("SELECT id FROM user WHERE username = ? AND password = ? AND is_deleted = 0", (user_id, password)) db.execute_query("SELECT id FROM user WHERE username = ? AND password = ? AND is_deleted = 0", (user_id, password))
result = db.fetchone() result = db.fetchone()
db.close() db.close()
@ -22,7 +23,8 @@ def check_user_login(user_id, password):
def get_user_info(user_id): def get_user_info(user_id):
"""获取用户信息""" """获取用户信息"""
try: try:
db = SQLUtils('sqlite', database='db/jtDB.db') # 始终使用SQLite数据源获取用户信息
db = SQLUtils(source_name='sqlite')
db.execute_query("SELECT username, 'Default Corp', 1, 1 FROM user WHERE username = ?", (user_id,)) db.execute_query("SELECT username, 'Default Corp', 1, 1 FROM user WHERE username = ?", (user_id,))
result = db.fetchone() result = db.fetchone()
db.close() db.close()

View File

@ -243,8 +243,8 @@ class MainWindow(MainWindowUI):
self.tray_edit.activated.connect(self.load_finished_inspection_data) # 当用户选择一项时触发 self.tray_edit.activated.connect(self.load_finished_inspection_data) # 当用户选择一项时触发
# 连接按钮事件 # 连接按钮事件
# self.input_button.clicked.connect(self.handle_input) self.input_button.clicked.connect(self.handle_input)
# self.output_button.clicked.connect(self.handle_output) self.output_button.clicked.connect(self.handle_output)
self.start_button.clicked.connect(self.handle_start) self.start_button.clicked.connect(self.handle_start)
self.stop_button.clicked.connect(self.handle_stop) self.stop_button.clicked.connect(self.handle_stop)
@ -753,7 +753,7 @@ class MainWindow(MainWindowUI):
inspection_dao.save_inspection_data(order_id, data) inspection_dao.save_inspection_data(order_id, data)
# 为贴标和称重也创建空记录 # 为贴标和称重也创建空记录
for position in [11, 12]: # 11是贴标12是称 for position in [11, 12, 13]: # 11是贴标12是毛重13是净
data = [{ data = [{
'position': position, 'position': position,
'config_id': position, 'config_id': position,
@ -878,13 +878,21 @@ class MainWindow(MainWindowUI):
self.save_inspection_data(order_id, tray_id, 11, 11, value, status) self.save_inspection_data(order_id, tray_id, 11, 11, value, status)
elif column == packaging_start_col + 1: elif column == packaging_start_col + 1:
# 重列 # 重列
data_type = "" data_type = ""
self.statusBar().showMessage(f"正在保存称重数据: {value}", 1000) self.statusBar().showMessage(f"正在保存称重数据: {value}", 1000)
# 设置单元格颜色为通过 # 设置单元格颜色为通过
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色 cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
# 保存重数据position和config_id都是12 # 保存重数据position和config_id都是12
self.save_inspection_data(order_id, tray_id, 12, 12, value, status) self.save_inspection_data(order_id, tray_id, 12, 12, value, status)
elif column == packaging_start_col + 2:
# 净重列
data_type = "净重"
self.statusBar().showMessage(f"正在保存净重数据: {value}", 1000)
# 设置单元格颜色为通过
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
# 保存净重数据position和config_id都是13
self.save_inspection_data(order_id, tray_id, 13, 13, value, status)
# 记录详细日志 # 记录详细日志
logging.info(f"处理单元格变更: 行={row}, 列={column}, 类型={data_type}, 工程号={order_id}, 值={value}, 状态={status}") logging.info(f"处理单元格变更: 行={row}, 列={column}, 类型={data_type}, 工程号={order_id}, 值={value}, 状态={status}")
@ -1090,6 +1098,10 @@ class MainWindow(MainWindowUI):
# 称重列索引 = 2(序号和工程号) + 检验列数 + 1(贴标) # 称重列索引 = 2(序号和工程号) + 检验列数 + 1(贴标)
weight_col = 2 + len(enabled_configs) + 1 weight_col = 2 + len(enabled_configs) + 1
self.process_table.setItem(row_idx, weight_col, QTableWidgetItem(str(value))) self.process_table.setItem(row_idx, weight_col, QTableWidgetItem(str(value)))
elif position == 13: # 净重
# 净重列索引 = 2(序号和工程号) + 检验列数 + 2(贴标和称重)
net_weight_col = 2 + len(enabled_configs) + 2
self.process_table.setItem(row_idx, net_weight_col, QTableWidgetItem(str(value)))
row_idx += 1 row_idx += 1
# 设置表格为可编辑状态 # 设置表格为可编辑状态
@ -1129,12 +1141,14 @@ class MainWindow(MainWindowUI):
# 从检验数据中获取贴标和称重数据 # 从检验数据中获取贴标和称重数据
label_value = "" label_value = ""
weight_value = "" weight_value = ""
net_weight_value = ""
for item in inspection_data: for item in inspection_data:
if item['position'] == 11: # 贴标 if item['position'] == 11: # 贴标
label_value = item['value'] label_value = item['value']
elif item['position'] == 12: # 称重 elif item['position'] == 12: # 称重
weight_value = item['value'] weight_value = item['value']
elif item['position'] == 13: # 净重
net_weight_value = item['value']
# 只要贴标字段有值,就可以写入包装记录 # 只要贴标字段有值,就可以写入包装记录
if not label_value: if not label_value:
@ -1152,7 +1166,7 @@ class MainWindow(MainWindowUI):
finish_time = datetime.now() finish_time = datetime.now()
# 将数据写入到数据库表 inspection_pack_data # 将数据写入到数据库表 inspection_pack_data
inspection_dao.save_package_record(order_id, tray_id, label_value, weight_value, finish_time) inspection_dao.save_package_record(order_id, tray_id, label_value, weight_value,net_weight_value, finish_time)
# 回显数据 # 回显数据
self.show_pack_item() self.show_pack_item()
@ -1223,10 +1237,15 @@ class MainWindow(MainWindowUI):
weight_item.setTextAlignment(Qt.AlignCenter) weight_item.setTextAlignment(Qt.AlignCenter)
self.record_table.setItem(row_index, 6, weight_item) self.record_table.setItem(row_index, 6, weight_item)
# 净重 - 第8列
net_weight_item = QTableWidgetItem(str(item[6]))
net_weight_item.setTextAlignment(Qt.AlignCenter)
self.record_table.setItem(row_index, 7, net_weight_item)
# 包装时间 # 包装时间
pack_time = QTableWidgetItem(str(item[6])) pack_time = QTableWidgetItem(str(item[7]))
weight_item.setTextAlignment(Qt.AlignCenter) pack_time.setTextAlignment(Qt.AlignCenter)
self.record_table.setItem(row_index, 7, pack_time) self.record_table.setItem(row_index, 8, pack_time)
# 更新包装记录统计数据 # 更新包装记录统计数据
self.update_package_statistics() self.update_package_statistics()
def update_package_statistics(self): def update_package_statistics(self):
@ -1495,6 +1514,8 @@ class MainWindow(MainWindowUI):
# 计算称重列索引 - 称重位置在检验列之后的第二列(贴标后面) # 计算称重列索引 - 称重位置在检验列之后的第二列(贴标后面)
weight_col = 2 + len(enabled_configs) + 1 weight_col = 2 + len(enabled_configs) + 1
# 计算净重列索引 - 净重位置在检验列之后的第三列(称重后面)
net_weight_col = 2 + len(enabled_configs) + 2
# 获取当前选中的行或第一个数据行 # 获取当前选中的行或第一个数据行
current_row = self.process_table.currentRow() current_row = self.process_table.currentRow()
@ -1531,6 +1552,15 @@ class MainWindow(MainWindowUI):
tray_id = self.tray_edit.currentText() tray_id = self.tray_edit.currentText()
self.save_inspection_data(order_id, tray_id, 12, 12, str(weight), "pass") self.save_inspection_data(order_id, tray_id, 12, 12, str(weight), "pass")
# 保存净重到数据库(毛重-工字轮重量TODO 先默认工字轮重量为10g后续从接口获取
net_weight = weight - 10
self.save_inspection_data(order_id, tray_id, 13, 13, str(net_weight), "pass")
# 设置净重单元格
net_weight_item = QTableWidgetItem(str(net_weight))
net_weight_item.setTextAlignment(Qt.AlignCenter)
self.process_table.setItem(data_row, net_weight_col, net_weight_item)
# 重新连接信号 # 重新连接信号
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)

View File

@ -1,9 +1,12 @@
from PySide6.QtWidgets import QMessageBox, QVBoxLayout from PySide6.QtWidgets import QMessageBox, QVBoxLayout
import logging import logging
import json
import os
from ui.settings_ui import SettingsUI from ui.settings_ui import SettingsUI
from utils.sql_utils import SQLUtils from utils.sql_utils import SQLUtils
from widgets.inspection_settings_widget import InspectionSettingsWidget from widgets.inspection_settings_widget import InspectionSettingsWidget
from widgets.pallet_type_settings_widget import PalletTypeSettingsWidget from widgets.pallet_type_settings_widget import PalletTypeSettingsWidget
from utils.config_loader import ConfigLoader
class SettingsWidget(SettingsUI): class SettingsWidget(SettingsUI):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -52,19 +55,23 @@ class SettingsWidget(SettingsUI):
else: else:
logging.error("无法找到pallet_type_layout布局") logging.error("无法找到pallet_type_layout布局")
# 加载配置文件
self.config_loader = ConfigLoader.get_instance()
# 连接信号和槽 # 连接信号和槽
self.connect_signals() self.connect_signals()
# 初始化数据库类型UI状态 # 初始化数据库类型UI状态
self.update_db_ui_state() self.load_db_config()
logging.info("SettingsWidget初始化完成") logging.info("SettingsWidget初始化完成")
def connect_signals(self): def connect_signals(self):
# 数据库类型选择 # 数据库类型选择
self.sqlite_radio.toggled.connect(self.update_db_ui_state) self.db_type_combo.currentTextChanged.connect(self.update_db_ui_state)
self.pgsql_radio.toggled.connect(self.update_db_ui_state)
self.mysql_radio.toggled.connect(self.update_db_ui_state) # 更新当前使用的数据源
self.current_source_combo.currentTextChanged.connect(self.update_default_source)
# 按钮动作 # 按钮动作
self.test_conn_button.clicked.connect(self.test_connection) self.test_conn_button.clicked.connect(self.test_connection)
@ -80,10 +87,67 @@ class SettingsWidget(SettingsUI):
if hasattr(self, 'back_button'): if hasattr(self, 'back_button'):
self.back_button.clicked.connect(self.back_to_main) self.back_button.clicked.connect(self.back_to_main)
def load_db_config(self):
"""加载数据库配置"""
try:
# 获取默认数据源
default_source = self.config_loader.get_value('database.default', 'sqlite').lower()
# 设置当前使用的数据源组合框
self._update_source_combo_items() # 更新组合框项目
index = self.current_source_combo.findText(default_source.capitalize(), Qt.MatchFixedString)
if index >= 0:
self.current_source_combo.setCurrentIndex(index)
# 默认选择当前使用的数据源类型
index = self.db_type_combo.findText(default_source.capitalize(), Qt.MatchFixedString)
if index >= 0:
self.db_type_combo.setCurrentIndex(index)
# 更新UI状态
self.update_db_ui_state()
except Exception as e:
logging.error(f"加载数据库配置失败: {e}")
def _update_source_combo_items(self):
"""更新数据源下拉框项目"""
try:
# 清空当前项目
self.current_source_combo.clear()
# 获取所有配置的数据源
sources = self.config_loader.get_value('database.sources', {})
# 添加数据源到下拉框
for source_name in sources.keys():
# 处理特殊情况postgresql显示为PostgreSQL
display_name = source_name.capitalize()
self.current_source_combo.addItem(display_name, source_name)
except Exception as e:
logging.error(f"更新数据源下拉框失败: {e}")
# 添加默认项
self.current_source_combo.addItem("SQLite", "sqlite")
def update_db_ui_state(self): def update_db_ui_state(self):
"""根据选择的数据库类型更新UI状态""" """根据选择的数据库类型更新UI状态"""
if self.sqlite_radio.isChecked(): db_type = self.db_type_combo.currentText().lower()
# SQLite模式下只需要数据库文件路径
# 处理特殊情况PostgreSQL对应postgresql
if db_type == "postgresql":
db_type = "postgresql"
# 加载选定类型的数据源配置
config_path = f"database.sources.{db_type}"
db_config = {}
if db_type == "sqlite":
# SQLite模式下只需要数据库文件路径和说明
path = self.config_loader.get_value(f"{config_path}.path", "db/jtDB.db")
description = self.config_loader.get_value(f"{config_path}.description", "")
self.host_input.setEnabled(False) self.host_input.setEnabled(False)
self.host_input.setText("") self.host_input.setText("")
self.user_input.setEnabled(False) self.user_input.setEnabled(False)
@ -93,41 +157,60 @@ class SettingsWidget(SettingsUI):
self.port_input.setEnabled(False) self.port_input.setEnabled(False)
self.port_input.setText("") self.port_input.setText("")
self.database_input.setEnabled(True) self.database_input.setEnabled(True)
self.database_input.setText("db/jtDB.db") self.database_input.setText(path)
elif self.pgsql_radio.isChecked(): self.desc_input.setText(description)
elif db_type == "postgresql":
# PostgreSQL模式下需要完整的连接信息 # PostgreSQL模式下需要完整的连接信息
host = self.config_loader.get_value(f"{config_path}.host", "localhost")
port = self.config_loader.get_value(f"{config_path}.port", "5432")
user = self.config_loader.get_value(f"{config_path}.user", "postgres")
password = self.config_loader.get_value(f"{config_path}.password", "")
name = self.config_loader.get_value(f"{config_path}.name", "jtDB")
description = self.config_loader.get_value(f"{config_path}.description", "")
self.host_input.setEnabled(True) self.host_input.setEnabled(True)
self.host_input.setText("localhost") self.host_input.setText(host)
self.user_input.setEnabled(True) self.user_input.setEnabled(True)
self.user_input.setText("postgres") self.user_input.setText(user)
self.password_input.setEnabled(True) self.password_input.setEnabled(True)
self.password_input.setText("") self.password_input.setText(password)
self.port_input.setEnabled(True) self.port_input.setEnabled(True)
self.port_input.setText("5432") self.port_input.setText(port)
self.database_input.setEnabled(True) self.database_input.setEnabled(True)
self.database_input.setText("jtDB") self.database_input.setText(name)
elif self.mysql_radio.isChecked(): self.desc_input.setText(description)
elif db_type == "mysql":
# MySQL模式下需要完整的连接信息 # MySQL模式下需要完整的连接信息
host = self.config_loader.get_value(f"{config_path}.host", "localhost")
port = self.config_loader.get_value(f"{config_path}.port", "3306")
user = self.config_loader.get_value(f"{config_path}.user", "root")
password = self.config_loader.get_value(f"{config_path}.password", "")
name = self.config_loader.get_value(f"{config_path}.name", "jtDB")
description = self.config_loader.get_value(f"{config_path}.description", "")
self.host_input.setEnabled(True) self.host_input.setEnabled(True)
self.host_input.setText("localhost") self.host_input.setText(host)
self.user_input.setEnabled(True) self.user_input.setEnabled(True)
self.user_input.setText("root") self.user_input.setText(user)
self.password_input.setEnabled(True) self.password_input.setEnabled(True)
self.password_input.setText("") self.password_input.setText(password)
self.port_input.setEnabled(True) self.port_input.setEnabled(True)
self.port_input.setText("3306") self.port_input.setText(port)
self.database_input.setEnabled(True) self.database_input.setEnabled(True)
self.database_input.setText("jtDB") self.database_input.setText(name)
self.desc_input.setText(description)
def update_default_source(self):
"""更新默认使用的数据源"""
default_source = self.current_source_combo.currentText().lower()
self.config_loader.set_value('database.default', default_source)
logging.info(f"已更新默认使用的数据源为: {default_source}")
def get_db_type(self): def get_db_type(self):
"""获取当前选择的数据库类型""" """获取当前选择的数据库类型"""
if self.sqlite_radio.isChecked(): return self.db_type_combo.currentText().lower()
return "sqlite"
elif self.pgsql_radio.isChecked():
return "pgsql"
elif self.mysql_radio.isChecked():
return "mysql"
return "sqlite" # 默认返回sqlite
def get_connection_params(self): def get_connection_params(self):
"""获取数据库连接参数""" """获取数据库连接参数"""
@ -174,19 +257,42 @@ class SettingsWidget(SettingsUI):
params = self.get_connection_params()[1] params = self.get_connection_params()[1]
desc = self.desc_input.text().strip() desc = self.desc_input.text().strip()
# 这里应该将设置保存到配置文件中 try:
# 为了简单起见,这里只显示一个消息框 # 构建要保存的配置数据
settings_info = f"数据库类型: {db_type}\n" config_path = f"database.sources.{db_type}"
for key, value in params.items():
if key != "password": if db_type == "sqlite":
settings_info += f"{key}: {value}\n" self.config_loader.set_value(f"{config_path}.path", params["database"])
self.config_loader.set_value(f"{config_path}.description", desc)
else: else:
settings_info += f"{key}: {'*' * len(value)}\n" self.config_loader.set_value(f"{config_path}.host", params["host"])
self.config_loader.set_value(f"{config_path}.port", params["port"])
settings_info += f"说明: {desc}" self.config_loader.set_value(f"{config_path}.user", params["user"])
self.config_loader.set_value(f"{config_path}.password", params["password"])
QMessageBox.information(self, "设置已保存", f"数据库设置已保存!\n\n{settings_info}") self.config_loader.set_value(f"{config_path}.name", params["database"])
logging.info(f"数据库设置已保存,类型: {db_type}") self.config_loader.set_value(f"{config_path}.description", desc)
# 更新数据源下拉框
self._update_source_combo_items()
# 构建要显示的消息
settings_info = f"数据库类型: {db_type}\n"
for key, value in params.items():
if key != "password":
settings_info += f"{key}: {value}\n"
else:
settings_info += f"{key}: {'*' * len(value)}\n"
settings_info += f"说明: {desc}"
# 显示成功消息
QMessageBox.information(self, "设置已保存", f"数据库设置已保存!\n\n{settings_info}")
logging.info(f"数据库设置已保存,类型: {db_type}")
except Exception as e:
# 显示错误消息
QMessageBox.critical(self, "保存失败", f"保存数据库设置失败!\n\n错误: {str(e)}")
logging.error(f"保存数据库设置失败: {str(e)}")
def handle_inspection_configs_changed(self): def handle_inspection_configs_changed(self):
"""处理检验配置变更""" """处理检验配置变更"""