282 lines
11 KiB
Python
282 lines
11 KiB
Python
# Copyright (c) 2025-2026 Jeffrey Hsu - JITToolBox
|
|
# #
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
# #
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
# #
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
from functools import wraps
|
|
from typing import Callable, Literal
|
|
|
|
from PySide6.QtCore import Qt, Signal, QThread
|
|
from PySide6.QtWidgets import QVBoxLayout, QFileDialog, QHBoxLayout
|
|
from qfluentwidgets import GroupHeaderCardWidget, FluentIcon, PushButton, LineEdit, IconWidget, BodyLabel, \
|
|
PrimaryPushButton, SwitchButton, HyperlinkButton, InfoBar, InfoBarPosition
|
|
|
|
from module import LOGLEVEL
|
|
from module.worker import ARGWorker
|
|
from ui import MAIN_THEME_COLOR
|
|
from ui.components.infobar import ProgressInfoBar
|
|
from ui.components.widget import Widget, MyGroupHeaderCardWidget
|
|
from utils.function import open_template
|
|
|
|
|
|
class InputSettingCard(MyGroupHeaderCardWidget):
|
|
chooseSignal = Signal(str)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.setTitle("输入选项")
|
|
self.setBorderRadius(8)
|
|
self.btnHBoxLayout = QHBoxLayout(self)
|
|
|
|
self.openTemplateButton = HyperlinkButton("", "模板下载")
|
|
self.chooseFileButton = PushButton("打开")
|
|
|
|
self.chooseFileButton.setFixedWidth(120)
|
|
self.btnHBoxLayout.addWidget(self.openTemplateButton)
|
|
self.btnHBoxLayout.addWidget(self.chooseFileButton)
|
|
|
|
self.inputGroup = self.addGroup(FluentIcon.DOCUMENT, "目标文件", "选择达成度计算表", self.btnHBoxLayout)
|
|
|
|
# ============================
|
|
self.chooseFileButton.clicked.connect(self.choose_file)
|
|
self.openTemplateButton.clicked.connect(lambda: open_template('template-achievement-file.xlsm', self))
|
|
|
|
def choose_file(self):
|
|
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Excel 文件 (*.xlsm);")
|
|
if file_path:
|
|
self.inputGroup.setContent("已选择文件:" + file_path)
|
|
self.chooseSignal.emit(file_path)
|
|
|
|
|
|
class OutputSettingCard(GroupHeaderCardWidget):
|
|
updateSignal = Signal(str, str)
|
|
startSignal = Signal()
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.setTitle("输出选项")
|
|
self.setBorderRadius(8)
|
|
|
|
self.chooseExportDirectoryButton = PushButton("选择")
|
|
self.exportFileNameLineEdit = LineEdit()
|
|
self.startButton = PrimaryPushButton(FluentIcon.PLAY_SOLID, "开始")
|
|
self.autoOpenSwitch = SwitchButton()
|
|
self.disableCompatibilityCheckSwitch = SwitchButton()
|
|
|
|
self.autoOpenSwitch.setChecked(True)
|
|
self.bottomLayout = QHBoxLayout()
|
|
self.hintIcon = IconWidget(FluentIcon.INFO.icon(color=MAIN_THEME_COLOR))
|
|
self.hintLabel = BodyLabel("点击开始按钮以开始生成 👉")
|
|
self.startButton.setEnabled(False)
|
|
|
|
# 设置底部工具栏布局
|
|
self.hintIcon.setFixedSize(16, 16)
|
|
self.hintIcon.autoFillBackground()
|
|
self.bottomLayout.setSpacing(10)
|
|
self.bottomLayout.setContentsMargins(24, 15, 24, 20)
|
|
self.bottomLayout.addWidget(self.hintIcon, 0, Qt.AlignLeft)
|
|
self.bottomLayout.addWidget(self.hintLabel, 0, Qt.AlignLeft)
|
|
self.bottomLayout.addStretch(1)
|
|
self.bottomLayout.addWidget(self.startButton, 0, Qt.AlignRight)
|
|
self.bottomLayout.setAlignment(Qt.AlignVCenter)
|
|
|
|
self.chooseExportDirectoryButton.setFixedWidth(120)
|
|
self.exportFileNameLineEdit.setPlaceholderText("请输入文件名")
|
|
self.startButton.setFixedWidth(120)
|
|
self.exportFileNameLineEdit.setFixedWidth(360)
|
|
self.exportFileNameLineEdit.setPlaceholderText("输入导出文件名,例如:21工程管理(1)达成度报告")
|
|
|
|
self.dirGroup = self.addGroup(FluentIcon.FOLDER, "导出目录", "选择导出文件的目录",
|
|
self.chooseExportDirectoryButton)
|
|
self.fnGroup = self.addGroup(FluentIcon.DOCUMENT, "导出文件名", "输入导出文件名", self.exportFileNameLineEdit)
|
|
self.aoGroup = self.addGroup(FluentIcon.VIEW, "自动打开", "生成完成后自动打开文件", self.autoOpenSwitch)
|
|
self.ccGroup = self.addGroup(FluentIcon.DEVELOPER_TOOLS, "关闭兼容性检查",
|
|
"⚠ 注意:该功能为实验性内容,仅当你明确了解其影响并知道自己在做什么时,才建议关闭兼容性检查!",
|
|
self.disableCompatibilityCheckSwitch)
|
|
self.ccGroup.setSeparatorVisible(True)
|
|
self.vBoxLayout.addLayout(self.bottomLayout)
|
|
|
|
# =============================
|
|
self.chooseExportDirectoryButton.clicked.connect(self.choose_dir)
|
|
self.startButton.clicked.connect(self.startSignal)
|
|
|
|
def choose_dir(self) -> None:
|
|
dir_path = QFileDialog.getExistingDirectory(self, "选择文件夹", "")
|
|
if dir_path:
|
|
self.dirGroup.setContent(f"当前保存的文件目录:{dir_path}")
|
|
self.updateSignal.emit('d', dir_path)
|
|
|
|
def update_path_and_filename_by_input_path(self, path: str):
|
|
dir_path = path[:path.rfind("/")]
|
|
self.dirGroup.setContent(f"已选择目录:{dir_path}")
|
|
self.updateSignal.emit('d', dir_path)
|
|
|
|
file_name = path[path.rfind("/") + 1:path.rfind(".")] + "-达成度报告"
|
|
self.exportFileNameLineEdit.setText(file_name)
|
|
self.updateSignal.emit('f', file_name)
|
|
|
|
|
|
class AchievementWidget(Widget):
|
|
error = Signal(str, str)
|
|
|
|
def __init__(self, key: str, parent=None):
|
|
super().__init__(key, parent)
|
|
|
|
self.inputGroup = InputSettingCard(self)
|
|
self.outputGroup = OutputSettingCard(self)
|
|
self.vbox = QVBoxLayout(self)
|
|
|
|
self.vbox.addWidget(self.inputGroup)
|
|
self.vbox.addWidget(self.outputGroup)
|
|
self.vbox.addStretch(1)
|
|
|
|
# =================================
|
|
|
|
self.pib = ProgressInfoBar(parent=self, isClosable=False, duration=-1)
|
|
self.pib.hide()
|
|
|
|
# =================================
|
|
|
|
self.thread = None
|
|
self.worker = None
|
|
self.successFlag = True
|
|
|
|
# ==================================
|
|
|
|
self.input_file_path = ""
|
|
self.output_file_path = ""
|
|
self.output_file_name = ""
|
|
|
|
# ==================================
|
|
|
|
self.inputGroup.chooseSignal.connect(self.input_signal_receive)
|
|
self.outputGroup.updateSignal.connect(self.export_signal_receive)
|
|
self.outputGroup.startSignal.connect(self.start_generate)
|
|
|
|
def enable_start_check(func: Callable):
|
|
@wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
result = func(self, *args, **kwargs)
|
|
|
|
fields = [
|
|
self.input_file_path,
|
|
self.output_file_path,
|
|
self.output_file_name,
|
|
]
|
|
if all(fields):
|
|
self.outputGroup.startButton.setEnabled(True)
|
|
else:
|
|
self.outputGroup.startButton.setEnabled(False)
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
@enable_start_check
|
|
def set_value(
|
|
self,
|
|
key: Literal['input_file_path', 'output_file_path', 'output_file_name'],
|
|
value: str
|
|
) -> None:
|
|
setattr(self, key, value)
|
|
|
|
def input_signal_receive(self, file_path: str) -> None:
|
|
self.set_value('input_file_path', file_path)
|
|
self.outputGroup.update_path_and_filename_by_input_path(file_path)
|
|
|
|
def export_signal_receive(self, s_type: Literal['d', 'f'], value: str) -> None:
|
|
if s_type == 'd':
|
|
self.set_value('output_file_path', value)
|
|
elif s_type == 'f':
|
|
self.set_value('output_file_name', value)
|
|
|
|
def start_generate(self):
|
|
self.thread = QThread()
|
|
self.worker = ARGWorker(
|
|
self.input_file_path,
|
|
self.output_file_path,
|
|
self.output_file_name,
|
|
disable_cc=self.outputGroup.disableCompatibilityCheckSwitch.isChecked()
|
|
)
|
|
self.worker.moveToThread(self.thread)
|
|
|
|
self.outputGroup.startButton.setEnabled(False)
|
|
|
|
self.thread.started.connect(self.worker.run)
|
|
|
|
self.show_info_bar()
|
|
self.successFlag = True
|
|
|
|
self.worker.info.connect(self.show_info)
|
|
self.worker.error.connect(self.show_error)
|
|
self.worker.finished.connect(self.thread.quit)
|
|
self.worker.finished.connect(self.worker.deleteLater)
|
|
self.thread.finished.connect(self.thread.deleteLater)
|
|
self.thread.finished.connect(self.clear_thread_worker_refs)
|
|
self.thread.finished.connect(self.after_generate)
|
|
|
|
# 启动线程
|
|
self.thread.start()
|
|
|
|
def clear_thread_worker_refs(self):
|
|
self.thread = None
|
|
self.worker = None
|
|
|
|
def after_generate(self):
|
|
if self.outputGroup.autoOpenSwitch.isChecked() and self.successFlag:
|
|
try:
|
|
os.startfile(self.output_file_path + "/" + self.output_file_name + ".docx")
|
|
except Exception as e:
|
|
self.show_error("?? 不好出错了", str(e))
|
|
if self.successFlag:
|
|
self.pib.show_success(
|
|
content="正在打开文件" if self.outputGroup.autoOpenSwitch.isChecked() else "文件已保存")
|
|
else:
|
|
self.pib.show_error()
|
|
self.outputGroup.startButton.setEnabled(True)
|
|
|
|
def show_error(self, title: str, content: str):
|
|
self.successFlag = False
|
|
self.error.emit(title, content)
|
|
|
|
def show_info_bar(self):
|
|
self.pib.set_title('请稍后')
|
|
self.pib.show()
|
|
self.pib.set_progress(101)
|
|
|
|
def show_info(self, content: str, level: str):
|
|
if level == LOGLEVEL.INFO:
|
|
self.pib.set_title(content)
|
|
elif level == LOGLEVEL.WARNING:
|
|
InfoBar.warning(
|
|
title='提示',
|
|
content=content,
|
|
orient=Qt.Horizontal,
|
|
isClosable=True,
|
|
position=InfoBarPosition.TOP_RIGHT,
|
|
duration=5000,
|
|
parent=self
|
|
)
|
|
elif level == LOGLEVEL.ERROR:
|
|
InfoBar.error(
|
|
title='错误',
|
|
content=content,
|
|
orient=Qt.Horizontal,
|
|
isClosable=True,
|
|
position=InfoBarPosition.TOP_RIGHT,
|
|
duration=-1,
|
|
parent=self
|
|
)
|