前后端分离项目
1.新建登录页面和首页,并配置路由
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
}
2.登录页面代码编写
样式属性
加载静态图片,require()
背景色 FAFAFA
垂直居中
display:flex;
align-items:center;
文字居中
text-align:center;
:xl=6 :lg=7 左右拖动,文字不会换行 <el-col>标签属性
<template>
<el-row type="flex" justify="center">
<el-col :xl=6 :lg=7>
<div>
<p>欢迎来到VueAdmin管理系统</p>
<el-image :src="require('@/assets/icon.png')" style="width: 185px;height: 185px"></el-image>
<p>公众号 MarkerHub</p>
<p>扫码二维码,回复【 VueAdmin 】获取登录密码</p>
</div>
</el-col>
<el-col :span="1">
<el-divider direction="vertical"></el-divider>
</el-col>
<el-col :xl=6 :lg=7>
<el-form :model="loginForm" :rules="rules" ref="loginForm" label-width="100px" class="demo-loginForm">
<el-form-item label="用户名" prop="username" style="width: 380px;">
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password" style="width: 380px;">
<el-input v-model="loginForm.password"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="code" style="width: 380px;">
<el-input v-model="loginForm.code" style="width: 172px;float: left"></el-input>
<!-- <el-image src="" style="float: left;margin-left: 10px;border-radius: 5px;width: auto"></el-image>-->
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('loginForm')">登录</el-button>
<el-button @click="resetForm('loginForm')">重置</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script>
export default {
name: "Login",
data() {
return {
loginForm: {
username: '',
password: '',
code: ''
},
rules: {
username: [
{required: true, message: '请输入用户名', trigger: 'blur'}
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'}
],
code: [
{required: true, message: '请输入验证码', trigger: 'blur'},
{min: 5, max: 5, message: '长度为5个字符', trigger: 'blur'}
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!');
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
<style scoped>
.el-row {
background-color: #fafafa;
height: 100%;
}
.el-col {
/*垂直居中*/
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
}
.el-divider {
height: 200px;
}
</style>
3.获取验证码
在src目录下新建mock.js文件,用于编写随机数据的api,然后我们需要在main.js中引入这个文件:
// 引入mockjs
const Mock = require('mockjs')
// 获取mock.Random对象
const Random = Mock.Random
let Result = {
code: 200,
msg: '操作成功',
data: null
}
/**
* 用于生成响应数据的函数
*Mock.mock(url,post/get,function(options));
* url 表示需要拦截的URL
* post/get 需要拦截的Ajax请求类型
*/
// 获取验证码图片base64编码以及一个随机码
Mock.mock('/captcha', 'get', () => {
Result.data = {
// 获取一个32位随机字符串
token: Random.string(32),
// 生成验证码为4y9i1的base64图片编码
captchaImg: Random.dataImage("120x40", "4y9i1")
}
return Result;
})
// 登录
Mock.mock('/login', 'post', () => {
return Result;
})
require("./mock") //引入mock数据
登录页面,获取验证码
getCaptcha() {
this.$axios.get('/captcha').then(res => {
this.loginForm.token = res.data.data.token
this.captchaImg = res.data.data.captchaImg
})
}
实现登录功能,提交表单后,从Header中获取用户的authorization,也就是含有用户登录信息的jwt,然后提交到store中进行状态管理
this.$store.commit(“SET_TOKEN”, jwt)表示调用store中的SET_TOKEN方法
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post('/login', this.loginForm).then(res => {
const jwt = res.headers['authorization']
// 将jwt存储到应用store中
this.$store.commit("SET_TOKEN", jwt)
this.$router.push("/index")
})
} else {
console.log('error submit!!');
return false;
}
});
}
token的状态同步
export default new Vuex.Store({
state: {
token: ''
},
getters: {},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
localStorage.setItem("token", token)
}
},
actions: {},
modules: {}
})
4.定义全局axios拦截器,内置拦截和外置拦截
在src目录下新建axios.js文件
import axios from "axios";
import router from "@/router";
import Element from "element-ui"
// axios.defaults.baseURL = "http://localhost:8081"
const request = axios.create({
timeout: 5000,
headers: {
'Content-type': 'application/json;charset=utf-8'
}
})
request.interceptors.request.use(config => {
config.headers['Authorization'] = localStorage.getItem("token")
return config
})
request.interceptors.response.use(response => {
let res = response.data;
if (res.code === 200) {
return response
} else {
Element.Message.error(!res.msg ? "系统异常" : res.msg, {duration: 3000})
return Promise.reject(response.data.msg)
}
},
error => {
if (error.response.data) {
error.message = error.response.data.msg
}
if (error.response.status === 401) {
router.push("/login")
}
Element.Message.error(error.message, {duration: 3000})
return Promise.reject(error)
}
)
export default request
在main.js中引入axios.js文件 import axios from “./axios”
点击登录时,提示如下错误
Mock.mock('/login', 'post', () => {
Result.code = 401;
Result.msg = "用户名或验证码错误!!";
return Result;
})
1.整体布局、头部布局和左侧导航菜单填充
justify-content:space-around 各个内容间有一定间隔,水平
<template>
<el-container>
<el-header>
<strong>VueAdmin后台管理系统</strong>
<div class="header-style">
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
<el-dropdown>
<span class="el-dropdown-link">
admin<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>我的</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-link href="http://www.baidu.com">百度</el-link>
<el-link href="https://www.bilibili.com/">B站</el-link>
</div>
</el-header>
<el-container>
<el-aside width="200px">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="Index">
<template slot="title">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
</template>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-s-operation"></i>
<span>系统管理</span>
</template>
<el-menu-item index="1-1">
<template slot="title">
<i class="el-icon-s-custom"></i>
<span slot="title">用户管理</span>
</template>
</el-menu-item>
<el-menu-item index="1-2">
<template slot="title">
<i class="el-icon-rank"></i>
<span slot="title">角色管理</span>
</template>
</el-menu-item>
<el-menu-item index="1-3">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">菜单管理</span>
</template>
</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-s-tools"></i>
<span>系统工具</span>
</template>
<el-menu-item index="2-2">
<template slot="title">
<i class="el-icon-s-order"></i>
<span slot="title">数字字典</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
name: "Index",
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
}
}
}
</script>
<style scoped>
.el-container {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.header-style {
float: right;
width: 210px;
justify-content: space-around;
display: flex;
align-items: center;
}
.el-header {
background-color: #89b5f5;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
text-align: left;
line-height: 200px;
}
.el-main {
color: #333;
line-height: 160px;
}
.el-dropdown-link {
cursor: pointer;
}
.el-menu-vertical-demo {
height: 100%;
}
a {
text-decoration: none;
}
</style>
2.抽取Vue组件
导航栏路由
<router-link to="/menus">
</router-link>
获取用户信息、修改个人密码
getUserInfo() {
this.$axios.get("/sys/userInfo").then(res => {
this.userInfo = res.data.data
})
}
// 获取用户信息
Mock.mock('/sys/userInfo', 'get', () => {
Result.data = {
id: '1',
username: 'adminTest',
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
}
return Result;
})
退出
exitClick() {
this.$axios.post("/logout").then(res => {
localStorage.clear()
sessionStorage.clear()
this.$store.commit("resetState")
this.$router.push("/login")
})
}
resetState: (state => {
state.token = ''
})
去掉a标签下划线 text-decoration:none
3.动态菜单栏
1】动态导航
<el-submenu :index="menu.name" v-for="menu in menuList">
<template slot="title">
<i :class="menu.icon"></i>
<span>{{menu.title}}</span>
</template>
<router-link :to="item.path" v-for="item in menu.children">
<el-menu-item :index="item.name">
<template slot="title">
<i :class="item.icon"></i>
<span slot="title">{{item.title}}</span>
</template>
</el-menu-item>
</router-link>
</el-submenu>
data() {
return {
menuList: [
{
title: '系统管理',
name: 'SysMange',
icon: 'el-icon-s-operation',
path: '',
children: [
{
title: '用户管理',
name: 'SysUser',
icon: 'el-icon-s-custom',
path: '/sys/users',
children: []
}
]
},
{
title: '系统工具',
name: 'SysTools',
icon: 'el-icon-s-tools',
path: '',
children: []
}
]
}
}
2】动态获取导航
获取导航信息
router.beforeEach((to, from, next) => {
axios.get("/sys/menu/nav", {
headers: {
Authorization: localStorage.getItem("token")
}
}).then(res => {
// 获取menuList
store.commit("setMenuList", res.data.data.nav)
// 获取权限
store.commit("setPermList", res.data.data.authority)
// console.log("********", store.state.menus.menuList)
})
next()
})
// 获取导航信息
Mock.mock('/sys/menu/nav', 'get', () => {
let nav = [
{
title: '系统管理',
name: 'SysMange',
icon: 'el-icon-s-operation',
path: '',
component: '',
children: [
{
title: '用户管理',
name: 'SysUser',
icon: 'el-icon-s-custom',
path: '/sys/users',
children: [],
component: 'sys/User'
}
]
},
{
title: '系统工具',
name: 'SysTools',
icon: 'el-icon-s-tools',
path: '',
children: [],
component: ''
}
]
let authority = []
Result.data = {
nav: nav,
authority: authority
}
return Result;
})
将导航列表menuList存储到state中,新建store/modules/menus.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default {
state: {
menuList: [],
permList: []
},
getters: {},
mutations: {
setMenuList(state, menus) {
state.menuList = menus
},
setPermList(state, perms) {
state.permList = perms
}
},
actions: {}
}
在store/index.js中引入
import menus from "./modules/menus";
modules: {
menus
}
在导航栏页面获取导航信息
computed: {
menuList: {
get() {
return this.$store.state.menus.menuList
}
}
}
3】动态绑定路由
router.beforeEach((to, from, next) => {
axios.get("/sys/menu/nav", {
headers: {
Authorization: localStorage.getItem("token")
}
}).then(res => {
// 获取menuList
store.commit("setMenuList", res.data.data.nav)
// 获取权限
store.commit("setPermList", res.data.data.authority)
// console.log("********", store.state.menus.menuList)
let hasRoutes = store.state.menus.hasRoutes
if (!hasRoutes) {
// 动态绑定路由
let newRoutes = router.options.routes
res.data.data.nav.forEach(item => {
if (!item.children) {
return null
}
item.children.forEach(val => {
// 导航转路由
let route = menuToRoute(val)
// 路由添加到路由管理器中
if (route) {
newRoutes[0].children.push(route)
}
})
})
router.addRoutes(newRoutes)
hasRoutes = true
store.commit("changeRouteStatus", hasRoutes)
}
})
next()
})
let menuToRoute = (menu) => {
let route = {
path: menu.path,
name: menu.name,
meta: {
title: menu.title,
icon: menu.icon
},
component: () => import('../views/' + menu.component + '.vue')
}
return route
}
changeRouteStatus(state, hasRoutes) {
state.hasRoutes = hasRoutes
}
4】动态标签页
新建Tags页面
<template>
<el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="tabClick">
<el-tab-pane
v-for="(item, index) in editableTabs"
:key="item.name"
:label="item.title"
:name="item.name">
</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
name: "Tabs",
data() {
return {}
},
computed: {
editableTabsValue: {
get() {
return this.$store.state.menus.editableTabsValue
},
set(val) {
this.$store.state.menus.editableTabsValue = val
}
},
editableTabs: {
get() {
return this.$store.state.menus.editableTabs
},
set(val) {
this.$store.state.menus.editableTabs = val
}
}
},
methods: {
tabClick(target) {
this.$router.push({name: target.name})
},
removeTab(targetName) {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
}
}
</script>
在Home.vue中引用
<el-main>
<Tags></Tags>
<router-view></router-view>
</el-main>
修改导航栏页面SideMenu.vue
// 修改导航栏默认值
:default-active=this.$store.state.menus.editableTabsValue
// 点击事件
// 首页
@click="selectClick({name: 'Index',title: '首页'})"
// other
@click="selectClick(item)"
selectClick(item) {
this.$store.commit("addTab", item)
}
state: {
editableTabsValue: 'Index',
editableTabs: [{
title: '首页',
name: 'Index'
}]
}
addTab(state, tab) {
let index = state.editableTabs.findIndex(val => val.name === tab.name);
if (index === -1) {
state.editableTabs.push({
title: tab.title,
name: tab.name
});
}
state.editableTabsValue = tab.name;
}
标签页存在如下debug
1.关闭标签页,子组件内容仍显示
this.$router.push({name: activeName})
// 首页标签不关闭
if (targetName === 'Index') {
return
}
2.刷新页面,访问路径与标签页内容显示不一致
watch: {
$route(to, form) {
if (to.path != '/login') {
let obj = {
name: to.name,
title: to.meta.title
}
this.$store.commit("addTab", obj)
}
}
}
3.退出重新登录,标签页仍然存在,清空处理
resetState: (state => {
state.menuList = []
state.permList = []
state.hasRoutes = false
state.editableTabsValue = 'Index'
state.editableTabs = [{
title: '首页',
name: 'Index'
}]
})
1.菜单页面布局,增删改查功能
<template>
<div>
<el-form :inline="true">
<el-form-item>
<el-button type="primary" @click="dialogVisible = true">新增</el-button>
</el-form-item>
</el-form>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
row-key="id"
border
stripe
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column prop="nameCn" label="名称"/>
<el-table-column prop="code" label="权限编码"/>
<el-table-column prop="icon" label="图标"/>
<el-table-column prop="type" label="类型">
<template slot-scope="scope">
<el-tag v-if="scope.row.type == 1">目录</el-tag>
<el-tag type="success" v-else-if="scope.row.type == 2">菜单</el-tag>
<el-tag type="info" v-else-if="scope.row.type == 3">按钮</el-tag>
</template>
</el-table-column>
<el-table-column prop="path" label="菜单URL"/>
<el-table-column prop="component" label="菜单组件"/>
<el-table-column prop="orderNum" label="排序号"/>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.status == 1">正常</el-tag>
<el-tag type="info" v-else-if="scope.row.status == 0">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="operate" label="操作">
<template slot-scope="scope">
<el-button type="text" @click="updateClick(scope.row.id)">修改</el-button>
<el-divider direction="vertical"></el-divider>
<template>
<el-popconfirm title="您确定要删除吗?" @confirm="delClick(scope.row.id)">
<el-button slot="reference" type="text">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<!-- 新增 -->
<el-dialog :title="title" :visible.sync="dialogVisible" width="40%">
<el-form :model="menuForm" :rules="rules" ref="menuForm" label-width="100px">
<el-form-item label="上级菜单" prop="parentId">
<el-select v-model="menuForm.parentId" placeholder="请选择上级菜单">
<template v-for="item in tableData">
<el-option :label="item.nameCn" :value="item.id"></el-option>
<template v-for="child in item.children">
<el-option :label="child.nameCn" :value="child.id">
<span>{{'-'+child.nameCn}}</span>
</el-option>
</template>
</template>
</el-select>
</el-form-item>
<el-form-item label="菜单名称" prop="nameCn">
<el-input v-model="menuForm.nameCn"></el-input>
</el-form-item>
<el-form-item label="权限编码" prop="code">
<el-input v-model="menuForm.code"></el-input>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input v-model="menuForm.icon"></el-input>
</el-form-item>
<el-form-item label="菜单URL" prop="path">
<el-input v-model="menuForm.path"></el-input>
</el-form-item>
<el-form-item label="菜单组件" prop="component">
<el-input v-model="menuForm.component"></el-input>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-radio-group v-model="menuForm.type">
<el-radio :label=1>目录</el-radio>
<el-radio :label=2>菜单</el-radio>
<el-radio :label=3>按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="menuForm.status">
<el-radio :label=1>正常</el-radio>
<el-radio :label=0>禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="排序号" prop="orderNum">
<el-input-number v-model="menuForm.orderNum" :min="0" label="排序号"></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="submitForm('menuForm')">提交</el-button>
<el-button @click="resetForm('menuForm')">重置</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
export default {
name: "Menu",
data() {
return {
tableData: [],
dialogVisible: false,
title: '新增菜单信息',
menuForm: {
id: '',
parentId: '',
nameCn: '',
code: '',
icon: '',
path: '',
component: '',
type: '',
status: '',
orderNum: '',
},
rules: {
parentId: [
{required: true, message: '上级菜单不能为空!', trigger: 'blur'}
],
nameCn: [
{required: true, message: '菜单名称不能为空!', trigger: 'blur'}
],
code: [
{required: true, message: '权限编码不能为空!', trigger: 'blur'}
],
type: [
{required: true, message: '类型不能为空!', trigger: 'blur'}
],
status: [
{required: true, message: '状态不能为空!', trigger: 'blur'}
],
orderNum: [
{required: true, message: '排序号不能为空!', trigger: 'blur'}
]
}
}
},
created() {
this.getMenuList();
},
methods: {
getMenuList() {
this.$axios.get("/sys/menu/list").then(res => {
this.tableData = res.data.data
})
},
// 提交操作
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post("/sys/menu/" + (this.menuForm.id ? "update" : "add")).then(res => {
this.resetForm(formName);
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getMenuList();
}
});
this.dialogVisible = false
})
} else {
console.log('error submit!!');
return false;
}
});
},
// 重置操作
resetForm(formName) {
this.$refs[formName].resetFields();
this.menuForm = {}
this.dialogVisible = false
},
// 修改操作
updateClick(id) {
this.$axios.get("/sys/menu/byId" + id).then(res => {
this.menuForm = res.data.data
this.dialogVisible = true
this.title = '修改菜单信息'
})
},
// 删除操作
delClick(id) {
this.$axios.post("/sys/menu/delete" + id).then(res => {
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getMenuList();
}
});
})
}
}
}
</script>
<style scoped>
</style>
2.mock.js,模拟后端返回数据
// 获取菜单列表
Mock.mock('/sys/menu/list', 'get', () => {
Result.data = [
{
id: '1',
parentId: '0',
nameCn: '菜单管理',
code: 'sys:manage',
icon: 'el-icon-s-operation',
path: '',
component: '',
type: '1',
status: '1',
orderNum: '1',
children: [
{
id: '2',
parentId: '1',
nameCn: '用户管理',
code: 'sys:user',
icon: 'el-icon-s-operation',
path: '',
component: '',
type: '2',
status: '1',
orderNum: '2',
children: []
},
{
id: '3',
parentId: '1',
nameCn: '角色管理',
code: 'sys:role',
icon: 'el-icon-s-operation',
path: '',
component: '',
type: '2',
status: '1',
orderNum: '3',
children: []
},
{
id: '4',
parentId: '1',
nameCn: '菜单管理',
code: 'sys:menu',
icon: 'el-icon-s-operation',
path: '',
component: '',
type: '2',
status: '1',
orderNum: '4',
children: []
}
]
}
]
return Result;
})
// 新增,更新,删除,使用正则表达式
Mock.mock(RegExp('/sys/menu/*'), 'post', () => {
return Result;
})
// 通过id获取某条菜单信息
Mock.mock(RegExp('/sys/menu/byId*'), 'get', () => {
Result.data = {
id: '2',
parentId: '1',
nameCn: '用户管理',
code: 'sys:user',
icon: 'el-icon-s-operation',
path: '',
component: '',
type: '2',
status: '1',
orderNum: '2',
children: []
}
return Result;
})
1.角色页面功能,增删改查功能
<template>
<div>
<el-form :inline="true">
<el-form-item>
<el-input placeholder="名称" clearable prefix-icon="el-icon-search">
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="getRoleList">搜索</el-button>
<el-button type="primary" @click="dialogVisible = true">新增</el-button>
<el-popconfirm title="您确定要删除吗?" @confirm="delClick(null)" style="margin-left: 10px;">
<el-button slot="reference" type="danger" :disabled="disabledStatus">批量删除</el-button>
</el-popconfirm>
</el-form-item>
</el-form>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
border
stripe
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column prop="nameCn" label="名称"/>
<el-table-column prop="code" label="唯一编码"/>
<el-table-column prop="describe" label="描述"/>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.status == 1">正常</el-tag>
<el-tag type="info" v-else-if="scope.row.status == 0">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="operate" label="操作">
<template slot-scope="scope">
<el-button type="text" @click="">分配权限</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" @click="updateClick(scope.row.id)">修改</el-button>
<el-divider direction="vertical"></el-divider>
<template>
<el-popconfirm title="您确定要删除吗?" @confirm="delClick(scope.row.id)">
<el-button slot="reference" type="text">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<!-- 新增 -->
<el-dialog :title="title" :visible.sync="dialogVisible" width="40%">
<el-form :model="roleForm" :rules="rules" ref="roleForm" label-width="100px">
<el-form-item label="角色名称" prop="nameCn">
<el-input v-model="roleForm.nameCn"></el-input>
</el-form-item>
<el-form-item label="唯一编码" prop="code">
<el-input v-model="roleForm.code"></el-input>
</el-form-item>
<el-form-item label="描述" prop="describe">
<el-input v-model="roleForm.describe"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="roleForm.status">
<el-radio :label=1>正常</el-radio>
<el-radio :label=0>禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('roleForm')">提交</el-button>
<el-button @click="resetForm('roleForm')">重置</el-button>
</el-form-item>
</el-form>
</el-dialog>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current=current
:page-sizes="[10, 20, 50, 100]"
:page-size=size
layout="total, sizes, prev, pager, next, jumper"
:total=total>
</el-pagination>
</div>
</template>
<script>
export default {
name: "Role",
data() {
return {
roleForm: {},
disabledStatus: true,
dialogVisible: false,
title: '新增角色信息',
tableData: [],
current: 1,
size: 10,
total: 0,
multipleSelection: [],
rules: {
nameCn: [
{required: true, message: '角色名称不能为空!', trigger: 'blur'}
],
code: [
{required: true, message: '唯一编码不能为空!', trigger: 'blur'}
],
status: [
{required: true, message: '状态不能为空!', trigger: 'blur'}
]
}
}
},
created() {
this.getRoleList();
},
methods: {
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
handleSelectionChange(val) {
this.multipleSelection = val;
this.disabledStatus = val.length == 0
},
getRoleList() {
this.$axios.get("/sys/role/list", {
params: {
nameCn: this.roleForm.nameCn,
current: this.current,
size: this.size
}
}).then(res => {
this.tableData = res.data.data.record
this.current = res.data.data.current
this.size = res.data.data.size
this.total = res.data.data.total
})
},
// 提交操作
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post("/sys/role/" + (this.roleForm.id ? "update" : "add")).then(res => {
this.resetForm(formName);
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getRoleList();
}
});
this.dialogVisible = false
})
} else {
console.log('error submit!!');
return false;
}
});
},
// 重置操作
resetForm(formName) {
this.$refs[formName].resetFields();
this.roleForm = {}
this.dialogVisible = false
},
// 修改操作
updateClick(id) {
this.$axios.get("/sys/role/byId" + id).then(res => {
this.roleForm = res.data.data
this.dialogVisible = true
})
},
// 删除操作
delClick(id) {
let ids = []
if (id) {
ids.push(id)
} else {
this.multipleSelection.forEach(item => {
ids.push(item.id)
})
}
console.log("***ids: ", ids)
this.$axios.post("/sys/role/delete", ids).then(res => {
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getRoleList();
}
});
})
},
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.size = val
this.getRoleList();
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.current = val
this.getRoleList();
}
}
}
</script>
<style scoped>
.el-pagination {
float: right;
padding-top: 20px;
}
</style>
2.mock.js,模拟后端返回数据
// 获取角色列表
Mock.mock(RegExp('/sys/role/list'), 'get', () => {
Result.data = {
"record": [
{
id: 1,
nameCn: '普通用户',
code: 'normal',
describe: '查看功能',
status: '1'
}, {
id: 2,
nameCn: '超级管理员',
code: 'admin',
describe: '系统默认最高权限',
status: '1'
}
],
current: 1,
size: 10,
total: 2
}
return Result;
})
// 新增,更新,删除,使用正则表达式
Mock.mock(RegExp('/sys/role/*'), 'post', () => {
return Result;
})
// 通过id获取某条角色信息
Mock.mock(RegExp('/sys/role/byId*'), 'get', () => {
Result.data = {
id: 2,
nameCn: '超级管理员',
code: 'admin',
describe: '系统默认最高权限',
status: '1'
}
return Result;
})
3.分配权限
// 触发事件
<el-button type="text" @click="permClick(scope.row.id)">分配权限</el-button>
<!-- 分配权限对话框 -->
<el-dialog title="分配权限" :visible.sync="permDialogVisible" width="40%">
<el-form :model="permForm" :rules="rules" ref="roleForm" label-width="100px">
<!-- 父子不相互关联 check-strictly=true-->
<el-tree
:data="permData"
show-checkbox
node-key="id"
ref="permTree"
:default-expand-all=true
:check-strictly=true
:props="defaultProps">
</el-tree>
</el-form>
<span slot="footer">
<el-button @click="permDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitPermForm('permForm')">确 定</el-button>
</span>
</el-dialog>
// 定义相关变量
data() {
return {
permDialogVisible: false,
permForm: {},
permData: [],
defaultProps: {
children: 'children',
label: 'nameCn'
}
}
},
methods:{
// 分配权限
permClick(id) {
this.permDialogVisible = true
this.$axios.get("/sys/role/info" + id).then(res => {
this.$refs.permTree.setCheckedKeys(res.data.data.menuIds);
this.permForm = res.data.data
})
},
// 分配权限提交操作
submitPermForm(formName) {
const menuIds = this.$refs.permTree.getCheckedKeys()
console.log("*****", menuIds)
this.$axios.post("/sys/role/addPerm" + this.permForm.id, menuIds).then(res => {
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getRoleList();
}
});
this.permDialogVisible = false
})
}
}
1.用户信息管理
<template>
<div>
<el-form :inline="true" :model="searchForm">
<el-form-item prop="username">
<el-input placeholder="名称" clearable v-model="searchForm.username" prefix-icon="el-icon-search">
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="getUserList">搜索</el-button>
<el-button type="primary" @click="dialogVisible = true">新增</el-button>
<el-popconfirm title="您确定要删除吗?" @confirm="delClick(null)" style="margin-left: 10px;">
<el-button slot="reference" type="danger" :disabled="disabledStatus">批量删除</el-button>
</el-popconfirm>
</el-form-item>
</el-form>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
border
stripe
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column prop="avatar" label="头像" width="60">
<template slot-scope="scope">
<el-avatar size="medium" :src="scope.row.avatar"></el-avatar>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名"/>
<el-table-column prop="userRole" label="用户角色">
<template slot-scope="scope">
<el-tag type="success" size="small" v-for="item in scope.row.userRole">{{item.name}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱"/>
<el-table-column prop="mobile" label="手机号"/>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.status == 1">正常</el-tag>
<el-tag type="info" v-else-if="scope.row.status == 0">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间"></el-table-column>
<el-table-column prop="operate" label="操作">
<template slot-scope="scope">
<el-button type="text" @click="roleClick(scope.row.id)">分配角色</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" @click="resetPwdClick(scope.row.id,scope.row.username)">重置密码</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" @click="updateClick(scope.row.id)">修改</el-button>
<el-divider direction="vertical"></el-divider>
<template>
<el-popconfirm title="您确定要删除吗?" @confirm="delClick(scope.row.id)">
<el-button slot="reference" type="text">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<!-- 新增 -->
<el-dialog title="新增用户信息" :visible.sync="dialogVisible" width="40%">
<el-form :model="userForm" :rules="rules" ref="userForm" label-width="100px">
<el-form-item label="用户名" prop="username">
<el-input v-model="userForm.username"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userForm.email"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="userForm.mobile"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="userForm.status">
<el-radio :label=1>正常</el-radio>
<el-radio :label=0>禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('userForm')">提交</el-button>
<el-button @click="resetForm('userForm')">重置</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 分配角色对话框 -->
<el-dialog title="分配角色" :visible.sync="roleDialogVisible" width="40%">
<el-form :model="roleForm" :rules="rules" ref="userForm" label-width="100px">
<!-- 父子不相互关联 check-strictly=true-->
<el-tree
:data="roleData"
show-checkbox
node-key="id"
ref="roleTree"
:default-expand-all=true
:props="defaultProps">
</el-tree>
</el-form>
<span slot="footer">
<el-button @click="roleDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitroleForm('roleForm')">确 定</el-button>
</span>
</el-dialog>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current=current
:page-sizes="[10, 20, 50, 100]"
:page-size=size
layout="total, sizes, prev, pager, next, jumper"
:total=total>
</el-pagination>
</div>
</template>
<script>
export default {
name: "User",
data() {
return {
circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
imageUrl: '',
searchForm: {
username: ''
},
userForm: {
id: '',
avatar: '',
username: '',
userRole: '',
email: '',
mobile: '',
status: '',
createTime: ''
},
disabledStatus: true,
dialogVisible: false,
tableData: [],
current: 1,
size: 10,
total: 0,
multipleSelection: [],
roleDialogVisible: false,
roleForm: {},
roleData: [],
defaultProps: {
children: 'children',
label: 'nameCn'
},
checkList: [],
rules: {
username: [
{required: true, message: '用户名不能为空!', trigger: 'blur'}
],
email: [
{required: true, message: '邮箱不能为空!', trigger: 'blur'}
],
mobile: [
{required: true, message: '手机号不能为空!', trigger: 'blur'}
],
status: [
{required: true, message: '状态不能为空!', trigger: 'blur'}
]
}
}
},
created() {
this.getUserList();
this.$axios.get("/sys/role/list").then(res => {
this.roleData = res.data.data.record
})
},
methods: {
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
handleSelectionChange(val) {
this.multipleSelection = val;
this.disabledStatus = val.length == 0
},
getUserList() {
this.$axios.get("/sys/user/list", {
params: {
username: this.userForm.username,
current: this.current,
size: this.size
}
}).then(res => {
this.tableData = res.data.data.record
this.current = res.data.data.current
this.size = res.data.data.size
this.total = res.data.data.total
})
},
// 提交操作
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post("/sys/user/" + (this.userForm.id ? "update" : "add")).then(res => {
this.resetForm(formName);
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getUserList();
}
});
this.dialogVisible = false
})
} else {
console.log('error submit!!');
return false;
}
});
},
// 重置操作
resetForm(formName) {
this.$refs[formName].resetFields();
this.userForm = {}
this.dialogVisible = false
},
// 修改操作
updateClick(id) {
this.$axios.get("/sys/user/byId" + id).then(res => {
this.userForm = res.data.data
this.dialogVisible = true
})
},
// 删除操作
delClick(id) {
let ids = []
if (id) {
ids.push(id)
} else {
this.multipleSelection.forEach(item => {
ids.push(item.id)
})
}
console.log("***ids: ", ids)
this.$axios.post("/sys/user/delete", ids).then(res => {
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getUserList();
}
});
})
},
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.size = val
this.getUserList();
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.current = val
this.getUserList();
},
// 分配角色
roleClick(id) {
this.roleDialogVisible = true
this.$axios.get("/sys/role/info" + id).then(res => {
this.$refs.roleTree.setCheckedKeys(res.data.data.menuIds);
this.roleForm = res.data.data
})
},
// 分配角色提交操作
submitroleForm(formName) {
const menuIds = this.$refs.roleTree.getCheckedKeys()
console.log("*****", menuIds)
this.$axios.post("/sys/user/addRole" + this.roleForm.id, menuIds).then(res => {
this.$message({
message: '恭喜你,操作成功!',
type: 'success',
onClose: () => {
this.getUserList();
}
});
this.roleDialogVisible = false
})
},
resetPwdClick(id, username) {
this.$confirm('将重置用户【' + username + '】的密码,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$axios.post("/sys/user/resetPwd", id).then(res => {
this.$message({
type: 'success',
message: '恭喜你,操作成功!',
onClose: () => {
this.getUserList();
}
});
});
}).catch(() => {
this.$message({
type: 'info',
message: '操作已取消!'
});
});
}
}
}
</script>
<style scoped>
.el-pagination {
float: right;
padding-top: 20px;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
2.mock.js,模拟后端返回数据
// 获取用户列表
Mock.mock(RegExp('/sys/user/list'), 'get', () => {
Result.data = {
"record": [
{
id: 1,
avatar: '',
username: '张三',
userRole: '1',
email: '[email protected]',
mobile: '18909129012',
status: '1',
createTime: '2022-12-09 10:12:58'
}, {
id: 2,
avatar: '',
username: '李四',
userRole: '2',
email: '[email protected]',
mobile: '19090129012',
status: '1',
createTime: '2022-12-09 10:12:58'
}
],
current: 1,
size: 10,
total: 2
}
return Result;
})
// 新增,更新,删除,使用正则表达式
Mock.mock(RegExp('/sys/user/*'), 'post', () => {
return Result;
})
// 通过id获取某条用户信息
Mock.mock(RegExp('/sys/user/byId*'), 'get', () => {
Result.data = {
id: 2,
avatar: '',
username: '李四',
userRole: '2',
email: '[email protected]',
mobile: '19090129012',
status: '1',
createTime: '2022-12-09 10:12:58',
}
return Result;
})
3.按钮权限的控制
1】定义全局方法
import Vue from 'vue'
Vue.mixin({
methods: {
hasRoute(perm) {
const authority = this.$store.state.menus.permList
return authority.indexOf(perm) > -1
}
}
})
import global from "./globalFun"
全局方法hasRoute调用,返回true,按钮显示,否则不显示
<el-button type="primary" @click="dialogVisible = true" v-if="hasRoute('sys:user:save')">新增</el-button>