本文主要是介绍pyside6增删改查插件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
通过JSON文件类配置数据,生成CRUD的界面,支持列表和树结构,支持数据钻取。
目录
一、form_config.json文件
二、代码及使用示例
三、效果图
一、form_config.json文件
{"id": 1,"title": "用户管理","database": "sqlite:///CRUD_DEMO_DATABASE.db","table": "user","fields": [{"name": "id","label": "编号","type": "id","placeholder": "0","required": true,"hidden": false,"is_search": true},{"name": "name","label": "名称","type": "text","placeholder": "张三丰","required": true,"hidden": false,"is_search": true},{"name": "age","label": "年龄","type": "number","placeholder": 99,"required": false,"hidden": false,"is_search": true},{"name": "province","label": "省份","type": "select","options": [{"label": "请选择...","value": null},{"label": "浙江省","value": "1"},{"label": "安徽省","value": "2"},{"label": "广东省","value": "3"},{"label": "江苏省","value": "4"},{"label": "四川省","value": "5"}],"placeholder": "男","required": false,"hidden": false,"is_search": true},{"name": "create_time","label": "创建时间","type": "datetime","placeholder": "2024-07-01 08:56:51","required": false,"hidden": false,"is_search": true}],"drill": [{"button_name": "我的菜单","form_config_json": "./crud_form_config_tree.json","foreign_key": "user_id"},{"button_name": "我的书籍","form_config_json": "./crud_form_config_drill.json","foreign_key": "user_id"}]
}
{"id": 2,"title": "书籍管理","database": "sqlite:///CRUD_DEMO_DATABASE.db","table": "book","fields": [{"name": "id","label": "编号","type": "id","placeholder": "0","required": true,"hidden": false,"is_search": true},{"name": "user_id","label": "用户ID","type": "number","placeholder": "0","required": true,"hidden": false,"is_search": true},{"name": "name","label": "书名","type": "text","placeholder": "万历十五年","required": true,"hidden": false,"is_search": true},{"name": "author","label": "作者","type": "text","placeholder": "当年明月","required": false,"hidden": false,"is_search": true}],"drill": [{"button_name": "用户列表","form_config_json": "./crud_form_config.json","foreign_key": "user_id"}]
}
{"id": 3,"title": "菜单管理","database": "sqlite:///TREE_DEMO_DATABASE.db","table": "menu","fields": [{"name": "id","label": "编号","type": "id","placeholder": "0","required": true,"hidden": false,"is_search": true},{"name": "user_id","label": "用户ID","type": "number","placeholder": "0","required": true,"hidden": false,"is_search": true},{"name": "name","label": "名称","type": "text","placeholder": "张三丰","required": true,"hidden": false,"is_search": true},{"name": "parent_id","label": "父节点","type": "select","options": [],"placeholder": "请选择父节点","required": false,"hidden": false,"is_search": true}],"drill": [{"button_name": "我的书籍","form_config_json": "./crud_form_config_drill.json","foreign_key": "user_id"}]
}
二、代码及使用示例
import json
import uuidimport dataset
import pandas as pd
from PySide6.QtGui import QRegularExpressionValidator
from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QLineEdit, QPushButton,QTableWidget, QTableWidgetItem, QHeaderView, QComboBox, QLabel, QSpinBox, QDialog, QDialogButtonBox, QDateTimeEdit,QGroupBox, QMessageBox, QTreeWidget, QTreeWidgetItem
)
from PySide6.QtCore import QDateTime, QThread, Signalfrom plugIn.title_plugin import QCustomTitleBar, WindowResizerclass ExportThread(QThread):finished = Signal()def __init__(self, table):super().__init__()self.table = tabledef run(self):data = list(self.table.all())df = pd.DataFrame(data)df.to_excel('exported_data.xlsx', index=False)# self.finished.emit()passclass CrudWidget(QWidget):def __init__(self, form_json: str = "./form_config.json", foreign_key=None, foreign_value=None):""":param form_json 表单文件:param foreign_key 外键名称:param foreign_value 外键值"""super().__init__()self.foreign_key = foreign_keyself.foreign_value = foreign_value# 初始化解析表单配置try:with open(form_json, 'r', encoding='utf-8') as f:self.config = json.load(f) # 加载表单配置except FileNotFoundError:raise ValueError(f"表单配置文件未找到:{self.config}")if 'id' not in self.config:raise ValueError("表单配置文件格式错误【无id参数】")if 'fields' not in self.config:raise ValueError("表单配置文件格式错误【无fields参数】")if 'title' not in self.config:raise ValueError("表单配置文件格式错误【无title参数】")if 'database' not in self.config:raise ValueError("表单配置文件格式错误【无database参数】")if 'table' not in self.config:raise ValueError("表单配置文件格式错误【无table参数】")fields = self.config['fields']if type(fields) is not list:raise ValueError("表单配置文件格式错误【fields必须是list类型】")for field in fields:if 'name' not in field:raise ValueError("表单配置文件格式错误【字段缺少name】")if 'label' not in field:raise ValueError("表单配置文件格式错误【字段缺少label】")if 'type' not in field:raise ValueError("表单配置文件格式错误【字段缺少type】")self.id = self.config['id']self.title = self.config['title']self.database = self.config['database']self.table = self.config['table']self.fields = self.config['fields']self.drill = self.config['drill']self.setWindowTitle(self.title) # 设置窗口标题self.setGeometry(100, 100, 800, 600) # 设置窗口位置和大小# self.title_bar = QCustomTitleBar(self, windowTitle=self.title)# self.resizer = WindowResizer(self)self.setStyleSheet("""QLineEdit, QSpinBox {padding: 5px;border: 1px solid #d0d0d0;border-radius: 5px;}QLabel {font-size: 14px;color: #333333;}""")self.db = dataset.connect(self.database) # 连接数据库self.table = self.db[self.config['table']] # 获取数据表self.init_ui() # 初始化UIself.load_data() # 加载数据def init_ui(self):self.layout = QVBoxLayout(self) # 创建垂直布局self.init_search_block() # 初始化搜索块self.init_data_block() # 初始化数据块def init_search_block(self):"""初始化搜索块"""if hasattr(self, 'search_group') and hasattr(self, 'search_group_layout'):while self.search_group_layout.count():item = self.search_group_layout.takeAt(0)widget = item.widget()if widget is not None:widget.deleteLater()else:self.search_group = QGroupBox() # 创建搜索区组self.layout.addWidget(self.search_group) # 将搜索布局添加到主布局self.search_group_layout = QHBoxLayout() # 创建水平布局self.search_group.setLayout(self.search_group_layout) # 设置组布局self.search_widgets = {} # 搜索部件字典for field in self.config['fields']:if field['is_search']: # 是否为搜索字段进行判断label = field['label'] # 获取字段标签widget = Noneif field['type'] == 'text':widget = QLineEdit() # 创建输入框widget.returnPressed.connect(self.search_data) # 监听回车键elif field['type'] == 'number':widget = QLineEdit() # 创建数字输入框validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)widget.returnPressed.connect(self.search_data) # 监听回车键elif field['type'] == 'datetime':widget = QDateTimeEdit()widget.setDisplayFormat("yyyy-MM-dd hh:mm:ss") # 设置显示格式widget.setCalendarPopup(True) # 启用弹出式日历选择elif field['type'] == 'select':widget = QComboBox()widget.currentIndexChanged.connect(self.search_data) # 监听切换selection事件for option in field['options']:widget.addItem(option['label'], option['value'])elif field['type'] == 'switch':# 使用QRadioButtonpasselse:widget = QLineEdit() # 创建输入框widget.returnPressed.connect(self.search_data) # 监听回车键self.search_widgets[field['name']] = widget # 将输入框添加到字典self.search_group_layout.addWidget(QLabel(label)) # 添加标签到布局self.search_group_layout.addWidget(widget) # 添加输入框到布局self.search_button = QPushButton("搜索") # 创建搜索按钮self.search_button.clicked.connect(self.search_data) # 连接搜索按钮信号self.reset_button = QPushButton("重置") # 创建重置按钮self.reset_button.clicked.connect(self.reset_search) # 连接重置按钮信号self.search_group_layout.addWidget(self.search_button) # 添加搜索按钮到布局self.search_group_layout.addWidget(self.reset_button) # 添加重置按钮到布局def init_data_block(self):self.data_layout = QVBoxLayout() # 创建垂直布局control_group = QGroupBox() # 创建控制区组control_group_layout = QVBoxLayout() # 创建垂直布局control_group.setLayout(control_group_layout) # 设置组布局self.control_layout = QHBoxLayout() # 创建水平布局self.add_button = QPushButton("新增") # 创建新增按钮self.add_button.clicked.connect(self.add_data) # 连接新增按钮信号self.delete_button = QPushButton("删除") # 创建删除按钮self.delete_button.clicked.connect(self.delete_data) # 连接删除按钮信号self.clear_button = QPushButton("清空") # 创建清空按钮self.clear_button.clicked.connect(self.clear_data) # 连接清空按钮信号self.export_button = QPushButton("导出") # 创建导出按钮self.export_button.clicked.connect(self.export_data) # 连接导出按钮信号self.hide_search_button = QPushButton("隐藏搜索块") # 创建隐藏搜索块按钮self.hide_search_button.clicked.connect(self.toggle_search_block) # 连接隐藏搜索块按钮信号self.refresh_button = QPushButton("刷新数据") # 创建刷新数据按钮self.refresh_button.clicked.connect(self.load_data) # 连接刷新数据按钮信号self.control_layout.addWidget(self.add_button) # 添加新增按钮到布局self.control_layout.addWidget(self.delete_button) # 添加删除按钮到布局self.control_layout.addWidget(self.clear_button) # 添加清空按钮到布局self.control_layout.addWidget(self.export_button) # 添加导出按钮到布局self.control_layout.addStretch() # 添加伸缩项self.control_layout.addWidget(self.hide_search_button) # 添加隐藏搜索块按钮到布局self.control_layout.addWidget(self.refresh_button) # 添加刷新数据按钮到布局control_group_layout.addLayout(self.control_layout) # 将控制布局添加到数据布局self.data_layout.addWidget(control_group) # 将控制布局添加到数据布局self.table_widget = QTableWidget() # 创建表格部件self.table_widget.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) # 设置表格不可编辑# "详情", "编辑", "删除"为基础按钮,外加钻取按钮self.table_widget.setColumnCount(len(self.config['fields']) + 3 + len(self.drill)) # 设置列数headers = [field['label'] for field in self.config['fields']] + ["详情", "编辑", "删除"] + [d['button_name'] ford inself.drill] # 设置表头self.table_widget.setHorizontalHeaderLabels(headers) # 设置表头标签self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # 设置列宽自动调整self.table_widget.setSortingEnabled(True) # 启用排序self.data_layout.addWidget(self.table_widget) # 将表格部件添加到数据布局pagination_group = QGroupBox() # 创建页码区组pagination_group_layout = QVBoxLayout() # 创建垂直布局pagination_group.setLayout(pagination_group_layout) # 设置组布局self.pagination_layout = QHBoxLayout() # 创建水平布局self.current_page_label = QLabel("当前页码: 1") # 创建当前页码标签self.total_records_label = QLabel("总记录数: 0") # 创建总记录数标签self.page_size_combo = QComboBox() # 创建每页显示条数下拉框self.page_size_combo.addItems(["5", "10", "20", "50", "100"]) # 添加选项self.page_size_combo.currentIndexChanged.connect(self.load_data) # 连接下拉框信号self.prev_button = QPushButton("上一页") # 创建上一页按钮self.prev_button.clicked.connect(self.prev_page) # 连接上一页按钮信号self.next_button = QPushButton("下一页") # 创建下一页按钮self.next_button.clicked.connect(self.next_page) # 连接下一页按钮信号self.page_input = QLineEdit("1") # 创建页码输入框self.page_input.setValidator(QRegularExpressionValidator(r'^(?:[0-9]|[1-9][0-9]|100)$')) # 限制数字范围为 0 到 100self.page_input.textChanged.connect(lambda text: self.page_input.setText("1") if text == "" else None) # 置空时自动设置为 1self.page_input.setFixedWidth(30) # 设置输入框宽度self.jump_button = QPushButton("跳转") # 创建跳转按钮self.jump_button.clicked.connect(self.jump_to_page) # 连接跳转按钮信号self.pagination_layout.addWidget(self.current_page_label) # 添加当前页码标签到布局self.pagination_layout.addWidget(self.total_records_label) # 添加总记录数标签到布局self.pagination_layout.addWidget(QLabel("每页显示:")) # 添加标签到布局self.pagination_layout.addWidget(self.page_size_combo) # 添加下拉框到布局self.pagination_layout.addStretch() # 添加伸缩项self.pagination_layout.addWidget(self.prev_button) # 添加上一页按钮到布局self.pagination_layout.addWidget(self.next_button) # 添加下一页按钮到布局self.pagination_layout.addWidget(self.page_input) # 添加页码输入框到布局self.pagination_layout.addWidget(self.jump_button) # 添加跳转按钮到布局pagination_group_layout.addLayout(self.pagination_layout) # 将分页布局添加到数据布局self.data_layout.addWidget(pagination_group) # 将分页布局添加到数据布局self.layout.addLayout(self.data_layout) # 将数据布局添加到主布局def load_data(self):self.table_widget.setRowCount(0) # 清空表格page_size = int(self.page_size_combo.currentText()) # 获取每页显示条数page = int(self.page_input.text()) # 获取当前页码offset = (page - 1) * page_size # 计算偏移量conditions = {} # 搜索条件字典if self.foreign_key and self.foreign_value:conditions[self.foreign_key] = self.foreign_valuequery = self.table.find(**conditions) # 查询数据total_records = len(list(query)) # 获取总记录数self.total_records_label.setText(f"总记录数: {total_records}") # 更新总记录数标签data = self.table.find(_limit=page_size, _offset=offset, **conditions) # 分页查询数据for row_data in data:row = self.table_widget.rowCount() # 获取当前行数self.table_widget.insertRow(row) # 插入新行for i, field in enumerate(self.config['fields']):if field['name'] in row_data:if field['type'] == 'select':mapping = {}for options in field['options']:mapping[options['value']] = options['label']item = QTableWidgetItem(str(mapping[row_data[field['name']]])) # 创建表格项self.table_widget.setItem(row, i, item) # 设置表格项到单元格else:item = QTableWidgetItem(str(row_data[field['name']])) # 创建表格项self.table_widget.setItem(row, i, item) # 设置表格项到单元格else:item = QTableWidgetItem("") # 创建表格项self.table_widget.setItem(row, i, item) # 设置表格项到单元格detail_button = QPushButton("详情") # 创建详情按钮detail_button.clicked.connect(lambda _, r=row: self.show_detail(r)) # 连接详情按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']), detail_button) # 设置详情按钮到单元格edit_button = QPushButton("编辑") # 创建编辑按钮edit_button.clicked.connect(lambda _, r=row: self.edit_data(r)) # 连接编辑按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']) + 1, edit_button) # 设置编辑按钮到单元格delete_button = QPushButton("删除") # 创建删除按钮delete_button.clicked.connect(lambda _, r=row: self.delete_data_row(r)) # 连接删除按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']) + 2, delete_button) # 设置删除按钮到单元格# 数据钻取按钮for index, d in enumerate(self.drill):drill_button = QPushButton(d['button_name']) # 创建钻取按钮drill_button.clicked.connect(lambda checked, r=row, d=d: self.drill_data_row(r, d)) # 连接删除按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']) + 2 + index + 1,drill_button) # 设置删除按钮到单元格self.current_page_label.setText(f"当前页码: {page}") # 更新当前页码标签def search_data(self):if not hasattr(self, 'table_widget'):return # 如果没有表格,则不执行操作conditions = {} # 搜索条件字典if self.foreign_key and self.foreign_value:conditions[self.foreign_key] = self.foreign_valuefor name, widget in self.search_widgets.items():if isinstance(widget, QComboBox):# value = widget.currentText() # 获取下拉框选中项value = widget.currentData()elif isinstance(widget, QDateTimeEdit):value = widget.text()if widget.text() == '2000-01-01 00:00:00':value = Noneelse:value = widget.text() # 获取输入框文本if value:conditions[name] = value # 添加到搜索条件字典print(conditions)query = self.table.find(**conditions) # 根据条件查询数据data = list(query) # 获取查询结果self.table_widget.setRowCount(0) # 清空表格for row_data in data:row = self.table_widget.rowCount() # 获取当前行数self.table_widget.insertRow(row) # 插入新行for i, field in enumerate(self.config['fields']):if field['type'] == 'select':mapping = {}for options in field['options']:mapping[options['value']] = options['label']item = QTableWidgetItem(str(mapping[row_data[field['name']]])) # 创建表格项self.table_widget.setItem(row, i, item) # 设置表格项到单元格else:item = QTableWidgetItem(str(row_data[field['name']])) # 创建表格项self.table_widget.setItem(row, i, item) # 设置表格项到单元格detail_button = QPushButton("详情") # 创建详情按钮detail_button.clicked.connect(lambda _, r=row: self.show_detail(r)) # 连接详情按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']), detail_button) # 设置详情按钮到单元格edit_button = QPushButton("编辑") # 创建编辑按钮edit_button.clicked.connect(lambda _, r=row: self.edit_data(r)) # 连接编辑按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']) + 1, edit_button) # 设置编辑按钮到单元格delete_button = QPushButton("删除") # 创建删除按钮delete_button.clicked.connect(lambda _, r=row: self.delete_data_row(r)) # 连接删除按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']) + 2, delete_button) # 设置删除按钮到单元格# 数据钻取按钮for index, d in enumerate(self.drill):drill_button = QPushButton(d['button_name']) # 创建钻取按钮drill_button.clicked.connect(lambda checked, r=row, d=d: self.drill_data_row(r, d)) # 连接删除按钮信号self.table_widget.setCellWidget(row, len(self.config['fields']) + 2 + index + 1,drill_button) # 设置删除按钮到单元格def reset_search(self):self.init_search_block()self.load_data() # 重新加载数据def add_data(self):dialog = QDialog(self) # 创建对话框dialog.setWindowTitle("新增数据") # 设置对话框标题form_layout = QFormLayout(dialog) # 创建表单布局dialog.setLayout(form_layout) # 设置对话框布局form_widgets = {} # 表单部件字典for field in self.config['fields']:label = field['label'] # 获取字段标签if field['type'] == 'id':passelif field['type'] == 'text':widget = QLineEdit() # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'number':widget = QLineEdit() # 创建数字输入框validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'datetime':widget = QDateTimeEdit()widget.setDisplayFormat("yyyy-MM-dd hh:mm:ss") # 设置显示格式widget.setCalendarPopup(True) # 启用弹出式日历选择widget.setDateTime(QDateTime.currentDateTime())form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setDateTime(self.foreign_value)elif field['type'] == 'select':widget = QComboBox()for option in field['options']:widget.addItem(option['label'], option['value'])form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setEditable(False)widget.setCurrentIndex(self.foreign_value)else:widget = QLineEdit() # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # 创建按钮框buttons.accepted.connect(dialog.accept) # 连接确认按钮信号buttons.rejected.connect(dialog.reject) # 连接取消按钮信号form_layout.addRow(buttons) # 添加按钮框到布局if dialog.exec() == QDialog.DialogCode.Accepted:data = {}for name, widget in form_widgets.items():if isinstance(widget, QLineEdit):data[name] = widget.text()validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)elif isinstance(widget, QSpinBox):data[name] = widget.value()elif isinstance(widget, QDateTimeEdit):data[name] = widget.dateTime().toString("yyyy-MM-dd HH:mm:ss")elif isinstance(widget, QComboBox):data[name] = widget.currentData()self.table.insert(data) # 插入数据self.load_data() # 重新加载数据def delete_data(self):# 删除数据逻辑selected_rows = set()for item in self.table_widget.selectedItems():selected_rows.add(item.row())if not selected_rows:QMessageBox.warning(self, "警告", "请选择要删除的行")returnconfirm = QMessageBox.warning(self, "确认删除", "你确定要删除选中的行吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:for row in sorted(selected_rows, reverse=True):# 获取要删除的记录的IDid_item = self.table_widget.item(row, 0) # 假设ID在第一列if id_item is not None:record_id = int(id_item.text())# 从数据库中删除记录self.table.delete(id=record_id)# 从表格中删除行self.table_widget.removeRow(row)def clear_data(self):confirm = QMessageBox.warning(self, "确认清空", "你确定要清空所有数据吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:self.table.delete() # 清空数据表self.load_data() # 重新加载数据def export_data(self):self.export_thread = ExportThread(self.table)self.export_thread.finished.connect(self.export_finished)self.export_thread.start()def export_finished(self):QMessageBox.information(self, "导出完成", "数据已成功导出到 exported_data.xlsx")def toggle_search_block(self):if self.search_group.isVisible():self.search_group.setVisible(False)self.hide_search_button.setText("显示搜索块")else:self.search_group.setVisible(True)self.hide_search_button.setText("隐藏搜索块")def prev_page(self):current_page = int(self.page_input.text()) # 获取当前页码if current_page > 1:self.page_input.setText(str(current_page - 1)) # 更新页码self.load_data() # 重新加载数据def next_page(self):current_page = int(self.page_input.text()) # 获取当前页码page_size = int(self.page_size_combo.currentText()) # 获取每页显示条数total_records = int(self.total_records_label.text().split(": ")[1]) # 获取总记录数if (current_page * page_size) < total_records:self.page_input.setText(str(current_page + 1)) # 更新页码self.load_data() # 重新加载数据def jump_to_page(self):self.load_data() # 重新加载数据def show_detail(self, row):dialog = QDialog(self) # 创建对话框dialog.setWindowTitle("数据详情") # 设置对话框标题form_layout = QFormLayout(dialog) # 创建表单布局dialog.setLayout(form_layout) # 设置对话框布局for i, field in enumerate(self.config['fields']):label = field['label'] # 获取字段标签value = self.table_widget.item(row, i).text() # 获取表格项文本form_layout.addRow(label, QLabel(value)) # 添加标签和值到布局buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) # 创建按钮框buttons.accepted.connect(dialog.accept) # 连接确认按钮信号form_layout.addRow(buttons) # 添加按钮框到布局dialog.exec() # 显示对话框def edit_data(self, row):dialog = QDialog(self) # 创建对话框dialog.setWindowTitle("编辑数据") # 设置对话框标题form_layout = QFormLayout(dialog) # 创建表单布局dialog.setLayout(form_layout) # 设置对话框布局form_widgets = {} # 表单部件字典for i, field in enumerate(self.config['fields']):label = field['label'] # 获取字段标签value = self.table_widget.item(row, i).text() # 获取表格项文本if field['type'] == 'id':widget = QLineEdit(value) # 创建标签框,ID不可编辑form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局widget.setReadOnly(True)elif field['type'] == 'text':widget = QLineEdit(value) # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'number':widget = QLineEdit(value) # 创建数字输入框validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'datetime':widget = QDateTimeEdit()widget.setDisplayFormat("yyyy-MM-dd hh:mm:ss") # 设置显示格式widget.setCalendarPopup(True) # 启用弹出式日历选择widget.setDateTime(QDateTime.fromString(value, "yyyy-MM-dd hh:mm:ss"))form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setDateTime(self.foreign_value)elif field['type'] == 'select':widget = QComboBox()for option in field['options']:widget.addItem(option['label'], option['value'])index = widget.findText(value)widget.setCurrentIndex(index)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setEditable(False)widget.setCurrentIndex(self.foreign_value)else:widget = QLineEdit(value) # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # 创建按钮框buttons.accepted.connect(dialog.accept) # 连接确认按钮信号buttons.rejected.connect(dialog.reject) # 连接取消按钮信号form_layout.addRow(buttons) # 添加按钮框到布局if dialog.exec() == QDialog.DialogCode.Accepted:data = {}for name, widget in form_widgets.items():if isinstance(widget, QLineEdit) or isinstance(widget, QLabel):data[name] = widget.text()elif isinstance(widget, QSpinBox):data[name] = widget.value()elif isinstance(widget, QDateTimeEdit):data[name] = widget.dateTime().toString("yyyy-MM-dd HH:mm:ss")elif isinstance(widget, QComboBox):data[name] = widget.currentData()self.table.update(data, ['id']) # 更新数据self.load_data() # 重新加载数据def delete_data_row(self, row):confirm = QMessageBox.warning(self, "确认删除", "你确定要删除选中的行吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:# 获取要删除的记录的IDid_item = self.table_widget.item(row, 0) # 假设ID在第一列if id_item is not None:record_id = int(id_item.text())# 从数据库中删除记录self.table.delete(id=record_id)# 从表格中删除行self.table_widget.removeRow(row)def drill_data_row(self, row: int, drill: dict):# 获取记录的IDid_item = self.table_widget.item(row, 0) # 假设ID在第一列if id_item is not None:record_id = int(id_item.text()) # 数据IDbutton_name = drill['button_name']drill_form_config_json_path = drill['form_config_json']foreign_key = drill['foreign_key']drill_form_config_json_data = json.load(open(drill_form_config_json_path, 'r', encoding='utf-8'))window_id = f"crud_widget_{drill_form_config_json_data['id']}"# 防止多次点击打开多个窗口if not hasattr(self, window_id) or getattr(self, window_id) is None:if 'parent_id' in [f['name'] for f in drill_form_config_json_data['fields']]:widget: TreeWidget = TreeWidget(form_json=drill_form_config_json_path, foreign_key=foreign_key,foreign_value=record_id)else:passwidget: CrudWidget = CrudWidget(form_json=drill_form_config_json_path, foreign_key=foreign_key,foreign_value=record_id)widget.isVisible()widget.isEnabled()setattr(self, window_id, widget)widget.show()else:# 每次重现的时候将参数强制赋值进去widget: CrudWidget = getattr(self, window_id)setattr(widget, "form_json", drill_form_config_json_path)setattr(widget, "foreign_key", foreign_key)setattr(widget, "foreign_value", record_id)widget.show()widget.activateWindow() # 激活窗口widget.raise_() # 将窗口提升到最前面def showEvent(self, event):"""这个函数必须重写一下。子窗口关闭后,引用还在上一层存着,并不会销毁,而是隐藏。再次显示并不会初始化。所以在显示的时候我们手动初始化一下。"""super().showEvent(event)self.load_data() # 加载数据class TreeWidget(QWidget):def __init__(self, form_json: str | dict = "./tree_form_config.json", foreign_key=None, foreign_value=None):""":param form_json 表单文件:param foreign_key 外键名称:param foreign_value 外键值"""super().__init__()self.foreign_key = foreign_keyself.foreign_value = foreign_value# 初始化解析表单配置try:with open(form_json, 'r', encoding='utf-8') as f:self.config = json.load(f) # 加载表单配置except FileNotFoundError:raise ValueError(f"表单配置文件未找到:{self.config}")if 'id' not in self.config:raise ValueError("表单配置文件格式错误【无id参数】")if 'fields' not in self.config:raise ValueError("表单配置文件格式错误【无fields参数】")if 'title' not in self.config:raise ValueError("表单配置文件格式错误【无title参数】")if 'database' not in self.config:raise ValueError("表单配置文件格式错误【无database参数】")if 'table' not in self.config:raise ValueError("表单配置文件格式错误【无table参数】")fields = self.config['fields']if type(fields) is not list:raise ValueError("表单配置文件格式错误【fields必须是list类型】")for field in fields:if 'name' not in field:raise ValueError("表单配置文件格式错误【字段缺少name】")if 'label' not in field:raise ValueError("表单配置文件格式错误【字段缺少label】")if 'type' not in field:raise ValueError("表单配置文件格式错误【字段缺少type】")if 'parent_id' not in [f['name'] for f in fields]:raise ValueError("表单配置文件格式错误【fields缺少parent_id】")self.id = self.config['id']self.title = self.config['title']self.database = self.config['database']self.table = self.config['table']self.fields = self.config['fields']self.drill = self.config['drill']self.setWindowTitle(self.title) # 设置窗口标题self.setGeometry(100, 100, 800, 600) # 设置窗口位置和大小# self.title_bar = QCustomTitleBar(self, windowTitle=self.title)# self.resizer = WindowResizer(self)self.setStyleSheet("""QLineEdit, QSpinBox {padding: 5px;border: 1px solid #d0d0d0;border-radius: 5px;}QLabel {font-size: 14px;color: #333333;}""")self.db = dataset.connect(self.database) # 连接数据库self.table = self.db[self.config['table']] # 获取数据表self.init_ui() # 初始化UIself.load_data() # 加载数据def init_ui(self):self.layout = QVBoxLayout(self) # 创建垂直布局self.init_search_block() # 初始化搜索块self.init_data_block() # 初始化数据块def init_search_block(self):"""初始化搜索块"""if hasattr(self, 'search_group') and hasattr(self, 'search_group_layout'):while self.search_group_layout.count():item = self.search_group_layout.takeAt(0)widget = item.widget()if widget is not None:widget.deleteLater()else:self.search_group = QGroupBox() # 创建搜索区组self.layout.addWidget(self.search_group) # 将搜索布局添加到主布局self.search_group_layout = QHBoxLayout() # 创建水平布局self.search_group.setLayout(self.search_group_layout) # 设置组布局self.search_widgets = {} # 搜索部件字典for field in self.config['fields']:if field['is_search']: # 是否为搜索字段进行判断label = field['label'] # 获取字段标签widget = Noneif field['type'] == 'text':widget = QLineEdit() # 创建输入框widget.returnPressed.connect(self.search_data) # 监听回车键elif field['type'] == 'number':widget = QLineEdit() # 创建数字输入框validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)widget.returnPressed.connect(self.search_data) # 监听回车键elif field['type'] == 'datetime':widget = QDateTimeEdit()widget.setDisplayFormat("yyyy-MM-dd hh:mm:ss") # 设置显示格式widget.setCalendarPopup(True) # 启用弹出式日历选择elif field['type'] == 'select':widget = QComboBox()widget.currentIndexChanged.connect(self.search_data) # 监听切换selection事件self.update_parent_options(widget)else:widget = QLineEdit() # 创建输入框widget.returnPressed.connect(self.search_data) # 监听回车键self.search_widgets[field['name']] = widget # 将输入框添加到字典self.search_group_layout.addWidget(QLabel(label)) # 添加标签到布局self.search_group_layout.addWidget(widget) # 添加输入框到布局self.search_button = QPushButton("搜索") # 创建搜索按钮self.search_button.clicked.connect(self.search_data) # 连接搜索按钮信号self.reset_button = QPushButton("重置") # 创建重置按钮self.reset_button.clicked.connect(self.reset_search) # 连接重置按钮信号self.search_group_layout.addWidget(self.search_button) # 添加搜索按钮到布局self.search_group_layout.addWidget(self.reset_button) # 添加重置按钮到布局def init_data_block(self):self.data_layout = QVBoxLayout() # 创建垂直布局control_group = QGroupBox() # 创建控制区组control_group_layout = QVBoxLayout() # 创建垂直布局control_group.setLayout(control_group_layout) # 设置组布局self.control_layout = QHBoxLayout() # 创建水平布局self.add_button = QPushButton("新增") # 创建新增按钮self.add_button.clicked.connect(self.add_data) # 连接新增按钮信号self.delete_button = QPushButton("删除") # 创建删除按钮self.delete_button.clicked.connect(self.delete_data) # 连接删除按钮信号self.clear_button = QPushButton("清空") # 创建清空按钮self.clear_button.clicked.connect(self.clear_data) # 连接清空按钮信号self.refresh_button = QPushButton("刷新数据") # 创建刷新数据按钮self.refresh_button.clicked.connect(self.load_data) # 连接刷新数据按钮信号# 初始状态为收起self.is_expanded = Falseself.expand_button = QPushButton("一键展开") # 创建刷新数据按钮self.expand_button.clicked.connect(self.toggle_expand_collapse) # 连接刷新数据按钮信号self.control_layout.addWidget(self.add_button) # 添加新增按钮到布局self.control_layout.addWidget(self.delete_button) # 添加删除按钮到布局self.control_layout.addWidget(self.clear_button) # 添加清空按钮到布局self.control_layout.addStretch() # 添加伸缩项self.control_layout.addWidget(self.expand_button) # 添加展开数据按钮到布局self.control_layout.addWidget(self.refresh_button) # 添加刷新数据按钮到布局control_group_layout.addLayout(self.control_layout) # 将控制布局添加到数据布局self.data_layout.addWidget(control_group) # 将控制布局添加到数据布局self.tree_widget = QTreeWidget() # 创建树形部件# "详情", "编辑", "删除"为基础按钮,外加钻取按钮self.tree_widget.setColumnCount(len(self.config['fields']) + 3 + len(self.drill)) # 设置列数headers = [field['label'] for field in self.config['fields']] + ["详情", "编辑", "删除"] + [d['button_name'] ford inself.drill] # 设置表头self.tree_widget.setHeaderLabels(headers) # 设置表头self.tree_widget.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # 设置列宽自动调整self.tree_widget.setSortingEnabled(True) # 启用排序self.data_layout.addWidget(self.tree_widget) # 将树形部件添加到数据布局self.layout.addLayout(self.data_layout) # 将数据布局添加到主布局def load_data(self):self.tree_widget.clear() # 清空树形部件conditions = {} # 搜索条件字典if self.foreign_key and self.foreign_value:conditions[self.foreign_key] = self.foreign_valuedata = self.table.all(**conditions) # 查询数据self.build_tree(list(data)) # 构建树形结构self.tree_widget.expandAll() # 展开树结构def build_tree(self, data):nodes = {}for row_data in data:item = []for field in self.config['fields']:if field['name'] in row_data:item.append(str(row_data[field['name']]))else:item.append(None)node = QTreeWidgetItem(item)nodes[row_data['id']] = nodefor row_data in data:if row_data['parent_id'] is None:self.tree_widget.addTopLevelItem(nodes[row_data['id']])else:parent_node = nodes.get(row_data['parent_id'])if parent_node is None:parent_node = nodes.get(int(row_data['parent_id']))if parent_node is not None:parent_node.addChild(nodes[row_data['id']])else:self.tree_widget.addTopLevelItem(nodes[row_data['id']])self.add_buttons(nodes[row_data['id']])def add_buttons(self, node):detail_button = QPushButton("详情") # 创建详情按钮detail_button.clicked.connect(lambda _, n=node: self.show_detail(n)) # 连接详情按钮信号self.tree_widget.setItemWidget(node, len(self.config['fields']), detail_button) # 设置详情按钮到单元格edit_button = QPushButton("编辑") # 创建编辑按钮edit_button.clicked.connect(lambda _, n=node: self.edit_data(n)) # 连接编辑按钮信号self.tree_widget.setItemWidget(node, len(self.config['fields']) + 1, edit_button) # 设置编辑按钮到单元格delete_button = QPushButton("删除") # 创建删除按钮delete_button.clicked.connect(lambda _, n=node: self.delete_data_node(n)) # 连接删除按钮信号self.tree_widget.setItemWidget(node, len(self.config['fields']) + 2, delete_button) # 设置删除按钮到单元格# 数据钻取按钮for index, d in enumerate(self.drill):drill_button = QPushButton(d['button_name']) # 创建钻取按钮drill_button.clicked.connect(lambda _, n=node: self.drill_data_row(n, d)) # 连接删除按钮信号self.tree_widget.setItemWidget(node, len(self.config['fields']) + 2 + index + 1, drill_button) # 设置删除按钮到单元格def search_data(self):conditions = {} # 搜索条件字典if self.foreign_key and self.foreign_value:conditions[self.foreign_key] = self.foreign_valuefor name, widget in self.search_widgets.items():if isinstance(widget, QComboBox):value = widget.currentData()else:value = widget.text() # 获取输入框文本if value:conditions[name] = value # 添加到搜索条件字典if 'parent_id' in conditions and conditions['parent_id'] is not None:params = ""for index, (k, v) in enumerate(conditions.items()):params = params + f" {k}=:{k}"if index != len(conditions.items()) - 1:params = params + " and"query = rf"""WITH RECURSIVE descendants AS (SELECT id, name, parent_idFROM {self.config['table']}WHERE {params}UNION ALLSELECT cdt.id, cdt.name, cdt.parent_idFROM {self.config['table']} cdtINNER JOIN descendants d ON cdt.parent_id = d.id)SELECT * FROM descendants"""data = list(self.db.query(query, **conditions))else:query = self.table.find(**conditions) # 根据条件查询数据data = list(query) # 获取查询结果if hasattr(self, 'tree_widget'):self.tree_widget.clear() # 清空树形部件else:self.tree_widget = QTreeWidget() # 创建树形部件self.build_tree(data) # 构建树形结构self.tree_widget.expandAll() # 展开树结构def reset_search(self):self.init_search_block()self.load_data() # 重新加载数据def add_data(self):dialog = QDialog(self) # 创建对话框dialog.setWindowTitle("新增数据") # 设置对话框标题form_layout = QFormLayout(dialog) # 创建表单布局dialog.setLayout(form_layout) # 设置对话框布局form_widgets = {} # 表单部件字典for field in self.config['fields']:label = field['label'] # 获取字段标签if field['type'] == 'id':passelif field['type'] == 'text':widget = QLineEdit() # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'number':widget = QLineEdit() # 创建数字输入框validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'datetime':widget = QDateTimeEdit()widget.setDisplayFormat("yyyy-MM-dd hh:mm:ss") # 设置显示格式widget.setCalendarPopup(True) # 启用弹出式日历选择widget.setDateTime(QDateTime.currentDateTime())form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setDateTime(self.foreign_value)elif field['type'] == 'select':widget = QComboBox()self.update_parent_options(widget)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setEditable(False)widget.setCurrentIndex(self.foreign_value)else:widget = QLineEdit() # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # 创建按钮框buttons.accepted.connect(dialog.accept) # 连接确认按钮信号buttons.rejected.connect(dialog.reject) # 连接取消按钮信号form_layout.addRow(buttons) # 添加按钮框到布局if dialog.exec() == QDialog.DialogCode.Accepted:data = {}for name, widget in form_widgets.items():if isinstance(widget, QLineEdit):data[name] = widget.text()elif isinstance(widget, QSpinBox):data[name] = widget.value()elif isinstance(widget, QDateTimeEdit):data[name] = widget.dateTime().toString("yyyy-MM-dd HH:mm:ss")elif isinstance(widget, QComboBox):data[name] = widget.currentData()self.table.insert(data) # 插入数据self.load_data() # 重新加载数据def delete_data(self):selected_items = self.tree_widget.selectedItems()if not selected_items:QMessageBox.warning(self, "警告", "请选择要删除的行")returnconfirm = QMessageBox.warning(self, "确认删除", "你确定要删除选中的行吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:for item in selected_items:self.delete_item(item)def delete_item(self, item):id_item = item.text(0) # 假设ID在第一列if id_item is not None:record_id = int(id_item)self.table.delete(id=record_id)parent = item.parent()if parent is None:self.tree_widget.takeTopLevelItem(self.tree_widget.indexOfTopLevelItem(item))else:parent.removeChild(item)def toggle_expand_collapse(self):if self.is_expanded:self.tree_widget.collapseAll()self.expand_button.setText("一键展开")else:self.tree_widget.expandAll()self.expand_button.setText("一键收起")self.is_expanded = not self.is_expandeddef clear_data(self):confirm = QMessageBox.warning(self, "确认清空", "你确定要清空所有数据吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:self.table.delete() # 清空数据表self.load_data() # 重新加载数据def update_parent_options(self, combo_box, self_id=None):combo_box.clear()combo_box.addItem("请选择父节点", None)conditions = {} # 搜索条件字典if self.foreign_key and self.foreign_value:conditions[self.foreign_key] = self.foreign_valuedata = self.table.find(**conditions)for row_data in data:# 有些时候父节点和子节点是同一个,所以需要排除这种情况if self_id is not None and str(row_data['id']) == str(self_id):continuecombo_box.addItem(row_data['name'], row_data['id'])def show_detail(self, node):dialog = QDialog(self) # 创建对话框dialog.setWindowTitle("数据详情") # 设置对话框标题form_layout = QFormLayout(dialog) # 创建表单布局dialog.setLayout(form_layout) # 设置对话框布局for i, field in enumerate(self.config['fields']):label = field['label'] # 获取字段标签value = node.text(i) # 获取表格项文本form_layout.addRow(label, QLabel(value)) # 添加标签和值到布局buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) # 创建按钮框buttons.accepted.connect(dialog.accept) # 连接确认按钮信号form_layout.addRow(buttons) # 添加按钮框到布局dialog.exec() # 显示对话框def edit_data(self, node):dialog = QDialog(self) # 创建对话框dialog.setWindowTitle("编辑数据") # 设置对话框标题form_layout = QFormLayout(dialog) # 创建表单布局dialog.setLayout(form_layout) # 设置对话框布局form_widgets = {} # 表单部件字典for i, field in enumerate(self.config['fields']):label = field['label'] # 获取字段标签value = node.text(i) # 获取节点文本if field['type'] == 'id':widget = QLineEdit(value) # 创建标签框,ID不可编辑form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局widget.setReadOnly(True)elif field['type'] == 'text':widget = QLineEdit(value) # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'number':widget = QLineEdit(value) # 创建数字输入框validator = QRegularExpressionValidator(r'^\d*$')widget.setValidator(validator)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))elif field['type'] == 'datetime':widget = QDateTimeEdit()widget.setDisplayFormat("yyyy-MM-dd hh:mm:ss") # 设置显示格式widget.setCalendarPopup(True) # 启用弹出式日历选择widget.setDateTime(QDateTime.fromString(value, "yyyy-MM-dd hh:mm:ss"))form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setDateTime(self.foreign_value)elif field['type'] == 'select':widget = QComboBox()self.update_parent_options(widget, self_id=node.text(0))if value and value != 'None':index = widget.findData(int(value))widget.setCurrentIndex(index)form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setEditable(False)widget.setCurrentIndex(self.foreign_value)else:widget = QLineEdit(value) # 创建输入框form_widgets[field['name']] = widget # 将部件添加到字典form_layout.addRow(label, widget) # 添加标签和部件到布局if field['name'] == self.foreign_key: # 如果是外键字段,则隐藏输入框widget.setReadOnly(True)widget.setText(str(self.foreign_value))buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # 创建按钮框buttons.accepted.connect(dialog.accept) # 连接确认按钮信号buttons.rejected.connect(dialog.reject) # 连接取消按钮信号form_layout.addRow(buttons) # 添加按钮框到布局if dialog.exec() == QDialog.DialogCode.Accepted:data = {}for name, widget in form_widgets.items():if isinstance(widget, QLineEdit) or isinstance(widget, QLabel):data[name] = widget.text()elif isinstance(widget, QSpinBox):data[name] = widget.value()elif isinstance(widget, QDateTimeEdit):data[name] = widget.dateTime().toString("yyyy-MM-dd HH:mm:ss")elif isinstance(widget, QComboBox):data[name] = widget.currentData()if str(data['parent_id']) == str(data['id']):raise Exception("父节点ID不能等于自身ID")# 还有一种情况要排除掉,那就是两个节点,互为其父节点。one = self.table.find_one(id=data['parent_id'])if one and one['parent_id'] == data['id']:raise Exception("父节点不能为其子节点")self.table.update(data, ['id']) # 更新数据self.load_data() # 重新加载数据def delete_data_node(self, node):confirm = QMessageBox.warning(self, "确认删除", "你确定要删除选中的行吗?",QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:id_item = node.text(0) # 假设ID在第一列if id_item is not None:record_id = int(id_item)self.table.delete(id=record_id)parent = node.parent()if parent is None:self.tree_widget.takeTopLevelItem(self.tree_widget.indexOfTopLevelItem(node))else:parent.removeChild(node)def drill_data_row(self, node, drill: dict):# 获取记录的IDrecord_id = node.text(0) # 假设ID在第一列if record_id is not None:button_name = drill['button_name']drill_form_config_json_path = drill['form_config_json']foreign_key = drill['foreign_key']drill_form_config_json_data = json.load(open(drill_form_config_json_path, 'r', encoding='utf-8'))window_id = f"crud_widget_{drill_form_config_json_data['id']}"# 防止多次点击打开多个窗口if not hasattr(self, window_id) or getattr(self, window_id) is None:if 'parent_id' in [f['name'] for f in drill_form_config_json_data['fields']]:widget: TreeWidget = TreeWidget(form_json=drill_form_config_json_path, foreign_key=foreign_key,foreign_value=record_id)else:passwidget: CrudWidget = CrudWidget(form_json=drill_form_config_json_path, foreign_key=foreign_key,foreign_value=record_id)widget.isVisible()widget.isEnabled()setattr(self, window_id, widget)widget.show()else:# 每次重现的时候将参数强制赋值进去widget = getattr(self, window_id)setattr(widget, "form_json", drill_form_config_json_path)setattr(widget, "foreign_key", foreign_key)setattr(widget, "foreign_value", record_id)widget.show()widget.activateWindow() # 激活窗口widget.raise_() # 将窗口提升到最前面def showEvent(self, event):"""这个函数必须重写一下。子窗口关闭后,引用还在上一层存着,并不会销毁,而是隐藏。再次显示并不会初始化。所以在显示的时候我们手动初始化一下。"""super().showEvent(event)self.init_search_block()self.load_data() # 加载数据class ExistingWindow(QWidget):def __init__(self, parent=None):super(ExistingWindow, self).__init__(parent)self.setWindowTitle("Existing Window")self.resize(600, 400)self.title_bar = QCustomTitleBar(self, windowTitle="配置")self.resizer = WindowResizer(self)self.layout = QVBoxLayout()self.setLayout(self.layout)# 其他初始化代码self.button = QPushButton("Test")self.layout.addWidget(self.button)self.crud_widget = None # 用于存储CrudWidget实例def btn_event():# 防止多次点击打开多个窗口if not hasattr(self, "crud_widget") or self.crud_widget is None:self.crud_widget = CrudWidget(form_json='crud_form_config.json')self.crud_widget.show()else:self.crud_widget.show()self.crud_widget.activateWindow() # 激活窗口self.crud_widget.raise_() # 将窗口提升到最前面self.button.clicked.connect(btn_event)if __name__ == '__main__':app = QApplication([])main = ExistingWindow()main.show()app.exec()
三、效果图
略
这篇关于pyside6增删改查插件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!