from typing import Literal, Callable from functools import wraps from PySide6.QtCore import Qt, Signal, QThread from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QFileDialog from qfluentwidgets import GroupHeaderCardWidget, PushButton, IconWidget, InfoBarIcon, \ BodyLabel, PrimaryPushButton, FluentIcon, LineEdit, InfoBar, InfoBarPosition, ProgressBar from module.worker import DTGWorker from ui.components.widget import Widget class InitSettingCard(GroupHeaderCardWidget): chooseSignal = Signal(str, str) def __init__(self, parent=None): super().__init__(parent) self.setTitle("初始化") self.setBorderRadius(8) self.chooseStudentButton = PushButton("打开") self.chooseQuestionButton = PushButton("打开") self.chooseStudentButton.setFixedWidth(120) self.chooseQuestionButton.setFixedWidth(120) self.stuGroup = self.addGroup(FluentIcon.DOCUMENT, "学生名单", "选择学生名单文件", self.chooseStudentButton) self.QueGroup = self.addGroup(FluentIcon.DOCUMENT, "题库", "选择题库文件", self.chooseQuestionButton) self.chooseStudentButton.clicked.connect( lambda: self.choose_file(self.stuGroup.setContent, "已选择文件:", lambda x: self.chooseSignal.emit('s', x))) self.chooseQuestionButton.clicked.connect( lambda: self.choose_file(self.QueGroup.setContent, "已选择文件:", lambda x: self.chooseSignal.emit('q', x))) def choose_file( self, set_value: Callable[[str], None] = None, prefix: str = "", cb: Callable[[str], None] = None ) -> None: file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Excel 文件 (*.xlsx *.xlsm);") if file_path and set_value: set_value(prefix + file_path) if cb: cb(file_path) class ExportSettingsCard(GroupHeaderCardWidget): startSignal = Signal() updateSignal = Signal(str, str) 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.hintIcon = IconWidget(InfoBarIcon.INFORMATION) self.hintLabel = BodyLabel("点击导出按钮以开始生成 👉") self.chooseExportDirectoryButton.setFixedWidth(120) self.startButton.setFixedWidth(120) self.exportFileNameLineEdit.setFixedWidth(360) self.exportFileNameLineEdit.setPlaceholderText("输入导出文件名,例如:21工程管理(1)答辩题目") self.bottomLayout = QHBoxLayout() self.startButton.setEnabled(False) # 设置底部工具栏布局 self.hintIcon.setFixedSize(16, 16) 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.dirGroup = self.addGroup(FluentIcon.FOLDER, "导出目录", "选择导出文件的目录", self.chooseExportDirectoryButton) self.fnGroup = self.addGroup(FluentIcon.DOCUMENT, "导出文件名", "输入导出文件的名称", self.exportFileNameLineEdit) self.fnGroup.setSeparatorVisible(True) self.vBoxLayout.addLayout(self.bottomLayout) # ============== self.chooseExportDirectoryButton.clicked.connect(self.choose_dir) self.startButton.clicked.connect(self.startSignal.emit) self.exportFileNameLineEdit.textChanged.connect(lambda x: self.updateSignal.emit('n', x)) 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_export_setting_by_signal(self, path: str) -> None: f_dir = path[:path.rfind('/')] f_name = path[path.rfind('/') + 1:path.rfind('.')] self.dirGroup.setContent(f"当前保存的文件目录:{f_dir}") self.updateSignal.emit('d', f_dir) self.exportFileNameLineEdit.setText(f"{f_name}-答辩题目") self.updateSignal.emit('n', f_name) class DefenseWidget(Widget): def __init__(self, key: str, parent=None): super().__init__(key, parent) self.initCard = InitSettingCard(self) self.exportCard = ExportSettingsCard(self) self.vbox = QVBoxLayout(self) self.vbox.addWidget(self.initCard) self.vbox.addWidget(self.exportCard) self.vbox.addStretch(1) self.infoBar = None self.pb = None self.thread = None self.worker = None # =================================== self.input_student_filepath = None self.input_question_filepath = None self.output_filepath = None self.output_filename = None # ==================================== self.initCard.chooseSignal.connect(self.input_signal_receive) self.exportCard.updateSignal.connect(self.export_update_signal_receive) self.exportCard.startSignal.connect(self.start_generate) def show_info_bar(self): self.infoBar = InfoBar( icon=InfoBarIcon.INFORMATION, title='请稍后', content="", orient=Qt.Horizontal, isClosable=False, position=InfoBarPosition.BOTTOM, duration=-1, parent=self ) self.pb = ProgressBar(self.infoBar) self.infoBar.addWidget(self.pb) self.infoBar.show() def set_pb_value(self, value: int) -> None: if self.pb: self.pb.setValue(value) if value == 100: self.infoBar.close() self.infoBar = InfoBar.success( title='成功!', content="正在打开文件...", orient=Qt.Horizontal, isClosable=True, position=InfoBarPosition.BOTTOM, duration=5000, parent=self ) self.pb = None self.exportCard.startButton.setEnabled(True) def enable_start_check(func: Callable): @wraps(func) def wrapper(self, *args, **kwargs): result = func(self, *args, **kwargs) fields = [ self.input_student_filepath, self.input_question_filepath, self.output_filepath, self.output_filename ] if all(fields): self.exportCard.startButton.setEnabled(True) else: self.exportCard.startButton.setEnabled(False) return result return wrapper @enable_start_check def set_value(self, key: str, value: str) -> None: if key == "input_student_filepath": self.input_student_filepath = value elif key == "input_question_filepath": self.input_question_filepath = value elif key == "output_filepath": self.output_filepath = value elif key == "output_filename": self.output_filename = value setattr(self, key, value) def input_signal_receive(self, s_type: Literal['s', 'q'], value: str): if s_type == "s": self.set_value("input_student_filepath", value) self.exportCard.update_export_setting_by_signal(value) elif s_type == "q": self.set_value("input_question_filepath", value) def export_update_signal_receive(self, s_type: Literal['d', 'n'], value: str): if s_type == "d": self.set_value("output_filepath", value) elif s_type == "n": self.set_value("output_filename", value) def start_generate(self): self.thread = QThread() self.worker = DTGWorker( self.input_student_filepath, self.input_question_filepath, self.output_filepath, self.output_filename ) self.worker.moveToThread(self.thread) self.show_info_bar() self.exportCard.startButton.setEnabled(False) # 线程启动与信号连接 self.thread.started.connect(self.worker.run) self.worker.progress.connect(self.set_pb_value) 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.start() def clear_thread_worker_refs(self): self.thread = None self.worker = None