#!/usr/bin/env python # -*- coding: utf-8 -*- """ 删除进度窗口组件 显示删除操作的进度和日志,完成后显示 Markdown 格式的报告。 """ import tkinter as tk from tkinter import ttk, scrolledtext from typing import Optional, Callable from datetime import datetime # 尝试导入 tkinterweb 和 markdown2 try: from tkinterweb import HtmlFrame HAS_TKINTERWEB = True except ImportError: HAS_TKINTERWEB = False try: import markdown2 HAS_MARKDOWN2 = True except ImportError: HAS_MARKDOWN2 = False class DeleteProgressWindow: """删除进度窗口""" def __init__( self, parent, title: str = "执行删除", managers: str = "", dryrun: bool = False, on_cancel: Optional[Callable] = None ): """ 初始化删除进度窗口 Args: parent: 父窗口 title: 窗口标题 managers: 负责人列表字符串 dryrun: 是否为预览模式 on_cancel: 取消回调函数 """ self.parent = parent self.on_cancel = on_cancel self.cancelled = False self.managers = managers self.dryrun = dryrun # 创建窗口 self.window = tk.Toplevel(parent) self.window.title(title) self.window.resizable(True, True) self.window.transient(parent) # 设置窗口大小 self.window.geometry("700x600") # 创建内容 self._create_widgets() # 居中显示 self._center() def _center(self): """将窗口居中显示""" self.window.update_idletasks() width = 700 height = 600 x = (self.window.winfo_screenwidth() // 2) - (width // 2) y = (self.window.winfo_screenheight() // 2) - (height // 2) self.window.geometry(f"{width}x{height}+{x}+{y}") def _create_widgets(self): """创建窗口组件""" # 主容器 self.main_frame = ttk.Frame(self.window, padding=10) self.main_frame.pack(fill=tk.BOTH, expand=True) # 信息区域 info_frame = ttk.Frame(self.main_frame) info_frame.pack(fill=tk.X, pady=(0, 10)) # 负责人信息 if self.managers: ttk.Label(info_frame, text=f"负责人: {self.managers}").pack(anchor="w") # 模式信息 mode_text = "预览模式 (不保存)" if self.dryrun else "正常执行" mode_label = ttk.Label(info_frame, text=f"模式: {mode_text}") mode_label.pack(anchor="w") # 进度区域 self.progress_frame = ttk.LabelFrame(self.main_frame, text="进度", padding=5) self.progress_frame.pack(fill=tk.X, pady=(0, 10)) self.progress_var = tk.StringVar(value="准备中...") self.progress_label = ttk.Label(self.progress_frame, textvariable=self.progress_var) self.progress_label.pack(anchor="w") self.progress_bar = ttk.Progressbar( self.progress_frame, mode='determinate', length=660, maximum=100 ) self.progress_bar.pack(fill=tk.X, pady=5) # 日志区域(执行过程中显示) self.log_frame = ttk.LabelFrame(self.main_frame, text="日志", padding=5) self.log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) self.log_text = scrolledtext.ScrolledText( self.log_frame, height=10, wrap=tk.WORD, state=tk.DISABLED, font=('Consolas', 9) ) self.log_text.pack(fill=tk.BOTH, expand=True) # 配置日志标签颜色 self.log_text.tag_configure('info', foreground='black') self.log_text.tag_configure('success', foreground='green') self.log_text.tag_configure('warning', foreground='orange') self.log_text.tag_configure('error', foreground='red') # 报告区域(完成后显示)- 初始隐藏 self.report_frame = ttk.LabelFrame(self.main_frame, text="执行报告", padding=5) # 根据 tkinterweb 可用性选择渲染方式 if HAS_TKINTERWEB: # 使用 HtmlFrame 渲染 HTML self.report_html = HtmlFrame(self.report_frame) self.report_html.pack(fill=tk.BOTH, expand=True) else: # 降级为文本显示 self.report_text = scrolledtext.ScrolledText( self.report_frame, height=20, wrap=tk.WORD, state=tk.DISABLED, font=('Consolas', 9) ) self.report_text.pack(fill=tk.BOTH, expand=True) # 按钮区域 button_frame = ttk.Frame(self.main_frame) button_frame.pack(fill=tk.X) self.cancel_button = ttk.Button( button_frame, text="取消执行", command=self._on_cancel ) self.cancel_button.pack(side=tk.RIGHT) # 关闭按钮(初始隐藏) self.close_button = ttk.Button( button_frame, text="关闭", command=self.close ) def _on_cancel(self): """处理取消操作""" self.cancelled = True self.cancel_button.config(state=tk.DISABLED, text="正在取消...") if self.on_cancel: self.on_cancel() else: self.append_log("用户取消了操作", "warning") def update_progress(self, current: int, total: int, message: str): """ 更新进度 Args: current: 当前进度值 total: 总数 message: 进度消息 """ if total > 0: percentage = int((current / total) * 100) self.progress_bar['value'] = percentage self.progress_var.set(message) else: self.progress_var.set(message) self.window.update_idletasks() def append_log(self, message: str, level: str = "info"): """ 追加日志 Args: message: 日志消息 level: 日志级别 (info, success, warning, error) """ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") log_entry = f"[{timestamp}] {message}\n" self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, log_entry, level) self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) self.window.update_idletasks() def show_report(self, markdown_content: str): """ 显示报告 Args: markdown_content: Markdown 格式的报告内容 """ # 隐藏进度区域和日志区域 self.progress_frame.pack_forget() self.log_frame.pack_forget() # 显示报告区域 self.report_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) # 根据可用库选择渲染方式 if HAS_TKINTERWEB and HAS_MARKDOWN2: # 使用 tkinterweb 渲染 HTML html_content = self._markdown_to_html(markdown_content) self.report_html.load_html(html_content) elif HAS_TKINTERWEB: # 只有 tkinterweb,使用简单 HTML html_content = self._markdown_to_simple_html(markdown_content) self.report_html.load_html(html_content) else: # 降级为文本显示 text_content = self._markdown_to_text(markdown_content) self.report_text.config(state=tk.NORMAL) self.report_text.delete(1.0, tk.END) self.report_text.insert(tk.END, text_content) self.report_text.config(state=tk.DISABLED) # 更新标题 self.window.title("执行报告") # 隐藏取消按钮,显示关闭按钮 self.cancel_button.pack_forget() self.close_button.pack(side=tk.RIGHT) # 更新进度标签 self.progress_var.set("执行完成") def _markdown_to_html(self, markdown_content: str) -> str: """ 将 Markdown 转换为 HTML Args: markdown_content: Markdown 内容 Returns: HTML 内容 """ # 使用 markdown2 转换 html_body = markdown2.markdown( markdown_content, extras=['tables', 'fenced-code-blocks'] ) # 添加样式 html_content = f""" {html_body} """ return html_content def _markdown_to_simple_html(self, markdown_content: str) -> str: """ 将 Markdown 转换为简单 HTML(不依赖 markdown2) Args: markdown_content: Markdown 内容 Returns: HTML 内容 """ lines = markdown_content.split('\n') html_parts = ['', ''] in_table = False for line in lines: if line.startswith('# '): html_parts.append(f'

{line[2:]}

') elif line.startswith('## '): html_parts.append(f'

{line[3:]}

') elif line.startswith('| '): if not in_table: html_parts.append('') in_table = True # 检查是否是表头分隔行 if '|--' in line or '|-' in line: continue cells = [cell.strip() for cell in line.split('|')[1:-1]] if cells: # 第一行作为表头 if html_parts[-1] == '
': html_parts.append('' + ''.join(f'' for c in cells) + '') else: html_parts.append('' + ''.join(f'' for c in cells) + '') elif line.startswith('- '): if in_table: html_parts.append('
{c}
{c}
') in_table = False html_parts.append(f'
  • {line[2:]}
  • ') elif line.strip() == '': if in_table: html_parts.append('') in_table = False html_parts.append('
    ') else: if in_table: html_parts.append('') in_table = False if line.strip(): html_parts.append(f'

    {line}

    ') if in_table: html_parts.append('') html_parts.append('') return '\n'.join(html_parts) def _markdown_to_text(self, markdown_content: str) -> str: """ 将 Markdown 转换为简单的文本格式 Args: markdown_content: Markdown 内容 Returns: 格式化后的文本 """ lines = markdown_content.split('\n') result = [] for line in lines: # 标题 if line.startswith('# '): result.append('=' * 60) result.append(line[2:]) result.append('=' * 60) elif line.startswith('## '): result.append('') result.append(line[3:]) result.append('-' * 40) elif line.startswith('| '): # 表格行 - 保持原样 result.append(line) elif line.startswith('|--') or line.startswith('|-'): # 表格分隔线 - 跳过 continue elif line.startswith('- '): # 列表项 result.append(' ' + line) elif line.strip() == '': result.append('') else: result.append(line) return '\n'.join(result) def close(self): """关闭窗口""" self.window.destroy() def is_cancelled(self) -> bool: """检查是否已取消""" return self.cancelled def set_completed(self): """设置为完成状态""" self.cancel_button.pack_forget() self.close_button.pack(side=tk.RIGHT)