修改界面

This commit is contained in:
2025-05-20 02:59:58 +08:00
parent 83b3790e8b
commit 9ca61d5092
13 changed files with 394 additions and 499 deletions

BIN
images/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

25
main.py
View File

@@ -1,26 +1,19 @@
import os
import shutil
import sys
from pathlib import Path
from PySide6.QtWidgets import QApplication
from ui.main import MainWindow
from utils.function import resource_path
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(resource_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()
try:
import pyi_splash
pyi_splash.close()
except ImportError:
pass
finally:
window.show()
sys.exit(app.exec())

View File

@@ -16,11 +16,24 @@ a = Analysis(
)
pyz = PYZ(a.pure)
splash = Splash(
'images\\splash.png',
binaries=a.binaries,
datas=a.datas,
text_pos=(5,298),
text_size=12,
text_color='black',
minify_script=True,
always_on_top=True,
)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
splash,
splash.binaries,
[],
debug=False,
bootloader_ignore_signals=False,

View File

@@ -22,6 +22,17 @@ class Question:
questions.append(Question(*line.strip('\n').split(',')))
return questions
@staticmethod
def load_from_xls(path: str) -> list['Question']:
questions = []
wb = load_workbook(path, read_only=True)
ws = wb.active
for row in ws.iter_rows(min_col=2, max_col=3, min_row=2, values_only=True):
if row[0] is None and row[1] is None:
break
questions.append(Question(*row))
return questions
@property
def no(self) -> str:
return self._no

44
module/worker.py Normal file
View File

@@ -0,0 +1,44 @@
import os
from pathlib import Path
from PySide6.QtCore import QObject, Signal
from module.doc import DocPaper
from module.schema import Course, Student, Question
from utils.function import resource_path
class DTGWorker(QObject):
progress = Signal(int)
finished = Signal()
def __init__(
self,
input_student_filepath: str,
input_question_filepath: str,
output_filepath: str,
output_filename: str
):
super().__init__()
self.input_filepath = input_student_filepath
self.input_question_filepath = input_question_filepath
self.output_filepath = output_filepath
self.output_filename = output_filename
def run(self):
course = Course.load_from_xls(self.input_filepath)
students = Student.load_from_xls(self.input_filepath)
questions = Question.load_from_xls(self.input_question_filepath)
d = DocPaper(self.output_filename, template_path=resource_path("template/template.docx"))
for index, student in enumerate(students):
if (p := int((index + 1) / len(students) * 100)) != 100:
self.progress.emit(p)
else:
self.progress.emit(99)
student.pick_question(questions)
d.add_paper(course, student)
d.save(self.output_filepath)
self.progress.emit(100)
os.startfile(Path(self.output_filepath) / f"{self.output_filename}.docx")
self.finished.emit()

View File

@@ -1,3 +1,4 @@
openpyxl
pyside6
python-docx
PySide6-Fluent-Widgets[full]

View File

@@ -1,90 +0,0 @@
0.0.01,简述本课程设计的基本内容。
0.0.03,简述本课程设计的基本步骤。
0.0.05,本课程设计采用的主要依据有哪些?
1.1.04,新建项目与扩建项目如何区分?
1.1.15,请举例说明基建的组成。
1.1.18,请说明基建的程序。
1.1.22,请说明工程造价的两层含义。
2.0.06,定额按用途划分一般有哪几种?各自的主要作用是什么?
2.1.06,施工定额与其它定额有何区别?
2.2.04,江苏省计价定额中的“综合单价”包括哪几部分?怎么计算出来的?
2.2.07,定额的换算方法一般有哪几种?请简单说明。
2.2.08,什么是材料的预算价格?
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,什么是材料暂估价?它在结算中如何完成结算?
6.1.42,规费有哪些?它们的计算基础是什么?
6.1.65,哪些费用为不可竞争费用?
6.2.04,工程类别该如何确定?
6.3.11,人工费调差如何实现?
6.3.23,总承包服务费该如何计取?
7.0.05,判定废标的常见情况有哪些?
1 0.0.01 简述本课程设计的基本内容。
2 0.0.03 简述本课程设计的基本步骤。
3 0.0.05 本课程设计采用的主要依据有哪些?
4 1.1.04 新建项目与扩建项目如何区分?
5 1.1.15 请举例说明基建的组成。
6 1.1.18 请说明基建的程序。
7 1.1.22 请说明工程造价的两层含义。
8 2.0.06 定额按用途划分一般有哪几种?各自的主要作用是什么?
9 2.1.06 施工定额与其它定额有何区别?
10 2.2.04 江苏省计价定额中的“综合单价”包括哪几部分?怎么计算出来的?
11 2.2.07 定额的换算方法一般有哪几种?请简单说明。
12 2.2.08 什么是材料的预算价格?
13 2.2.22 材料的预算价格包括哪些组成部分?
14 3.0.08 什么是设计概算?其一般由谁编制?
15 3.1.06 施工图预算一般由谁编制?主要依据有哪些?
16 3.1.15 常用的投标策略有哪些?
17 3.2.12 什么是“两算对比”?由谁完成?有何意义?
18 3.3.04 竣工决算由谁完成?其主要方法有哪些?
19 3.3.08 竣工决算的主要依据有哪些?
20 4.1.02 什么是工程量清单?
21 4.1.05 什么是招标工程量清单?什么是已标价工程量清单?
22 4.1.09 工程量清单包括哪五大类?
23 4.1.09 什么是招标控制价?它由谁编制?
24 4.1.11 清单的项目编码该如何确定?
25 4.1.13 清单中的项目名称该如何确定?
26 4.2.05 什么是综合单价?
27 4.2.12 综合单价包括哪些组成部分?各组成部分如何计算得到?
28 4.2.14 综合单价如何形成?
29 4.3.22 工程量的计算精度有何要求?
30 5.0.02 统筹法算量中提到的“三线一面”是指什么?
31 5.0.12 普通的单层建筑如何计算建筑面积?
32 5.0.15 坡屋顶该如何计算建筑面积?
33 5.0.16 阳台该如何计算建筑面积?
34 5.0.19 楼梯该如何计算建筑面积?
35 5.0.24 雨篷该如何计算建筑面积?
36 5.0.28 飘窗计算建筑面积的条件有什么?
37 5.01.01 回填土一般有哪几类?
38 5.01.04 计算挖基坑土方时,如何考虑工作面和放坡?
39 5.01.05 挖土深度该如何确定?
40 5.01.08 挖土时干、湿土该如何划分?
41 5.01.18 挖沟槽土方和挖基坑土方有何区别?
42 5.01.37 平整场地的内容是什么?它该如何计算工程量?
43 5.01.57 如何判定是缺土内运还是余土外运?
44 5.03.05 预制钢筋混凝土管桩该如何计算工程量?
45 5.03.08 什么是送桩?其该如何计算工程量?
46 5.03.16 打孔灌注桩一般应计算哪些子项的工程量?
47 5.03.24 钻孔灌注桩一般应计算哪些子项的工程量?
48 5.03.39 截桩该如何计算工程量?
49 5.04.08 砖墙与基础如何划分?
50 5.04.11 计算砖墙工程量时,墙高该如何取定?
51 5.04.15 请简述不同厚度的标准砖墙的计算厚度分别是多少。
52 5.04.19 计算砖墙工程量时,应扣除哪些内容?
53 5.04.21 砖砌大放脚该如何计算工程量?
54 5.04.33 砖砌台阶该如何计算工程量?
55 5.05.04 什么是钢筋的理论重量?如何得到?
56 5.05.05 钢筋工程量有几种计算方法?如何选用?
57 5.06.04 基础下的混凝土垫层,其厚度有何约定?
58 5.06.04 混凝土墙该如何计算工程量?
59 5.06.08 钢筋混凝土独立基础该如何计算工程量?杯形基础该如何计算工程量并套定额?
60 5.06.12 混凝土柱高该如何确定?
61 5.06.18 构造柱该如何计算工程量?
62 5.06.34 钢筋混凝土过梁长度该如何取定?
63 5.06.46 有梁板和无梁板分别如何计算工程量?
64 5.06.55 混凝土墙计算工程量时,有哪些内容应扣除?
65 5.06.73 钢筋混凝土直形楼梯该如何计算工程量?
66 5.06.78 钢筋混凝土阳台(其栏板为砖砌)该如何计算工程量?
67 5.06.82 钢筋混凝土雨篷该如何计算工程量?
68 5.10.05 卷材防水屋面该如何计算工程量?
69 5.10.09 刚性防水屋面该如何计算工程量?
70 5.13.07 整体面层地面和块料面层地面工程量的计算有何区别?
71 5.13.23 楼梯整体面层和楼梯块料面层工程量的计算有何区别?
72 5.14.05 内墙面抹灰该如何计算工程量?
73 5.14.11 外墙面抹灰该如何计算工程量?
74 5.15.05 吊顶中,简单型龙骨和复杂性龙骨有何区别?
75 5.15.15 天棚抹灰该如何计算工程量?
76 5.19.03 何时应计算超高费?
77 5.20.05 综合脚手架和单项脚手架分别什么时候采用?
78 5.21.03 模板工程量有几种计算方法?如何选用?
79 6.1.03 清单计价条件下,工程造价包括哪几大部分?
80 6.1.14 机械费包括哪些组成部分?
81 6.1.23 单价措施费和总价措施费有何区别?
82 6.1.32 什么是暂列金额?如果工程中未列支完,剩余的暂列金额归谁所有?
83 6.1.33 什么是暂列金额?它由谁确定?
84 6.1.37 什么是材料暂估价?它在结算中如何完成结算?
85 6.1.42 规费有哪些?它们的计算基础是什么?
86 6.1.65 哪些费用为不可竞争费用?
87 6.2.04 工程类别该如何确定?
88 6.3.11 人工费调差如何实现?
89 6.3.23 总承包服务费该如何计取?
90 7.0.05 判定废标的常见情况有哪些?

9
ui/components/widget.py Normal file
View File

@@ -0,0 +1,9 @@
from PySide6.QtWidgets import QFrame
class Widget(QFrame):
def __init__(self, key: str, parent=None):
super().__init__(parent=parent)
# 必须给子界面设置全局唯一的对象名
self.setObjectName(key.replace(' ', '-'))

View File

@@ -1,102 +1,23 @@
import os
import platform
import subprocess
from pathlib import Path
from qfluentwidgets import FluentIcon, MSFluentWindow, NavigationItemPosition
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
from utils.function import resource_path
from ui.pyui.about_ui import AboutWidget
from ui.pyui.defense_ui import DefenseWidget
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):
class MainWindow(MSFluentWindow):
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)
self.homeInterface = DefenseWidget('Defense Interface', self)
self.aboutInterface = AboutWidget('About Interface', self)
def on_button_clicked(self):
self.statusBar().showMessage("这是状态栏信息")
self.initNavigation()
self.initWindow()
def choose_student_list(self):
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Excel 文件 (*.xlsx);")
if file_path:
self.student_list_path.setText(file_path)
def initNavigation(self):
self.addSubInterface(self.homeInterface, FluentIcon.HOME, '首页')
self.addSubInterface(self.aboutInterface, FluentIcon.INFO, '关于', position=NavigationItemPosition.BOTTOM)
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)
def initWindow(self):
self.resize(900, 700)
self.setWindowTitle('答辩题目生成器')

View File

@@ -1,159 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="windowModality">
<enum>Qt::WindowModality::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>200</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>学生名单</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="student_list_path"/>
</item>
<item>
<widget class="QPushButton" name="select_student_list">
<property name="text">
<string>选择</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>导出设置</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>目 录</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="output_path"/>
</item>
<item>
<widget class="QPushButton" name="select_output_dir">
<property name="text">
<string>选择</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>文件名</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="output_filename"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="edit_topics">
<property name="text">
<string>编辑题库</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="start">
<property name="text">
<string>导出</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

49
ui/pyui/about_ui.py Normal file
View File

@@ -0,0 +1,49 @@
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QVBoxLayout
from qfluentwidgets import PrimaryPushSettingCard, FluentIcon, GroupHeaderCardWidget, PushButton
from ui.components.widget import Widget
class AboutWidget(Widget):
def __init__(self, key: str, parent=None):
super().__init__(key, parent)
self.version_card = PrimaryPushSettingCard(
text="获取源码",
icon=FluentIcon.INFO,
title="关于",
content="作者许方杰。当前版本1.0.0\n使用 GPLv3 开源协议,作者不对使用本软件造成的任何损失负责。"
)
self.button_list = [
PushButton("访问网站"),
PushButton("访问网站"),
PushButton("访问网站"),
PushButton("访问网站"),
]
self.url_list = [
"https://qt.io",
"https://qfluentwidgets.com",
"https://openpyxl.readthedocs.io/en/stable",
"https://github.com/python-openxml/python-docx"
]
self.group_card = GroupHeaderCardWidget(self)
self.group_card.setTitle("第三方框架")
self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.version_card)
self.vbox.addWidget(self.group_card)
self.vbox.addStretch(1)
self.group_card.addGroup("", "PySide6", self.url_list[0], self.button_list[0])
self.group_card.addGroup("", "QFluentWidgets", self.url_list[1], self.button_list[1])
self.group_card.addGroup("", "openpyxl", self.url_list[2], self.button_list[2])
self.group_card.addGroup("", "python-docx", self.url_list[3], self.button_list[3])
self.version_card.clicked.connect(
lambda: QDesktopServices.openUrl("https://cantyonion.site/git/cantyonion/DefenseTopicGenerator")
)
self.button_list[0].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[0]))
self.button_list[1].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[1]))
self.button_list[2].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[2]))
self.button_list[3].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[3]))

243
ui/pyui/defense_ui.py Normal file
View File

@@ -0,0 +1,243 @@
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

View File

@@ -1,140 +0,0 @@
# -*- 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