Improve GUI: add visual report display with formatting

This commit is contained in:
2025-12-29 02:41:08 +08:00
parent 8fc58a17b1
commit 116974ed36

View File

@@ -19,13 +19,136 @@ from src.database import DailyLogsDatabase
from src.report import DailyReportGenerator from src.report import DailyReportGenerator
class ReportFrame(ttk.LabelFrame):
"""日报显示框架"""
def __init__(self, parent):
super().__init__(parent, text="日报预览", padding="10")
self.parent = parent
self._create_widgets()
def _create_widgets(self):
"""创建组件"""
# 日期标题
self.date_label = ttk.Label(self, text="--月--日", font=('SimHei', 14, 'bold'))
self.date_label.pack(pady=(0, 10))
# 船次信息表格
self.ships_frame = ttk.Frame(self)
self.ships_frame.pack(fill=tk.X, pady=5)
# 统计信息
self.stats_frame = ttk.Frame(self)
self.stats_frame.pack(fill=tk.X, pady=10)
# 人员信息
self.personnel_frame = ttk.Frame(self)
self.personnel_frame.pack(fill=tk.X, pady=10)
def display_report(self, report_text: str, date: str):
"""显示日报"""
# 解析日报内容
lines = report_text.strip().split('\n')
# 更新日期
self.date_label.config(text=f"{date[5:7]}{date[8:10]}")
# 清空旧数据
for widget in self.ships_frame.winfo_children():
widget.destroy()
for widget in self.stats_frame.winfo_children():
widget.destroy()
for widget in self.personnel_frame.winfo_children():
widget.destroy()
# 解析并显示各部分
current_section = None
ships_data = []
stats_data = {}
for line in lines:
line = line.strip()
if not line:
continue
if line.startswith('日期:'):
continue
elif line.startswith('船名:'):
current_section = 'ships'
ships_data.append({'name': line.replace('船名:', '').strip(), 'teu': None})
elif line.startswith('作业量:'):
if ships_data:
ships_data[-1]['teu'] = line.replace('作业量:', '').replace('TEU', '').strip()
elif line.startswith('当日实际作业量:'):
stats_data['daily_total'] = line.replace('当日实际作业量:', '').replace('TEU', '').strip()
elif line.startswith('当月计划作业量:'):
stats_data['monthly_plan'] = line.replace('当月计划作业量:', '').replace('TEU', '').replace(' (用天数*300TEU)', '').strip()
elif line.startswith('当月实际作业量:'):
stats_data['monthly_actual'] = line.replace('当月实际作业量:', '').replace('TEU', '').strip()
elif line.startswith('当月完成比例:'):
stats_data['completion'] = line.replace('当月完成比例:', '').replace('%', '').strip()
elif '白班人员' in line:
stats_data['day_shift'] = line.split('')[1].strip() if '' in line else ''
elif '夜班人员' in line:
stats_data['night_shift'] = line.split('')[1].strip() if '' in line else ''
elif '值班手机' in line:
stats_data['phone'] = line.split('')[1].strip() if '' in line else ''
# 显示船次信息
if ships_data:
ttk.Label(self.ships_frame, text="船次信息", font=('', 10, 'bold')).pack(anchor=tk.W)
for ship in ships_data:
ship_frame = ttk.Frame(self.ships_frame)
ship_frame.pack(fill=tk.X, pady=2)
ttk.Label(ship_frame, text=f"🚢 {ship['name']}", foreground='#1976D2').pack(side=tk.LEFT)
ttk.Label(ship_frame, text=f"{ship['teu']} TEU", font=('', 10, 'bold'), foreground='#388E3C').pack(side=tk.RIGHT)
# 显示统计信息
if stats_data:
ttk.Label(self.stats_frame, text="统计信息", font=('', 10, 'bold')).pack(anchor=tk.W)
stats_grid = ttk.Frame(self.stats_frame)
stats_grid.pack(fill=tk.X, pady=5)
# 第一行
ttk.Label(stats_grid, text="当日作业:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
ttk.Label(stats_grid, text=f"{stats_data.get('daily_total', '0')} TEU", foreground='#388E3C', font=('', 10, 'bold')).grid(row=0, column=1, sticky=tk.W)
ttk.Label(stats_grid, text="月计划:").grid(row=0, column=2, sticky=tk.W, padx=(20, 10))
ttk.Label(stats_grid, text=f"{stats_data.get('monthly_plan', '0')} TEU").grid(row=0, column=3, sticky=tk.W)
# 第二行
ttk.Label(stats_grid, text="月实际:").grid(row=1, column=0, sticky=tk.W, padx=(0, 10), pady=(5, 0))
ttk.Label(stats_grid, text=f"{stats_data.get('monthly_actual', '0')} TEU", foreground='#388E3C', font=('', 10, 'bold')).grid(row=1, column=1, sticky=tk.W, pady=(5, 0))
ttk.Label(stats_grid, text="完成率:").grid(row=1, column=2, sticky=tk.W, padx=(20, 10), pady=(5, 0))
completion = float(stats_data.get('completion', 0))
color = '#388E3C' if completion >= 100 else '#FF9800' if completion >= 80 else '#F44336'
ttk.Label(stats_grid, text=f"{stats_data.get('completion', '0')}%", foreground=color, font=('', 10, 'bold')).grid(row=1, column=3, sticky=tk.W, pady=(5, 0))
# 显示人员信息
ttk.Label(self.personnel_frame, text="班次人员", font=('', 10, 'bold')).pack(anchor=tk.W)
personnel_grid = ttk.Frame(self.personnel_frame)
personnel_grid.pack(fill=tk.X, pady=5)
ttk.Label(personnel_grid, text="☀️ 白班:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
ttk.Label(personnel_grid, text=stats_data.get('day_shift', '-')).grid(row=0, column=1, sticky=tk.W)
ttk.Label(personnel_grid, text="🌙 夜班:").grid(row=1, column=0, sticky=tk.W, padx=(0, 10), pady=(5, 0))
ttk.Label(personnel_grid, text=stats_data.get('night_shift', '-')).grid(row=1, column=1, sticky=tk.W, pady=(5, 0))
ttk.Label(personnel_grid, text="📞 值班:").grid(row=2, column=0, sticky=tk.W, padx=(0, 10), pady=(5, 0))
ttk.Label(personnel_grid, text=stats_data.get('phone', '-'), foreground='#1976D2').grid(row=2, column=1, sticky=tk.W, pady=(5, 0))
class OrbitInGUI: class OrbitInGUI:
"""码头作业日志管理工具 GUI""" """码头作业日志管理工具 GUI"""
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
self.root.title("码头作业日志管理工具 - OrbitIn") self.root.title("码头作业日志管理工具 - OrbitIn")
self.root.geometry("800x600") self.root.geometry("900x650")
self.root.resizable(True, True) self.root.resizable(True, True)
# 设置样式 # 设置样式
@@ -37,10 +160,10 @@ class OrbitInGUI:
main_frame.pack(fill=tk.BOTH, expand=True) main_frame.pack(fill=tk.BOTH, expand=True)
# 左侧控制面板 # 左侧控制面板
left_frame = ttk.Frame(main_frame) left_frame = ttk.LabelFrame(main_frame, text="操作面板", padding="10")
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
# 右侧输出区域 # 右侧区域
right_frame = ttk.Frame(main_frame) right_frame = ttk.Frame(main_frame)
right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
@@ -140,19 +263,25 @@ class OrbitInGUI:
) )
btn_clear.pack(pady=5) btn_clear.pack(pady=5)
# === 右侧输出区域 === # === 右侧区域 ===
# 状态标签 # 状态标签
self.status_var = tk.StringVar(value="就绪") self.status_var = tk.StringVar(value="就绪")
status_label = ttk.Label(right_frame, textvariable=self.status_var) status_label = ttk.Label(right_frame, textvariable=self.status_var)
status_label.pack(anchor=tk.W) status_label.pack(anchor=tk.W)
# 日报显示框架
self.report_frame = ReportFrame(right_frame)
self.report_frame.pack(fill=tk.X, pady=(5, 10))
# 输出文本框 # 输出文本框
ttk.Label(right_frame, text="日志输出:").pack(anchor=tk.W)
self.output_text = scrolledtext.ScrolledText( self.output_text = scrolledtext.ScrolledText(
right_frame, right_frame,
wrap=tk.WORD, wrap=tk.WORD,
font=('Consolas', 10), font=('Consolas', 9),
bg='#f5f5f5' bg='#f5f5f5',
height=10
) )
self.output_text.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) self.output_text.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
@@ -162,10 +291,10 @@ class OrbitInGUI:
# 初始消息 # 初始消息
self.log_message("码头作业日志管理工具 - OrbitIn") self.log_message("码头作业日志管理工具 - OrbitIn")
self.log_message("=" * 50) self.log_message("=" * 50)
self.log_message("日期格式: YYYY-MM-DD")
self.log_message("月份格式: YYYY-MM")
self.log_message("")
self.log_message("按 Ctrl+Enter 快速获取数据") self.log_message("按 Ctrl+Enter 快速获取数据")
# 默认显示今日日报
self.generate_today_report()
def log_message(self, message, is_error=False): def log_message(self, message, is_error=False):
"""输出日志消息""" """输出日志消息"""
@@ -185,20 +314,6 @@ class OrbitInGUI:
self.status_var.set(status) self.status_var.set(status)
self.root.update() self.root.update()
def run_in_thread(self, func, *args, **kwargs):
"""在后台线程中运行函数"""
def worker():
try:
func(*args, **kwargs)
except Exception as e:
self.root.after(0, lambda: self.log_message(str(e), is_error=True))
self.root.after(0, lambda: self.set_status("错误"))
finally:
self.root.after(0, lambda: self.set_status("就绪"))
thread = threading.Thread(target=worker, daemon=True)
thread.start()
def fetch_data(self): def fetch_data(self):
"""获取并处理数据""" """获取并处理数据"""
self.set_status("正在获取数据...") self.set_status("正在获取数据...")
@@ -253,6 +368,9 @@ class OrbitInGUI:
stats = db.get_stats() stats = db.get_stats()
db.close() db.close()
self.log_message(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船") self.log_message(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船")
# 刷新日报显示
self.generate_today_report()
else: else:
self.log_message("未解析到任何记录") self.log_message("未解析到任何记录")
@@ -296,6 +414,9 @@ class OrbitInGUI:
count = db.insert_many([log.to_dict() for log in logs]) count = db.insert_many([log.to_dict() for log in logs])
db.close() db.close()
self.log_message(f"已保存 {count} 条记录") self.log_message(f"已保存 {count} 条记录")
# 刷新日报显示
self.generate_today_report()
self.set_status("完成") self.set_status("完成")
@@ -325,11 +446,15 @@ class OrbitInGUI:
report = g.generate_report(date) report = g.generate_report(date)
g.close() g.close()
# 在日志中显示原始内容
self.log_message("") self.log_message("")
self.log_message("=" * 40) self.log_message("=" * 40)
self.log_message(report) self.log_message(report)
self.log_message("=" * 40) self.log_message("=" * 40)
# 在可视化区域显示
self.report_frame.display_report(report, date)
self.set_status("完成") self.set_status("完成")
except Exception as e: except Exception as e:
@@ -367,6 +492,8 @@ class OrbitInGUI:
if result: if result:
self.log_message("添加成功!") self.log_message("添加成功!")
# 刷新日报显示
self.generate_today_report()
else: else:
self.log_message("添加失败!", is_error=True) self.log_message("添加失败!", is_error=True)