From 8803444755305d50ec0b22a6015b5ddcb28bc63a Mon Sep 17 00:00:00 2001 From: snowy-yang <102860110+snowy-yang@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:35:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E6=8E=A5=E7=BA=BF?= =?UTF-8?q?=E4=B8=8A=20React=20=E6=9E=84=E5=BB=BA=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=EF=BC=8Clegacy=20=E6=A8=A1=E6=9D=BF=E9=99=8D=E7=BA=A7=E4=B8=BA?= =?UTF-8?q?=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为什么:前端迁移到 Vite 后,首页应直接返回 frontend/dist/index.html 字节(golden 口径随之变为构建产物原样字节),仅当 dist 未构建时 回退 Jinja2 渲染的 legacy 模板。同步挂载 /assets 静态目录、排除 frontend 的 node_modules/tsbuildinfo 入库、docker 构建只带 dist 不带源码、ruff/pyright 排除 frontend 目录(TS 侧由 tsc+vite 把关)。 --- .dockerignore | 7 +++++++ .gitignore | 5 +++++ pyproject.toml | 4 ++-- web/app.py | 9 +++++++++ web/routers/index.py | 37 +++++++++++++++++++++---------------- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/.dockerignore b/.dockerignore index 761b329..5661338 100644 --- a/.dockerignore +++ b/.dockerignore @@ -42,3 +42,10 @@ _css_ok.txt 云村小助手-网站开关.bat _audit_work/ venv/ +# 前端源码与依赖不进镜像(dist 构建产物保留,后端直接服务它) +frontend/node_modules/ +frontend/src/ +frontend/index.html +frontend/package*.json +frontend/tsconfig.json +frontend/vite.config.ts diff --git a/.gitignore b/.gitignore index 13083fb..8de473a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,11 @@ __pycache__ # _mkpkg.py 会在项目根写入打包标记文件(内容为文件数/体积或 public 标记) _pkg_ok.txt +# 前端工程:node_modules 不进库;dist 构建产物必须提交(后端直接服务它, +# Docker COPY . . 无需 node 即可工作) +frontend/node_modules/ +frontend/*.tsbuildinfo + # 运行时敏感数据与私有文档——公开包买家若将本项目纳入 git 管理, # 以下内容绝不能进版本库(用户数据库/网易云 Cookie/日志/审计文档) data/ diff --git a/pyproject.toml b/pyproject.toml index f2b6b0c..a8e75a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ package = false [tool.ruff] target-version = "py312" line-length = 120 -extend-exclude = [".venv", ".zcode", "templates", "static", "data"] +extend-exclude = [".venv", ".zcode", "templates", "static", "frontend", "data"] [tool.ruff.lint] # E/F 基础错误、I 导入排序、B bugbear、UP 升级、C4 推导式、ASYNC 异步陷阱、S 安全、RUF @@ -90,7 +90,7 @@ include = [ "music_downloader", "_mkpkg.py", ] -exclude = [".venv", ".zcode", "data", "downloads", "templates", "static"] +exclude = [".venv", ".zcode", "data", "downloads", "templates", "static", "frontend"] venvPath = "." venv = ".venv" pythonVersion = "3.12" diff --git a/web/app.py b/web/app.py index be32f1d..413f609 100644 --- a/web/app.py +++ b/web/app.py @@ -114,6 +114,15 @@ def create_app(api_prefix: str = '', *, start_monitor: bool = True) -> FastAPI: if _static_dir.is_dir(): app.mount('/static', StaticFiles(directory=str(_static_dir)), name='static') + # React 构建产物(vite build → frontend/dist,hash 指纹资源挂 /assets) + _dist_dir = Path(__file__).resolve().parent.parent / 'frontend' / 'dist' + if (_dist_dir / 'assets').is_dir(): + app.mount( + '/assets', + StaticFiles(directory=str(_dist_dir / 'assets')), + name='assets', + ) + # 业务路由:统一挂到 api_prefix(默认空前缀,即前端使用的根路径) app.include_router(system_router.router, prefix=api_prefix, tags=['system']) app.include_router(site_router.router, prefix=api_prefix, tags=['site']) diff --git a/web/routers/index.py b/web/routers/index.py index 7ea8677..2ab14dd 100644 --- a/web/routers/index.py +++ b/web/routers/index.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- """首页与静态资源。 -- GET / 首页:渲染 templates/index.html,禁止缓存 -- 静态资源由 Starlette StaticFiles 挂载 /static(见 web/app.py) +- GET / 首页:优先服务 React 构建产物 frontend/dist/index.html; + dist 未构建时回退旧版 templates/index.html(平滑过渡) +- 静态资源由 Starlette StaticFiles 挂载 /assets 与 /static(见 web/app.py) """ from pathlib import Path @@ -11,24 +12,28 @@ from fastapi.responses import HTMLResponse router = APIRouter() -_TEMPLATE = Path(__file__).resolve().parent.parent.parent / 'templates' / 'index.html' +_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent +_DIST_INDEX = _PROJECT_ROOT / 'frontend' / 'dist' / 'index.html' +_LEGACY_TEMPLATE = _PROJECT_ROOT / 'templates' / 'index.html' + + +def _render_index() -> str: + """渲染首页 HTML:React 构建产物优先,缺失时回退旧模板。""" + if _DIST_INDEX.is_file(): + return _DIST_INDEX.read_text(encoding='utf-8') + import jinja2 + + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(str(_LEGACY_TEMPLATE.parent)), + autoescape=True, + ) + return env.get_template(_LEGACY_TEMPLATE.name).render() @router.get('/', response_class=HTMLResponse) async def index(request: Request) -> HTMLResponse: - """首页(禁止缓存,确保前端更新即时生效)。 - - 用 Jinja2 以与历史模板渲染一致的默认口径(keep_trailing_newline=False), - 使输出逐字节不变。 - """ - import jinja2 - - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(str(_TEMPLATE.parent)), - autoescape=True, - ) - html = env.get_template(_TEMPLATE.name).render() + """首页(禁止缓存,确保前端更新即时生效)。""" return HTMLResponse( - content=html, + content=_render_index(), headers={'Cache-Control': 'no-cache, no-store, must-revalidate'}, )