Browse Source

fix:动态路由

zhangrenyuan/feature-20250714163943-金币前端二期
lihui 4 weeks ago
parent
commit
db45faa90a
  1. 1
      .env.development
  2. 379
      src/router/index.js
  3. 80
      src/utils/menu-utils.ts
  4. 66
      src/views/audit/audit.vue
  5. 6
      src/views/consume/coinConsume.vue
  6. 2
      src/views/home.vue
  7. 4
      src/views/login.vue
  8. 6
      src/views/recharge/coinRecharge.vue
  9. 6
      src/views/refund/coinRefund.vue
  10. 7
      src/views/usergold/clientCount.vue
  11. 31
      src/views/workspace/index.vue

1
.env.development

@ -1,3 +1,4 @@
VITE_API_BASE='https://hwjb.homilychart.com/dev/admin' VITE_API_BASE='https://hwjb.homilychart.com/dev/admin'
# VITE_API_BASE='http://192.168.9.52:8081/'
VITE_UPLOAD_URL=http://39.101.133.168:8828/hljw/api/aws/upload VITE_UPLOAD_URL=http://39.101.133.168:8828/hljw/api/aws/upload

379
src/router/index.js

@ -1,114 +1,319 @@
import { createRouter, createWebHashHistory } from 'vue-router'; import { createRouter, createWebHashHistory } from 'vue-router';
import axios from "axios"; import axios from "axios";
import request from "@/util/http.js";
import {ref} from "vue";
// 路由定义(包含权限映射 meta.permissionId)
const routes = [
// {
// path: '/workspace',
// name: "workspace",
// component: () => import("../views/workspace/audit.vue"),
// meta: { permissionId: 10 } // 对应"工作台展示"id=10
// },
{
path: '/',
redirect: "/login"
},
{
path: "/login",
name: "login",
component: () => import("../views/login.vue"),
},
{
meta: { requireAuth: true },
path: '/',
component: () => import("../views/home.vue"),
children: [
// 工作台
{
path: '/workspace',
name: "workspace",
component: () => import("../views/workspace/index.vue"),
meta: { permissionId: 10 } // 对应"工作台展示"id=10
},
// 审核
{
path: '/audit',
name: "audit",
component: () => import("../views/audit/audit.vue"),
meta: { permissionId: 40 },
// redirect: '/index',
children: [
// 充值审核
{
path: 'rechargeAudit',
name: "rechargeAudit",
component: () => import("../views/audit/rechargeAudit.vue"),
meta: { permissionId: [11, 12] } // 对应"查看充值审核"id=11、"充值审批"id=12
},
// 退款审核
{
path: 'refundAudit',
name: "refundAudit",
component: () => import("../views/audit/refundAudit.vue"),
meta: { permissionId: [13, 14] } // 对应"查看退款审核"id=13、"退款审批"id=14
},
]
},
// 金币消耗
{
path: '/coinConsume',
name: "coinConsume",
component: () => import("../views/consume/coinConsume.vue"),
// redirect: '/coinConsume/add',
meta: { permissionId: 6 },
children: [
// 金币新增消耗
{
path: 'add',
name: "addCoinConsume",
component: () => import("../views/consume/addCoinConsume.vue"),
meta: { permissionId: 19 } // 对应"提交金币消耗"id=19
},
// 金币消耗明细详情
{
path: 'detail',
name: "coinConsumeDetail",
component: () => import("../views/consume/coinConsumeDetail.vue"),
meta: { permissionId: 20 } // 对应"查看金币消耗明细"id=20
}
]
},
// 汇率管理
{
path: '/rate',
name: "rate",
component: () => import("../views/managerecharge/rate.vue"),
meta: { permissionId: [15, 16] } // 对应"汇率查看"id=15、"汇率修改"id=16
},
// 金币充值
{
path: '/coinRecharge',
name: "coinRecharge",
component: () => import("../views/recharge/coinRecharge.vue"),
// redirect: '/coinRecharge/add',
children: [
// 金币新增充值
{
path: 'add',
name: "addCoinRecharge",
component: () => import("../views/recharge/addCoinRecharge.vue"),
meta: { permissionId: 17 } // 对应"提交金币充值"id=17
},
// 金币充值明细详情
{
path: 'detail',
name: "coinRechargeDetail",
component: () => import("../views/recharge/coinRechargeDetail.vue"),
meta: { permissionId: 18 } // 对应"查看金币充值明细"id=18
}
]
},
// 金币退款
{
path: '/coinRefund',
name: "coinRefund",
component: () => import("../views/refund/coinRefund.vue"),
// redirect: '/coinRefund/add',
meta: { permissionId: 7 },
children: [
// 金币新增退款
{
path: 'add',
name: "addCoinRefund",
component: () => import("../views/refund/addCoinRefund.vue"),
meta: { permissionId: 21 } // 对应"提交金币退款"id=21
},
// 金币退款明细详情
{
path: 'detail',
name: "coinRefundDetail",
component: () => import("../views/refund/coinRefundDetail.vue"),
meta: { permissionId: 22 } // 对应"查看金币退款明细"id=22
}
]
},
// 客户账户明细
{
path: '/usergold',
name: "usergold",
component: () => import("../views/usergold/clientCount.vue"),
// redirect: '/usergold/detail',
meta: { permissionId: 8 },
children: [
// 金币明细
{
path: 'detail',
name: "clientCountDetail",
component: () => import("../views/usergold/clientCountDetail.vue"),
meta: { permissionId: 23 } // 对应"查看金币明细"id=23
},
// 金币余额
{
path: 'balance',
name: "clientCountBalance",
component: () => import("../views/usergold/clientCountBalance.vue"),
meta: { permissionId: 24 } // 对应"查看金币余额"id=24
},
]
},
// 权限管理
{
path: '/permissions',
name: "permissions",
component: () => import("../views/permissions/permission.vue"),
meta: { permissionId: [25, 26, 27, 28, 29] } // 对应权限管理下的所有操作
},
// 没有权限
{
path: '/noPermission',
name: "noPermission",
component: () => import("../views/noPermissionPage.vue")
}
]
},
// 跳转页面(无需权限)
{
path: '/PasswordSuccess',
name: "PasswordSuccess",
component: () => import("../components/PasswordSuccess.vue")
}
];
// 创建路由实例
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: [
{ path: '/workspace', name: "workspace", component: () => import("../views/workspace/index.vue") },
{ path: '/', redirect: "/login" },
{path: "/login",name: "login",component: () => import("../views/login.vue"),},
// { path: '/test', component: () => import("../views/z.vue") },
{
meta: { requireAuth: true },
path: '/', component: () => import("../views/home.vue"),
children: [
// 工作台
{ path: '/workspace/:area?', name: "workspace", component: () => import("../views/workspace/index.vue") },
// 充值审核
{ path: '/rechargeAudit', name: "rechargeAudit", component: () => import("../views/audit/rechargeAudit.vue") },
// 退款审核
{ path: '/refundAudit', name: "refundAudit", component: () => import("../views/audit/refundAudit.vue") },
// 金币消耗
{ path: '/coinConsume', name: "coinConsume", component: () => import("../views/consume/coinConsume.vue"),
redirect: '/coinConsume/add',// 重定向到新增消耗页面
children: [
// 金币新增消耗
{ path: 'add', name: "addCoinConsume", component: () => import("../views/consume/addCoinConsume.vue") },
// 金币消耗明细详情
{ path: 'detail', name: "coinConsumeDetail", component: () => import("../views/consume/coinConsumeDetail.vue") }
]
},
// 金豆消耗
{ path: '/beanConsume', name: "beanConsume", component: () => import("../views/consume/beanConsume.vue") },
// 汇率管理
{ path: '/rate', name: "rate", component: () => import("../views/managerecharge/rate.vue") },
// 金币充值
{ path: '/coinRecharge', name: "coinRecharge", component: () => import("../views/recharge/coinRecharge.vue"),
redirect: '/coinRecharge/add',// 重定向到新增充值页面
children: [
// 金币新增充值
{ path: 'add', name: "addCoinRecharge", component: () => import("../views/recharge/addCoinRecharge.vue") },
// 金币充值明细详情
{ path: 'detail', name: "coinRechargeDetail", component: () => import("../views/recharge/coinRechargeDetail.vue") }
]
},
// 金豆充值
{ path: '/beanRecharge', name: "beanRecharge", component: () => import("../views/recharge/beanRecharge.vue") },
// 金币退款
{ path: '/coinRefund', name: "coinRefund", component: () => import("../views/refund/coinRefund.vue"),
redirect: '/coinRefund/add',// 重定向到新增退款页面
children: [
// 金币新增消耗
{ path: 'add', name: "addCoinRefund", component: () => import("../views/refund/addCoinRefund.vue") },
// 金币消耗明细详情
{ path: 'detail', name: "coinRefundDetail", component: () => import("../views/refund/coinRefundDetail.vue") }
]
},
// 金豆退款
{ path: '/beanRefund', name: "beanRefund", component: () => import("../views/refund/beanRefund.vue") },
// 客户账户明细
{ path: '/usergold', name: "usergold", component: () => import("../views/usergold/clientCount.vue"),
redirect: '/usergold/detail',// 重定向到客户账户明细页面
children: [
// 金币明细
{ path: 'detail', name: "clientCountDetail", component: () => import("../views/usergold/clientCountDetail.vue") },
// 金币余额
{ path: 'balance', name: "clientCountBalance", component: () => import("../views/usergold/clientCountBalance.vue") },
]
},
// 权限管理
{ path: '/permissions', name: "permissions", component: () => import("../views/permissions/permission.vue") },
// 没有权限
{ path: '/noPermission', name: "noPermission", component: () => import("../views/noPermissionPage.vue") }
]
},
// 跳转页面
{ path: '/PasswordSuccess', name: "PasswordSuccess", component: () => import("../components/PasswordSuccess.vue") },
]
routes
}); });
// 全局拦截器 token 过期拦截
// 全局拦截器:token过期处理
axios.interceptors.response.use( axios.interceptors.response.use(
response => response, response => response,
error => { error => {
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
// 清除本地存储的token
localStorage.removeItem('token'); localStorage.removeItem('token');
// 跳转到登录页
router.push({ router.push({
name: 'login', name: 'login',
query: { machineId: localStorage.getItem('machineId'), expired: true }
query: {
machineId: localStorage.getItem('machineId'),
expired: true
}
}); });
} }
return Promise.reject(error); return Promise.reject(error);
} }
); );
// 工具函数:从菜单树提取所有权限ID
const getAllPermissionIds = (menuTree) => {
let permissionIds = [];
const traverse = (menuList) => {
menuList.forEach(menu => {
permissionIds.push(menu.id);
if (menu.children && menu.children.length > 0) {
traverse(menu.children);
}
});
};
traverse(menuTree);
return permissionIds;
};
// 存储管理员信息(全局可访问)
const adminData = ref(null);
router.beforeEach((to, from, next) => {
// 获取管理员信息(返回Promise,方便路由守卫中使用)
export const getAdminData = async function () {
try {
const result = await request({
url: "/admin/userinfo",
});
adminData.value = result; // 存储管理员信息(包含roleId)
return result; // 返回结果,供路由守卫使用
} catch (error) {
console.log("获取管理员信息失败", error);
throw error; // 抛出错误,让路由守卫捕获
}
};
// 全局路由守卫
router.beforeEach(async (to, from, next) => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
const machineId = localStorage.getItem("machineId"); const machineId = localStorage.getItem("machineId");
if (to.name != "login" && !token) {
next('/login?machineId=' + machineId);
// 1. 未登录:强制跳转到登录页
if (to.name !== "login" && !token) {
next(`/login?machineId=${machineId || ''}`);
return;
} }
next();
})
// 2. 已登录:处理权限验证
if (token) {
// 获取管理员信息
let roleId = null;
console.log('adminData:', adminData)
try {
await getAdminData(); // 等待管理员信息获取完成
roleId = adminData.value.roleId;
if (!roleId) {
throw new Error("未获取到roleId");
}
} catch (error) {
localStorage.removeItem('token'); // 清除token,强制重新登录
next(`/login?machineId=${machineId || ''}`);
return;
}
let userPermissionIds = [];
try {
const response = await request( {url: "/menu/tree",
data:{id: roleId}
});
console.log('roleId:', roleId)
console.log('response:', response)
console.log('userPermissionIds:', userPermissionIds)
if (response.code === 200 && response.data) {
userPermissionIds = getAllPermissionIds(response.data); // 提取权限id
console.log('userPermissionIds:', userPermissionIds)
}
} catch (error) {
console.error('获取菜单树失败:', error);
localStorage.removeItem('token');
next(`/login?machineId=${machineId || ''}`);
return;
}
// 2.4 权限验证(逻辑不变)
console.log('to.meta:',to.meta)
const requiresPermission = to.meta && to.meta.permissionId;
if (requiresPermission) {
const hasPermission = Array.isArray(requiresPermission)
? requiresPermission.some(id => userPermissionIds.includes(id))
: userPermissionIds.includes(requiresPermission);
if (!hasPermission) {
next('/noPermission');
return;
}
}
}
// 3. 正常跳转
next();
});
export default router;
export default router;

80
src/utils/menu-utils.ts

@ -1,60 +1,59 @@
// 菜单树过滤 // 菜单树过滤
export function filterMenu(menuList) { export function filterMenu(menuList) {
return menuList
// 过滤不是4级的 123 为菜单
.filter(menu => menu.menuType !== 4)
.map(menu => ({
...menu,
children: menu.children ? filterMenu(menu.children) : []
}))
.sort((a, b) => a.priority - b.priority); // 按 id 升序
return menuList
// 过滤不是4级的 123 为菜单
.filter(menu => menu.menuType !== 4)
.map(menu => ({
...menu,
children: menu.children ? filterMenu(menu.children) : []
}))
.sort((a, b) => a.priority - b.priority); // 按 id 升序
} }
// 辅助函数:查找第一个可访问的菜单项 // 辅助函数:查找第一个可访问的菜单项
export function findFirstAccessibleMenu(menuList) { export function findFirstAccessibleMenu(menuList) {
if (!menuList || menuList.length === 0) return null
for (const menu of menuList) {
if (menu.menuType === 1) { // 根
const childResult = findFirstAccessibleMenu(menu.children)
if (childResult) return childResult
} else if (menu.menuType === 2) { // 目录
return menu
} else if (menu.menuType === 3) { // 菜单
return menu
if (!menuList || menuList.length === 0) return null
for (const menu of menuList) {
if (menu.menuType === 1) { // 根
const childResult = findFirstAccessibleMenu(menu.children)
if (childResult) return childResult
} else if (menu.menuType === 2) { // 目录
return menu
} else if (menu.menuType === 3) { // 菜单
return menu
}
} }
}
return null
return null
} }
// 路由映射 // 路由映射
export const getRoutePath = (menu) => { export const getRoutePath = (menu) => {
// 路由映射表:key为接口menuName,value为对应路由路径
const routeMap = {
'工作台': '/workspace',
// 路由映射表:key为接口menuName,value为对应路由路径
const routeMap = {
'工作台': '/workspace',
'财务审核': '/rechargeAudit',
'充值审核': '/rechargeAudit',
'退款审核': '/refundAudit',
'审核页面': '/audit',
'财务审核': '/audit',
'汇率管理': '/rate',
'汇率管理': '/rate',
'消耗管理': '/coinConsume',
'消耗页面': '/coinConsume',
'消耗管理': '/coinConsume',
'消耗页面': '/coinConsume',
'权限管理': '/permissions',
'权限管理': '/permissions',
'充值管理': '/coinRecharge',
'充值页面': '/coinRecharge',
'充值管理': '/coinRecharge',
'充值页面': '/coinRecharge',
'退款管理': '/coinRefund',
'退款页面': '/coinRefund',
'退款管理': '/coinRefund',
'退款页面': '/coinRefund',
'客户账户明细': '/usergold',
};
'客户账户明细': '/usergold',
};
// 未匹配的菜单默认使用id作为路由(可根据实际需求调整)
return routeMap[menu.menuName] || '/noPermissionPage'
// 未匹配的菜单默认使用id作为路由(可根据实际需求调整)
return routeMap[menu.menuName] || '/noPermissionPage'
} }
@ -66,9 +65,10 @@ const adminData = ref({
import API from "@/util/http.js"; import API from "@/util/http.js";
import {ref} from "vue"; import {ref} from "vue";
export const getAdminData = async function () {
export const getAdminData = async function () {
try { try {
const result = await API({ url: '/admin/userinfo', data: {} })
const result = await API({url: '/admin/userinfo', data: {}})
adminData.value = result adminData.value = result
console.log('请求成功', result) console.log('请求成功', result)
console.log('用户信息', adminData.value) console.log('用户信息', adminData.value)

66
src/views/audit/audit.vue

@ -0,0 +1,66 @@
<template>
<div>
<!-- 这里放置标签切换的按钮 -->
<el-button-group>
<!-- 切换后状态显示 primary 样式否则是默认样式 -->
<el-button
:type="activeTab === 'rechargeAudit' ? 'primary' : 'default'"
@click="goRechargeAudit"
>
充值审核
</el-button>
<el-button
:type="activeTab === 'refundAudit' ? 'primary' : 'default'"
@click="goRefundAudit"
>
退款审核
</el-button>
</el-button-group>
<!-- 渲染子路由组件 -->
<router-view></router-view>
</div>
</template>
<script setup >
import {onMounted, ref, watch} from 'vue';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();//
const route = useRoute();//
// activeTab
const activeTab = ref(route.name === 'rechargeAudit' ? 'rechargeAudit' : 'refundAudit');
//clientCountBalancebalancedetail
//clientCountDetaildetail
const goRechargeAudit = () => {
// activeTab detail
activeTab.value = 'rechargeAudit';
router.push({ name: 'rechargeAudit' });
};
const goRefundAudit = () => {
// activeTab balance
activeTab.value = 'refundAudit';
router.push({ name: 'refundAudit' });
};
// activeTab
watch(() => route.name, (newName) => {
if (newName === 'rechargeAudit') {
activeTab.value = 'rechargeAudit';
} else if (newName === 'refundAudit') {
activeTab.value = 'refundAudit';
}});
//
// if (route.name === 'usergold') {
// router.push({ name: 'clientCountDetail' });
// }
onMounted(async function () {
console.log("@@@@@@@@@@@@",route.name)
});
</script>

6
src/views/consume/coinConsume.vue

@ -55,7 +55,7 @@ watch(() => route.name, (newName) => {
}); });
// //
if (route.name === 'coinConsume') {
router.push({ name: 'addCoinConsume' });
}
// if (route.name === 'coinConsume') {
// router.push({ name: 'addCoinConsume' });
// }
</script> </script>

2
src/views/home.vue

@ -48,6 +48,8 @@ const getRoutePath = (menu) => {
const routeMap = { const routeMap = {
'工作台': '/workspace', '工作台': '/workspace',
'审核页面': '/audit',
'财务审核': '/audit',
'充值审核': '/rechargeAudit', '充值审核': '/rechargeAudit',
'退款审核': '/refundAudit', '退款审核': '/refundAudit',

4
src/views/login.vue

@ -51,10 +51,12 @@ const login = async function () {
// //
const firstMenu = findFirstAccessibleMenu(filteredMenu) const firstMenu = findFirstAccessibleMenu(filteredMenu)
console.log('第一个有效菜单项:', firstMenu)
// //
const redirectPath = firstMenu ? getRoutePath(firstMenu) : '/noPermissionPage' const redirectPath = firstMenu ? getRoutePath(firstMenu) : '/noPermissionPage'
console.log('获取到的菜单树:', filteredMenu)
console.log('跳转路径:', redirectPath)
router.push(redirectPath) router.push(redirectPath)
// router.push('/workspace') // router.push('/workspace')

6
src/views/recharge/coinRecharge.vue

@ -55,7 +55,7 @@ watch(() => route.name, (newName) => {
}); });
// //
if (route.name === 'coinRecharge') {
router.push({ name: 'addCoinRecharge' });
}
// if (route.name === 'coinRecharge') {
// router.push({ name: 'addCoinRecharge' });
// }
</script> </script>

6
src/views/refund/coinRefund.vue

@ -55,7 +55,7 @@ watch(() => route.name, (newName) => {
}); });
// //
if (route.name === 'coinRefund') {
router.push({ name: 'addCoinRefund' });
}
// if (route.name === 'coinRefund') {
// router.push({ name: 'addCoinRefund' });
// }
</script> </script>

7
src/views/usergold/clientCount.vue

@ -16,6 +16,7 @@
金币余额 金币余额
</el-button> </el-button>
</el-button-group> </el-button-group>
<!-- 渲染子路由组件 --> <!-- 渲染子路由组件 -->
<router-view></router-view> <router-view></router-view>
</div> </div>
@ -54,7 +55,7 @@ watch(() => route.name, (newName) => {
}}); }});
// //
if (route.name === 'usergold') {
router.push({ name: 'clientCountDetail' });
}
// if (route.name === 'usergold') {
// router.push({ name: 'clientCountDetail' });
// }
</script> </script>

31
src/views/workspace/index.vue

@ -167,9 +167,9 @@
<el-col :span="18"> <el-col :span="18">
<div class="bar"> <div class="bar">
<div v-if="chartLoading" class="loading-overlay">
<div class="loading-spinner"></div>
</div>
<!-- <div v-if="chartLoading" class="loading-overlay">-->
<!-- <div class="loading-spinner"></div>-->
<!-- </div>-->
<div ref="chartRef" style="width: 100%; height: 400px"></div> <div ref="chartRef" style="width: 100%; height: 400px"></div>
</div> </div>
@ -690,19 +690,18 @@ const updateChart = (chartData) => {
} }
}, },
series: series, series: series,
dataZoom: [
{
type: 'slider',
show: true,
start: 0,
end: 20,
maxSpan: 20,
minSpan: 20,
height: 2,
},
]
// dataZoom: [
// {
// type: 'slider',
// show: true,
// start: 0,
// end: 100,
// maxSpan: 100,
// minSpan: 100,
//
// height: 2,
// },
// ]
} }
chartInstance.setOption(option) chartInstance.setOption(option)

Loading…
Cancel
Save