重构ui底层动态路由+动态菜单

This commit is contained in:
likun 2025-05-16 10:07:49 +08:00
parent 6ece507c90
commit 790d5d22ca
17 changed files with 291 additions and 192 deletions

View File

@ -14,6 +14,7 @@ import (
func (svc *Service) CheckToken(token string, userId int) error {
if err := tokenlib.ValidToken(token, userId); err != nil {
xlog.Warnf("valid token:%v user:%v error:%v", token, userId, err)
return err
}

View File

@ -14,11 +14,19 @@ import LocalCache from "@/stores/localCache.js";
import empty from '@/components/restful/empty.vue';
import {getWhereConditionDesc} from "@/utils/string.js";
import UserDetail from "@/components/game/userDetail.vue";
import router from "@/router/index.js";
const props = defineProps({
rowClickDialogBtns: Array,
})
let rowClickDialogBtns = []
if (props.rowClickDialogBtns) {
rowClickDialogBtns = props.rowClickDialogBtns
}
// console.log("btns:", rowClickDialogBtns)
const cachedResource = LocalCache.getCache("resource");
const listRsp = ref({fields_desc: [], rows: []})
@ -28,7 +36,7 @@ const resource_raw_node = cachedResource;
const hasListPermit = resource_raw_node.meta.methods.get !== undefined && resource_raw_node.meta.methods.get === true;
const globalClickBtns = resource_raw_node.meta.global_click_btns ? resource_raw_node.meta.global_click_btns : [];
let rowClickBtns = resource_raw_node.meta.row_click_btns ? resource_raw_node.meta.row_click_btns : []
rowClickBtns.push(...props.rowClickDialogBtns)
rowClickBtns.push(...rowClickDialogBtns)
const rowClickBtnVisibleList = reactive(rowClickBtns.map(() => false))
const rowClickBtnSelectRow = ref(null)
@ -176,6 +184,7 @@ const dialogObjectForm = ref({
})
const route = useRoute()
let hasRouteQueryParams = false
if (route.query.from != undefined && route.query.from != "") {
Object.keys((route.query)).forEach(key => {
const value = route.query[key]
@ -183,10 +192,11 @@ if (route.query.from != undefined && route.query.from != "") {
return
}
dialogObjectForm.value[key] = value
console.log("进入页面,来自查询参数的数据:", key, value)
// console.log("", key, value)
})
console.log("进入页面,来自查询参数的数据:", route.query)
// console.log("", route.query)
dialogAddVisible.value = true
hasRouteQueryParams = true
}
@ -390,7 +400,7 @@ function deleteItem(row) {
}
const handleCloseDialog = () => {
console.log("关闭添加/编辑弹窗")
// console.log("/")
dialogAddVisible.value = false
dialogEditVisible.value = false
dialogObjectForm.value = {
@ -399,6 +409,13 @@ const handleCloseDialog = () => {
selectedItem.value = null
selectedItemNum.value = 0
selectedItemBag.value = null
if (hasRouteQueryParams) {
router.replace({
path: route.path,
query: {}
})
}
}
const loadingRemoteItems = ref(false)

View File

@ -6,6 +6,7 @@ import {useRoute} from 'vue-router';
import LocalCache from "@/stores/localCache.js";
import {getPermissionTreeByProjects, getSelectedPermissions} from "@/utils/permission.js";
import empty from '@/components/restful/empty.vue';
import {getProjects} from "@/stores/user.js";
const cachedResource = LocalCache.getCache("resource");
@ -29,8 +30,7 @@ const item = ref({
})
// console.log("enter table, resource:", cachedResource)
const projectsRoute = LocalCache.getCache("projectsRoute")
const projectsRoute = getProjects()
const permitTreeDefaultProps = {children: 'children', label: 'label'}
const permitTree = ref([])

View File

@ -1,59 +1,46 @@
import router, {isGetUserInfo, projectOpTreeRoutes, setProjectOperationRoutes} from './router'
import {cachedProject, cachedProjectResource} from '@/stores/project.js'
import {getUserInfo} from "@/api/sys.js";
import LocalCache from "@/stores/localCache.js";
import ExpireCache from "@/stores/expireCache.js";
import router from './router'
import {getToken} from '@/stores/user.js'
import userStore from "@/stores/user.js";
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
console.log("进入登录页面")
next()
} else {
const token = ExpireCache.getCache("token");
if (!token) {
// 没有token
console.log("重定向到login")
next('/login?redirect=/home') // 没有token都重定向到登录页面
return
}
const token = getToken();
// console.log("token:", token)
// alert(token.token)
if (token && token.token !== undefined && token.token !== '') {
if (to.path === '/login') {
// 有token 跳过登录
console.log("有token走登录跳过登录", token)
next({path: '/'})
} else {
// 有token就去拉最新的用户数据动态生成菜单
if (to.path.startsWith('/project')) {
const pathSegments = to.path.split("/").filter(segment => segment !== "");
if (pathSegments.length === 3) {
console.log("跳到页面:" + to.path + ",当前所有路由:", router.getRoutes())
// const projectId = pathSegments[pathSegments.length - 2]
// const resource = pathSegments[pathSegments.length - 1]
// 进行资源操作点击
const oldResource = LocalCache.getCache("resource")
if (oldResource.path !== to.path) {
LocalCache.setCache("resource", to)
}
if (!userStore().hasGetUserInfo()) {
// console.log("访问页面获取用户信息。。。", to.path)
userStore().getUserInfo().then(() => {
// console.log("获取用户信息成功,继续:", to.path)
next({...to, replace: true})
}).catch(err => {
userStore().logout().then(() => {
ElMessage.error(err)
next({path: '/'})
})
})
} else {
console.log("获取过用户数据,跳过获取。。。")
next()
}
}
if (isGetUserInfo.value === false) {
getUserInfo().then((res) => {
const projectList = ref([])
// res.data.user
// res.data.token
projectList.value = res.data.projects
LocalCache.setCache('user', res.data.user_info)
setProjectOperationRoutes(projectList.value)
// console.log("all routes:", router.getRoutes())
next({...to, replace: true}); // 在已有页面里查找
}, (err) => {
console.log("跳转路径:", to.path, " 报错:", err)
LocalCache.deleteCache("token")
LocalCache.deleteCache("user")
LocalCache.deleteCache("projectsRoute")
})
} else {
console.log("访问页面" + to.path + "已经请求过用户数据,跳过获取")
// console.log("op tree routes length valid:", projectOpTreeRoutes.value.length)
} else {
if (to.path === '/login') {
// alert("登录页面重复走登录,排查为什么!")
next()
return
}
console.log("token无效走登录。", token)
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
}
})

View File

@ -2,10 +2,6 @@ import {createRouter, createWebHistory} from 'vue-router'
import Home from '../views/Home.vue'
import LocalCache from "@/stores/localCache.js";
export const isGetUserInfo = ref(false) // 是否获取过后端路由接口,用于第一次进页面或者刷新页面拉取路由恢复菜单
export const projectOpTreeRoutes = ref([]) // 项目操作的树形路由,用于菜单树生成
export const projectOpFlatRoutes = ref([]) // 项目操作的展平路由用于直接挂钩到父组件即home页面否则点击子菜单无法在父页面内容区显示
export const constProjectResourceRoute = {
path: '/project',
name: 'project',
@ -62,7 +58,7 @@ export const constHomeChildrenRoutes = [
constProjectResourceRoute,
]
const homeRoute = {
export const homeRoute = {
path: '/',
name: 'home',
component: Home,
@ -83,73 +79,3 @@ const router = createRouter({
})
export default router
export function setProjectOperationRoutes(projectList) {
// console.log("resourceList:", projectList)
isGetUserInfo.value = true
projectOpTreeRoutes.value = []
projectOpFlatRoutes.value = []
for (let i = 0; i < projectList.length; i++) {
const project = projectList[i]
let projectHasAnyResourcePermission = false // 判断项目是否至少有一个资源权限,否则就不显示这个项目菜单
const projectRoute = {
path: '/project/' + project.project_id,
name: project.project_id,
meta: {
projectId: project.project_id,
projectName: project.project_name,
},
// component: () => {
// return import('@/components/restful/table.vue')
// },
children: [],
props: true
}
const resourceList = project.resource_list
resourceList.forEach((resource) => {
const routePath = projectRoute.path + "/" + resource.resource
const resourceRoute = {
path: routePath,
name: projectRoute.name + "_" + resource.resource,
meta: {
desc: resource.desc,
projectId: project.project_id,
resource: resource.resource,
resource_url: routePath,
methods: {},
global_click_btns: resource.global_btns,
row_click_btns: resource.row_btns,
},
component: () => import('@/views/project/project_op.vue'),
props: true
}
if (resource.resource === 'cdkey') {
resourceRoute.component = () => import('@/views/project/project_cdkey.vue')
}
resource.show_methods.forEach((method) => {
if (method == "get") {
projectHasAnyResourcePermission = true
}
resourceRoute.meta.methods[method] = true
})
// router.addRoute(projectRoute)
projectRoute.children.push(resourceRoute)
projectOpFlatRoutes.value.push(resourceRoute)
// console.log("add resource route:", resourceRoute)
})
if (projectHasAnyResourcePermission) {
projectOpTreeRoutes.value.push(projectRoute)
}
homeRoute.children = constHomeChildrenRoutes.concat(projectOpFlatRoutes.value)
router.addRoute(homeRoute)
}
LocalCache.setCache("projectsRoute", projectList)
LocalCache.setCache("homeRoute", homeRoute)
// console.log("重新获取了用户数据,刷新路由表:", router.getRoutes())
}

View File

@ -1,6 +1,7 @@
class LocalCache {
// 添加
setCache(key, value) {
// console.log("设置cache:", key, value)
window.localStorage.setItem(key, JSON.stringify(value))
}
@ -11,6 +12,8 @@ class LocalCache {
if (value) {
return JSON.parse(value)
}
// console.log("获取到cache token", key, value)
return undefined
}
// 删除

View File

@ -0,0 +1,3 @@
const store = createPinia()
export default store

View File

@ -1,30 +0,0 @@
import {ref} from 'vue'
import {defineStore} from 'pinia'
/*
项目pinia缓存刷新页面就清空了适合一些页面跳转传参逻辑
*/
export const cachedProject = defineStore('clickedProject', () => {
const project = ref({})
const set = (setProject) => project.value = setProject
const get = () => project.value
return {
set,
get,
persist: true
}
}, {persist: true})
export const cachedProjectResource = defineStore('clickedProject', () => {
const setResource = ref({})
const set = (setResource) => setResource.value = setResource
const get = () => setResource.value
return {
set,
get,
persist: true
}
}, {
persist: true
})

189
ui/src/stores/user.js Normal file
View File

@ -0,0 +1,189 @@
import LocalCache from "@/stores/localCache.js";
import {getUserInfo, login} from "@/api/sys.js";
import {defineStore} from 'pinia';
import router, {constHomeChildrenRoutes, homeRoute} from "@/router/index.js";
const userStore = defineStore(
'user',
{
state: () => ({
tokenInfo: getToken(), // token expire_at
userInfo: getCacheUserInfo(), // userid nick_name icon character permissions
projects: getProjects(), // project_id project_name resource_list resource_total_list
dynamicMenuItems: [], // 动态项目菜单
dynamicRouteChildren: [], // 动态项目路由
isGetUserInfo: false,
}),
actions: {
hasGetUserInfo() {
this.generateDynamicRoutes()
return this.isGetUserInfo
},
getDynamicRouteChildren() {
return this.dynamicRouteChildren;
},
pushDynamicRouteChildren(child) {
this.dynamicRouteChildren.push(child);
},
pushDynamicMenuItems(item) {
this.dynamicMenuItems.push(item);
},
clearDynamicRouteChildren(child) {
this.dynamicRouteChildren = []
},
clearDynamicMenuItems(item) {
this.dynamicMenuItems = []
},
login(userName, password) {
return new Promise((resolve, reject) => {
login({user: userName, password: password}).then(res => {
this.userInfo = res.data.user_info
this.tokenInfo = res.data.token_info
this.projects = res.data.projects
// console.log("登录响应成功设置token", this.tokenInfo)
setToken(this.tokenInfo)
setUserInfo(this.userInfo)
setProjects(this.projects)
this.generateDynamicRoutes()
this.isGetUserInfo = true
resolve();
}).catch(err => {
reject(err)
})
})
},
getUserInfo() {
return new Promise((resolve, reject) => {
getUserInfo().then(res => {
this.userInfo = res.data.user_info
this.tokenInfo = res.data.token_info
this.projects = res.data.projects
setToken(this.tokenInfo)
setUserInfo(this.userInfo)
setProjects(this.projects)
this.generateDynamicRoutes()
this.isGetUserInfo = true
resolve();
}).catch(err => {
reject(err)
})
})
},
logout() {
console.log("走logout清理缓存。。")
this.userInfo = null
this.tokenInfo = null
this.projects = null
clearUserInfo()
clearToken()
clearProjects()
},
generateDynamicRoutes() {
this.clearDynamicRouteChildren()
this.clearDynamicMenuItems()
const projectList = this.projects;
for (let i = 0; i < projectList.length; i++) {
const project = projectList[i]
let projectHasAnyResourcePermission = false // 判断项目是否至少有一个资源权限,否则就不显示这个项目菜单
const projectRoute = {
path: '/project/' + project.project_id,
name: project.project_id,
meta: {
projectId: project.project_id,
projectName: project.project_name,
},
// component: () => {
// return import('@/components/restful/table.vue')
// },
children: [],
props: true
}
const resourceList = project.resource_list
resourceList.forEach((resource) => {
const routePath = projectRoute.path + "/" + resource.resource
const resourceRoute = {
path: routePath,
name: projectRoute.name + "_" + resource.resource,
meta: {
desc: resource.desc,
projectId: project.project_id,
resource: resource.resource,
resource_url: routePath,
methods: {},
global_click_btns: resource.global_btns,
row_click_btns: resource.row_btns,
},
component: () => import('@/views/project/project_op.vue'),
props: true
}
if (resource.resource === 'cdkey') {
resourceRoute.component = () => import('@/views/project/project_cdkey.vue')
}
resource.show_methods.forEach((method) => {
if (method == "get") {
projectHasAnyResourcePermission = true
}
resourceRoute.meta.methods[method] = true
})
// router.addRoute(projectRoute)
projectRoute.children.push(resourceRoute)
this.pushDynamicRouteChildren(resourceRoute)
// console.log("add resource route:", resourceRoute)
})
if (projectHasAnyResourcePermission) {
this.pushDynamicMenuItems(projectRoute)
}
}
console.log("pinia重新生成路由。。")
homeRoute.children = constHomeChildrenRoutes.concat(this.getDynamicRouteChildren())
router.addRoute(homeRoute)
}
}
}
)
export const getToken = () => {
return LocalCache.getCache("tokenInfo")
}
const setToken = (token) => {
LocalCache.setCache("tokenInfo", token)
}
export const clearToken = () => {
LocalCache.deleteCache("tokenInfo")
}
const getCacheUserInfo = () => {
return LocalCache.getCache("userInfo")
}
export const clearUserInfo = () => {
LocalCache.deleteCache("userInfo")
}
const setUserInfo = (userInfo) => {
LocalCache.setCache("userInfo", userInfo)
}
export const getProjects = () => {
return LocalCache.getCache("projects")
}
export const clearProjects = () => {
LocalCache.deleteCache("projects")
}
const setProjects = (projects) => {
LocalCache.setCache("projects", projects)
}
export default userStore

View File

@ -6,6 +6,7 @@ import LocalCache from "@/stores/localCache.js";
// import ExpireCache from "@/stores/expireCache";
import errcodeDetail from '@/components/widget/errcodeDetail.vue'
import userStore from "@/stores/user.js";
// 创建axios实例
const service = axios.create({
@ -28,11 +29,11 @@ const service = axios.create({
const reqInterceptor = (config) => {
let user = LocalCache.getCache("user");
let token = LocalCache.getCache("token");
let user = userStore().userInfo;
let token = userStore().tokenInfo;
let userId = user ? parseInt(user.user_id, 10) : 0;
token = token ? token : "";
token = token ? token.token : "";
config.headers = config.headers || {}
config.headers.UserId = userId;
@ -63,6 +64,7 @@ const resInterceptor = (res) => {
if (code === 5) {
// token过期
console.log("token无效重新登录")
userStore().logout()
location.href = "/login"
return Promise.reject()
}

View File

@ -2,19 +2,22 @@
import {useRoute, useRouter} from 'vue-router'
import {computed} from 'vue'
import avatarUrl from '@/assets/icon/header.png'
import {constUserChildrenRoutes, isGetUserInfo, projectOpTreeRoutes} from '@/router/index.js'
import {constUserChildrenRoutes} from '@/router/index.js'
import LocalCache from "@/stores/localCache.js";
import welcome from '@/views/welcome.vue'
import userStore from "@/stores/user.js";
const router = useRouter()
const route = useRoute()
const userInfo = LocalCache.getCache("user")
const nickName = userInfo.nick_name
const nickName = userStore().userInfo.nick_name
//
const activeMenu = computed(() => route.path)
const dynamicMenuItems = userStore().dynamicMenuItems
// console.log("", dynamicMenuItems)
const hasClickedMenu = ref(false)
@ -48,10 +51,7 @@ function logout() {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
isGetUserInfo.value = false
LocalCache.deleteCache("user")
LocalCache.deleteCache("token")
LocalCache.deleteCache("projectsRoute")
userStore().logout()
router.push('/login')
}).catch(() => {
});
@ -97,7 +97,7 @@ function logout() {
<!-- <el-divider></el-divider>-->
<!-- 动态菜单每个项目操作 -->
<template v-for="project in projectOpTreeRoutes">
<template v-for="project in dynamicMenuItems">
<el-sub-menu :index="project.path">
<!-- 设置菜单栏标题 -->
<template #title>

View File

@ -56,6 +56,7 @@ import LocalCache from "@/stores/localCache";
import ExpireCache from "@/stores/expireCache";
import {login} from "@/api/sys.js";
import router from "@/router/index.js";
import userStore from "@/stores/user.js";
const redirect = ref(undefined);
const {proxy} = getCurrentInstance();
@ -77,18 +78,13 @@ const submitForm = (formEl) => {
//
proxy.$refs.ruleFormRef.validate((valid) => {
if (valid) {
login(loginForm.value).then((res) => {
// token
// console.log('', res.payload.username)
// console.log('token', res.payload.token)
LocalCache.setCache('token', res.data.token_info.token)
LocalCache.setCache('user', res.data.user_info)
ExpireCache.setCache("token", res.data.token_info.token, res.data.token_info.expire_at)
console.log("登录成功,返回数据:", res.data,)
userStore().login(loginForm.value.user, loginForm.value.password).then(() => {
console.log("登录成功,推送首页。。")
router.push({path: "/"})
}, (res) => {
console.log("login response error:")
console.log(res)
}).catch(() => {
ElMessage.error("login response error")
})
} else {
console.log('error submit!')

View File

@ -4,6 +4,7 @@ import tableView from '@/components/restful/table.vue'
import LocalCache from "@/stores/localCache.js";
import {useRouter} from 'vue-router'
import {constProjectResourceRoute} from "@/router/index.js";
import userStore from "@/stores/user.js";
// import {Aim, ArrowRightBold} from '@element-plus/icons-vue'
LocalCache.setCache("project", {})
@ -11,7 +12,7 @@ LocalCache.setCache("project", {})
const router = useRouter()
let resourceCache = constProjectResourceRoute
const userInfo = LocalCache.getCache("user")
const userInfo = userStore().userInfo
if (userInfo.character !== "admin") {
resourceCache.meta.methods = {} //
}

View File

@ -3,10 +3,11 @@
import tableView from "@/components/restful/table.vue";
import LocalCache from "@/stores/localCache.js";
import userDetail from "@/components/game/userDetail.vue";
import router, {projectOpTreeRoutes} from '@/router/index.js'
import router from '@/router/index.js'
import userStore from "@/stores/user.js";
const cachedResource = LocalCache.getCache("resource");
const homeRoutes = LocalCache.getCache("homeRoute");
const homeRoutes = userStore().dynamicRouteChildren
const projectId = cachedResource.meta.projectId
const rowClickDialogBtns = []
@ -29,14 +30,14 @@ switch (cachedResource.meta.resource) {
btn_color_type: "default",
btn_type: 2,
click_handler: (row) => {
for (let i = 0; i < homeRoutes.children.length; i++) {
const curRecourseRoute = homeRoutes.children[i]
for (let i = 0; i < homeRoutes.length; i++) {
const curRecourseRoute = homeRoutes[i]
if (curRecourseRoute.meta === undefined) {
continue
}
if (curRecourseRoute.meta.projectId === projectId && curRecourseRoute.meta.resource === "whitelist") {
//
console.log("角色页面点击:", row, "跳转快速封禁")
// console.log(":", row, "")
LocalCache.setCache("resource", curRecourseRoute)
router.push({
path: curRecourseRoute.path, query: {
@ -61,14 +62,14 @@ switch (cachedResource.meta.resource) {
btn_color_type: "info",
btn_type: 2,
click_handler: (row) => {
for (let i = 0; i < homeRoutes.children.length; i++) {
const curRecourseRoute = homeRoutes.children[i]
for (let i = 0; i < homeRoutes.length; i++) {
const curRecourseRoute = homeRoutes[i]
if (curRecourseRoute.meta === undefined) {
continue
}
if (curRecourseRoute.meta.projectId === projectId && curRecourseRoute.meta.resource === "ban") {
//
console.log("角色页面点击:", row, "跳转快速封禁")
// console.log(":", row, "")
LocalCache.setCache("resource", curRecourseRoute)
router.push({
path: curRecourseRoute.path, query: {

View File

@ -1,6 +1,7 @@
<script setup>
import tableView from "@/components/restful/tableUser.vue";
import LocalCache from "@/stores/localCache.js";
import userStore from "@/stores/user.js";
let resourceCache = {
meta: {
@ -15,7 +16,7 @@ let resourceCache = {
},
},
}
const userInfo = LocalCache.getCache("user")
const userInfo = userStore().userInfo
if (userInfo.character !== "admin") {
resourceCache.meta.methods = {} //
}

View File

@ -1,6 +1,7 @@
<script setup>
import tableView from "@/components/restful/tableUser.vue";
import LocalCache from "@/stores/localCache.js";
import userStore from "@/stores/user.js";
let resourceCache = {
meta: {
@ -15,7 +16,7 @@ let resourceCache = {
},
},
}
const userInfo = LocalCache.getCache("user")
const userInfo = userStore().userInfo
if (userInfo.character !== "admin") {
resourceCache.meta.methods = {} //
}

View File

@ -1,7 +1,8 @@
<script setup>
import LocalCache from "@/stores/localCache.js";
import userStore from "@/stores/user.js";
const userInfo = LocalCache.getCache("user")
const userInfo = userStore().userInfo;
</script>