<think>我们之前使用的数据库查询返回的是元组列表,每个元组代表一行,通过索引访问字段。
如果连接池配置为返回字典(例如使用pymysql.cursors.DictCursor),那么查询结果将是字典列表,每个字典的键是字段名,值是对应的值。
为了适应这种变化,我们需要修改代码中处理查询结果的部分。
修改点:
1. 在连接池配置中指定cursorclass为pymysql.cursors.DictCursor。
2. 在查询数据时,使用字段名(而不是索引)来访问数据。
3. 在EditWindow中,根据字段名获取数据并填充表单。
4. 在主窗口加载数据到表格时,使用字段名来获取每列的值。
下面我们修改代码以支持返回字典。</think>如果数据库连接池返回的是字典列表(即每行数据是一个字典,键为列名),我们需要调整代码以适应这种数据结构。以下是修改后的完整实现,使用字典格式处理查询结果:
```python
import sys
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QTableWidget, QTableWidgetItem,
QMenu, QAction, QMessageBox, QAbstractItemView,
QVBoxLayout, QLabel, QLineEdit, QPushButton, QWidget,
QStatusBar, QTextEdit, QFormLayout
)
from PyQt5.QtCore import Qt, pyqtSignal
import pymysql
from pymysql.cursors import DictCursor
from dbutils.pooled_db import PooledDB
# 数据库连接池配置 - 使用DictCursor返回字典
DB_POOL = PooledDB(
creator=pymysql,
maxconnections=5,
host='localhost',
user='your_user',
password='your_password',
database='medical_db',
charset='utf8mb4',
cursorclass=DictCursor # 关键:使用字典游标
)
class EditWindow(QMainWindow):
"""编辑症状详细信息窗口"""
operation_completed = pyqtSignal() # 自定义信号,操作完成时发射
def __init__(self, symptom_id=None, symptom_name=None, parent=None):
"""
初始化编辑窗口
:param symptom_id: 症状ID
:param symptom_name: 症状名称
:param parent: 父窗口
"""
super().__init__(parent)
self.symptom_id = symptom_id
self.symptom_name = symptom_name
self.setWindowTitle(f'编辑症状: {symptom_name}' if symptom_name else '添加新症状')
self.setGeometry(300, 300, 600, 500) # 设置窗口大小
# 设置中央部件
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
# 设置状态栏
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
# 初始化UI
self.setup_ui()
# 如果是编辑模式,加载症状详细信息
if symptom_id:
self.load_symptom_details()
def setup_ui(self):
"""设置UI界面"""
# 主布局
main_layout = QVBoxLayout(self.central_widget)
# 表单布局
form_layout = QFormLayout()
# 症状名称
self.name_edit = QLineEdit()
form_layout.addRow(QLabel("症状名称:"), self.name_edit)
# 症状描述
self.description_edit = QTextEdit()
self.description_edit.setMinimumHeight(100)
form_layout.addRow(QLabel("症状描述:"), self.description_edit)
# 常见病因
self.causes_edit = QTextEdit()
self.causes_edit.setMinimumHeight(80)
form_layout.addRow(QLabel("常见病因:"), self.causes_edit)
# 治疗方法
self.treatment_edit = QTextEdit()
self.treatment_edit.setMinimumHeight(100)
form_layout.addRow(QLabel("治疗方法:"), self.treatment_edit)
# 相关科室
self.department_edit = QLineEdit()
form_layout.addRow(QLabel("相关科室:"), self.department_edit)
# 添加到主布局
main_layout.addLayout(form_layout)
# 提交按钮
self.submit_btn = QPushButton("提交")
self.submit_btn.clicked.connect(self.submit_data)
main_layout.addWidget(self.submit_btn)
# 取消按钮
self.cancel_btn = QPushButton("取消")
self.cancel_btn.clicked.connect(self.close)
main_layout.addWidget(self.cancel_btn)
def load_symptom_details(self):
"""从数据库加载症状详细信息(字典格式)"""
conn = DB_POOL.connection()
cursor = conn.cursor()
try:
# 查询症状详细信息 - 返回字典
sql = """
SELECT id, name, description, common_causes, treatment, department
FROM symptoms
WHERE id = %s
"""
cursor.execute(sql, (self.symptom_id,))
symptom_data = cursor.fetchone() # 返回字典
if symptom_data:
# 使用字段名访问数据
self.name_edit.setText(symptom_data['name'])
self.description_edit.setText(symptom_data['description'])
self.causes_edit.setText(symptom_data['common_causes'])
self.treatment_edit.setText(symptom_data['treatment'])
self.department_edit.setText(symptom_data['department'])
else:
self.status_bar.showMessage("未找到该症状的详细信息", 3000)
except Exception as e:
QMessageBox.critical(self, "数据库错误", f"加载数据失败: {str(e)}")
finally:
cursor.close()
conn.close()
def submit_data(self):
"""提交更新后的症状信息"""
# 获取表单数据
name = self.name_edit.text().strip()
description = self.description_edit.toPlainText().strip()
causes = self.causes_edit.toPlainText().strip()
treatment = self.treatment_edit.toPlainText().strip()
department = self.department_edit.text().strip()
# 验证输入
if not name:
self.status_bar.showMessage("症状名称不能为空!", 5000)
return
if not description:
self.status_bar.showMessage("症状描述不能为空!", 5000)
return
# 获取数据库连接
conn = DB_POOL.connection()
cursor = conn.cursor()
try:
if self.symptom_id:
# 更新现有症状 - 使用字典格式参数
sql = """
UPDATE symptoms
SET name = %(name)s, description = %(description)s,
common_causes = %(causes)s, treatment = %(treatment)s,
department = %(department)s
WHERE id = %(id)s
"""
params = {
'name': name,
'description': description,
'causes': causes,
'treatment': treatment,
'department': department,
'id': self.symptom_id
}
cursor.execute(sql, params)
self.status_bar.showMessage("症状信息更新成功!", 3000)
else:
# 添加新症状 - 使用字典格式参数
sql = """
INSERT INTO symptoms (name, description, common_causes, treatment, department)
VALUES (%(name)s, %(description)s, %(causes)s, %(treatment)s, %(department)s)
"""
params = {
'name': name,
'description': description,
'causes': causes,
'treatment': treatment,
'department': department
}
cursor.execute(sql, params)
self.status_bar.showMessage("新症状添加成功!", 3000)
# 获取新添加的症状ID
self.symptom_id = cursor.lastrowid
conn.commit()
# 发射操作完成信号
self.operation_completed.emit()
self.close()
except Exception as e:
conn.rollback()
QMessageBox.critical(self, "数据库错误", f"操作失败: {str(e)}")
finally:
cursor.close()
conn.close()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("症状管理系统")
self.setGeometry(100, 100, 1000, 600)
# 创建表格
self.table = QTableWidget()
# 列配置 - 与数据库字段对应
self.columns = ['id', 'name', 'description', 'common_causes', 'treatment', 'department']
self.column_headers = ['ID', '症状名称', '描述', '常见病因', '治疗方法', '相关科室']
self.table.setColumnCount(len(self.columns))
self.table.setHorizontalHeaderLabels(self.column_headers)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
# 设置列宽
self.table.setColumnWidth(0, 50) # ID
self.table.setColumnWidth(1, 150) # 名称
self.table.setColumnWidth(2, 200) # 描述
self.table.setColumnWidth(3, 200) # 病因
self.table.setColumnWidth(4, 200) # 治疗
self.table.setColumnWidth(5, 100) # 科室
# 设置右键菜单策略
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu)
self.setCentralWidget(self.table)
self.load_data() # 加载初始数据
# 存储编辑窗口引用
self.edit_window = None
def load_data(self):
"""从数据库加载症状数据到表格(字典格式)"""
conn = DB_POOL.connection()
cursor = conn.cursor()
try:
cursor.execute("SELECT id, name, description, common_causes, treatment, department FROM symptoms")
rows = cursor.fetchall() # 返回字典列表
self.table.setRowCount(len(rows))
for row_idx, row_data in enumerate(rows):
for col_idx, col_name in enumerate(self.columns):
value = row_data.get(col_name, "")
item = QTableWidgetItem(str(value) if value is not None else "")
# 设置为不可编辑
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
# 对于长文本,设置工具提示
if col_name in ['description', 'common_causes', 'treatment'] and value and len(value) > 20:
item.setToolTip(value[:100] + "..." if len(value) > 100 else value)
self.table.setItem(row_idx, col_idx, item)
except Exception as e:
QMessageBox.critical(self, "数据库错误", f"加载数据失败: {str(e)}")
finally:
cursor.close()
conn.close()
def show_context_menu(self, pos):
"""显示右键菜单"""
menu = QMenu()
# 创建菜单项
add_action = QAction("添加症状", self)
edit_action = QAction("编辑症状", self)
delete_action = QAction("删除症状", self)
# 连接信号
add_action.triggered.connect(self.add_symptom)
edit_action.triggered.connect(self.edit_symptom)
delete_action.triggered.connect(self.delete_symptom)
# 添加到菜单
menu.addAction(add_action)
menu.addAction(edit_action)
menu.addAction(delete_action)
# 显示菜单
menu.exec_(self.table.viewport().mapToGlobal(pos))
def add_symptom(self):
"""添加新症状"""
# 关闭已存在的编辑窗口
if self.edit_window:
self.edit_window.close()
self.edit_window = EditWindow(parent=self)
self.edit_window.operation_completed.connect(self.load_data) # 连接操作完成信号
self.edit_window.setWindowModality(Qt.ApplicationModal) # 设置为模态窗口
self.edit_window.show()
def edit_symptom(self):
"""编辑选中症状"""
selected_rows = self.table.selectionModel().selectedRows()
if not selected_rows:
QMessageBox.warning(self, "警告", "请先选择一行记录")
return
row_idx = selected_rows[0].row()
symptom_id = int(self.table.item(row_idx, 0).text()) # ID列
symptom_name = self.table.item(row_idx, 1).text() # 名称列
# 关闭已存在的编辑窗口
if self.edit_window:
self.edit_window.close()
# 创建编辑窗口,传入症状ID和名称
self.edit_window = EditWindow(
symptom_id=symptom_id,
symptom_name=symptom_name,
parent=self
)
self.edit_window.operation_completed.connect(self.load_data) # 连接操作完成信号
self.edit_window.setWindowModality(Qt.ApplicationModal) # 设置为模态窗口
self.edit_window.show()
def delete_symptom(self):
"""删除选中症状"""
selected_rows = self.table.selectionModel().selectedRows()
if not selected_rows:
QMessageBox.warning(self, "警告", "请先选择一行记录")
return
row_idx = selected_rows[0].row()
symptom_id = int(self.table.item(row_idx, 0).text())
symptom_name = self.table.item(row_idx, 1).text()
# 确认对话框
reply = QMessageBox.question(
self, '确认删除',
f"确定要删除症状 '{symptom_name}' (ID: {symptom_id}) 吗?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
conn = DB_POOL.connection()
cursor = conn.cursor()
try:
cursor.execute("DELETE FROM symptoms WHERE id = %s", (symptom_id,))
conn.commit()
self.load_data() # 刷新表格
QMessageBox.information(self, "成功", "症状已成功删除")
except Exception as e:
conn.rollback()
QMessageBox.critical(self, "数据库错误", f"删除失败: {str(e)}")
finally:
cursor.close()
conn.close()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
```
### 关键修改点说明
1. **数据库连接池配置字典游标**:
```python
DB_POOL = PooledDB(
# ...其他配置...
cursorclass=DictCursor # 关键:使用字典游标
)
```
- 添加 `cursorclass=DictCursor` 参数使查询结果返回字典格式
2. **编辑窗口加载数据(字典访问)**:
```python
def load_symptom_details(self):
# ...
cursor.execute(sql, (self.symptom_id,))
symptom_data = cursor.fetchone() # 返回字典
if symptom_data:
self.name_edit.setText(symptom_data['name'])
self.description_edit.setText(symptom_data['description'])
# ...其他字段...
```
- 使用字段名(如 `'name'`, `'description'`)而不是索引访问数据
3. **数据库操作使用命名参数**:
```python
# 更新操作使用命名参数
sql = """
UPDATE symptoms
SET name = %(name)s, description = %(description)s,
common_causes = %(causes)s, treatment = %(treatment)s,
department = %(department)s
WHERE id = %(id)s
"""
params = {
'name': name,
'description': description,
'causes': causes,
'treatment': treatment,
'department': department,
'id': self.symptom_id
}
cursor.execute(sql, params)
```
- 使用命名参数使SQL语句更清晰
- 参数传递使用字典格式
4. **主窗口加载表格数据(字典访问)**:
```python
# 定义列名映射
self.columns = ['id', 'name', 'description', 'common_causes', 'treatment', 'department']
self.column_headers = ['ID', '症状名称', '描述', '常见病因', '治疗方法', '相关科室']
# 加载数据
for row_idx, row_data in enumerate(rows):
for col_idx, col_name in enumerate(self.columns):
value = row_data.get(col_name, "")
item = QTableWidgetItem(str(value) if value is not None else "")
```
- 使用列名字典访问数据
- 通过 `row_data.get(col_name, "")` 安全获取值
### 字典格式的优势
1. **代码可读性**:
- 使用字段名(如 `row_data['name']`)比索引(如 `row_data[1]`)更直观
- 减少因列顺序变化导致的错误
2. **灵活性**:
- 更容易处理可选字段(使用 `get()` 方法提供默认值)
- 添加新字段时只需扩展列名列表,无需修改大量索引
3. **安全性**:
- 避免因列顺序变化导致的错误
- 更清晰的SQL语句参数绑定
### 使用字典格式的注意事项
1. **数据库表结构变化**:
- 如果数据库表结构变化(如添加/删除列),需要更新代码中的字段名列表
- 建议使用常量定义字段名
2. **性能考虑**:
- 字典格式比元组格式占用更多内存
- 对于大数据量查询,需考虑内存使用情况
3. **字段名大小写**:
- 不同数据库对字段名大小写处理不同
- MySQL默认不区分大小写,但建议代码中保持一致性
### 完整工作流程
1. **主窗口加载数据**:
- 执行SQL查询,返回字典列表
- 遍历字典列表,使用字段名获取值填充表格
2. **编辑症状**:
- 选择症状行,获取ID和名称
- 打开编辑窗口,传入ID和名称
- 编辑窗口根据ID查询,返回单个字典
- 使用字段名填充表单控件
3. **提交修改**:
- 获取表单数据
- 构建参数字典
- 执行SQL更新/插入操作
- 发射信号通知主窗口刷新
### 扩展功能:动态列处理
如果需要处理动态变化的列结构,可以这样实现:
```python
def load_data(self):
conn = DB_POOL.connection()
cursor = conn.cursor()
try:
# 获取列信息
cursor.execute("SHOW COLUMNS FROM symptoms")
columns_info = cursor.fetchall()
self.columns = [col['Field'] for col in columns_info]
self.column_headers = [col['Field'].replace('_', ' ').title() for col in columns_info]
# 更新表格列
self.table.setColumnCount(len(self.columns))
self.table.setHorizontalHeaderLabels(self.column_headers)
# 查询数据
cursor.execute("SELECT * FROM symptoms")
rows = cursor.fetchall()
self.table.setRowCount(len(rows))
for row_idx, row_data in enumerate(rows):
for col_idx, col_name in enumerate(self.columns):
value = row_data.get(col_name, "")
item = QTableWidgetItem(str(value) if value is not None else "")
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(row_idx, col_idx, item)
# ...错误处理...
```
这样代码会根据数据库表结构动态调整列,更加灵活。
### 字典格式的最佳实践
1. **使用常量定义字段名**:
```python
class SymptomFields:
ID = 'id'
NAME = 'name'
DESCRIPTION = 'description'
CAUSES = 'common_causes'
TREATMENT = 'treatment'
DEPARTMENT = 'department'
```
2. **安全访问字段**:
```python
# 使用get()方法提供默认值
name = symptom_data.get(SymptomFields.NAME, "未知")
```
3. **处理空值**:
```python
# 使用三元表达式处理空值
description = symptom_data[SymptomFields.DESCRIPTION] if symptom_data[SymptomFields.DESCRIPTION] else "无描述"
```
4. **字段验证**:
```python
required_fields = [SymptomFields.NAME, SymptomFields.DESCRIPTION]
for field in required_fields:
if field not in symptom_data or not symptom_data[field]:
raise ValueError(f"缺少必要字段: {field}")
```