diff --git a/.env.development b/.env.development index 3b5ebee..49f0feb 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,4 @@ 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 diff --git a/src/router/index.js b/src/router/index.js index 2c19462..1362ca2 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,114 +1,319 @@ import { createRouter, createWebHashHistory } from 'vue-router'; 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({ 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( response => response, error => { if (error.response && error.response.status === 401) { - // 清除本地存储的token localStorage.removeItem('token'); - // 跳转到登录页 router.push({ name: 'login', - query: { machineId: localStorage.getItem('machineId'), expired: true } + query: { + machineId: localStorage.getItem('machineId'), + expired: true + } }); } 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 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; \ No newline at end of file diff --git a/src/utils/menu-utils.ts b/src/utils/menu-utils.ts index e7e49b1..84212a0 100644 --- a/src/utils/menu-utils.ts +++ b/src/utils/menu-utils.ts @@ -1,60 +1,59 @@ // 菜单树过滤 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) { - 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) => { - // 路由映射表: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 {ref} from "vue"; -export const getAdminData = async function () { + +export const getAdminData = async function () { try { - const result = await API({ url: '/admin/userinfo', data: {} }) + const result = await API({url: '/admin/userinfo', data: {}}) adminData.value = result console.log('请求成功', result) console.log('用户信息', adminData.value) diff --git a/src/views/audit/audit.vue b/src/views/audit/audit.vue new file mode 100644 index 0000000..3df670e --- /dev/null +++ b/src/views/audit/audit.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/views/consume/coinConsume.vue b/src/views/consume/coinConsume.vue index 0be13d0..5c05116 100644 --- a/src/views/consume/coinConsume.vue +++ b/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' }); +// } diff --git a/src/views/home.vue b/src/views/home.vue index 14243e7..2c4c54e 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -48,6 +48,8 @@ const getRoutePath = (menu) => { const routeMap = { '工作台': '/workspace', + '审核页面': '/audit', + '财务审核': '/audit', '充值审核': '/rechargeAudit', '退款审核': '/refundAudit', diff --git a/src/views/login.vue b/src/views/login.vue index b730657..c24c406 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -51,10 +51,12 @@ const login = async function () { // 获取第一个有效菜单项 const firstMenu = findFirstAccessibleMenu(filteredMenu) + console.log('第一个有效菜单项:', firstMenu) // 根据权限跳转 const redirectPath = firstMenu ? getRoutePath(firstMenu) : '/noPermissionPage' - +console.log('获取到的菜单树:', filteredMenu) + console.log('跳转路径:', redirectPath) router.push(redirectPath) // router.push('/workspace') diff --git a/src/views/recharge/coinRecharge.vue b/src/views/recharge/coinRecharge.vue index 6033aca..be35c21 100644 --- a/src/views/recharge/coinRecharge.vue +++ b/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' }); +// } diff --git a/src/views/refund/coinRefund.vue b/src/views/refund/coinRefund.vue index 482e909..e3bd42c 100644 --- a/src/views/refund/coinRefund.vue +++ b/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' }); +// } diff --git a/src/views/usergold/clientCount.vue b/src/views/usergold/clientCount.vue index ddc0e37..49330e6 100644 --- a/src/views/usergold/clientCount.vue +++ b/src/views/usergold/clientCount.vue @@ -16,6 +16,7 @@ 金币余额 + @@ -54,7 +55,7 @@ watch(() => route.name, (newName) => { }}); // 当进入父路由时,默认跳转到金币明细页面 -if (route.name === 'usergold') { - router.push({ name: 'clientCountDetail' }); -} +// if (route.name === 'usergold') { +// router.push({ name: 'clientCountDetail' }); +// } diff --git a/src/views/workspace/index.vue b/src/views/workspace/index.vue index aae8ce9..815e28a 100644 --- a/src/views/workspace/index.vue +++ b/src/views/workspace/index.vue @@ -167,9 +167,9 @@
-
-
-
+ + +
@@ -690,19 +690,18 @@ const updateChart = (chartData) => { } }, 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)