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

相关文章

在Mysql环境下对数据进行增删改查的操作方法

《在Mysql环境下对数据进行增删改查的操作方法》本文介绍了在MySQL环境下对数据进行增删改查的基本操作,包括插入数据、修改数据、删除数据、数据查询(基本查询、连接查询、聚合函数查询、子查询)等,并... 目录一、插入数据:二、修改数据:三、删除数据:1、delete from 表名;2、truncate

IDEA常用插件之代码扫描SonarLint详解

《IDEA常用插件之代码扫描SonarLint详解》SonarLint是一款用于代码扫描的插件,可以帮助查找隐藏的bug,下载并安装插件后,右键点击项目并选择“Analyze”、“Analyzewit... 目录SonajavascriptrLint 查找隐藏的bug下载安装插件扫描代码查看结果总结Sona

[MySQL表的增删改查-进阶]

🌈个人主页:努力学编程’ ⛅个人推荐: c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构,刷题刻不容缓:点击一起刷题 🌙心灵鸡汤:总有人要赢,为什么不能是我呢 💻💻💻数据库约束 🔭🔭🔭约束类型 not null: 指示某列不能存储 NULL 值unique: 保证某列的每行必须有唯一的值default: 规定没有给列赋值时的默认值.primary key:

Maven(插件配置和生命周期的绑定)

1.这篇文章很好,介绍的maven插件的。 2.maven的source插件为例,可以把源代码打成包。 Goals Overview就可以查看该插件下面所有的目标。 这里我们要使用的是source:jar-no-fork。 3.查看source插件的example,然后配置到riil-collect.xml中。  <build>   <plugins>    <pl

jenkins 插件执行shell命令时,提示“Command not found”处理方法

首先提示找不到“Command not found,可能我们第一反应是查看目标机器是否已支持该命令,不过如果相信能找到这里来的朋友估计遇到的跟我一样,其实目标机器是没有问题的通过一些远程工具执行shell命令是可以执行。奇怪的就是通过jenkinsSSH插件无法执行,经一番折腾各种搜索发现是jenkins没有加载/etc/profile导致。 【解决办法】: 需要在jenkins调用shell脚

Jenkins 插件 地址证书报错问题解决思路

问题提示摘要: SunCertPathBuilderException: unable to find valid certification path to requested target...... 网上很多的解决方式是更新站点的地址,我这里修改了一个日本的地址(清华镜像也好),其实发现是解决不了上述的报错问题的,其实,最终拉去插件的时候,会提示证书的问题,几经周折找到了其中一遍博文

eclipse安装subversion(SVN)版本控制插件

陈科肇 查看插件更新站点 网址:http://subclipse.tigris.org/servlets/ProjectProcess?pageID=p4wYuA 网站截图: 根据自己的eclipse版本,选择需要的更新站点. 使用eclipse集成subservion插件 Help > Install New Software…> 等待下载安装插件…

Weex入门教程之2,Android Studio安装Weex插件

插件位置及描述 https://plugins.jetbrains.com/idea/plugin/8460-weex 貌似对windows还不是很支持,先放着吧。 安装 插件功能 先预览下都有什么功能 安装完成Weex插件后,如果在main toolbar找不到这些功能图标,那么就需要手动添加到main toolbar 添加到main toolbar 红框内就是

mybatis框架基础以及自定义插件开发

文章目录 框架概览框架预览MyBatis框架的核心组件MyBatis框架的工作原理MyBatis框架的配置MyBatis框架的最佳实践 自定义插件开发1. 添加依赖2. 创建插件类3. 配置插件4. 启动类中注册插件5. 测试插件 参考文献 框架概览 MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,为开发者提供了极大的灵活性和便利性。以下是关于M

08 增删查功能

划重点: lable 标签keyup:键盘事件标签内添加样式:style使用事件修饰符:preventforEach :遍历 数组indexOf: 可以返回要查询的某个字符串值在整个字符串中首次出现的位置下标findIndex:返回传入一个测试条件(函数)符合条件数组的首个元素的位置splice:向/从数组中添加/删除项目,然后返回被删除后的新的项目数组 黑椒蟹 一对: <!DOCTYPE