From aaf4be815add25a364e8457fc45f6b121033cda9 Mon Sep 17 00:00:00 2001 From: lihui Date: Fri, 25 Jul 2025 11:09:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=9A=E6=AC=A1=E8=B0=83=E7=94=A8tree?= =?UTF-8?q?=20,=E6=9D=83=E9=99=90=E4=B8=8D=E9=87=8D=E6=96=B0=E5=88=B0?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 10 +- src/router/index.js | 152 +++++++++-------------- src/store/index.js | 45 +++++++ src/utils/menuUtils.js | 61 +++++++++ src/views/audit/audit.vue | 127 +++++++++++++------ src/views/consume/coinConsume.vue | 126 ++++++++++++++----- src/views/home.vue | 238 ++++++++---------------------------- src/views/login.vue | 53 ++++---- src/views/recharge/coinRecharge.vue | 120 +++++++++++++----- src/views/refund/coinRefund.vue | 120 +++++++++++++----- src/views/usergold/clientCount.vue | 109 ++++++++++++----- 11 files changed, 690 insertions(+), 471 deletions(-) create mode 100644 src/store/index.js create mode 100644 src/utils/menuUtils.js diff --git a/src/main.ts b/src/main.ts index e5388b2..795d7b9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,13 +12,13 @@ import VxeUI from 'vxe-pc-ui' import 'vxe-pc-ui/lib/style.css' import VxeUITable from 'vxe-table' import 'vxe-table/lib/style.css' - const a = createApp(App) - +import { useAdminStore } from '../src/store' // 全局注册 ElementPlus 图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { a.component(key, component) } +const pinia = createPinia() // 使用 ElementPlus 和路由器 a.use(ElementPlus, { @@ -27,8 +27,12 @@ a.use(ElementPlus, { .use(router) .use(VxeUI) .use(VxeUITable) - .use(createPinia()) + .use(pinia) .mount('#app') +// 恢复localStorage数据 +const adminStore = useAdminStore() +adminStore.initFromLocalStorage() + // 注册 JsonExcel 组件 a.component('downloadExcel', JsonExcel) diff --git a/src/router/index.js b/src/router/index.js index 247742b..4939c4f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,17 +1,11 @@ -import { createRouter, createWebHashHistory } from 'vue-router'; +import {createRouter, createWebHashHistory} from 'vue-router'; import axios from "axios"; -import request from "@/util/http.js"; -import {ref} from "vue"; +import {storeToRefs} from "pinia"; +import {useAdminStore} from "@/store/index.js"; + // 路由定义(包含权限映射 meta.permissionId) const routes = [ - // { - - // path: '/workspace', - // name: "workspace", - // component: () => import("../views/workspace/audit.vue"), - // meta: { permissionId: 10 } // 对应"工作台展示"id=10 - // }, { path: '/', redirect: "/login" @@ -21,17 +15,17 @@ const routes = [ name: "login", component: () => import("../views/login.vue"), }, - { - meta: { requireAuth: true }, + { + meta: {requireAuth: true}, path: '/', component: () => import("../views/home.vue"), - children: [ - // 工作台 + children: [ + // 工作台 { path: '/workspace', name: "workspace", component: () => import("../views/workspace/index.vue"), - meta: { permissionId: 10 } // 对应"工作台展示"id=10 + meta: {permissionId: 10} // 对应"工作台展示"id=10 }, @@ -40,148 +34,147 @@ const routes = [ path: '/audit', name: "audit", component: () => import("../views/audit/audit.vue"), - meta: { permissionId: 40 }, + meta: {permissionId: 40}, // redirect: '/index', children: [ - // 充值审核======================================== + // 充值审核 { path: 'rechargeAudit', name: "rechargeAudit", component: () => import("../views/audit/rechargeAudit.vue"), - meta: { permissionId: [11, 12] } // 对应"查看充值审核"id=11、"充值审批"id=12 + 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 + 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: [ - // 金币新增消耗 + meta: {permissionId: 6}, + children: [ + // 金币新增消耗 { path: 'add', name: "addCoinConsume", component: () => import("../views/consume/addCoinConsume.vue"), - meta: { permissionId: 19 } // 对应"提交金币消耗"id=19 + meta: {permissionId: 19} // 对应"提交金币消耗"id=19 }, - // 金币消耗明细详情 + // 金币消耗明细详情 { path: 'detail', name: "coinConsumeDetail", component: () => import("../views/consume/coinConsumeDetail.vue"), - meta: { permissionId: 20 } // 对应"查看金币消耗明细"id=20 + meta: {permissionId: 20} // 对应"查看金币消耗明细"id=20 } ] }, - // 汇率管理 + // 汇率管理 { path: '/rate', name: "rate", component: () => import("../views/managerecharge/rate.vue"), - meta: { permissionId: [15, 16] } // 对应"汇率查看"id=15、"汇率修改"id=16 + meta: {permissionId: [15, 16]} // 对应"汇率查看"id=15、"汇率修改"id=16 }, - // 金币充值 + // 金币充值 { path: '/coinRecharge', name: "coinRecharge", component: () => import("../views/recharge/coinRecharge.vue"), // redirect: '/coinRecharge/add', - children: [ - // 金币新增充值 + children: [ + // 金币新增充值 { path: 'add', name: "addCoinRecharge", component: () => import("../views/recharge/addCoinRecharge.vue"), - meta: { permissionId: 17 } // 对应"提交金币充值"id=17 + meta: {permissionId: 17} // 对应"提交金币充值"id=17 }, - // 金币充值明细详情 + // 金币充值明细详情 { path: 'detail', name: "coinRechargeDetail", component: () => import("../views/recharge/coinRechargeDetail.vue"), - meta: { permissionId: 18 } // 对应"查看金币充值明细"id=18 + meta: {permissionId: 18} // 对应"查看金币充值明细"id=18 } ] }, - // 金币退款 + // 金币退款 { path: '/coinRefund', name: "coinRefund", component: () => import("../views/refund/coinRefund.vue"), // redirect: '/coinRefund/add', - meta: { permissionId: 7 }, - children: [ + meta: {permissionId: 7}, + children: [ // 金币新增退款 { path: 'add', name: "addCoinRefund", component: () => import("../views/refund/addCoinRefund.vue"), - meta: { permissionId: 21 } // 对应"提交金币退款"id=21 + meta: {permissionId: 21} // 对应"提交金币退款"id=21 }, // 金币退款明细详情 { path: 'detail', name: "coinRefundDetail", component: () => import("../views/refund/coinRefundDetail.vue"), - meta: { permissionId: 22 } // 对应"查看金币退款明细"id=22 + meta: {permissionId: 22} // 对应"查看金币退款明细"id=22 } ] }, - // 客户账户明细 + // 客户账户明细 { path: '/usergold', name: "usergold", component: () => import("../views/usergold/clientCount.vue"), // redirect: '/usergold/detail', - meta: { permissionId: 8 }, - children: [ - // 金币明细 + meta: {permissionId: 8}, + children: [ + // 金币明细 { path: 'detail', name: "clientCountDetail", component: () => import("../views/usergold/clientCountDetail.vue"), - meta: { permissionId: 23 } // 对应"查看金币明细"id=23 + meta: {permissionId: 23} // 对应"查看金币明细"id=23 }, - // 金币余额 + // 金币余额 { path: 'balance', name: "clientCountBalance", component: () => import("../views/usergold/clientCountBalance.vue"), - meta: { permissionId: 24 } // 对应"查看金币余额"id=24 + meta: {permissionId: 24} // 对应"查看金币余额"id=24 }, - ] - }, - // 权限管理 + ] + }, + // 权限管理 { path: '/permissions', name: "permissions", component: () => import("../views/permissions/permission.vue"), - meta: { permissionId: [25, 26, 27, 28, 29] } // 对应权限管理下的所有操作 + meta: {permissionId: [25, 26, 27, 28, 29]} // 对应权限管理下的所有操作 }, - // 没有权限 + // 没有权限 { path: '/noPermission', name: "noPermission", component: () => import("../views/noPermissionPage.vue") } - ] - }, + ] + }, // 跳转页面(无需权限) { path: '/PasswordSuccess', @@ -229,26 +222,14 @@ const getAllPermissionIds = (menuTree) => { return permissionIds; }; -// 存储管理员信息(全局可访问) -const adminData = ref(null); -// 获取管理员信息(返回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 adminStore = useAdminStore() + const { adminData, menuTree } = storeToRefs(adminStore) + const token = localStorage.getItem("token"); const machineId = localStorage.getItem("machineId"); @@ -265,40 +246,27 @@ router.beforeEach(async (to, from, next) => { let roleId = null; console.log('adminData:', adminData) try { - await getAdminData(); // 等待管理员信息获取完成 roleId = adminData.value.roleId; if (!roleId) { - throw new Error("未获取到roleId"); + localStorage.removeItem('token'); // 清除token,强制重新登录 + next(`/login?machineId=${machineId || ''}`); + return; } } catch (error) { localStorage.removeItem('token'); // 清除token,强制重新登录 + adminStore.clearState() 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) + // 拿权限id + userPermissionIds = getAllPermissionIds(menuTree.value) + // 2.4 权限验证(逻辑不变) + console.log('to.meta:', to.meta) const requiresPermission = to.meta && to.meta.permissionId; if (requiresPermission) { diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..2a86cd8 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,45 @@ +// src/store/index.js +import { defineStore } from 'pinia' + +export const useAdminStore = defineStore('admin', { + state: () => ({ + adminData: null, // 用户信息 + menuTree: null, // 菜单权限树 + }), + actions: { + // 设置用户信息并同步到localStorage + setAdminData(info) { + this.adminData = info + localStorage.setItem('adminData', JSON.stringify(info)) + }, + + // 设置菜单树并同步到localStorage + setMenuTree(tree) { + this.menuTree = tree + localStorage.setItem('menuTree', JSON.stringify(tree)) + }, + + // 从localStorage初始化数据 + initFromLocalStorage() { + const adminData = localStorage.getItem('adminData') + const menuTree = localStorage.getItem('menuTree') + + if (adminData) { + this.adminData = JSON.parse(adminData) + } + + if (menuTree) { + this.menuTree = JSON.parse(menuTree) + } + }, + + // 清空状态并移除localStorage数据 + clearState() { + this.adminData = null + this.menuTree = null + localStorage.removeItem('adminData') + localStorage.removeItem('menuTree') + // localStorage.removeItem('token') + } + } +}) \ No newline at end of file diff --git a/src/utils/menuUtils.js b/src/utils/menuUtils.js new file mode 100644 index 0000000..971ba33 --- /dev/null +++ b/src/utils/menuUtils.js @@ -0,0 +1,61 @@ +// 菜单树过滤 (展示的? ) +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 升序 +} + +// 辅助函数:查找第一个可访问的菜单项 +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 + } + } + return null +} + +// 路由映射 +export const getRoutePath = (menu) => { + // 路由映射表:key为接口menuName,value为对应路由路径 + const routeMap = { + '工作台': '/workspace', + + '审核页面': '/audit', + '财务审核': '/audit', + + '充值审核': '/audit/rechargeAudit', + '退款审核': '/audit/refundAudit', + + '汇率管理': '/rate', + + '消耗管理': '/coinConsume', + '消耗页面': '/coinConsume', + + '权限管理': '/permissions', + + '充值管理': '/coinRecharge', + '充值页面': '/coinRecharge', + + '退款管理': '/coinRefund', + '退款页面': '/coinRefund', + + '客户账户明细': '/usergold', + }; + + // 未匹配的菜单默认使用id作为路由(可根据实际需求调整) + return routeMap[menu.menuName] || '/noPermissionPage' + +} \ No newline at end of file diff --git a/src/views/audit/audit.vue b/src/views/audit/audit.vue index 3df670e..b631b87 100644 --- a/src/views/audit/audit.vue +++ b/src/views/audit/audit.vue @@ -1,66 +1,117 @@ - +// 初始化逻辑 - 保留原有逻辑处理首次加载 +onMounted(() => { + // 父路由默认跳转 + if (route.path === '/audit') { + const defaultRoute = getDefaultAuditRoute(); + if (defaultRoute) { + navigateTo(defaultRoute); + } else { + // console.warn('用户没有充值审核和退款审核的权限'); + router.push({ name: 'noPermission' }); + } + } else { + // 非父路由初始化当前标签状态(带权限校验) + if (route.name === 'rechargeAudit' && hasRechargePermission.value) { + activeTab.value = route.name; + } else if (route.name === 'refundAudit' && hasRefundPermission.value) { + activeTab.value = route.name; + } + } +}); + \ No newline at end of file diff --git a/src/views/consume/coinConsume.vue b/src/views/consume/coinConsume.vue index 5c05116..90040f8 100644 --- a/src/views/consume/coinConsume.vue +++ b/src/views/consume/coinConsume.vue @@ -1,61 +1,121 @@ +// 监听路径变化,处理直接访问父路由的情况 +watch(() => route.path, (newPath) => { + if (newPath === '/coinConsume') { // 假设父路由路径为/coinConsume + const defaultRoute = getDefaultConsumeRoute(); + if (defaultRoute) { + navigateTo(defaultRoute); + } else { + console.warn('用户没有新增消耗和金币消耗明细的权限'); + // router.push({ name: 'noPermission' }); // 可根据实际需求启用 + } + } +}); + +// 初始化逻辑(完善权限校验和状态同步) +onMounted(() => { + // 父路由默认跳转 + if (route.path === '/coinConsume') { // 假设父路由路径为/coinConsume + const defaultRoute = getDefaultConsumeRoute(); + if (defaultRoute) { + navigateTo(defaultRoute); + } else { + // console.warn('用户没有新增消耗和金币消耗明细的权限'); + router.push({ name: 'noPermission' }); // 可根据实际需求启用 + } + } else { + // 子路由直接进入时同步状态(带权限校验) + if (route.name === routeMap.add && hasAddPermission.value) { + activeTab.value = 'add'; + } else if (route.name === routeMap.detail && hasDetailPermission.value) { + activeTab.value = 'detail'; + } + } +}); + \ No newline at end of file diff --git a/src/views/home.vue b/src/views/home.vue index 9f6d8ef..da56eea 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -1,118 +1,37 @@ @@ -224,31 +73,44 @@ const handleClosePasswordDialog = () => { z-index: 100; /* 确保侧边栏在其他元素之上 */ "> - + diff --git a/src/views/login.vue b/src/views/login.vue index c24c406..b77bf3c 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -1,11 +1,12 @@ +// 监听路径变化,处理直接访问父路由路径的情况 +watch(() => route.path, (newPath) => { + if (newPath === '/coinRecharge') { // 假设父路由路径为/coinRecharge + const defaultTab = getDefaultRoute(); + if (defaultTab) { + navigateTo(defaultTab); + } else { + console.warn('用户没有新增充值和金币充值明细的权限'); + // router.push({ name: 'noPermission' }); // 可根据实际需求启用 + } + } +}); + +// 初始化 +onMounted(() => { + // 父路由默认跳转(同时处理路径和名称两种判断方式) + if (route.name === 'coinRecharge' || route.path === '/coinRecharge') { + const defaultTab = getDefaultRoute(); + if (defaultTab) { + navigateTo(defaultTab); + } else { + // console.warn('用户没有新增充值和金币充值明细的权限'); + router.push({ name: 'noPermission' }); // 可根据实际需求启用 + } + } else { + // 子路由直接进入时同步状态(增加权限校验) + if (route.name === routeMap.add && hasAddPermission.value) { + activeTab.value = 'add'; + } else if (route.name === routeMap.detail && hasDetailPermission.value) { + activeTab.value = 'detail'; + } + } +}); + \ No newline at end of file diff --git a/src/views/refund/coinRefund.vue b/src/views/refund/coinRefund.vue index e3bd42c..7cfdcca 100644 --- a/src/views/refund/coinRefund.vue +++ b/src/views/refund/coinRefund.vue @@ -1,61 +1,119 @@ +// 监听路径变化,处理父路由直接访问的情况 +watch(() => route.path, (newPath) => { + if (newPath === '/coinRefund') { + const defaultTab = getDefaultRoute(); + if (defaultTab) { + navigateTo(defaultTab); + } else { + console.warn('用户没有新增退款和金币退款明细的权限'); + // router.push({ name: 'noPermission' }); + } + } +}); + +// 初始化 +onMounted(() => { + // 父路由默认跳转 + if (route.path === '/coinRefund') { + const defaultTab = getDefaultRoute(); + if (defaultTab) { + navigateTo(defaultTab); + } else { + // console.warn('用户没有新增退款和金币退款明细的权限'); + router.push({ name: 'noPermission' }); + } + } else { + // 子路由直接进入时同步状态 + if (route.name === routeMap.add && hasAddPermission.value) { + activeTab.value = 'add'; + } else if (route.name === routeMap.detail && hasDetailPermission.value) { + activeTab.value = 'detail'; + } + } +}); + \ No newline at end of file diff --git a/src/views/usergold/clientCount.vue b/src/views/usergold/clientCount.vue index 49330e6..cd11c32 100644 --- a/src/views/usergold/clientCount.vue +++ b/src/views/usergold/clientCount.vue @@ -1,61 +1,104 @@ +// 初始化 +onMounted(() => { + // 父路由默认跳转 + if (route.name === 'usergold') { + const defaultTab = getDefaultRoute(); + if (defaultTab) { + navigateTo(defaultTab); + } + } else { + // 子路由直接进入时同步状态 + if (route.name === routeMap.detail) { + activeTab.value = 'detail'; + } else if (route.name === routeMap.balance) { + activeTab.value = 'balance'; + } + } +}); + \ No newline at end of file