From 6961b70a7deab7e335ffdda33c777e1344404015 Mon Sep 17 00:00:00 2001 From: Jeffrey Hsu Date: Fri, 16 May 2025 16:15:18 +0800 Subject: [PATCH] first commit --- .gitignore | 8 +++ main.py | 25 +++++++ main.spec | 38 ++++++++++ module/doc.py | 62 ++++++++++++++++ module/schema.py | 115 +++++++++++++++++++++++++++++ requirements.txt | 3 + template/questions.csv | 90 +++++++++++++++++++++++ template/template.docx | Bin 0 -> 16174 bytes ui/main.py | 110 ++++++++++++++++++++++++++++ ui/main.ui | 159 +++++++++++++++++++++++++++++++++++++++++ ui/pyui/main.py | 140 ++++++++++++++++++++++++++++++++++++ 11 files changed, 750 insertions(+) create mode 100644 .gitignore create mode 100644 main.py create mode 100644 main.spec create mode 100644 module/doc.py create mode 100644 module/schema.py create mode 100644 requirements.txt create mode 100644 template/questions.csv create mode 100644 template/template.docx create mode 100644 ui/main.py create mode 100644 ui/main.ui create mode 100644 ui/pyui/main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f710f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.venv +build +dist +.vscode +.idea +__pycache__ +*.pyc +files \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..00be303 --- /dev/null +++ b/main.py @@ -0,0 +1,25 @@ +import os +import shutil +import sys +from pathlib import Path + +from PySide6.QtWidgets import QApplication + +from ui.main import MainWindow + +appdata_dir = Path(os.environ["APPDATA"]) +dtg_dir = appdata_dir / "dtg" +target_csv = dtg_dir / "questions.csv" + +if not target_csv.exists(): + dtg_dir.mkdir(parents=True, exist_ok=True) + source_csv = Path("template/questions.csv") + if not source_csv.exists(): + raise FileNotFoundError(f"源文件不存在:{source_csv.resolve()}") + shutil.copy(source_csv, target_csv) + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..2791bbb --- /dev/null +++ b/main.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[], + datas=[('template', 'template')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + name='答辩题目生成器', +) diff --git a/module/doc.py b/module/doc.py new file mode 100644 index 0000000..dc25a7c --- /dev/null +++ b/module/doc.py @@ -0,0 +1,62 @@ +import pathlib +from copy import deepcopy + +from docx import Document +from docx.shared import Cm + +from module.schema import Course, Student, Question + + +class DocPaper: + def __init__(self, filename: str = 'Paper', template_path: str = '../template/template.docx'): + self._doc = Document() + self._template = Document(template_path) + self._filename = filename + + section = self._doc.sections[0] + section.top_margin = Cm(2) + section.bottom_margin = Cm(1) + section.left_margin = Cm(1.4) + section.right_margin = Cm(1.4) + + def add_paper(self, course: Course, student: Student): + temp_table = self._template.tables[0] + new_table = deepcopy(temp_table) + para = self._doc.add_paragraph() + para._p.addprevious(new_table._element) + + data_list = { + '%CNAME%': course.name, + '%CLASS%': student.class_name, + '%SNAME%': student.name, + '%NO%': student.no, + '%SO%': student.so, + '%Q1%': student.picked_questions[0].topic, + '%Q2%': student.picked_questions[1].topic, + '%Q3%': student.picked_questions[2].topic + } + + # 替换表格中的占位符 + for row in new_table.rows: + for cell in row.cells: + for para in cell.paragraphs: + for run in para.runs: + for key, val in data_list.items(): + if key in run.text: + run.text = run.text.replace(key, val) + break + + def save(self, path: str = './'): + self._doc.save(str(pathlib.Path(path) / f"{self._filename}.docx")) + + +if __name__ == '__main__': + course = Course.load_from_xls('../files/21工程管理-工程造价Ⅱ-点名册-系统0828.xlsx') + students = Student.load_from_xls('../files/21工程管理-工程造价Ⅱ-点名册-系统0828.xlsx') + questions = Question.load_from_csv() + + d = DocPaper() + for student in students: + student.pick_question(questions) + d.add_paper(course, student) + d.save() diff --git a/module/schema.py b/module/schema.py new file mode 100644 index 0000000..022e789 --- /dev/null +++ b/module/schema.py @@ -0,0 +1,115 @@ +import random +from typing import Optional +from openpyxl.reader.excel import load_workbook + + +class Question: + def __init__(self, no: str, topic: str): + self._no: str = no + self._topic: str = topic + + def __str__(self): + return f"Question" + + def __repr__(self): + return self.__str__() + + @staticmethod + def load_from_csv(path: Optional[str] = None) -> list['Question']: + questions = [] + with open(path if path else '../files/questions.csv', encoding='gbk') as f: + for line in f.readlines(): + questions.append(Question(*line.strip('\n').split(','))) + return questions + + @property + def no(self) -> str: + return self._no + + @property + def topic(self) -> str: + return self._topic + + +class Student: + def __init__(self, no: str, so: str, name: str, major: str, class_name: str): + self._no: str = no + self._so: str = so + self._name: str = name + self._major: str = major + self._class_name: str = class_name + self._picked_questions: list[Question] = [] + + def __str__(self): + return f"Student" + + def __repr__(self): + return self.__str__() + + @staticmethod + def load_from_xls(path: str) -> list['Student']: + wb = load_workbook(path, read_only=True) + ws = wb.active + students = [] + for row in ws.iter_rows(min_row=6, max_col=5, values_only=True): + students.append(Student(*row)) + return [x for x in students if x.valid] + + @property + def valid(self) -> bool: + return bool(self._no and self._so and self._name and self._major and self._class_name) + + @property + def picked_questions(self) -> list[Question]: + return self._picked_questions + + @property + def no(self) -> str: + return self._no + + @property + def so(self) -> str: + return self._so + + @property + def name(self) -> str: + return self._name + + @property + def major(self) -> str: + return self._major + + @property + def class_name(self) -> str: + return self._class_name + + def pick_question(self, questions: list[Question], num: int = 3) -> None: + if len(questions) < num: + raise ValueError("Not enough questions to pick from.") + self._picked_questions = random.sample(questions, num) + + +class Course: + def __init__(self, name: str): + self._name = name + + def __str__(self): + return f"Course" + + def __repr__(self): + return self.__str__() + + @staticmethod + def load_from_xls(path: str) -> 'Course': + wb = load_workbook(path, read_only=True) + ws = wb.active + name: str = ws['E3'].value + return Course(name[5:]) + + @property + def name(self) -> str: + return self._name + + +if __name__ == '__main__': + ... \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e2a3b9b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +openpyxl +pyside6 +python-docx diff --git a/template/questions.csv b/template/questions.csv new file mode 100644 index 0000000..396f8b6 --- /dev/null +++ b/template/questions.csv @@ -0,0 +1,90 @@ +0.0.01,γƵĻݡ +0.0.03,γƵĻ衣 +0.0.05,γƲõҪЩ +1.1.04,½ĿĿ֣ +1.1.15,˵ɡ +1.1.18,˵ij +1.1.22,˵۵㺬塣 +2.0.06,;һļ֣ԵҪʲô +2.1.06,ʩк +2.2.04,ʡƼ۶еġۺϵۡļ֣ôģ +2.2.07,Ļ㷽һļ֣˵ +2.2.08,ʲôDzϵԤ۸ +2.2.22,ϵԤ۸Щɲ֣ +3.0.08,ʲôƸ㣿һ˭ƣ +3.1.06,ʩͼԤһ˭ƣҪЩ +3.1.15,õͶЩ +3.2.12,ʲôǡԱȡ˭ɣк壿 +3.3.04,˭ɣҪЩ +3.3.08,ҪЩ +4.1.02,ʲôǹ嵥 +4.1.05,ʲôб깤嵥ʲôѱ۹嵥 +4.1.09,嵥ࣿ +4.1.09,ʲôбƼۣ˭ƣ +4.1.11,嵥Ŀȷ +4.1.13,嵥еĿƸȷ +4.2.05,ʲôۺϵۣ +4.2.12,ۺϵ۰Щɲ֣ɲμõ +4.2.14,ۺϵγɣ +4.3.22,ļ㾫кҪ +5.0.02,ͳ﷨ᵽġһ桱ָʲô +5.0.12,ͨĵ㽨μ㽨 +5.0.15,ݶμ㽨 +5.0.16,̨μ㽨 +5.0.19,¥ݸμ㽨 +5.0.24,μ㽨 +5.0.28,Ʈ㽨ʲô +5.01.01,һļࣿ +5.01.04,ڻʱοǹͷ£ +5.01.05,ȸȷ +5.01.08,ʱɡʪλ֣ +5.01.18,ڹڻк +5.01.37,ƽصʲôμ㹤 +5.01.57,жȱ˻ˣ +5.03.05,ԤƸֽ׮μ㹤 +5.03.08,ʲô׮μ㹤 +5.03.16,׹ע׮һӦЩĹ +5.03.24,׹ע׮һӦЩĹ +5.03.39,׮μ㹤 +5.04.08,שǽλ֣ +5.04.11,שǽʱǽ߸ȡ +5.04.15,ͬȵı׼שǽļȷֱǶ١ +5.04.19,שǽʱӦ۳Щݣ +5.04.21,שŽŸμ㹤 +5.04.33,ש̨׸μ㹤 +5.05.04,ʲôǸֽεõ +5.05.05,ֽмּ㷽ѡã +5.06.04,µĻ㣬кԼ +5.06.04,ǽμ㹤 +5.06.08,ֽμ㹤λμ㹤׶ +5.06.12,߸ȷ +5.06.18,μ㹤 +5.06.34,ֽȸȡ +5.06.46,ֱμ㹤 +5.06.55,ǽ㹤ʱЩӦ۳ +5.06.73,ֱֽ¥ݸμ㹤 +5.06.78,ֽ̨Ϊשμ㹤 +5.06.82,ֽμ㹤 +5.10.05,ķˮμ㹤 +5.10.09,Էˮμ㹤 +5.13.07,Ϳ湤ļк +5.13.23,¥¥ݿ㹤ļк +5.14.05,ǽĨҸμ㹤 +5.14.11,ǽĨҸμ㹤 +5.15.05,УǺ͸к +5.15.15,ĨҸμ㹤 +5.19.03,ʱӦ㳬߷ѣ +5.20.05,ۺϽּܺ͵ֱּܷʲôʱã +5.21.03,ģ幤мּ㷽ѡã +6.1.03,嵥Ƽ£۰ļ󲿷֣ +6.1.14,еѰЩɲ֣ +6.1.23,۴ʩѺܼ۴ʩк +6.1.32,ʲôнδ֧꣬ʣн˭У +6.1.33,ʲôн˭ȷ +6.1.37,ʲôDzݹۣڽɽ㣿 +6.1.42,Щǵļʲô +6.1.65,ЩΪɾã +6.2.04,ȷ +6.3.11,˹ѵʵ֣ +6.3.23,ܳаѸμȡ +7.0.05,жϱijЩ diff --git a/template/template.docx b/template/template.docx new file mode 100644 index 0000000000000000000000000000000000000000..e5231cf0886438b90866c176698db15e602d238b GIT binary patch literal 16174 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%0Agu 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 I{!#mX00nPTE&u=k literal 0 HcmV?d00001 diff --git a/ui/main.py b/ui/main.py new file mode 100644 index 0000000..54dba71 --- /dev/null +++ b/ui/main.py @@ -0,0 +1,110 @@ +import os +import platform +import subprocess +import sys +from pathlib import Path + +from PySide6.QtCore import QObject, QThread, Signal +from PySide6.QtWidgets import QMainWindow, QFileDialog + +from module.doc import DocPaper +from module.schema import Course, Student, Question +from ui.pyui.main import Ui_MainWindow + + +def resource_path(relative_path: str) -> str: + if hasattr(sys, '_MEIPASS'): + base_path = sys._MEIPASS + else: + base_path = os.path.abspath(".") + return os.path.join(base_path, relative_path) + + +class Worker(QObject): + progress = Signal(str) + finished = Signal() + + def __init__(self, input_filepath: str, output_filepath: str, output_filename: str): + super().__init__() + self.input_filepath = input_filepath + self.output_filepath = output_filepath + self.output_filename = output_filename + + def run(self): + self.progress.emit("第一步:正在读取课程信息...") + course = Course.load_from_xls(self.input_filepath) + self.progress.emit("第二步:正在读取学生信息...") + students = Student.load_from_xls(self.input_filepath) + self.progress.emit("第三步:正在读取题目信息...") + questions = Question.load_from_csv(str(Path(os.environ["APPDATA"]) / "dtg" / "questions.csv")) + + d = DocPaper(self.output_filename, template_path=resource_path("template/template.docx")) + for student in students: + self.progress.emit(f"第四步:({student.no}/{len(students)})正在处理学生:{student.name},学号:{student.so}") + student.pick_question(questions) + d.add_paper(course, student) + self.progress.emit("第五步:正在保存文件...") + d.save(self.output_filepath) + self.progress.emit("第六步:文件保存成功!") + os.startfile(Path(self.output_filepath) / f"{self.output_filename}.docx") + + +class MainWindow(QMainWindow, Ui_MainWindow): + def __init__(self): + super().__init__() + self.setupUi(self) + self.setWindowTitle("答辩题目生成器") + + self.select_student_list.clicked.connect(self.choose_student_list) + self.select_output_dir.clicked.connect(self.choose_output_dir) + self.edit_topics.clicked.connect(self.edit_questions) + self.start.clicked.connect(self.start_generate) + + def on_button_clicked(self): + self.statusBar().showMessage("这是状态栏信息") + + def choose_student_list(self): + file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Excel 文件 (*.xlsx);") + if file_path: + self.student_list_path.setText(file_path) + + input_dir_path = file_path[:file_path.rfind('/')] + default_output_filename = file_path[file_path.rfind('/') + 1:file_path.rfind('-点名册')] + + self.output_path.setText(input_dir_path) + self.output_filename.setText(default_output_filename) + + def choose_output_dir(self): + dir_path = QFileDialog.getExistingDirectory(self, "选择文件夹", "") + if dir_path: + self.output_path.setText(dir_path) + + def edit_questions(self): + csv_path = Path(os.environ["APPDATA"]) / "dtg" / "questions.csv" + + if platform.system() == "Windows": + os.startfile(csv_path.resolve()) + elif platform.system() == "Darwin": # macOS + subprocess.run(["open", csv_path.resolve()]) + else: # Linux + subprocess.run(["xdg-open", csv_path.resolve()]) + + self.update_statusbar("已打开题目文件,请在外部程序编辑后保存。") + + def start_generate(self): + self.thread = QThread() + self.worker = Worker(self.student_list_path.text(), self.output_path.text(), self.output_filename.text()) + self.worker.moveToThread(self.thread) + + # 线程启动与信号连接 + self.thread.started.connect(self.worker.run) + self.worker.progress.connect(self.update_statusbar) + self.worker.finished.connect(self.thread.quit) + self.worker.finished.connect(self.worker.deleteLater) + self.thread.finished.connect(self.thread.deleteLater) + + # 启动线程 + self.thread.start() + + def update_statusbar(self, message): + self.statusBar().showMessage(message) diff --git a/ui/main.ui b/ui/main.ui new file mode 100644 index 0000000..32ab679 --- /dev/null +++ b/ui/main.ui @@ -0,0 +1,159 @@ + + + MainWindow + + + Qt::WindowModality::ApplicationModal + + + + 0 + 0 + 500 + 200 + + + + + 500 + 200 + + + + + 500 + 200 + + + + MainWindow + + + + + + + + + 学生名单 + + + + + + + + + + 选择 + + + + + + + + + 导出设置 + + + + + + + + + 40 + 0 + + + + + 40 + 16777215 + + + + 目 录 + + + + + + + + + + 选择 + + + + + + + + + + + + 40 + 0 + + + + + 40 + 16777215 + + + + 文件名 + + + + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 编辑题库 + + + + + + + 导出 + + + + + + + + + + + + diff --git a/ui/pyui/main.py b/ui/pyui/main.py new file mode 100644 index 0000000..36e4443 --- /dev/null +++ b/ui/pyui/main.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'main.ui' +## +## Created by: Qt User Interface Compiler version 6.9.0 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QGridLayout, QGroupBox, QHBoxLayout, + QLabel, QLineEdit, QMainWindow, QPushButton, + QSizePolicy, QSpacerItem, QStatusBar, QWidget) + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.setWindowModality(Qt.WindowModality.ApplicationModal) + MainWindow.resize(500, 200) + MainWindow.setMinimumSize(QSize(500, 200)) + MainWindow.setMaximumSize(QSize(500, 200)) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.gridLayout_2 = QGridLayout(self.centralwidget) + self.gridLayout_2.setObjectName(u"gridLayout_2") + self.horizontalLayout_3 = QHBoxLayout() + self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.label = QLabel(self.centralwidget) + self.label.setObjectName(u"label") + + self.horizontalLayout_3.addWidget(self.label) + + self.student_list_path = QLineEdit(self.centralwidget) + self.student_list_path.setObjectName(u"student_list_path") + + self.horizontalLayout_3.addWidget(self.student_list_path) + + self.select_student_list = QPushButton(self.centralwidget) + self.select_student_list.setObjectName(u"select_student_list") + + self.horizontalLayout_3.addWidget(self.select_student_list) + + + self.gridLayout_2.addLayout(self.horizontalLayout_3, 0, 0, 1, 1) + + self.groupBox = QGroupBox(self.centralwidget) + self.groupBox.setObjectName(u"groupBox") + self.gridLayout = QGridLayout(self.groupBox) + self.gridLayout.setObjectName(u"gridLayout") + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.label_2 = QLabel(self.groupBox) + self.label_2.setObjectName(u"label_2") + self.label_2.setMinimumSize(QSize(40, 0)) + self.label_2.setMaximumSize(QSize(40, 16777215)) + + self.horizontalLayout.addWidget(self.label_2) + + self.output_path = QLineEdit(self.groupBox) + self.output_path.setObjectName(u"output_path") + + self.horizontalLayout.addWidget(self.output_path) + + self.select_output_dir = QPushButton(self.groupBox) + self.select_output_dir.setObjectName(u"select_output_dir") + + self.horizontalLayout.addWidget(self.select_output_dir) + + + self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) + + self.horizontalLayout_2 = QHBoxLayout() + self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.label_3 = QLabel(self.groupBox) + self.label_3.setObjectName(u"label_3") + self.label_3.setMinimumSize(QSize(40, 0)) + self.label_3.setMaximumSize(QSize(40, 16777215)) + + self.horizontalLayout_2.addWidget(self.label_3) + + self.output_filename = QLineEdit(self.groupBox) + self.output_filename.setObjectName(u"output_filename") + + self.horizontalLayout_2.addWidget(self.output_filename) + + + self.gridLayout.addLayout(self.horizontalLayout_2, 1, 0, 1, 1) + + + self.gridLayout_2.addWidget(self.groupBox, 1, 0, 1, 1) + + self.horizontalLayout_4 = QHBoxLayout() + self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + + self.horizontalLayout_4.addItem(self.horizontalSpacer) + + self.edit_topics = QPushButton(self.centralwidget) + self.edit_topics.setObjectName(u"edit_topics") + + self.horizontalLayout_4.addWidget(self.edit_topics) + + self.start = QPushButton(self.centralwidget) + self.start.setObjectName(u"start") + + self.horizontalLayout_4.addWidget(self.start) + + + self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 0, 1, 1) + + MainWindow.setCentralWidget(self.centralwidget) + self.statusbar = QStatusBar(MainWindow) + self.statusbar.setObjectName(u"statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) + self.label.setText(QCoreApplication.translate("MainWindow", u"\u5b66\u751f\u540d\u5355", None)) + self.select_student_list.setText(QCoreApplication.translate("MainWindow", u"\u9009\u62e9", None)) + self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"\u5bfc\u51fa\u8bbe\u7f6e", None)) + self.label_2.setText(QCoreApplication.translate("MainWindow", u"\u76ee \u5f55", None)) + self.select_output_dir.setText(QCoreApplication.translate("MainWindow", u"\u9009\u62e9", None)) + self.label_3.setText(QCoreApplication.translate("MainWindow", u"\u6587\u4ef6\u540d", None)) + self.edit_topics.setText(QCoreApplication.translate("MainWindow", u"\u7f16\u8f91\u9898\u5e93", None)) + self.start.setText(QCoreApplication.translate("MainWindow", u"\u5bfc\u51fa", None)) + # retranslateUi +