- 新增 Pagination 通用组件:首页/上一页/页码/下一页/末页导航 - PickModal 和 SwapModal 集成分页功能,每页20项 - 搜索/筛选切换自动回到第1页 - 现代化CSS样式:hover浮起动效、主色高亮、移动端适配 - 新增 dashboard、admin 管理模板 - 菜系(cuisine)模型和迁移 - 构建脚本 build.py 支持 PyInstaller 打包 - 前端资源重新构建
156 lines
6.1 KiB
Python
156 lines
6.1 KiB
Python
"""
|
|
\u5b66\u6821\u8ba2\u9910\u83dc\u5355\u751f\u6210\u5668 - EXE \u5165\u53e3
|
|
\u53cc\u51fb\u8fd0\u884c\u6b64\u811a\u672c\u6216\u6253\u5305\u540e\u7684 EXE\uff0c\u81ea\u52a8\u5b8c\u6210\uff1a
|
|
1. \u521d\u59cb\u5316\u6570\u636e\u5e93\uff08\u9996\u6b21\u8fd0\u884c\u81ea\u52a8\u8fc1\u79fb + \u5199\u5165\u793a\u4f8b\u83dc\u54c1\uff09
|
|
2. \u542f\u52a8 Django \u670d\u52a1\uff08\u540c\u65f6\u6258\u7ba1\u524d\u7aef\u9759\u6001\u9875\u9762\uff09
|
|
3. 自动打开浏览器(默认)或原生窗口(--gui 模式)
|
|
"""
|
|
import os
|
|
import sys
|
|
import threading
|
|
import time
|
|
import webbrowser
|
|
|
|
# Ensure UTF-8 output on Windows console (fixes GBK encoding errors with rich)
|
|
if sys.platform == "win32":
|
|
os.system("chcp 65001 >nul 2>&1")
|
|
try:
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
except Exception:
|
|
pass
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.text import Text
|
|
from rich.table import Table
|
|
from rich import box
|
|
|
|
console = Console()
|
|
|
|
GUI_MODE = "--gui" in sys.argv
|
|
|
|
|
|
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)
|
|
# In dev mode, add backend/ to path so config and meals are importable
|
|
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
|
|
|
|
with console.status("[bold cyan]\u6b63\u5728\u8fc1\u79fb\u6570\u636e\u5e93...", spinner="dots"):
|
|
call_command("migrate", "--run-syncdb", verbosity=0)
|
|
console.print(" [green]\u2713[/green] \u6570\u636e\u5e93\u8fc1\u79fb\u5b8c\u6210")
|
|
|
|
from meals.models import Dish
|
|
if Dish.objects.count() == 0:
|
|
with console.status("[bold cyan]\u6b63\u5728\u5199\u5165\u793a\u4f8b\u83dc\u54c1\u6570\u636e...", spinner="dots"):
|
|
try:
|
|
call_command("seed_dishes", verbosity=0)
|
|
except Exception:
|
|
pass
|
|
console.print(" [green]\u2713[/green] \u793a\u4f8b\u83dc\u54c1\u6570\u636e\u5df2\u5199\u5165")
|
|
else:
|
|
console.print(" [green]\u2713[/green] \u83dc\u54c1\u6570\u636e\u5df2\u5b58\u5728\uff0c\u8df3\u8fc7")
|
|
|
|
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")
|
|
console.print(" [green]\u2713[/green] \u5df2\u521b\u5efa\u7ba1\u7406\u5458 [bold]admin[/bold] / [bold]admin123[/bold]")
|
|
else:
|
|
console.print(" [green]\u2713[/green] \u7ba1\u7406\u5458\u8d26\u53f7\u5df2\u5b58\u5728")
|
|
|
|
|
|
def open_browser(port):
|
|
time.sleep(2)
|
|
webbrowser.open(f"http://127.0.0.1:{port}/")
|
|
|
|
|
|
def print_banner(port):
|
|
logo = Text()
|
|
logo.append(" _____ _ _ ____ \n", style="bold cyan")
|
|
logo.append(" / ____| | (_) | _ \\ \n", style="bold cyan")
|
|
logo.append("| (___ | |__ __ _ _ __ _ ___ ___ _ __ | |_) | __ ___ _____ _ __ \n", style="bold cyan")
|
|
logo.append(" \\___ \\| '_ \\ / _` | '__| |/ __/ _ \\| '_ \\| _ < / _` \\ \\ /\\ / / _ \\| '_ \\ \n", style="bold cyan")
|
|
logo.append(" ____) | | | | (_| | | | | (_| (_) | | | | |_) | (_| |\\ V V / (_) | | | |\n", style="bold cyan")
|
|
logo.append("|_____/|_| |_|\\__,_|_| |_|\\___\\___/|_| |_|____/ \\__,_| \\_/\\_/ \\___/|_| |_|\n", style="bold cyan")
|
|
|
|
info_table = Table(show_header=False, box=box.SIMPLE, padding=(0, 2))
|
|
info_table.add_column("key", style="bold white")
|
|
info_table.add_column("value", style="cyan")
|
|
info_table.add_row("\u5bfc\u822a\u9996\u9875", f"http://127.0.0.1:{port}/")
|
|
info_table.add_row("\u83dc\u5355\u751f\u6210\u5668", f"http://127.0.0.1:{port}/app/")
|
|
info_table.add_row("\u540e\u53f0\u7ba1\u7406", f"http://127.0.0.1:{port}/admin")
|
|
info_table.add_row("\u7ba1\u7406\u5458", "admin / admin123")
|
|
info_table.add_row("\u505c\u6b62\u670d\u52a1", "Ctrl + C")
|
|
|
|
from rich.console import Group as RenderGroup
|
|
|
|
console.print(Panel(
|
|
RenderGroup(logo, Text(""), info_table),
|
|
title="[bold green]\u5b66\u6821\u8ba2\u9910\u83dc\u5355\u751f\u6210\u5668[/bold green]",
|
|
subtitle="[dim]School Meal Planner[/dim]",
|
|
border_style="bright_blue",
|
|
padding=(1, 2),
|
|
))
|
|
|
|
|
|
# manager.py is no longer launched separately; the web dashboard IS the manager
|
|
|
|
|
|
def main():
|
|
if GUI_MODE:
|
|
from desktop import main as gui_main
|
|
gui_main()
|
|
return
|
|
console.clear()
|
|
setup_environment()
|
|
console.print()
|
|
console.print("[bold cyan]\u6b63\u5728\u521d\u59cb\u5316\u7cfb\u7edf...[/bold cyan]")
|
|
console.print()
|
|
init_database()
|
|
port = 8000
|
|
print_banner(port)
|
|
# Auto-open dashboard in browser after server starts
|
|
threading.Thread(target=open_browser, args=(port,), daemon=True).start()
|
|
console.print("[dim]Django \u670d\u52a1\u542f\u52a8\u4e2d...[/dim]")
|
|
console.print()
|
|
from django.core.management import call_command
|
|
call_command("runserver", f"127.0.0.1:{port}", "--noreload", verbosity=1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|