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")
|
@router.post("/login/guest")
|
||||||
async def guest_login():
|
async def guest_login():
|
||||||
guest_id = f"guest_{uuid.uuid4().hex[:12]}"
|
guest_id = str(uuid.uuid4())
|
||||||
access_token = create_access_token(
|
access_token = create_access_token(
|
||||||
{"sub": guest_id, "tier": "guest", "role": "guest", "is_guest": True},
|
{"sub": guest_id, "tier": "guest", "role": "guest", "is_guest": True},
|
||||||
expires_delta=timedelta(hours=24)
|
expires_delta=timedelta(hours=24)
|
||||||
@@ -109,8 +109,18 @@ async def refresh(data: RefreshRequest):
|
|||||||
if not payload or payload.get("type") != "refresh":
|
if not payload or payload.get("type") != "refresh":
|
||||||
raise HTTPException(status_code=401, detail="Invalid refresh token")
|
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 {
|
return {
|
||||||
"access_token": create_access_token({"sub": payload["sub"]}),
|
"access_token": create_access_token({"sub": payload["sub"], **extra}),
|
||||||
"token_type": "bearer",
|
"token_type": "bearer",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +137,18 @@ async def get_me(
|
|||||||
if not payload:
|
if not payload:
|
||||||
raise HTTPException(status_code=401, detail="Invalid token")
|
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"]))
|
result = await db.execute(select(User).where(User.id == payload["sub"]))
|
||||||
user = result.scalar_one_or_none()
|
user = result.scalar_one_or_none()
|
||||||
if not user:
|
if not user:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from fastapi import HTTPException, Depends, Header
|
|||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
from app.core.security import decode_token
|
from app.core.security import decode_token
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import uuid
|
||||||
|
|
||||||
security = HTTPBearer(auto_error=False)
|
security = HTTPBearer(auto_error=False)
|
||||||
|
|
||||||
@@ -23,7 +24,16 @@ async def get_current_user_id(
|
|||||||
if not payload:
|
if not payload:
|
||||||
raise HTTPException(status_code=401, detail="Invalid or expired token")
|
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(
|
async def get_current_user(
|
||||||
|
|||||||
+2
-1
@@ -31,11 +31,12 @@ app = FastAPI(
|
|||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
docs_url="/docs",
|
docs_url="/docs",
|
||||||
redoc_url="/redoc",
|
redoc_url="/redoc",
|
||||||
|
debug=settings.DEBUG,
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=[settings.FRONTEND_URL, "*"],
|
allow_origins=[settings.FRONTEND_URL],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|||||||
+3
-17
@@ -20,26 +20,12 @@ html, body, #app {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix: make uni-page scrollable and properly sized for fixed tabbar */
|
/* Let uni-app framework manage layout for native tabbar */
|
||||||
uni-page {
|
uni-page {
|
||||||
height: calc(100% - 50px) !important;
|
|
||||||
overflow-y: auto !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-y: auto !important;
|
||||||
overflow-x: hidden !important;
|
min-height: 100% !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;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
"backgroundColor": "#f5f5f5"
|
"backgroundColor": "#f5f5f5"
|
||||||
},
|
},
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"custom": true,
|
"custom": false,
|
||||||
"color": "#666666",
|
"color": "#666666",
|
||||||
"selectedColor": "#1890ff",
|
"selectedColor": "#1890ff",
|
||||||
"backgroundColor": "#ffffff",
|
"backgroundColor": "#ffffff",
|
||||||
|
|||||||
@@ -94,20 +94,28 @@
|
|||||||
|
|
||||||
<view class="quick-actions">
|
<view class="quick-actions">
|
||||||
<view class="action-item" @click="goToPage('/pages/translate/translate')">
|
<view class="action-item" @click="goToPage('/pages/translate/translate')">
|
||||||
<text class="action-icon">🔤</text>
|
<view class="action-icon-wrap" style="background:#e6f7ff">
|
||||||
<text class="action-text">翻译</text>
|
<text class="action-icon-text" style="color:#1890ff">译</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-label">智能翻译</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="hasLogin ? goToPage('/pages/customers/customers') : goToLogin()">
|
<view class="action-item" @click="hasLogin ? goToPage('/pages/customers/customers') : goToLogin()">
|
||||||
<text class="action-icon">👥</text>
|
<view class="action-icon-wrap" style="background:#fff7e6">
|
||||||
<text class="action-text">客户</text>
|
<text class="action-icon-text" style="color:#fa8c16">客</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-label">客户管理</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="hasLogin ? goToPage('/pages/marketing/marketing') : goToLogin()">
|
<view class="action-item" @click="hasLogin ? goToPage('/pages/marketing/marketing') : goToLogin()">
|
||||||
<text class="action-icon">📢</text>
|
<view class="action-icon-wrap" style="background:#f6ffed">
|
||||||
<text class="action-text">营销</text>
|
<text class="action-icon-text" style="color:#52c41a">营</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-label">营销文案</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="hasLogin ? goToPage('/pages/quotation/quotation') : goToLogin()">
|
<view class="action-item" @click="hasLogin ? goToPage('/pages/quotation/quotation') : goToLogin()">
|
||||||
<text class="action-icon">📄</text>
|
<view class="action-icon-wrap" style="background:#fff0f6">
|
||||||
<text class="action-text">报价</text>
|
<text class="action-icon-text" style="color:#eb2f96">价</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-label">报价单</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -716,39 +724,6 @@ const copyTryResult = () => {
|
|||||||
padding: 40rpx;
|
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 {
|
.followup-card {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
@@ -776,6 +751,42 @@ const copyTryResult = () => {
|
|||||||
margin-top: 12rpx;
|
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 {
|
.more-section {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
|
|||||||
@@ -83,13 +83,13 @@
|
|||||||
{{ isRegister ? '已有账号?立即登录' : '没有账号?立即注册' }}
|
{{ isRegister ? '已有账号?立即登录' : '没有账号?立即注册' }}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<view class="divider" v-if="false">
|
<view class="divider" v-if="isWechatAvailable">
|
||||||
<view class="line"></view>
|
<view class="line"></view>
|
||||||
<text class="text">或</text>
|
<text class="text">或</text>
|
||||||
<view class="line"></view>
|
<view class="line"></view>
|
||||||
</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>
|
<text class="wechat-icon">W</text>
|
||||||
微信一键登录
|
微信一键登录
|
||||||
</button>
|
</button>
|
||||||
@@ -120,6 +120,11 @@ const isRegister = ref(false)
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const showForm = ref(false)
|
const showForm = ref(false)
|
||||||
|
const isWechatAvailable = ref(false)
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
isWechatAvailable.value = true
|
||||||
|
// #endif
|
||||||
|
|
||||||
const toggleMode = () => {
|
const toggleMode = () => {
|
||||||
isRegister.value = !isRegister.value
|
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 getAuthHeader = () => {
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
|
|||||||
Reference in New Issue
Block a user