fix:实现数据核对页面
This commit is contained in:
20
.vscode/settings.json
vendored
Normal file
20
.vscode/settings.json
vendored
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,14 +7,16 @@ from nicegui import ui, app, run, native
|
|||||||
from screeninfo import get_monitors
|
from screeninfo import get_monitors
|
||||||
|
|
||||||
from config.config import load_config
|
from config.config import load_config
|
||||||
|
|
||||||
# 导入我们的模块
|
# 导入我们的模块
|
||||||
from ui.core.logger import setup_logger
|
from ui.core.logger import setup_logger
|
||||||
from ui.views.config_page import create_config_page
|
from ui.views.config_page import create_config_page
|
||||||
from ui.views.home_page import create_home_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
|
from utils.font_utils import install_fonts_from_directory
|
||||||
|
|
||||||
sys.stdout.reconfigure(encoding='utf-8')
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding='utf-8')
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
# 1. 初始化配置
|
# 1. 初始化配置
|
||||||
config = load_config("config.toml")
|
config = load_config("config.toml")
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ def get_path(relative_path):
|
|||||||
获取资源的绝对路径。
|
获取资源的绝对路径。
|
||||||
兼容:开发环境(直接运行) 和 生产环境(打包成exe后解压的临时目录)
|
兼容:开发环境(直接运行) 和 生产环境(打包成exe后解压的临时目录)
|
||||||
"""
|
"""
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, "_MEIPASS"):
|
||||||
base_path = sys._MEIPASS
|
base_path = sys._MEIPASS
|
||||||
else:
|
else:
|
||||||
# 开发环境当前目录
|
# 开发环境当前目录
|
||||||
@@ -74,20 +76,25 @@ def calculate_window_size():
|
|||||||
# 1. 挂载静态资源 (CSS/图片)
|
# 1. 挂载静态资源 (CSS/图片)
|
||||||
# 注意:这里使用 get_path 确保打包后能找到
|
# 注意:这里使用 get_path 确保打包后能找到
|
||||||
static_dir = get_path(os.path.join("ui", "assets"))
|
static_dir = get_path(os.path.join("ui", "assets"))
|
||||||
app.add_static_files('/assets', static_dir)
|
app.add_static_files("/assets", static_dir)
|
||||||
|
|
||||||
|
|
||||||
# 3. 页面路由
|
# 3. 页面路由
|
||||||
@ui.page('/')
|
@ui.page("/")
|
||||||
def index_page():
|
def index_page():
|
||||||
create_home_page()
|
create_home_page()
|
||||||
|
|
||||||
|
|
||||||
@ui.page('/config')
|
@ui.page("/config")
|
||||||
def config_page():
|
def config_page():
|
||||||
create_config_page()
|
create_config_page()
|
||||||
|
|
||||||
|
|
||||||
|
@ui.page("/data")
|
||||||
|
def data_page():
|
||||||
|
create_data_page()
|
||||||
|
|
||||||
|
|
||||||
# 4. 启动时钩子
|
# 4. 启动时钩子
|
||||||
async def startup_check():
|
async def startup_check():
|
||||||
try:
|
try:
|
||||||
@@ -109,5 +116,5 @@ if __name__ in {"__main__", "__mp_main__"}:
|
|||||||
native=True,
|
native=True,
|
||||||
window_size=calculated_size,
|
window_size=calculated_size,
|
||||||
port=native.find_open_port(), # 自动寻找端口
|
port=native.find_open_port(), # 自动寻找端口
|
||||||
reload=False
|
reload=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
/* 全局字体 */
|
/* 全局字体 */
|
||||||
body {
|
body {
|
||||||
font-family: "微软雅黑", "Microsoft YaHei", sans-serif;
|
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
||||||
background-color: #f0f4f8;
|
background-color: #f0f4f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 标题栏 */
|
/* 标题栏 */
|
||||||
.app-header {
|
.app-header {
|
||||||
background-color: #2E8B57; /* SeaGreen */
|
background-color: #2e8b57; /* SeaGreen */
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +48,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 绿色标题 */
|
/* 绿色标题 */
|
||||||
.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 */
|
/* assets/style.css */
|
||||||
|
|
||||||
@@ -118,7 +123,8 @@ body {
|
|||||||
height: 0px !important;
|
height: 0px !important;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
#nicegui-content, #q-app {
|
#nicegui-content,
|
||||||
|
#q-app {
|
||||||
/* 确保容器内容可以滚动,但滚动条被隐藏 */
|
/* 确保容器内容可以滚动,但滚动条被隐藏 */
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
@@ -128,3 +134,49 @@ body {
|
|||||||
/* IE/Edge 隐藏滚动条 */
|
/* IE/Edge 隐藏滚动条 */
|
||||||
-ms-overflow-style: none;
|
-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;
|
||||||
|
}
|
||||||
|
|||||||
193
ui/views/data_page.py
Normal file
193
ui/views/data_page.py
Normal file
@@ -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('<link href="/assets/style.css" rel="stylesheet" />')
|
||||||
|
ui.add_head_html(
|
||||||
|
"""
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
}
|
||||||
|
/* 主体内容区:确保高度撑满并独立滚动 */
|
||||||
|
.content-area {
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
/* 详情弹窗增强 */
|
||||||
|
.detail-row {
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
padding: 10px 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.detail-row:hover { background-color: #f8fafc; }
|
||||||
|
.field-label { font-weight: 600; color: #64748b; width: 110px; flex-shrink: 0; font-size: 0.875rem; }
|
||||||
|
.field-value { color: #1e293b; font-size: 0.935rem; line-height: 1.5; }
|
||||||
|
|
||||||
|
/* 数据卡片微调 */
|
||||||
|
.data-card {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from nicegui import ui
|
from nicegui import ui
|
||||||
from config.config import load_config
|
from config.config import load_config
|
||||||
from ui.core.state import app_state
|
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 (
|
from utils.generate_utils import (
|
||||||
@@ -12,7 +12,7 @@ from utils.generate_utils import (
|
|||||||
generate_zodiac,
|
generate_zodiac,
|
||||||
generate_signature,
|
generate_signature,
|
||||||
)
|
)
|
||||||
from utils.file_utils import initialize_project, open_folder
|
from utils.file_utils import open_folder
|
||||||
|
|
||||||
config = load_config("config.toml")
|
config = load_config("config.toml")
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ def create_home_page():
|
|||||||
ui.button(text, on_click=lambda: run_task(func)).props(
|
ui.button(text, on_click=lambda: run_task(func)).props(
|
||||||
f"outline"
|
f"outline"
|
||||||
).classes("w-full")
|
).classes("w-full")
|
||||||
|
|
||||||
# 特殊处理带参数的
|
# 特殊处理带参数的
|
||||||
async def run_convert():
|
async def run_convert():
|
||||||
await run_task(batch_convert_folder, config.get("output_folder"))
|
await run_task(batch_convert_folder, config.get("output_folder"))
|
||||||
@@ -72,7 +73,7 @@ def create_home_page():
|
|||||||
func_btn("📁 生成图片路径", generate_template)
|
func_btn("📁 生成图片路径", generate_template)
|
||||||
func_btn("🤖 生成评语 (AI)", generate_comment_all)
|
func_btn("🤖 生成评语 (AI)", generate_comment_all)
|
||||||
func_btn("📊 生成报告 (PPT)", generate_report)
|
func_btn("📊 生成报告 (PPT)", generate_report)
|
||||||
func_btn("📑 格式转换 (PDF)", run_convert).props("outline")
|
func_btn("📑 格式转换 (PDF)", run_convert)
|
||||||
func_btn("🐂 生肖转化 (生日)", generate_zodiac)
|
func_btn("🐂 生肖转化 (生日)", generate_zodiac)
|
||||||
func_btn("💴 园长一键签名", generate_signature)
|
func_btn("💴 园长一键签名", generate_signature)
|
||||||
|
|
||||||
@@ -95,13 +96,17 @@ def create_home_page():
|
|||||||
"📤 打开数据文件夹",
|
"📤 打开数据文件夹",
|
||||||
on_click=lambda: open_folder(config.get("data_folder")),
|
on_click=lambda: open_folder(config.get("data_folder")),
|
||||||
).props(f"outline")
|
).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(
|
ui.button("⛔ 停止", on_click=stop_now).props("color=negative").classes(
|
||||||
"flex-1"
|
"flex-1"
|
||||||
)
|
)
|
||||||
|
|
||||||
# === 日志区 ===
|
# === 日志区 ===
|
||||||
with ui.card().classes("func-card card-logging"):
|
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"
|
"w-full bg-white shadow-sm rounded"
|
||||||
):
|
):
|
||||||
app_state.log_element = ui.log(max_lines=200).classes(
|
app_state.log_element = ui.log(max_lines=200).classes(
|
||||||
|
|||||||
Reference in New Issue
Block a user