fix:优化配置存储

This commit is contained in:
2026-01-30 00:19:09 +08:00
parent 6a0c826a06
commit 02cbd256d8
18 changed files with 228 additions and 420 deletions

33
.idea/workspace.xml generated
View File

@@ -4,11 +4,23 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="41690157-d51b-4dae-98de-6b96990d681a" name="更改" comment="fix优化一些命名规范">
<change afterPath="$PROJECT_DIR$/ui/views/convert_pdf_page.py" afterDir="false" />
<list default="true" id="41690157-d51b-4dae-98de-6b96990d681a" name="更改" comment="fix优化PDF转换逻辑">
<change afterPath="$PROJECT_DIR$/ui/views/templates/back_home.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config.toml" beforeDir="false" afterPath="$PROJECT_DIR$/config.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/config.py" beforeDir="false" afterPath="$PROJECT_DIR$/config/config.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/data/names.xlsx" beforeDir="false" afterPath="$PROJECT_DIR$/data/names.xlsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/views/config_page.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/views/config_page.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/views/convert_pdf_page.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/views/convert_pdf_page.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/views/data_page.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/views/data_page.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/views/home_page.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/views/home_page.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/views/signature_page.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/views/signature_page.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/utils/font_utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/utils/font_utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/utils/generate_utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/utils/generate_utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/utils/growt_utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/utils/growt_utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -137,7 +149,7 @@
<workItem from="1768312972093" duration="486000" />
<workItem from="1768314152581" duration="7000" />
<workItem from="1769696481591" duration="57000" />
<workItem from="1769696548056" duration="2806000" />
<workItem from="1769696548056" duration="6079000" />
</task>
<task id="LOCAL-00001" summary="fix修复一些BUG">
<option name="closed" value="true" />
@@ -171,7 +183,15 @@
<option name="project" value="LOCAL" />
<updated>1766330259490</updated>
</task>
<option name="localTasksCounter" value="5" />
<task id="LOCAL-00005" summary="fix优化PDF转换逻辑">
<option name="closed" value="true" />
<created>1769699406868</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1769699406868</updated>
</task>
<option name="localTasksCounter" value="6" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -191,13 +211,14 @@
<component name="VcsManagerConfiguration">
<MESSAGE value="fix修复一些BUG" />
<MESSAGE value="fix优化一些命名规范" />
<option name="LAST_COMMIT_MESSAGE" value="fix优化一些命名规范" />
<MESSAGE value="fix优化PDF转换逻辑" />
<option name="LAST_COMMIT_MESSAGE" value="fix优化PDF转换逻辑" />
<option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="true" />
<option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="true" />
<option name="REARRANGE_BEFORE_PROJECT_COMMIT" value="true" />
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/growth_report$main.coverage" NAME="main 覆盖结果" MODIFIED="1769699312262" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/growth_report$main.coverage" NAME="main 覆盖结果" MODIFIED="1769703398756" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/growth_report$main_nicegui.coverage" NAME="main_nicegui 覆盖结果" MODIFIED="1766329725535" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
</component>
</project>

View File

@@ -0,0 +1,25 @@
{
"data_folder": "D:\\working\\tools\\growth_report\\data",
"templates_folder": "D:\\working\\tools\\growth_report\\templates",
"source_file": "大四班 幼儿学期发展报告.pptx",
"output_folder": "output",
"excel_file": "names.xlsx",
"image_folder": "images",
"fonts_dir": "fonts",
"signature_image": "signature.png",
"class_name": "K4D",
"teachers": [
"康璐璐",
"冯宇阳",
"孙继艳"
],
"class_type": 2,
"default_comment": "暂无评语",
"age_group": "大班上学期",
"ai": {
"api_key": "sk-ccb6a1445126f2e56ba50d7622ff350f",
"api_url": "https://apis.iflow.cn/v1/chat/completions",
"model": "deepseek-v3.2",
"prompt": "# Role \n你是一位拥有20年经验的资深幼儿园主班老师。你的文笔温暖、细腻、充满爱意擅长发现每个孩子身上独特的闪光点。你的评语风格是“治愈系”的能让家长读完后感到欣慰并对未来充满希望。\n\n# Goal\n请根据用户提供的【幼儿姓名】、【年龄段/班级】以及【日常表现关键词/评分数据】,撰写一份高质量的学期末成长评语。\n# Constraints & Rules\n1. **严格的格式排版 (Strict Formatting)**:\n- **换行**:正文中间不要随意换行,保持为一段完整的段落。\n2. **称呼处理**:\n- 自动识别用户输入的姓名,去掉姓氏。\n- 例如:“王小明” -> 第一行输出“小明宝贝:”。\n3. **分龄侧重 (根据 Age_Group 调整侧重点)**:\n- **小班 (3-4岁)**:侧重于适应集体生活、情绪稳定性、基本生活自理能力、愿意与老师互动。\n- **中班 (4-5岁)**:侧重于社交互动、分享与合作、动手能力、好奇心、规则意识。\n- **大班 (5-6岁)**:侧重于学习习惯、逻辑思维、领导力、任务意识、幼小衔接准备。\n4. **写作结构 (固定内容)**:\n- **开头**:固定文本必须包含:“{class_type}”\n- **正文**:结合【表现关键词】和【性别】,具体描述进步和优点。\n- **结尾**:委婉地提出期望(“如果你能...老师会更为你骄傲”),并送上祝福。\n5. **语气风格**:\n- 积极正面,多用肯定句。\n- 字数控制在 150-250 字之间。\n# Input Format\n- Name {{name}}\n- Age_Group {{class_name}}\n- Traits {{traits}}\n- Sex {{sex}}\n# Output Example\n(假设输入:Name=张图图, Age_Group=小班, Traits=适应能力强, 爱笑, 挑食,Sex=女)\n亲爱的图图宝贝{class_type}\n你是一个爱笑的小天使每天早上都能看到你甜甜的笑脸。从一开始的哭鼻子到现在能开心地参与游戏你的适应能力让老师感到惊喜。不过老师发现你在吃饭时偶尔会把不喜欢的青菜挑出来哦。如果你能和青菜宝宝做好朋友把身体练得棒棒的那就更完美啦祝可爱的图图宝贝新年快乐健康成长\n"
}
}

View File

@@ -1,98 +0,0 @@
[paths]
# TODO PPT模版路径
source_file = "大班幼儿学期发展报告.pptx"
# 输出文件夹
output_folder = "output"
# Excel数据文件路径
excel_file = "names.xlsx"
# 图片资源文件夹
image_folder = "images"
# 字体文件夹
fonts_dir = "fonts"
[class_info]
# TODO 班级名称
class_name = "K4D"
# TODO 老师名单 (数组格式)
teachers = [""]
[defaults]
# 当Excel中没有评语时的默认内容
default_comment = "暂无评语"
age_group = "大班上学期"
# ========================
# Excel 配置 (Data)
# ========================
[excel]
sheet_name = "Sheet1"
# 对应Excel表头名称顺序必须与代码中的解包顺序一致
# 顺序:姓名, 英文名, 性别, 生日, 属相, 好朋友, 爱好, 游戏, 食物, 评语
columns = [
"姓名",
"英文名",
"性别",
"生日",
"属相",
"我的好朋友",
"我的爱好",
"喜欢的游戏",
"喜欢吃的食物",
"表现特征",
"评价",
]
# TODO API 配置
[ai]
api_key = ""
api_url = ""
model = ""
prompt = """
# Role
你是一位拥有20年经验的资深幼儿园主班老师。你的文笔温暖、细腻、充满爱意擅长发现每个孩子身上独特的闪光点。你的评语风格是“治愈系”的能让家长读完后感到欣慰并对未来充满希望。
# Goal
请根据用户提供的【幼儿姓名】、【年龄段/班级】以及【日常表现关键词/评分数据】,撰写一份高质量的学期末成长评语。
# Constraints & Rules
1. **称呼处理**
- 自动识别用户输入的姓名。
- **必须去掉姓氏**,只使用名。
- 统一格式为:“[名]宝贝,你好!”或“[名]宝贝:”。
- 例如:“王小明” -> “小明宝贝”;“李在这个” -> “在这个宝贝”。
2. **分龄侧重 (根据 Age_Group 调整侧重点)**
- **小班 (3-4岁)**:侧重于适应集体生活、情绪稳定性、基本生活自理能力(吃饭、午睡、如厕)、愿意与老师互动。
- **中班 (4-5岁)**:侧重于社交互动、分享与合作、动手能力、好奇心、规则意识的建立、自信心的增强。
- **大班 (5-6岁)**:侧重于学习习惯、逻辑思维、领导力/榜样作用、任务意识、为幼小衔接做的准备、抗挫折能力。
3. **写作结构 (三段式)**
- **开头**:亲切的问候 + 总体印象(用美好的形容词,如文静、活泼、机灵等)。
- **正文**:结合提供的【表现关键词】,具体描述孩子的进步和优点(必须具体,拒绝空洞)。
- **结尾**:委婉地提出一点小小的期望(用“如果你能...老师会更为你骄傲”的句式),并送上新学期的祝福。
4. **语气风格**
- 积极正面,多用肯定句。
- 避免生硬的批评,将缺点转化为“待提升的潜力”或“期望”。
- 字数控制在 150-250 字之间适合PPT展示
# Workflow
1. 分析用户输入的年龄段,确定评价基调。
2. 处理姓名,提取昵称。
3. 将输入的关键词串联成通顺、优美的句子。
4. 按照三段式结构输出最终评语。
# Input Format
用户将提供 JSON 格式或特定格式的数据,包含:
- Name {{name}}
- Age_Group {{class_name}}
- Traits (表现关键词/特征,如:吃饭香、爱画画、有些胆小、数学好)
# Output Example
(假设输入Name=张图图, Age_Group=小班, Traits=适应能力强, 爱笑, 挑食)
图图宝贝,你好!
你是一个爱笑的小天使,每天早上都能看到你甜甜的笑脸,老师的心都要被你融化了。这个学期你进步真大呀,从一开始的哭鼻子到现在能开心地参与游戏,你的适应能力让老师感到惊喜。在集体活动中,你总是那么投入。
不过,老师发现你在吃饭时偶尔会把不喜欢的青菜挑出来哦。如果你能和青菜宝宝做好朋友,把身体练得棒棒的,那就更完美啦!
祝可爱的图图宝贝新年快乐,健康成长!
"""

View File

@@ -1,26 +0,0 @@
[paths]
source_file = "大四班 幼儿学期发展报告.pptx"
output_folder = "output"
excel_file = "names.xlsx"
image_folder = "images"
fonts_dir = "fonts"
signature_image = "d:\\working\\tools\\growth_report\\data\\signature.png"
[class_info]
class_name = "K4D"
teachers = [
"康璐璐",
"冯宇阳",
"孙继艳",
]
class_type = 2
[defaults]
default_comment = ""
age_group = "大班上学期"
[ai]
api_key = "sk-ccb6a1445126f2e56ba50d7622ff350f"
api_url = "https://apis.iflow.cn/v1/chat/completions"
model = "deepseek-v3.2"
prompt = "# Role \n你是一位拥有20年经验的资深幼儿园主班老师。你的文笔温暖、细腻、充满爱意擅长发现每个孩子身上独特的闪光点。你的评语风格是“治愈系”的能让家长读完后感到欣慰并对未来充满希望。\n\n# Goal\n请根据用户提供的【幼儿姓名】、【年龄段/班级】以及【日常表现关键词/评分数据】,撰写一份高质量的学期末成长评语。\n# Constraints & Rules\n1. **严格的格式排版 (Strict Formatting)**:\n- **换行**:正文中间不要随意换行,保持为一段完整的段落。\n2. **称呼处理**:\n- 自动识别用户输入的姓名,去掉姓氏。\n- 例如:“王小明” -> 第一行输出“小明宝贝:”。\n3. **分龄侧重 (根据 Age_Group 调整侧重点)**:\n- **小班 (3-4岁)**:侧重于适应集体生活、情绪稳定性、基本生活自理能力、愿意与老师互动。\n- **中班 (4-5岁)**:侧重于社交互动、分享与合作、动手能力、好奇心、规则意识。\n- **大班 (5-6岁)**:侧重于学习习惯、逻辑思维、领导力、任务意识、幼小衔接准备。\n4. **写作结构 (固定内容)**:\n- **开头**:固定文本必须包含:“{class_type}”\n- **正文**:结合【表现关键词】和【性别】,具体描述进步和优点。\n- **结尾**:委婉地提出期望(“如果你能...老师会更为你骄傲”),并送上祝福。\n5. **语气风格**:\n- 积极正面,多用肯定句。\n- 字数控制在 150-250 字之间。\n# Input Format\n- Name {{name}}\n- Age_Group {{class_name}}\n- Traits {{traits}}\n- Sex {{sex}}\n# Output Example\n(假设输入:Name=张图图, Age_Group=小班, Traits=适应能力强, 爱笑, 挑食,Sex=女)\n亲爱的图图宝贝{class_type}\n你是一个爱笑的小天使每天早上都能看到你甜甜的笑脸。从一开始的哭鼻子到现在能开心地参与游戏你的适应能力让老师感到惊喜。不过老师发现你在吃饭时偶尔会把不喜欢的青菜挑出来哦。如果你能和青菜宝宝做好朋友把身体练得棒棒的那就更完美啦祝可爱的图图宝贝新年快乐健康成长\n"

View File

@@ -1,6 +1,9 @@
import os
import sys
from loguru import logger
from nicegui import app
# 1. 处理读取库
try:
import tomllib as toml_read # Python 3.11+
@@ -39,53 +42,69 @@ def get_resource_path(relative_path):
return external_path
def init_storage():
"""如果 storage 是空的,则注入默认配置"""
cfg = app.storage.general
if "class_name" not in cfg:
logger.info("初始化默认配置...")
default_config = {
"data_folder": os.path.join(get_base_dir(), "data"),
"templates_folder": os.path.join(get_base_dir(), "templates"),
"source_file": "大四班 幼儿学期发展报告.pptx",
"output_folder": "output",
"excel_file": "names.xlsx",
"image_folder": "images",
"fonts_dir": "fonts",
"signature_image": "signature.png",
"class_name": "K4D",
"teachers": ["康璐璐", "冯宇阳", "孙继艳"],
"class_type": 2,
"default_comment": "暂无评语",
"age_group": "大班上学期",
"ai": {
"api_key": "sk-ccb6a1445126f2e56ba50d7622ff350f",
"api_url": "https://apis.iflow.cn/v1/chat/completions",
"model": "deepseek-v3.2",
"prompt": "# Role \n你是一位拥有20年经验的资深幼儿园主班老师。你的文笔温暖、细腻、充满爱意擅长发现每个孩子身上独特的闪光点。你的评语风格是“治愈系”的能让家长读完后感到欣慰并对未来充满希望。\n\n# Goal\n请根据用户提供的【幼儿姓名】、【年龄段/班级】以及【日常表现关键词/评分数据】,撰写一份高质量的学期末成长评语。\n# Constraints & Rules\n1. **严格的格式排版 (Strict Formatting)**:\n- **换行**:正文中间不要随意换行,保持为一段完整的段落。\n2. **称呼处理**:\n- 自动识别用户输入的姓名,去掉姓氏。\n- 例如:“王小明” -> 第一行输出“小明宝贝:”。\n3. **分龄侧重 (根据 Age_Group 调整侧重点)**:\n- **小班 (3-4岁)**:侧重于适应集体生活、情绪稳定性、基本生活自理能力、愿意与老师互动。\n- **中班 (4-5岁)**:侧重于社交互动、分享与合作、动手能力、好奇心、规则意识。\n- **大班 (5-6岁)**:侧重于学习习惯、逻辑思维、领导力、任务意识、幼小衔接准备。\n4. **写作结构 (固定内容)**:\n- **开头**:固定文本必须包含:“{class_type}\n- **正文**:结合【表现关键词】和【性别】,具体描述进步和优点。\n- **结尾**:委婉地提出期望(“如果你能...老师会更为你骄傲”),并送上祝福。\n5. **语气风格**:\n- 积极正面,多用肯定句。\n- 字数控制在 150-250 字之间。\n# Input Format\n- Name {{name}}\n- Age_Group {{class_name}}\n- Traits {{traits}}\n- Sex {{sex}}\n# Output Example\n(假设输入:Name=张图图, Age_Group=小班, Traits=适应能力强, 爱笑, 挑食,Sex=女)\n亲爱的图图宝贝:{class_type}\n你是一个爱笑的小天使,每天早上都能看到你甜甜的笑脸。从一开始的哭鼻子到现在能开心地参与游戏,你的适应能力让老师感到惊喜。不过,老师发现你在吃饭时偶尔会把不喜欢的青菜挑出来哦。如果你能和青菜宝宝做好朋友,把身体练得棒棒的,那就更完美啦!祝可爱的图图宝贝新年快乐,健康成长!\n",
},
}
cfg.update(default_config)
# ==========================================
# 1. 配置加载 (Config Loader)
# ==========================================
def load_config(config_filename="config.toml"):
config_path = get_resource_path(config_filename)
if not os.path.exists(config_path):
# 如果彻底找不到,返回一个最小化的默认值,防止程序奔溃
return {"source_file": "", "ai": {"api_key": ""}, "teachers": []}
def load_config():
try:
with open(config_path, "rb") as f:
data = toml_read.load(f)
config_data = app.storage.general
base_dir = get_base_dir()
# 使用 .get() 安全获取,防止 KeyError: 'paths'
paths = data.get("paths", {})
class_info = data.get("class_info", {})
defaults = data.get("defaults", {})
config = {
"root_path": base_dir,
"data_folder": os.path.join(os.path.join("data")),
# 扁平化映射
"source_file": get_resource_path(
os.path.join("templates", paths.get("source_file", ""))
os.path.join("templates", config_data.get("source_file", ""))
),
"excel_file": get_resource_path(
os.path.join("data", paths.get("excel_file", ""))
os.path.join("data", config_data.get("excel_file", "names"))
),
"image_folder": get_resource_path(
os.path.join("data", paths.get("image_folder", ""))
os.path.join("data", config_data.get("image_folder", ""))
),
"fonts_dir": get_resource_path(paths.get("fonts_dir", "fonts")),
"fonts_dir": get_resource_path(config_data.get("fonts_dir", "fonts")),
"output_folder": os.path.join(
base_dir, paths.get("output_folder", "output")
base_dir, config_data.get("output_folder", "output")
),
"signature_image": get_resource_path(
os.path.join("data", paths.get("signature_image", "signature.png"))
os.path.join("data", config_data.get("signature_image", "signature.png"))
),
"class_name": class_info.get("class_name", "未命名班级"),
"teachers": class_info.get("teachers", []),
"class_type": class_info.get("class_type", 0),
"default_comment": defaults.get("default_comment", "暂无评语"),
"age_group": defaults.get("age_group", "大班上学期"),
"ai": data.get(
"class_name": config_data.get("class_name", "未命名班级"),
"teachers": config_data.get("teachers", []),
"class_type": config_data.get("class_type", 0),
"default_comment": config_data.get("default_comment", "暂无评语"),
"age_group": config_data.get("age_group", "大班上学期"),
"ai": config_data.get(
"ai", {"api_key": "", "api_url": "", "model": "", "prompt": ""}
),
}
@@ -94,52 +113,3 @@ def load_config(config_filename="config.toml"):
except Exception as e:
print(f"解析配置文件失败: {e}")
return {}
# ==========================================
# 2. 配置保存 (Config Saver)
# ==========================================
def save_config(config_data, config_filename="config.toml"):
if not toml_write:
return False, "未安装 tomli-w 库,无法保存。请运行 pip install tomli-w"
base_path = get_base_dir()
save_path = os.path.join(base_path, config_filename)
try:
# 将扁平化的数据重新打包成嵌套结构,以适配 load_config 的读取逻辑
new_data = {
"paths": {
"source_file": os.path.basename(config_data.get("source_file", "")),
"output_folder": os.path.basename(
config_data.get("output_folder", "output")
),
"excel_file": os.path.basename(config_data.get("excel_file", "")),
"image_folder": os.path.basename(config_data.get("image_folder", "")),
"fonts_dir": os.path.basename(config_data.get("fonts_dir", "fonts")),
"signature_image": get_resource_path(
os.path.join(
"data", config_data.get("signature_image", "signature.png")
)
),
},
"class_info": {
"class_name": config_data.get("class_name", ""),
"teachers": config_data.get("teachers", []),
"class_type": config_data.get("class_type", 0),
},
"defaults": {
"default_comment": config_data.get("default_comment", ""),
"age_group": config_data.get("age_group", ""),
},
"ai": config_data.get("ai", {}),
}
# 写入文件
with open(save_path, "wb") as f:
f.write(toml_write.dumps(new_data).encode("utf-8"))
return True, f"成功保存到: {save_path}"
except Exception as e:
return False, f"写入失败: {str(e)}"

Binary file not shown.

14
main.py
View File

@@ -6,7 +6,7 @@ from loguru import logger
from nicegui import ui, app, run, native
from screeninfo import get_monitors
from config.config import load_config
from config.config import init_storage
# 导入我们的模块
from ui.core.logger import setup_logger
from ui.views.config_page import create_config_page
@@ -18,9 +18,6 @@ from utils.font_utils import install_fonts_from_directory
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
# 1. 初始化配置
config = load_config("config.toml")
setup_logger()
@@ -105,12 +102,17 @@ def signature_page(folder: str = ""):
def convert_pdf_page(folder: str = ""):
create_convert_pdf_page(folder)
# 4. 启动时钩子
async def startup_check():
try:
logger.info("系统启动: 初始化资源...")
await run.io_bound(install_fonts_from_directory, config["fonts_dir"])
os.makedirs(config["output_folder"], exist_ok=True)
# 初始化存储
init_storage()
cfg = app.storage.general
await run.io_bound(install_fonts_from_directory, cfg["fonts_dir"])
os.makedirs(cfg["output_folder"], exist_ok=True)
logger.success("资源初始化完成")
except Exception as e:
logger.error(f"初始化失败: {e}")

View File

@@ -8,6 +8,7 @@ dependencies = [
"comtypes>=1.4.13",
"langchain>=1.1.3",
"langchain-openai>=1.1.1",
"logger",
"loguru>=0.7.3",
"nicegui>=3.4.0",
"openpyxl>=3.1.5",

View File

@@ -1,189 +1,98 @@
from nicegui import ui
import os
from utils.template_utils import get_template_files
# 修改点 1统一导入避免与变量名 config 冲突
from config.config import load_config, save_config
from nicegui import ui, app
from ui.views.templates.back_home import backHome
from utils.template_utils import get_template_files
def create_config_page():
# 修改点 2将加载逻辑放入页面生成函数内确保每次刷新页面获取最新值
conf_data = load_config("config.toml")
template_options = get_template_files()
current_filename = os.path.basename(conf_data.get("source_file", ""))
# 获取当前持久化存储中的数据
cfg = app.storage.general
template_options = get_template_files()
current_filename = os.path.basename(cfg.get("source_file", ""))
if current_filename and current_filename not in template_options:
template_options.append(current_filename)
ui.add_head_html('<link href="/assets/style.css" rel="stylesheet" />')
# 样式修正:添加全屏且不滚动条的 CSS
ui.add_head_html(
"""
ui.add_head_html("""
<style>
body { overflow: hidden; }
.main-card { height: calc(100vh - 100px); display: flex; flex-direction: column; }
.q-tab-panels { flex-grow: 1; overflow-y: auto !important; }
</style>
"""
)
""")
with ui.header().classes("app-header items-center justify-between shadow-md"):
# 左侧:图标和标题
with ui.row().classes("items-center gap-2"):
ui.image("/assets/icon.ico").classes("w-8 h-8").props("fit=contain")
ui.label("尚城幼儿园成长报告助手").classes("text-xl font-bold")
# 右侧:署名 + 配置按钮
with ui.row().classes("items-center gap-4"):
ui.label("By 寒寒 | 这里的每一份评语都充满爱意").classes(
"text-xs opacity-90"
)
ui.button(icon="home", on_click=lambda: ui.navigate.to("/")).props(
"flat round color=white"
)
backHome()
# 修改点 3使用 flex 布局撑满
with ui.card().classes("w-full max-w-5xl mx-auto shadow-lg main-card p-0"):
with ui.tabs().classes("w-full") as tabs:
tab_path = ui.tab("路径设置", icon="folder")
tab_class = ui.tab("班级与教师", icon="school")
tab_ai = ui.tab("AI 接口配置", icon="psychology")
with ui.tab_panels(tabs, value=tab_path).classes(
"w-full flex-grow bg-transparent"
):
# --- 路径设置 ---
with ui.tab_panel(tab_path).classes("w-full p-0"):
with ui.column().classes("w-full p-4 gap-4"):
source_file = (
ui.select(
options=template_options,
label="PPT 模板",
value=current_filename,
)
.props("outlined fill-input")
.classes("w-full")
)
excel_file = (
ui.input(
"Excel 文件",
value=os.path.basename(conf_data.get("excel_file", "")),
)
.props("outlined")
.classes("w-full")
)
image_folder = (
ui.input(
"图片目录",
value=os.path.basename(conf_data.get("image_folder", "")),
)
.props("outlined")
.classes("w-full")
)
output_folder = (
ui.input(
"输出目录",
value=os.path.basename(
conf_data.get("output_folder", "output")
),
)
.props("outlined")
.classes("w-full")
)
with ui.tab_panels(tabs, value=tab_path).classes("w-full flex-grow bg-transparent"):
# --- 1. 路径设置 ---
with ui.tab_panel(tab_path).classes("w-full p-4 gap-4"):
# 注意:这里改用普通的 value= 参数,不使用 bind_value
source_file = ui.select(options=template_options, label="PPT 模板", value=cfg.get('source_file')).props(
"outlined").classes("w-full")
excel_file = ui.input("Excel 文件名", value=cfg.get('excel_file')).props("outlined").classes("w-full")
image_folder = ui.input("图片目录名", value=cfg.get('image_folder')).props("outlined").classes("w-full")
output_folder = ui.input("输出目录名", value=cfg.get('output_folder', 'output')).props(
"outlined").classes("w-full")
# --- 班级信息 ---
with ui.tab_panel(tab_class).classes("w-full p-0"):
with ui.column().classes("w-full p-4 gap-4"):
class_name = (
ui.input("班级名称", value=conf_data.get("class_name", ""))
.props("outlined")
.classes("w-full")
)
age_group = (
ui.select(
options=[
"小班上学期",
"小班下学期",
"中班上学期",
"中班下学期",
"大班上学期",
"大班下学期",
],
# --- 2. 班级信息 ---
with ui.tab_panel(tab_class).classes("w-full p-4 gap-4"):
class_name = ui.input("班级名称", value=cfg.get('class_name')).props("outlined").classes("w-full")
age_group = ui.select(
options=["小班上学期", "小班下学期", "中班上学期", "中班下学期", "大班上学期", "大班下学期"],
label="年龄段",
value=conf_data.get("age_group", "中班上学期"),
)
.props("outlined")
.classes("w-full")
)
teachers_text = (
ui.textarea(
"教师名单", value="\n".join(conf_data.get("teachers", []))
)
.props("outlined")
.classes("w-full h-40")
)
class_type = (
ui.select(
value=cfg.get('age_group')
).props("outlined").classes("w-full")
teachers_text = ui.textarea("教师名单(每行一个)", value="\n".join(cfg.get("teachers", []))).props(
"outlined").classes("w-full h-40")
class_type = ui.select(
options={0: "便宜班", 1: "昂贵班", 2: "昂贵的双木桥班"},
label="班级类型",
value=conf_data.get("class_type", 0),
)
.props("outlined")
.classes("w-full")
)
value=cfg.get('class_type', 0)
).props("outlined").classes("w-full")
# --- AI 配置 ---
with ui.tab_panel(tab_ai).classes("w-full p-0"):
with ui.column().classes("w-full p-4 gap-4"):
ai_key = (
ui.input("API Key", value=conf_data["ai"].get("api_key", ""))
.props("outlined password")
.classes("w-full")
)
ai_url = (
ui.input("API URL", value=conf_data["ai"].get("api_url", ""))
.props("outlined")
.classes("w-full")
)
ai_model = (
ui.input("Model Name", value=conf_data["ai"].get("model", ""))
.props("outlined")
.classes("w-full")
)
ai_prompt = (
ui.textarea(
"System Prompt", value=conf_data["ai"].get("prompt", "")
)
.props("outlined")
.classes("w-full h-full")
)
# 底部固定按钮
# --- 3. AI 配置 ---
with ui.tab_panel(tab_ai).classes("w-full p-4 gap-4"):
ai_data = cfg.get('ai', {}) # 获取子字典
ai_key = ui.input("API Key", value=ai_data.get("api_key")).props("outlined password").classes("w-full")
ai_url = ui.input("API URL", value=ai_data.get("api_url")).props("outlined").classes("w-full")
ai_model = ui.input("Model Name", value=ai_data.get("model")).props("outlined").classes("w-full")
ai_prompt = ui.textarea("System Prompt", value=ai_data.get("prompt")).props("outlined").classes(
"w-full h-64")
# --- 底部按钮:点击后统一更新到 Storage ---
with ui.row().classes("w-full p-4"):
async def handle_save():
new_data = {
def handle_manual_save():
# 统一更新到 app.storage.general
# NiceGUI 会在此时感知到字典变化并触发自动保存到 JSON 文件
cfg.update({
"source_file": source_file.value,
"excel_file": excel_file.value,
"image_folder": image_folder.value,
"output_folder": output_folder.value,
"class_name": class_name.value,
"age_group": age_group.value,
"signature_image": conf_data.get("signature_image", ""),
"teachers": [
t.strip() for t in teachers_text.value.split("\n") if t.strip()
],
"teachers": [t.strip() for t in teachers_text.value.split('\n') if t.strip()],
"class_type": class_type.value,
"ai": {
"api_key": ai_key.value,
"api_url": ai_url.value,
"model": ai_model.value,
"prompt": ai_prompt.value,
},
}
# 修改点 4直接调用导入的 save_config 函数名
success, message = save_config(new_data)
ui.notify("配置已保存重启生效", type="positive")
})
ui.notify("配置已成功更新至系统存储", type="positive", icon="save")
ui.button("保存配置", on_click=handle_save).classes("w-full py-4").props(
"outline color=primary"
)
ui.button("保存配置", icon="save", on_click=handle_manual_save).classes("w-full py-4 shadow-md").props(
"color=primary")

View File

@@ -5,6 +5,7 @@ import pythoncom
from loguru import logger
from nicegui import ui
from ui.views.templates.back_home import backHome
from utils.file_utils import open_folder
progress_bar = None
@@ -85,18 +86,7 @@ def create_convert_pdf_page(folder: str = ""):
)
with ui.header().classes("app-header items-center justify-between shadow-md"):
# 左侧:图标和标题
with ui.row().classes("items-center gap-2"):
ui.image("/assets/icon.ico").classes("w-8 h-8").props("fit=contain")
ui.label("尚城幼儿园成长报告助手").classes("text-xl font-bold")
# 右侧:署名 + 配置按钮
with ui.row().classes("items-center gap-4"):
ui.label("By 寒寒 | 这里的每一份评语都充满爱意").classes(
"text-xs opacity-90"
)
ui.button(icon="home", on_click=lambda: ui.navigate.to("/")).props(
"flat round color=white"
)
backHome()
with ui.card().classes("w-full"):
with ui.row().classes("w-full justify-between"):

View File

@@ -1,28 +1,17 @@
import pandas as pd
from nicegui import ui
from config.config import load_config
import os
import pandas as pd
from nicegui import ui
def create_header():
with ui.header().classes("app-header items-center justify-between shadow-md"):
with ui.row().classes("items-center gap-2"):
ui.image("/assets/icon.ico").classes("w-8 h-8").props("fit=contain")
ui.label("尚城幼儿园成长报告助手").classes("text-xl font-bold")
with ui.row().classes("items-center gap-4"):
ui.label("By 寒寒 | 这里的每一份评语都充满爱意").classes(
"text-xs opacity-90"
)
ui.button(icon="home", on_click=lambda: ui.navigate.to("/")).props(
"flat round color=white"
).tooltip("回到首页")
from config.config import load_config
from ui.views.templates.back_home import backHome
def create_data_page():
ui.add_head_html('<link href="/assets/style.css" rel="stylesheet" />')
create_header()
with ui.header().classes("app-header items-center justify-between shadow-md"):
backHome()
with ui.column().classes("w-full max-w-6xl mx-auto p-4 gap-4 thin-scrollbar"):
with ui.card().classes("func-card"):
@@ -37,7 +26,7 @@ def create_data_page():
def load_data():
conf_data = load_config("config.toml")
conf_data = load_config()
excel_path = conf_data.get("excel_file")
with (

View File

@@ -12,7 +12,7 @@ from utils.generate_utils import (
generate_zodiac,
)
config = load_config("config.toml")
config = load_config()
def create_header():

View File

@@ -1,10 +1,12 @@
from nicegui import ui
import os
import traceback
from loguru import logger
from nicegui import ui
from pptx import Presentation
from config.config import load_config
from utils.file_utils import open_folder
from loguru import logger
import traceback
from pptx import Presentation
def create_signature_page(folder: str = ""):
@@ -142,7 +144,7 @@ def sign_file(folder, file_name: str):
"""
try:
# 1. 加载配置文件
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试

View File

@@ -0,0 +1,16 @@
from nicegui import ui
def backHome():
"""返回首页"""
with ui.row().classes("items-center gap-2"):
ui.image("/assets/icon.ico").classes("w-8 h-8").props("fit=contain")
ui.label("尚城幼儿园成长报告助手").classes("text-xl font-bold")
with ui.row().classes("items-center gap-4"):
ui.label("By 寒寒 | 这里的每一份评语都充满爱意").classes(
"text-xs opacity-90"
)
ui.button(icon="home", on_click=lambda: ui.navigate.to("/")).props(
"flat round color=white"
).tooltip("回到首页")

View File

@@ -4,14 +4,13 @@
import os
import platform
import shutil
import time
from pathlib import Path
from loguru import logger
from config.config import load_config
config = load_config("config.toml")
config = load_config()
def get_system_fonts():

View File

@@ -1,20 +1,17 @@
import os
import threading
import time
import pythoncom
import pandas as pd
from loguru import logger
from pptx import Presentation
from rich.console import Console
import traceback
import comtypes.client
import pandas as pd
import pythoncom
from loguru import logger
from pptx import Presentation
from config.config import load_config
from utils.agent_utils import generate_comment
from utils.file_utils import check_file_exists, get_output_pptx_files
from utils.image_utils import find_image_path
from utils.zodiac_utils import calculate_zodiac
from utils.growt_utils import (
replace_one_page,
replace_two_page,
@@ -22,6 +19,8 @@ from utils.growt_utils import (
replace_four_page,
replace_five_page,
)
from utils.image_utils import find_image_path
from utils.zodiac_utils import calculate_zodiac
# ==========================================
@@ -35,7 +34,7 @@ def generate_template(stop_event: threading.Event = None, progress_callback=None
"""
# 1. 加载配置文件
try:
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试
@@ -89,7 +88,7 @@ def generate_comment_all(stop_event: threading.Event = None, progress_callback=N
"""
# 1. 加载配置文件
try:
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试
@@ -184,7 +183,7 @@ def generate_report(stop_event: threading.Event = None, progress_callback=None):
:params progress_callback 进度回调函数
""" # 1. 加载配置文件
try:
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试
@@ -316,6 +315,7 @@ def generate_report(stop_event: threading.Event = None, progress_callback=None):
class_image_path = find_image_path(
config["image_folder"], config["class_name"]
)
print(config["image_folder"], config["class_name"])
# 添加检查班级图片是否存在,若不存在则跳过
if check_file_exists(class_image_path):
@@ -378,7 +378,7 @@ def generate_convert_pdf(stop_event: threading.Event = None, progress_callback=N
"""
# 1. 加载配置文件
try:
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试
@@ -470,7 +470,7 @@ def generate_zodiac(stop_event: threading.Event = None, progress_callback=None):
"""
# 1. 加载配置文件
try:
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试
@@ -559,7 +559,7 @@ def generate_signature(progress_callback=None) -> str:
"""
# 1. 加载配置文件
try:
config = load_config("config.toml")
config = load_config()
except Exception as e:
logger.error(f"配置文件获取失败: {str(e)}")
# 打印详细报错位置,方便调试

View File

@@ -1,5 +1,5 @@
from rich.console import Console
from loguru import logger
from rich.console import Console
from config.config import load_config
from utils.pptx_utils import replace_text_in_slide, replace_picture
@@ -10,7 +10,7 @@ console = Console()
# ==========================================
# 1. 配置区域 (Configuration)
# ==========================================
config = load_config("config.toml")
config = load_config()
def replace_one_page(prs, name, class_name):

10
uv.lock generated
View File

@@ -1,5 +1,5 @@
version = 1
revision = 2
revision = 3
requires-python = ">=3.13"
[[package]]
@@ -413,6 +413,7 @@ dependencies = [
{ name = "comtypes" },
{ name = "langchain" },
{ name = "langchain-openai" },
{ name = "logger" },
{ name = "loguru" },
{ name = "nicegui" },
{ name = "openpyxl" },
@@ -434,6 +435,7 @@ requires-dist = [
{ name = "comtypes", specifier = ">=1.4.13" },
{ name = "langchain", specifier = ">=1.1.3" },
{ name = "langchain-openai", specifier = ">=1.1.1" },
{ name = "logger" },
{ name = "loguru", specifier = ">=0.7.3" },
{ name = "nicegui", specifier = ">=3.4.0" },
{ name = "openpyxl", specifier = ">=3.1.5" },
@@ -742,6 +744,12 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/63/54/4577ef9424debea2fa08af338489d593276520d2e2f8950575d292be612c/langsmith-0.4.59-py3-none-any.whl", hash = "sha256:97c26399286441a7b7b06b912e2801420fbbf3a049787e609d49dc975ab10bc5" },
]
[[package]]
name = "logger"
version = "1.4"
source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/73/2f/b0d28eaa1e2c1cf64129f8da3fe701888d152677fec708cd0f13e8309e1e/logger-1.4.tar.gz", hash = "sha256:4ecac57133c6376fa215f0fe6b4dc4d60e4d1ad8be005cab4e8a702df682f8b3" }
[[package]]
name = "loguru"
version = "0.7.3"