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
|
启动命令: uvicorn backend.app.main:app --reload
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
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.config import get_settings
|
||||||
from backend.app.core.exceptions import AppException
|
from backend.app.core.exceptions import AppException
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
app = FastAPI(title=settings.app_name)
|
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 中统一定义
|
# 挂载所有 API 路由,路由前缀在 router.py 中统一定义
|
||||||
app.include_router(api_router)
|
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>
|
<template>
|
||||||
import { useToast } from "../composables/useToast";
|
|
||||||
const toast = useToast();
|
|
||||||
<section class="page">
|
<section class="page">
|
||||||
<!-- <div class="page-hero">-->
|
<!-- <div class="page-hero">-->
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
@ -105,6 +103,9 @@ const toast = useToast();
|
|||||||
import { computed, onMounted, reactive, ref } from "vue";
|
import { computed, onMounted, reactive, ref } from "vue";
|
||||||
|
|
||||||
import { createCustomer, fetchCustomersPage } from "../mockApi";
|
import { createCustomer, fetchCustomersPage } from "../mockApi";
|
||||||
|
import { useToast } from "../composables/useToast";
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
/** 数据加载状态 */
|
/** 数据加载状态 */
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|||||||
@ -4,8 +4,6 @@
|
|||||||
左侧展示系统介绍信息,右侧为登录卡片,已登录时自动跳转。
|
左侧展示系统介绍信息,右侧为登录卡片,已登录时自动跳转。
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
import { useToast } from "../composables/useToast";
|
|
||||||
const toast = useToast();
|
|
||||||
<section class="login-shell">
|
<section class="login-shell">
|
||||||
<div class="hero-panel">
|
<div class="hero-panel">
|
||||||
<span class="eyebrow">Sales Workbench</span>
|
<span class="eyebrow">Sales Workbench</span>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user