fix: CORS/API 500 issues, switch to native tabbar, restore quick-actions
- Backend: guest UUID format fix, /auth/me guest branch, UUID validation in deps.py, CORS config fix - Frontend: switch to native tabbar (custom: false), cleanup App.vue, redesign quick-actions with colored icons, conditional wechat login, proxy API requests via Vite
This commit is contained in:
@@ -82,7 +82,7 @@ async def login(
|
||||
|
||||
@router.post("/login/guest")
|
||||
async def guest_login():
|
||||
guest_id = f"guest_{uuid.uuid4().hex[:12]}"
|
||||
guest_id = str(uuid.uuid4())
|
||||
access_token = create_access_token(
|
||||
{"sub": guest_id, "tier": "guest", "role": "guest", "is_guest": True},
|
||||
expires_delta=timedelta(hours=24)
|
||||
@@ -109,8 +109,18 @@ async def refresh(data: RefreshRequest):
|
||||
if not payload or payload.get("type") != "refresh":
|
||||
raise HTTPException(status_code=401, detail="Invalid refresh token")
|
||||
|
||||
# 保留游客/角色等信息
|
||||
extra = {}
|
||||
if payload.get("is_guest"):
|
||||
extra = {"is_guest": True, "tier": "guest", "role": "guest"}
|
||||
else:
|
||||
extra = {
|
||||
"tier": payload.get("tier", "free"),
|
||||
"role": payload.get("role", "user"),
|
||||
}
|
||||
|
||||
return {
|
||||
"access_token": create_access_token({"sub": payload["sub"]}),
|
||||
"access_token": create_access_token({"sub": payload["sub"], **extra}),
|
||||
"token_type": "bearer",
|
||||
}
|
||||
|
||||
@@ -127,6 +137,18 @@ async def get_me(
|
||||
if not payload:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
if payload.get("is_guest"):
|
||||
return {
|
||||
"id": payload["sub"],
|
||||
"phone": None,
|
||||
"username": "游客用户",
|
||||
"tier": "guest",
|
||||
"role": "guest",
|
||||
"is_guest": True,
|
||||
"settings": {},
|
||||
"created_at": None,
|
||||
}
|
||||
|
||||
result = await db.execute(select(User).where(User.id == payload["sub"]))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
|
||||
@@ -2,6 +2,7 @@ from fastapi import HTTPException, Depends, Header
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from app.core.security import decode_token
|
||||
from typing import Optional
|
||||
import uuid
|
||||
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
@@ -23,7 +24,16 @@ async def get_current_user_id(
|
||||
if not payload:
|
||||
raise HTTPException(status_code=401, detail="Invalid or expired token")
|
||||
|
||||
return payload.get("sub")
|
||||
user_id = payload.get("sub")
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=401, detail="Invalid token payload")
|
||||
|
||||
try:
|
||||
uuid.UUID(user_id)
|
||||
except (ValueError, AttributeError):
|
||||
raise HTTPException(status_code=401, detail="Token expired, please login again")
|
||||
|
||||
return user_id
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
|
||||
+2
-1
@@ -31,11 +31,12 @@ app = FastAPI(
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
debug=settings.DEBUG,
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[settings.FRONTEND_URL, "*"],
|
||||
allow_origins=[settings.FRONTEND_URL],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
|
||||
+3
-17
@@ -20,26 +20,12 @@ html, body, #app {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Fix: make uni-page scrollable and properly sized for fixed tabbar */
|
||||
/* Let uni-app framework manage layout for native tabbar */
|
||||
uni-page {
|
||||
height: calc(100% - 50px) !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
-webkit-overflow-scrolling: touch !important;
|
||||
}
|
||||
uni-page-body, uni-page-wrapper {
|
||||
uni-page-body {
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
-webkit-overflow-scrolling: touch !important;
|
||||
}
|
||||
|
||||
/* Ensure the page head doesn't block scrolling */
|
||||
uni-page-head {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* The uni-tabbar is already position:fixed by the framework */
|
||||
uni-tabbar {
|
||||
z-index: 999 !important;
|
||||
min-height: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
"backgroundColor": "#f5f5f5"
|
||||
},
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"custom": false,
|
||||
"color": "#666666",
|
||||
"selectedColor": "#1890ff",
|
||||
"backgroundColor": "#ffffff",
|
||||
|
||||
@@ -94,20 +94,28 @@
|
||||
|
||||
<view class="quick-actions">
|
||||
<view class="action-item" @click="goToPage('/pages/translate/translate')">
|
||||
<text class="action-icon">🔤</text>
|
||||
<text class="action-text">翻译</text>
|
||||
<view class="action-icon-wrap" style="background:#e6f7ff">
|
||||
<text class="action-icon-text" style="color:#1890ff">译</text>
|
||||
</view>
|
||||
<text class="action-label">智能翻译</text>
|
||||
</view>
|
||||
<view class="action-item" @click="hasLogin ? goToPage('/pages/customers/customers') : goToLogin()">
|
||||
<text class="action-icon">👥</text>
|
||||
<text class="action-text">客户</text>
|
||||
<view class="action-icon-wrap" style="background:#fff7e6">
|
||||
<text class="action-icon-text" style="color:#fa8c16">客</text>
|
||||
</view>
|
||||
<text class="action-label">客户管理</text>
|
||||
</view>
|
||||
<view class="action-item" @click="hasLogin ? goToPage('/pages/marketing/marketing') : goToLogin()">
|
||||
<text class="action-icon">📢</text>
|
||||
<text class="action-text">营销</text>
|
||||
<view class="action-icon-wrap" style="background:#f6ffed">
|
||||
<text class="action-icon-text" style="color:#52c41a">营</text>
|
||||
</view>
|
||||
<text class="action-label">营销文案</text>
|
||||
</view>
|
||||
<view class="action-item" @click="hasLogin ? goToPage('/pages/quotation/quotation') : goToLogin()">
|
||||
<text class="action-icon">📄</text>
|
||||
<text class="action-text">报价</text>
|
||||
<view class="action-icon-wrap" style="background:#fff0f6">
|
||||
<text class="action-icon-text" style="color:#eb2f96">价</text>
|
||||
</view>
|
||||
<text class="action-label">报价单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -716,39 +724,6 @@ const copyTryResult = () => {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.followup-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16rpx;
|
||||
@@ -776,6 +751,42 @@ const copyTryResult = () => {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-icon-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-icon-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.more-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
|
||||
@@ -83,13 +83,13 @@
|
||||
{{ isRegister ? '已有账号?立即登录' : '没有账号?立即注册' }}
|
||||
</text>
|
||||
|
||||
<view class="divider" v-if="false">
|
||||
<view class="divider" v-if="isWechatAvailable">
|
||||
<view class="line"></view>
|
||||
<text class="text">或</text>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
|
||||
<button class="wechat-btn" @click="handleWechatLogin" v-if="false">
|
||||
<button class="wechat-btn" @click="handleWechatLogin" v-if="isWechatAvailable">
|
||||
<text class="wechat-icon">W</text>
|
||||
微信一键登录
|
||||
</button>
|
||||
@@ -120,6 +120,11 @@ const isRegister = ref(false)
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const showForm = ref(false)
|
||||
const isWechatAvailable = ref(false)
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
isWechatAvailable.value = true
|
||||
// #endif
|
||||
|
||||
const toggleMode = () => {
|
||||
isRegister.value = !isRegister.value
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const BASE_URL = 'http://localhost:8000/api/v1'
|
||||
export const BASE_URL = '/api/v1'
|
||||
|
||||
const getAuthHeader = () => {
|
||||
const token = uni.getStorageSync('token')
|
||||
|
||||
Reference in New Issue
Block a user