Merge pull request '完善后台框架及路由菜单' (#6) from dev into main

Reviewed-on: #6
This commit is contained in:
西街长安 2025-06-12 18:59:30 +08:00
commit d7e6b1f5e6
12 changed files with 209 additions and 125 deletions

17
package-lock.json generated
View File

@ -12,6 +12,7 @@
"blueimp-md5": "^2.19.0",
"bootstrap": "^5.3.6",
"core-js": "^3.8.3",
"feather-icons": "^4.29.2",
"jquery": "^3.7.1",
"perfect-scrollbar": "^1.5.6",
"vue": "^2.6.14",
@ -3788,6 +3789,12 @@
"node": ">=6.0"
}
},
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/clean-css": {
"version": "5.3.3",
"resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.3.tgz",
@ -5428,6 +5435,16 @@
"node": ">=0.8.0"
}
},
"node_modules/feather-icons": {
"version": "4.29.2",
"resolved": "https://registry.npmmirror.com/feather-icons/-/feather-icons-4.29.2.tgz",
"integrity": "sha512-0TaCFTnBTVCz6U+baY2UJNKne5ifGh7sMG4ZC2LoBWCZdIyPa+y6UiR4lEYGws1JOFWdee8KAsAIvu0VcXqiqA==",
"license": "MIT",
"dependencies": {
"classnames": "^2.2.5",
"core-js": "^3.1.3"
}
},
"node_modules/figures": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/figures/-/figures-2.0.0.tgz",

View File

@ -11,6 +11,7 @@
"blueimp-md5": "^2.19.0",
"bootstrap": "^5.3.6",
"core-js": "^3.8.3",
"feather-icons": "^4.29.2",
"jquery": "^3.7.1",
"perfect-scrollbar": "^1.5.6",
"vue": "^2.6.14",

View File

@ -24,7 +24,6 @@ Vue.use(Alert)
async function initApp() {
//初始化请求系统配置信息
await store.dispatch('config/fetch_config')
new Vue({
router,
store,

View File

@ -11,9 +11,13 @@ const register = async (param)=> await request.post('/api/Auth/Register',param)
//获取所有系统配置
const getAllConfig = async ()=> await request.get('/api/systemconfig/getallsystemconfig')
//获取用户个人信息
const getUserInfo = async () => await request.get('/api/User/UserInfo')
export default {
login,
register,
SendValidateCode,
getAllConfig
getAllConfig,
getUserInfo
}

View File

@ -34,12 +34,45 @@ const routes = [
path: '/layout',
component: () => import('@/views/layout/Home.vue'),
children: [
{ path: 'index', component: () => import('@/views/layout/Dashboard.vue') }
{
path: 'index',
component: () => import('@/views/layout/Dashboard.vue'),
meta: {
title: '主页',
icon: 'home',
showInMenu: true,
showInUser:false,
isHome:true
}
},
{
path: 'users',
component: () => import('@/views/layout/Users.vue'),
meta: {
title: '用户管理',
icon: 'users',
showInMenu: true,
showInUser:false,
isHome:false
}
},
{
path: 'test',
component: () => import('@/views/layout/Users.vue'),
meta: {
title: '用户管理',
icon: 'inbox',
showInMenu: true,
showInUser:false,
isHome:false
}
},
]
}
]
const router = new VueRouter({
mode: 'history',
routes
})

View File

@ -2,11 +2,13 @@ import Vue from 'vue'
import Vuex from 'vuex'
import config from './modules/config'
import userinfo from './modules/userinfo'
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
config
config,
userinfo
}
})

View File

@ -0,0 +1,60 @@
import api from '@/request/api'
const state = {
user: JSON.parse(localStorage.getItem('USER_INFO') || null)
}
const mutations = {
/**
* 储存当前登录用户信息
* @param {*} state
* @param {*} userinfo 用户信息
*/
SET_USERINFO(state,userinfo){
state.user = userinfo
localStorage.setItem('USER_INFO',JSON.stringify(userinfo))
state.loaded = true
},
/**
* 退出登录清除用户数据
* @param {*} state
*/
LOGOUT(state){
state.user = null
localStorage.removeItem('USER_INFO')
}
}
const actions = {
async fetchUserInfo({ commit, state }) {
if (state.user) return
try {
const res = await api.getUserInfo()
if(res.code == 1000){
commit('SET_USERINFO', res.data)
}else{
console.error('获取用户信息失败')
}
} catch (e) {
console.error('获取用户信息失败', e)
}
}
}
const getters = {
/**
* 用户是否为管理员
* @param {*} state
* @returns
*/
isAdmin:state => state.user.roles.some(x => x.role == 'Admin')
}
export default {
namespaced:true,
state,
mutations,
actions,
getters
}

View File

@ -21,30 +21,34 @@ function runqueque(token) {
* @returns
*/
export function handlerefresherror(response) {
const { config, data } = response
if (data.code == 2009 && !config._retry) {
const { config, data, status } = response
if ((data.code == 2009 || status == 401 && config.url != "/api/auth/refresh") && !config._retry) {
config._retry = true
if (!isrefreshing) {
isrefreshing = true
return request.post('/auth/refreshToken', {
return request.post('/api/auth/refresh', {
userid: auth.getUserId(),
refreshtoken: auth.getRefreshToken()
})
.then(r => {
const {Token , RefreshToken} = r.data.data
auth.setAccessToken(Token)
auth.setRefreshToken(RefreshToken)
runqueque(Token)
// 用新 token 重做原始请求
config.headers['Authorization'] = `Bearer ${Token}`
return request(config)
})
.catch(err => {
auth.clear()
window.location.href = '/auth/login'
return Promise.reject(err)
})
.finally(() => isrefreshing = false)
.then(r => {
if (r.code != 1000) {
window.location.href = '/auth/login'
return Promise.reject(r.data)
}
const { token, refreshToken } = r.data
auth.setAccessToken(token)
auth.setRefreshToken(refreshToken)
runqueque(token)
// 用新 token 重做原始请求
config.headers['Authorization'] = `Bearer ${token}`
return request(config)
})
.catch(err => {
auth.clear()
window.location.href = '/auth/login'
return Promise.reject(err)
})
.finally(() => isrefreshing = false)
}
// 刷新中:队列化

View File

@ -71,6 +71,7 @@ export default {
try {
const res = await this.$api.login(this.formdata)
let msg = ''
let type = 'danger'
switch (res.code) {
case 2000: msg = '登录成功'; break
case 2001: msg = '用户名或密码错误'; break
@ -82,12 +83,18 @@ export default {
//
auth.setAccessToken(res.data.token)
auth.setRefreshToken(res.data.refreshToken)
auth.setUserId(res.data.userInfo.id)
this.$store.commit('userinfo/SET_USERINFO', res.data.userInfo)
type = 'success'
this.$alert(msg, type)
this.$router.push('/layout/index')
}else{
this.$alert(msg, type)
}
this.$alert(msg,'success')
//this.$router.push()
} catch (e) {
console.log(e)
alert('未知错误')
this.$alert('未知错误', 'error')
} finally {
//
this.formdata.password = passwordOld
@ -104,6 +111,4 @@ export default {
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -5,16 +5,16 @@
<div class="" id="navbarNav">
<ul class="navbar-nav" id="leftNav">
<li class="nav-item">
<a class="nav-link" id="sidebar-toggle" href="#"><i data-feather="arrow-left"></i></a>
<a class="nav-link" id="sidebar-toggle" href="#" @click="toggerSiderBar"><i data-feather="arrow-left"></i></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">主页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Settings</a>
<a class="nav-link" href="#">设置</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Help</a>
<a class="nav-link" href="#">帮助</a>
</li>
</ul>
</div>
@ -136,100 +136,16 @@
<div class="page-sidebar">
<ul class="list-unstyled accordion-menu">
<li class="sidebar-title">
Main
主页
</li>
<li class="active-page">
<a href="index.html"><i data-feather="home"></i>Dashboard</a>
<li class="active-page" v-for="item in routeMenu.filter(x => x.meta.isHome)" :key="item.path">
<router-link :to="item.path"><i :data-feather="item.meta.icon"></i>{{item.meta.title}}</router-link>
</li>
<li class="sidebar-title">
Apps
菜单
</li>
<li>
<a href="email.html"><i data-feather="inbox"></i>Email</a>
</li>
<li>
<a href="calendar.html"><i data-feather="calendar"></i>Calendar</a>
</li>
<li>
<a href="social.html"><i data-feather="user"></i>Social</a>
</li>
<li>
<a href="file-manager.html"><i data-feather="message-circle"></i>File Manager</a>
</li>
<li class="sidebar-title">
Elements
</li>
<li>
<a href="index.html"><i data-feather="code"></i>Components<i
class="fas fa-chevron-right dropdown-icon"></i></a>
<ul class="">
<li><a href="alerts.html"><i class="far fa-circle"></i>Alerts</a></li>
<li><a href="typography.html"><i class="far fa-circle"></i>Typography</a></li>
<li><a href="icons.html"><i class="far fa-circle"></i>Icons</a></li>
<li><a href="badge.html"><i class="far fa-circle"></i>Badge</a></li>
<li><a href="buttons.html"><i class="far fa-circle"></i>Buttons</a></li>
<li><a href="dropdowns.html"><i class="far fa-circle"></i>Dropdowns</a></li>
<li><a href="list-group.html"><i class="far fa-circle"></i>List Group</a></li>
<li><a href="toasts.html"><i class="far fa-circle"></i>Toasts</a></li>
<li><a href="modal.html"><i class="far fa-circle"></i>Modal</a></li>
<li><a href="pagination.html"><i class="far fa-circle"></i>Pagination</a></li>
<li><a href="popovers.html"><i class="far fa-circle"></i>Popovers</a></li>
<li><a href="progress.html"><i class="far fa-circle"></i>Progress</a></li>
<li><a href="spinners.html"><i class="far fa-circle"></i>Spinners</a></li>
<li><a href="accordion.html"><i class="far fa-circle"></i>Accordion</a></li>
</ul>
</li>
<li>
<a href="index.html"><i data-feather="gift"></i>Plugins<i
class="fas fa-chevron-right dropdown-icon"></i></a>
<ul class="">
<li><a href="block-ui.html"><i class="far fa-circle"></i>Block UI</a></li>
<li><a href="session-timeout.html"><i class="far fa-circle"></i>Session Timeout</a></li>
<li><a href="tree-view.html"><i class="far fa-circle"></i>Tree View</a></li>
</ul>
</li>
<li>
<a href="index.html"><i data-feather="edit"></i>Form<i
class="fas fa-chevron-right dropdown-icon"></i></a>
<ul class="">
<li><a href="form-elements.html"><i class="far fa-circle"></i>Form Elements</a></li>
<li><a href="form-layout.html"><i class="far fa-circle"></i>Form Layout</a></li>
<li><a href="form-validation.html"><i class="far fa-circle"></i>Form Validation</a></li>
</ul>
</li>
<li>
<a href="cards.html"><i data-feather="layers"></i>Cards</a>
</li>
<li>
<a href="index.html"><i data-feather="list"></i>Tables<i
class="fas fa-chevron-right dropdown-icon"></i></a>
<ul class="">
<li><a href="tables.html"><i class="far fa-circle"></i>Basic Tables</a></li>
<li><a href="data-tables.html"><i class="far fa-circle"></i>Data Tables</a></li>
</ul>
</li>
<li>
<a href="charts.html"><i data-feather="pie-chart"></i>Charts</a>
</li>
<li class="sidebar-title">
Other
</li>
<li>
<a href="index.html"><i data-feather="star"></i>Pages<i
class="fas fa-chevron-right dropdown-icon"></i></a>
<ul class="">
<li><a href="invoice.html"><i class="far fa-circle"></i>Invoice</a></li>
<li><a href="404.html"><i class="far fa-circle"></i>404 Page</a></li>
<li><a href="500.html"><i class="far fa-circle"></i>500 Page</a></li>
<li><a href="blank-page.html"><i class="far fa-circle"></i>Blank Page</a></li>
<li><a href="login.html"><i class="far fa-circle"></i>Login</a></li>
<li><a href="register.html"><i class="far fa-circle"></i>Register</a></li>
<li><a href="lockscreen.html"><i class="far fa-circle"></i>Lockscreen</a></li>
<li><a href="price.html"><i class="far fa-circle"></i>Price</a></li>
</ul>
</li>
<li>
<a href="#"><i data-feather="check-circle"></i>Documentation</a>
<li v-for="item in routeMenu.filter(x => !x.meta.isHome)" :key="item.path">
<router-link :to="item.path"><i :data-feather="item.meta.icon"></i>{{item.meta.title}}</router-link>
</li>
</ul>
</div>
@ -242,8 +158,47 @@
<script>
import feather from 'feather-icons'
import { mapGetters } from 'vuex'
export default {
data(){
return{
routeMenu: []
}
},
methods:{
/**
* 展开/关闭侧边菜单栏
*/
toggerSiderBar(){
document.body.classList.toggle('sidebar-hidden')
},
updateRouterMenu(){
const baseroutes = this.$router.options.routes
const route = baseroutes.find(x => x.path == '/layout')
this.routeMenu = route.children.filter(x => this.isAdmin ? x.meta.showInMenu : (x.meta.showInMenu && x.meta.showInUser))
}
},watch:{
isAdmin:{
handler(newval){
this.updateRouterMenu()
},
immediate:true
}
},
computed:{
...mapGetters('userinfo',['isAdmin'])
},
mounted(){
feather.replace()
},
beforeCreate(){
//
this.$store.dispatch('userinfo/fetchUserInfo')
}
}
</script>
<script>
<style>
</script>
</style>

View File

View File

@ -2,7 +2,11 @@ const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
publicPath: process.env.BRANCH_PATH || '/', // 👈 支持动态子路径部署
transpileDependencies: true
transpileDependencies: true,
dev: {
devtool: 'source-map',
}
})