This commit is contained in:
douboer
2025-09-07 01:19:00 +08:00
parent 5e1788884f
commit c463e3b785
31 changed files with 3124 additions and 61 deletions

View File

@@ -1,16 +1,23 @@
import sys
import os
import re
from urllib.parse import unquote
import datetime
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QListWidget,
QFileDialog, QMessageBox, QLineEdit, QFormLayout, QDialog, QDialogButtonBox
QApplication, QWidget, QPushButton, QLabel, QListWidget,
QFileDialog, QMessageBox, QLineEdit, QFormLayout, QDialog, QDialogButtonBox, QTextEdit, QHBoxLayout, QSizePolicy
)
from PyQt6.QtGui import QIcon
from PyQt6.QtGui import QIcon, QPixmap
from PyQt6.QtCore import QSettings, QSize, QByteArray
from PyQt6 import uic
import config
from exportbooknotes import BookNotesExporter
from booklist_parse import BookListManager
from review_worker import BookReviewWorker
from cover_mixin import CoverMixin
from finished_books_mixin import FinishedBooksMixin
class ConfigDialog(QDialog):
def __init__(self, parent=None):
@@ -32,68 +39,309 @@ class ConfigDialog(QDialog):
def get_config(self):
return {k: v.text() for k, v in self.inputs.items()}
class IBookExportApp(QWidget):
class IBookExportApp(CoverMixin, FinishedBooksMixin, QWidget):
def __init__(self):
super().__init__()
# 加载 UI 文件
# ====== UI 加载 ======
ui_file = os.path.join(os.path.dirname(__file__), 'ibook_export_app.ui')
uic.loadUi(ui_file, self)
# 设置窗口标题
self.setWindowTitle("notesExporter")
# 设置窗口图标
self.setWindowTitle("iBook笔记专家")
if os.path.exists(config.APP_ICON):
self.setWindowIcon(QIcon(config.APP_ICON))
# 初始化数据
# ====== 数据准备 ======
try:
from exportbooknotes import sync_source_files
sync_source_files(config)
except Exception as e:
print(f"警告: 初始同步源数据失败: {e}")
self.exporter = BookNotesExporter(config)
self.manager = BookListManager(plist_path=config.LOCAL_BOOKS_PLIST, db_path=config.LOCAL_LIBRARY_DB)
self.booksinfo = self.manager.get_books_info()
self.last_open_times = self.manager.get_books_last_open()
self.assetid2name = {}
self.assetid2lastopen = {}
self.assetid2name, self.assetid2lastopen = {}, {}
for assetid, info in self.booksinfo.items():
name = info.get('displayname') or info.get('itemname') or assetid
if '-' in name:
name = name.split('-', 1)[0].strip()
self.assetid2name[assetid] = name
ts = self.last_open_times.get(assetid, {}).get('last_open', 0)
self.assetid2lastopen[assetid] = ts
sorted_assetids = sorted(self.assetid2name.keys(), key=lambda aid: self.assetid2lastopen[aid], reverse=True)
self.sorted_assetids = sorted_assetids
# 填充书籍列表
for aid in sorted_assetids:
self.assetid2lastopen[assetid] = self.last_open_times.get(assetid, {}).get('last_open', 0)
self.sorted_assetids = sorted(self.assetid2name.keys(), key=lambda aid: self.assetid2lastopen[aid], reverse=True)
for aid in self.sorted_assetids:
self.listwidget.addItem(f"{self.assetid2name[aid]} [{self.assetid2lastopen[aid]}]")
# 连接信号
# ====== 信号 ======
self.export_btn.clicked.connect(self.export_notes)
self.config_btn.clicked.connect(self.show_config)
# 回车直接导出(使用事件过滤器)
self.listwidget.currentRowChanged.connect(self.update_book_info)
self.listwidget.installEventFilter(self)
# ====== 封面标签 ======
if all(hasattr(self, n) for n in ('cover_label_1','cover_label_2','cover_label_3')):
self._cover_labels = [self.cover_label_1, self.cover_label_2, self.cover_label_3]
else:
self._cover_labels = [getattr(self, 'book_cover_label', QLabel('封面', self))]
for lab in self._cover_labels:
lab.setMinimumWidth(180)
lab.setMaximumWidth(180)
lab.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
try:
from PyQt6.QtCore import Qt as _QtFP
lab.setFocusPolicy(_QtFP.FocusPolicy.NoFocus)
except Exception:
pass
# 封面高度是否弹性跟随文本区(默认 False: 固定算法,不扩张)
self.cover_elastic = False
# 调整文本区域弹性:宽度随可用空间扩展,高度优先扩展
try:
from PyQt6.QtWidgets import QSizePolicy as _QSP
sp = self.book_toc_textedit.sizePolicy()
sp.setHorizontalPolicy(_QSP.Policy.Expanding)
sp.setVerticalPolicy(_QSP.Policy.Expanding)
self.book_toc_textedit.setSizePolicy(sp)
# 给封面横排区域一个较小的最小高度,避免撑开
if hasattr(self, 'covers_layout') and self._cover_labels:
for lab in self._cover_labels:
lab.setMinimumHeight(10)
except Exception:
pass
self.cover_ratio = 1.2
self._export_tab_index = None
self.book_toc_textedit.setPlainText("书籍信息 / 简评")
# 状态 & 缓存
self._review_worker = None
self._current_bookname = None
self._active_workers = []
self._cover_pixmaps_original = []
# 恢复窗口尺寸
self._restore_window_geometry()
# 设置封面标签对齐
try:
from PyQt6.QtCore import Qt as _QtAlign
for lab in self._cover_labels:
lab.setAlignment(_QtAlign.AlignmentFlag.AlignHCenter | _QtAlign.AlignmentFlag.AlignTop)
except Exception:
pass
# 初始封面 + 首本书信息 (及 AI 简评触发)
self._load_initial()
# 已读书籍网格
try:
self._populate_finished_books_grid()
except Exception as e:
print('警告: 已读书籍网格填充失败', e)
# 滚动区域策略 & 事件过滤
if hasattr(self, 'finished_scroll_area'):
from PyQt6.QtCore import Qt as _Qt
self.finished_scroll_area.setHorizontalScrollBarPolicy(_Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.finished_scroll_area.setVerticalScrollBarPolicy(_Qt.ScrollBarPolicy.ScrollBarAsNeeded)
try:
self.finished_scroll_area.viewport().installEventFilter(self)
except Exception as _e_vp:
print('信息: 安装 viewport 事件过滤器失败', _e_vp)
# Tab 切换监听 (C + A 方案)
from PyQt6.QtWidgets import QTabWidget
try:
tabs = self.findChildren(QTabWidget)
if tabs:
self._main_tab_widget = tabs[0]
self._main_tab_widget.currentChanged.connect(self._on_main_tab_changed)
self._detect_export_tab_index()
except Exception as _e_tab:
print('信息: 连接 tab 切换失败', _e_tab)
# 首次显示后再排一次
from PyQt6.QtCore import QTimer
QTimer.singleShot(80, self._relayout_finished_grid)
def eventFilter(self, obj, event):
from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtCore import QEvent
# 方案 C: 监听 finished_scroll_area 的 viewport Resize
try:
if hasattr(self, 'finished_scroll_area') and obj == self.finished_scroll_area.viewport():
if event.type() == QEvent.Type.Resize:
# 可选节流:仅当宽度真实变化较大时
new_w = obj.width()
last_w = getattr(self, '_finished_viewport_last_width', None)
if last_w is None or abs(new_w - last_w) > 8:
self._finished_viewport_last_width = new_w
self._relayout_finished_grid()
except Exception:
pass
# 原有 listwidget 回车导出逻辑
if obj == self.listwidget and event.type() == QEvent.Type.KeyPress:
# 检查回车键Enter/Return
if event.key() in (0x01000004, 0x01000005): # Qt.Key_Return, Qt.Key_Enter
if event.key() in (0x01000004, 0x01000005): # Return / Enter
self.export_notes()
return True
return super().eventFilter(obj, event)
def show_config(self):
dlg = ConfigDialog(self)
if dlg.exec():
new_config = dlg.get_config()
# 这里只是演示实际可写入config.py或动态加载
QMessageBox.information(self, "提示", "配置已更新(仅本次运行有效)")
def _load_initial(self):
"""启动时:
1. 显示前三本封面
2. 初始化文本区域为第一本书的基础信息(无简评内容,只留段落标题)
"""
try:
if not hasattr(self, '_cover_labels') or not self.sorted_assetids:
return
from PyQt6.QtGui import QPixmap
self._cover_pixmaps_original = []
first_indices = list(range(min(3, len(self.sorted_assetids))))
for pos in range(3):
if pos < len(first_indices):
aid = self.sorted_assetids[first_indices[pos]]
info = self.booksinfo.get(aid, {})
cpath = self.find_book_cover(aid, info)
if cpath:
pm = QPixmap(cpath)
if not pm.isNull():
self._cover_pixmaps_original.append(pm)
self._cover_labels[pos].setPixmap(pm)
continue
self._cover_pixmaps_original.append(None)
self._cover_labels[pos].setText('无封面')
else:
self._cover_pixmaps_original.append(None)
self._cover_labels[pos].setText('')
self._apply_cover_scale()
# 首本书信息
first_aid = self.sorted_assetids[0]
first_info = self.booksinfo.get(first_aid, {})
bookname_display = first_info.get('displayname') or first_info.get('itemname') or first_aid
author = first_info.get('author', '')
btype = first_info.get('type', '')
get_time = first_info.get('date', '')
self._base_info_cache = {
'bookname': bookname_display,
'author': author,
'type': btype,
'date': get_time
}
self._current_bookname = bookname_display
# 读取/触发首本书书评
import json
json_path = os.path.join(os.path.dirname(__file__), 'bookintro.json')
try:
with open(json_path, 'r', encoding='utf-8') as f:
intro_dict = json.load(f)
except Exception:
intro_dict = {}
review = intro_dict.get(bookname_display)
if review:
html = self._build_book_html(review)
self.book_toc_textedit.setHtml(html)
else:
loading_html = self._build_book_html("简评获取中...")
self.book_toc_textedit.setHtml(loading_html)
prompt = f"{bookname_display} 400字书评 三段 简洁精炼"
worker = BookReviewWorker(bookname_display, prompt, json_path, parent=self)
worker.finished.connect(lambda bname, rev: self._on_review_finished(bname, rev))
worker.finished.connect(lambda _b, _r, w=worker: self._remove_worker(w))
self._review_worker = worker
self._active_workers.append(worker)
worker.start()
except Exception as e:
print('初始加载失败:', e)
def _on_main_tab_changed(self, index):
"""方案 A: 当切换到 '已读书籍' 标签时强制重排一次,并做一次短延迟的二次重排,避免初次显示宽度未稳定。"""
try:
if not hasattr(self, '_main_tab_widget'):
return
w = self._main_tab_widget.widget(index)
# 通过对象名或包含的 finished_scroll_area 判断(加括号避免优先级问题)
hit = False
if hasattr(self, 'finished_scroll_area'):
try:
if self.finished_scroll_area.isAncestorOf(w) or w.isAncestorOf(self.finished_scroll_area):
hit = True
except Exception:
pass
# 退化:检查对象名包含关键字
if not hit:
name = getattr(w, 'objectName', lambda: '')()
if 'finished' in name.lower():
hit = True
if hit:
# 立即重排
self._relayout_finished_grid()
from PyQt6.QtCore import QTimer
# 120ms 后再重排一次(宽度稳定后)
QTimer.singleShot(120, self._relayout_finished_grid)
except Exception as e:
print('tab 切换重排失败:', e)
def showEvent(self, event):
"""首次显示窗口后,再安排一次延迟重排,提升初始网格正确率。"""
try:
super().showEvent(event)
from PyQt6.QtCore import QTimer
QTimer.singleShot(80, self._relayout_finished_grid)
except Exception:
pass
def _detect_export_tab_index(self):
"""探测导出标签索引:优先使用 ui 中命名的 tab_export否则通过包含 listwidget 的页面推断。"""
if getattr(self, '_export_tab_index', None) is not None:
return
try:
if not hasattr(self, '_main_tab_widget'):
return
idx = -1
if hasattr(self, 'tab_export'):
idx = self._main_tab_widget.indexOf(self.tab_export)
if idx < 0:
from PyQt6.QtWidgets import QListWidget
for i in range(self._main_tab_widget.count()):
page = self._main_tab_widget.widget(i)
if page.findChild(QListWidget, 'listwidget') is not None:
idx = i
break
if idx < 0:
idx = 0 # 兜底
self._export_tab_index = idx
except Exception as e:
print('探测导出标签索引失败:', e)
def _switch_to_export_tab(self):
try:
if not hasattr(self, '_main_tab_widget'):
from PyQt6.QtWidgets import QTabWidget
tabs = self.findChildren(QTabWidget)
if tabs:
self._main_tab_widget = tabs[0]
if not hasattr(self, '_main_tab_widget'):
return
self._detect_export_tab_index()
if self._export_tab_index is None:
return
if self._main_tab_widget.currentIndex() != self._export_tab_index:
self._main_tab_widget.setCurrentIndex(self._export_tab_index)
except Exception as e:
print('切换导出标签失败:', e)
def _on_finished_cover_clicked(self, asset_id):
# 在主列表中选中对应书籍(若存在)
try:
if not hasattr(self, 'sorted_assetids'):
return
if asset_id not in self.sorted_assetids:
return
row = self.sorted_assetids.index(asset_id)
# 切换到“导出”标签(自动探测索引)
self._switch_to_export_tab()
self.listwidget.setCurrentRow(row)
# 确保可见并聚焦
try:
item = self.listwidget.item(row)
if item:
self.listwidget.scrollToItem(item)
self.listwidget.setFocus()
except Exception:
pass
# 触发 update_book_info 逻辑自动刷新右侧
except Exception as e:
print('点击已读书籍封面失败:', e)
def export_notes(self):
from exportbooknotes import sync_source_files
sync_source_files(config)
idx = self.listwidget.currentRow()
if idx < 0:
QMessageBox.warning(self, "提示", "请先选择一本书")
@@ -111,12 +359,283 @@ class IBookExportApp(QWidget):
self.exporter.export_booksnote_to_md(selected_booksnote, selected_booksinfo, out_path)
QMessageBox.information(self, "导出成功", f"已导出到:{out_path}")
def show_config(self):
dlg = ConfigDialog(self)
if dlg.exec():
dlg.get_config()
QMessageBox.information(self, "提示", "配置已更新(仅本次运行有效)")
def update_book_info(self, row):
if row < 0:
# 不再清空:保持初始加载的封面;仅清空文本
self.book_toc_textedit.clear()
return
assetid = self.sorted_assetids[row]
book_info = self.booksinfo.get(assetid, {})
# 计算当前与后续两本
total = len(self.sorted_assetids)
indices = [(row + i) % total for i in range(min(3,total))]
self._cover_pixmaps_original = []
from PyQt6.QtGui import QPixmap
# 先清空所有标签
for lab in self._cover_labels:
lab.clear()
lab.setText("加载中")
for pos, aid_idx in enumerate(indices):
aid_show = self.sorted_assetids[aid_idx]
binfo = self.booksinfo.get(aid_show, {})
cpath = self.find_book_cover(aid_show, binfo)
label = self._cover_labels[pos]
if cpath:
pm = QPixmap(cpath)
if not pm.isNull():
self._cover_pixmaps_original.append(pm)
label.setText("")
continue
self._cover_pixmaps_original.append(None)
label.setText("无封面")
# 填充不足三本情况
for pos in range(len(indices), 3):
lab = self._cover_labels[pos]
lab.setText("")
self._cover_pixmaps_original.append(None)
self._apply_cover_scale()
# 生成 HTML 信息基础部分
bookname_display = book_info.get('displayname', '') or book_info.get('itemname', '') or assetid
author = book_info.get('author', '')
btype = book_info.get('type', '')
get_time = book_info.get('date', '')
self._base_info_cache = {
'bookname': bookname_display,
'author': author,
'type': btype,
'date': get_time
}
import json
bookname = bookname_display
self._current_bookname = bookname
json_path = os.path.join(os.path.dirname(__file__), 'bookintro.json')
try:
with open(json_path, 'r', encoding='utf-8') as f:
intro_dict = json.load(f)
except Exception:
intro_dict = {}
review = intro_dict.get(bookname)
if review:
html = self._build_book_html(review)
self.book_toc_textedit.setHtml(html)
else:
prompt = f"{bookname} 400字书评 三段 简洁精炼"
loading_html = self._build_book_html("简评获取中...")
self.book_toc_textedit.setHtml(loading_html)
worker = BookReviewWorker(bookname, prompt, json_path, parent=self)
# UI 更新
worker.finished.connect(lambda bname, review: self._on_review_finished(bname, review))
# 完成后从活动列表移除
worker.finished.connect(lambda _b, _r, w=worker: self._remove_worker(w))
self._review_worker = worker
self._active_workers.append(worker)
worker.start()
def _on_review_finished(self, bookname, review, base_text=None):
# base_text 兼容旧调用,可忽略
if bookname != self._current_bookname:
return
html = self._build_book_html(review)
self.book_toc_textedit.setHtml(html)
def _load_initial_covers(self):
"""在应用启动时加载列表中前三本书的封面到三个标签。"""
try:
if not hasattr(self, '_cover_labels') or not self.sorted_assetids:
return
from PyQt6.QtGui import QPixmap
self._cover_pixmaps_original = []
# 取前三本
first_indices = list(range(min(3, len(self.sorted_assetids))))
for pos in range(3):
if pos < len(first_indices):
aid = self.sorted_assetids[first_indices[pos]]
info = self.booksinfo.get(aid, {})
cpath = self.find_book_cover(aid, info)
if cpath:
pm = QPixmap(cpath)
if not pm.isNull():
self._cover_pixmaps_original.append(pm)
self._cover_labels[pos].setPixmap(pm)
continue
self._cover_pixmaps_original.append(None)
self._cover_labels[pos].setText('无封面')
else:
self._cover_pixmaps_original.append(None)
self._cover_labels[pos].setText('')
self._apply_cover_scale()
except Exception as e:
print('初始封面加载失败:', e)
def resizeEvent(self, event):
# 窗口尺寸变化时重新计算封面大小
try:
self._apply_cover_scale()
self._relayout_finished_grid()
except Exception:
pass
super().resizeEvent(event)
def _build_book_html(self, review_text: str) -> str:
"""构建包含加粗紫红色标题的 HTML 内容AI书评分段显示。"""
info = getattr(self, '_base_info_cache', {})
magenta = "#C71585" # 紫红色
def line(title, value):
return f"<p><span style='color:{magenta};font-weight:bold;'>{title}</span> {value}</p>"
# 书评分段处理
def review_lines(text):
# 按换行或两个以上空格分段
if not text:
return [""]
# 兼容多种分段格式
segments = [seg.strip() for seg in re.split(r'\n{2,}|\r{2,}|\n|\r|\s{2,}', text) if seg.strip()]
return [f"<p>{seg}</p>" for seg in segments]
parts = [
line("书名:", f"{info.get('author','')} - {info.get('bookname','')}") ,
line("作者:", info.get('author','')),
line("类型:", info.get('type','')),
line("获取时间:", info.get('date','')),
f"<span style='color:{magenta};font-weight:bold;'>书籍简评:</span>"
]
parts += review_lines(review_text)
return "".join(parts)
def _init_charts(self):
"""使用原生 Qt 组件渲染统计标签页四个图表(取代 matplotlib"""
try:
from charts import BarChartWidget, BubbleMetricsWidget, ScatterChartWidget
except Exception as e:
print('警告: 无法导入原生图表组件 charts.py:', e)
return
required = [
('frame_week', 'weekLayout'),
('frame_month', 'monthLayout'),
('frame_year', 'yearLayout'),
('frame_bubble', 'bubbleLayout'),
]
for attr, layout_name in required:
if not hasattr(self, attr) or not hasattr(self, layout_name):
print('信息: 缺少统计容器', attr)
return
try:
week_data = self.manager.get_total_readtime(days=7)
month_data = self.manager.get_total_readtime(days=30)
year_data = self.manager.get_total_readtime12m()
year_total_minutes = self.manager.get_total_readtime_year()
except Exception as e:
print('警告: 统计数据获取失败:', e)
return
if all(v == 0 for v in week_data + month_data + year_data):
for _, layout_name in required:
getattr(self, layout_name).addWidget(QLabel('暂无阅读数据'))
return
# 最近7天weekday 英文缩写索引0=今天)
today = datetime.date.today()
recent_days = [today - datetime.timedelta(days=i) for i in range(len(week_data))]
WEEK_ABBR = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
week_labels = [WEEK_ABBR[d.weekday()] for d in recent_days]
# 最近30天日期标签旋转45度防重叠
month_recent_days = [today - datetime.timedelta(days=i) for i in range(len(month_data))]
month_labels = [f"{d.month}{d.day}" for d in month_recent_days]
year_labels = [f'{i+1}' for i in range(12)]
year_hours = [round(m/60.0, 1) for m in year_data]
week_chart = BarChartWidget(week_data, title='', unit='分钟', labels=week_labels, value_format=lambda v: f'{int(v)}')
# 让横坐标先显示第30天数据即最近的日期在最左侧
month_chart = BarChartWidget(
month_data[::-1], # 反转数据
title='',
unit='分钟',
labels=month_labels[::-1], # 反转标签
value_format=lambda v: f'{int(v)}',
label_rotation=45
)
year_chart = ScatterChartWidget(year_hours, title='', unit='小时', labels=year_labels)
# 确保图表在网格中可弹性扩展
from PyQt6.QtWidgets import QSizePolicy
for wdg in (week_chart, month_chart, year_chart):
wdg.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.weekLayout.addWidget(week_chart)
self.monthLayout.addWidget(month_chart)
self.yearLayout.addWidget(year_chart)
year_hours_total = year_total_minutes / 60.0
month_avg_hours = (sum(year_data)/12.0)/60.0 if year_data else 0
week_hours = sum(week_data)/60.0
day_avg_minutes = (sum(month_data)/30.0) if month_data else 0
bubble_metrics = [
('全年', year_hours_total, 'h', '#5b6ee1'),
('月均', month_avg_hours, 'h', '#c9b2d9'),
('近7天', week_hours, 'h', '#f4b2c2'),
('日均', day_avg_minutes, 'm', '#b9b542'),
]
bubble_widget = BubbleMetricsWidget(bubble_metrics)
bubble_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.bubbleLayout.addWidget(bubble_widget)
# ---------------- 窗口尺寸持久化 ----------------
def _restore_window_geometry(self):
settings = QSettings('iBookTools', 'iBook笔记专家')
geo = settings.value('mainWindowGeometry')
if isinstance(geo, QByteArray) and not geo.isEmpty():
try:
self.restoreGeometry(geo)
return
except Exception:
pass
# 兼容旧版本仅存宽高
w = settings.value('windowWidth', type=int)
h = settings.value('windowHeight', type=int)
if w and h and w > 100 and h > 100:
self.resize(QSize(w, h))
else:
# 默认初始尺寸
self.resize(1500, 900)
def closeEvent(self, event):
try:
settings = QSettings('iBookTools', 'iBook笔记专家')
# 新格式:直接保存几何
settings.setValue('mainWindowGeometry', self.saveGeometry())
# 兼容旧字段
settings.setValue('windowWidth', self.width())
settings.setValue('windowHeight', self.height())
# 优雅等待所有后台线程结束(给最多 8 秒)
deadline = datetime.datetime.now() + datetime.timedelta(seconds=8)
for w in list(self._active_workers):
if w.isRunning():
remaining = (deadline - datetime.datetime.now()).total_seconds()
if remaining <= 0:
break
# wait 参数是毫秒
w.wait(int(min(remaining, 2) * 1000)) # 分批等待,避免一次性卡太久
except Exception:
pass
super().closeEvent(event)
def _remove_worker(self, worker):
try:
if worker in self._active_workers:
self._active_workers.remove(worker)
except Exception:
pass
# ------------------------------------------------
# ------------------------------------------------
if __name__ == "__main__":
app = QApplication(sys.argv)
# 设置应用程序名称和组织信息
app.setApplicationName("notesExporter")
app.setApplicationDisplayName("notesExporter")
app.setApplicationName("iBook笔记专家")
app.setApplicationDisplayName("iBook笔记专家")
app.setOrganizationName("iBook Tools")
# 设置应用程序图标
@@ -124,5 +643,9 @@ if __name__ == "__main__":
app.setWindowIcon(QIcon(config.APP_ICON))
win = IBookExportApp()
try:
win._init_charts()
except Exception:
pass
win.show()
sys.exit(app.exec())