Files
tophux_scrape/product/sqlite_viewer.py

445 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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("输入筛选条件,如:<75 或 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()
# 检查是否为数值比较(支持 <, >, <=, >=, =, != 操作符)
import re
numeric_pattern = r'^\s*([><]=?|!=|=)\s*([\d.]+)\s*$'
match = re.match(numeric_pattern, filter_condition)
if match:
# 数值比较
operator = match.group(1)
value = match.group(2)
query = f"SELECT * FROM {self.current_table} WHERE {selected_field} {operator} ?"
filter_value = float(value)
else:
# 文本模糊匹配
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)
if match:
logger.info(f"应用数值筛选条件: {selected_field} {operator} {value}, 匹配到 {len(data)} 行数据")
self.status_bar.showMessage(f"筛选结果: {len(data)} 行数据 (条件: {selected_field} {operator} {value})")
else:
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()