431 lines
15 KiB
Python
431 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
SQLite数据库查看器 - 基于PySide6
|
||
功能:打开product目录下的product.db的sqlite文件,显示表和数据的界面
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import sqlite3
|
||
from loguru import logger
|
||
|
||
from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
|
||
QWidget, QPushButton, QTableWidget, QTableWidgetItem,
|
||
QListWidget, QListWidgetItem, QSplitter, QFileDialog,
|
||
QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox,
|
||
QLineEdit, QGroupBox)
|
||
from PySide6.QtCore import Qt
|
||
from PySide6.QtGui import QAction
|
||
|
||
|
||
class SQLiteViewer(QMainWindow):
|
||
"""SQLite数据库查看器主窗口"""
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
logger.info("初始化SQLite数据库查看器")
|
||
self.db_connection = None
|
||
self.current_table = None
|
||
self.init_ui()
|
||
|
||
def init_ui(self):
|
||
"""初始化用户界面"""
|
||
logger.info("设置主窗口界面")
|
||
self.setWindowTitle("SQLite数据库查看器")
|
||
self.setGeometry(100, 100, 1200, 800)
|
||
|
||
# 创建中央部件
|
||
central_widget = QWidget()
|
||
self.setCentralWidget(central_widget)
|
||
|
||
# 创建主布局
|
||
main_layout = QVBoxLayout(central_widget)
|
||
|
||
# 创建顶部按钮布局
|
||
self.create_top_buttons(main_layout)
|
||
|
||
# 创建筛选控件区域
|
||
self.create_filter_section(main_layout)
|
||
|
||
# 创建分割器(左侧表列表,右侧数据表格)
|
||
self.create_splitter(main_layout)
|
||
|
||
# 创建状态栏
|
||
self.create_status_bar()
|
||
|
||
# 创建菜单栏
|
||
self.create_menubar()
|
||
|
||
logger.info("界面初始化完成")
|
||
|
||
def create_top_buttons(self, layout):
|
||
"""创建顶部按钮布局"""
|
||
logger.info("创建顶部按钮")
|
||
button_layout = QHBoxLayout()
|
||
|
||
# 打开数据库按钮
|
||
self.open_button = QPushButton("打开SQLite数据库")
|
||
self.open_button.clicked.connect(self.open_database)
|
||
button_layout.addWidget(self.open_button)
|
||
|
||
# 刷新按钮
|
||
self.refresh_button = QPushButton("刷新")
|
||
self.refresh_button.clicked.connect(self.refresh_data)
|
||
self.refresh_button.setEnabled(False)
|
||
button_layout.addWidget(self.refresh_button)
|
||
|
||
# 数据库路径显示
|
||
self.db_path_label = QLabel("未打开数据库")
|
||
button_layout.addWidget(self.db_path_label)
|
||
|
||
button_layout.addStretch()
|
||
layout.addLayout(button_layout)
|
||
|
||
def create_filter_section(self, layout):
|
||
"""创建筛选控件区域"""
|
||
logger.info("创建筛选控件区域")
|
||
|
||
# 创建筛选分组框
|
||
filter_group = QGroupBox("数据筛选")
|
||
filter_layout = QHBoxLayout(filter_group)
|
||
|
||
# 字段选择标签
|
||
filter_layout.addWidget(QLabel("筛选字段:"))
|
||
|
||
# 字段选择下拉框
|
||
self.field_combo = QComboBox()
|
||
self.field_combo.setMinimumWidth(150)
|
||
filter_layout.addWidget(self.field_combo)
|
||
|
||
# 筛选条件标签
|
||
filter_layout.addWidget(QLabel("筛选条件:"))
|
||
|
||
# 筛选条件输入框
|
||
self.filter_input = QLineEdit()
|
||
self.filter_input.setPlaceholderText("输入筛选条件,如:name='test' 或 created_at>'2024-01-01'")
|
||
self.filter_input.setMinimumWidth(300)
|
||
filter_layout.addWidget(self.filter_input)
|
||
|
||
# 筛选按钮
|
||
self.filter_button = QPushButton("筛选")
|
||
self.filter_button.clicked.connect(self.apply_filter)
|
||
filter_layout.addWidget(self.filter_button)
|
||
|
||
# 清除筛选按钮
|
||
self.clear_filter_button = QPushButton("清除筛选")
|
||
self.clear_filter_button.clicked.connect(self.clear_filter)
|
||
self.clear_filter_button.setEnabled(False)
|
||
filter_layout.addWidget(self.clear_filter_button)
|
||
|
||
filter_layout.addStretch()
|
||
|
||
# 初始状态下禁用筛选控件
|
||
self.field_combo.setEnabled(False)
|
||
self.filter_input.setEnabled(False)
|
||
self.filter_button.setEnabled(False)
|
||
|
||
layout.addWidget(filter_group)
|
||
|
||
def create_splitter(self, layout):
|
||
"""创建分割器界面"""
|
||
logger.info("创建分割器界面")
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
|
||
# 左侧:表列表
|
||
left_widget = QWidget()
|
||
left_layout = QVBoxLayout(left_widget)
|
||
|
||
left_layout.addWidget(QLabel("数据库表列表:"))
|
||
self.table_list = QListWidget()
|
||
self.table_list.itemClicked.connect(self.on_table_selected)
|
||
left_layout.addWidget(self.table_list)
|
||
|
||
# 右侧:数据表格
|
||
right_widget = QWidget()
|
||
right_layout = QVBoxLayout(right_widget)
|
||
|
||
right_layout.addWidget(QLabel("表数据:"))
|
||
self.data_table = QTableWidget()
|
||
self.data_table.setAlternatingRowColors(True)
|
||
right_layout.addWidget(self.data_table)
|
||
|
||
splitter.addWidget(left_widget)
|
||
splitter.addWidget(right_widget)
|
||
splitter.setSizes([300, 900])
|
||
|
||
layout.addWidget(splitter)
|
||
|
||
def create_status_bar(self):
|
||
"""创建状态栏"""
|
||
logger.info("创建状态栏")
|
||
self.status_bar = QStatusBar()
|
||
self.setStatusBar(self.status_bar)
|
||
self.status_bar.showMessage("就绪")
|
||
|
||
def create_menubar(self):
|
||
"""创建菜单栏"""
|
||
logger.info("创建菜单栏")
|
||
menubar = self.menuBar()
|
||
|
||
# 文件菜单
|
||
file_menu = menubar.addMenu("文件")
|
||
|
||
open_action = QAction("打开数据库", self)
|
||
open_action.triggered.connect(self.open_database)
|
||
file_menu.addAction(open_action)
|
||
|
||
exit_action = QAction("退出", self)
|
||
exit_action.triggered.connect(self.close)
|
||
file_menu.addAction(exit_action)
|
||
|
||
def open_database(self):
|
||
"""打开SQLite数据库文件"""
|
||
logger.info("打开数据库文件对话框")
|
||
|
||
# 默认打开product目录下的product.db
|
||
default_path = os.path.join('product', 'product.db')
|
||
if os.path.exists(default_path):
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self, "打开SQLite数据库", default_path, "SQLite数据库文件 (*.db *.sqlite *.sqlite3)"
|
||
)
|
||
else:
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self, "打开SQLite数据库", "", "SQLite数据库文件 (*.db *.sqlite *.sqlite3)"
|
||
)
|
||
|
||
if file_path:
|
||
logger.info(f"打开数据库文件: {file_path}")
|
||
self.connect_to_database(file_path)
|
||
|
||
def connect_to_database(self, file_path):
|
||
"""连接到指定的SQLite数据库"""
|
||
try:
|
||
if self.db_connection:
|
||
self.db_connection.close()
|
||
|
||
self.db_connection = sqlite3.connect(file_path)
|
||
logger.info("数据库连接成功")
|
||
|
||
self.db_path_label.setText(f"数据库: {os.path.basename(file_path)}")
|
||
self.status_bar.showMessage(f"已连接到数据库: {os.path.basename(file_path)}")
|
||
self.refresh_button.setEnabled(True)
|
||
|
||
# 加载表列表
|
||
self.load_table_list()
|
||
|
||
except sqlite3.Error as e:
|
||
logger.error(f"数据库连接失败: {e}")
|
||
QMessageBox.critical(self, "错误", f"无法打开数据库: {e}")
|
||
|
||
def load_table_list(self):
|
||
"""加载数据库表列表"""
|
||
if not self.db_connection:
|
||
return
|
||
|
||
try:
|
||
cursor = self.db_connection.cursor()
|
||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||
tables = cursor.fetchall()
|
||
|
||
self.table_list.clear()
|
||
for table in tables:
|
||
item = QListWidgetItem(table[0])
|
||
self.table_list.addItem(item)
|
||
|
||
logger.info(f"加载了 {len(tables)} 个表")
|
||
self.status_bar.showMessage(f"已加载 {len(tables)} 个表")
|
||
|
||
except sqlite3.Error as e:
|
||
logger.error(f"加载表列表失败: {e}")
|
||
QMessageBox.critical(self, "错误", f"加载表列表失败: {e}")
|
||
|
||
def on_table_selected(self, item):
|
||
"""当表被选中时加载表数据和字段列表"""
|
||
table_name = item.text()
|
||
logger.info(f"选中表: {table_name}")
|
||
self.current_table = table_name
|
||
self.load_table_data(table_name)
|
||
self.update_field_combo(table_name)
|
||
|
||
def load_table_data(self, table_name):
|
||
"""加载指定表的数据"""
|
||
if not self.db_connection:
|
||
return
|
||
|
||
try:
|
||
cursor = self.db_connection.cursor()
|
||
|
||
# 获取表结构
|
||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||
columns = cursor.fetchall()
|
||
column_names = [col[1] for col in columns]
|
||
|
||
# 获取数据
|
||
cursor.execute(f"SELECT * FROM {table_name}")
|
||
data = cursor.fetchall()
|
||
|
||
# 设置表格
|
||
self.data_table.setRowCount(len(data))
|
||
self.data_table.setColumnCount(len(column_names))
|
||
self.data_table.setHorizontalHeaderLabels(column_names)
|
||
|
||
# 填充数据
|
||
for row_idx, row_data in enumerate(data):
|
||
for col_idx, cell_data in enumerate(row_data):
|
||
item = QTableWidgetItem(str(cell_data) if cell_data is not None else "")
|
||
self.data_table.setItem(row_idx, col_idx, item)
|
||
|
||
# 调整列宽
|
||
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
logger.info(f"加载表 {table_name} 数据完成,共 {len(data)} 行")
|
||
self.status_bar.showMessage(f"表 {table_name}: {len(data)} 行数据")
|
||
|
||
except sqlite3.Error as e:
|
||
logger.error(f"加载表数据失败: {e}")
|
||
QMessageBox.critical(self, "错误", f"加载表数据失败: {e}")
|
||
|
||
def update_field_combo(self, table_name):
|
||
"""更新字段选择下拉框"""
|
||
if not self.db_connection:
|
||
return
|
||
|
||
try:
|
||
cursor = self.db_connection.cursor()
|
||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||
columns = cursor.fetchall()
|
||
|
||
# 清空当前字段列表
|
||
self.field_combo.clear()
|
||
|
||
# 添加所有字段到下拉框
|
||
for column in columns:
|
||
field_name = column[1] # 字段名在第二个位置
|
||
self.field_combo.addItem(field_name)
|
||
|
||
# 启用筛选控件
|
||
self.field_combo.setEnabled(True)
|
||
self.filter_input.setEnabled(True)
|
||
self.filter_button.setEnabled(True)
|
||
|
||
logger.info(f"更新字段下拉框: {table_name}, 共 {len(columns)} 个字段")
|
||
|
||
except sqlite3.Error as e:
|
||
logger.error(f"获取表字段信息失败: {e}")
|
||
QMessageBox.warning(self, "错误", f"获取表字段信息失败: {e}")
|
||
|
||
def apply_filter(self):
|
||
"""应用筛选条件"""
|
||
if not self.db_connection or not self.current_table:
|
||
return
|
||
|
||
selected_field = self.field_combo.currentText()
|
||
filter_condition = self.filter_input.text().strip()
|
||
|
||
if not selected_field or not filter_condition:
|
||
QMessageBox.warning(self, "警告", "请选择筛选字段并输入筛选条件")
|
||
return
|
||
|
||
try:
|
||
cursor = self.db_connection.cursor()
|
||
|
||
# 构建SQL查询语句
|
||
query = f"SELECT * FROM {self.current_table} WHERE {selected_field} LIKE ?"
|
||
|
||
# 处理筛选条件(支持模糊匹配)
|
||
filter_value = f"%{filter_condition}%"
|
||
|
||
# 执行查询
|
||
cursor.execute(query, (filter_value,))
|
||
data = cursor.fetchall()
|
||
|
||
# 获取表结构
|
||
cursor.execute(f"PRAGMA table_info({self.current_table})")
|
||
columns = cursor.fetchall()
|
||
column_names = [col[1] for col in columns]
|
||
|
||
# 更新表格显示
|
||
self.data_table.setRowCount(len(data))
|
||
self.data_table.setColumnCount(len(column_names))
|
||
self.data_table.setHorizontalHeaderLabels(column_names)
|
||
|
||
# 填充筛选后的数据
|
||
for row_idx, row_data in enumerate(data):
|
||
for col_idx, cell_data in enumerate(row_data):
|
||
item = QTableWidgetItem(str(cell_data) if cell_data is not None else "")
|
||
self.data_table.setItem(row_idx, col_idx, item)
|
||
|
||
# 调整列宽
|
||
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
# 启用清除筛选按钮
|
||
self.clear_filter_button.setEnabled(True)
|
||
|
||
logger.info(f"应用筛选条件: {selected_field} LIKE '%{filter_condition}%', 匹配到 {len(data)} 行数据")
|
||
self.status_bar.showMessage(f"筛选结果: {len(data)} 行数据 (条件: {selected_field} 包含 '{filter_condition}')")
|
||
|
||
except sqlite3.Error as e:
|
||
logger.error(f"筛选数据失败: {e}")
|
||
QMessageBox.critical(self, "错误", f"筛选数据失败: {e}")
|
||
|
||
def clear_filter(self):
|
||
"""清除筛选条件,显示所有数据"""
|
||
if not self.current_table:
|
||
return
|
||
|
||
try:
|
||
# 重新加载完整数据
|
||
self.load_table_data(self.current_table)
|
||
|
||
# 清空筛选条件
|
||
self.filter_input.clear()
|
||
|
||
# 禁用清除筛选按钮
|
||
self.clear_filter_button.setEnabled(False)
|
||
|
||
logger.info("清除筛选条件,显示所有数据")
|
||
self.status_bar.showMessage("已清除筛选条件,显示所有数据")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清除筛选失败: {e}")
|
||
QMessageBox.critical(self, "错误", f"清除筛选失败: {e}")
|
||
|
||
def refresh_data(self):
|
||
"""刷新当前数据"""
|
||
logger.info("刷新数据")
|
||
if self.current_table:
|
||
self.load_table_data(self.current_table)
|
||
else:
|
||
self.load_table_list()
|
||
|
||
def closeEvent(self, event):
|
||
"""关闭事件处理"""
|
||
logger.info("关闭应用程序")
|
||
if self.db_connection:
|
||
self.db_connection.close()
|
||
event.accept()
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
logger.info("启动SQLite数据库查看器")
|
||
|
||
# 配置日志
|
||
logger.add("sqlite_viewer.log", rotation="10 MB", level="INFO")
|
||
|
||
app = QApplication(sys.argv)
|
||
|
||
# 设置应用程序信息
|
||
app.setApplicationName("SQLite数据库查看器")
|
||
app.setApplicationVersion("1.0.0")
|
||
|
||
viewer = SQLiteViewer()
|
||
viewer.show()
|
||
|
||
logger.info("应用程序启动完成")
|
||
sys.exit(app.exec())
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |