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)
|
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 { defineStore } from 'pinia'
|
||||||
import { login } from '@/api/auth'
|
import { login, logout as logoutApi } from '@/api/auth'
|
||||||
import type { LoginParams } from '@/types'
|
import type { LoginParams } from '@/types'
|
||||||
|
|
||||||
interface AuthState {
|
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.token = null
|
||||||
this.userInfo = null
|
this.userInfo = null
|
||||||
this.isAuthenticated = false
|
this.isAuthenticated = false
|
||||||
|
|
@ -64,6 +71,7 @@ export const useAuthStore = defineStore('auth', {
|
||||||
localStorage.removeItem('isAuthenticated')
|
localStorage.removeItem('isAuthenticated')
|
||||||
|
|
||||||
// 注意:这里不清除保存的用户名和密码,以便下次自动填充
|
// 注意:这里不清除保存的用户名和密码,以便下次自动填充
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,10 @@
|
||||||
<span class="product-label">单价</span>
|
<span class="product-label">单价</span>
|
||||||
<span class="product-value">{{ formatAmount(product.price) }}</span>
|
<span class="product-value">{{ formatAmount(product.price) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="product-row">
|
||||||
|
<span class="product-label">折扣</span>
|
||||||
|
<span class="product-value">{{ getProductDiscountRate(product.discount) }}</span>
|
||||||
|
</div>
|
||||||
<div class="product-row">
|
<div class="product-row">
|
||||||
<span class="product-label">税率</span>
|
<span class="product-label">税率</span>
|
||||||
<span class="product-value">
|
<span class="product-value">
|
||||||
|
|
@ -233,6 +237,10 @@
|
||||||
<span class="product-label">单价</span>
|
<span class="product-label">单价</span>
|
||||||
<span class="product-value">{{ formatAmount(product.price) }}</span>
|
<span class="product-value">{{ formatAmount(product.price) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="product-row">
|
||||||
|
<span class="product-label">折扣</span>
|
||||||
|
<span class="product-value">{{ getProductDiscountRate(product.discount) }}</span>
|
||||||
|
</div>
|
||||||
<div class="product-row">
|
<div class="product-row">
|
||||||
<span class="product-label">税率</span>
|
<span class="product-label">税率</span>
|
||||||
<span class="product-value">
|
<span class="product-value">
|
||||||
|
|
@ -278,6 +286,10 @@
|
||||||
<span class="product-label">单价</span>
|
<span class="product-label">单价</span>
|
||||||
<span class="product-value">{{ formatAmount(product.price) }}</span>
|
<span class="product-value">{{ formatAmount(product.price) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="product-row">
|
||||||
|
<span class="product-label">折扣</span>
|
||||||
|
<span class="product-value">{{ getProductDiscountRate(product.discount) }}</span>
|
||||||
|
</div>
|
||||||
<div class="product-row">
|
<div class="product-row">
|
||||||
<span class="product-label">税率</span>
|
<span class="product-label">税率</span>
|
||||||
<span class="product-value">
|
<span class="product-value">
|
||||||
|
|
@ -617,6 +629,9 @@ const getTotalAmount = () => {
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getProductDiscountRate=(discount: number)=>{
|
||||||
|
return (discount * 100).toFixed(1) + '%'
|
||||||
|
}
|
||||||
// 获取现金折扣率
|
// 获取现金折扣率
|
||||||
const getDiscountRate = () => {
|
const getDiscountRate = () => {
|
||||||
if (!currentOrderInfo.value || !currentOrderInfo.value.discountFold) {
|
if (!currentOrderInfo.value || !currentOrderInfo.value.discountFold) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="order-list-page">
|
<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">
|
<div class="search-container">
|
||||||
<van-search
|
<van-search
|
||||||
|
|
@ -122,11 +140,14 @@ import { ref, onMounted, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useOrderStore } from '@/store/order'
|
import { useOrderStore } from '@/store/order'
|
||||||
|
import { useAuthStore } from '@/store/auth'
|
||||||
import { formatOrderStatus, formatAmount, formatDate } from '@/utils'
|
import { formatOrderStatus, formatAmount, formatDate } from '@/utils'
|
||||||
|
import { showConfirmDialog, showSuccessToast, showFailToast } from 'vant'
|
||||||
import type { OrderStatus, ApprovalStatus } from '@/types'
|
import type { OrderStatus, ApprovalStatus } from '@/types'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const orderStore = useOrderStore()
|
const orderStore = useOrderStore()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
// 待审批列表相关状态
|
// 待审批列表相关状态
|
||||||
const { orderList, loading, finished } = storeToRefs(orderStore)
|
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(() => {
|
onMounted(() => {
|
||||||
|
|
@ -289,11 +338,61 @@ onMounted(() => {
|
||||||
background-color: var(--van-background-color);
|
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相关样式
|
// Tab相关样式
|
||||||
.approval-tabs {
|
.approval-tabs {
|
||||||
:deep(.van-tabs__wrap) {
|
:deep(.van-tabs__wrap) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 72px; // 调整导航栏高度
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
@ -432,7 +531,7 @@ onMounted(() => {
|
||||||
.search-container {
|
.search-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
margin-bottom: 20px;
|
margin: 16px 20px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索栏样式优化
|
// 搜索栏样式优化
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue