更新了sqlite视图的功能
This commit is contained in:
135
product/MERGE_SUMMARY.md
Normal file
135
product/MERGE_SUMMARY.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# 系统合并总结报告
|
||||||
|
|
||||||
|
## 合并概述
|
||||||
|
|
||||||
|
成功将 `integrated_scraper.py` 和 `product_ai_analysis.py` 合并为一个统一的系统,提供完整的产品抓取和AI分析功能。
|
||||||
|
|
||||||
|
## 功能特性对比
|
||||||
|
|
||||||
|
| 功能 | integrated_scraper.py | product_ai_analysis.py | 合并后系统 |
|
||||||
|
|------|----------------------|----------------------|-------------|
|
||||||
|
| 产品抓取 | ✅ | ❌ | ✅ |
|
||||||
|
| AI分析 | ❌ | ✅ | ✅ |
|
||||||
|
| 数据库操作 | ✅ | ✅ | ✅ |
|
||||||
|
| 配置管理 | ❌ | ❌ | ✅ |
|
||||||
|
| 统一命令行 | ❌ | ❌ | ✅ |
|
||||||
|
| 错误处理 | 基础 | 基础 | 增强 |
|
||||||
|
| 日志记录 | ✅ | ✅ | ✅ |
|
||||||
|
| 进度显示 | ✅ | ❌ | ✅ |
|
||||||
|
|
||||||
|
## 新文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
product/
|
||||||
|
├── integrated_product_system.py # 核心系统(合并后)
|
||||||
|
├── run_system.py # 命令行界面
|
||||||
|
├── config.py # 配置文件
|
||||||
|
├── README.md # 使用说明
|
||||||
|
└── MERGE_SUMMARY.md # 合并总结
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心改进
|
||||||
|
|
||||||
|
### 1. 统一接口
|
||||||
|
- 单一入口点处理抓取和分析
|
||||||
|
- 支持多种运行模式(抓取/分析/完整)
|
||||||
|
- 统一的配置管理和错误处理
|
||||||
|
|
||||||
|
### 2. 配置管理
|
||||||
|
- 集中化的配置系统
|
||||||
|
- 支持命令行参数覆盖
|
||||||
|
- 灵活的数据库和API配置
|
||||||
|
|
||||||
|
### 3. 用户体验
|
||||||
|
- 简化的命令行界面
|
||||||
|
- 详细的进度显示和日志记录
|
||||||
|
- 增强的错误处理和恢复机制
|
||||||
|
|
||||||
|
### 4. 代码质量
|
||||||
|
- 模块化设计,职责分离
|
||||||
|
- 增强的错误处理和日志记录
|
||||||
|
- 完整的文档和示例
|
||||||
|
|
||||||
|
## 使用方式对比
|
||||||
|
|
||||||
|
### 原系统使用方式
|
||||||
|
```bash
|
||||||
|
# 抓取产品
|
||||||
|
python integrated_scraper.py --limit 100
|
||||||
|
|
||||||
|
# 分析产品(需要单独运行)
|
||||||
|
python product_ai_analysis.py --max-products 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### 合并后系统使用方式
|
||||||
|
```bash
|
||||||
|
# 完整工作流程(抓取+分析)
|
||||||
|
python run_system.py --mode full --limit 100 --max-products 50
|
||||||
|
|
||||||
|
# 仅抓取
|
||||||
|
python run_system.py --mode scraping --limit 100
|
||||||
|
|
||||||
|
# 仅分析
|
||||||
|
python run_system.py --mode analysis --max-products 50
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能增强
|
||||||
|
|
||||||
|
### 新增功能
|
||||||
|
- ✅ 统一的工作流程管理
|
||||||
|
- ✅ 灵活的运行模式选择
|
||||||
|
- ✅ 增强的配置系统
|
||||||
|
- ✅ 改进的命令行界面
|
||||||
|
- ✅ 更好的错误处理
|
||||||
|
|
||||||
|
### 保留功能
|
||||||
|
- ✅ 产品抓取功能(完整保留)
|
||||||
|
- ✅ AI分析功能(完整保留)
|
||||||
|
- ✅ 数据库结构(完全兼容)
|
||||||
|
- ✅ 日志记录(增强版)
|
||||||
|
- ✅ 进度显示(改进版)
|
||||||
|
|
||||||
|
## 向后兼容性
|
||||||
|
|
||||||
|
- ✅ 数据库结构完全兼容
|
||||||
|
- ✅ 保留所有原有功能
|
||||||
|
- ✅ 支持原有命令行参数
|
||||||
|
- ✅ 兼容现有数据文件
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
✅ 系统导入测试通过
|
||||||
|
✅ 数据库初始化测试通过
|
||||||
|
✅ 命令行界面测试通过
|
||||||
|
✅ 配置系统测试通过
|
||||||
|
✅ 异步调用修复测试通过
|
||||||
|
|
||||||
|
所有测试均成功完成,系统功能正常。
|
||||||
|
|
||||||
|
## 重要修复记录
|
||||||
|
|
||||||
|
### 异步调用问题修复 (v1.0.1)
|
||||||
|
**问题**: `asyncio.run() cannot be called from a running event loop`
|
||||||
|
**原因**: 在已有事件循环中嵌套调用 `asyncio.run()`
|
||||||
|
**解决方案**:
|
||||||
|
- 重构 `run_full_workflow` 方法为异步版本 `run_full_workflow_async`
|
||||||
|
- 新增同步入口方法,使用新事件循环执行异步函数
|
||||||
|
- 主函数直接调用异步版本,避免嵌套事件循环
|
||||||
|
**状态**: ✅ 已修复并测试通过
|
||||||
|
|
||||||
|
## 迁移指南
|
||||||
|
|
||||||
|
### 从原系统迁移
|
||||||
|
1. 备份现有数据库文件
|
||||||
|
2. 使用新的命令行界面
|
||||||
|
3. 根据需要调整配置文件
|
||||||
|
4. 验证数据完整性
|
||||||
|
|
||||||
|
### 配置迁移
|
||||||
|
- 原有参数大部分兼容
|
||||||
|
- 新增配置选项可选使用
|
||||||
|
- 支持环境变量和配置文件
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
合并后的系统提供了更简洁、更强大、更易用的产品抓取和AI分析功能,同时保持了与原有系统的完全兼容性。通过统一的接口和增强的配置管理,大大简化了使用流程,提高了开发效率。
|
||||||
@@ -83,6 +83,20 @@ python run_system.py --log-file my_log.log --log-level DEBUG
|
|||||||
python run_system.py --mode scraping --urls https://www.producthunt.com/posts/example-product
|
python run_system.py --mode scraping --urls https://www.producthunt.com/posts/example-product
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.0.1 (当前版本)
|
||||||
|
- ✅ 修复异步调用问题,支持在已有事件循环中运行
|
||||||
|
- ✅ 优化错误处理和事件循环管理
|
||||||
|
- ✅ 测试验证所有运行模式正常工作
|
||||||
|
|
||||||
|
### v1.0.0
|
||||||
|
- ✨ 合并integrated_scraper.py和product_ai_analysis.py功能
|
||||||
|
- ✨ 添加统一的配置管理
|
||||||
|
- ✨ 提供简化的命令行界面
|
||||||
|
- ✨ 增强错误处理和日志记录
|
||||||
|
- ✨ 支持多种运行模式
|
||||||
|
|
||||||
## 数据库结构
|
## 数据库结构
|
||||||
|
|
||||||
### products表(产品信息)
|
### products表(产品信息)
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
10
product/sqlite_viewer.log
Normal file
10
product/sqlite_viewer.log
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
2025-11-28 22:29:36.321 | INFO | __main__:__init__:109 - 初始化SQLite数据库查看器
|
||||||
|
2025-11-28 22:29:36.322 | INFO | __main__:init_ui:116 - 设置主窗口界面
|
||||||
|
2025-11-28 22:29:36.324 | INFO | __main__:create_top_buttons:146 - 创建顶部按钮
|
||||||
|
2025-11-28 22:29:36.330 | INFO | __main__:create_filter_section:169 - 创建筛选控件区域
|
||||||
|
2025-11-28 22:29:36.347 | INFO | __main__:create_splitter:214 - 创建分割器界面
|
||||||
|
2025-11-28 22:29:36.361 | INFO | __main__:create_status_bar:261 - 创建状态栏
|
||||||
|
2025-11-28 22:29:36.362 | INFO | __main__:create_menubar:268 - 创建菜单栏
|
||||||
|
2025-11-28 22:29:36.389 | INFO | __main__:init_ui:142 - 界面初始化完成
|
||||||
|
2025-11-28 22:29:36.572 | INFO | __main__:main:669 - 应用程序启动完成
|
||||||
|
2025-11-28 22:29:40.545 | INFO | __main__:closeEvent:647 - 关闭应用程序
|
||||||
@@ -14,9 +14,91 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayo
|
|||||||
QWidget, QPushButton, QTableWidget, QTableWidgetItem,
|
QWidget, QPushButton, QTableWidget, QTableWidgetItem,
|
||||||
QListWidget, QListWidgetItem, QSplitter, QFileDialog,
|
QListWidget, QListWidgetItem, QSplitter, QFileDialog,
|
||||||
QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox,
|
QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox,
|
||||||
QLineEdit, QGroupBox)
|
QLineEdit, QGroupBox, QTextEdit, QStyledItemDelegate, QMenu)
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt, QSize
|
||||||
from PySide6.QtGui import QAction
|
from PySide6.QtGui import QAction, QFontMetrics
|
||||||
|
|
||||||
|
|
||||||
|
class MultiLineDelegate(QStyledItemDelegate):
|
||||||
|
"""多行文本委托,支持自动调整行高"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.min_height = 30 # 最小行高
|
||||||
|
self.max_height = 200 # 最大行高
|
||||||
|
|
||||||
|
def paint(self, painter, option, index):
|
||||||
|
"""自定义绘制,支持多行文本"""
|
||||||
|
# 保存原始选项
|
||||||
|
opt = option
|
||||||
|
|
||||||
|
# 获取文本内容
|
||||||
|
text = index.data(Qt.DisplayRole)
|
||||||
|
if text is None:
|
||||||
|
text = ""
|
||||||
|
|
||||||
|
# 设置文本换行
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
# 计算文本高度
|
||||||
|
metrics = QFontMetrics(option.font)
|
||||||
|
rect = option.rect
|
||||||
|
|
||||||
|
# 计算需要的行数
|
||||||
|
lines = text.count('\n') + 1
|
||||||
|
line_height = metrics.lineSpacing()
|
||||||
|
text_height = lines * line_height + 10 # 添加一些边距
|
||||||
|
|
||||||
|
# 限制高度在最小和最大值之间
|
||||||
|
if text_height < self.min_height:
|
||||||
|
text_height = self.min_height
|
||||||
|
elif text_height > self.max_height:
|
||||||
|
text_height = self.max_height
|
||||||
|
|
||||||
|
# 调整绘制区域高度
|
||||||
|
opt.rect.setHeight(text_height)
|
||||||
|
|
||||||
|
# 调用父类绘制方法
|
||||||
|
super().paint(painter, opt, index)
|
||||||
|
|
||||||
|
def sizeHint(self, option, index):
|
||||||
|
"""返回建议的单元格大小"""
|
||||||
|
# 获取文本内容
|
||||||
|
text = index.data(Qt.DisplayRole)
|
||||||
|
if text is None:
|
||||||
|
text = ""
|
||||||
|
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
# 计算文本尺寸
|
||||||
|
metrics = QFontMetrics(option.font)
|
||||||
|
|
||||||
|
# 计算行数
|
||||||
|
lines = text.count('\n') + 1
|
||||||
|
line_height = metrics.lineSpacing()
|
||||||
|
text_height = lines * line_height + 10 # 添加边距
|
||||||
|
|
||||||
|
# 计算文本宽度(考虑换行)
|
||||||
|
if '\n' in text:
|
||||||
|
# 多行文本,计算最长行的宽度
|
||||||
|
max_width = 0
|
||||||
|
for line in text.split('\n'):
|
||||||
|
line_width = metrics.horizontalAdvance(line) + 20
|
||||||
|
max_width = max(max_width, line_width)
|
||||||
|
else:
|
||||||
|
# 单行文本
|
||||||
|
max_width = metrics.horizontalAdvance(text) + 20
|
||||||
|
|
||||||
|
# 限制高度
|
||||||
|
if text_height < self.min_height:
|
||||||
|
text_height = self.min_height
|
||||||
|
elif text_height > self.max_height:
|
||||||
|
text_height = self.max_height
|
||||||
|
|
||||||
|
# 最小宽度设置为100像素
|
||||||
|
max_width = max(max_width, 100)
|
||||||
|
|
||||||
|
return QSize(max_width, text_height)
|
||||||
|
|
||||||
|
|
||||||
class SQLiteViewer(QMainWindow):
|
class SQLiteViewer(QMainWindow):
|
||||||
@@ -148,6 +230,24 @@ class SQLiteViewer(QMainWindow):
|
|||||||
right_layout.addWidget(QLabel("表数据:"))
|
right_layout.addWidget(QLabel("表数据:"))
|
||||||
self.data_table = QTableWidget()
|
self.data_table = QTableWidget()
|
||||||
self.data_table.setAlternatingRowColors(True)
|
self.data_table.setAlternatingRowColors(True)
|
||||||
|
|
||||||
|
# 设置表格支持多行内容和可调整列宽
|
||||||
|
self.data_table.setItemDelegate(MultiLineDelegate(self.data_table))
|
||||||
|
self.data_table.setWordWrap(True) # 启用自动换行
|
||||||
|
self.data_table.setTextElideMode(Qt.ElideNone) # 不省略文本
|
||||||
|
|
||||||
|
# 设置列头支持拖拽调整大小
|
||||||
|
header = self.data_table.horizontalHeader()
|
||||||
|
header.setSectionsMovable(True) # 允许移动列
|
||||||
|
header.setStretchLastSection(False) # 不自动拉伸最后一列
|
||||||
|
|
||||||
|
# 设置行头自动调整高度
|
||||||
|
self.data_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||||
|
|
||||||
|
# 添加右键菜单支持
|
||||||
|
self.data_table.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.data_table.customContextMenuRequested.connect(self.show_table_context_menu)
|
||||||
|
|
||||||
right_layout.addWidget(self.data_table)
|
right_layout.addWidget(self.data_table)
|
||||||
|
|
||||||
splitter.addWidget(left_widget)
|
splitter.addWidget(left_widget)
|
||||||
@@ -273,11 +373,37 @@ class SQLiteViewer(QMainWindow):
|
|||||||
# 填充数据
|
# 填充数据
|
||||||
for row_idx, row_data in enumerate(data):
|
for row_idx, row_data in enumerate(data):
|
||||||
for col_idx, cell_data in enumerate(row_data):
|
for col_idx, cell_data in enumerate(row_data):
|
||||||
item = QTableWidgetItem(str(cell_data) if cell_data is not None else "")
|
# 处理None值和格式化数据
|
||||||
|
if cell_data is None:
|
||||||
|
display_text = ""
|
||||||
|
elif isinstance(cell_data, (int, float)):
|
||||||
|
# 数字类型保持原样,但转换为字符串
|
||||||
|
display_text = str(cell_data)
|
||||||
|
else:
|
||||||
|
# 文本类型,保留原始格式,包括换行符
|
||||||
|
display_text = str(cell_data)
|
||||||
|
|
||||||
|
item = QTableWidgetItem(display_text)
|
||||||
|
item.setToolTip(display_text) # 添加悬停提示
|
||||||
self.data_table.setItem(row_idx, col_idx, item)
|
self.data_table.setItem(row_idx, col_idx, item)
|
||||||
|
|
||||||
# 调整列宽
|
# 调整列宽 - 使用Interactive模式让用户可以手动调整
|
||||||
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
header = self.data_table.horizontalHeader()
|
||||||
|
header.setSectionResizeMode(QHeaderView.Interactive)
|
||||||
|
|
||||||
|
# 设置初始列宽为内容宽度,但有最大宽度限制
|
||||||
|
for col in range(len(column_names)):
|
||||||
|
# 计算该列内容的最大宽度
|
||||||
|
max_width = 0
|
||||||
|
for row in range(min(100, len(data))): # 只检查前100行,避免性能问题
|
||||||
|
item = self.data_table.item(row, col)
|
||||||
|
if item and item.text():
|
||||||
|
text_width = self.data_table.fontMetrics().horizontalAdvance(item.text()) + 20
|
||||||
|
max_width = max(max_width, text_width)
|
||||||
|
|
||||||
|
# 设置列宽,最小100像素,最大400像素
|
||||||
|
column_width = min(max(max_width, 100), 400)
|
||||||
|
self.data_table.setColumnWidth(col, column_width)
|
||||||
|
|
||||||
logger.info(f"加载表 {table_name} 数据完成,共 {len(data)} 行")
|
logger.info(f"加载表 {table_name} 数据完成,共 {len(data)} 行")
|
||||||
self.status_bar.showMessage(f"表 {table_name}: {len(data)} 行数据")
|
self.status_bar.showMessage(f"表 {table_name}: {len(data)} 行数据")
|
||||||
@@ -363,11 +489,37 @@ class SQLiteViewer(QMainWindow):
|
|||||||
# 填充筛选后的数据
|
# 填充筛选后的数据
|
||||||
for row_idx, row_data in enumerate(data):
|
for row_idx, row_data in enumerate(data):
|
||||||
for col_idx, cell_data in enumerate(row_data):
|
for col_idx, cell_data in enumerate(row_data):
|
||||||
item = QTableWidgetItem(str(cell_data) if cell_data is not None else "")
|
# 处理None值和格式化数据
|
||||||
|
if cell_data is None:
|
||||||
|
display_text = ""
|
||||||
|
elif isinstance(cell_data, (int, float)):
|
||||||
|
# 数字类型保持原样,但转换为字符串
|
||||||
|
display_text = str(cell_data)
|
||||||
|
else:
|
||||||
|
# 文本类型,保留原始格式,包括换行符
|
||||||
|
display_text = str(cell_data)
|
||||||
|
|
||||||
|
item = QTableWidgetItem(display_text)
|
||||||
|
item.setToolTip(display_text) # 添加悬停提示
|
||||||
self.data_table.setItem(row_idx, col_idx, item)
|
self.data_table.setItem(row_idx, col_idx, item)
|
||||||
|
|
||||||
# 调整列宽
|
# 调整列宽 - 使用Interactive模式让用户可以手动调整
|
||||||
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
header = self.data_table.horizontalHeader()
|
||||||
|
header.setSectionResizeMode(QHeaderView.Interactive)
|
||||||
|
|
||||||
|
# 设置初始列宽为内容宽度,但有最大宽度限制
|
||||||
|
for col in range(len(column_names)):
|
||||||
|
# 计算该列内容的最大宽度
|
||||||
|
max_width = 0
|
||||||
|
for row in range(min(100, len(data))): # 只检查前100行,避免性能问题
|
||||||
|
item = self.data_table.item(row, col)
|
||||||
|
if item and item.text():
|
||||||
|
text_width = self.data_table.fontMetrics().horizontalAdvance(item.text()) + 20
|
||||||
|
max_width = max(max_width, text_width)
|
||||||
|
|
||||||
|
# 设置列宽,最小100像素,最大400像素
|
||||||
|
column_width = min(max(max_width, 100), 400)
|
||||||
|
self.data_table.setColumnWidth(col, column_width)
|
||||||
|
|
||||||
# 启用清除筛选按钮
|
# 启用清除筛选按钮
|
||||||
self.clear_filter_button.setEnabled(True)
|
self.clear_filter_button.setEnabled(True)
|
||||||
@@ -413,6 +565,83 @@ class SQLiteViewer(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self.load_table_list()
|
self.load_table_list()
|
||||||
|
|
||||||
|
def show_table_context_menu(self, position):
|
||||||
|
"""显示表格右键菜单"""
|
||||||
|
menu = QMenu()
|
||||||
|
|
||||||
|
# 添加菜单项
|
||||||
|
auto_resize_action = menu.addAction("自动调整列宽")
|
||||||
|
auto_resize_rows_action = menu.addAction("自动调整行高")
|
||||||
|
copy_action = menu.addAction("复制选中内容")
|
||||||
|
|
||||||
|
# 显示菜单
|
||||||
|
action = menu.exec(self.data_table.mapToGlobal(position))
|
||||||
|
|
||||||
|
if action == auto_resize_action:
|
||||||
|
self.auto_resize_columns()
|
||||||
|
elif action == auto_resize_rows_action:
|
||||||
|
self.auto_resize_rows()
|
||||||
|
elif action == copy_action:
|
||||||
|
self.copy_selected_content()
|
||||||
|
|
||||||
|
def auto_resize_columns(self):
|
||||||
|
"""自动调整所有列宽"""
|
||||||
|
logger.info("自动调整列宽")
|
||||||
|
|
||||||
|
# 遍历所有列
|
||||||
|
for col in range(self.data_table.columnCount()):
|
||||||
|
# 计算该列内容的最大宽度
|
||||||
|
max_width = 0
|
||||||
|
for row in range(min(100, self.data_table.rowCount())): # 只检查前100行
|
||||||
|
item = self.data_table.item(row, col)
|
||||||
|
if item and item.text():
|
||||||
|
text_width = self.data_table.fontMetrics().horizontalAdvance(item.text()) + 20
|
||||||
|
max_width = max(max_width, text_width)
|
||||||
|
|
||||||
|
# 设置列宽,最小100像素,最大500像素
|
||||||
|
column_width = min(max(max_width, 100), 500)
|
||||||
|
self.data_table.setColumnWidth(col, column_width)
|
||||||
|
|
||||||
|
self.status_bar.showMessage("已自动调整列宽")
|
||||||
|
|
||||||
|
def auto_resize_rows(self):
|
||||||
|
"""自动调整所有行高"""
|
||||||
|
logger.info("自动调整行高")
|
||||||
|
|
||||||
|
# 触发重新计算行高
|
||||||
|
self.data_table.resizeRowsToContents()
|
||||||
|
|
||||||
|
self.status_bar.showMessage("已自动调整行高")
|
||||||
|
|
||||||
|
def copy_selected_content(self):
|
||||||
|
"""复制选中的内容"""
|
||||||
|
selected_items = self.data_table.selectedItems()
|
||||||
|
if not selected_items:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 按行列组织数据
|
||||||
|
rows = {}
|
||||||
|
for item in selected_items:
|
||||||
|
row = item.row()
|
||||||
|
col = item.column()
|
||||||
|
if row not in rows:
|
||||||
|
rows[row] = {}
|
||||||
|
rows[row][col] = item.text()
|
||||||
|
|
||||||
|
# 构建复制的文本
|
||||||
|
text_lines = []
|
||||||
|
for row in sorted(rows.keys()):
|
||||||
|
row_data = []
|
||||||
|
for col in sorted(rows[row].keys()):
|
||||||
|
row_data.append(rows[row][col])
|
||||||
|
text_lines.append('\t'.join(row_data))
|
||||||
|
|
||||||
|
# 复制到剪贴板
|
||||||
|
clipboard = QApplication.clipboard()
|
||||||
|
clipboard.setText('\n'.join(text_lines))
|
||||||
|
|
||||||
|
self.status_bar.showMessage(f"已复制 {len(selected_items)} 个单元格的内容")
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""关闭事件处理"""
|
"""关闭事件处理"""
|
||||||
logger.info("关闭应用程序")
|
logger.info("关闭应用程序")
|
||||||
|
|||||||
Reference in New Issue
Block a user