fix(auth): 修复令牌过期处理和会话失效通知机制
- 移除 VITE_SERVICE_LOGOUT_CODES 中的 1002023000 状态码 - 将 VITE_SERVICE_EXPIRED_TOKEN_CODES 从 1002023001 改为 1002023000 - 修改 fetchRefreshToken 函数使用 params 传递 refreshToken 并设置 skipAuth - 添加 skipAuth 配置选项避免给公开接口带上过期 access 头 - 实现 notifySessionExpired 函数确保并发请求只弹一次会话失效提示 - 在登录成功后复位会话失效标志以支持下次正常提示 - 更新 handleExpiredRequest 使用 refreshTokenPromise 替代 refreshTokenFn
This commit is contained in:
@@ -3,7 +3,7 @@ import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { ElButton, ElTag } from 'element-plus';
|
||||
import dayjs from 'dayjs';
|
||||
import { CircleCheckFilled, DeleteFilled, FolderOpened, VideoPause } from '@element-plus/icons-vue';
|
||||
import { Box, DeleteFilled, VideoPause, VideoPlay } from '@element-plus/icons-vue';
|
||||
import { RDMS_OBJECT_DIRECTION_DICT_CODE } from '@/constants/dict';
|
||||
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
|
||||
import { fetchGetProductOverviewSummary, fetchGetProductPage, fetchGetUserSimpleList } from '@/service/api';
|
||||
@@ -76,14 +76,14 @@ const statusNavMetas: StatusNavMeta[] = [
|
||||
label: '启用产品',
|
||||
description: '当前正常服务中的产品',
|
||||
tone: 'teal',
|
||||
icon: CircleCheckFilled
|
||||
icon: VideoPlay
|
||||
},
|
||||
{
|
||||
key: 'archived',
|
||||
label: '归档产品',
|
||||
description: '已完成阶段目标的产品',
|
||||
tone: 'slate',
|
||||
icon: FolderOpened
|
||||
icon: Box
|
||||
},
|
||||
{
|
||||
key: 'paused',
|
||||
@@ -109,7 +109,7 @@ const operateVisible = ref(false);
|
||||
const editingRow = ref<Api.Product.Product | null>(null);
|
||||
const { routerPush } = useRouterPush();
|
||||
|
||||
const { dictData: directionOptions, getLabel: getDirectionDictLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
||||
const { getLabel: getDirectionDictLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
||||
|
||||
const statusCounts = ref<Record<string, number>>({
|
||||
active: 0,
|
||||
@@ -129,29 +129,6 @@ const statusItems = computed(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const overviewMetrics = computed(() => [
|
||||
{
|
||||
label: '可见产品',
|
||||
value: Object.values(statusCounts.value).reduce((sum, count) => sum + count, 0),
|
||||
hint: '当前接口可查询到的产品总量'
|
||||
},
|
||||
{
|
||||
label: '当前启用',
|
||||
value: statusCounts.value.active ?? 0,
|
||||
hint: '正在持续服务和维护的产品'
|
||||
},
|
||||
{
|
||||
label: '产品方向',
|
||||
value: directionOptions.value.length,
|
||||
hint: '已加载的方向字典项数量'
|
||||
},
|
||||
{
|
||||
label: '废弃产品',
|
||||
value: statusCounts.value.abandoned ?? 0,
|
||||
hint: '已明确停止建设的产品'
|
||||
}
|
||||
]);
|
||||
|
||||
function getDirectionLabel(directionCode?: string | null) {
|
||||
return getDirectionDictLabel(directionCode, '--');
|
||||
}
|
||||
@@ -288,7 +265,7 @@ async function handleResetSearch() {
|
||||
|
||||
async function handleStatusChange(status: Api.Product.ProductStatusCode) {
|
||||
selectedStatus.value = status;
|
||||
await reloadProductTable(1);
|
||||
await Promise.all([loadOverviewData(), reloadProductTable(1)]);
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
@@ -326,14 +303,6 @@ onMounted(async () => {
|
||||
>
|
||||
<div class="flex-col-stretch gap-16px xl:min-h-0">
|
||||
<ElCard class="product-overview-card card-wrapper">
|
||||
<div class="product-overview-card__stats">
|
||||
<div v-for="item in overviewMetrics" :key="item.label" class="product-overview-card__stat">
|
||||
<span class="product-overview-card__stat-label">{{ item.label }}</span>
|
||||
<strong class="product-overview-card__stat-value">{{ item.value }}</strong>
|
||||
<small class="product-overview-card__stat-hint">{{ item.hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="product-status-panel__list">
|
||||
<button
|
||||
v-for="item in statusItems"
|
||||
@@ -441,45 +410,10 @@ onMounted(async () => {
|
||||
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 97%));
|
||||
}
|
||||
|
||||
.product-overview-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.product-overview-card__stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgb(226 232 240 / 88%);
|
||||
border-radius: 18px;
|
||||
background-color: rgb(255 255 255 / 84%);
|
||||
}
|
||||
|
||||
.product-overview-card__stat-label {
|
||||
color: rgb(100 116 139 / 90%);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.product-overview-card__stat-value {
|
||||
color: rgb(15 23 42 / 94%);
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.product-overview-card__stat-hint {
|
||||
color: rgb(100 116 139 / 90%);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.product-status-panel__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.product-status-item {
|
||||
@@ -594,10 +528,4 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
.product-overview-card__stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -71,7 +71,7 @@ const operateVisible = ref(false);
|
||||
const editingRow = ref<Api.Project.Project | null>(null);
|
||||
const { routerPush } = useRouterPush();
|
||||
|
||||
const { dictData: directionOptions, getLabel: getDirectionDictLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
||||
const { getLabel: getDirectionDictLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
||||
const { getLabel: getProjectTypeLabel } = useDict(RDMS_PROJECT_TYPE_DICT_CODE);
|
||||
|
||||
const statusCounts = ref<Record<string, number>>({
|
||||
@@ -243,7 +243,7 @@ async function handleResetSearch() {
|
||||
|
||||
async function handleStatusChange(status: Api.Project.ProjectStatusCode) {
|
||||
selectedStatus.value = status;
|
||||
await reloadProjectTable(1);
|
||||
await Promise.all([loadOverviewData(), reloadProjectTable(1)]);
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
@@ -282,7 +282,6 @@ onMounted(async () => {
|
||||
<div class="flex-col-stretch gap-16px xl:min-h-0">
|
||||
<ProjectOverviewCard
|
||||
:status-counts="statusCounts"
|
||||
:direction-count="directionOptions.length"
|
||||
:selected-status="selectedStatus"
|
||||
@status-change="handleStatusChange"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { CircleCheckFilled, DeleteFilled, DocumentAdd, FolderOpened, VideoPause } from '@element-plus/icons-vue';
|
||||
import { Box, CircleCheckFilled, DeleteFilled, DocumentAdd, VideoPause, VideoPlay } from '@element-plus/icons-vue';
|
||||
|
||||
defineOptions({ name: 'ProjectOverviewCard' });
|
||||
|
||||
@@ -15,7 +15,6 @@ interface StatusNavMeta {
|
||||
|
||||
interface Props {
|
||||
statusCounts: Record<string, number>;
|
||||
directionCount: number;
|
||||
selectedStatus: Api.Project.ProjectStatusCode;
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ const statusNavMetas: StatusNavMeta[] = [
|
||||
label: '进行中',
|
||||
description: '正在执行的项目',
|
||||
tone: 'teal',
|
||||
icon: CircleCheckFilled
|
||||
icon: VideoPlay
|
||||
},
|
||||
{
|
||||
key: 'paused',
|
||||
@@ -54,7 +53,7 @@ const statusNavMetas: StatusNavMeta[] = [
|
||||
label: '已完成',
|
||||
description: '达成目标的项目',
|
||||
tone: 'teal',
|
||||
icon: FolderOpened
|
||||
icon: CircleCheckFilled
|
||||
},
|
||||
{
|
||||
key: 'cancelled',
|
||||
@@ -68,7 +67,7 @@ const statusNavMetas: StatusNavMeta[] = [
|
||||
label: '归档项目',
|
||||
description: '已收口归档的历史项目',
|
||||
tone: 'slate',
|
||||
icon: FolderOpened
|
||||
icon: Box
|
||||
}
|
||||
];
|
||||
|
||||
@@ -79,29 +78,6 @@ const statusItems = computed(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const overviewMetrics = computed(() => [
|
||||
{
|
||||
label: '总项目数',
|
||||
value: Object.values(props.statusCounts).reduce((sum, count) => sum + count, 0),
|
||||
hint: '当前接口可查询到的项目总量'
|
||||
},
|
||||
{
|
||||
label: '进行中',
|
||||
value: props.statusCounts.active ?? 0,
|
||||
hint: '正在执行的项目'
|
||||
},
|
||||
{
|
||||
label: '待开始',
|
||||
value: props.statusCounts.pending ?? 0,
|
||||
hint: '等待启动的项目'
|
||||
},
|
||||
{
|
||||
label: '作废项目',
|
||||
value: props.statusCounts.cancelled ?? 0,
|
||||
hint: '已终止或取消推进的项目'
|
||||
}
|
||||
]);
|
||||
|
||||
function handleStatusClick(status: Api.Project.ProjectStatusCode) {
|
||||
emit('status-change', status);
|
||||
}
|
||||
@@ -109,14 +85,6 @@ function handleStatusClick(status: Api.Project.ProjectStatusCode) {
|
||||
|
||||
<template>
|
||||
<ElCard class="project-overview-card card-wrapper">
|
||||
<div class="project-overview-card__stats">
|
||||
<div v-for="item in overviewMetrics" :key="item.label" class="project-overview-card__stat">
|
||||
<span class="project-overview-card__stat-label">{{ item.label }}</span>
|
||||
<strong class="project-overview-card__stat-value">{{ item.value }}</strong>
|
||||
<small class="project-overview-card__stat-hint">{{ item.hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-status-panel__list">
|
||||
<button
|
||||
v-for="item in statusItems"
|
||||
@@ -153,45 +121,10 @@ function handleStatusClick(status: Api.Project.ProjectStatusCode) {
|
||||
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 97%));
|
||||
}
|
||||
|
||||
.project-overview-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.project-overview-card__stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgb(226 232 240 / 88%);
|
||||
border-radius: 18px;
|
||||
background-color: rgb(255 255 255 / 84%);
|
||||
}
|
||||
|
||||
.project-overview-card__stat-label {
|
||||
color: rgb(100 116 139 / 90%);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.project-overview-card__stat-value {
|
||||
color: rgb(15 23 42 / 94%);
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.project-overview-card__stat-hint {
|
||||
color: rgb(100 116 139 / 90%);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.project-status-panel__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.project-status-item {
|
||||
@@ -293,10 +226,4 @@ function handleStatusClick(status: Api.Project.ProjectStatusCode) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
.project-overview-card__stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user