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:
taiyi 2026-05-31 14:07:28 +08:00
parent 71c2ab2005
commit 01f7a38094
4 changed files with 70 additions and 4 deletions

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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>