fix: 修复多个关键问题

- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页
- 修复API拦截器:401错误不显示提示,直接跳转
- 增强验证码显示:图片尺寸从120x40增加到200x80
- 增大验证码字体:从28号增加到48号
- 优化验证码字符:排除易混淆的0和1
- 减少干扰线:从5条减少到3条,添加背景色优化
- 增强登录API日志:添加详细的调试日志
- 增强验证码生成和验证日志
- 优化异常处理和错误追踪

影响文件:
- src/router/index.ts
- src/api/request.ts
- app/services/auth_service.py
- app/api/v1/auth.py
- app/schemas/user.py

测试状态:
- 前端构建通过
- 后端语法检查通过
- 验证码显示效果优化完成

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-01-25 00:26:21 +08:00
commit e71181f0a3
150 changed files with 39549 additions and 0 deletions

245
app/api/v1/assets.py Normal file
View File

@@ -0,0 +1,245 @@
"""
资产管理API路由
"""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from app.core.deps import get_db, get_current_user
from app.schemas.asset import (
AssetCreate,
AssetUpdate,
AssetResponse,
AssetWithRelations,
AssetStatusHistoryResponse,
AssetStatusTransition,
AssetQueryParams
)
from app.services.asset_service import asset_service
router = APIRouter()
@router.get("/", response_model=List[AssetResponse])
def get_assets(
skip: int = Query(0, ge=0, description="跳过条数"),
limit: int = Query(20, ge=1, le=100, description="返回条数"),
keyword: Optional[str] = Query(None, description="搜索关键词"),
device_type_id: Optional[int] = Query(None, description="设备类型ID"),
organization_id: Optional[int] = Query(None, description="网点ID"),
status: Optional[str] = Query(None, description="状态"),
purchase_date_start: Optional[str] = Query(None, description="采购日期开始(YYYY-MM-DD)"),
purchase_date_end: Optional[str] = Query(None, description="采购日期结束(YYYY-MM-DD)"),
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
获取资产列表
- **skip**: 跳过条数
- **limit**: 返回条数最大100
- **keyword**: 搜索关键词(编码/名称/型号/序列号)
- **device_type_id**: 设备类型ID筛选
- **organization_id**: 网点ID筛选
- **status**: 状态筛选
- **purchase_date_start**: 采购日期开始
- **purchase_date_end**: 采购日期结束
"""
items, total = asset_service.get_assets(
db=db,
skip=skip,
limit=limit,
keyword=keyword,
device_type_id=device_type_id,
organization_id=organization_id,
status=status,
purchase_date_start=purchase_date_start,
purchase_date_end=purchase_date_end
)
return items
@router.get("/statistics")
def get_asset_statistics(
organization_id: Optional[int] = Query(None, description="网点ID筛选"),
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
获取资产统计信息
- **organization_id**: 网点ID筛选
返回资产总数、总价值、状态分布等统计信息
"""
return asset_service.get_statistics(db, organization_id)
@router.get("/{asset_id}", response_model=AssetWithRelations)
def get_asset(
asset_id: int,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
获取资产详情
- **asset_id**: 资产ID
返回资产详情及其关联信息
"""
return asset_service.get_asset(db, asset_id)
@router.get("/scan/{asset_code}", response_model=AssetWithRelations)
def scan_asset(
asset_code: str,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
扫码查询资产
- **asset_code**: 资产编码
通过扫描二维码查询资产详情
"""
return asset_service.scan_asset_by_code(db, asset_code)
@router.post("/", response_model=AssetResponse, status_code=status.HTTP_201_CREATED)
def create_asset(
obj_in: AssetCreate,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
创建资产
- **asset_name**: 资产名称
- **device_type_id**: 设备类型ID
- **brand_id**: 品牌ID可选
- **model**: 规格型号
- **serial_number**: 序列号
- **supplier_id**: 供应商ID
- **purchase_date**: 采购日期
- **purchase_price**: 采购价格
- **warranty_period**: 保修期(月)
- **organization_id**: 所属网点ID
- **location**: 存放位置
- **dynamic_attributes**: 动态字段值
- **remark**: 备注
"""
return asset_service.create_asset(
db=db,
obj_in=obj_in,
creator_id=current_user.id
)
@router.put("/{asset_id}", response_model=AssetResponse)
def update_asset(
asset_id: int,
obj_in: AssetUpdate,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
更新资产
- **asset_id**: 资产ID
- **asset_name**: 资产名称
- **brand_id**: 品牌ID
- **model**: 规格型号
- **serial_number**: 序列号
- **supplier_id**: 供应商ID
- **purchase_date**: 采购日期
- **purchase_price**: 采购价格
- **warranty_period**: 保修期
- **organization_id**: 所属网点ID
- **location**: 存放位置
- **dynamic_attributes**: 动态字段值
- **remark**: 备注
"""
return asset_service.update_asset(
db=db,
asset_id=asset_id,
obj_in=obj_in,
updater_id=current_user.id
)
@router.delete("/{asset_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_asset(
asset_id: int,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
删除资产
- **asset_id**: 资产ID
软删除资产
"""
asset_service.delete_asset(
db=db,
asset_id=asset_id,
deleter_id=current_user.id
)
return None
# ===== 状态管理 =====
@router.post("/{asset_id}/status", response_model=AssetResponse)
def change_asset_status(
asset_id: int,
status_transition: AssetStatusTransition,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
变更资产状态
- **asset_id**: 资产ID
- **new_status**: 目标状态
- **remark**: 备注
- **extra_data**: 额外数据
状态说明:
- pending: 待入库
- in_stock: 库存中
- in_use: 使用中
- transferring: 调拨中
- maintenance: 维修中
- pending_scrap: 待报废
- scrapped: 已报废
- lost: 已丢失
"""
return asset_service.change_asset_status(
db=db,
asset_id=asset_id,
status_transition=status_transition,
operator_id=current_user.id,
operator_name=current_user.real_name
)
@router.get("/{asset_id}/history", response_model=List[AssetStatusHistoryResponse])
def get_asset_status_history(
asset_id: int,
skip: int = Query(0, ge=0, description="跳过条数"),
limit: int = Query(50, ge=1, le=100, description="返回条数"),
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
获取资产状态历史
- **asset_id**: 资产ID
- **skip**: 跳过条数
- **limit**: 返回条数
返回资产的所有状态变更记录
"""
return asset_service.get_asset_status_history(db, asset_id, skip, limit)