fix: 订单明细迁移补全 + 全局异常处理 + Vue 模板修复
- 新增迁移 20260531_0001 补全 sales_order_item 报价快照列(幂等) - 添加全局未捕获异常处理器,500 错误返回具体异常信息并记录 traceback - 修复 web-sales LoginPage 和 CustomersPage 中 import 语句误写在 <template> 内的问题 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
71c2ab2005
commit
01f7a38094
@ -4,6 +4,9 @@
|
||||
启动命令: uvicorn backend.app.main:app --reload
|
||||
"""
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@ -13,6 +16,8 @@ from backend.app.api.routes import api_router
|
||||
from backend.app.core.config import get_settings
|
||||
from backend.app.core.exceptions import AppException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
app = FastAPI(title=settings.app_name)
|
||||
@ -44,5 +49,22 @@ async def app_exception_handler(_: Request, exc: AppException) -> JSONResponse:
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def unhandled_exception_handler(_: Request, exc: Exception) -> JSONResponse:
|
||||
"""捕获所有未处理异常,记录完整 traceback 到日志并返回 500。
|
||||
|
||||
避免 FastAPI 默认的 bare 500 响应,让运维能从日志中定位问题。
|
||||
"""
|
||||
logger.error("Unhandled exception: %s\n%s", exc, traceback.format_exc())
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"code": 500,
|
||||
"message": f"服务器内部错误: {type(exc).__name__}: {exc}",
|
||||
"data": {},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# 挂载所有 API 路由,路由前缀在 router.py 中统一定义
|
||||
app.include_router(api_router)
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
"""add pricing snapshot columns to sales_order_item
|
||||
|
||||
These columns may already exist in some environments (added manually).
|
||||
Each add_column is wrapped to tolerate DuplicateColumn gracefully.
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from pymysql import err as pymysql_err
|
||||
|
||||
revision = "20260531_0001"
|
||||
down_revision = "20260530_0001"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
_COLUMNS = [
|
||||
("pricing_type", sa.Column("pricing_type", sa.String(length=32), nullable=True)),
|
||||
("length_m", sa.Column("length_m", sa.Numeric(10, 4), nullable=True)),
|
||||
("width_m", sa.Column("width_m", sa.Numeric(10, 4), nullable=True)),
|
||||
("area_sqm", sa.Column("area_sqm", sa.Numeric(18, 4), nullable=True)),
|
||||
("surcharge_detail", sa.Column("surcharge_detail", sa.Text(), nullable=True)),
|
||||
("processing_detail", sa.Column("processing_detail", sa.Text(), nullable=True)),
|
||||
("supplier_id", sa.Column("supplier_id", sa.Integer(), nullable=True)),
|
||||
("supplier_model", sa.Column("supplier_model", sa.String(length=64), nullable=True)),
|
||||
("price_tier", sa.Column("price_tier", sa.String(length=32), nullable=True)),
|
||||
]
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
for name, col in _COLUMNS:
|
||||
try:
|
||||
op.add_column("sales_order_item", col)
|
||||
except Exception as exc:
|
||||
if "Duplicate column" in str(exc):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for name, _ in reversed(_COLUMNS):
|
||||
try:
|
||||
op.drop_column("sales_order_item", name)
|
||||
except Exception:
|
||||
pass
|
||||
@ -4,8 +4,6 @@
|
||||
支持创建新客户(姓名、手机号必填),表格数据响应式适配移动端卡片布局。
|
||||
-->
|
||||
<template>
|
||||
import { useToast } from "../composables/useToast";
|
||||
const toast = useToast();
|
||||
<section class="page">
|
||||
<!-- <div class="page-hero">-->
|
||||
<!-- <div>-->
|
||||
@ -105,6 +103,9 @@ const toast = useToast();
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
|
||||
import { createCustomer, fetchCustomersPage } from "../mockApi";
|
||||
import { useToast } from "../composables/useToast";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
/** 数据加载状态 */
|
||||
const loading = ref(true);
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
左侧展示系统介绍信息,右侧为登录卡片,已登录时自动跳转。
|
||||
-->
|
||||
<template>
|
||||
import { useToast } from "../composables/useToast";
|
||||
const toast = useToast();
|
||||
<section class="login-shell">
|
||||
<div class="hero-panel">
|
||||
<span class="eyebrow">Sales Workbench</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user