diff --git a/module/worker.py b/module/worker.py index d7775c2..bb64a50 100644 --- a/module/worker.py +++ b/module/worker.py @@ -42,7 +42,7 @@ class DTGWorker(QObject): max_question_count = math.ceil(len(students) * 3 / len(questions)) - d = DocPaper(self.output_filename, template_path=resource_path("template/template-2.docx")) + d = DocPaper(self.output_filename, resource_path("template/template-defense-paper-paper.docx")) for index, student in enumerate(students): if (p := int((index + 1) / len(students) * 100)) != 100: self.progress[int].emit(p) diff --git a/template/template-achievement-file.xlsm b/template/template-achievement-file.xlsm new file mode 100644 index 0000000..e2cb968 Binary files /dev/null and b/template/template-achievement-file.xlsm differ diff --git a/template/template-defense-oral-paper.docx b/template/template-defense-oral-paper.docx new file mode 100644 index 0000000..a88f29e Binary files /dev/null and b/template/template-defense-oral-paper.docx differ diff --git a/template/template-defense-oral-student.xlsm b/template/template-defense-oral-student.xlsm new file mode 100644 index 0000000..0bb0bde Binary files /dev/null and b/template/template-defense-oral-student.xlsm differ diff --git a/template/template-2.docx b/template/template-defense-paper-paper.docx similarity index 100% rename from template/template-2.docx rename to template/template-defense-paper-paper.docx diff --git a/template/template-defense-paper-questions.xlsm b/template/template-defense-paper-questions.xlsm new file mode 100644 index 0000000..85ac0a3 Binary files /dev/null and b/template/template-defense-paper-questions.xlsm differ diff --git a/template/template-defense-paper-student-1.xlsm b/template/template-defense-paper-student-1.xlsm new file mode 100644 index 0000000..2730f40 Binary files /dev/null and b/template/template-defense-paper-student-1.xlsm differ diff --git a/template/template-defense-paper-student-2.xlsm b/template/template-defense-paper-student-2.xlsm new file mode 100644 index 0000000..e2cb968 Binary files /dev/null and b/template/template-defense-paper-student-2.xlsm differ diff --git a/template/template-pick-student.xlsm b/template/template-pick-student.xlsm new file mode 100644 index 0000000..2f79c81 Binary files /dev/null and b/template/template-pick-student.xlsm differ diff --git a/template/template.docx b/template/template.docx deleted file mode 100644 index 9277325..0000000 Binary files a/template/template.docx and /dev/null differ diff --git a/ui/components/widget.py b/ui/components/widget.py index ebec839..9bb23d9 100644 --- a/ui/components/widget.py +++ b/ui/components/widget.py @@ -1,8 +1,10 @@ import random +from typing import Union from PySide6.QtCore import QTimer, Qt, Signal -from PySide6.QtWidgets import QWidget, QVBoxLayout, QFrame -from qfluentwidgets import DisplayLabel, LargeTitleLabel +from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QWidget, QVBoxLayout, QFrame, QLayout +from qfluentwidgets import DisplayLabel, LargeTitleLabel, GroupHeaderCardWidget, FluentIconBase, CardGroupWidget from module.picker.schema import PickerStudent @@ -68,3 +70,27 @@ class RollingTextWidget(QWidget): def clear_text(self): self.soLabel.clear() self.nameLabel.clear() + + +class MyCardGroupWidget(CardGroupWidget): + def addLayout(self, layout: QLayout, stretch=0): + self.hBoxLayout.addLayout(layout, stretch=stretch) + + +class MyGroupHeaderCardWidget(GroupHeaderCardWidget): + def addGroup(self, icon: Union[str, FluentIconBase, QIcon], title: str, content: str, + object: Union[QWidget, QLayout], + stretch=0) -> CardGroupWidget: + group = MyCardGroupWidget(icon, title, content, self) + + if isinstance(object, QWidget): + group.addWidget(object, stretch=stretch) + elif isinstance(object, QLayout): + group.addLayout(object, stretch=stretch) + + if self.groupWidgets: + self.groupWidgets[-1].setSeparatorVisible(True) + + self.groupLayout.addWidget(group) + self.groupWidgets.append(group) + return group diff --git a/ui/pyui/achievement_ui.py b/ui/pyui/achievement_ui.py index ad25bad..542769d 100644 --- a/ui/pyui/achievement_ui.py +++ b/ui/pyui/achievement_ui.py @@ -5,16 +5,17 @@ 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 + PrimaryPushButton, SwitchButton, HyperlinkButton 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 +from ui.components.widget import Widget, MyGroupHeaderCardWidget +from utils.function import open_template -class InputSettingCard(GroupHeaderCardWidget): +class InputSettingCard(MyGroupHeaderCardWidget): chooseSignal = Signal(str) def __init__(self, parent=None): @@ -22,15 +23,20 @@ class InputSettingCard(GroupHeaderCardWidget): 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.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);") diff --git a/ui/pyui/defense_ui.py b/ui/pyui/defense_ui.py index 41b59a9..0d1ca12 100644 --- a/ui/pyui/defense_ui.py +++ b/ui/pyui/defense_ui.py @@ -1,18 +1,43 @@ from functools import wraps from typing import Literal, Callable -from PySide6.QtCore import Qt, Signal, QThread -from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QFileDialog, QButtonGroup, QWidget +from PySide6.QtCore import Qt, Signal, QThread, QEvent +from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QFileDialog, QButtonGroup, QWidget, QApplication from qfluentwidgets import GroupHeaderCardWidget, PushButton, IconWidget, BodyLabel, PrimaryPushButton, FluentIcon, \ - LineEdit, RadioButton + LineEdit, RadioButton, HyperlinkButton, FlyoutViewBase, TeachingTipView, TeachingTip, TeachingTipTailPosition from module.worker import DTGWorker from ui import MAIN_THEME_COLOR from ui.components.infobar import ProgressInfoBar -from ui.components.widget import Widget +from ui.components.widget import Widget, MyGroupHeaderCardWidget +from utils.function import open_template -class InitSettingCard(GroupHeaderCardWidget): +class ChooseTemplateView(FlyoutViewBase): + closed = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self.vBoxLayout = QVBoxLayout(self) + QApplication.instance().installEventFilter(self) + + def paintEvent(self, e): + ... + + def eventFilter(self, watched, event): + if event.type() == QEvent.MouseButtonPress: + if not self.rect().contains(self.mapFromGlobal(event.globalPosition().toPoint())): + self.closed.emit() + return super().eventFilter(watched, event) + + def addTemplate(self, content: str, cb: Callable[[], None]): + label = HyperlinkButton("", content) + self.vBoxLayout.addWidget(label) + label.clicked.connect(cb) + label.clicked.connect(self.closed.emit) + + +class InitSettingCard(MyGroupHeaderCardWidget): chooseSignal = Signal(str, str) def __init__(self, parent=None): @@ -21,19 +46,29 @@ class InitSettingCard(GroupHeaderCardWidget): self.setTitle("输入选项") self.setBorderRadius(8) + self.sBtnHBoxLayout = QHBoxLayout(self) + self.qBtnHBoxLayout = QHBoxLayout(self) + self.sTemplateButton = HyperlinkButton("", "下载模板") self.chooseStudentButton = PushButton("打开") + self.qTemplateButton = HyperlinkButton("", "下载模板") self.chooseQuestionButton = PushButton("打开") self.chooseStudentButton.setFixedWidth(120) self.chooseQuestionButton.setFixedWidth(120) + self.sBtnHBoxLayout.addWidget(self.sTemplateButton) + self.sBtnHBoxLayout.addWidget(self.chooseStudentButton) + self.qBtnHBoxLayout.addWidget(self.qTemplateButton) + self.qBtnHBoxLayout.addWidget(self.chooseQuestionButton) - self.stuGroup = self.addGroup(FluentIcon.DOCUMENT, "学生名单", "选择学生名单文件", self.chooseStudentButton) - self.QueGroup = self.addGroup(FluentIcon.DOCUMENT, "题库", "选择题库文件", self.chooseQuestionButton) + self.stuGroup = self.addGroup(FluentIcon.DOCUMENT, "学生名单", "选择学生名单文件", self.sBtnHBoxLayout) + self.QueGroup = self.addGroup(FluentIcon.DOCUMENT, "题库", "选择题库文件", self.qBtnHBoxLayout) 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))) + self.qTemplateButton.clicked.connect(lambda: open_template('template-defense-paper-questions.xlsm', self)) + self.sTemplateButton.clicked.connect(self.show_template_list_view) def choose_file( self, @@ -47,6 +82,19 @@ class InitSettingCard(GroupHeaderCardWidget): if cb: cb(file_path) + def show_template_list_view(self): + view = ChooseTemplateView(self) + view.addTemplate("普通模板", lambda: open_template("template-defense-paper-student-1.xlsm", self)) + view.addTemplate("达成度模板", lambda: open_template("template-defense-paper-student-2.xlsm", self)) + w = TeachingTip.make( + target=self.sTemplateButton, + view=view, + tailPosition=TeachingTipTailPosition.TOP, + duration=-1, + parent=self + ) + view.closed.connect(w.close) + class ExportSettingsCard(GroupHeaderCardWidget): startSignal = Signal() diff --git a/ui/pyui/picker_ui.py b/ui/pyui/picker_ui.py index f63e81f..998b886 100644 --- a/ui/pyui/picker_ui.py +++ b/ui/pyui/picker_ui.py @@ -1,12 +1,13 @@ from PySide6.QtCore import Qt, Signal, QTimer from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QFileDialog -from qfluentwidgets import GroupHeaderCardWidget, PushButton, FluentIcon, PrimaryPushButton, IconWidget, BodyLabel, \ - SpinBox +from qfluentwidgets import PushButton, FluentIcon, PrimaryPushButton, IconWidget, BodyLabel, \ + SpinBox, HyperlinkButton from module.picker.schema import PickerExcel, PickerStudent from ui import MAIN_THEME_COLOR -from ui.components.widget import Widget +from ui.components.widget import Widget, MyGroupHeaderCardWidget from ui.pyui.sub.picker import PickStudentLabelUi +from utils.function import open_template class PickStudentMode(QWidget): @@ -15,10 +16,12 @@ class PickStudentMode(QWidget): def __init__(self, parent=None): super().__init__(parent) - self.card = GroupHeaderCardWidget(self) + self.card = MyGroupHeaderCardWidget(self) self.vbox = QVBoxLayout(self) self.vbox.setContentsMargins(0, 0, 0, 0) + self.btnHBox = QHBoxLayout(self) + self.openTemplateBtn = HyperlinkButton("", "下载模板") self.chooseBtn = PushButton("打开") self.startButton = PrimaryPushButton(FluentIcon.PLAY_SOLID, "开始") self.bottomLayout = QHBoxLayout() @@ -45,8 +48,10 @@ class PickStudentMode(QWidget): self.bottomLayout.addStretch(1) self.bottomLayout.addWidget(self.startButton, 0, Qt.AlignRight) self.bottomLayout.setAlignment(Qt.AlignVCenter) + self.btnHBox.addWidget(self.openTemplateBtn) + self.btnHBox.addWidget(self.chooseBtn) - self.group = self.card.addGroup(FluentIcon.DOCUMENT, "学生名单", "选择学生名单", self.chooseBtn) + self.group = self.card.addGroup(FluentIcon.DOCUMENT, "学生名单", "选择学生名单", self.btnHBox) self.spinGroup = self.card.addGroup(FluentIcon.SETTING, "提问次数", "设置提问的最大次数", self.spinbox) self.spinGroup.setSeparatorVisible(True) self.card.vBoxLayout.addLayout(self.bottomLayout) @@ -60,6 +65,7 @@ class PickStudentMode(QWidget): self.spinbox.valueChanged.connect(lambda: PickerExcel.save_total_time(value=self.spinbox.value())) self.startButton.clicked.connect(self.start_rolling) self.psui.rollingText.finishSignal.connect(self.finish_rolling) + self.openTemplateBtn.clicked.connect(lambda: open_template("template-pick-student.xlsm", self)) # ============================== self.filepath = "" self.students = [] diff --git a/ui/pyui/sub/picker.py b/ui/pyui/sub/picker.py index c868153..fb07152 100644 --- a/ui/pyui/sub/picker.py +++ b/ui/pyui/sub/picker.py @@ -3,7 +3,7 @@ import sys from PySide6.QtCore import Signal from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QApplication, QGridLayout from qfluentwidgets import PushButton, SpinBox, PrimaryPushButton, \ - BodyLabel + BodyLabel, CommandBarView, Action, FluentIcon, Flyout, FlyoutAnimationType from ui.components.widget import RollingTextWidget diff --git a/utils/function.py b/utils/function.py index 01d59ce..ce6f12d 100644 --- a/utils/function.py +++ b/utils/function.py @@ -1,10 +1,16 @@ import io import os import re +import shutil import sys +import tempfile +from typing import Optional import matplotlib +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QWidget from matplotlib import pyplot as plt +from qfluentwidgets import InfoBar, InfoBarPosition def format_ranges(nums): @@ -185,4 +191,35 @@ def resource_path(relative_path: str) -> str: return os.path.join(base_path, relative_path) +def open_template(file_name: str, widget: QWidget) -> None: + """将模板文件复制到临时目录并打开""" + file_path = resource_path("template/" + file_name) + + if not os.path.exists(file_path): + raise FileNotFoundError(f"Template file '{file_name}' not found.") + + # 复制到临时目录 + tmp_dir = tempfile.gettempdir() + tmp_file_path = os.path.join(tmp_dir, file_name) + shutil.copy(file_path, tmp_file_path) + + # 打开文件 + if sys.platform.startswith('win'): + os.startfile(tmp_file_path) + elif sys.platform.startswith('darwin'): + os.system(f'open "{tmp_file_path}"') + else: + os.system(f'xdg-open "{tmp_file_path}"') + + InfoBar.info( + title='已打开文件', + content="编辑后请将文件另存为,保存至其他目录", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP_RIGHT, + duration=2000, + parent=widget + ) + + DEVELOPMENT_ENV = getattr(sys, 'frozen', False)