Files
test-loderweb/ui.py
2026-02-16 10:36:43 +05:00

426 lines
18 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
"""
Интерфейс (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("<Button-1>", 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("<KeyRelease>", 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()