From d1f6d7da7daa9ef6446f84c393ec109cc51b766b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=92=E5=AF=92?= <2596194220@qq.com> Date: Thu, 1 Jan 2026 00:28:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E5=AE=9E=E7=8E=B0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A0=B8=E5=AF=B9=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 20 +++++ main_nicegui.py | 21 +++-- ui/assets/style.css | 156 ++++++++++++++++++++++------------ ui/views/data_page.py | 193 ++++++++++++++++++++++++++++++++++++++++++ ui/views/home_page.py | 13 ++- 5 files changed, 340 insertions(+), 63 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 ui/views/data_page.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b831c2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.singleQuote": true, + + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/node_modules": true, + "**/dist": true, + ".idea/**": true, + "**/__pycache__": true + }, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} diff --git a/main_nicegui.py b/main_nicegui.py index 519cac0..6f8fe67 100644 --- a/main_nicegui.py +++ b/main_nicegui.py @@ -7,14 +7,16 @@ from nicegui import ui, app, run, native from screeninfo import get_monitors from config.config import load_config + # 导入我们的模块 from ui.core.logger import setup_logger from ui.views.config_page import create_config_page from ui.views.home_page import create_home_page +from ui.views.data_page import create_data_page from utils.font_utils import install_fonts_from_directory -sys.stdout.reconfigure(encoding='utf-8') -sys.stderr.reconfigure(encoding='utf-8') +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") # 1. 初始化配置 config = load_config("config.toml") @@ -27,7 +29,7 @@ def get_path(relative_path): 获取资源的绝对路径。 兼容:开发环境(直接运行) 和 生产环境(打包成exe后解压的临时目录) """ - if hasattr(sys, '_MEIPASS'): + if hasattr(sys, "_MEIPASS"): base_path = sys._MEIPASS else: # 开发环境当前目录 @@ -74,20 +76,25 @@ def calculate_window_size(): # 1. 挂载静态资源 (CSS/图片) # 注意:这里使用 get_path 确保打包后能找到 static_dir = get_path(os.path.join("ui", "assets")) -app.add_static_files('/assets', static_dir) +app.add_static_files("/assets", static_dir) # 3. 页面路由 -@ui.page('/') +@ui.page("/") def index_page(): create_home_page() -@ui.page('/config') +@ui.page("/config") def config_page(): create_config_page() +@ui.page("/data") +def data_page(): + create_data_page() + + # 4. 启动时钩子 async def startup_check(): try: @@ -109,5 +116,5 @@ if __name__ in {"__main__", "__mp_main__"}: native=True, window_size=calculated_size, port=native.find_open_port(), # 自动寻找端口 - reload=False + reload=True, ) diff --git a/ui/assets/style.css b/ui/assets/style.css index 8d3055c..31d4e6b 100644 --- a/ui/assets/style.css +++ b/ui/assets/style.css @@ -2,58 +2,63 @@ /* 全局字体 */ body { - font-family: "微软雅黑", "Microsoft YaHei", sans-serif; - background-color: #f0f4f8; + font-family: '微软雅黑', 'Microsoft YaHei', sans-serif; + background-color: #f0f4f8; } /* 标题栏 */ .app-header { - background-color: #2E8B57; /* SeaGreen */ - color: white; + background-color: #2e8b57; /* SeaGreen */ + color: white; } /* 卡片通用样式 */ .func-card { - width: 100%; - padding: 1rem; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - background-color: white; - border-radius: 0.5rem; + width: 100%; + padding: 1rem; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + background-color: white; + border-radius: 0.5rem; } /* 核心功能区顶部边框 */ .card-core { - border-top: 4px solid #16a34a; /* green-600 */ + border-top: 4px solid #16a34a; /* green-600 */ } /* 数据管理区顶部边框 */ .card-data { - border-top: 4px solid #3b82f6; /* blue-500 */ + border-top: 4px solid #3b82f6; /* blue-500 */ } /* 系统操作区顶部边框 */ .card-system { - border-top: 4px solid #ef4444; /* red-500 */ + border-top: 4px solid #ef4444; /* red-500 */ } .card-logging { - border-top: 4px solid #9c1be0; + border-top: 4px solid #9c1be0; } /* 标题文字 */ .section-title { - font-size: 1.125rem; - font-weight: 700; - margin-bottom: 0.5rem; + font-size: 1.125rem; + font-weight: 700; + margin-bottom: 0.5rem; } /* 绿色标题 */ -.text-green { color: #166534; } +.text-green { + color: #166534; +} /* 蓝色标题 */ -.text-blue { color: #1e40af; } +.text-blue { + color: #1e40af; +} /* 红色标题 */ -.text-red { color: #991b1b; } - +.text-red { + color: #991b1b; +} /* assets/style.css */ @@ -68,24 +73,24 @@ body { * 适用于 NiceGUI 默认的 Chromium 浏览器 */ .hide-scrollbar::-webkit-scrollbar { - /* 完全隐藏滚动条 */ - width: 0px; - background: transparent; /* 使滚动条轨道透明 */ + /* 完全隐藏滚动条 */ + width: 0px; + background: transparent; /* 使滚动条轨道透明 */ } /* 2. 隐藏 Firefox 浏览器的滚动条 */ .hide-scrollbar { - /* 设置滚动条宽度为 thin (细),比 auto (默认) 要窄 */ - scrollbar-width: none; /* 'none' 是最新且更彻底的隐藏方式 */ - - /* 确保容器内容溢出时可以滚动 */ - overflow: auto; + /* 设置滚动条宽度为 thin (细),比 auto (默认) 要窄 */ + scrollbar-width: none; /* 'none' 是最新且更彻底的隐藏方式 */ + + /* 确保容器内容溢出时可以滚动 */ + overflow: auto; } /* 示例:如果你只想隐藏日志区的滚动条 */ .card-logging .q-expansion-item__content .nicegui-log .q-scrollarea__content { - /* 如果 nicegui-log 内部使用了 q-scrollarea,可能需要针对其内容应用样式 */ - scrollbar-width: none; + /* 如果 nicegui-log 内部使用了 q-scrollarea,可能需要针对其内容应用样式 */ + scrollbar-width: none; } /* ---------------------------------- */ @@ -93,38 +98,85 @@ body { /* 如果完全隐藏不好,可以试试这个更温和的方案 */ /* ---------------------------------- */ .thin-scrollbar::-webkit-scrollbar { - width: 6px; /* 调整宽度 */ - height: 6px; /* 调整高度 */ + width: 6px; /* 调整宽度 */ + height: 6px; /* 调整高度 */ } .thin-scrollbar::-webkit-scrollbar-thumb { - background-color: #a0a0a0; /* 拇指颜色 */ - border-radius: 3px; - border: 1px solid #f0f4f8; /* 边框颜色 */ + background-color: #a0a0a0; /* 拇指颜色 */ + border-radius: 3px; + border: 1px solid #f0f4f8; /* 边框颜色 */ } .thin-scrollbar::-webkit-scrollbar-track { - background: transparent; + background: transparent; } .thin-scrollbar { - scrollbar-width: thin; /* Firefox 细滚动条 */ - scrollbar-color: #a0a0a0 transparent; /* Firefox 颜色设置 */ + scrollbar-width: thin; /* Firefox 细滚动条 */ + scrollbar-color: #a0a0a0 transparent; /* Firefox 颜色设置 */ } *::-webkit-scrollbar { - /* 完全隐藏滚动条 */ - width: 0px !important; - height: 0px !important; - background: transparent !important; + /* 完全隐藏滚动条 */ + width: 0px !important; + height: 0px !important; + background: transparent !important; +} +#nicegui-content, +#q-app { + /* 确保容器内容可以滚动,但滚动条被隐藏 */ + overflow: auto; + + /* Firefox 隐藏滚动条 */ + scrollbar-width: none; + + /* IE/Edge 隐藏滚动条 */ + -ms-overflow-style: none; +} +/* 档案卡片容器 */ +.profile-dialog { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); +} +/* 档案条目样式 */ +.info-item { + background: #ffffff; + border: 1px solid #f1f5f9; + border-radius: 12px; + padding: 14px 18px; + margin-bottom: 10px; + transition: all 0.3s ease; + display: flex; + align-items: flex-start; +} +.info-item:hover { + border-color: #3b82f6; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.08); + transform: translateY(-1px); +} +/* 标签与内容的对比度 */ +.info-label { + font-size: 0.75rem; + font-weight: 700; + color: #94a3b8; /* 浅灰色标签 */ + text-transform: uppercase; + letter-spacing: 0.05em; + width: 100px; + margin-top: 2px; +} +.info-value { + font-size: 0.95rem; + font-weight: 500; + color: #1e293b; /* 深色内容 */ + line-height: 1.6; + word-break: break-all; +} +/* 自定义滚动条 */ +.custom-scroll::-webkit-scrollbar { + width: 4px; +} +.custom-scroll::-webkit-scrollbar-thumb { + background: #e2e8f0; + border-radius: 10px; } -#nicegui-content, #q-app { - /* 确保容器内容可以滚动,但滚动条被隐藏 */ - overflow: auto; - - /* Firefox 隐藏滚动条 */ - scrollbar-width: none; - - /* IE/Edge 隐藏滚动条 */ - -ms-overflow-style: none; -} \ No newline at end of file diff --git a/ui/views/data_page.py b/ui/views/data_page.py new file mode 100644 index 0000000..08e165e --- /dev/null +++ b/ui/views/data_page.py @@ -0,0 +1,193 @@ +import pandas as pd +from nicegui import ui +from config.config import load_config +import os + + +def data_page_header(): + # 头部样式保持不变,仅补充主体所需的 CSS 变量和类 + ui.add_head_html('') + ui.add_head_html( + """ + + """ + ) + + with ui.header().classes("app-header items-center justify-between shadow-md px-6"): + 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("回到首页") + + +def load_data(): + conf_data = load_config("config.toml") + excel_path = conf_data.get("excel_file") + + # --- 1. 详情模态框 (UI 升级:档案感排版) --- + with ( + ui.dialog() as detail_dialog, + ui.card().classes( + "w-[600px] p-0 profile-dialog rounded-3xl overflow-hidden shadow-2xl" + ), + ): + # 【头部】渐变背景与动态图标 + with ui.row().classes( + "w-full bg-gradient-to-r from-blue-50 to-indigo-50 items-center justify-between" + ): + with ui.column().classes("gap-0"): + ui.label("幼儿成长档案").classes( + "text-xl p-4 font-black text-slate-800" + ) + ui.button(icon="close", on_click=detail_dialog.close).props( + "flat round color=primary" + ).classes("bg-white/50") + + # 【中部】卡片流内容区 + with ui.column().classes("w-full p-6 h-[450px] overflow-auto gap-0"): + content_container = ui.column().classes("w-full") + + # 【底部】毛玻璃效果操作栏 + with ui.row().classes( + "w-full p-5 bg-slate-50/80 backdrop-blur-md border-t justify-end gap-3" + ): + ui.button("确认", on_click=detail_dialog.close).props( + "unelevated color=blue-6" + ).classes("px-10 rounded-xl shadow-lg shadow-blue-200 font-bold") + + def handle_cell_click(e): + row_data = e.args["data"] + content_container.clear() + + # 尝试提取名字用于头部(假设你的列名里有“姓名”) + student_name = row_data.get("姓名", "详细数据") + + with content_container: + # 顶部增加一个个人摘要卡片 + with ui.row().classes( + "w-full items-center gap-4 p-4 bg-blue-600 rounded-2xl shadow-md shadow-blue-100" + ): + ui.avatar("person", color="white", text_color="blue-6").props( + "size=48px" + ) + with ui.column().classes("gap-0 text-white"): + ui.label(student_name).classes("text-lg font-bold") + + # 遍历渲染每一条数据 + for key, value in row_data.items(): + if key == "姓名": + continue # 名字已经显示在摘要里了 + with ui.element("div").classes("info-item"): + ui.label(key).classes("info-label font-bold text-blue-800") + ui.label(str(value)).classes("info-value flex-1") + + detail_dialog.open() + + # --- 2. 数据读取与展示 --- + if not excel_path or not os.path.exists(excel_path): + with ui.column().classes("w-full items-center p-12 text-slate-400"): + ui.icon("folder_off", size="64px") + ui.label("数据文件未找到,请检查配置路径").classes("mt-4") + return + + try: + df = pd.read_excel(excel_path) + for col in df.select_dtypes(include=["datetime"]): + df[col] = df[col].dt.strftime("%Y-%m-%d") + df = df.fillna("-") + + # 表格上方信息条 + with ui.row().classes( + "bg-blue-50 w-full p-3 px-6 items-center rounded-t-xl border-b border-blue-100" + ): + ui.icon("fact_check", color="primary", size="20px") + ui.label(f"班级:{conf_data.get('class_name', '未设定')}").classes( + "text-sm font-bold text-blue-800" + ) + ui.separator().props("vertical").classes("mx-2") + ui.label(f"共加载 {len(df)} 条幼儿记录").classes("text-xs text-slate-500") + ui.space() + ui.label("💡 提示:点击行可展开完整评语详情").classes( + "text-xs text-amber-600 bg-amber-50 px-2 py-1 rounded" + ) + + # AgGrid (优化表格高度与交互) + ui.aggrid( + { + "columnDefs": [ + { + "headerName": col, + "field": col, + "sortable": True, + "filter": True, + "cellClass": "text-slate-600", + "suppressMovable": True, + } + for col in df.columns + ], + "rowData": df.to_dict("records"), + "pagination": True, + "paginationPageSize": 20, + "theme": "balham", + } + ).classes("w-full flex-grow h-[550px] border-none").on( + "cellClicked", handle_cell_click + ) + + except Exception as e: + ui.notify(f"加载数据时发生错误: {e}", type="negative", position="top") + + +def create_data_page(): + data_page_header() + with ui.card().classes("w-full m-auto max-w-6xl gap-6"): + # 主标题区域 + with ui.row().classes("items-end justify-between w-full px-2"): + with ui.column().classes("gap-1"): + ui.label("数据预览与核对").classes( + "text-3xl font-bold text-slate-800 tracking-tight" + ) + ui.button( + "刷新表格", icon="sync", on_click=lambda: ui.navigate.to("/data") + ).props("outline color=primary").classes("rounded-lg bg-white shadow-sm") + + # 数据卡片容器 + with ui.card().classes("w-full p-0 data-card rounded-xl overflow-hidden"): + load_data() diff --git a/ui/views/home_page.py b/ui/views/home_page.py index 7a7df44..eb12a01 100644 --- a/ui/views/home_page.py +++ b/ui/views/home_page.py @@ -1,7 +1,7 @@ from nicegui import ui from config.config import load_config from ui.core.state import app_state -from ui.core.task_runner import run_task, select_folder +from ui.core.task_runner import run_task # 导入业务函数 from utils.generate_utils import ( @@ -12,7 +12,7 @@ from utils.generate_utils import ( generate_zodiac, generate_signature, ) -from utils.file_utils import initialize_project, open_folder +from utils.file_utils import open_folder config = load_config("config.toml") @@ -65,6 +65,7 @@ def create_home_page(): ui.button(text, on_click=lambda: run_task(func)).props( f"outline" ).classes("w-full") + # 特殊处理带参数的 async def run_convert(): await run_task(batch_convert_folder, config.get("output_folder")) @@ -72,7 +73,7 @@ def create_home_page(): func_btn("📁 生成图片路径", generate_template) func_btn("🤖 生成评语 (AI)", generate_comment_all) func_btn("📊 生成报告 (PPT)", generate_report) - func_btn("📑 格式转换 (PDF)", run_convert).props("outline") + func_btn("📑 格式转换 (PDF)", run_convert) func_btn("🐂 生肖转化 (生日)", generate_zodiac) func_btn("💴 园长一键签名", generate_signature) @@ -95,13 +96,17 @@ def create_home_page(): "📤 打开数据文件夹", on_click=lambda: open_folder(config.get("data_folder")), ).props(f"outline") + ui.button( + "🔍 查看数据", + on_click=lambda: ui.navigate.to("/data"), + ).props(f"outline") ui.button("⛔ 停止", on_click=stop_now).props("color=negative").classes( "flex-1" ) # === 日志区 === with ui.card().classes("func-card card-logging"): - with ui.expansion("📝 系统实时日志", value=True).classes( + with ui.expansion("📝 系统实时日志", value=False).classes( "w-full bg-white shadow-sm rounded" ): app_state.log_element = ui.log(max_lines=200).classes(