""" Dashboard Manager - School Meal Planner A lightweight control panel / visualization entry point. When run standalone, starts the server and opens the dashboard. When run alongside the EXE (run.py), it is NOT auto-launched. """ import os import sys import threading import time import urllib.request import webbrowser try: import tkinter as tk from tkinter import messagebox HAS_TKINTER = True except ImportError: HAS_TKINTER = False # --------------------------------------------------------------------------- # Environment setup (shared with run.py) # --------------------------------------------------------------------------- def _frozen_dir(): if getattr(sys, "frozen", False): return sys._MEIPASS return os.path.dirname(os.path.abspath(__file__)) def _app_dir(): if getattr(sys, "frozen", False): return os.path.dirname(sys.executable) return os.path.dirname(os.path.abspath(__file__)) def setup_environment(): base = _frozen_dir() app = _app_dir() sys.path.insert(0, base) backend_dir = os.path.join(base, 'backend') if os.path.isdir(backend_dir) and backend_dir not in sys.path: sys.path.insert(0, backend_dir) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") os.environ["DJANGO_DEBUG"] = "True" os.environ["DJANGO_SECRET_KEY"] = "exe-local-secret-key-do-not-use-in-production" os.environ["DJANGO_ALLOWED_HOSTS"] = "127.0.0.1,localhost" os.environ["DJANGO_DB_PATH"] = os.path.join(app, "db.sqlite3") os.environ["DJANGO_STATIC_URL"] = "/static/" os.environ["EXE_MODE"] = "1" static_fe = os.path.join(base, "backend", "static", "frontend") if not os.path.isdir(static_fe): static_fe = os.path.join(base, "static", "frontend") os.environ["FRONTEND_DIR"] = static_fe def init_database(): import django django.setup() from django.core.management import call_command call_command("migrate", "--run-syncdb", verbosity=0) from meals.models import Dish if Dish.objects.count() == 0: try: call_command("seed_dishes", verbosity=0) except Exception: pass from django.contrib.auth import get_user_model User = get_user_model() if not User.objects.filter(username="admin").exists(): User.objects.create_superuser("admin", "admin@example.com", "admin123") def start_server(port): import django django.setup() from django.core.management import call_command call_command("runserver", f"127.0.0.1:{port}", "--noreload", verbosity=0) def _server_alive(port=8000): """Check if Django is already running on the port.""" try: urllib.request.urlopen(f"http://127.0.0.1:{port}/admin/", timeout=2) return True except Exception: return False # --------------------------------------------------------------------------- # GUI - Visualization Panel # --------------------------------------------------------------------------- BG = "#1e293b" BG_CARD = "#334155" ACCENT = "#38bdf8" GREEN = "#4ade80" RED = "#f87171" FG_DIM = "#94a3b8" FONT_TITLE = ("Microsoft YaHei UI", 16, "bold") FONT_LABEL = ("Microsoft YaHei UI", 11) FONT_BTN = ("Microsoft YaHei UI", 11, "bold") FONT_STATUS = ("Microsoft YaHei UI", 10) PORT = 8000 URL_DASHBOARD = f"http://127.0.0.1:{PORT}/dashboard/" URL_FRONTEND = f"http://127.0.0.1:{PORT}" URL_ADMIN = f"http://127.0.0.1:{PORT}/admin" ADMIN_USER = "admin" ADMIN_PASS = "admin123" class ManagerApp: def __init__(self): self.root = tk.Tk() self.root.title("\u5b66\u6821\u8ba2\u9910\u83dc\u5355\u751f\u6210\u5668 - \u53ef\u89c6\u5316\u7ba1\u7406\u9762\u677f") self.root.geometry("420x480") self.root.resizable(False, False) self.root.configure(bg=BG) self.root.protocol("WM_DELETE_WINDOW", self._on_close) self._server_thread = None self._build_ui() self._check_server() def _build_ui(self): root = self.root # Header header = tk.Frame(root, bg=ACCENT, height=56) header.pack(fill="x") header.pack_propagate(False) tk.Label( header, text="\u53ef\u89c6\u5316\u7ba1\u7406\u9762\u677f", font=FONT_TITLE, bg=ACCENT, fg="#0f172a", ).pack(expand=True) # Status bar self._status_frame = tk.Frame(root, bg=BG_CARD, bd=1, relief="solid") self._status_frame.pack(fill="x", padx=16, pady=(16, 8)) tk.Label( self._status_frame, text="\u670d\u52a1\u72b6\u6001", font=FONT_LABEL, bg=BG_CARD, fg=FG_DIM, anchor="w", ).pack(side="left", padx=(12, 8), pady=10) self._status_dot = tk.Canvas( self._status_frame, width=12, height=12, bg=BG_CARD, highlightthickness=0, ) self._status_dot.pack(side="left", pady=10) self._status_dot.create_oval(2, 2, 10, 10, fill=FG_DIM, outline="", tags="dot") self._status_label = tk.Label( self._status_frame, text="\u68c0\u6d4b\u4e2d...", font=FONT_STATUS, bg=BG_CARD, fg=FG_DIM, ) self._status_label.pack(side="left", padx=(4, 12), pady=10) # Credentials cred_frame = tk.Frame(root, bg=BG_CARD, bd=1, relief="solid") cred_frame.pack(fill="x", padx=16, pady=4) tk.Label(cred_frame, text="\u7ba1\u7406\u5458\u4fe1\u606f", font=FONT_LABEL, bg=BG_CARD, fg=FG_DIM, anchor="w").pack(side="left", padx=(12, 8), pady=10) tk.Label(cred_frame, text=f"{ADMIN_USER} / {ADMIN_PASS}", font=FONT_BTN, bg=BG_CARD, fg=GREEN).pack(side="right", padx=(0, 12), pady=10) # URL url_frame = tk.Frame(root, bg=BG_CARD, bd=1, relief="solid") url_frame.pack(fill="x", padx=16, pady=4) tk.Label(url_frame, text="\u672c\u5730\u670d\u52a1", font=FONT_LABEL, bg=BG_CARD, fg=FG_DIM, anchor="w").pack(side="left", padx=(12, 8), pady=10) tk.Label(url_frame, text=f"127.0.0.1:{PORT}", font=FONT_BTN, bg=BG_CARD, fg=ACCENT).pack(side="right", padx=(0, 12), pady=10) # Action buttons btn_frame = tk.Frame(root, bg=BG) btn_frame.pack(fill="x", padx=16, pady=(16, 4)) self._make_button(btn_frame, "\U0001f4ca \u6253\u5f00\u53ef\u89c6\u5316\u9762\u677f", lambda: self._open_url(URL_DASHBOARD)).pack(fill="x", pady=(0, 6)) self._make_button(btn_frame, "\U0001f310 \u6253\u5f00\u83dc\u5355\u751f\u6210\u5668", lambda: self._open_url(URL_FRONTEND)).pack(fill="x", pady=(0, 6)) self._make_button(btn_frame, "\U0001f6e0\ufe0f \u6253\u5f00\u540e\u53f0\u7ba1\u7406", lambda: self._open_url(URL_ADMIN)).pack(fill="x", pady=(0, 6)) self._make_button(btn_frame, "\U0001f37d\ufe0f \u83dc\u54c1\u7ba1\u7406", lambda: self._open_url(f"{URL_ADMIN}/meals/dish/")).pack(fill="x", pady=(0, 6)) # Bottom bottom_frame = tk.Frame(root, bg=BG) bottom_frame.pack(fill="x", padx=16, pady=(12, 16)) self._make_button(bottom_frame, "\u505c\u6b62\u670d\u52a1", self._stop_server, bg_color=RED, hover_color="#dc2626").pack(fill="x", pady=(0, 4)) self._make_button(bottom_frame, "\u91cd\u542f\u670d\u52a1", self._restart_server, bg_color="#f59e0b", hover_color="#d97706").pack(fill="x") def _make_button(self, parent, text, command, bg_color=ACCENT, hover_color=None): if hover_color is None: hover_color = bg_color btn = tk.Label(parent, text=text, font=FONT_BTN, bg=bg_color, fg="#0f172a", cursor="hand2", padx=12, pady=10, anchor="w") btn.bind("", lambda e: command()) btn.bind("", lambda e: btn.configure(bg=hover_color)) btn.bind("", lambda e: btn.configure(bg=bg_color)) return btn def _check_server(self): """Detect if server is already running, or start it.""" self._set_status("\u68c0\u6d4b\u4e2d...", FG_DIM) def _worker(): if _server_alive(PORT): self.root.after(0, lambda: self._set_status("Running", GREEN)) self.root.after(500, lambda: self._open_url(URL_DASHBOARD)) else: # Start server ourselves self.root.after(0, lambda: self._set_status("\u542f\u52a8\u670d\u52a1...", FG_DIM)) try: setup_environment() init_database() self.root.after(0, lambda: self._set_status("Running", GREEN)) self.root.after(1000, lambda: self._open_url(URL_DASHBOARD)) start_server(PORT) except Exception as ex: self.root.after(0, lambda: self._set_status("\u542f\u52a8\u5931\u8d25", RED)) self.root.after(0, lambda: messagebox.showerror("\u9519\u8bef", str(ex))) self._server_thread = threading.Thread(target=_worker, daemon=True) self._server_thread.start() def _stop_server(self): messagebox.showinfo("\u63d0\u793a", "\u670d\u52a1\u7531\u4e3b\u7a0b\u5e8f\u7ba1\u7406\u3002\u5173\u95ed\u672c\u7a97\u53e3\u5373\u53ef\u505c\u6b62\u670d\u52a1\u3002") def _restart_server(self): self._set_status("\u91cd\u542f\u4e2d...", FG_DIM) def _worker(): time.sleep(1) if _server_alive(PORT): self.root.after(0, lambda: self._set_status("Running", GREEN)) else: self.root.after(0, lambda: self._set_status("\u672a\u8fde\u63a5", RED)) threading.Thread(target=_worker, daemon=True).start() def _open_url(self, url): webbrowser.open(url) def _set_status(self, text, color): self._status_label.configure(text=text, fg=color) self._status_dot.itemconfig("dot", fill=color) def _on_close(self): self.root.destroy() def run(self): self.root.mainloop() def main(): setup_environment() init_database() if HAS_TKINTER: app = ManagerApp() app.run() else: print() print("=" * 56) print(" \u5b66\u6821\u8ba2\u9910\u83dc\u5355\u751f\u6210\u5668 - \u53ef\u89c6\u5316\u7ba1\u7406\u9762\u677f") print("=" * 56) print(f" \u53ef\u89c6\u5316\u9762\u677f: {URL_DASHBOARD}") print(f" \u83dc\u5355\u751f\u6210\u5668: {URL_FRONTEND}") print(f" \u540e\u53f0\u7ba1\u7406: {URL_ADMIN}") print(f" \u7ba1\u7406\u5458\u8d26\u53f7: {ADMIN_USER} / {ADMIN_PASS}") print("-" * 56) if _server_alive(PORT): print(" \u670d\u52a1\u5df2\u8fd0\u884c\uff0c\u6253\u5f00\u6d4f\u89c8\u5668...") threading.Thread(target=lambda: (time.sleep(1), webbrowser.open(URL_DASHBOARD)), daemon=True).start() else: print(" \u670d\u52a1\u672a\u8fd0\u884c\uff0c\u6b63\u5728\u542f\u52a8...") threading.Thread(target=lambda: (time.sleep(3), webbrowser.open(URL_DASHBOARD)), daemon=True).start() try: start_server(PORT) except KeyboardInterrupt: print("\n\u670d\u52a1\u5df2\u505c\u6b62") if __name__ == "__main__": main()