""" Интерфейс (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)) # ===== ПОЛЕ 4: ВЫБОР ВКЛАДКИ НА СТРАНИЦЕ ===== tab_select_label = tk.Label( main_frame, text="📑 Вкладка на странице для обработки:", font=self.font_label, bg="#2C3E50", fg="#ECF0F1" ) tab_select_label.pack(anchor="w", pady=(10, 5)) # Фрейм для списка вкладок и выпадающего списка tab_config_frame = tk.Frame(main_frame, bg="#2C3E50") tab_config_frame.pack(fill=tk.X, pady=(0, 5)) # Поле для ввода доступных вкладок tab_list_label = tk.Label( main_frame, text="Доступные вкладки (через запятую):", font=("Arial", 9), bg="#2C3E50", fg="#BDC3C7" ) tab_list_label.pack(anchor="w", pady=(0, 2)) self.tabs_list_entry = tk.Entry(main_frame, font=self.font_label) self.tabs_list_entry.pack(anchor="w", fill=tk.X, pady=(0, 5)) self.tabs_list_entry.insert(0, "Внесение ФС, Корректировка ОН, Внесение КС") self.tabs_list_entry.bind("", self._update_tab_selector) # Выпадающий список для выбора вкладки tab_choice_label = tk.Label( main_frame, text="Выберите вкладку:", font=("Arial", 9), bg="#2C3E50", fg="#BDC3C7" ) tab_choice_label.pack(anchor="w", pady=(5, 2)) self.tab_choice_combo = ttk.Combobox(main_frame, state="readonly", font=self.font_label) self.tab_choice_combo.pack(anchor="w", fill=tk.X, pady=(0, 15)) self._update_tab_selector() # Инициализация списка вкладок # ===== ЛОГИРОВАНИЕ ===== 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() def _update_tab_selector(self, event=None): """Обновляет выпадающий список вкладок на основе введенного текста.""" tabs_text = self.tabs_list_entry.get().strip() if tabs_text: tabs = [t.strip() for t in tabs_text.split(",") if t.strip()] self.tab_choice_combo["values"] = tabs if tabs and self.tab_choice_combo.current() == -1: self.tab_choice_combo.current(0) # Выбираем первую по умолчанию else: self.tab_choice_combo["values"] = [] def _update_tab_selector(self, event=None): """Обновляет выпадающий список вкладок на основе введенного текста.""" tabs_text = self.tabs_list_entry.get().strip() if tabs_text: tabs = [t.strip() for t in tabs_text.split(",") if t.strip()] self.tab_choice_combo["values"] = tabs if tabs and self.tab_choice_combo.current() == -1: self.tab_choice_combo.current(0) # Выбираем первую по умолчанию else: self.tab_choice_combo["values"] = [] 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(",")] # Получаем выбранную вкладку selected_tab = self.tab_choice_combo.get().strip() if self.tab_choice_combo.get() else None # Отключаем кнопку и запускаем обработку в потоке 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, selected_tab), daemon=True ) thread.start() def _processing_thread(self, directory, url, upload_texts, process_texts, selected_tab=None): """Потоковая функция обработки.""" 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) # Выбор вкладки на странице (если указана) if selected_tab: logging.info(f"🔍 Попытка выбрать вкладку на странице: '{selected_tab}'") browser_service = processor.browser if browser_service and browser_service.select_tab_by_multiple_methods(selected_tab, timeout=15): logging.info(f"✅ Вкладка '{selected_tab}' успешно выбрана") else: logging.warning(f"⚠️ Не удалось выбрать вкладку '{selected_tab}', продолжаю без выбора вкладки") else: logging.info("ℹ️ Вкладка не указана, пропускаю выбор вкладки") # Запускаем обработку results = processor.start_processing(upload_texts, process_texts, selected_tab) # Показываем результаты 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("Ошибка", "Буфер обмена пуст или недоступен.") def main(): """Главная функция.""" root = tk.Tk() app = UIManager(root) root.mainloop() if __name__ == "__main__": main()