feat(auth): 实现用户退出登录功能
- 新增 logout API 接口用于服务端登出 - 在 auth store 中实现异步 logout 方法,调用后端接口并清理本地状态 - 在订单列表页面添加退出登录按钮及确认弹窗 - 更新 Vite 配置注释以支持多环境切换 - 在详情页展示产品折扣信息并格式化显示 --- - 增加了 `/logout` 后端接口调用 - 完善了前端登出逻辑,包括异常处理和状态清理 - 提供了用户登出确认交互流程 - 折扣率计算函数 `getProductDiscountRate` 已添加并应用 - 样式调整适配新增的顶部导航栏布局master
parent
3271644a01
commit
c524d8a4cd
|
|
@ -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')
|
||||
}
|
||||
|
|
@ -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')
|
||||
|
||||
// 注意:这里不清除保存的用户名和密码,以便下次自动填充
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
// 搜索栏样式优化
|
||||
|
|
|
|||
Loading…
Reference in New Issue