diff --git a/.gitignore b/.gitignore index 42df938..185e280 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,11 @@ __pycache__/ *.py[cod] *$py.class +# 确保所有层级的__pycache__目录都被忽略 +**/__pycache__/ +*/__pycache__/ +*/**/__pycache__/ + # IDE相关 .idea/ .vscode/ diff --git a/camera/__pycache__/CamOperation_class.cpython-310.pyc b/camera/__pycache__/CamOperation_class.cpython-310.pyc deleted file mode 100644 index 8323c12..0000000 Binary files a/camera/__pycache__/CamOperation_class.cpython-310.pyc and /dev/null differ diff --git a/camera/__pycache__/CameraParams_const.cpython-310.pyc b/camera/__pycache__/CameraParams_const.cpython-310.pyc deleted file mode 100644 index 4f01d68..0000000 Binary files a/camera/__pycache__/CameraParams_const.cpython-310.pyc and /dev/null differ diff --git a/camera/__pycache__/CameraParams_header.cpython-310.pyc b/camera/__pycache__/CameraParams_header.cpython-310.pyc deleted file mode 100644 index ff7ea12..0000000 Binary files a/camera/__pycache__/CameraParams_header.cpython-310.pyc and /dev/null differ diff --git a/camera/__pycache__/MvCameraControl_class.cpython-310.pyc b/camera/__pycache__/MvCameraControl_class.cpython-310.pyc deleted file mode 100644 index c5fcd32..0000000 Binary files a/camera/__pycache__/MvCameraControl_class.cpython-310.pyc and /dev/null differ diff --git a/camera/__pycache__/MvErrorDefine_const.cpython-310.pyc b/camera/__pycache__/MvErrorDefine_const.cpython-310.pyc deleted file mode 100644 index c2cda85..0000000 Binary files a/camera/__pycache__/MvErrorDefine_const.cpython-310.pyc and /dev/null differ diff --git a/camera/__pycache__/PixelType_header.cpython-310.pyc b/camera/__pycache__/PixelType_header.cpython-310.pyc deleted file mode 100644 index 76af58f..0000000 Binary files a/camera/__pycache__/PixelType_header.cpython-310.pyc and /dev/null differ diff --git a/config/app_config.json b/config/app_config.json index e96b4c6..33a27f1 100644 --- a/config/app_config.json +++ b/config/app_config.json @@ -4,18 +4,34 @@ "version": "1.0.0", "features": { "enable_serial_ports": false, - "enable_keyboard_listener": true, + "enable_keyboard_listener": false, "enable_camera": false } }, "database": { - "type": "sqlite", - "path": "db/jtDB.db", - "host": "", - "port": "", - "user": "", - "password": "", - "name": "" + "default": "sqlite", + "sources": { + "sqlite": { + "path": "db/jtDB.db", + "description": "默认SQLite数据库" + }, + "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": { "enabled": false, diff --git a/dao/inspection_dao.py b/dao/inspection_dao.py index 274531f..cf01c0c 100644 --- a/dao/inspection_dao.py +++ b/dao/inspection_dao.py @@ -403,6 +403,7 @@ class InspectionDAO: tray_id, COALESCE(axis_package_id, '') as axis_package_id, COALESCE(weight, 0) as weight, + COALESCE(net_weight, 0) as net_weight, STRFTIME('%Y-%m-%d %H:%M:%S', pack_time) as pack_time FROM inspection_pack_data WHERE tray_id = ? @@ -416,7 +417,7 @@ 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, finish_time): + def save_package_record(self, order_id, tray_id, label_value, weight_value,net_weight_value, finish_time): """保存包装记录 Args: @@ -429,10 +430,10 @@ class InspectionDAO: # TODO:调用接口,获取到工程号对应的其他信息,比如材质,规格,后续完成 try: 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) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """ - 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.conn.commit() except Exception as e: diff --git a/db/jtDB.db b/db/jtDB.db index 6a85577..8b0a9fb 100644 Binary files a/db/jtDB.db and b/db/jtDB.db differ diff --git a/from pymodbus.py b/from pymodbus.py index 35d88c5..a13ebc9 100644 --- a/from pymodbus.py +++ b/from pymodbus.py @@ -2,12 +2,12 @@ from pymodbus.client import ModbusTcpClient client = ModbusTcpClient('localhost', port=5020) 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=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===") client.close() \ No newline at end of file diff --git a/main.py b/main.py index 59eca21..7d3ee9f 100644 --- a/main.py +++ b/main.py @@ -179,8 +179,13 @@ def main(): # 创建db目录(如果不存在) 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 init_database() logging.info("初始化数据库完成") @@ -190,6 +195,11 @@ def main(): exit_code = app.exec() logging.info(f"应用程序退出,退出码: {exit_code}") + + # 关闭所有数据库连接 + from utils.sql_utils import SQLUtils + SQLUtils.close_all_connections() + sys.exit(exit_code) except Exception as e: diff --git a/ui/__pycache__/login_ui.cpython-310.pyc b/ui/__pycache__/login_ui.cpython-310.pyc deleted file mode 100644 index e9585f7..0000000 Binary files a/ui/__pycache__/login_ui.cpython-310.pyc and /dev/null differ diff --git a/ui/main_window_ui.py b/ui/main_window_ui.py index 66cf74b..db87a8e 100644 --- a/ui/main_window_ui.py +++ b/ui/main_window_ui.py @@ -285,13 +285,13 @@ class MainWindowUI(QMainWindow): } """ - # self.input_button = QPushButton("上料") - # self.input_button.setFont(self.normal_font) - # self.input_button.setStyleSheet(button_style + "background-color: #e3f2fd; border: 1px solid #2196f3;") + self.input_button = QPushButton("上料") + self.input_button.setFont(self.normal_font) + self.input_button.setStyleSheet(button_style + "background-color: #e3f2fd; border: 1px solid #2196f3;") - # self.output_button = QPushButton("下料") - # self.output_button.setFont(self.normal_font) - # self.output_button.setStyleSheet(button_style + "background-color: #fff8e1; border: 1px solid #ffc107;") + self.output_button = QPushButton("下料") + self.output_button.setFont(self.normal_font) + self.output_button.setStyleSheet(button_style + "background-color: #fff8e1; border: 1px solid #ffc107;") self.start_button = QPushButton("开始") 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.button_layout.addWidget(self.input_button, 0, 0) - # self.button_layout.addWidget(self.output_button, 0, 1) - self.button_layout.addWidget(self.start_button, 0, 1) - self.button_layout.addWidget(self.stop_button, 0, 2) + self.button_layout.addWidget(self.input_button, 0, 0) + self.button_layout.addWidget(self.output_button, 0, 1) + self.button_layout.addWidget(self.start_button, 0, 2) + self.button_layout.addWidget(self.stop_button, 0, 3) self.control_layout.addWidget(self.button_container) @@ -501,13 +501,13 @@ class MainWindowUI(QMainWindow): 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) # 设置列标题 - record_headers = ["序号", "订单", "品名", "规格", "托号", "轴包装号", "重量", "完成时间"] + record_headers = ["序号", "订单", "品名", "规格", "托号", "轴包装号", "毛重", "净重", "完成时间"] for col, header in enumerate(record_headers): self.record_table.setItem(0, col, self.create_header_item(header)) @@ -520,7 +520,7 @@ class MainWindowUI(QMainWindow): 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): self.record_table.setColumnWidth(col, width) @@ -593,7 +593,7 @@ class MainWindowUI(QMainWindow): 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) # 重新设置列宽 @@ -614,9 +614,9 @@ class MainWindowUI(QMainWindow): self.process_table.setSpan(0, 2, 1, self.inspection_columns) self.process_table.setItem(0, 2, self.create_header_item("检验")) - # 包装区域(2列) + # 包装区域(3列) 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("包装")) # 第二行:列标题 @@ -636,7 +636,7 @@ class MainWindowUI(QMainWindow): self.process_table.setItem(1, 2 + i, self.create_header_item(header_text)) # 包装区域列标题 - packaging_headers = ["贴标", "称重"] + packaging_headers = ["贴标", "毛重", "净重"] for i, header in enumerate(packaging_headers): 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 self.process_table.setColumnWidth(packaging_start_col, 140) # 贴标 - self.process_table.setColumnWidth(packaging_start_col + 1, 140) # 称重 \ No newline at end of file + self.process_table.setColumnWidth(packaging_start_col + 1, 140) # 毛重 + self.process_table.setColumnWidth(packaging_start_col + 2, 140) # 净重 \ No newline at end of file diff --git a/ui/settings_ui.py b/ui/settings_ui.py index 5ad530a..622fe21 100644 --- a/ui/settings_ui.py +++ b/ui/settings_ui.py @@ -205,25 +205,31 @@ class SettingsUI(QWidget): self.database_layout = QVBoxLayout(self.database_tab) 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_layout = QHBoxLayout() - self.sqlite_radio = QCheckBox("SQLite") - self.sqlite_radio.setFont(self.normal_font) - self.sqlite_radio.setChecked(True) + self.db_type_combo = QComboBox() + self.db_type_combo.setFont(self.normal_font) + self.db_type_combo.addItem("SQLite") + self.db_type_combo.addItem("PostgreSQL") + self.db_type_combo.addItem("MySQL") - self.pgsql_radio = QCheckBox("PostgreSQL") - self.pgsql_radio.setFont(self.normal_font) - - 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.addWidget(QLabel("当前配置类型:")) + self.db_type_layout.addWidget(self.db_type_combo) 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.database_layout.addWidget(self.db_type_group) diff --git a/utils/__pycache__/config_loader.cpython-310.pyc b/utils/__pycache__/config_loader.cpython-310.pyc deleted file mode 100644 index 924dd84..0000000 Binary files a/utils/__pycache__/config_loader.cpython-310.pyc and /dev/null differ diff --git a/utils/__pycache__/menu_translator.cpython-310.pyc b/utils/__pycache__/menu_translator.cpython-310.pyc deleted file mode 100644 index cd70295..0000000 Binary files a/utils/__pycache__/menu_translator.cpython-310.pyc and /dev/null differ diff --git a/utils/__pycache__/sql_utils.cpython-310.pyc b/utils/__pycache__/sql_utils.cpython-310.pyc deleted file mode 100644 index a1332ee..0000000 Binary files a/utils/__pycache__/sql_utils.cpython-310.pyc and /dev/null differ diff --git a/utils/__pycache__/version_manager.cpython-310.pyc b/utils/__pycache__/version_manager.cpython-310.pyc deleted file mode 100644 index 04c96b8..0000000 Binary files a/utils/__pycache__/version_manager.cpython-310.pyc and /dev/null differ diff --git a/utils/config_loader.py b/utils/config_loader.py index 4e4345d..3a06d30 100644 --- a/utils/config_loader.py +++ b/utils/config_loader.py @@ -32,6 +32,9 @@ class ConfigLoader: with open(self.config_file, 'r', encoding='utf-8') as f: self.config = json.load(f) logging.info(f"已加载配置文件: {self.config_file}") + + # 检查并升级配置文件结构(兼容旧版本的配置) + self._upgrade_config_if_needed() else: # 创建默认配置 self.config = { @@ -40,17 +43,50 @@ class ConfigLoader: "version": "1.0.0", "features": { "enable_serial_ports": False, - "enable_keyboard_listener": False + "enable_keyboard_listener": False, + "enable_camera": False } }, "database": { - "type": "sqlite", - "path": "db/jtDB.db", - "host": "", - "port": "", - "user": "", - "password": "", - "name": "" + "current": "sqlite", + "sources": { + "sqlite": { + "path": "db/jtDB.db", + "description": "默认SQLite数据库" + }, + "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", "features": { "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): """保存配置到文件""" try: @@ -163,4 +275,25 @@ class ConfigLoader: self.config['serial'][key] = config_data # 这里不保存配置,等待调用save_config方法时一并保存 - return True \ No newline at end of file + 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}', {}) \ No newline at end of file diff --git a/utils/init_db.py b/utils/init_db.py index ae084ac..79892ea 100644 --- a/utils/init_db.py +++ b/utils/init_db.py @@ -2,9 +2,20 @@ from utils.sql_utils import SQLUtils import datetime import os import logging +from utils.config_loader import ConfigLoader 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: db.begin_transaction() diff --git a/utils/sql_utils.py b/utils/sql_utils.py index dc145bc..0b31ab7 100644 --- a/utils/sql_utils.py +++ b/utils/sql_utils.py @@ -1,4 +1,6 @@ import sys +import logging +from utils.config_loader import ConfigLoader try: import psycopg2 @@ -10,27 +12,127 @@ try: except ImportError: sqlite3 = None +try: + import mysql.connector +except ImportError: + mysql = None + 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.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.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() + + # 将新连接添加到连接池 + if self.conn and self.cursor: + SQLUtils._connection_pool[conn_key] = (self.conn, self.cursor) def connect(self): - if self.db_type == 'pgsql' or self.db_type == 'postgresql': - if not psycopg2: - raise ImportError('psycopg2 is not installed') - self.conn = psycopg2.connect(**self.kwargs) - elif self.db_type == 'sqlite' or self.db_type == 'sqlite3': - if not sqlite3: - raise ImportError('sqlite3 is not installed') - self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:')) - else: - raise ValueError(f'Unsupported db_type: {self.db_type}') - self.cursor = self.conn.cursor() + """连接到数据库""" + try: + if self.db_type in ['pgsql', 'postgresql']: + if not psycopg2: + raise ImportError('psycopg2 is not installed') + self.conn = psycopg2.connect(**self.kwargs) + elif self.db_type in ['sqlite', 'sqlite3']: + if not sqlite3: + raise ImportError('sqlite3 is not installed') + self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:')) + 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): if params is None: @@ -72,7 +174,36 @@ class SQLUtils: return self.cursor.fetchall() def close(self): - if self.cursor: - self.cursor.close() - if self.conn: - self.conn.close() \ No newline at end of file + """关闭连接(实际上是将连接返回到连接池)""" + # 这里不再实际关闭连接,让连接池管理连接生命周期 + pass + + @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') \ No newline at end of file diff --git a/widgets/__pycache__/camera_manager.cpython-310.pyc b/widgets/__pycache__/camera_manager.cpython-310.pyc deleted file mode 100644 index 4834915..0000000 Binary files a/widgets/__pycache__/camera_manager.cpython-310.pyc and /dev/null differ diff --git a/widgets/__pycache__/login_widget.cpython-310.pyc b/widgets/__pycache__/login_widget.cpython-310.pyc deleted file mode 100644 index 96561a0..0000000 Binary files a/widgets/__pycache__/login_widget.cpython-310.pyc and /dev/null differ diff --git a/widgets/login_widget.py b/widgets/login_widget.py index 7007981..1a88fea 100644 --- a/widgets/login_widget.py +++ b/widgets/login_widget.py @@ -10,7 +10,8 @@ import threading def check_user_login(user_id, password): """验证用户登录""" 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)) result = db.fetchone() db.close() @@ -22,7 +23,8 @@ def check_user_login(user_id, password): def get_user_info(user_id): """获取用户信息""" 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,)) result = db.fetchone() db.close() diff --git a/widgets/main_window.py b/widgets/main_window.py index 52fc53a..d2c6919 100644 --- a/widgets/main_window.py +++ b/widgets/main_window.py @@ -243,8 +243,8 @@ class MainWindow(MainWindowUI): self.tray_edit.activated.connect(self.load_finished_inspection_data) # 当用户选择一项时触发 # 连接按钮事件 - # self.input_button.clicked.connect(self.handle_input) - # self.output_button.clicked.connect(self.handle_output) + self.input_button.clicked.connect(self.handle_input) + self.output_button.clicked.connect(self.handle_output) self.start_button.clicked.connect(self.handle_start) self.stop_button.clicked.connect(self.handle_stop) @@ -753,7 +753,7 @@ class MainWindow(MainWindowUI): 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 = [{ 'position': position, 'config_id': position, @@ -878,13 +878,21 @@ class MainWindow(MainWindowUI): self.save_inspection_data(order_id, tray_id, 11, 11, value, status) elif column == packaging_start_col + 1: - # 称重列 - data_type = "称重" + # 毛重列 + data_type = "毛重" self.statusBar().showMessage(f"正在保存称重数据: {value}", 1000) # 设置单元格颜色为通过 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) + 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}") @@ -1090,6 +1098,10 @@ class MainWindow(MainWindowUI): # 称重列索引 = 2(序号和工程号) + 检验列数 + 1(贴标) weight_col = 2 + len(enabled_configs) + 1 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 # 设置表格为可编辑状态 @@ -1129,12 +1141,14 @@ class MainWindow(MainWindowUI): # 从检验数据中获取贴标和称重数据 label_value = "" weight_value = "" - + net_weight_value = "" for item in inspection_data: if item['position'] == 11: # 贴标 label_value = item['value'] elif item['position'] == 12: # 称重 weight_value = item['value'] + elif item['position'] == 13: # 净重 + net_weight_value = item['value'] # 只要贴标字段有值,就可以写入包装记录 if not label_value: @@ -1152,7 +1166,7 @@ class MainWindow(MainWindowUI): finish_time = datetime.now() # 将数据写入到数据库表 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() @@ -1223,10 +1237,15 @@ class MainWindow(MainWindowUI): weight_item.setTextAlignment(Qt.AlignCenter) 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])) - weight_item.setTextAlignment(Qt.AlignCenter) - self.record_table.setItem(row_index, 7, pack_time) + pack_time = QTableWidgetItem(str(item[7])) + pack_time.setTextAlignment(Qt.AlignCenter) + self.record_table.setItem(row_index, 8, pack_time) # 更新包装记录统计数据 self.update_package_statistics() def update_package_statistics(self): @@ -1495,6 +1514,8 @@ class MainWindow(MainWindowUI): # 计算称重列索引 - 称重位置在检验列之后的第二列(贴标后面) weight_col = 2 + len(enabled_configs) + 1 + # 计算净重列索引 - 净重位置在检验列之后的第三列(称重后面) + net_weight_col = 2 + len(enabled_configs) + 2 # 获取当前选中的行或第一个数据行 current_row = self.process_table.currentRow() @@ -1531,6 +1552,15 @@ class MainWindow(MainWindowUI): tray_id = self.tray_edit.currentText() 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) diff --git a/widgets/settings_widget.py b/widgets/settings_widget.py index d15f8b7..784dc81 100644 --- a/widgets/settings_widget.py +++ b/widgets/settings_widget.py @@ -1,9 +1,12 @@ from PySide6.QtWidgets import QMessageBox, QVBoxLayout import logging +import json +import os from ui.settings_ui import SettingsUI from utils.sql_utils import SQLUtils from widgets.inspection_settings_widget import InspectionSettingsWidget from widgets.pallet_type_settings_widget import PalletTypeSettingsWidget +from utils.config_loader import ConfigLoader class SettingsWidget(SettingsUI): def __init__(self, parent=None): @@ -52,19 +55,23 @@ class SettingsWidget(SettingsUI): else: logging.error("无法找到pallet_type_layout布局") + # 加载配置文件 + self.config_loader = ConfigLoader.get_instance() + # 连接信号和槽 self.connect_signals() # 初始化数据库类型UI状态 - self.update_db_ui_state() + self.load_db_config() logging.info("SettingsWidget初始化完成") def connect_signals(self): # 数据库类型选择 - self.sqlite_radio.toggled.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.db_type_combo.currentTextChanged.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) @@ -80,10 +87,67 @@ class SettingsWidget(SettingsUI): if hasattr(self, 'back_button'): 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): """根据选择的数据库类型更新UI状态""" - if self.sqlite_radio.isChecked(): - # SQLite模式下,只需要数据库文件路径 + db_type = self.db_type_combo.currentText().lower() + + # 处理特殊情况: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.setText("") self.user_input.setEnabled(False) @@ -93,41 +157,60 @@ class SettingsWidget(SettingsUI): self.port_input.setEnabled(False) self.port_input.setText("") self.database_input.setEnabled(True) - self.database_input.setText("db/jtDB.db") - elif self.pgsql_radio.isChecked(): + self.database_input.setText(path) + self.desc_input.setText(description) + + elif db_type == "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.setText("localhost") + self.host_input.setText(host) self.user_input.setEnabled(True) - self.user_input.setText("postgres") + self.user_input.setText(user) self.password_input.setEnabled(True) - self.password_input.setText("") + self.password_input.setText(password) self.port_input.setEnabled(True) - self.port_input.setText("5432") + self.port_input.setText(port) self.database_input.setEnabled(True) - self.database_input.setText("jtDB") - elif self.mysql_radio.isChecked(): + self.database_input.setText(name) + self.desc_input.setText(description) + + elif db_type == "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.setText("localhost") + self.host_input.setText(host) self.user_input.setEnabled(True) - self.user_input.setText("root") + self.user_input.setText(user) self.password_input.setEnabled(True) - self.password_input.setText("") + self.password_input.setText(password) self.port_input.setEnabled(True) - self.port_input.setText("3306") + self.port_input.setText(port) 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): """获取当前选择的数据库类型""" - if self.sqlite_radio.isChecked(): - return "sqlite" - elif self.pgsql_radio.isChecked(): - return "pgsql" - elif self.mysql_radio.isChecked(): - return "mysql" - return "sqlite" # 默认返回sqlite + return self.db_type_combo.currentText().lower() def get_connection_params(self): """获取数据库连接参数""" @@ -174,19 +257,42 @@ class SettingsWidget(SettingsUI): params = self.get_connection_params()[1] desc = self.desc_input.text().strip() - # 这里应该将设置保存到配置文件中 - # 为了简单起见,这里只显示一个消息框 - settings_info = f"数据库类型: {db_type}\n" - for key, value in params.items(): - if key != "password": - settings_info += f"{key}: {value}\n" + try: + # 构建要保存的配置数据 + config_path = f"database.sources.{db_type}" + + if db_type == "sqlite": + self.config_loader.set_value(f"{config_path}.path", params["database"]) + self.config_loader.set_value(f"{config_path}.description", desc) 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}") + self.config_loader.set_value(f"{config_path}.host", params["host"]) + self.config_loader.set_value(f"{config_path}.port", params["port"]) + self.config_loader.set_value(f"{config_path}.user", params["user"]) + self.config_loader.set_value(f"{config_path}.password", params["password"]) + self.config_loader.set_value(f"{config_path}.name", params["database"]) + 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): """处理检验配置变更"""