描述
描述 | 地址 |
源码地址 | springboot3.0+spring security6+vue3 前后分离: springboot3.0+spring security6+vue3 前后分离 |
springboot3.0+spring security6+vue3 前后分离之后端部分 | springboot3.0+spring security6+vue3 前后分离之后端部分-CSDN博客 |
使用vite创建项目
npm create vite
输入项目的名字
选中vue
选择TypeScript
进入项目目录
npm install
启动项目
浏览器访问
项目路径
安装插件
npm i axios
npm i element-plus @element-plus/icons-vue
npm i vue-router
npm i pinia
vue.d.ts
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
vite.config.ts
配置跨域
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve:{
alias:{
//相对路径别名配置 用@符号替代src
"@": path.resolve("./src")
}
},
server: {
proxy: {
'/sp': {
target: 'http://localhost:8083/',
changeOrigin: true,
//把api前置去掉
rewrite: (path) => path.replace(/^\/sp/, "")
}
}
}
})
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
//解析非相对模块的基地址,默认是当前目录
"baseUrl": "./",
"paths": {
//路径映射,相当于baseUrl
"@/*":["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue","vue.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
.env.development
#测试环境标识
NODE_ENV="dev"
VITE_APP_TITLE="应用标题"
#基础路径
VITE_APP_BASE_API="http://localhost:8083"
#后端服务器的域名
VITE_SERVE="http://aa.com"
src\permisstion.ts
拦截所有路径
//引入路由器
import router from '@/router/index'
//引入小仓库
import loginStore from "@/store/modules/login";
//引入大仓库
import pinia from "@/store/index.ts";
//获取小仓库对象
let lgst=loginStore(pinia);
//全局前置路由守卫,任意路由切换都会触发钩子
router.beforeEach((to,from,next)=>{
//console.log(to.path)
//如果是从login进来
if(to.path=='/login'){
//放行往下走
next()
}else{
//判断是否存在token
if(!lgst.token){
//如果未登录,直接跳到登录界面
next('/login')
}else{
//获取登录用户权限信息
let res=lgst.useMyAuth();
// console.log('path', to.path);
// console.log('auth', to.meta.auth);
// console.log('to', to);
res.then((x)=>{
if(!to.meta.auth){
//如果权限是空的 也放行
next()
}else if(x.indexOf(to.meta.auth)!==-1){
//如果登录了 权限也有放行
next()
}else{
//如果都不满足 调到无权限界面
next('/per')
}
})
}
}
})
//全局后置守卫
router.afterEach((to,from)=>{
})
src\main.ts
入口文件
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//国际化
//@ts-ignore 忽略当前ts文件报红的错误,不设置这个在打包的时候会报错
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
//引入element-plus的icons
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
//引入路由
import router from './router/index'
//引入大仓库
import pinia from './store/index'
//引入全局路由守卫
import '@/permisstion.ts'
const app = createApp(App)
//使用ElementPlus,并设置国际化
app.use(ElementPlus, {
locale: zhCn,
})
//使用路由
app.use(router)
//使用大仓库
app.use(pinia)
//全局注册icon图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
//挂载到app上
app.mount('#app')
src\App.vue
<template>
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
src\api\index.ts
后端接口文件
import request from "@/utils/request";
//统一管理接口
enum API{
//登录后端接口
LOGIN="/dengLu",
//获取当前登录人的菜单
GET_MY_MENU_LIST="/getMyMenuList",
//获取当前登录人的权限
GET_MY_AUTH="/getMyAuth",
//获取所有用户
GET_USER_LIST="/getUserList",
//获取所有角色
GET_ROLE_LIST="/getRoleList",
//获取所有菜单
GET_MENU_LIST="/getMenuList",
//添加用户
ADD_USER="/addUser",
//获取当前登录人信息
GET_USER_INFO="/getUserInfo",
//为用户分配角色
SET_USER_ROLE="/setUserRole",
//查看用户对应的角色id
GET_USER_ROLE_IDS="/getUserRoleIds",
//添加角色
ADD_ROLE="/addRole",
//修改角色
EDIT_ROLE="/editRole",
//删除角色
DELETE_ROLE="/deleteRole",
//添加菜单
ADD_MENU="/addMenu",
//修改菜单
EDIT_MENU="/editMenu",
//删除菜单
DELETE_MENU="/deleteMenu",
//查看角色对应的权限
GET_ROLE_AUTH="/getRoleAuth",
//为角色分配权限
SET_ROLE_AUTH="/setRoleAuth",
}
//登录接口方法
export const reqLogin=(data:any)=> request.post<any,any>(API.LOGIN,data)
//获取当前登录人的菜单
export const getMyMenuList=()=> request.post<any>(API.GET_MY_MENU_LIST)
//获取当前登录人的权限
export const getMyAuth=()=> request.post<any>(API.GET_MY_AUTH)
//获取所有用户
export const getUserList=()=> request.post<any>(API.GET_USER_LIST)
//获取所有角色
export const getRoleList=()=> request.post<any>(API.GET_ROLE_LIST)
//获取所有菜单
export const getMenuList=()=> request.post<any>(API.GET_MENU_LIST)
//添加用户
export const addUser=(data:any)=> request.post<any>(API.ADD_USER,data)
//获取当前登录人信息
export const getUserInfo=()=> request.post<any>(API.GET_USER_INFO)
//为用户分配角色
export const setUserRole=(data:any)=> request.post<any>(API.SET_USER_ROLE,data)
//查看用户对应的角色id
export const getUserRoleIds=(data:any)=> request.post<any>(API.GET_USER_ROLE_IDS,data)
//添加角色
export const addRole=(data:any)=> request.post<any>(API.ADD_ROLE,data)
//修改角色
export const editRole=(data:any)=> request.post<any>(API.EDIT_ROLE,data)
//删除角色
export const deleteRole=(data:any)=> request.post<any>(API.DELETE_ROLE,data)
//添加菜单
export const addMenu=(data:any)=> request.post<any>(API.ADD_MENU,data)
//修改菜单
export const editMenu=(data:any)=> request.post<any>(API.EDIT_MENU,data)
//删除菜单
export const deleteMenu=(data:any)=> request.post<any>(API.DELETE_MENU,data)
//查看角色对应的权限
export const getRoleAuth=(data:any)=> request.post<any>(API.GET_ROLE_AUTH,data)
//为角色分配权限
export const setRoleAuth=(data:any)=> request.post<any>(API.SET_ROLE_AUTH,data)
src\router\index.ts
//通过vue-router插件实现模版路由配置
import { createRouter,createWebHashHistory } from 'vue-router';
//引入路由数组
import {luYou} from '@/router/routers'
//创建路由器
const router=createRouter({
//路由模式
history: createWebHashHistory(),
//注意单词 别写错了
//注意单词 别写错了
routes: luYou
})
export default router;
src\router\routers.ts
路由数组 就是往那个vue界面去跳转
//单独暴露路由
export const luYou=[
{
path:'/login',
component:()=> import('@/views/login/index.vue'),
name:'login',
meta:{
//隐藏不显示到菜单上 true:隐藏 ,false:显示
hidden:true,
//菜单的名称
title:'login',
//饿了么ui图标的名字 固定写法
icon:'Plus',
auth:''
}
},
{
path:'/per',
component:()=> import('@/views/per/index.vue'),
name:'per',
meta:{
hidden:true,
title:'per',
icon:'Plus',
auth:''
}
},
{
path:'/404',
component:()=> import('@/views/404/index.vue'),
name:'404',
meta:{
hidden:true,
title:'404',
icon:'Plus',
auth:''
}
},
{
//根页面
path:'/',
component:()=> import('@/views/menu/index.vue'),
name:'menu',
meta:{
hidden:true,
title:'login',
icon:'Plus',
auth:''
}
},
{
//匹配到不存在的路径就跳转404
path:'/:pathMatch(.*)*',
//重定向到404
redirect: '/404',
//任意路由
name:'any',
meta:{
hidden:true,
title:'any',
icon:'Plus',
auth:''
}
},
{
path:'/org',
component:()=> import('@/views/menu/index.vue'),
name:'org',
meta:{
hidden:true,
title:'组织管理',
icon:'Plus',
auth:'org'
},
children:[
{
path:'/org/er',
component:()=> import('@/views/org/index.vue'),
name:'orgEr',
meta:{
hidden:true,
title:'组织管理二级菜单',
icon:'Plus',
auth:'org:er'
}
}
]
},
{
path:'/user',
component:()=> import('@/views/menu/index.vue'),
name:'user',
meta:{
hidden:true,
title:'用户管理',
icon:'Plus',
auth:'user'
},
children:[
{
path:'/user/er',
component:()=> import('@/views/user/index.vue'),
name:'userEr',
meta:{
hidden:true,
title:'用户管理二级菜单',
icon:'Plus',
auth:'user:er'
}
}
]
},
{
path:'/role',
component:()=> import('@/views/menu/index.vue'),
name:'role',
meta:{
hidden:true,
title:'角色管理',
icon:'Plus',
auth:'role'
},
children:[
{
path:'/role/er',
component:()=> import('@/views/role/index.vue'),
name:'roleEr',
meta:{
hidden:true,
title:'角色管理二级菜单',
icon:'Plus',
auth:'role:er'
}
}
]
},
{
path:'/auth',
component:()=> import('@/views/menu/index.vue'),
name:'auth',
meta:{
hidden:true,
title:'权限管理',
icon:'Plus',
auth:'auth'
},
children:[
{
path:'/auth/er',
component:()=> import('@/views/auth/index.vue'),
name:'authEr',
meta:{
hidden:true,
title:'权限管理二级菜单',
icon:'Plus',
auth:'auth:er'
}
}
]
},
{
path:'/apply',
component:()=> import('@/views/menu/index.vue'),
name:'apply',
meta:{
hidden:true,
title:'应用管理',
icon:'Plus',
auth:'apply'
},
children:[
{
path:'/apply/er',
component:()=> import('@/views/apply/index.vue'),
name:'applyEr',
meta:{
hidden:true,
title:'应用管理二级菜单',
icon:'Plus',
auth:'apply:er'
}
}
]
},
]
src\store\modules\login.ts
//创建用户相关的小仓库
import {defineStore} from 'pinia'
//引入接口
import { reqLogin,getUserInfo,getMyAuth } from '../../api/index';
//创建登录小仓库
let loginStore=defineStore('login',{
//小仓库存储数据的地方
state:()=>{
return {
//从本地拿到token
token: localStorage.getItem('TOKEN'),
//当前登录人姓名
name:'',
}
},
//异步| 逻辑处理的地方,在方法前面定义async,那么必须在拿到对应的方法前面加await 一起使用
actions:{
async userLogin(data: any){
//拿到登录后的结果
let res:any=await reqLogin(data);
if(res){
let code=res.code;
let msg=res.msg;
let data=res.data;
if(code=='200'){
//刷新token
this.token=data;
//把token放入本地
localStorage.setItem('TOKEN',data);
return 'ok';
}else{
//失败返回失败对象
return Promise.reject(Error(msg))
}
}else{
//失败返回失败对象
return Promise.reject(Error('账号密码输入错误'))
}
},
async userInfo(){
//登录后获取用户信息 请求头自带token
let res=await getUserInfo();
this.name=res.data.name;
},
userLogout(){
//退出登录 清空用户信息 和token
this.token=''
this.name=''
localStorage.removeItem('TOKEN');
},
async useMyAuth(){
//登录后获取用户信息 请求头自带token
let res=await getMyAuth();
return res.data;
},
},
getters:{
}
})
//对外暴露小仓库的方法
export default loginStore;
src\store\modules\luyou.ts
//创建用户相关的小仓库
import {defineStore} from 'pinia'
//引入路由数组
import {luYou} from '@/router/routers'
//引入接口
import { getMyAuth } from '../../api/index';
//创建小仓库
let luYouStore=defineStore('luYou',{
//小仓库存储数据的地方
state:()=>{
return {
//路由
ly:[]
}
},
//异步| 逻辑处理的地方,在方法前面定义async,那么必须在拿到对应的方法前面加await 一起使用
actions:{
async useMyMenuList(){
//获取当前登录人的权限集合
let res=await getMyAuth();
let auths=res.data;
//赋值给新的数组
let newLy = luYou.map(x => {
if(auths.indexOf(x.meta.auth) !== -1){
//如果路径匹配 那么设置为显示菜单
x.meta.hidden=false
}
let children=x.children;
if(children){
children.map(y=>{
if(auths.indexOf(y.meta.auth) !== -1){
//如果路径匹配 那么设置为显示菜单
y.meta.hidden=false
}
return y;
})
}
return x;
});
//更新state的数据
this.ly=newLy;
}
},
getters:{
}
})
//对外暴露小仓库的方法
export default luYouStore;
src\store\index.ts
//引入pinia
import {createPinia} from 'pinia'
//创建大仓库
let pinia=createPinia();
//暴露大仓库 入口文件需要安装仓库
export default pinia;
src\utils\request.ts
axios 二次封装 请求头 自动带token
import axios from "axios";
//引入消息提示
import {ElMessage} from 'element-plus';
//引入登录小仓库
import loginStore from "@/store/modules/login";
//利用axios对象的create方法,去创建axios实例
let request=axios.create({
//基础路径 自动带上前缀
baseURL: import.meta.env.VITE_APP_BASE_API,
//超时时间,超过时间就获取不到后端的数据了
timeout: 5000
})
//request实例添加请求拦截器
request.interceptors.request.use((config)=>{
let lgst=loginStore();
if(lgst.token){
//config配置对象,headers属性请求头,给后台携带公共参数
//设置token 放到请求头上
config.headers['Authorization']='Bearer '+lgst.token;
}
//console.log('请求拦截器:',config)
return config;
})
//添加响应拦截器
request.interceptors.response.use((response)=>{
//成功回调
//console.log('响应拦截器:',response)
return response.data;
},(error)=>{
//http状态码
//console.log('错误状态:',error.response)
if(error.response==undefined){
ElMessage({
type:'error',
message:'网络异常'
})
}else{
ElMessage({
type:'error',
message:error.response.data.msg
})
}
//返回一个失败的Promise对象
return Promise.reject(error);
})
//对外暴露 才能被外部使用
export default request;
src\views\404\index.vue
<template>
<div>
我是404
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
src\views\apply\index.vue
<template>
我是应用
</template>
<script>
export default {
}
</script>
<style>
</style>
src\views\auth\index.vue
<template>
<div>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>权限管理</span>
</div>
</template>
<el-button type="primary" icon="Plus" @click="add(0,'1')" v-if="authErAdd">添加一级菜单</el-button>
<el-table
:data="tableData"
style="width: 100%; margin-bottom: 20px"
row-key="id"
border
default-expand-all
>
<el-table-column prop="name" label="菜单名称/按钮名称" sortable width="200"/>
<el-table-column prop="auth" label="权限名称" sortable width="180"/>
<el-table-column prop="path" label="路径" sortable width="180"/>
<el-table-column prop="createTime" label="创建时间" sortable width="180"/>
<el-table-column prop="desc" label="备注" sortable width="180"/>
<el-table-column
prop="isType"
label="类型"
width="180"
>
<template #default="scope">
<el-tag v-if="scope.row.isType=='1'">
菜单
</el-tag>
<el-tag v-if="scope.row.isType=='2'">
按钮
</el-tag>
</template>
</el-table-column>
<el-table-column prop="caozuo" label="操作">
<!--具名插槽-->
<template #default="caozuo">
<!--一级菜单 可以添加二级菜单-->
<el-button type="primary" icon="plus" v-if="authErAddSub && caozuo.row.isType=='1' && caozuo.row.parentId==0" @click="add(caozuo.row.id,'1')">添加二级菜单</el-button>
<!--二级菜单 可以添加按钮-->
<el-button type="primary" icon="plus" v-if="authErBtn && caozuo.row.isType=='1' && caozuo.row.parentId!=0" @click="add(caozuo.row.id,'2')">添加按钮</el-button>
<el-button type="primary" v-if="authErEdit" icon="edit" @click="edit(caozuo.row)">修改</el-button>
<el-button type="primary" v-if="authErDel" icon="delete" @click="del(caozuo.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!--点击新增弹出对话框-->
<el-dialog
v-model="dialogVisible"
title="添加"
width="30%"
:before-close="handleClose"
>
<el-form :model="addBean">
<el-form-item label="父级id" >
<el-input label-width="80%" v-model="addBean.parentId" disabled/>
</el-form-item>
<el-form-item label="名称" >
<el-input label-width="80%" v-model="addBean.name"/>
</el-form-item>
<el-form-item label="权限名称" >
<el-input label-width="80%" v-model="addBean.auth"/>
</el-form-item>
<el-form-item label="路径" >
<el-input label-width="80%" v-model="addBean.path"/>
</el-form-item>
<el-form-item label="排序" >
<el-input-number :min="1" :max="1000" v-model="addBean.paiXu" />
</el-form-item>
<el-form-item label="类型" >
<el-select v-model="addBean.isType" disabled>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<!--底部插槽-->
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="insert">确定</el-button>
</span>
</template>
</el-dialog>
<!--点击修改弹出对话框-->
<el-dialog
v-model="editDialog"
title="修改"
width="30%"
:before-close="handleClose"
>
<el-form :model="updateBean">
<el-form-item label="菜单id">
<el-input label-width="80%" v-model="updateBean.id" disabled/>
</el-form-item>
<el-form-item label="名称" >
<el-input label-width="80%" v-model="updateBean.name"/>
</el-form-item>
<el-form-item label="权限名称" >
<el-input label-width="80%" v-model="updateBean.auth"/>
</el-form-item>
<el-form-item label="路径" >
<el-input label-width="80%" v-model="updateBean.path"/>
</el-form-item>
<el-form-item label="排序" >
<el-input-number :min="1" :max="1000" v-model="updateBean.paiXu" />
</el-form-item>
<el-form-item label="类型" >
<el-select v-model="updateBean.isType" disabled>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<!--底部插槽-->
<template #footer>
<span class="dialog-footer">
<el-button @click="editDialog = false">取消</el-button>
<el-button type="primary" @click="update">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {ref,onMounted,reactive} from 'vue'
//引入后端接口
import {getMenuList,addMenu,editMenu,deleteMenu,getMyAuth} from '@/api/index'
//引入提示
import { ElMessage,ElMessageBox } from 'element-plus'
import { fa } from 'element-plus/es/locale/index.mjs';
//表格数据
let tableData=ref([]);
//获取分页数据
let getData=async ()=>{
//必须异步阻塞去获取 否则拿到的就是一个Promise对象
let res=await getMenuList();
tableData.value=res.data;
}
//加载时调用
onMounted(()=>{
//调用查询
getData();
//获取按钮权限
getAuthData();
})
//下拉框内容
const options = [
{
value: '1',
label: '菜单',
},
{
value: '2',
label: '按钮',
}
]
//----------------------添加-----------------------
//修改对象
let addBean=reactive({
parentId:null,
name:'',
auth:'',
isType:'',
paiXu:'',
path:''
})
//默认不显示对话框
let dialogVisible=ref(false)
//添加显示对话框
let add=(parentId,isType)=>{
//显示对话框
dialogVisible.value=true;
//把输入框显示的内容清空
addBean.parentId=parentId;
addBean.name='';
addBean.auth='';
addBean.isType=isType;
addBean.paiXu='';
addBean.path='';
}
//添加权限 调用后端接口
let insert= async ()=>{
//调用后端添加品牌方法
let res=await addMenu(addBean);
console.log(res);
if(res.code=='200'){
//执行成功后关闭对话框
dialogVisible.value=false;
ElMessage.success('添加成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
}
//----------------------删除-----------------------
let del=async (id)=>{
await ElMessageBox.confirm(
'确定删除吗?',
'删除',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
deleteMenu({
id:id
}).then((res)=>{
if(res.code=='200'){
ElMessage.success('删除成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
})
})
.catch(() => {
})
}
//----------------------修改-----------------------
//修改对象
let updateBean=reactive({
id:null,
name:'',
auth:'',
isType:'',
paiXu:'',
path:''
})
//默认不显示对话框
let editDialog=ref(false)
//显示对话框
let edit=(row)=>{
//显示对话框
editDialog.value=true;
//把输入框显示内容赋值
updateBean.id=row.id;
updateBean.name=row.name;
updateBean.auth=row.auth;
updateBean.isType=row.isType;
updateBean.paiXu=row.paiXu;
updateBean.path=row.path;
}
//修改权限 调用后端接口
let update= async ()=>{
//调用后端添加品牌方法
let res=await editMenu(updateBean);
if(res.code=='200'){
//执行成功后关闭对话框
editDialog.value=false;
ElMessage.success('修改成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
}
//--------------------------按钮显示隐藏----------------------------
//按钮权限
//添加一级菜单按钮默认不显示
let authErAdd=ref(false);
//添加二级菜单按钮默认不显示
let authErAddSub=ref(false);
//修改按钮默认不显示
let authErEdit=ref(false);
//删除按钮默认不显示
let authErDel=ref(false);
//添加子级按钮 默认不显示
let authErBtn=ref(false);
let getAuthData= async()=>{
//获取菜单权限
let res=await getMyAuth();
//如果存在对应的权限 才显示按钮
if(res.data.indexOf('auth:er:add')!==-1){
authErAdd.value=true;
}
if(res.data.indexOf('auth:er:del')!==-1){
authErDel.value=true;
}
if(res.data.indexOf('auth:er:edit')!==-1){
authErEdit.value=true;
}
if(res.data.indexOf('auth:er:add:sub')!==-1){
authErAddSub.value=true;
}
if(res.data.indexOf('auth:er:btn')!==-1){
authErBtn.value=true;
}
}
</script>
<style scoped>
</style>
<style>
</style>
src\views\home\index.vue
<template>
<div>
我是首页
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
src\views\login\index.vue
<template>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>权限管理系统</span>
</div>
</template>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
>
<el-form-item label="账号" prop="account">
<el-input v-model="ruleForm.account" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="ruleForm.password"
type="password"
placeholder="请输入密码"
show-password
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login()">
登录
</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
//引入路由
import {useRouter} from 'vue-router'
//引入登录小仓库
import loginStore from '../../store/modules/login.ts';
//定义接口
interface RuleForm {
account: string
password: string
}
//获取路由器
let router=useRouter();
const ruleFormRef = ref<RuleForm>()
//使用接口 并且 为每一个文本框赋 默认值
const ruleForm = reactive<RuleForm>({
account: 'user',
password: '123456'
})
//校验文本框的内容
const rules = reactive<FormRules<RuleForm>>({
account: [
{ required: true, message: '账号不能为空', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在3到10之间', trigger: 'blur' },
],
password: [
{
required: true,
message: '密码不能为空',
trigger: 'change',
},
]
})
//登录方法
let login= async ()=>{
//阻塞form 表单的校验 必须完成之后才能往下走 因为validate是一个async方法
await ruleFormRef.value.validate();
try {
//阻塞拿到结果才往下走
await loginStore().userLogin(ruleForm);
//弹框
ElMessage({
showClose: true,
message: '登录成功',
type: 'success',
})
//跳转根路径
router.push("/")
} catch (error) {
ElMessage({
showClose: true,
message: (error as Error).message,
type: 'error',
})
}
}
</script>
src\views\menu\index.vue
<template>
<div>
<el-row>
<el-col :span="3">
<!--显示左侧菜单-->
<!--组件传参 把菜单数组传过去-->
<Sub :menuList="lyst.ly"></Sub>
</el-col>
<el-col :span="21">
<div>
<el-row>
<el-col :span="21"></el-col>
<el-col :span="3">
<!--显示用户信息-->
当前登录人:{{ lgStore.name }}
<el-button type="primary" @click="logout">退出登录</el-button>
</el-col>
</el-row>
</div>
<div>
<!--显示菜单点击后的内容-->
<router-view></router-view>
</div>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import Sub from '@/views/sub/index.vue'
//引入小仓库
import luYouStore from '@/store/modules/luyou';
import { onMounted } from 'vue';
//引入路由
import {useRouter} from 'vue-router'
//引入登录小仓库
import loginStore from '../../store/modules/login.ts';
//获取路由器
let router=useRouter();
//使用小仓库
let lyst=luYouStore()
//拿到登录仓库对象
let lgStore=loginStore();
//生命周期挂载
onMounted(()=>{
//初始化菜单
lyst.useMyMenuList();
//获取用户信息
lgStore.userInfo();
})
//退出登录
let logout=()=>{
loginStore().userLogout()
//退出后跳转到登录界面
router.push('/login')
}
</script>
<style scoped>
</style>
src\views\org\index.vue
<template>
我是组织架构
</template>
<script>
export default {
}
</script>
<style>
</style>
src\views\per\index.vue
<template>
<h1>
无权限
</h1>
</template>
<script>
export default {
}
</script>
<style>
</style>
src\views\role\index.vue
<template>
<div>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>角色管理</span>
</div>
</template>
<el-button type="primary" icon="Plus" @click="add" v-if="roleErAdd">添加</el-button>
<el-table :data="tableData" style="width: 100%" >
<el-table-column label="序号" width="180" type="index"/>
<el-table-column prop="roleCode" label="角色编码" width="180" />
<el-table-column prop="roleName" label="角色名称" width="180" />
<el-table-column prop="caozuo" label="操作">
<!--具名插槽-->
<template #default="caozuo">
<el-button type="primary" icon="edit" @click="edit(caozuo.row)" v-if="roleErEdit">修改</el-button>
<el-button type="primary" icon="delete" @click="del(caozuo.row.id)" v-if="roleErDel">删除</el-button>
<el-button type="primary" icon="Setting" @click="dakai(caozuo.row.id)" v-if="roleErFp">分配权限</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!--点击新增按钮弹出对话框-->
<el-dialog
v-model="dialogVisible"
title="添加"
width="30%"
:before-close="handleClose"
>
<el-form :model="addBean">
<el-form-item label="角色编码" >
<el-input label-width="80%" v-model="addBean.roleCode"/>
</el-form-item>
<el-form-item label="角色名称" >
<el-input label-width="80%" v-model="addBean.roleName"/>
</el-form-item>
</el-form>
<!--底部插槽-->
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="insert">确定</el-button>
</span>
</template>
</el-dialog>
<!--点击修改按钮弹出对话框-->
<el-dialog
v-model="editDialog"
title="修改"
width="30%"
:before-close="handleClose"
>
<el-form :model="updateBean">
<el-form-item label="角色id" >
<el-input label-width="80%" v-model="updateBean.id" disabled/>
</el-form-item>
<el-form-item label="角色编码" >
<el-input label-width="80%" v-model="updateBean.roleCode"/>
</el-form-item>
<el-form-item label="角色名称" >
<el-input label-width="80%" v-model="updateBean.roleName"/>
</el-form-item>
</el-form>
<!--底部插槽-->
<template #footer>
<span class="dialog-footer">
<el-button @click="editDialog = false">取消</el-button>
<el-button type="primary" @click="update">确定</el-button>
</span>
</template>
</el-dialog>
<!--点击分配权限按钮弹出对话框-->
<el-dialog
v-model="fpDialog"
title="分配权限"
width="30%"
:before-close="handleClose"
>
<el-tree
ref="tree"
:data="data"
show-checkbox
node-key="id"
default-expand-all
:default-checked-keys="checkedArr"
:props="defaultProps"
>
</el-tree>
<el-button @click="fpDialog = false">取消</el-button>
<el-button type="success" @click="fpAuth">确定</el-button>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {ref,onMounted,reactive} from 'vue'
//引入后端接口
import {getRoleList,addRole,deleteRole,editRole,getMenuList,getRoleAuth,setRoleAuth,getMyAuth} from '@/api/index'
import { ElMessage, ElMessageBox } from 'element-plus'
//----------------------查询-----------------------
//表格数据
let tableData=ref([]);
//获取分页数据
let getData=async ()=>{
//必须异步阻塞去获取 否则拿到的就是一个Promise对象
let res=await getRoleList();
tableData.value=res.data;
}
//加载时调用
onMounted(()=>{
//调用查询
getData();
//获取按钮权限
getAuthData();
})
//----------------------添加-----------------------
//添加对象
let addBean=reactive({
roleCode:'',
roleName:''
})
//默认不显示对话框
let dialogVisible=ref(false)
//添加显示对话框
let add=()=>{
//显示对话框
dialogVisible.value=true;
//把输入框显示的内容清空
addBean.roleCode='';
addBean.roleName='';
}
//添加角色 调用后端接口
let insert= async ()=>{
//调用后端添加品牌方法
let res=await addRole(addBean);
console.log(res);
if(res.code=='200'){
//执行成功后关闭对话框
dialogVisible.value=false;
ElMessage.success('添加成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
}
//----------------------修改-----------------------
//修改对象
let updateBean=reactive({
id:null,
roleCode:'',
roleName:''
})
//默认不显示对话框
let editDialog=ref(false)
//显示对话框
let edit=(row)=>{
//显示对话框
editDialog.value=true;
//把输入框显示内容赋值
updateBean.id=row.id;
updateBean.roleCode=row.roleCode;
updateBean.roleName=row.roleName;
}
//修改角色 调用后端接口
let update= async ()=>{
//调用后端添加品牌方法
let res=await editRole(updateBean);
console.log(res);
if(res.code=='200'){
//执行成功后关闭对话框
editDialog.value=false;
ElMessage.success('修改成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
}
//----------------------删除-----------------------
let del=async (id)=>{
await ElMessageBox.confirm(
'确定删除吗?',
'删除',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
deleteRole({
id:id
}).then((res)=>{
if(res.code=='200'){
ElMessage.success('删除成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
})
})
.catch(() => {
})
}
//----------------------分配权限-----------------------
const fpDialog = ref(false)
//定义后端的显示字段
const defaultProps = {
children: 'children',
label: 'name',
}
//显示的数组
let data=ref([])
//选中的数组
let checkedArr=ref([])
//选中的树的id数组
let tree=ref([])
//分配权限 那一列的 角色id
let authRoleId=ref()
//打开树
let dakai=async(id)=>{
//先把选择的清空掉
checkedArr.value=null;
//调用后端权限方法
let res=await getMenuList();
if(res.code=='200'){
data.value=res.data;
//赋值给角色id
authRoleId.value=id;
//获取当前角色对应的权限
getRoleAuth({
roleId:id
}).then((auths)=>{
//选中的数组赋值
checkedArr.value= getCheck(data.value,[],auths.data);
fpDialog.value=true;
});
}
}
//选中的数组遍历
let getCheck=(item:any,arr:any,auths:any)=>{
item.forEach(x => {
if(x.isType=='2' && auths.indexOf(x.auth)!==-1){
console.log(x.auth,'------',auths)
//如果是按钮 并且选中了 那么设置树展示
arr.push(x.id)
}
if(x.children && x.children.length>0){
//如果有子集那么递归子集
getCheck(x.children,arr,auths)
}
});
return arr;
}
//分配权限方法
let fpAuth= async()=>{
//拿到当前选中的节点的id数组
let menuIds=tree.value.getCheckedKeys();
//为角色分配权限
let res=await setRoleAuth({
roleId:authRoleId.value,
menuIds:menuIds
});
if(res.code=='200'){
authRoleId.value='';
ElMessage.success('分配成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
//关闭窗口
fpDialog.value = false;
}
//--------------------------按钮显示隐藏----------------------------
//按钮权限
//添加按钮默认不显示
let roleErAdd=ref(false);
//分配权限按钮默认不显示
let roleErFp=ref(false);
//修改按钮默认不显示
let roleErEdit=ref(false);
//删除按钮默认不显示
let roleErDel=ref(false);
let getAuthData= async()=>{
//获取菜单权限
let res=await getMyAuth();
//如果存在对应的权限 才显示按钮
if(res.data.indexOf('role:er:add')!==-1){
roleErAdd.value=true;
}
if(res.data.indexOf('role:er:del')!==-1){
roleErDel.value=true;
}
if(res.data.indexOf('role:er:edit')!==-1){
roleErEdit.value=true;
}
if(res.data.indexOf('role:er:fp')!==-1){
roleErFp.value=true;
}
}
</script>
<style scoped>
</style>
<style>
</style>
src\views\sub\index.vue
<template>
<!--菜单-->
<el-menu v-for="x in menuList" :key="x.path">
<!--根据权限 是否展示一级菜单-->
<el-sub-menu :index="x.path" v-if="!x.meta.hidden">
<!--插槽占位 显示一级菜单-->
<template #title>
<!--显示图标-->
<el-icon>
<component :is="x.meta.icon"></component>
</el-icon>
<span>{{x.meta.title}}</span>
</template>
<!--显示二级菜单-->
<template v-for="y in x.children" :key="y.path" >
<!--根据权限 是否展示二级菜单-->
<el-menu-item :index="y.path" @click="goRouter" v-if="!y.meta.hidden">
<!--显示图标-->
<el-icon>
<component :is="y.meta.icon"></component>
</el-icon>
<!--显示标题-->
{{y.meta.title}}
</el-menu-item>
</template>
</el-sub-menu>
</el-menu>
</template>
<script setup lang="ts">
//引入路由器
import {useRouter} from 'vue-router'
//接收父组件传过来的菜单数组
defineProps(['menuList'])
//获取路由器对象
let router=useRouter();
//点击菜单触发的方法
let goRouter=(vc:any)=>{
//通过路由器跳转界面
router.push(vc.index)
}
</script>
<style>
</style>
src\views\user\index.vue
<template>
<div>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>用户管理</span>
</div>
</template>
<!--有权限才显示-->
<el-button type="primary" icon="Plus" @click="add" v-if="userErAdd">添加</el-button>
<el-table :data="tableData" style="width: 100%" >
<el-table-column label="序号" width="180" type="index"/>
<el-table-column prop="account" label="账号" width="180" />
<el-table-column prop="name" label="姓名" width="180" />
<el-table-column prop="createTime" label="创建时间" width="200" />
<el-table-column prop="caozuo" label="操作">
<!--具名插槽-->
<template #default="caozuo">
<!--把表格的主键传递过去-->
<el-button type="primary" icon="Setting" @click="fenPeiRole(caozuo.row.id)" v-if="userErFp">分配角色</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!--点击新增按钮弹出对话框-->
<el-dialog
v-model="dialogVisible"
title="添加"
width="30%"
:before-close="handleClose"
>
<el-form :model="addBean">
<el-form-item label="账号" >
<el-input label-width="80%" v-model="addBean.account"/>
</el-form-item>
<el-form-item label="姓名" >
<el-input label-width="80%" v-model="addBean.name"/>
</el-form-item>
<el-form-item label="密码" >
<el-input label-width="80%" v-model="addBean.password"/>
</el-form-item>
</el-form>
<!--底部插槽-->
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="insert">确定</el-button>
</span>
</template>
</el-dialog>
<!--弹出分配角色对话框-->
<el-dialog
v-model="dialogRole"
title="分配角色"
width="30%"
:before-close="handleClose"
>
<el-form :model="setBean">
<el-form-item label="用户id">
<el-input label-width="80%" v-model="setBean.userId" disabled/>
</el-form-item>
<el-form-item label="角色" >
<el-select
v-model="roleIds"
multiple
clearable
collapse-tags
placeholder=""
popper-class="custom-header"
:max-collapse-tags="1"
>
<template #header>
<el-checkbox
v-model="checkAll"
:indeterminate="indeterminate"
@change="handleCheckAll"
>
All
</el-checkbox>
</template>
<el-option
v-for="item in cities"
:key="item.id"
:label="item.roleName"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<!--底部插槽-->
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogRole = false">取消</el-button>
<el-button type="primary" @click="updateRole">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {ref,onMounted,reactive,watch} from 'vue'
//引入后端接口
import {getUserList,addUser,getRoleList,setUserRole,getUserRoleIds,getMyAuth} from '@/api/index'
import type { CheckboxValueType } from 'element-plus'
//引入提示
import { ElMessage } from 'element-plus'
//表格数据
let tableData=ref([]);
//获取分页数据
let getData=async ()=>{
//必须异步阻塞去获取 否则拿到的就是一个Promise对象
let res=await getUserList();
tableData.value=res.data;
}
//--------------------------按钮显示隐藏----------------------------
//添加按钮默认不显示
let userErFp=ref(false);
//分配角色按钮默认不显示
let userErAdd=ref(false);
let getAuthData= async()=>{
//获取菜单权限
let res=await getMyAuth();
//如果存在对应的权限 才显示按钮
if(res.data.indexOf('user:er:add')!==-1){
userErAdd.value=true;
}
if(res.data.indexOf('user:er:fp')!==-1){
userErFp.value=true;
}
}
//加载时调用
onMounted(()=>{
//调用查询
getData();
//调用权限
getAuthData();
})
//添加对象
let addBean=reactive({
name:'',
account:'',
password:''
})
//默认不显示对话框
let dialogVisible=ref(false)
//添加显示对话框
let add=()=>{
//显示对话框
dialogVisible.value=true;
//把输入框显示的内容清空
addBean.name='';
addBean.account='';
addBean.password='';
}
//添加用户 调用后端接口
let insert= async ()=>{
//调用后端添加品牌方法
let res=await addUser(addBean);
console.log(res);
if(res.code=='200'){
//执行成功后关闭对话框
dialogVisible.value=false;
ElMessage.success('添加成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
}
//角色信息
const cities = ref([])
//全选
const checkAll = ref(false)
//监听值的变化
const indeterminate = ref(false)
//角色id 集合
const roleIds = ref<CheckboxValueType[]>([])
//分配角色对象
let setBean=reactive({
userId:'',
roleIds:[]
})
//默认不显示分配角色对话框
let dialogRole=ref(false)
//显示分配角色对话框
let fenPeiRole= async(id)=>{
//显示对话框
dialogRole.value=true;
//获取后台角色列表接口
let res=await getRoleList();
cities.value=res.data;
//赋值用户id
setBean.userId=id;
//获取已经分配过的角色id
let response=await getUserRoleIds({
userId:id
});
//把已分配过的角色id数组,给roleIds赋值 使得下拉框默认选中
roleIds.value=response.data;
}
// //添加用户 调用后端接口
let updateRole= async ()=>{
//把角色id集合赋值 给后台对象
setBean.roleIds=roleIds.value;
//调用后端分配角色方法
let res=await setUserRole(setBean);
console.log(res);
if(res.code=='200'){
//执行成功后关闭对话框
dialogRole.value=false;
ElMessage.success('分配成功')
//调用查询刷新界面
getData();
}else{
//弹窗错误消息
ElMessage.error(res.msg);
}
}
//监听下拉框发生的变化
watch(roleIds, (val) => {
if (val.length === 0) {
checkAll.value = false
indeterminate.value = false
} else if (val.length === cities.value.length) {
checkAll.value = true
indeterminate.value = false
} else {
indeterminate.value = true
}
})
//点击All 方法
const handleCheckAll = (val: CheckboxValueType) => {
indeterminate.value = false
if (val) {
roleIds.value = cities.value.map((_) => _.id)
} else {
roleIds.value = []
}
}
</script>
<style lang="scss">
.custom-header {
.el-checkbox {
display: flex;
height: unset;
}
}
</style>
启动项目
npm run dev
用户关联角色
角色分配权限
界面上的菜单和按钮 都会动态显示或者隐藏
如果按钮都没有选择 那么这个二级菜单 不会显示
如果按钮选择一个 那么二级菜单也能访问
退出登录后 会清空token和用户信息
没有token 自动跳转到登录界面
访问错误的路径会显示404
没有路径的权限 访问会进入无权限的界面
注册用户之后 密码都是加密的
在登录的时候 会把密码加密进行对比
用户名:zhangsan
密码:123456