pyside6增删改查插件

2024-09-02 07:44
文章标签 插件 改查 增删 pyside6

本文主要是介绍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增删改查插件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129398

相关文章

CnPlugin是PL/SQL Developer工具插件使用教程

《CnPlugin是PL/SQLDeveloper工具插件使用教程》:本文主要介绍CnPlugin是PL/SQLDeveloper工具插件使用教程,具有很好的参考价值,希望对大家有所帮助,如有错... 目录PL/SQL Developer工具插件使用安装拷贝文件配置总结PL/SQL Developer工具插

maven中的maven-antrun-plugin插件示例详解

《maven中的maven-antrun-plugin插件示例详解》maven-antrun-plugin是Maven生态中一个强大的工具,尤其适合需要复用Ant脚本或实现复杂构建逻辑的场景... 目录1. 核心功能2. 典型使用场景3. 配置示例4. 关键配置项5. 优缺点分析6. 最佳实践7. 常见问题

MybatisX快速生成增删改查的方法示例

《MybatisX快速生成增删改查的方法示例》MybatisX是基于IDEA的MyBatis/MyBatis-Plus开发插件,本文主要介绍了MybatisX快速生成增删改查的方法示例,文中通过示例代... 目录1 安装2 基本功能2.1 XML跳转2.2 代码生成2.2.1 生成.xml中的sql语句头2

MyBatis分页插件PageHelper深度解析与实践指南

《MyBatis分页插件PageHelper深度解析与实践指南》在数据库操作中,分页查询是最常见的需求之一,传统的分页方式通常有两种内存分页和SQL分页,MyBatis作为优秀的ORM框架,本身并未提... 目录1. 为什么需要分页插件?2. PageHelper简介3. PageHelper集成与配置3.

Maven 插件配置分层架构深度解析

《Maven插件配置分层架构深度解析》:本文主要介绍Maven插件配置分层架构深度解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Maven 插件配置分层架构深度解析引言:当构建逻辑遇上复杂配置第一章 Maven插件配置的三重境界1.1 插件配置的拓扑

Idea插件MybatisX失效的问题解决

《Idea插件MybatisX失效的问题解决》:本文主要介绍Idea插件MybatisX失效的问题解决,详细的介绍了4种问题的解决方法,具有一定的参考价值,感兴趣的可以了解一下... 目录一、重启idea或者卸载重装MyBATis插件(无需多言)二、检查.XML文件与.Java(该文件后缀Idea可能会隐藏

springboot使用Scheduling实现动态增删启停定时任务教程

《springboot使用Scheduling实现动态增删启停定时任务教程》:本文主要介绍springboot使用Scheduling实现动态增删启停定时任务教程,具有很好的参考价值,希望对大家有... 目录1、配置定时任务需要的线程池2、创建ScheduledFuture的包装类3、注册定时任务,增加、删

PyQt6/PySide6中QTableView类的实现

《PyQt6/PySide6中QTableView类的实现》本文主要介绍了PyQt6/PySide6中QTableView类的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学... 目录1. 基本概念2. 创建 QTableView 实例3. QTableView 的常用属性和方法

PyQt6/PySide6中QTreeView类的实现

《PyQt6/PySide6中QTreeView类的实现》QTreeView是PyQt6或PySide6库中用于显示分层数据的控件,本文主要介绍了PyQt6/PySide6中QTreeView类的实现... 目录1. 基本概念2. 创建 QTreeView 实例3. QTreeView 的常用属性和方法属性

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient