fix
This commit is contained in:
322
ui.py
Normal file
322
ui.py
Normal file
@@ -0,0 +1,322 @@
|
||||
"""
|
||||
Интерфейс (UI) для приложения автоматизации загрузки файлов
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox, ttk
|
||||
import threading
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from services.processor import FileProcessorService
|
||||
|
||||
|
||||
class UIManager:
|
||||
"""Менеджер интерфейса приложения"""
|
||||
|
||||
def __init__(self, root):
|
||||
"""Инициализирует интерфейс."""
|
||||
self.root = root
|
||||
self.root.title("🔄 Автоматизация загрузки файлов")
|
||||
self.root.geometry("700x600")
|
||||
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))
|
||||
|
||||
self.url_entry = tk.Entry(main_frame, font=self.font_label)
|
||||
self.url_entry.pack(anchor="w", fill=tk.X, pady=(0, 15))
|
||||
self.url_entry.insert(0, "https://")
|
||||
|
||||
# ===== ПОЛЕ 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()
|
||||
|
||||
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)
|
||||
|
||||
# Запускаем обработку
|
||||
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 main():
|
||||
"""Главная функция."""
|
||||
root = tk.Tk()
|
||||
app = UIManager(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user