Compare commits
5 Commits
1a00811cfc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 96350bb8e2 | |||
| 28e35ea429 | |||
| a708bbfa72 | |||
| db53baba23 | |||
| d95bdef3f5 |
19
build.bat
19
build.bat
@@ -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 -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple -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 ===
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -73,6 +73,13 @@ class ExcelReader:
|
|||||||
image = io.BytesIO(self._images[cell]())
|
image = io.BytesIO(self._images[cell]())
|
||||||
return Image.open(image)
|
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)):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -271,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"""
|
||||||
@@ -284,9 +297,18 @@ 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()
|
||||||
@@ -655,7 +677,7 @@ class ExcelReader:
|
|||||||
yield "专业负责人/系主任(签字)"
|
yield "专业负责人/系主任(签字)"
|
||||||
yield ("整改意见:\n"
|
yield ("整改意见:\n"
|
||||||
"\n\n\n"
|
"\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"{" " * 8}{self.suggestion_template_list[3] if self.suggestion_template_list[3] is not None else '\n\n\n'}\n\n\n")
|
||||||
yield ""
|
yield ""
|
||||||
yield "签字:"
|
yield "签字:"
|
||||||
yield ""
|
yield ""
|
||||||
@@ -664,7 +686,7 @@ class ExcelReader:
|
|||||||
yield "课程负责人(签字)"
|
yield "课程负责人(签字)"
|
||||||
yield ("拟整改计划与措施:\n"
|
yield ("拟整改计划与措施:\n"
|
||||||
"\n\n\n"
|
"\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"{" " * 8}{self.suggestion_template_list[4] if self.suggestion_template_list[4] is not None else '\n\n\n'}\n\n\n")
|
||||||
yield ""
|
yield ""
|
||||||
yield "签字:"
|
yield "签字:"
|
||||||
yield ""
|
yield ""
|
||||||
|
|||||||
101
ui/main.py
101
ui/main.py
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|
||||||
gen_build_info()
|
if __name__ == '__main__':
|
||||||
|
gen_build_info()
|
||||||
|
|||||||
Reference in New Issue
Block a user