Files
gloria/daily_report_gui.py
qichi.liang 00d2218c6d feat: 初始化福州港日报管理系统
- 添加日报生成功能 (report_generator.py)
- 添加 GUI 界面 (daily_report_gui.py)
- 添加班次交接报告功能 (shift_report.py)
- 集成飞书 API 获取排班信息
- 集成 Metabase 查询作业数据
- 生成 AGENTS.md 文档
2026-03-03 02:07:34 +08:00

373 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()