后端: - 客户模块 CRUD+搜索 (api/services/repositories 分层) - 合同模块: FG单号规则/意向单转正式单/订单创建自动快照客户信息 - 优化下料: FFD首递减装箱算法(利用率统计/余料/未排入清单) 前端 (Vue3+TS+Element Plus): - 7页面: 登录/工作台/订单中心/合同/客户/样板库/优化下料 - 自研二维窗型画布渲染器: 解析drawing_data渲染玻璃区域/外框/ 扇开向标记/尺寸标注, 支持缩放平移复位 - 样板库与订单联动(用此样板下单自动带图纸) - axios统一envelope拦截/登录守卫/付费墙提示 工程: - SPA history 回退路由, 构建产物挂载后端 - 全接口回归 6/6 通过
87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
"""应用入口:FastAPI 装配、异常处理、静态资源挂载、开发服务器。"""
|
||
from pathlib import Path
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.exceptions import RequestValidationError
|
||
from fastapi.staticfiles import StaticFiles
|
||
|
||
from huamenchuang.api.router import api_router
|
||
from huamenchuang.core.database import init_db
|
||
from huamenchuang.core.deps import ModuleLockedError, NotAuthedError
|
||
from huamenchuang.core.response import fail, ok, paywall
|
||
|
||
app = FastAPI(title="华门窗云平台", version="0.1.0")
|
||
|
||
|
||
@app.on_event("startup")
|
||
async def startup() -> None:
|
||
await init_db()
|
||
|
||
|
||
@app.exception_handler(NotAuthedError)
|
||
async def auth_handler(request: Request, exc: NotAuthedError):
|
||
return _json({"code": 401, "message": str(exc), "data": "", "other_data": "", "other_data2": ""})
|
||
|
||
|
||
@app.exception_handler(ModuleLockedError)
|
||
async def module_locked_handler(request: Request, exc: ModuleLockedError):
|
||
return _json(paywall(exc.module))
|
||
|
||
|
||
@app.exception_handler(RequestValidationError)
|
||
async def validation_handler(request: Request, exc: RequestValidationError):
|
||
"""参数校验失败 → 422 语义 envelope(列出字段,不泄漏内部细节)。"""
|
||
fields = [f"{'.'.join(str(x) for x in e['loc'][1:])}" for e in exc.errors()]
|
||
return _json(fail(f"参数错误: {', '.join(fields)}"))
|
||
|
||
|
||
@app.exception_handler(Exception)
|
||
async def unhandled_handler(request: Request, exc: Exception):
|
||
"""兜底异常:统一 500 envelope(不回栈信息)。"""
|
||
return _json(fail("服务器内部错误,请稍后再试"))
|
||
|
||
|
||
def _json(payload: dict):
|
||
from fastapi.responses import JSONResponse
|
||
return JSONResponse(payload)
|
||
|
||
|
||
@api_router.get("/health", tags=["系统"])
|
||
async def health():
|
||
return ok(data={"status": "up"}, message="ok")
|
||
|
||
|
||
app.include_router(api_router)
|
||
|
||
from fastapi.responses import FileResponse # noqa: E402
|
||
|
||
# 静态资源:样板平面图(data/windoor-images)
|
||
_imgdir = Path(__file__).resolve().parents[2] / "data" / "windoor-images"
|
||
if _imgdir.exists():
|
||
app.mount("/static/windoor-images", StaticFiles(directory=str(_imgdir)), name="windoor-images")
|
||
|
||
# 前端构建产物挂载(前后端规范:构建到后端目录)+ history 路由回退
|
||
_dist = Path(__file__).resolve().parents[2] / "frontend" / "dist"
|
||
if _dist.exists():
|
||
app.mount("/assets", StaticFiles(directory=str(_dist / "assets")), name="assets")
|
||
|
||
@app.get("/{full_path:path}", include_in_schema=False)
|
||
async def spa_fallback(full_path: str):
|
||
"""非 API/静态路径一律返回 index.html(前端 history 模式)。"""
|
||
if full_path.startswith(("api/", "static/")):
|
||
return JSONResponse(
|
||
{"code": 0, "message": "接口不存在", "data": "", "other_data": "", "other_data2": ""})
|
||
target = _dist / full_path
|
||
if full_path and target.is_file():
|
||
return FileResponse(target)
|
||
return FileResponse(_dist / "index.html")
|
||
|
||
|
||
def main() -> None:
|
||
import uvicorn
|
||
uvicorn.run("huamenchuang.main:app", host="0.0.0.0", port=8000, reload=True)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|