refactor: 首页界面优化
This commit is contained in:
@@ -7,8 +7,7 @@ VITE_APP_PORT = 4096
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# API 服务器的 URL
|
||||
# VITE_APP_API_URL = http://localhost:8000
|
||||
VITE_APP_API_URL = https://api.youlai.tech
|
||||
VITE_APP_API_URL = http://localhost:8000
|
||||
|
||||
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
|
||||
#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
|
||||
#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
# API 基础路径,开发环境下的请求前缀
|
||||
VITE_APP_BASE_API = '/prod-api'
|
||||
VITE_APP_API_URL = https://api.youlai.tech
|
||||
|
||||
5
src/config/index.ts
Normal file
5
src/config/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 配置统一导出
|
||||
*/
|
||||
|
||||
export * from "./menu";
|
||||
@@ -14,7 +14,7 @@ export interface WorkMenuGroup {
|
||||
children: WorkMenuItem[];
|
||||
}
|
||||
|
||||
export const workMenuConfig: WorkMenuGroup[] = [
|
||||
export const menuConfig: WorkMenuGroup[] = [
|
||||
{
|
||||
title: "系统管理",
|
||||
children: [
|
||||
@@ -31,59 +31,59 @@
|
||||
|
||||
<!-- 通知公告 -->
|
||||
<view class="section">
|
||||
<wd-notice-bar
|
||||
:text="noticeText"
|
||||
color="#34D19D"
|
||||
type="info"
|
||||
@click="handleNoticeClick"
|
||||
>
|
||||
<template #prefix>
|
||||
<wd-tag color="#FAA21E" bg-color="#FAA21E" plain custom-style="margin-right: 10rpx">
|
||||
通知公告
|
||||
</wd-tag>
|
||||
</template>
|
||||
</wd-notice-bar>
|
||||
<view class="notice-bar" @click="handleNoticeClick">
|
||||
<view class="notice-bar__icon">
|
||||
<wd-icon name="check-outline" size="32rpx" color="#34D19D" />
|
||||
</view>
|
||||
<view class="notice-bar__content">
|
||||
<text class="notice-bar__text">{{ noticeText || "暂无通知公告" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="section">
|
||||
<wd-card>
|
||||
<template #title>
|
||||
<view class="overview-header">
|
||||
<text class="overview-header__title">今日数据概览</text>
|
||||
<view class="overview-header__more" @click="handleOverviewMore">查看更多</view>
|
||||
<view class="stat-grid">
|
||||
<view class="stat-card stat-card--green">
|
||||
<view class="stat-card__bg">
|
||||
<view class="stat-card__icon stat-card__icon--user"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="overview-grid">
|
||||
<view class="overview-item">
|
||||
<image class="overview-item__icon" src="/static/icons/visitor.png" />
|
||||
<view class="overview-item__info">
|
||||
<text class="overview-item__label">访客数</text>
|
||||
<text class="overview-item__value">{{ visitStatsData.todayUvCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<image class="overview-item__icon" src="/static/icons/browser.png" />
|
||||
<view class="overview-item__info">
|
||||
<text class="overview-item__label">浏览量</text>
|
||||
<text class="overview-item__value">{{ visitStatsData.todayPvCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item" v-if="appVersion">
|
||||
<image class="overview-item__icon" src="/static/icons/setting.png" />
|
||||
<view class="overview-item__info">
|
||||
<text class="overview-item__label">版本</text>
|
||||
<text class="overview-item__value">{{ appVersion }}</text>
|
||||
</view>
|
||||
<view class="stat-card__head">
|
||||
<text class="stat-card__label">访客数</text>
|
||||
<view class="stat-card__dot stat-card__dot--green"></view>
|
||||
</view>
|
||||
<text class="stat-card__num stat-card__num--green">
|
||||
{{ visitStatsData.todayUvCount }}
|
||||
</text>
|
||||
</view>
|
||||
</wd-card>
|
||||
<view class="stat-card stat-card--blue">
|
||||
<view class="stat-card__bg">
|
||||
<view class="stat-card__icon stat-card__icon--eye"></view>
|
||||
</view>
|
||||
<view class="stat-card__head">
|
||||
<text class="stat-card__label">浏览量</text>
|
||||
<view class="stat-card__dot stat-card__dot--blue"></view>
|
||||
</view>
|
||||
<text class="stat-card__num stat-card__num--blue">
|
||||
{{ visitStatsData.todayPvCount }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="appVersion" class="stat-card stat-card--orange stat-card--full">
|
||||
<view class="stat-card__bg">
|
||||
<view class="stat-card__icon stat-card__icon--gear"></view>
|
||||
</view>
|
||||
<view class="stat-card__head">
|
||||
<text class="stat-card__label">应用版本</text>
|
||||
<view class="stat-card__dot stat-card__dot--orange"></view>
|
||||
</view>
|
||||
<text class="stat-card__num stat-card__num--orange">{{ appVersion }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 访问趋势图表 -->
|
||||
<view class="section">
|
||||
<wd-card>
|
||||
<wd-card custom-class="chart-card">
|
||||
<template #title>
|
||||
<view class="chart-header">
|
||||
<text class="chart-header__title">访问趋势</text>
|
||||
@@ -130,7 +130,8 @@ import { onReady } from "@dcloudio/uni-app";
|
||||
import { dayjs } from "wot-design-uni";
|
||||
import { useRouter } from "uni-mini-router";
|
||||
import { useUserStore } from "@/store";
|
||||
import { workMenuConfig } from "@/constants/work-menu";
|
||||
import { menuConfig } from "@/config/menu";
|
||||
import { checkLogin, isLoggedIn } from "@/utils/auth";
|
||||
import LogAPI, { type VisitStatsVO as ApiVisitStatsVO, type VisitTrendVO } from "@/api/log";
|
||||
import NoticeAPI, { type NoticePageVO } from "@/api/notice";
|
||||
|
||||
@@ -190,16 +191,36 @@ const noticeText = computed(() => {
|
||||
// 用户权限列表
|
||||
const userPerms = computed(() => userStore.userInfo?.perms || []);
|
||||
|
||||
// 是否已登录
|
||||
const isLogged = computed(() => isLoggedIn());
|
||||
|
||||
// 检查是否有权限
|
||||
const hasPermission = (perm: string) => {
|
||||
if (!perm) return true;
|
||||
return userPerms.value.includes(perm) || userPerms.value.includes("*:*:*");
|
||||
};
|
||||
|
||||
// 快捷入口:按权限过滤后取前 4 个
|
||||
const quickNavList = computed(() => {
|
||||
// 默认菜单(未登录时显示)
|
||||
const defaultNavList = computed(() => {
|
||||
const result: { icon: string; title: string; url: string; perm: string }[] = [];
|
||||
for (const group of workMenuConfig) {
|
||||
for (const group of menuConfig) {
|
||||
for (const item of group.children) {
|
||||
result.push(item);
|
||||
if (result.length >= 4) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
// 快捷入口:已登录按权限过滤,未登录显示默认菜单
|
||||
const quickNavList = computed(() => {
|
||||
if (!isLogged.value) {
|
||||
return defaultNavList.value;
|
||||
}
|
||||
const result: { icon: string; title: string; url: string; perm: string }[] = [];
|
||||
for (const group of menuConfig) {
|
||||
for (const item of group.children) {
|
||||
if (hasPermission(item.perm)) {
|
||||
result.push(item);
|
||||
@@ -252,6 +273,11 @@ function loadAppVersion() {
|
||||
}
|
||||
|
||||
async function loadNoticeData() {
|
||||
// 未登录时不调用通知接口
|
||||
if (!isLogged.value) {
|
||||
noticeList.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { list } = await NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 2 });
|
||||
noticeList.value = list || [];
|
||||
@@ -293,6 +319,10 @@ async function loadVisitTrendData() {
|
||||
// ============================================================================
|
||||
|
||||
function handleNavClick(item: NavItem) {
|
||||
// 未登录时先检查登录状态并跳转登录页
|
||||
if (!checkLogin()) {
|
||||
return;
|
||||
}
|
||||
router.push({ path: item.url });
|
||||
}
|
||||
|
||||
@@ -300,10 +330,6 @@ function handleNoticeClick() {
|
||||
router.push({ path: "/pages/work/notice/index" });
|
||||
}
|
||||
|
||||
function handleOverviewMore() {
|
||||
router.push({ path: "/pages/work/log/index" });
|
||||
}
|
||||
|
||||
function handleSwiperClick(e: any) {
|
||||
console.log("Swiper click:", e);
|
||||
}
|
||||
@@ -435,8 +461,8 @@ onReady(() => {
|
||||
.section--overlay {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-top: -72rpx;
|
||||
padding: 16rpx 8rpx;
|
||||
margin-top: -140rpx;
|
||||
padding: 18rpx 8rpx;
|
||||
background: var(--color-bg);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 16rpx 36rpx rgba(0, 0, 0, 0.08);
|
||||
@@ -466,45 +492,202 @@ onReady(() => {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 数据统计网格
|
||||
// 通知栏
|
||||
// ============================================================================
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
.notice-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 24rpx 24rpx 20rpx;
|
||||
border: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
&__icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 16rpx;
|
||||
flex-shrink: 0;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(52, 209, 157, 0.1);
|
||||
border-radius: 12rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
&__info {
|
||||
margin-left: 32rpx;
|
||||
&__content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
display: -webkit-box;
|
||||
line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 统计卡片(简洁大气风格)
|
||||
// ============================================================================
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// 背景图标容器
|
||||
&__bg {
|
||||
position: absolute;
|
||||
right: 12rpx;
|
||||
bottom: 12rpx;
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
opacity: 0.08;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// SVG 装饰图形
|
||||
&__icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// 用户组图标 - 访客数
|
||||
&--user {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%232ab789'%3E%3Ccircle cx='24' cy='16' r='8'/%3E%3Ccircle cx='10' cy='20' r='6'/%3E%3Ccircle cx='38' cy='20' r='6'/%3E%3Cpath d='M24 28c-8 0-14 4-14 8v6h28v-6c0-4-6-8-14-8z'/%3E%3Cpath d='M10 28c-4 0-8 2-8 5v5h8v-5c0-2 1-4 3-5-1 0-2 0-3 0z'/%3E%3Cpath d='M38 28c4 0 8 2 8 5v5h-8v-5c0-2-1-4-3-5 1 0 2 0 3 0z'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
// 图表图标 - 浏览量
|
||||
&--eye {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%233a8ee6'%3E%3Crect x='4' y='24' width='8' height='20' rx='2'/%3E%3Crect x='16' y='14' width='8' height='30' rx='2'/%3E%3Crect x='28' y='20' width='8' height='24' rx='2'/%3E%3Crect x='40' y='8' width='8' height='36' rx='2'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
// 盾牌徽章图标 - 版本
|
||||
&--gear {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%23e8a838'%3E%3Cpath d='M24 4L8 10v12c0 11 8 18 16 22 8-4 16-11 16-22V10L24 4z'/%3E%3Cpath d='M20 24l4 4 8-8' stroke='white' stroke-width='3' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
// 彩色卡片变体
|
||||
&--green {
|
||||
background: linear-gradient(135deg, #f0fdf9 0%, #fff 100%);
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: linear-gradient(135deg, #f0f7ff 0%, #fff 100%);
|
||||
}
|
||||
|
||||
&--orange {
|
||||
background: linear-gradient(135deg, #fffbf0 0%, #fff 100%);
|
||||
}
|
||||
|
||||
&__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&__value {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
&__dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&--green {
|
||||
background: #34d19d;
|
||||
box-shadow: 0 0 10rpx rgba(52, 209, 157, 0.4);
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: #409eff;
|
||||
box-shadow: 0 0 10rpx rgba(64, 158, 255, 0.4);
|
||||
}
|
||||
|
||||
&--orange {
|
||||
background: #faa21e;
|
||||
box-shadow: 0 0 12rpx rgba(250, 162, 30, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
&__num {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: -1rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&--green {
|
||||
color: #2ab789;
|
||||
text-shadow: 0 4rpx 12rpx rgba(42, 183, 137, 0.3);
|
||||
}
|
||||
|
||||
&--blue {
|
||||
color: #3a8ee6;
|
||||
text-shadow: 0 4rpx 12rpx rgba(58, 142, 230, 0.3);
|
||||
}
|
||||
|
||||
&--orange {
|
||||
color: #e8a838;
|
||||
text-shadow: 0 4rpx 12rpx rgba(232, 168, 56, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 20rpx;
|
||||
|
||||
.stat-card__head {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stat-card__num {
|
||||
font-size: 28rpx;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.stat-card__bg {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
right: 16rpx;
|
||||
bottom: 50%;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,4 +712,17 @@ onReady(() => {
|
||||
height: 300px;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
:deep(.chart-card) {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.chart-card .wd-card) {
|
||||
margin: 0 !important;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
:deep(.chart-card .wd-card__body) {
|
||||
padding: 24rpx !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from "uni-mini-router";
|
||||
import { useUserStore } from "@/store";
|
||||
import { workMenuConfig } from "@/constants/work-menu";
|
||||
import { menuConfig } from "@/config/menu";
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
@@ -39,7 +39,7 @@ const hasPermission = (perm: string) => {
|
||||
|
||||
// 根据权限过滤后的菜单列表
|
||||
const visibleGridList = computed(() => {
|
||||
return workMenuConfig
|
||||
return menuConfig
|
||||
.map((group) => ({
|
||||
...group,
|
||||
children: group.children.filter((item) => hasPermission(item.perm)),
|
||||
|
||||
5
src/types/auto-imports.d.ts
vendored
5
src/types/auto-imports.d.ts
vendored
@@ -10,6 +10,7 @@ declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const PERM_ALL: typeof import('../utils/permission')['PERM_ALL']
|
||||
const ROLE_ROOT: typeof import('../utils/permission')['ROLE_ROOT']
|
||||
const RequestError: typeof import('../utils/request')['RequestError']
|
||||
const Storage: typeof import('../utils/storage')['Storage']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const applyThemeOnPageShow: typeof import('../utils/theme')['applyThemeOnPageShow']
|
||||
@@ -204,6 +205,9 @@ declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
// @ts-ignore
|
||||
export type { RequestError } from '../utils/request'
|
||||
import('../utils/request')
|
||||
}
|
||||
|
||||
// for vue template auto import
|
||||
@@ -213,6 +217,7 @@ declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
readonly CommonUtil: UnwrapRef<typeof import('wot-design-uni')['CommonUtil']>
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly RequestError: UnwrapRef<typeof import('../utils/request')['RequestError']>
|
||||
readonly Storage: UnwrapRef<typeof import('../utils/storage')['Storage']>
|
||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||
readonly auth: UnwrapRef<typeof import('../api/auth')['default']>
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
import { getAccessToken } from "./auth";
|
||||
import { getAccessToken, clearTokens } from "./auth";
|
||||
|
||||
/**
|
||||
* 请求错误类
|
||||
*/
|
||||
export class RequestError extends Error {
|
||||
/** HTTP 状态码 */
|
||||
statusCode: number;
|
||||
/** 业务错误码 */
|
||||
code: number;
|
||||
|
||||
constructor(message: string, statusCode: number, code?: number) {
|
||||
super(message);
|
||||
this.name = "RequestError";
|
||||
this.statusCode = statusCode;
|
||||
this.code = code ?? statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求配置
|
||||
interface RequestOptions<T = any> {
|
||||
@@ -8,27 +25,25 @@ interface RequestOptions<T = any> {
|
||||
header?: Record<string, string>;
|
||||
timeout?: number;
|
||||
responseType?: "text" | "arraybuffer";
|
||||
skipAuth?: boolean; // 标记是否跳过认证
|
||||
/** 是否跳过错误提示(如 Toast) */
|
||||
skipErrorToast?: boolean;
|
||||
}
|
||||
|
||||
// 请求函数
|
||||
/**
|
||||
* 请求函数
|
||||
* - 有 token 则自动添加 Authorization 头
|
||||
* - 无 token 则直接发送请求,由后端判断是否需要认证
|
||||
* - 401 时清除 token 并跳转登录页
|
||||
*/
|
||||
function request<T = any>(options: RequestOptions): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
// 构建请求头
|
||||
const header = Object.assign({}, options.header || {});
|
||||
|
||||
// 检查是否需要添加认证令牌
|
||||
if (!options.skipAuth) {
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
header["Authorization"] = `Bearer ${token}`;
|
||||
} else {
|
||||
// 需要认证但没有令牌,跳转到登录页
|
||||
uni.navigateTo({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
return reject(new Error("请先登录"));
|
||||
}
|
||||
// 有 token 则添加认证头,无 token 则不添加(由后端判断)
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
header["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 根据平台决定URL前缀
|
||||
@@ -57,38 +72,36 @@ function request<T = any>(options: RequestOptions): Promise<T> {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res.data.data);
|
||||
}
|
||||
// 未授权错误
|
||||
// 未授权错误:清除 token 并跳转登录页
|
||||
else if (res.statusCode === 401) {
|
||||
// 如果需要认证且未授权,跳转到登录页
|
||||
if (!options.skipAuth) {
|
||||
uni.navigateTo({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
}
|
||||
reject(new Error(serverMsg || "未授权,请重新登录"));
|
||||
clearTokens();
|
||||
uni.navigateTo({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
reject(new RequestError(serverMsg || "未授权,请重新登录", 401));
|
||||
}
|
||||
// 其他错误
|
||||
else {
|
||||
const errorMsg = serverMsg || `请求失败: ${res.statusCode}`;
|
||||
reject(new Error(errorMsg));
|
||||
const errorCode = res?.data?.code || res.statusCode;
|
||||
reject(
|
||||
new RequestError(serverMsg || `请求失败: ${res.statusCode}`, res.statusCode, errorCode)
|
||||
);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || "网络请求失败"));
|
||||
reject(new RequestError(err.errMsg || "网络请求失败", 0));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 无需认证的请求
|
||||
* @param options 请求配置
|
||||
* 无需认证的请求(兼容旧调用)
|
||||
*
|
||||
* 说明:当前 request 实现不会在“无 token”时阻断请求,认证与否由后端决定。
|
||||
*/
|
||||
export function publicRequest<T = any>(options: RequestOptions): Promise<T> {
|
||||
return request<T>({
|
||||
...options,
|
||||
skipAuth: true,
|
||||
});
|
||||
return request<T>(options);
|
||||
}
|
||||
|
||||
export default request;
|
||||
|
||||
Reference in New Issue
Block a user