修复鉴权:401拦截器加refresh重试 + admin refresh接口 + 路由守卫完善
根因:access_token每2小时过期,前端收到401直接清token跳登录,没有用refresh_token续期 后端修复: - adminauth模块新增 POST /admin/auth/refresh 接口 - Service 注入 JWTManager,支持 admin refresh token 换新 token pair - Refresh 方法验证 subjectType=admin + tokenType=refresh 前端修复: - 401 拦截器核心改造:收到401先调 refresh 接口续期 - 加 isRefreshing 锁 + pendingRequests 队列防止并发刷新 - refresh 用原生 axios.post 避免拦截器递归 - 成功则更新 localStorage + 重试原请求,失败才清 token 跳登录 - 排除 /auth/refresh 自身避免死循环 - 支持 /admin/ 请求独立 token 管理 - auth.ts/adminAuth.ts 新增手动 refreshUserToken/refreshAdminSession - 路由守卫给所有需登录路由添加 meta.requiresAuth - 守卫同时支持 PC 端 /login 和移动端 /m/login
This commit is contained in:
@@ -78,11 +78,14 @@ const visibleListings = computed(() => {
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
if (keyword && !text.includes(keyword)) return false;
|
||||
if (filters.server && getServerRegion(item) !== filters.server) return false;
|
||||
if (filters.loginMethod && getLoginMethod(item) !== filters.loginMethod) return false;
|
||||
if (filters.server && getServerRegion(item) !== filters.server)
|
||||
return false;
|
||||
if (filters.loginMethod && getLoginMethod(item) !== filters.loginMethod)
|
||||
return false;
|
||||
if (filters.rank && item.rank_level !== filters.rank) return false;
|
||||
if (filters.minCoin && getCoinM(item) < filters.minCoin) return false;
|
||||
if (filters.maxPrice && getListingDisplayPrice(item) > filters.maxPrice) return false;
|
||||
if (filters.maxPrice && getListingDisplayPrice(item) > filters.maxPrice)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -103,7 +106,11 @@ const visibleListings = computed(() => {
|
||||
});
|
||||
|
||||
const statCards = computed(() => [
|
||||
{ label: "可租账号", value: `${visibleListings.value.length}`, hint: "当前筛选结果" },
|
||||
{
|
||||
label: "可租账号",
|
||||
value: `${visibleListings.value.length}`,
|
||||
hint: "当前筛选结果",
|
||||
},
|
||||
{
|
||||
label: "高哈夫币",
|
||||
value: `${listings.value.filter((item) => getCoinM(item) >= 100).length}`,
|
||||
@@ -111,7 +118,10 @@ const statCards = computed(() => [
|
||||
},
|
||||
{
|
||||
label: "账密登录",
|
||||
value: `${listings.value.filter((item) => getLoginMethod(item) === "账号密码").length}`,
|
||||
value: `${
|
||||
listings.value.filter((item) => getLoginMethod(item) === "账号密码")
|
||||
.length
|
||||
}`,
|
||||
hint: "交接更快",
|
||||
},
|
||||
]);
|
||||
@@ -125,7 +135,7 @@ async function loadHome() {
|
||||
fetchListings(),
|
||||
fetchMobileHomeConfig(),
|
||||
]);
|
||||
listings.value = nextListings;
|
||||
listings.value = nextListings.items;
|
||||
announcements.value = config.announcements;
|
||||
banners.value = config.banners;
|
||||
publishOptions.value = config.publish_options;
|
||||
@@ -165,7 +175,10 @@ function uniqueOptions(values: string[]) {
|
||||
</div>
|
||||
<div class="home-searchbar">
|
||||
<el-icon><Search /></el-icon>
|
||||
<input v-model="filters.keyword" placeholder="搜区服 / 段位 / 哈夫币 / 皮肤" />
|
||||
<input
|
||||
v-model="filters.keyword"
|
||||
placeholder="搜区服 / 段位 / 哈夫币 / 皮肤"
|
||||
/>
|
||||
</div>
|
||||
<RouterLink class="home-service" to="/notifications">
|
||||
<el-icon><Headset /></el-icon>
|
||||
@@ -205,13 +218,27 @@ function uniqueOptions(values: string[]) {
|
||||
<label>
|
||||
<span>区服</span>
|
||||
<el-select v-model="filters.server" clearable placeholder="全部区服">
|
||||
<el-option v-for="item in serverOptions" :key="item" :label="item" :value="item" />
|
||||
<el-option
|
||||
v-for="item in serverOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</label>
|
||||
<label>
|
||||
<span>上号方式</span>
|
||||
<el-select v-model="filters.loginMethod" clearable placeholder="全部方式">
|
||||
<el-option v-for="item in loginMethodOptions" :key="item" :label="item" :value="item" />
|
||||
<el-select
|
||||
v-model="filters.loginMethod"
|
||||
clearable
|
||||
placeholder="全部方式"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in loginMethodOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</label>
|
||||
<label>
|
||||
@@ -249,18 +276,30 @@ function uniqueOptions(values: string[]) {
|
||||
<div class="quick-chip-group">
|
||||
<button type="button" @click="filters.minCoin = 100">100M+</button>
|
||||
<button type="button" @click="filters.minCoin = 300">300M+</button>
|
||||
<button type="button" @click="filters.loginMethod = '账号密码'">账密优先</button>
|
||||
<button type="button" @click="filters.loginMethod = '账号密码'">
|
||||
账密优先
|
||||
</button>
|
||||
<button type="button" @click="filters.server = 'QQ'">QQ 区服</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="home-main">
|
||||
<section class="hero-board">
|
||||
<el-carousel height="210px" indicator-position="outside" :interval="3600">
|
||||
<el-carousel-item v-for="slide in banners" :key="slide.title || slide.image_url">
|
||||
<el-carousel
|
||||
height="210px"
|
||||
indicator-position="outside"
|
||||
:interval="3600"
|
||||
>
|
||||
<el-carousel-item
|
||||
v-for="slide in banners"
|
||||
:key="slide.title || slide.image_url"
|
||||
>
|
||||
<div
|
||||
class="hero-slide"
|
||||
:class="[`tone-${slide.tone}`, { 'has-image': slide.image_url }]"
|
||||
:class="[
|
||||
`tone-${slide.tone}`,
|
||||
{ 'has-image': slide.image_url },
|
||||
]"
|
||||
>
|
||||
<img
|
||||
v-if="slide.image_url"
|
||||
@@ -335,7 +374,13 @@ function uniqueOptions(values: string[]) {
|
||||
<div class="desktop-title">
|
||||
<strong>{{ getListingTitle(item) }}</strong>
|
||||
</div>
|
||||
<p>{{ getListingSubtitle(item) || item.description || "平台担保交接,资料以截图为准。" }}</p>
|
||||
<p>
|
||||
{{
|
||||
getListingSubtitle(item) ||
|
||||
item.description ||
|
||||
"平台担保交接,资料以截图为准。"
|
||||
}}
|
||||
</p>
|
||||
<div class="desktop-tags">
|
||||
<span>{{ getServerRegion(item) }}</span>
|
||||
<span>{{ getLoginMethod(item) }}</span>
|
||||
@@ -598,7 +643,11 @@ function uniqueOptions(values: string[]) {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
content: "";
|
||||
background: linear-gradient(90deg, rgba(10, 18, 32, 0.68), rgba(10, 18, 32, 0.12));
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(10, 18, 32, 0.68),
|
||||
rgba(10, 18, 32, 0.12)
|
||||
);
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
|
||||
Reference in New Issue
Block a user