feat: 管理后台完整可用 + 注册登录记日志 + 提取信息结构化展示 + 微信配置就绪

- 管理后台用户/统计/日志/配置四页签全部对接真实后端API
- auth注册/登录/游客/微信登录事件写入usage_logs表
- 提取信息结果从原始JSON改为卡片式字段列表(中文标签)
- 管理后台搜索按钮增加加载态和结果数提示
- 配置WECHAT_APP_ID/WECHAT_APP_SECRET
- 客户/产品/报价单CRUD页面完整(导出导入批量操作)
This commit is contained in:
TradeMate Dev
2026-05-18 23:50:48 +08:00
parent 32d2b57df7
commit 4755cc75ba
14 changed files with 1495 additions and 119 deletions
+27 -7
View File
@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException, status, Header
from fastapi import APIRouter, Depends, HTTPException, status, Header, Request
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
@@ -9,6 +9,7 @@ from app.models.user import User
from app.core.security import hash_password, verify_password, create_access_token, create_refresh_token, decode_token
from pydantic import BaseModel, EmailStr
from datetime import datetime, timedelta
from app.services.admin import AdminService
router = APIRouter()
@@ -37,7 +38,7 @@ class RefreshRequest(BaseModel):
@router.post("/register")
async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
async def register(data: RegisterRequest, request: Request, db: AsyncSession = Depends(get_db)):
existing = await db.execute(select(User).where(User.phone == data.phone))
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Phone already registered")
@@ -51,6 +52,9 @@ async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
db.add(user)
await db.flush()
client_ip = request.client.host if request.client else None
await AdminService(db).log_usage(str(user.id), "user.register", {"phone": data.phone}, ip=client_ip)
return {
"id": str(user.id),
"phone": user.phone,
@@ -63,12 +67,17 @@ async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
@router.post("/login", response_model=LoginResponse)
async def login(
data: LoginRequest,
request: Request,
db: AsyncSession = Depends(get_db),
):
phone = data.username or data.phone
if not phone:
login_id = data.username or data.phone
if not login_id:
raise HTTPException(status_code=422, detail="phone required")
result = await db.execute(select(User).where(User.phone == phone))
result = await db.execute(
select(User).where(
(User.phone == login_id) | (User.username == login_id)
)
)
user = result.scalar_one_or_none()
if not user or not verify_password(data.password, user.password_hash):
@@ -77,6 +86,9 @@ async def login(
detail="Invalid credentials",
)
client_ip = request.client.host if request.client else None
await AdminService(db).log_usage(str(user.id), "user.login", {"login_id": login_id}, ip=client_ip)
return LoginResponse(
access_token=create_access_token({"sub": str(user.id), "tier": user.tier, "role": user.role}),
refresh_token=create_refresh_token({"sub": str(user.id)}),
@@ -90,7 +102,7 @@ async def login(
@router.post("/login/guest")
async def guest_login():
async def guest_login(request: Request, db: AsyncSession = Depends(get_db)):
guest_id = str(uuid.uuid4())
access_token = create_access_token(
{"sub": guest_id, "tier": "guest", "role": "guest", "is_guest": True},
@@ -98,6 +110,9 @@ async def guest_login():
)
refresh_token = create_refresh_token({"sub": guest_id, "is_guest": True})
client_ip = request.client.host if request.client else None
await AdminService(db).log_usage(guest_id, "user.login_guest", {}, ip=client_ip)
return LoginResponse(
access_token=access_token,
refresh_token=refresh_token,
@@ -197,7 +212,7 @@ async def wechat_config():
@router.post("/wechat-login")
async def wechat_login(data: WeChatLoginRequest, db: AsyncSession = Depends(get_db)):
async def wechat_login(data: WeChatLoginRequest, request: Request, db: AsyncSession = Depends(get_db)):
from app.services.wechat import wechat_service
session = await wechat_service.code2session(data.code)
@@ -207,6 +222,7 @@ async def wechat_login(data: WeChatLoginRequest, db: AsyncSession = Depends(get_
openid = session.get("openid")
result = await db.execute(select(User).where(User.wechat_openid == openid))
user = result.scalar_one_or_none()
is_new = False
if not user:
user = User(
@@ -216,6 +232,10 @@ async def wechat_login(data: WeChatLoginRequest, db: AsyncSession = Depends(get_
)
db.add(user)
await db.flush()
is_new = True
client_ip = request.client.host if request.client else None
await AdminService(db).log_usage(str(user.id), "user.wechat_login", {"is_new": is_new}, ip=client_ip)
return LoginResponse(
access_token=create_access_token({"sub": str(user.id), "tier": user.tier, "role": user.role}),