refactor(路由与用户管理): 优化路由处理和用户登出逻辑
- 修改用户登出时的重定向逻辑,确保用户显式返回登录页,避免保留重定向参数 - 引入默认动态路由子项,简化路由配置 - 更新后端路由初始化逻辑,确保动态路由的正确处理 - 增强代码可读性,修复部分代码风格问题
This commit is contained in:
@@ -180,8 +180,8 @@ export default defineComponent({
|
||||
.then(async () => {
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
// 显式回到登录页,避免保留之前受保护页面的重定向参数
|
||||
await router.replace('/login');
|
||||
})
|
||||
.catch(() => {});
|
||||
} else if (path === 'wareHouse') {
|
||||
|
||||
@@ -4,14 +4,12 @@ import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { useRequestOldRoutes } from '/@/stores/requestOldRoutes';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route';
|
||||
import { dynamicRoutes, defaultDynamicRouteChildren, notFoundAndNoPower } from '/@/router/route';
|
||||
import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { getUserMenus } from '/@/api/system/menu/index';
|
||||
|
||||
|
||||
|
||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||
|
||||
@@ -43,11 +41,12 @@ export async function initBackEndControlRoutes() {
|
||||
await useUserInfo().setPermissions();
|
||||
// 获取路由菜单数据
|
||||
await getBackEndControlRoutes();
|
||||
let menuRoute = Session.get('userMenu')
|
||||
let menuRoute = Session.get('userMenu');
|
||||
// 存储接口原始路由(未处理component),根据需求选择使用
|
||||
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(menuRoute)));
|
||||
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
dynamicRoutes[0].children?.push(...await backEndComponent(menuRoute));
|
||||
dynamicRoutes[0].children = [...defaultDynamicRouteChildren];
|
||||
dynamicRoutes[0].children?.push(...(await backEndComponent(menuRoute)));
|
||||
// 添加动态路由
|
||||
await setAddRoute();
|
||||
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
@@ -104,10 +103,10 @@ export async function setAddRoute() {
|
||||
* @returns 返回后端路由菜单数据
|
||||
*/
|
||||
export async function getBackEndControlRoutes() {
|
||||
let menuRoute = Session.get('userMenu')
|
||||
let permissions = Session.get('permissions')
|
||||
let menuRoute = Session.get('userMenu');
|
||||
let permissions = Session.get('permissions');
|
||||
if (!menuRoute || !permissions) {
|
||||
await refreshBackEndControlRoutes()
|
||||
await refreshBackEndControlRoutes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,11 +117,11 @@ export async function getBackEndControlRoutes() {
|
||||
*/
|
||||
export async function refreshBackEndControlRoutes() {
|
||||
// 获取路由
|
||||
await getUserMenus().then((res:any)=>{
|
||||
Session.set('userMenu',res.data.menuList)
|
||||
Session.set('permissions',res.data.permissions)
|
||||
})
|
||||
await useUserInfo().setPermissions()
|
||||
await getUserMenus().then((res: any) => {
|
||||
Session.set('userMenu', res.data.menuList);
|
||||
Session.set('permissions', res.data.permissions);
|
||||
});
|
||||
await useUserInfo().setPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,16 +139,16 @@ export function setBackEndControlRefreshRoutes() {
|
||||
* @returns 返回处理成函数后的 component
|
||||
*/
|
||||
export function backEndComponent(routes: any) {
|
||||
if (!routes) return;
|
||||
if (!routes) return [];
|
||||
return routes.map((item: any) => {
|
||||
if(item.children&&item.children.length>0){
|
||||
item.children.some((ci:any)=>{
|
||||
if(!ci.meta.isHide){
|
||||
item.redirect = ci
|
||||
return true
|
||||
if (item.children && item.children.length > 0) {
|
||||
item.children.some((ci: any) => {
|
||||
if (!ci.meta.isHide) {
|
||||
item.redirect = ci;
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
})
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
|
||||
item.children && backEndComponent(item.children);
|
||||
|
||||
@@ -21,6 +21,39 @@ import { RouteRecordRaw } from 'vue-router';
|
||||
* @description 各字段请查看 `/@/views/system/menu/component/addMenu.vue 下的 ruleForm`
|
||||
* @returns 返回路由菜单数据
|
||||
*/
|
||||
export const defaultDynamicRouteChildren: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('/@/views/home/index.vue'),
|
||||
meta: {
|
||||
title: 'message.router.home',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: true,
|
||||
isAffix: true,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-shouye',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/personal',
|
||||
name: 'personals',
|
||||
component: () => import('/@/views/system/personal/index.vue'),
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin'],
|
||||
icon: 'iconfont icon-diannao',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -30,38 +63,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
meta: {
|
||||
isKeepAlive: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('/@/views/home/index.vue'),
|
||||
meta: {
|
||||
title: 'message.router.home',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: true,
|
||||
isAffix: true,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-shouye',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/personal',
|
||||
name: 'personals',
|
||||
component: () => import('/@/views/system/personal/index.vue'),
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin'],
|
||||
icon: 'iconfont icon-diannao',
|
||||
},
|
||||
},
|
||||
],
|
||||
children: [...defaultDynamicRouteChildren],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ const performLogout = () => {
|
||||
Session.clear();
|
||||
localStorage.clear();
|
||||
isHandlingTokenExpired = false;
|
||||
// 跳转到后台管理登录页,确保完全刷新
|
||||
// Hash 路由统一回登录页,避免跳到错误地址
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
window.location.href = '/#/login';
|
||||
}, 500);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,18 +6,15 @@
|
||||
<span>店铺维度统计</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 销售趋势图表 -->
|
||||
<div class="chart-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">销售趋势</div>
|
||||
</template>
|
||||
<div ref="salesChartRef" class="chart"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 搜索条件 -->
|
||||
<div class="search-container">
|
||||
<el-form :model="searchParams" :inline="true" class="search-form">
|
||||
<el-form-item label="店铺">
|
||||
<el-select v-model="searchParams.shopId" placeholder="选择店铺">
|
||||
<el-option label="全部店铺" value="" />
|
||||
<el-option v-for="shop in shopList" :key="shop.id" :label="shop.name" :value="shop.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="searchParams.dateRange"
|
||||
@@ -26,14 +23,61 @@
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
:shortcuts="[
|
||||
{
|
||||
text: '最近7天',
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '最近30天',
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '最近90天',
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '今年',
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date(new Date().getFullYear(), 0, 1);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '去年',
|
||||
value: () => {
|
||||
const end = new Date(new Date().getFullYear() - 1, 11, 31);
|
||||
const start = new Date(new Date().getFullYear() - 1, 0, 1);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间粒度">
|
||||
<el-select v-model="searchParams.granularity" placeholder="选择时间粒度">
|
||||
<el-option label="小时" value="hour" />
|
||||
<el-option label="日" value="day" />
|
||||
<el-option label="周" value="week" />
|
||||
<el-option label="月" value="month" />
|
||||
<el-option label="季度" value="quarter" />
|
||||
<el-option label="年" value="year" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -42,6 +86,15 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 销售趋势图表 -->
|
||||
<div class="chart-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">销售趋势 - {{ selectedShopName }}</div>
|
||||
</template>
|
||||
<div ref="salesChartRef" class="chart"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 核心指标 -->
|
||||
<div class="stats-cards">
|
||||
<el-card class="stats-card">
|
||||
@@ -73,19 +126,85 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 店铺列表 -->
|
||||
<div class="shop-list">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">店铺列表</div>
|
||||
</template>
|
||||
<el-table :data="shopList" style="width: 100%" @row-click="handleShopClick">
|
||||
<el-table-column prop="id" label="店铺ID" width="100" />
|
||||
<el-table-column prop="name" label="店铺名称" />
|
||||
<el-table-column prop="type" label="店铺类型">
|
||||
<template #default="scope">
|
||||
<el-tag size="small">{{ scope.row.type === 'physical' ? '线下店铺' : '线上店铺' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="scope.row.status === 'active' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === 'active' ? '营业中' : '已关闭' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" @click="handleShopSelect(scope.row.id)">查看详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const searchParams = reactive({
|
||||
shopId: '',
|
||||
dateRange: [],
|
||||
granularity: 'day',
|
||||
});
|
||||
|
||||
// 监听店铺选择变化,自动更新数据
|
||||
watch(
|
||||
() => searchParams.shopId,
|
||||
() => {
|
||||
handleSearch();
|
||||
}
|
||||
);
|
||||
|
||||
// 监听时间粒度变化,自动更新数据
|
||||
watch(
|
||||
() => searchParams.granularity,
|
||||
() => {
|
||||
handleSearch();
|
||||
}
|
||||
);
|
||||
|
||||
interface Shop {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const shopList = ref<Shop[]>([]);
|
||||
|
||||
// 模拟店铺列表数据
|
||||
const getMockShopList = () => {
|
||||
return [
|
||||
{ id: '1', name: '店铺A', type: 'physical', status: 'active' },
|
||||
{ id: '2', name: '店铺B', type: 'online', status: 'active' },
|
||||
{ id: '3', name: '店铺C', type: 'physical', status: 'active' },
|
||||
{ id: '4', name: '店铺D', type: 'online', status: 'active' },
|
||||
{ id: '5', name: '店铺E', type: 'physical', status: 'active' },
|
||||
];
|
||||
};
|
||||
|
||||
const statsData = reactive({
|
||||
totalSales: 1258000,
|
||||
orderCount: 5230,
|
||||
@@ -100,15 +219,47 @@ const statsData = reactive({
|
||||
const salesChartRef = ref();
|
||||
let salesChart: echarts.ECharts | null = null;
|
||||
|
||||
// 计算选中的店铺名称
|
||||
const selectedShopName = computed(() => {
|
||||
if (!searchParams.shopId) return '全部店铺';
|
||||
const shop = shopList.value.find((s) => s.id === searchParams.shopId);
|
||||
return shop ? shop.name : '未知店铺';
|
||||
});
|
||||
|
||||
// 模拟销售趋势数据
|
||||
const getMockSalesTrend = () => {
|
||||
const dates = ['1月', '2月', '3月', '4月', '5月', '6月'];
|
||||
const getMockSalesTrend = (shopId: string) => {
|
||||
let dates: string[] = [];
|
||||
const baseSales = shopId ? 800000 + parseInt(shopId) * 100000 : 1000000;
|
||||
const baseOrders = shopId ? 3000 + parseInt(shopId) * 500 : 4000;
|
||||
|
||||
// 根据时间粒度生成不同的时间标签
|
||||
switch (searchParams.granularity) {
|
||||
case 'hour':
|
||||
dates = ['0时', '4时', '8时', '12时', '16时', '20时'];
|
||||
break;
|
||||
case 'day':
|
||||
dates = ['1日', '5日', '10日', '15日', '20日', '25日'];
|
||||
break;
|
||||
case 'week':
|
||||
dates = ['第1周', '第2周', '第3周', '第4周', '第5周', '第6周'];
|
||||
break;
|
||||
case 'month':
|
||||
dates = ['1月', '2月', '3月', '4月', '5月', '6月'];
|
||||
break;
|
||||
case 'quarter':
|
||||
dates = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||
break;
|
||||
case 'year':
|
||||
dates = ['2022年', '2023年', '2024年', '2025年', '2026年'];
|
||||
break;
|
||||
default:
|
||||
dates = ['1月', '2月', '3月', '4月', '5月', '6月'];
|
||||
}
|
||||
|
||||
return dates.map((date) => ({
|
||||
date,
|
||||
sales: 1000000 + Math.random() * 500000,
|
||||
orders: 4000 + Math.random() * 2000,
|
||||
xx: 4000 + Math.random() * 2020,
|
||||
ss: 4000 + Math.random() * 2010,
|
||||
sales: baseSales + Math.random() * 500000,
|
||||
orders: baseOrders + Math.random() * 2000,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -123,17 +274,28 @@ const handleSearch = () => {
|
||||
orderGrowth: 12.3 + Math.random() * 3,
|
||||
refundRateChange: -0.5 + Math.random() * 1,
|
||||
logisticsRateGrowth: 1.2 + Math.random() * 0.5,
|
||||
salesTrend: getMockSalesTrend(),
|
||||
salesTrend: getMockSalesTrend(searchParams.shopId),
|
||||
};
|
||||
Object.assign(statsData, mockData);
|
||||
initSalesChart(mockData.salesTrend);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
searchParams.shopId = '';
|
||||
searchParams.dateRange = [];
|
||||
searchParams.granularity = 'day';
|
||||
};
|
||||
|
||||
const handleShopClick = (shop: Shop) => {
|
||||
searchParams.shopId = shop.id;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
const handleShopSelect = (shopId: string) => {
|
||||
searchParams.shopId = shopId;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
const initSalesChart = (salesTrend: any[]) => {
|
||||
if (!salesChartRef.value) return;
|
||||
|
||||
@@ -200,6 +362,9 @@ const initSalesChart = (salesTrend: any[]) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化店铺列表
|
||||
shopList.value = getMockShopList();
|
||||
// 初始化数据
|
||||
handleSearch();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
@@ -271,4 +436,8 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.shop-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user