feat: 初始化福州港日报管理系统

- 添加日报生成功能 (report_generator.py)
- 添加 GUI 界面 (daily_report_gui.py)
- 添加班次交接报告功能 (shift_report.py)
- 集成飞书 API 获取排班信息
- 集成 Metabase 查询作业数据
- 生成 AGENTS.md 文档
This commit is contained in:
2026-03-03 02:07:34 +08:00
commit 00d2218c6d
16 changed files with 3713 additions and 0 deletions

372
daily_report_gui.py Normal file
View File

@@ -0,0 +1,372 @@
#!/usr/bin/env python3
"""
日报展示和复制工具
基于tkinter的GUI应用用于展示日报信息和一键复制
"""
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
from datetime import datetime, timedelta
import sys
import os
import threading
# 添加项目根目录到路径
project_root = os.path.dirname(os.path.abspath(__file__))
if project_root not in sys.path:
sys.path.insert(0, project_root)
class DailyReportApp:
"""日报展示和复制应用"""
def __init__(self, root):
self.root = root
self.root.title("日报管理系统")
self.root.geometry("1400x900")
self.root.minsize(1200, 700)
# 设置默认字体大小
self.default_font_size = 14
self.title_font_size = 16
# 创建主布局
self.create_main_layout()
# 初始化数据
self.current_report_date = None
self.report_content = ""
self.is_generating = False
def create_main_layout(self):
"""创建主布局"""
# 确保能导入项目模块
project_root = os.path.dirname(os.path.abspath(__file__))
if project_root not in sys.path:
sys.path.insert(0, project_root)
# 主容器
main_container = tk.Frame(self.root, padx=10, pady=10)
main_container.pack(fill="both", expand=True)
# 配置权重
main_container.columnconfigure(1, weight=3) # 中间区域
main_container.columnconfigure(2, weight=1) # 右侧区域
main_container.rowconfigure(1, weight=1)
# 1. 顶部提示区域
self.create_notice_area(main_container)
# 2. 左侧日期选择区域
self.create_left_panel(main_container)
# 3. 中间日报展示区域
self.create_center_panel(main_container)
# 4. 右侧复制按钮区域
self.create_right_panel(main_container)
def create_notice_area(self, parent):
"""创建提示区域"""
notice_frame = tk.Frame(parent, bd=2, relief="solid", bg="#FFF3E0")
notice_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 10))
# 静态提示标签
self.notice_label = tk.Label(
notice_frame,
text="重要提示本程序需在8:00过后运行确保最后一条船的指令结束时间超过8点以保证日报数据完整性",
fg="#FF6B6B",
bg="#FFF3E0",
font=("", self.title_font_size, "bold"),
padx=10,
pady=10,
)
self.notice_label.pack(fill="x", expand=True)
def create_left_panel(self, parent):
"""创建左侧控制面板"""
left_frame = tk.LabelFrame(
parent,
text="日期选择",
padx=10,
pady=10,
font=("", self.title_font_size, "bold"),
)
left_frame.grid(row=1, column=0, sticky="nsew", padx=(0, 10))
# 昨日汇总按钮
self.yesterday_btn = tk.Button(
left_frame,
text="昨日汇总",
command=self.select_yesterday,
font=("", self.default_font_size),
padx=10,
pady=5,
bg="#4CAF50",
fg="white",
)
self.yesterday_btn.pack(fill="x", pady=(0, 10))
# 日期选择器
tk.Label(left_frame, text="选择日期:", font=("", self.default_font_size)).pack(
anchor="w", pady=(10, 5)
)
# 日期输入框
self.date_var = tk.StringVar()
self.date_entry = tk.Entry(
left_frame, textvariable=self.date_var, font=("", self.default_font_size)
)
self.date_entry.pack(fill="x", pady=(0, 5))
# 日期提示
tk.Label(
left_frame,
text="格式YYYY-MM-DD",
fg="gray",
font=("", self.default_font_size - 2),
).pack(anchor="w")
# 生成日报按钮
self.generate_btn = tk.Button(
left_frame,
text="生成日报",
command=self.generate_report_async,
font=("", self.default_font_size),
padx=10,
pady=5,
bg="#2196F3",
fg="white",
)
self.generate_btn.pack(fill="x", pady=(20, 0))
# 当前选中日期标签
self.selected_date_label = tk.Label(
left_frame,
text="当前选中:无",
fg="#2196F3",
font=("", self.default_font_size, "bold"),
)
self.selected_date_label.pack(anchor="w", pady=(20, 0))
def create_center_panel(self, parent):
"""创建中间日报展示区域"""
center_frame = tk.LabelFrame(
parent,
text="日报内容",
padx=10,
pady=10,
font=("", self.title_font_size, "bold"),
)
center_frame.grid(row=1, column=1, sticky="nsew", padx=(0, 10))
# 日报信息文本框
self.report_text = scrolledtext.ScrolledText(
center_frame,
wrap=tk.WORD,
padx=10,
pady=10,
height=30,
font=("", self.default_font_size),
)
self.report_text.pack(fill="both", expand=True)
# 默认提示文本
self.report_text.insert(
tk.END, '请点击左侧"昨日汇总"按钮,或选择日期后点击"生成日报"按钮...'
)
self.report_text.config(state=tk.DISABLED)
def create_right_panel(self, parent):
"""创建右侧控制面板"""
right_frame = tk.LabelFrame(
parent,
text="操作",
padx=10,
pady=10,
font=("", self.title_font_size, "bold"),
)
right_frame.grid(row=1, column=2, sticky="nsew")
# 复制按钮
self.copy_btn = tk.Button(
right_frame,
text="复制日报",
command=self.copy_report,
font=("", self.default_font_size),
padx=10,
pady=5,
bg="#4CAF50",
fg="white",
)
self.copy_btn.pack(fill="x", pady=(0, 20))
# 复制状态标签
self.copy_status = tk.Label(
right_frame, text="", font=("", self.default_font_size)
)
self.copy_status.pack(fill="x")
# 分隔线
tk.Frame(right_frame, height=2, bg="gray").pack(fill="x", pady=20)
# 清空按钮
self.clear_btn = tk.Button(
right_frame,
text="清空",
command=self.clear_report,
font=("", self.default_font_size),
padx=10,
pady=5,
)
self.clear_btn.pack(fill="x", pady=(0, 10))
# 退出按钮
self.exit_btn = tk.Button(
right_frame,
text="退出",
command=self.root.quit,
font=("", self.default_font_size),
padx=10,
pady=5,
)
self.exit_btn.pack(fill="x")
def select_yesterday(self):
"""选择昨天"""
yesterday = datetime.now() - timedelta(days=1)
self.date_var.set(yesterday.strftime("%Y-%m-%d"))
self.selected_date_label.config(
text=f"当前选中:昨天 ({yesterday.strftime('%Y-%m-%d')})"
)
self.generate_report_async()
def generate_report_async(self):
"""异步生成日报(避免界面卡死)"""
if self.is_generating:
return
self.is_generating = True
self.generate_btn.config(state=tk.DISABLED, text="生成中...")
# 在后台线程生成日报
thread = threading.Thread(target=self._generate_report_thread)
thread.daemon = True
thread.start()
def _generate_report_thread(self):
"""在后台线程中生成日报"""
try:
date_str = self.date_var.get().strip()
if not date_str:
self.root.after(0, lambda: self._on_generate_error("请先选择日期"))
return
try:
report_date = datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
self.root.after(
0,
lambda: self._on_generate_error(
"日期格式错误!请使用 YYYY-MM-DD 格式"
),
)
return
# 更新UI
self.root.after(
0,
lambda: self.selected_date_label.config(
text=f"当前选中:{report_date.strftime('%Y-%m-%d')}"
),
)
# 生成日报
try:
from report_generator import DailyReportGenerator
generator = DailyReportGenerator()
report = generator.generate_daily_report(report_date)
self.root.after(0, lambda: self._on_generate_success(report))
except Exception as e:
error_msg = f"生成日报时出错:{str(e)}\n\n请确保:\n1. 环境变量配置正确\n2. Metabase 和飞书服务可访问\n3. 在8:00过后运行程序"
self.root.after(0, lambda: self._on_generate_error(error_msg))
except Exception as e:
self.root.after(0, lambda: self._on_generate_error(str(e)))
def _on_generate_success(self, report):
"""生成成功回调"""
self.report_content = report
self.report_text.config(state=tk.NORMAL)
self.report_text.delete(1.0, tk.END)
self.report_text.insert(tk.END, report)
self.report_text.config(state=tk.DISABLED)
self._reset_generate_button()
def _on_generate_error(self, error_msg):
"""生成失败回调"""
self.report_content = error_msg
self.report_text.config(state=tk.NORMAL)
self.report_text.delete(1.0, tk.END)
self.report_text.insert(tk.END, error_msg)
self.report_text.config(state=tk.DISABLED)
self._reset_generate_button()
messagebox.showerror("错误", error_msg)
def _reset_generate_button(self):
"""重置生成按钮状态"""
self.is_generating = False
self.generate_btn.config(state=tk.NORMAL, text="生成日报")
def copy_report(self):
"""复制日报到剪贴板"""
if (
not self.report_content
or self.report_content
== '请点击左侧"昨日汇总"按钮,或选择日期后点击"生成日报"按钮...'
):
messagebox.showwarning("提示", "请先生成日报!")
return
try:
# 复制到剪贴板
self.root.clipboard_clear()
self.root.clipboard_append(self.report_content)
# 显示成功提示
self.copy_status.config(text="已复制到剪贴板!", fg="#4CAF50")
self.root.after(3000, lambda: self.copy_status.config(text=""))
except Exception as e:
messagebox.showerror("错误", f"复制失败:{str(e)}")
def clear_report(self):
"""清空日报内容"""
self.report_content = ""
self.report_text.config(state=tk.NORMAL)
self.report_text.delete(1.0, tk.END)
self.report_text.insert(
tk.END, '请点击左侧"昨日汇总"按钮,或选择日期后点击"生成日报"按钮...'
)
self.report_text.config(state=tk.DISABLED)
self.date_var.set("")
self.selected_date_label.config(text="当前选中:无")
self.copy_status.config(text="")
def main():
"""主函数"""
# 创建主窗口
root = tk.Tk()
# 创建应用
app = DailyReportApp(root)
# 运行主循环
root.mainloop()
if __name__ == "__main__":
main()