2025-12-29 02:25:51 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""
|
|
|
|
|
|
码头作业日志管理工具 - GUI 界面
|
|
|
|
|
|
"""
|
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
|
from tkinter import ttk, messagebox, scrolledtext
|
|
|
|
|
|
import threading
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
# 添加项目根目录到 Python 路径
|
|
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
|
|
|
|
|
|
|
|
from src.confluence import ConfluenceClient
|
|
|
|
|
|
from src.extractor import HTMLTextExtractor
|
|
|
|
|
|
from src.parser import HandoverLogParser
|
|
|
|
|
|
from src.database import DailyLogsDatabase
|
|
|
|
|
|
from src.report import DailyReportGenerator
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-29 02:41:08 +08:00
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-29 02:25:51 +08:00
|
|
|
|
class OrbitInGUI:
|
|
|
|
|
|
"""码头作业日志管理工具 GUI"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, root):
|
|
|
|
|
|
self.root = root
|
|
|
|
|
|
self.root.title("码头作业日志管理工具 - OrbitIn")
|
2025-12-29 02:41:08 +08:00
|
|
|
|
self.root.geometry("900x650")
|
2025-12-29 02:25:51 +08:00
|
|
|
|
self.root.resizable(True, True)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置样式
|
|
|
|
|
|
style = ttk.Style()
|
|
|
|
|
|
style.theme_use('clam')
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主框架
|
|
|
|
|
|
main_frame = ttk.Frame(root, padding="10")
|
|
|
|
|
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 左侧控制面板
|
2025-12-29 02:41:08 +08:00
|
|
|
|
left_frame = ttk.LabelFrame(main_frame, text="操作面板", padding="10")
|
2025-12-29 02:25:51 +08:00
|
|
|
|
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
|
|
|
|
|
|
|
2025-12-29 02:41:08 +08:00
|
|
|
|
# 右侧主区域
|
2025-12-29 02:25:51 +08:00
|
|
|
|
right_frame = ttk.Frame(main_frame)
|
|
|
|
|
|
right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
# === 左侧控制面板 ===
|
|
|
|
|
|
|
|
|
|
|
|
# 获取数据按钮
|
|
|
|
|
|
btn_fetch = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="📥 获取并处理数据",
|
|
|
|
|
|
command=self.fetch_data,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_fetch.pack(pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
btn_fetch_debug = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="📥 获取 (Debug模式)",
|
|
|
|
|
|
command=self.fetch_debug,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_fetch_debug.pack(pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
# 分隔线
|
|
|
|
|
|
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
|
|
|
|
|
|
|
|
# 生成日报
|
|
|
|
|
|
ttk.Label(left_frame, text="生成日报:").pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
|
|
|
|
|
|
|
date_frame = ttk.Frame(left_frame)
|
|
|
|
|
|
date_frame.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
self.date_var = tk.StringVar(value=datetime.now().strftime('%Y-%m-%d'))
|
|
|
|
|
|
date_entry = ttk.Entry(date_frame, textvariable=self.date_var, width=12)
|
|
|
|
|
|
date_entry.pack(side=tk.LEFT)
|
|
|
|
|
|
|
|
|
|
|
|
btn_report = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="📊 生成日报",
|
|
|
|
|
|
command=self.generate_report,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_report.pack(pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
btn_report_today = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="📊 今日日报",
|
|
|
|
|
|
command=self.generate_today_report,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_report_today.pack(pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
# 分隔线
|
|
|
|
|
|
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
|
|
|
|
|
|
|
|
# 未统计数据
|
|
|
|
|
|
ttk.Label(left_frame, text="添加未统计数据:").pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
|
|
|
|
|
|
|
unaccounted_frame = ttk.Frame(left_frame)
|
|
|
|
|
|
unaccounted_frame.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(unaccounted_frame, text="月份:").pack(side=tk.LEFT)
|
|
|
|
|
|
self.month_var = tk.StringVar(value=datetime.now().strftime('%Y-%m'))
|
|
|
|
|
|
month_entry = ttk.Entry(unaccounted_frame, textvariable=self.month_var, width=8)
|
|
|
|
|
|
month_entry.pack(side=tk.LEFT, padx=(5, 10))
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(unaccounted_frame, text="TEU:").pack(side=tk.LEFT)
|
|
|
|
|
|
self.teu_var = tk.StringVar()
|
|
|
|
|
|
teu_entry = ttk.Entry(unaccounted_frame, textvariable=self.teu_var, width=8)
|
|
|
|
|
|
teu_entry.pack(side=tk.LEFT, padx=(5, 0))
|
|
|
|
|
|
|
|
|
|
|
|
btn_unaccounted = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="➕ 添加",
|
|
|
|
|
|
command=self.add_unaccounted,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_unaccounted.pack(pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
# 分隔线
|
|
|
|
|
|
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
|
|
|
|
|
|
|
|
# 数据库统计
|
|
|
|
|
|
btn_stats = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="📈 数据库统计",
|
|
|
|
|
|
command=self.show_stats,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_stats.pack(pady=5)
|
|
|
|
|
|
|
|
|
|
|
|
# 清空输出按钮
|
|
|
|
|
|
btn_clear = ttk.Button(
|
|
|
|
|
|
left_frame,
|
|
|
|
|
|
text="🗑️ 清空输出",
|
|
|
|
|
|
command=self.clear_output,
|
|
|
|
|
|
width=20
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_clear.pack(pady=5)
|
|
|
|
|
|
|
2025-12-29 02:41:08 +08:00
|
|
|
|
# === 右侧主区域 ===
|
2025-12-29 02:25:51 +08:00
|
|
|
|
|
|
|
|
|
|
# 状态标签
|
|
|
|
|
|
self.status_var = tk.StringVar(value="就绪")
|
|
|
|
|
|
status_label = ttk.Label(right_frame, textvariable=self.status_var)
|
|
|
|
|
|
status_label.pack(anchor=tk.W)
|
|
|
|
|
|
|
2025-12-29 02:41:08 +08:00
|
|
|
|
# 日报显示框架
|
|
|
|
|
|
self.report_frame = ReportFrame(right_frame)
|
|
|
|
|
|
self.report_frame.pack(fill=tk.X, pady=(5, 10))
|
|
|
|
|
|
|
2025-12-29 02:25:51 +08:00
|
|
|
|
# 输出文本框
|
2025-12-29 02:41:08 +08:00
|
|
|
|
ttk.Label(right_frame, text="日志输出:").pack(anchor=tk.W)
|
2025-12-29 02:25:51 +08:00
|
|
|
|
self.output_text = scrolledtext.ScrolledText(
|
|
|
|
|
|
right_frame,
|
|
|
|
|
|
wrap=tk.WORD,
|
2025-12-29 02:41:08 +08:00
|
|
|
|
font=('Consolas', 9),
|
|
|
|
|
|
bg='#f5f5f5',
|
|
|
|
|
|
height=10
|
2025-12-29 02:25:51 +08:00
|
|
|
|
)
|
|
|
|
|
|
self.output_text.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
|
|
|
|
|
|
|
|
|
|
|
|
# 绑定快捷键
|
|
|
|
|
|
self.root.bind('<Control-Return>', lambda e: self.fetch_data())
|
|
|
|
|
|
|
|
|
|
|
|
# 初始消息
|
|
|
|
|
|
self.log_message("码头作业日志管理工具 - OrbitIn")
|
|
|
|
|
|
self.log_message("=" * 50)
|
|
|
|
|
|
self.log_message("按 Ctrl+Enter 快速获取数据")
|
2025-12-29 02:41:08 +08:00
|
|
|
|
|
|
|
|
|
|
# 默认显示今日日报
|
|
|
|
|
|
self.generate_today_report()
|
2025-12-29 02:25:51 +08:00
|
|
|
|
|
|
|
|
|
|
def log_message(self, message, is_error=False):
|
|
|
|
|
|
"""输出日志消息"""
|
|
|
|
|
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
|
|
|
|
|
prefix = "❌" if is_error else "📝"
|
|
|
|
|
|
self.output_text.insert(tk.END, f"[{timestamp}] {prefix} {message}\n")
|
|
|
|
|
|
self.output_text.see(tk.END)
|
|
|
|
|
|
self.root.update()
|
|
|
|
|
|
|
|
|
|
|
|
def clear_output(self):
|
|
|
|
|
|
"""清空输出"""
|
|
|
|
|
|
self.output_text.delete(1.0, tk.END)
|
|
|
|
|
|
self.log_message("输出已清空")
|
|
|
|
|
|
|
|
|
|
|
|
def set_status(self, status):
|
|
|
|
|
|
"""设置状态"""
|
|
|
|
|
|
self.status_var.set(status)
|
|
|
|
|
|
self.root.update()
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_data(self):
|
|
|
|
|
|
"""获取并处理数据"""
|
|
|
|
|
|
self.set_status("正在获取数据...")
|
|
|
|
|
|
self.log_message("开始获取数据...")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 加载配置
|
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
|
|
|
|
|
|
base_url = os.getenv('CONFLUENCE_BASE_URL')
|
|
|
|
|
|
token = os.getenv('CONFLUENCE_TOKEN')
|
|
|
|
|
|
content_id = os.getenv('CONFLUENCE_CONTENT_ID')
|
|
|
|
|
|
|
|
|
|
|
|
if not base_url or not token or not content_id:
|
|
|
|
|
|
self.log_message("错误: 未配置 Confluence 信息,请检查 .env 文件", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取 HTML
|
|
|
|
|
|
self.log_message("正在从 Confluence 获取 HTML...")
|
|
|
|
|
|
client = ConfluenceClient(base_url, token)
|
|
|
|
|
|
html = client.get_html(content_id)
|
|
|
|
|
|
|
|
|
|
|
|
if not html:
|
|
|
|
|
|
self.log_message("错误: 未获取到 HTML 内容", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.log_message(f"获取成功,共 {len(html)} 字符")
|
|
|
|
|
|
|
|
|
|
|
|
# 提取文本
|
|
|
|
|
|
self.log_message("正在提取布局文本...")
|
|
|
|
|
|
extractor = HTMLTextExtractor()
|
|
|
|
|
|
layout_text = extractor.extract(html)
|
|
|
|
|
|
self.log_message(f"提取完成,共 {len(layout_text)} 字符")
|
|
|
|
|
|
|
|
|
|
|
|
# 解析数据
|
|
|
|
|
|
self.log_message("正在解析日志数据...")
|
|
|
|
|
|
parser = HandoverLogParser()
|
|
|
|
|
|
logs = parser.parse(layout_text)
|
|
|
|
|
|
self.log_message(f"解析到 {len(logs)} 条记录")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到数据库
|
|
|
|
|
|
if logs:
|
|
|
|
|
|
self.log_message("正在保存到数据库...")
|
|
|
|
|
|
db = DailyLogsDatabase()
|
|
|
|
|
|
count = db.insert_many([log.to_dict() for log in logs])
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
self.log_message(f"已保存 {count} 条记录")
|
|
|
|
|
|
|
|
|
|
|
|
# 显示统计
|
|
|
|
|
|
db = DailyLogsDatabase()
|
|
|
|
|
|
stats = db.get_stats()
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
self.log_message(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船")
|
2025-12-29 02:41:08 +08:00
|
|
|
|
|
|
|
|
|
|
# 刷新日报显示
|
|
|
|
|
|
self.generate_today_report()
|
2025-12-29 02:25:51 +08:00
|
|
|
|
else:
|
|
|
|
|
|
self.log_message("未解析到任何记录")
|
|
|
|
|
|
|
|
|
|
|
|
self.set_status("完成")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_message(f"错误: {e}", is_error=True)
|
|
|
|
|
|
self.set_status("错误")
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_debug(self):
|
|
|
|
|
|
"""Debug模式获取数据"""
|
|
|
|
|
|
self.set_status("正在获取 Debug 数据...")
|
|
|
|
|
|
self.log_message("使用本地 layout_output.txt 进行 Debug...")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 检查本地文件
|
|
|
|
|
|
if os.path.exists('layout_output.txt'):
|
|
|
|
|
|
filepath = 'layout_output.txt'
|
|
|
|
|
|
elif os.path.exists('debug/layout_output.txt'):
|
|
|
|
|
|
filepath = 'debug/layout_output.txt'
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.log_message("错误: 未找到 layout_output.txt 文件", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.log_message(f"使用文件: {filepath}")
|
|
|
|
|
|
|
|
|
|
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
text = f.read()
|
|
|
|
|
|
|
|
|
|
|
|
self.log_message(f"读取完成,共 {len(text)} 字符")
|
|
|
|
|
|
|
|
|
|
|
|
# 解析数据
|
|
|
|
|
|
self.log_message("正在解析日志数据...")
|
|
|
|
|
|
parser = HandoverLogParser()
|
|
|
|
|
|
logs = parser.parse(text)
|
|
|
|
|
|
self.log_message(f"解析到 {len(logs)} 条记录")
|
|
|
|
|
|
|
|
|
|
|
|
if logs:
|
|
|
|
|
|
self.log_message("正在保存到数据库...")
|
|
|
|
|
|
db = DailyLogsDatabase()
|
|
|
|
|
|
count = db.insert_many([log.to_dict() for log in logs])
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
self.log_message(f"已保存 {count} 条记录")
|
2025-12-29 02:41:08 +08:00
|
|
|
|
|
|
|
|
|
|
# 刷新日报显示
|
|
|
|
|
|
self.generate_today_report()
|
2025-12-29 02:25:51 +08:00
|
|
|
|
|
|
|
|
|
|
self.set_status("完成")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_message(f"错误: {e}", is_error=True)
|
|
|
|
|
|
self.set_status("错误")
|
|
|
|
|
|
|
|
|
|
|
|
def generate_report(self):
|
|
|
|
|
|
"""生成指定日期的日报"""
|
|
|
|
|
|
date = self.date_var.get().strip()
|
|
|
|
|
|
|
|
|
|
|
|
if not date:
|
|
|
|
|
|
self.log_message("错误: 请输入日期", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
datetime.strptime(date, '%Y-%m-%d')
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
self.log_message("错误: 日期格式无效,请使用 YYYY-MM-DD", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.set_status("正在生成日报...")
|
|
|
|
|
|
self.log_message(f"生成 {date} 的日报...")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
g = DailyReportGenerator()
|
|
|
|
|
|
report = g.generate_report(date)
|
|
|
|
|
|
g.close()
|
|
|
|
|
|
|
2025-12-29 02:41:08 +08:00
|
|
|
|
# 在日志中显示原始内容
|
2025-12-29 02:25:51 +08:00
|
|
|
|
self.log_message("")
|
|
|
|
|
|
self.log_message("=" * 40)
|
|
|
|
|
|
self.log_message(report)
|
|
|
|
|
|
self.log_message("=" * 40)
|
|
|
|
|
|
|
2025-12-29 02:41:08 +08:00
|
|
|
|
# 在可视化区域显示
|
|
|
|
|
|
self.report_frame.display_report(report, date)
|
|
|
|
|
|
|
2025-12-29 02:25:51 +08:00
|
|
|
|
self.set_status("完成")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_message(f"错误: {e}", is_error=True)
|
|
|
|
|
|
self.set_status("错误")
|
|
|
|
|
|
|
|
|
|
|
|
def generate_today_report(self):
|
|
|
|
|
|
"""生成今日日报"""
|
|
|
|
|
|
today = datetime.now().strftime('%Y-%m-%d')
|
|
|
|
|
|
self.date_var.set(today)
|
|
|
|
|
|
self.generate_report()
|
|
|
|
|
|
|
|
|
|
|
|
def add_unaccounted(self):
|
|
|
|
|
|
"""添加未统计数据"""
|
|
|
|
|
|
year_month = self.month_var.get().strip()
|
|
|
|
|
|
teu = self.teu_var.get().strip()
|
|
|
|
|
|
|
|
|
|
|
|
if not year_month or not teu:
|
|
|
|
|
|
self.log_message("错误: 请输入月份和 TEU", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
teu = int(teu)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
self.log_message("错误: TEU 必须是数字", is_error=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.set_status("正在添加...")
|
|
|
|
|
|
self.log_message(f"添加 {year_month} 月未统计数据: {teu}TEU")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
db = DailyLogsDatabase()
|
|
|
|
|
|
result = db.insert_unaccounted(year_month, teu, '')
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
|
self.log_message("添加成功!")
|
2025-12-29 02:41:08 +08:00
|
|
|
|
# 刷新日报显示
|
|
|
|
|
|
self.generate_today_report()
|
2025-12-29 02:25:51 +08:00
|
|
|
|
else:
|
|
|
|
|
|
self.log_message("添加失败!", is_error=True)
|
|
|
|
|
|
|
|
|
|
|
|
self.set_status("完成")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_message(f"错误: {e}", is_error=True)
|
|
|
|
|
|
self.set_status("错误")
|
|
|
|
|
|
|
|
|
|
|
|
def show_stats(self):
|
|
|
|
|
|
"""显示数据库统计"""
|
|
|
|
|
|
self.set_status("正在统计...")
|
|
|
|
|
|
self.log_message("数据库统计信息:")
|
|
|
|
|
|
self.log_message("-" * 30)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
db = DailyLogsDatabase()
|
|
|
|
|
|
stats = db.get_stats()
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
|
|
|
|
|
|
self.log_message(f"总记录数: {stats['total']}")
|
|
|
|
|
|
self.log_message(f"船次数量: {len(stats['ships'])}")
|
|
|
|
|
|
self.log_message(f"日期范围: {stats['date_range']['start']} ~ {stats['date_range']['end']}")
|
|
|
|
|
|
|
|
|
|
|
|
if stats['ships']:
|
|
|
|
|
|
self.log_message("")
|
|
|
|
|
|
self.log_message("船次列表:")
|
|
|
|
|
|
for ship in sorted(stats['ships']):
|
|
|
|
|
|
self.log_message(f" - {ship}")
|
|
|
|
|
|
|
|
|
|
|
|
self.set_status("完成")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_message(f"错误: {e}", is_error=True)
|
|
|
|
|
|
self.set_status("错误")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""主函数"""
|
|
|
|
|
|
root = tk.Tk()
|
|
|
|
|
|
app = OrbitInGUI(root)
|
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
main()
|