Compare commits

...

9 Commits

8 changed files with 302 additions and 65 deletions

View File

@@ -1,37 +1,36 @@
@echo off @echo off
setlocal setlocal
chcp 65001
echo === 尝试激活虚拟环境 === echo === Activating virtual environment ===
if exist ".venv\Scripts\activate.bat" ( if exist ".venv\Scripts\activate.bat" (
call .venv\Scripts\activate.bat call .venv\Scripts\activate.bat
) else ( ) else (
echo 未发现虚拟环境,尝试创建中... echo Virtual environment not found. Creating...
python -m venv .venv python -m venv .venv
if errorlevel 1 ( if errorlevel 1 (
echo [错误] 创建虚拟环境失败,请确认是否已安装 Python。 echo [ERROR] Failed to create virtual environment. Please ensure Python is installed.
pause pause
exit /b 1 exit /b 1
) )
echo 虚拟环境创建成功,开始激活... echo Virtual environment created. Activating...
call .venv\Scripts\activate.bat call .venv\Scripts\activate.bat
) )
echo === 安装依赖项 === echo === Installing dependencies ===
pip install -r requirements.txt pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple -r requirements.txt
if errorlevel 1 ( if errorlevel 1 (
echo [错误] pip 安装依赖失败! echo [ERROR] pip failed to install dependencies.
pause pause
exit /b 1 exit /b 1
) )
echo === 使用 pyinstaller 构建 === echo === Building with pyinstaller ===
python .\utils\hook.py python .\utils\hook.py
pyinstaller .\main.spec pyinstaller .\main.spec
if errorlevel 1 ( if errorlevel 1 (
echo [错误] 构建失败! echo [ERROR] Build failed.
pause pause
exit /b 1 exit /b 1
) )
echo === 构建完成 === echo === Build completed ===

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2025 Jeffrey Hsu - JITToolBox # Copyright (c) 2025-2026 Jeffrey Hsu - JITToolBox
# # # #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -155,9 +155,14 @@ class DocxWriter:
for performance in self.excel_reader.achievement_level[r_index]: for performance in self.excel_reader.achievement_level[r_index]:
non_none_count = 3 - performance.scores.count(None) non_none_count = 3 - performance.scores.count(None)
if non_none_count > 1: if non_none_count > 1:
try:
cell_start = table.cell(row, col_span) cell_start = table.cell(row, col_span)
cell_end = table.cell(row, col_span + non_none_count - 1) cell_end = table.cell(row, col_span + non_none_count - 1)
cell_start.merge(cell_end) cell_start.merge(cell_end)
except IndexError:
pass
# self.signal(f"单元格合并失败:({row}, {col_span}),需要自行检查表格准确性",
# LOGLEVEL.WARNING)
col_span += non_none_count col_span += non_none_count
start = rows - X + 3 + self.excel_reader.kpi_number start = rows - X + 3 + self.excel_reader.kpi_number
@@ -239,8 +244,8 @@ class DocxWriter:
f". 课程目标达成情况的合理性评价") f". 课程目标达成情况的合理性评价")
self.set_run_font(run, 14, 'Times New Roman', '黑体', True) self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
rows = 9 rows = 11
cols = 4 cols = 6
table = doc.add_table(rows=rows, cols=cols) table = doc.add_table(rows=rows, cols=cols)
# 设置外侧框线粗1.5磅内侧框线粗0.5磅 # 设置外侧框线粗1.5磅内侧框线粗0.5磅
self.set_table_borders(table) self.set_table_borders(table)
@@ -250,9 +255,21 @@ class DocxWriter:
cell_end = table.cell(0, cols - 1) cell_end = table.cell(0, cols - 1)
cell_start.merge(cell_end) cell_start.merge(cell_end)
# 合并第二行至最后 # 合并第二行至最后
for i in range(1, 9): for i in range(1, rows):
if i == 2: match i:
continue case 2:
table.cell(i, 2).merge(table.cell(i, 3))
table.cell(i, 4).merge(table.cell(i, 5))
table.cell(i, 1).width = Cm(7.42)
table.cell(i, 2).width = Cm(7.42)
table.cell(i, 4).width = Cm(7.41)
case 8 | 10:
table.cell(i - 1, 0).merge(table.cell(i, 0))
table.cell(i, 1).width = Cm(11.23)
table.cell(i, 2).width = Cm(1.48)
table.cell(i, 3).width = Cm(3.4)
table.cell(i, 4).width = Cm(1.39)
case _:
cell_start = table.cell(i, 1) cell_start = table.cell(i, 1)
cell_end = table.cell(i, cols - 1) cell_end = table.cell(i, cols - 1)
cell_start.merge(cell_end) cell_start.merge(cell_end)
@@ -276,8 +293,36 @@ class DocxWriter:
for t_index, table in enumerate(doc.tables): for t_index, table in enumerate(doc.tables):
self.set_table_borders(table) self.set_table_borders(table)
# part_3_table_index 表格第9和11行索引8和10特殊边框处理
if t_index in part_3_table_index:
for r_idx in [8, 10]:
row = table.rows[r_idx]
prev_row = table.rows[r_idx - 1]
# 上一行第8行和第10行索引7和9第2-6列移除下边框
for c_idx in range(1, 6):
self.set_cell_border(prev_row.cells[c_idx], bottom=0)
# 第2列索引1没有上边框和右边框
self.set_cell_border(row.cells[1], top=0, right=0)
# 第3-5列索引2-4没有上边框和左右边框
for c_idx in [2, 3, 4]:
self.set_cell_border(row.cells[c_idx], top=0, left=0, right=0)
# 第6列索引5没有上边框和左边框
self.set_cell_border(row.cells[5], top=0, left=0)
# 插入签名图片
if self.excel_reader.major_director_signature_image is not None:
self.insert_pil_image(table.cell(8, 3),
self.excel_reader.major_director_signature_image,
height=Cm(1.2))
if self.excel_reader.course_leader_signature_image is not None:
self.insert_pil_image(table.cell(10, 3),
self.excel_reader.course_leader_signature_image,
height=Cm(1.2))
for r_index, row in enumerate(table.rows): for r_index, row in enumerate(table.rows):
row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
# part_3_table_index 表格第9和11行索引8和10行高为1.2cm
if t_index in part_3_table_index and r_index in [8, 10]:
row.height = Cm(1.2)
else:
row.height = Cm(0.7) row.height = Cm(0.7)
for c_index, cell in enumerate(row.cells): for c_index, cell in enumerate(row.cells):
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
@@ -336,6 +381,8 @@ class DocxWriter:
(6, 1), (6, 1),
(7, 1), (7, 1),
(8, 1), (8, 1),
(9, 1),
(10, 1),
] ]
if r_index == 0: if r_index == 0:
for run in paragraph.runs: for run in paragraph.runs:
@@ -379,6 +426,7 @@ class DocxWriter:
""" """
设置单元格边框 设置单元格边框
kwargs: top, bottom, left, right, inside_h, inside_v kwargs: top, bottom, left, right, inside_h, inside_v
值为0时移除边框值大于0时设置边框粗细
""" """
tc = cell._tc tc = cell._tc
tcPr = tc.get_or_add_tcPr() tcPr = tc.get_or_add_tcPr()
@@ -391,6 +439,10 @@ class DocxWriter:
if value is not None: if value is not None:
tag = 'w:{}'.format(key) tag = 'w:{}'.format(key)
border = OxmlElement(tag) border = OxmlElement(tag)
if value == 0:
# 移除边框
border.set(qn('w:val'), 'nil')
else:
border.set(qn('w:val'), 'single') border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), str(int(value * 8))) border.set(qn('w:sz'), str(int(value * 8)))
border.set(qn('w:space'), '0') border.set(qn('w:space'), '0')
@@ -490,6 +542,15 @@ class DocxWriter:
run = paragraph.add_run() run = paragraph.add_run()
run.add_picture(image_stream, width=width, height=height) run.add_picture(image_stream, width=width, height=height)
def insert_pil_image(self, cell, pil_image, width=None, height=Cm(4.5)):
"""插入PIL Image对象到单元格"""
image_stream = io.BytesIO()
pil_image.save(image_stream, format='PNG')
image_stream.seek(0)
paragraph = cell.paragraphs[0]
run = paragraph.add_run()
run.add_picture(image_stream, width=width, height=height)
def is_chinese(self, char): def is_chinese(self, char):
"""判断字符是否为中文""" """判断字符是否为中文"""
if '\u4e00' <= char <= '\u9fff': if '\u4e00' <= char <= '\u9fff':

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2025 Jeffrey Hsu - JITToolBox # Copyright (c) 2025-2026 Jeffrey Hsu - JITToolBox
# # # #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
import datetime import datetime
import traceback import traceback
import io
from typing import Optional, Callable from typing import Optional, Callable
import openpyxl import openpyxl
@@ -22,6 +23,7 @@ from openpyxl.utils import get_column_letter, column_index_from_string
from openpyxl.workbook.workbook import Workbook from openpyxl.workbook.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet from openpyxl.worksheet.worksheet import Worksheet
from packaging import version from packaging import version
from PIL import Image
from module import LOGLEVEL, COMPATIBLE_VERSION from module import LOGLEVEL, COMPATIBLE_VERSION
from module.schema import Performance from module.schema import Performance
@@ -49,6 +51,34 @@ class ExcelReader:
ignore_version_check: bool ignore_version_check: bool
pic_list: list pic_list: list
suggestion_template_list: list[Optional[str]] suggestion_template_list: list[Optional[str]]
major_director_signature_image: Optional[Image.Image]
course_leader_signature_image: Optional[Image.Image]
class _SheetImageLoader:
"""Lightweight image loader scoped for ExcelReader use."""
def __init__(self, sheet: Worksheet):
self._images: dict[str, Callable[[], bytes]] = {}
for image in getattr(sheet, "_images", []):
row = image.anchor._from.row + 1
col = get_column_letter(image.anchor._from.col + 1)
self._images[f"{col}{row}"] = image._data
def image_in(self, cell: str) -> bool:
return cell in self._images
def get(self, cell: str) -> Image.Image:
if cell not in self._images:
raise ValueError(f"Cell {cell} doesn't contain an image")
image = io.BytesIO(self._images[cell]())
return Image.open(image)
class ValidError(Exception):
def __init__(self, message):
self.message = message
def __str__(self):
return self.message
def __init__(self, file_path: str, version_check: bool = False, def __init__(self, file_path: str, version_check: bool = False,
signal: Callable[[str, str], None] = lambda x, y: print(x)): signal: Callable[[str, str], None] = lambda x, y: print(x)):
@@ -75,6 +105,8 @@ class ExcelReader:
self.pic_list = [] self.pic_list = []
self.signal = signal self.signal = signal
self.suggestion_template_list = [] self.suggestion_template_list = []
self.major_director_signature_image = None
self.course_leader_signature_image = None
def parse_excel(self): def parse_excel(self):
try: try:
@@ -104,6 +136,13 @@ class ExcelReader:
# 读取课程负责人 # 读取课程负责人
self.course_lead_teacher_name = sheet["D8"].value self.course_lead_teacher_name = sheet["D8"].value
need_signature_images = CUR_VERSION >= version.parse("9.4") and sheet["H10"].value == ""
if need_signature_images:
self._load_signature_images()
else:
self.major_director_signature_image = None
self.course_leader_signature_image = None
# 读取班级和人数 # 读取班级和人数
max_class_size = 4 max_class_size = 4
match CUR_VERSION: match CUR_VERSION:
@@ -239,9 +278,15 @@ class ExcelReader:
for i in range(len(self.suggestion_template_list), 5): for i in range(len(self.suggestion_template_list), 5):
self.suggestion_template_list.append(None) self.suggestion_template_list.append(None)
self.validate_data() if vd_lst := self.validate_data():
raise self.ValidError("\n\n".join(vd_lst))
self.gen_picture() self.gen_picture()
except self.ValidError as ve:
raise Exception(f"""
数据验证失败:\n\n{str(ve)}
""")
except Exception as e: except Exception as e:
error_message = traceback.format_exc() error_message = traceback.format_exc()
raise Exception(f""" raise Exception(f"""
@@ -252,13 +297,40 @@ class ExcelReader:
def set_file_path(self, file_path: str): def set_file_path(self, file_path: str):
self.file_path = file_path self.file_path = file_path
def validate_data(self): def validate_data(self) -> list[str]:
lst: list[str] = []
self.signal("正在验证数据", LOGLEVEL.INFO) self.signal("正在验证数据", LOGLEVEL.INFO)
return 0 if len(self.kpi_list) != self.kpi_number:
self.signal("\"课程目标\"\"目标支撑的毕业要求指标点\"数量与期望目标数量不符", LOGLEVEL.ERROR)
lst.append(
f"\"课程目标\"\"目标支撑的毕业要求指标点\"数量与期望目标数量不符请检查Excel表格中的\"课程目标\"\"目标支撑的毕业要求指标点\"列是否填写完整。"
f"期望得到 {self.kpi_number} 个,实际检测到 {len(self.kpi_list)} 个。"
f"如想暂时不填请在Excel表格对应的位置添加一个空格"
)
return lst
def run(self): def run(self):
self.parse_excel() self.parse_excel()
def _load_signature_images(self):
signature_cells = {
"major_director_signature_image": ("I34", "K34"),
"course_leader_signature_image": ("I35", "K35"),
}
wb_with_images: Workbook = openpyxl.load_workbook(self.file_path, data_only=True)
try:
sheet_with_images: Worksheet = wb_with_images["初始录入"]
loader = self._SheetImageLoader(sheet_with_images)
for attr, (check_cell, image_cell) in signature_cells.items():
if sheet_with_images[check_cell].value and loader.image_in(image_cell):
setattr(self, attr, loader.get(image_cell))
else:
setattr(self, attr, None)
finally:
wb_with_images.close()
def clear_all_data(self): def clear_all_data(self):
self.kpi_list = [] self.kpi_list = []
self.kpi_number = 0 self.kpi_number = 0
@@ -278,6 +350,8 @@ class ExcelReader:
self.hml_list = [] self.hml_list = []
self.question_data = {} self.question_data = {}
self.pic_list = [] self.pic_list = []
self.major_director_signature_image = None
self.course_leader_signature_image = None
def set_version_check(self, version_check: bool): def set_version_check(self, version_check: bool):
self.ignore_version_check = version_check self.ignore_version_check = version_check
@@ -486,7 +560,7 @@ class ExcelReader:
yield "改进措施" yield "改进措施"
yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n" yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n"
f"{self.suggestion_template_list[0] if self.suggestion_template_list[0] is not None else '\n\n\n在这填入您的改进措施\n\n\n'}") f"{self.suggestion_template_list[0] if self.suggestion_template_list[0] is not None else '\n\n\n在这填入您的改进措施\n\n\n'}")
for i in range(88888): while True:
yield "如果您看到了本段文字,请联系开发者" yield "如果您看到了本段文字,请联系开发者"
def get_word_template_part_2(self): def get_word_template_part_2(self):
@@ -581,7 +655,7 @@ class ExcelReader:
yield "改进措施" yield "改进措施"
yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n" yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n"
f"{self.suggestion_template_list[1] if self.suggestion_template_list[1] is not None else '\n\n\n在这填入您的改进措施\n\n\n'}") f"{self.suggestion_template_list[1] if self.suggestion_template_list[1] is not None else '\n\n\n在这填入您的改进措施\n\n\n'}")
for i in range(88888): while True:
yield "如果您看到了本段文字,请联系开发者" yield "如果您看到了本段文字,请联系开发者"
def get_word_template_part_3(self): def get_word_template_part_3(self):
@@ -603,14 +677,22 @@ class ExcelReader:
yield "专业负责人/系主任(签字)" yield "专业负责人/系主任(签字)"
yield ("整改意见:\n" yield ("整改意见:\n"
"\n\n\n" "\n\n\n"
f"{self.suggestion_template_list[3] if self.suggestion_template_list[3] is not None else '\n\n\n'}" f"{" " * 8}{self.suggestion_template_list[3] if self.suggestion_template_list[3] is not None else '\n\n\n'}\n\n\n")
f"\n\n\n\t\t\t\t\t\t\t\t\t签字:\t\t\t日期:{datetime.datetime.now().strftime("%Y-%m-%d")}\n") yield ""
yield "签字:"
yield ""
yield "日期:"
yield datetime.datetime.now().strftime("%Y-%m-%d")
yield "课程负责人(签字)" yield "课程负责人(签字)"
yield ("拟整改计划与措施:\n" yield ("拟整改计划与措施:\n"
"\n\n\n" "\n\n\n"
f"{self.suggestion_template_list[4] if self.suggestion_template_list[4] is not None else '\n\n\n'}" f"{" " * 8}{self.suggestion_template_list[4] if self.suggestion_template_list[4] is not None else '\n\n\n'}\n\n\n")
f"\n\n\n\t\t\t\t\t\t\t\t\t签字:\t\t\t日期:{datetime.datetime.now().strftime("%Y-%m-%d")}\n") yield ""
for i in range(88888): yield "签字:"
yield ""
yield "日期:"
yield datetime.datetime.now().strftime("%Y-%m-%d")
while True:
yield "如果您看到了本段文字,请联系开发者" yield "如果您看到了本段文字,请联系开发者"

View File

@@ -13,6 +13,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
from dataclasses import dataclass
from typing import Callable
from PySide6.QtGui import QIcon, QShowEvent from PySide6.QtGui import QIcon, QShowEvent
from qfluentwidgets import FluentIcon, MSFluentWindow, NavigationItemPosition, MessageBox, setThemeColor from qfluentwidgets import FluentIcon, MSFluentWindow, NavigationItemPosition, MessageBox, setThemeColor
@@ -20,40 +24,99 @@ from ui import MAIN_THEME_COLOR, BLUE_BACKGROUND_COLOR
from ui.pyui.about_ui import AboutWidget from ui.pyui.about_ui import AboutWidget
from ui.pyui.achievement_ui import AchievementWidget from ui.pyui.achievement_ui import AchievementWidget
from ui.pyui.defense_ui import DefenseWidget from ui.pyui.defense_ui import DefenseWidget
# from ui.pyui.picker_ui import PickerWidget from ui.pyui.picker_ui import PickerWidget
from ui.pyui.test_ui import TestWidget from ui.pyui.test_ui import TestWidget
from utils.function import RELEASE_ENV from utils.function import RELEASE_ENV
@dataclass(frozen=True)
class InterfaceSpec:
key: str
factory: Callable[[], object]
icon: FluentIcon
nav_text: str
position: NavigationItemPosition = NavigationItemPosition.TOP
enabled: bool = True
class MainWindow(MSFluentWindow): class MainWindow(MSFluentWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
setThemeColor(MAIN_THEME_COLOR) setThemeColor(MAIN_THEME_COLOR)
self.setCustomBackgroundColor(BLUE_BACKGROUND_COLOR, BLUE_BACKGROUND_COLOR) self.setCustomBackgroundColor(BLUE_BACKGROUND_COLOR, BLUE_BACKGROUND_COLOR)
self.interface_specs = self.build_interface_specs()
self.achievementInterface = AchievementWidget('Achievement Interface', self) self.interfaces = self.create_interfaces(self.interface_specs)
self.defenseInterface = DefenseWidget('Defense Interface', self) self.bind_error_handlers()
self.aboutInterface = AboutWidget('About Interface', self)
# self.pickerInterface = PickerWidget('Picker Interface', self)
if not RELEASE_ENV:
self.testInterface = TestWidget('Test Interface', self)
self.achievementInterface.error.connect(self.showError)
self.defenseInterface.errorSignal.connect(self.showError)
# self.pickerInterface.errorSignal.connect(self.showError)
self.initNavigation() self.initNavigation()
self.initWindow() self.initWindow()
def initNavigation(self): def build_interface_specs(self) -> list[InterfaceSpec]:
self.addSubInterface(self.achievementInterface, FluentIcon.SPEED_HIGH, '达成度') return [
self.addSubInterface(self.defenseInterface, FluentIcon.FEEDBACK, '答辩') InterfaceSpec(
# self.addSubInterface(self.pickerInterface, FluentIcon.PEOPLE, '提问') key="achievement",
if not RELEASE_ENV: factory=lambda: AchievementWidget('Achievement Interface', self),
self.addSubInterface(self.testInterface, FluentIcon.VIEW, '测试') icon=FluentIcon.SPEED_HIGH,
nav_text='达成度',
enabled=True,
),
InterfaceSpec(
key="defense",
factory=lambda: DefenseWidget('Defense Interface', self),
icon=FluentIcon.FEEDBACK,
nav_text='答辩',
enabled=True,
),
InterfaceSpec(
key="picker",
factory=lambda: PickerWidget('Picker Interface', self),
icon=FluentIcon.PEOPLE,
nav_text='提问',
enabled=not RELEASE_ENV,
),
InterfaceSpec(
key="test",
factory=lambda: TestWidget('Test Interface', self),
icon=FluentIcon.VIEW,
nav_text='测试',
enabled=not RELEASE_ENV,
),
InterfaceSpec(
key="about",
factory=lambda: AboutWidget('About Interface', self),
icon=FluentIcon.INFO,
nav_text='关于',
position=NavigationItemPosition.BOTTOM,
enabled=True
),
]
self.addSubInterface(self.aboutInterface, FluentIcon.INFO, '关于', position=NavigationItemPosition.BOTTOM) def create_interfaces(self, specs: list[InterfaceSpec]) -> dict[str, object]:
interfaces: dict[str, object] = {}
for spec in specs:
if not spec.enabled:
continue
widget = spec.factory()
interfaces[spec.key] = widget
setattr(self, f"{spec.key}Interface", widget)
return interfaces
def bind_error_handlers(self):
achievement = self.interfaces.get("achievement")
defense = self.interfaces.get("defense")
if achievement and hasattr(achievement, "error"):
achievement.error.connect(self.showError)
if defense and hasattr(defense, "errorSignal"):
defense.errorSignal.connect(self.showError)
def initNavigation(self):
for spec in self.interface_specs:
widget = self.interfaces.get(spec.key)
if not widget:
continue
self.addSubInterface(widget, spec.icon, spec.nav_text, position=spec.position)
def initWindow(self): def initWindow(self):
self.resize(900, 700) self.resize(900, 700)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2025 Jeffrey Hsu - JITToolBox # Copyright (c) 2025-2026 Jeffrey Hsu - JITToolBox
# # # #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@ from typing import Callable, Literal
from PySide6.QtCore import Qt, Signal, QThread from PySide6.QtCore import Qt, Signal, QThread
from PySide6.QtWidgets import QVBoxLayout, QFileDialog, QHBoxLayout from PySide6.QtWidgets import QVBoxLayout, QFileDialog, QHBoxLayout
from qfluentwidgets import GroupHeaderCardWidget, FluentIcon, PushButton, LineEdit, IconWidget, BodyLabel, \ from qfluentwidgets import GroupHeaderCardWidget, FluentIcon, PushButton, LineEdit, IconWidget, BodyLabel, \
PrimaryPushButton, SwitchButton, HyperlinkButton PrimaryPushButton, SwitchButton, HyperlinkButton, InfoBar, InfoBarPosition
from module import LOGLEVEL from module import LOGLEVEL
from module.worker import ARGWorker from module.worker import ARGWorker
@@ -259,3 +259,23 @@ class AchievementWidget(Widget):
def show_info(self, content: str, level: str): def show_info(self, content: str, level: str):
if level == LOGLEVEL.INFO: if level == LOGLEVEL.INFO:
self.pib.set_title(content) 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
)

View File

@@ -18,11 +18,23 @@ from datetime import datetime
def gen_build_info(): def gen_build_info():
hash_str = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('utf-8').strip() try:
hash_str = subprocess.check_output(
['git', 'rev-parse', '--short', 'HEAD'],
stderr=subprocess.DEVNULL
).decode('utf-8').strip()
except FileNotFoundError:
# git 未安装
hash_str = 'unknown'
except subprocess.CalledProcessError:
# 不是 git 仓库(如从压缩包下载)
hash_str = 'unknown'
with open('build_info.py', 'w', encoding='utf-8') as f: with open('build_info.py', 'w', encoding='utf-8') as f:
f.write(f"# Auto-generated build info\n") f.write(f"# Auto-generated build info\n")
f.write(f"BUILD_TIME = '{datetime.now().isoformat(sep=' ', timespec='seconds')}'\n") f.write(f"BUILD_TIME = '{datetime.now().isoformat(sep=' ', timespec='seconds')}'\n")
f.write(f"GIT_HASH = '{hash_str}'\n") f.write(f"GIT_HASH = '{hash_str}'\n")
if __name__ == '__main__':
gen_build_info() gen_build_info()