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(