""" Интерфейс (UI) для приложения автоматизации загрузки файлов """ import tkinter as tk from tkinter import filedialog, messagebox, ttk import threading import logging from pathlib import Path from services.processor import FileProcessorService from services.browser_service import BrowserService class UIManager: """Менеджер интерфейса приложения""" def __init__(self, root): """Инициализирует интерфейс.""" self.root = root self.root.title("🔄 Автоматизация загрузки файлов") self.root.geometry("700x800") self.root.configure(bg="#2C3E50") self.root.minsize(500, 400) self.font_title = ("Arial", 14, "bold") self.font_label = ("Arial", 11) self.font_button = ("Arial", 10, "bold") self.processor = None self.processing = False self._setup_logging() self._create_ui() def _setup_logging(self): """Настраивает логирование в текстовое поле.""" # Обычное логирование в консоль logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) def _create_ui(self): """Создает элементы интерфейса.""" # Главный фрейм main_frame = tk.Frame(self.root, bg="#2C3E50") main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) # Заголовок title_label = tk.Label( main_frame, text="📁 Автоматизация загрузки файлов XLSX", font=self.font_title, bg="#2C3E50", fg="#ECF0F1" ) title_label.pack(pady=(0, 20)) # ===== ПОЛЕ 1: ДИРЕКТОРИЯ С ФАЙЛАМИ ===== dir_label = tk.Label( main_frame, text="📂 Директория с файлами .xlsx:", font=self.font_label, bg="#2C3E50", fg="#ECF0F1" ) dir_label.pack(anchor="w", pady=(10, 5)) dir_frame = tk.Frame(main_frame, bg="#2C3E50") dir_frame.pack(fill=tk.X, pady=(0, 15)) self.dir_entry = tk.Entry(dir_frame, font=self.font_label, width=60) self.dir_entry.pack(side="left", fill=tk.X, expand=True) self.dir_entry.bind("", lambda e: self._select_directory()) dir_button = tk.Button( dir_frame, text="🗂️ Выбрать", command=self._select_directory, font=self.font_button, bg="#3498DB", fg="white", padx=10 ) dir_button.pack(side="left", padx=(10, 0)) # ===== ПОЛЕ 2: URL СТРАНИЦЫ ===== url_label = tk.Label( main_frame, text="🌐 URL страницы для загрузки:", font=self.font_label, bg="#2C3E50", fg="#ECF0F1" ) url_label.pack(anchor="w", pady=(10, 5)) # Кнопка Вставить рядом с полем URL url_frame = tk.Frame(main_frame, bg="#2C3E50") url_frame.pack(fill=tk.X, pady=(0, 15)) self.url_entry = tk.Entry(url_frame, font=self.font_label) self.url_entry.pack(side="left", fill=tk.X, expand=True) insert_button = tk.Button( url_frame, text="Вставить", command=self._insert_from_clipboard, font=self.font_button, bg="#27AE60", fg="white", padx=10 ) insert_button.pack(side="left", padx=(10, 0)) # ===== ПОЛЕ 3: ВАРИАНТЫ КНОПОК ===== buttons_label = tk.Label( main_frame, text="🔘 Варианты текста кнопок (через запятую):", font=self.font_label, bg="#2C3E50", fg="#ECF0F1" ) buttons_label.pack(anchor="w", pady=(10, 5)) self.upload_buttons_entry = tk.Entry(main_frame, font=self.font_label) self.upload_buttons_entry.pack(anchor="w", fill=tk.X, pady=(0, 5)) self.upload_buttons_entry.insert(0, "Загрузить файл, Выбрать файл, Добавить файл") upload_label = tk.Label( main_frame, text="(Кнопка для загрузки файла)", font=("Arial", 9), bg="#2C3E50", fg="#BDC3C7" ) upload_label.pack(anchor="w", pady=(0, 10)) self.process_buttons_entry = tk.Entry(main_frame, font=self.font_label) self.process_buttons_entry.pack(anchor="w", fill=tk.X, pady=(0, 5)) self.process_buttons_entry.insert(0, "Загрузить, Обработать, Преобразовать, Изменить, Конвертировать") process_label = tk.Label( main_frame, text="(Кнопка для обработки/загрузки файла)", font=("Arial", 9), bg="#2C3E50", fg="#BDC3C7" ) process_label.pack(anchor="w", pady=(0, 15)) # ===== ЛОГИРОВАНИЕ ===== log_label = tk.Label( main_frame, text="📋 Логирование процесса:", font=self.font_label, bg="#2C3E50", fg="#ECF0F1" ) log_label.pack(anchor="w", pady=(10, 5)) # Frame для логов с прокруткой log_frame = tk.Frame(main_frame, bg="#34495E") log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) scrollbar = tk.Scrollbar(log_frame) scrollbar.pack(side="right", fill="y") self.log_text = tk.Text( log_frame, font=("Courier", 9), bg="#1C2833", fg="#82E0AA", height=10, yscrollcommand=scrollbar.set ) self.log_text.pack(fill=tk.BOTH, expand=True) scrollbar.config(command=self.log_text.yview) # ===== КНОПКИ ===== button_frame = tk.Frame(main_frame, bg="#2C3E50") button_frame.pack(fill=tk.X) self.start_button = tk.Button( button_frame, text="🚀 Начать загрузку", command=self._start_processing, font=self.font_button, bg="#27AE60", fg="white", padx=20, pady=12 ) self.start_button.pack(side="right", padx=(10, 0)) self.clear_log_button = tk.Button( button_frame, text="🗑️ Очистить логи", command=self._clear_logs, font=self.font_button, bg="#E74C3C", fg="white", padx=15, pady=12 ) self.clear_log_button.pack(side="right") # Перенаправление логов в текстовое поле self._setup_log_handler() # ===== ВЫБОР ВКЛАДКИ ===== tabs_label = tk.Label( main_frame, text="📑 Выбор вкладки браузера:", font=self.font_label, bg="#2C3E50", fg="#ECF0F1" ) tabs_label.pack(anchor="w", pady=(10, 5)) self.tab_selector = TabSelectorApp(main_frame, BrowserService(download_dir="./downloads")) self.tab_selector.pack(fill=tk.X, pady=(0, 15)) def _setup_log_handler(self): """Настраивает обработчик логов в текстовое поле.""" # Создаем кастомный обработчик для логирования в UI class TextHandler(logging.Handler): def __init__(self, text_widget): super().__init__() self.text_widget = text_widget def emit(self, record): msg = self.format(record) self.text_widget.insert(tk.END, msg + "\n") self.text_widget.see(tk.END) self.text_widget.update() handler = TextHandler(self.log_text) handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%H:%M:%S')) logging.getLogger().addHandler(handler) def _select_directory(self): """Открывает диалог выбора директории.""" directory = filedialog.askdirectory(title="Выберите директорию с .xlsx файлами") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) logging.info(f"✅ Выбрана директория: {directory}") def _clear_logs(self): """Очищает логи.""" self.log_text.delete(1.0, tk.END) logging.info("🗑️ Логи очищены") def _start_processing(self): """Запускает процесс обработки в отдельном потоке.""" if self.processing: messagebox.showwarning("⚠️ Внимание", "Процесс уже запущен!") return # Валидация входных данных directory = self.dir_entry.get().strip() url = self.url_entry.get().strip() if not directory: messagebox.showerror("❌ Ошибка", "Выберите директорию с файлами!") return if not Path(directory).exists(): messagebox.showerror("❌ Ошибка", "Указанная директория не существует!") return if not url or url == "https://": messagebox.showerror("❌ Ошибка", "Введите URL страницы!") return # Парсим варианты кнопок upload_texts = [t.strip() for t in self.upload_buttons_entry.get().split(",")] process_texts = [t.strip() for t in self.process_buttons_entry.get().split(",")] # Отключаем кнопку и запускаем обработку в потоке self.processing = True self.start_button.config(state="disabled", bg="#95A5A6") thread = threading.Thread( target=self._processing_thread, args=(directory, url, upload_texts, process_texts), daemon=True ) thread.start() def _processing_thread(self, directory, url, upload_texts, process_texts): """Потоковая функция обработки.""" try: logging.info("\n" + "="*70) logging.info("🔄 ЗАПУСК АВТОМАТИЗАЦИИ ЗАГРУЗКИ ФАЙЛОВ") logging.info("="*70) # Получаем путь для загрузок (рядом с программой) download_dir = str(Path(__file__).parent / "downloads") Path(download_dir).mkdir(exist_ok=True) # Создаем процессор processor = FileProcessorService(directory, url, download_dir) # Проверка вкладок перед началом загрузки browser_service = BrowserService(download_dir="./downloads") browser_service.set_tabs(["Вкладка 1", "Вкладка 2", "Вкладка 3"]) # Пример: выбор вкладки по индексу if not browser_service.select_tab_from_list(0): logging.error("❌ Не удалось выбрать вкладку. Проверьте массив вкладок.") return # Запускаем обработку results = processor.start_processing(upload_texts, process_texts) # Показываем результаты self._show_results(results) except Exception as e: logging.error(f"❌ Критическая ошибка: {e}") messagebox.showerror("❌ Ошибка", f"Произошла ошибка:\n{str(e)}") finally: self.processing = False self.start_button.config(state="normal", bg="#27AE60") def _show_results(self, results): """Показывает результаты обработки.""" result_text = ( f"✅ ОБРАБОТКА ЗАВЕРШЕНА\n\n" f"📊 Результаты:\n" f"• Всего файлов найдено: {results['total_files']}\n" f"• Обработано успешно: {results['processed_files']}\n" f"• Ошибок: {len(results['failed_files'])}\n" ) if results['failed_files']: result_text += f"\n❌ Файлы с ошибками:\n" for file in results['failed_files'][:5]: result_text += f" • {file}\n" if len(results['failed_files']) > 5: result_text += f" ... и еще {len(results['failed_files']) - 5}" if results['errors']: result_text += f"\n⚠️ Системные ошибки:\n" for error in results['errors']: result_text += f" • {error}\n" messagebox.showinfo("Результаты", result_text) def _insert_from_clipboard(self): """Обработчик для вставки текста из буфера обмена.""" try: clipboard_text = self.root.clipboard_get() self.url_entry.delete(0, tk.END) # Очистить поле self.url_entry.insert(0, clipboard_text) # Вставить текст из буфера except tk.TclError: messagebox.showerror("Ошибка", "Буфер обмена пуст или недоступен.") class TabSelectorApp: def __init__(self, parent, browser_service): self.browser_service = browser_service self.frame = tk.Frame(parent, bg="#2C3E50") # Выпадающий список self.combo = ttk.Combobox(self.frame, state="readonly") self.combo.pack(pady=10) # Кнопка для выбора вкладки self.select_button = tk.Button(self.frame, text="Выбрать вкладку", command=self.select_tab) self.select_button.pack(pady=10) def pack(self, **kwargs): """Упрощает упаковку фрейма.""" self.frame.pack(**kwargs) def set_tabs(self, tabs): """Устанавливает массив вкладок и обновляет выпадающий список.""" self.tabs = tabs self.combo["values"] = self.tabs if self.tabs: self.combo.current(0) # Устанавливаем первый элемент по умолчанию def select_tab(self): """Выбирает вкладку из выпадающего списка.""" selected_index = self.combo.current() if selected_index == -1: print("❌ Вкладка не выбрана.") return success = self.browser_service.select_tab_from_list(selected_index) if success: print(f"✅ Вкладка '{self.tabs[selected_index]}' выбрана.") else: print(f"❌ Не удалось выбрать вкладку '{self.tabs[selected_index]}'." ) def main(): """Главная функция.""" root = tk.Tk() app = UIManager(root) root.mainloop() if __name__ == "__main__": main()