|
12 | 12 | from pathlib import Path |
13 | 13 | from typing import Any, Callable |
14 | 14 |
|
15 | | -from flask import Flask, jsonify, render_template, request, session |
| 15 | +from flask import Flask, jsonify, render_template, request, send_from_directory, session |
16 | 16 |
|
17 | 17 | from services.outlook_manager import ( |
18 | 18 | FlagStateUpdateRequest, |
@@ -45,6 +45,8 @@ def create_app( |
45 | 45 | app = Flask(__name__) |
46 | 46 | app.config["JSON_AS_ASCII"] = False |
47 | 47 | app.config["SECRET_KEY"] = os.getenv("MAIL_ADMIN_SECRET_KEY", "change-me-before-production") |
| 48 | + frontend_dist_dir = Path(app.static_folder or "static") / "frontend" |
| 49 | + frontend_index_file = frontend_dist_dir / "index.html" |
48 | 50 |
|
49 | 51 | mailbox_manager = manager or MailboxManager() |
50 | 52 | mailbox_store = store or MailboxStore(database_path or os.getenv("MAILBOX_DB_PATH", "data/mailboxes.db")) |
@@ -76,12 +78,21 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: |
76 | 78 |
|
77 | 79 | @app.get("/") |
78 | 80 | def index() -> str: |
79 | | - return render_template("index.html") |
| 81 | + return _serve_frontend_index(frontend_index_file, frontend_dist_dir) |
80 | 82 |
|
81 | 83 | @app.get("/favicon.ico") |
82 | 84 | def favicon() -> tuple[str, int]: |
83 | 85 | return "", 204 |
84 | 86 |
|
| 87 | + @app.get("/<path:path>") |
| 88 | + def frontend_routes(path: str) -> Any: |
| 89 | + normalized_path = path.strip() |
| 90 | + if normalized_path.startswith("api/") or normalized_path == "api": |
| 91 | + raise MailboxError("接口不存在", code="not_found", status_code=404) |
| 92 | + if normalized_path.startswith("static/"): |
| 93 | + raise MailboxError("静态资源不存在", code="not_found", status_code=404) |
| 94 | + return _serve_frontend_index(frontend_index_file, frontend_dist_dir) |
| 95 | + |
85 | 96 | @app.get("/api/health") |
86 | 97 | def health() -> Any: |
87 | 98 | return jsonify({"status": "ok", "service": "inboxops"}) |
@@ -2310,6 +2321,34 @@ def _probe_mailbox_connection( |
2310 | 2321 | return "连接成功,收件箱暂无邮件" if not latest_subject else f"连接成功,最近邮件:{latest_subject}" |
2311 | 2322 |
|
2312 | 2323 |
|
| 2324 | +def _serve_frontend_index(frontend_index_file: Path, frontend_dist_dir: Path) -> Any: |
| 2325 | + if frontend_index_file.exists(): |
| 2326 | + return send_from_directory(frontend_dist_dir, "index.html") |
| 2327 | + return ( |
| 2328 | + """ |
| 2329 | + <!doctype html> |
| 2330 | + <html lang="zh-CN"> |
| 2331 | + <head> |
| 2332 | + <meta charset="utf-8" /> |
| 2333 | + <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 2334 | + <title>Email Outlook</title> |
| 2335 | + <style> |
| 2336 | + body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; padding: 40px; color: #0f172a; } |
| 2337 | + code { background: #f1f5f9; padding: 2px 6px; border-radius: 6px; } |
| 2338 | + </style> |
| 2339 | + </head> |
| 2340 | + <body> |
| 2341 | + <h1>前端尚未构建</h1> |
| 2342 | + <p>请在 <code>templates</code> 目录执行 <code>npm install</code> 与 <code>npm run build</code>。</p> |
| 2343 | + <p>开发模式可执行 <code>npm run dev</code>,并通过 Vite 本地地址访问。</p> |
| 2344 | + </body> |
| 2345 | + </html> |
| 2346 | + """, |
| 2347 | + 503, |
| 2348 | + {"Content-Type": "text/html; charset=utf-8"}, |
| 2349 | + ) |
| 2350 | + |
| 2351 | + |
2313 | 2352 | def _label_for_method(method: str) -> str: |
2314 | 2353 | return { |
2315 | 2354 | "graph_api": "Graph API", |
|
0 commit comments