fix:修复模板问题,修复系统操作逻辑,修复系统的一些BUG
This commit is contained in:
16
config.toml
16
config.toml
@@ -1,26 +1,26 @@
|
||||
[paths]
|
||||
source_file = "大二班 幼儿学期发展报告.pptx"
|
||||
source_file = "大四班 幼儿学期发展报告.pptx"
|
||||
output_folder = "output"
|
||||
excel_file = "names.xlsx"
|
||||
image_folder = "images"
|
||||
fonts_dir = "fonts"
|
||||
signature_image = "signature.png"
|
||||
signature_image = "C:\\Users\\Administrator\\Desktop\\文档资料\\code\\growth_report\\data\\"
|
||||
|
||||
[class_info]
|
||||
class_name = "K4B"
|
||||
class_name = "K4D"
|
||||
teachers = [
|
||||
"李垚",
|
||||
"廖琴琴",
|
||||
"余鸿仙",
|
||||
"康璐璐",
|
||||
"冯宇阳",
|
||||
"孙继艳",
|
||||
]
|
||||
class_type = 1
|
||||
class_type = 2
|
||||
|
||||
[defaults]
|
||||
default_comment = ""
|
||||
age_group = "大班上学期"
|
||||
|
||||
[ai]
|
||||
api_key = "sk-8b0c9522df8843b4d0e7e91ecb628957"
|
||||
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"
|
||||
|
||||
@@ -18,24 +18,27 @@ except ImportError:
|
||||
# 如果没安装,提供一个 fallback 提示
|
||||
toml_write = None
|
||||
|
||||
|
||||
def get_base_dir():
|
||||
if getattr(sys, 'frozen', False):
|
||||
if getattr(sys, "frozen", False):
|
||||
return os.path.dirname(sys.executable)
|
||||
else:
|
||||
# 假设当前文件在项目根目录或根目录下的某个文件夹中
|
||||
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def get_resource_path(relative_path):
|
||||
base_path = get_base_dir()
|
||||
external_path = os.path.join(base_path, relative_path)
|
||||
if os.path.exists(external_path):
|
||||
return external_path
|
||||
if getattr(sys, 'frozen', False):
|
||||
if getattr(sys, "frozen", False):
|
||||
internal_path = os.path.join(sys._MEIPASS, relative_path)
|
||||
if os.path.exists(internal_path):
|
||||
return internal_path
|
||||
return external_path
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 1. 配置加载 (Config Loader)
|
||||
# ==========================================
|
||||
@@ -59,20 +62,32 @@ def load_config(config_filename="config.toml"):
|
||||
|
||||
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", ""))),
|
||||
"excel_file": get_resource_path(os.path.join("data", paths.get("excel_file", ""))),
|
||||
"image_folder": get_resource_path(os.path.join("data", paths.get("image_folder", ""))),
|
||||
"source_file": get_resource_path(
|
||||
os.path.join("templates", paths.get("source_file", ""))
|
||||
),
|
||||
"excel_file": get_resource_path(
|
||||
os.path.join("data", paths.get("excel_file", ""))
|
||||
),
|
||||
"image_folder": get_resource_path(
|
||||
os.path.join("data", paths.get("image_folder", ""))
|
||||
),
|
||||
"fonts_dir": get_resource_path(paths.get("fonts_dir", "fonts")),
|
||||
"output_folder": os.path.join(base_dir, paths.get("output_folder", "output")),
|
||||
"signature_image": get_resource_path(os.path.join("data", paths.get("signature_image", ""))),
|
||||
|
||||
"output_folder": os.path.join(
|
||||
base_dir, paths.get("output_folder", "output")
|
||||
),
|
||||
"signature_image": get_resource_path(
|
||||
os.path.join("data", paths.get("signature_image", ""))
|
||||
),
|
||||
"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("ai", {"api_key": "", "api_url": "", "model":"","prompt":""}),
|
||||
"ai": data.get(
|
||||
"ai", {"api_key": "", "api_url": "", "model": "", "prompt": ""}
|
||||
),
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -80,6 +95,7 @@ def load_config(config_filename="config.toml"):
|
||||
print(f"解析配置文件失败: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 2. 配置保存 (Config Saver)
|
||||
# ==========================================
|
||||
@@ -95,11 +111,15 @@ def save_config(config_data, config_filename="config.toml"):
|
||||
new_data = {
|
||||
"paths": {
|
||||
"source_file": os.path.basename(config_data.get("source_file", "")),
|
||||
"output_folder": os.path.basename(config_data.get("output_folder", "output")),
|
||||
"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_image": get_resource_path(
|
||||
os.path.join("data", config_data.get("signature_image", ""))
|
||||
),
|
||||
},
|
||||
"class_info": {
|
||||
"class_name": config_data.get("class_name", ""),
|
||||
@@ -110,7 +130,7 @@ def save_config(config_data, config_filename="config.toml"):
|
||||
"default_comment": config_data.get("default_comment", ""),
|
||||
"age_group": config_data.get("age_group", ""),
|
||||
},
|
||||
"ai": config_data.get("ai", {})
|
||||
"ai": config_data.get("ai", {}),
|
||||
}
|
||||
|
||||
# 写入文件
|
||||
|
||||
BIN
data/names.xlsx
BIN
data/names.xlsx
Binary file not shown.
BIN
templates/中一班 幼儿学期发展报告.pptx
Normal file
BIN
templates/中一班 幼儿学期发展报告.pptx
Normal file
Binary file not shown.
BIN
templates/大四班 幼儿学期发展报告.pptx
Normal file
BIN
templates/大四班 幼儿学期发展报告.pptx
Normal file
Binary file not shown.
BIN
templates/小三班 幼儿学期发展报告.pptx
Normal file
BIN
templates/小三班 幼儿学期发展报告.pptx
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,14 +1,16 @@
|
||||
from nicegui import ui
|
||||
import os
|
||||
from utils.template_utils import get_template_files
|
||||
|
||||
# 修改点 1:统一导入,避免与变量名 config 冲突
|
||||
from config.config import load_config, save_config
|
||||
|
||||
|
||||
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', ''))
|
||||
current_filename = os.path.basename(conf_data.get("source_file", ""))
|
||||
|
||||
if current_filename and current_filename not in template_options:
|
||||
template_options.append(current_filename)
|
||||
@@ -16,63 +18,148 @@ def create_config_page():
|
||||
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.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-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')
|
||||
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"
|
||||
)
|
||||
|
||||
# 修改点 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.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_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_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_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=['小班上学期', '小班下学期', '中班上学期', '中班下学期', '大班上学期', '大班下学期'],
|
||||
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(
|
||||
options={0: '便宜班', 1: '昂贵班'},
|
||||
label='班级类型', value=conf_data.get('class_type', 0)
|
||||
).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=[
|
||||
"小班上学期",
|
||||
"小班下学期",
|
||||
"中班上学期",
|
||||
"中班下学期",
|
||||
"大班上学期",
|
||||
"大班下学期",
|
||||
],
|
||||
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(
|
||||
options={0: "便宜班", 1: "昂贵班", 2: "昂贵的双木桥班"},
|
||||
label="班级类型",
|
||||
value=conf_data.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')
|
||||
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")
|
||||
)
|
||||
# 底部固定按钮
|
||||
with ui.row().classes('w-full p-4'):
|
||||
with ui.row().classes("w-full p-4"):
|
||||
|
||||
async def handle_save():
|
||||
new_data = {
|
||||
"source_file": source_file.value,
|
||||
@@ -81,17 +168,21 @@ def create_config_page():
|
||||
"output_folder": output_folder.value,
|
||||
"class_name": class_name.value,
|
||||
"age_group": age_group.value,
|
||||
"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
|
||||
}
|
||||
"prompt": ai_prompt.value,
|
||||
},
|
||||
}
|
||||
# 修改点 4:直接调用导入的 save_config 函数名
|
||||
success, message = save_config(new_data)
|
||||
ui.notify("配置已保存重启生效", type='positive')
|
||||
ui.notify("配置已保存重启生效", type="positive")
|
||||
|
||||
ui.button('保存配置', on_click=handle_save).classes('w-full py-4').props('outline color=primary')
|
||||
ui.button("保存配置", on_click=handle_save).classes("w-full py-4").props(
|
||||
"outline color=primary"
|
||||
)
|
||||
|
||||
@@ -5,25 +5,35 @@ from ui.core.task_runner import run_task, select_folder
|
||||
|
||||
# 导入业务函数
|
||||
from utils.generate_utils import (
|
||||
generate_template, generate_comment_all,
|
||||
batch_convert_folder, generate_report, generate_zodiac, generate_signature
|
||||
generate_template,
|
||||
generate_comment_all,
|
||||
batch_convert_folder,
|
||||
generate_report,
|
||||
generate_zodiac,
|
||||
generate_signature,
|
||||
)
|
||||
from utils.file_utils import export_templates_folder, initialize_project, export_data
|
||||
from utils.file_utils import initialize_project, open_folder
|
||||
|
||||
config = load_config("config.toml")
|
||||
|
||||
|
||||
def create_header():
|
||||
with ui.header().classes('app-header items-center justify-between shadow-md'):
|
||||
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-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')
|
||||
with ui.row().classes("items-center gap-4"):
|
||||
ui.label("By 寒寒 | 这里的每一份评语都充满爱意").classes(
|
||||
"text-xs opacity-90"
|
||||
)
|
||||
# 添加配置按钮
|
||||
ui.button(icon='settings', on_click=lambda: ui.navigate.to('/config')).props('flat round color=white').tooltip('系统配置')
|
||||
ui.button(
|
||||
icon="settings", on_click=lambda: ui.navigate.to("/config")
|
||||
).props("flat round color=white").tooltip("系统配置")
|
||||
|
||||
|
||||
def create_home_page():
|
||||
# 1. 引入外部 CSS
|
||||
@@ -32,62 +42,70 @@ def create_home_page():
|
||||
create_header()
|
||||
|
||||
# 主容器
|
||||
with ui.column().classes('w-full max-w-4xl mx-auto p-4 gap-4 thin-scrollbar'):
|
||||
with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-4 thin-scrollbar"):
|
||||
|
||||
# === 进度条区域 ===
|
||||
with ui.card().classes('func-card'):
|
||||
app_state.progress_label = ui.label('⛳ 任务进度: 待命').classes('font-bold text-gray-700 mb-1')
|
||||
with ui.card().classes("func-card"):
|
||||
app_state.progress_label = ui.label("⛳ 任务进度: 待命").classes(
|
||||
"font-bold text-gray-700 mb-1"
|
||||
)
|
||||
# 使用 NiceGUI 原生属性配合 CSS 类
|
||||
app_state.progress_bar = ui.linear_progress(value=0, show_value=False).classes('h-4 rounded')
|
||||
app_state.progress_bar.props('color=positive') # 使用 Quasar 颜色变量
|
||||
app_state.progress_bar = ui.linear_progress(
|
||||
value=0, show_value=False
|
||||
).classes("h-4 rounded")
|
||||
app_state.progress_bar.props("color=positive") # 使用 Quasar 颜色变量
|
||||
|
||||
# === 核心功能区 ===
|
||||
with ui.card().classes('func-card card-core'):
|
||||
ui.label('🛠️ 核心功能').classes('section-title text-green')
|
||||
with ui.card().classes("func-card card-core"):
|
||||
ui.label("🛠️ 核心功能").classes("section-title text-green")
|
||||
|
||||
with ui.grid(columns=3).classes('w-full gap-3'):
|
||||
with ui.grid(columns=3).classes("w-full gap-3"):
|
||||
# 辅助函数:快速创建按钮
|
||||
def func_btn(text, icon, func):
|
||||
ui.button(text, on_click=lambda: run_task(func)).props(f'outline').classes('w-full')
|
||||
ui.button(text, on_click=lambda: run_task(func)).props(
|
||||
f"outline"
|
||||
).classes("w-full")
|
||||
|
||||
func_btn("📁 生成图片路径", "image", generate_template)
|
||||
func_btn("🤖 生成评语 (AI)", "smart_toy", generate_comment_all)
|
||||
func_btn("📊 生成报告 (PPT)", "analytics", generate_report)
|
||||
|
||||
func_btn('📁 生成图片路径', 'image', generate_template)
|
||||
func_btn('🤖 生成评语 (AI)', 'smart_toy', generate_comment_all)
|
||||
func_btn('📊 生成报告 (PPT)', 'analytics', generate_report)
|
||||
# 特殊处理带参数的
|
||||
async def run_convert():
|
||||
await run_task(batch_convert_folder, config.get("output_folder"))
|
||||
ui.button('📑 格式转换 (PDF)', on_click=run_convert).props('outline')
|
||||
func_btn('🐂 生肖转化 (生日)', 'pets', generate_zodiac)
|
||||
func_btn('💴 园长一键签名', 'refresh', generate_signature)
|
||||
|
||||
ui.button("📑 格式转换 (PDF)", on_click=run_convert).props("outline")
|
||||
func_btn("🐂 生肖转化 (生日)", "pets", generate_zodiac)
|
||||
func_btn("💴 园长一键签名", "refresh", generate_signature)
|
||||
|
||||
# === 下方双栏布局 ===
|
||||
with ui.grid(columns=2).classes('w-full gap-4'):
|
||||
# 数据管理
|
||||
with ui.card().classes('func-card card-data'):
|
||||
ui.label('📦 数据管理').classes('section-title text-blue')
|
||||
with ui.row().classes('w-full'):
|
||||
async def do_export(func):
|
||||
path = await select_folder()
|
||||
if path: await run_task(func, path)
|
||||
with ui.card().classes("func-card card-data"):
|
||||
ui.label("⚙️ 系统操作").classes("section-title text-blue")
|
||||
|
||||
ui.button('📦 导出模板', on_click=lambda: do_export(export_templates_folder)).props(f'outline')
|
||||
ui.button('📤 导出备份', on_click=lambda: do_export(export_data)).props(f'outline')
|
||||
# 系统操作
|
||||
with ui.card().classes('func-card card-system'):
|
||||
ui.label('⚙️ 系统操作').classes('section-title text-red')
|
||||
with ui.row().classes('w-full'):
|
||||
def stop_now():
|
||||
if app_state.is_running:
|
||||
app_state.stop_event.set()
|
||||
ui.notify("发送停止信号...", type="warning")
|
||||
|
||||
ui.button('⛔ 停止', on_click=stop_now).props('color=negative').classes('flex-1')
|
||||
with ui.row().classes("w-full"):
|
||||
ui.button(
|
||||
"📦 打开输出文件夹",
|
||||
on_click=lambda: open_folder(config.get("output_folder")),
|
||||
).props(f"outline")
|
||||
ui.button(
|
||||
"📤 打开数据文件夹",
|
||||
on_click=lambda: open_folder(config.get("data_folder")),
|
||||
).props(f"outline")
|
||||
ui.button("⛔ 停止", on_click=stop_now).props("color=negative").classes(
|
||||
"flex-1"
|
||||
)
|
||||
|
||||
async def reset_sys():
|
||||
await run_task(initialize_project)
|
||||
|
||||
ui.button('⚠️ 初始化', on_click=reset_sys).props('outline color=warning').classes('flex-1')
|
||||
# === 日志区 ===
|
||||
with ui.card().classes('func-card card-logging'):
|
||||
with ui.expansion('📝 系统实时日志',value=True).classes('w-full bg-white shadow-sm rounded'):
|
||||
app_state.log_element = ui.log(max_lines=200).classes('w-full h-40 font-mono text-xs bg-gray-100 p-2')
|
||||
with ui.card().classes("func-card card-logging"):
|
||||
with ui.expansion("📝 系统实时日志", value=True).classes(
|
||||
"w-full bg-white shadow-sm rounded"
|
||||
):
|
||||
app_state.log_element = ui.log(max_lines=200).classes(
|
||||
"w-full h-40 font-mono text-xs bg-gray-100 p-2"
|
||||
)
|
||||
|
||||
@@ -8,8 +8,9 @@ import traceback
|
||||
from config.config import load_config
|
||||
|
||||
class_type_config =[
|
||||
"本期开展了小袋鼠整合主题课程:(语言、社会、科学、健康、艺术)、生活数学;特色课程(英语、体能、美工、篮球)",
|
||||
"本学期开展了柏克莱主题课程(语言、社会、科学、艺术、健康);英语及特色课程(体能、舞蹈、美工、魔力猴、足球、国学)"
|
||||
"本期开展了小袋鼠整合主题课程:(语言、社会、科学、健康、艺术)、生活数学;特色课程(英语、体能、美工、篮球)。",
|
||||
"本学期开展了柏克莱主题课程(语言、社会、科学、艺术、健康);英语及特色课程(体能、舞蹈、美工、魔力猴、足球、国学)。",
|
||||
"本学期开展了双木桥主题课程(图说汉字、妙趣汉音、情智阅读、麦斯思维、专注力训练);英语及特色课程(体能、舞蹈、美工、魔力猴、足球、国学)。"
|
||||
]
|
||||
|
||||
def generate_comment(name, age_group, traits,sex):
|
||||
|
||||
@@ -5,6 +5,7 @@ from loguru import logger
|
||||
import zipfile
|
||||
import traceback
|
||||
|
||||
|
||||
def export_templates_folder(output_folder, stop_event, progress_callback=None):
|
||||
"""
|
||||
将指定文件夹压缩为 zip 包
|
||||
@@ -121,7 +122,9 @@ def export_data(save_dir, root_dir=".", progress_callback=None):
|
||||
has_files = True
|
||||
# 更新进度条
|
||||
if progress_callback:
|
||||
progress_callback(processed_count + 1, total_files, "导出数据中...")
|
||||
progress_callback(
|
||||
processed_count + 1, total_files, "导出数据中..."
|
||||
)
|
||||
|
||||
if has_files:
|
||||
# 确保进度条最后能走到 100%
|
||||
@@ -139,6 +142,7 @@ def export_data(save_dir, root_dir=".", progress_callback=None):
|
||||
except Exception as e:
|
||||
logger.error(f"导出过程出错: {str(e)}")
|
||||
import traceback
|
||||
|
||||
logger.error(traceback.format_exc())
|
||||
return None
|
||||
|
||||
@@ -218,6 +222,7 @@ def check_file_exists(file_path):
|
||||
"""
|
||||
return file_path and isinstance(file_path, str) and os.path.exists(file_path)
|
||||
|
||||
|
||||
def get_output_pptx_files(output_dir="output"):
|
||||
"""
|
||||
获取 output 文件夹下所有的 pptx 文件
|
||||
@@ -241,3 +246,17 @@ def get_output_pptx_files(output_dir="output"):
|
||||
except Exception as e:
|
||||
logger.error(f"发生未知错误: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def open_folder(folder_path):
|
||||
"""
|
||||
打开指定文件夹
|
||||
:param folder_path: 文件夹路径
|
||||
"""
|
||||
try:
|
||||
if os.path.exists(folder_path):
|
||||
os.startfile(folder_path)
|
||||
else:
|
||||
logger.error(f"文件夹不存在: {folder_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"打开文件夹失败: {e}")
|
||||
|
||||
@@ -21,28 +21,28 @@ def replace_text_in_slide(prs, slide_index, placeholder, text):
|
||||
original_paragraph_formats = []
|
||||
for paragraph in shape.text_frame.paragraphs:
|
||||
paragraph_format = {
|
||||
'alignment': paragraph.alignment,
|
||||
'space_before': getattr(paragraph, 'space_before', None),
|
||||
'space_after': getattr(paragraph, 'space_after', None),
|
||||
'line_spacing': getattr(paragraph, 'line_spacing', None),
|
||||
'left_indent': getattr(paragraph, 'left_indent', None),
|
||||
'right_indent': getattr(paragraph, 'right_indent', None),
|
||||
'first_line_indent': getattr(paragraph, 'first_line_indent', None),
|
||||
'font_info': []
|
||||
"alignment": paragraph.alignment,
|
||||
"space_before": getattr(paragraph, "space_before", None),
|
||||
"space_after": getattr(paragraph, "space_after", None),
|
||||
"line_spacing": getattr(paragraph, "line_spacing", None),
|
||||
"left_indent": getattr(paragraph, "left_indent", None),
|
||||
"right_indent": getattr(paragraph, "right_indent", None),
|
||||
"first_line_indent": getattr(paragraph, "first_line_indent", None),
|
||||
"font_info": [],
|
||||
}
|
||||
for run in paragraph.runs:
|
||||
run_format = {
|
||||
'font_name': run.font.name,
|
||||
'font_size': run.font.size,
|
||||
'bold': run.font.bold,
|
||||
'italic': run.font.italic,
|
||||
'underline': run.font.underline,
|
||||
'color': run.font.color,
|
||||
'character_space': getattr(run.font, 'space', None),
|
||||
'all_caps': getattr(run.font, 'all_caps', None),
|
||||
'small_caps': getattr(run.font, 'small_caps', None)
|
||||
"font_name": run.font.name,
|
||||
"font_size": run.font.size,
|
||||
"bold": run.font.bold,
|
||||
"italic": run.font.italic,
|
||||
"underline": run.font.underline,
|
||||
"color": run.font.color,
|
||||
"character_space": getattr(run.font, "space", None),
|
||||
"all_caps": getattr(run.font, "all_caps", None),
|
||||
"small_caps": getattr(run.font, "small_caps", None),
|
||||
}
|
||||
paragraph_format['font_info'].append(run_format)
|
||||
paragraph_format["font_info"].append(run_format)
|
||||
original_paragraph_formats.append(paragraph_format)
|
||||
|
||||
# 2. 设置新文本
|
||||
@@ -51,49 +51,63 @@ def replace_text_in_slide(prs, slide_index, placeholder, text):
|
||||
# 3. 恢复格式
|
||||
for i, paragraph in enumerate(shape.text_frame.paragraphs):
|
||||
orig_idx = i if i < len(original_paragraph_formats) else -1
|
||||
if not original_paragraph_formats: break
|
||||
if not original_paragraph_formats:
|
||||
break
|
||||
|
||||
original_para = original_paragraph_formats[orig_idx]
|
||||
|
||||
# 恢复段落属性
|
||||
for attr in ['alignment', 'space_before', 'space_after', 'line_spacing',
|
||||
'left_indent', 'right_indent', 'first_line_indent']:
|
||||
for attr in [
|
||||
"alignment",
|
||||
"space_before",
|
||||
"space_after",
|
||||
"line_spacing",
|
||||
"left_indent",
|
||||
"right_indent",
|
||||
"first_line_indent",
|
||||
]:
|
||||
if original_para[attr] is not None:
|
||||
setattr(paragraph, attr, original_para[attr])
|
||||
|
||||
# 恢复字体属性
|
||||
for j, run in enumerate(paragraph.runs):
|
||||
font_idx = j if j < len(original_para['font_info']) else 0
|
||||
if not original_para['font_info']: break
|
||||
font_idx = j if j < len(original_para["font_info"]) else 0
|
||||
if not original_para["font_info"]:
|
||||
break
|
||||
|
||||
original_font = original_para['font_info'][font_idx]
|
||||
original_font = original_para["font_info"][font_idx]
|
||||
|
||||
# 字体名称检查与回退
|
||||
if original_font['font_name']:
|
||||
if is_font_available(original_font['font_name']):
|
||||
run.font.name = original_font['font_name']
|
||||
if original_font["font_name"]:
|
||||
if is_font_available(original_font["font_name"]):
|
||||
run.font.name = original_font["font_name"]
|
||||
else:
|
||||
run.font.name = "微软雅黑"
|
||||
|
||||
# 恢复其他字体属性
|
||||
if original_font['font_size']: run.font.size = original_font['font_size']
|
||||
if original_font['bold']: run.font.bold = original_font['bold']
|
||||
if original_font['italic']: run.font.italic = original_font['italic']
|
||||
if original_font['underline']: run.font.underline = original_font['underline']
|
||||
if original_font['all_caps']: run.font.all_caps = original_font['all_caps']
|
||||
if original_font["font_size"]:
|
||||
run.font.size = original_font["font_size"]
|
||||
if original_font["bold"]:
|
||||
run.font.bold = original_font["bold"]
|
||||
if original_font["italic"]:
|
||||
run.font.italic = original_font["italic"]
|
||||
if original_font["underline"]:
|
||||
run.font.underline = original_font["underline"]
|
||||
if original_font["all_caps"]:
|
||||
run.font.all_caps = original_font["all_caps"]
|
||||
|
||||
if original_font['character_space']:
|
||||
if original_font["character_space"]:
|
||||
try:
|
||||
run.font.space = original_font['character_space']
|
||||
run.font.space = original_font["character_space"]
|
||||
except:
|
||||
pass
|
||||
logger.error(f"错误: 无法设置字体间距 {original_font['character_space']}")
|
||||
|
||||
if original_font['color']:
|
||||
if original_font["color"]:
|
||||
try:
|
||||
if hasattr(original_font['color'], 'rgb'):
|
||||
run.font.color.rgb = original_font['color'].rgb
|
||||
if hasattr(original_font["color"], "rgb"):
|
||||
run.font.color.rgb = original_font["color"].rgb
|
||||
except:
|
||||
pass
|
||||
logger.error(f"错误: 无法设置字体颜色 {original_font['color']}")
|
||||
|
||||
|
||||
def replace_picture(prs, slide_index, placeholder, img_path):
|
||||
@@ -125,7 +139,12 @@ def replace_picture(prs, slide_index, placeholder, img_path):
|
||||
|
||||
if target_shape:
|
||||
# 获取原位置信息
|
||||
left, top, width, height = target_shape.left, target_shape.top, target_shape.width, target_shape.height
|
||||
left, top, width, height = (
|
||||
target_shape.left,
|
||||
target_shape.top,
|
||||
target_shape.width,
|
||||
target_shape.height,
|
||||
)
|
||||
|
||||
# 2. 获取修正后的图片流
|
||||
img_stream = get_corrected_image_stream(img_path)
|
||||
@@ -138,3 +157,5 @@ def replace_picture(prs, slide_index, placeholder, img_path):
|
||||
|
||||
# 5. 恢复层级位置 (z-order)
|
||||
sp_tree.insert(target_index, new_shape._element)
|
||||
else:
|
||||
logger.warning(f"警告: 幻灯片 {slide_index} 中未找到占位符 {placeholder}")
|
||||
|
||||
Reference in New Issue
Block a user