feat(auth): 实现用户退出登录功能

- 新增 logout API 接口用于服务端登出
- 在 auth store 中实现异步 logout 方法,调用后端接口并清理本地状态
- 在订单列表页面添加退出登录按钮及确认弹窗
- 更新 Vite 配置注释以支持多环境切换
- 在详情页展示产品折扣信息并格式化显示

---
- 增加了 `/logout` 后端接口调用
- 完善了前端登出逻辑,包括异常处理和状态清理
- 提供了用户登出确认交互流程
- 折扣率计算函数 `getProductDiscountRate` 已添加并应用
- 样式调整适配新增的顶部导航栏布局
master
chenhao 2025-12-01 16:25:32 +08:00
parent 3271644a01
commit c524d8a4cd
4 changed files with 141 additions and 12 deletions

View File

@ -18,3 +18,10 @@ export const login = (params: LoginParams): Promise<AxiosResponse<ApiResponse<{
return http.post('/login', formData)
}
/**
* 退
*/
export const logout = (): Promise<AxiosResponse<ApiResponse<any>>> => {
return http.post('/logout')
}

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { login } from '@/api/auth'
import { login, logout as logoutApi } from '@/api/auth'
import type { LoginParams } from '@/types'
interface AuthState {
@ -55,7 +55,14 @@ export const useAuthStore = defineStore('auth', {
/**
*
*/
logout() {
async logout() {
try {
// 调用后端logout接口清除session
await logoutApi()
} catch (error) {
console.warn('服务器退出登录失败,仅清除本地状态:', error)
} finally {
// 清除本地状态
this.token = null
this.userInfo = null
this.isAuthenticated = false
@ -64,6 +71,7 @@ export const useAuthStore = defineStore('auth', {
localStorage.removeItem('isAuthenticated')
// 注意:这里不清除保存的用户名和密码,以便下次自动填充
}
},
/**

View File

@ -188,6 +188,10 @@
<span class="product-label">单价</span>
<span class="product-value">{{ formatAmount(product.price) }}</span>
</div>
<div class="product-row">
<span class="product-label">折扣</span>
<span class="product-value">{{ getProductDiscountRate(product.discount) }}</span>
</div>
<div class="product-row">
<span class="product-label">税率</span>
<span class="product-value">
@ -233,6 +237,10 @@
<span class="product-label">单价</span>
<span class="product-value">{{ formatAmount(product.price) }}</span>
</div>
<div class="product-row">
<span class="product-label">折扣</span>
<span class="product-value">{{ getProductDiscountRate(product.discount) }}</span>
</div>
<div class="product-row">
<span class="product-label">税率</span>
<span class="product-value">
@ -278,6 +286,10 @@
<span class="product-label">单价</span>
<span class="product-value">{{ formatAmount(product.price) }}</span>
</div>
<div class="product-row">
<span class="product-label">折扣</span>
<span class="product-value">{{ getProductDiscountRate(product.discount) }}</span>
</div>
<div class="product-row">
<span class="product-label">税率</span>
<span class="product-value">
@ -617,6 +629,9 @@ const getTotalAmount = () => {
return total
}
const getProductDiscountRate=(discount: number)=>{
return (discount * 100).toFixed(1) + '%'
}
//
const getDiscountRate = () => {
if (!currentOrderInfo.value || !currentOrderInfo.value.discountFold) {

View File

@ -1,5 +1,23 @@
<template>
<div class="order-list-page">
<!-- 顶部导航栏 -->
<div class="header-container">
<div class="header-content">
<h1 class="page-title">订单审批</h1>
<div class="header-actions">
<van-button
type="default"
size="small"
icon="logout"
@click="handleLogout"
class="logout-btn"
>
退出
</van-button>
</div>
</div>
</div>
<!-- 搜索栏 -->
<div class="search-container">
<van-search
@ -122,11 +140,14 @@ import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useOrderStore } from '@/store/order'
import { useAuthStore } from '@/store/auth'
import { formatOrderStatus, formatAmount, formatDate } from '@/utils'
import { showConfirmDialog, showSuccessToast, showFailToast } from 'vant'
import type { OrderStatus, ApprovalStatus } from '@/types'
const router = useRouter()
const orderStore = useOrderStore()
const authStore = useAuthStore()
//
const { orderList, loading, finished } = storeToRefs(orderStore)
@ -273,6 +294,34 @@ const goToCompletedDetail = (businessId: number) => {
})
}
// 退
const handleLogout = async () => {
try {
await showConfirmDialog({
title: '退出登录',
message: '确定要退出当前账号吗?',
confirmButtonText: '确定',
cancelButtonText: '取消',
confirmButtonColor: '#ee0a24'
})
// 退
await authStore.logout()
//
showSuccessToast('已退出登录')
//
router.replace('/login')
} catch (error: any) {
//
if (error !== 'cancel') {
console.error('退出登录失败:', error)
showFailToast('退出登录失败')
}
}
}
onMounted(() => {
@ -289,11 +338,61 @@ onMounted(() => {
background-color: var(--van-background-color);
}
//
.header-container {
position: sticky;
top: 0;
z-index: 20;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-bottom: 1px solid #f0f0f0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
min-height: 56px;
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #333333;
margin: 0;
}
.header-actions {
display: flex;
align-items: center;
}
.logout-btn {
background: #ffffff;
border: 1px solid #e8e8e8;
color: #666666;
font-weight: 500;
font-size: 14px;
transition: all 0.2s ease;
&:hover, &:active {
background: #f5f5f5;
border-color: #d0d0d0;
color: #333333;
}
:deep(.van-icon) {
margin-right: 4px;
font-size: 14px;
}
}
// Tab
.approval-tabs {
:deep(.van-tabs__wrap) {
position: sticky;
top: 0;
top: 72px; //
z-index: 10;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
@ -432,7 +531,7 @@ onMounted(() => {
.search-container {
position: relative;
z-index: 10;
margin-bottom: 20px;
margin: 16px 20px 20px 20px;
}
//