云办公
一、项目简介
本项目为在线办公系统,主要用于管理日常的办公事务。它包含了:日常的各种流程审批、新闻、通知、公告、文件信息、财务、人事、费用、资产、行政、项目、移动办公等;
二、环境搭建
-
安装Node
-
安装@vue/cli
npm install @vue/cli
- 查看版本
- 创建项目
vue create cloudoffice
- 启动项目
npm run serve
- 代码管理
git init
touch README.md
git add .
git commit -m '初始化项目'
git remote add origin git@gitee.com:harrylee13/cloud-office.git
git push -u origin "master"
三、实现登录
1. 引入ElementUI库
# 方式一:npm安装
npm i element-ui -S
# 方式二:CDN引入
# 引入样式
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
# 引入组件库
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
2. 完整引入
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
3.编写登录页面
<template>
<div>
<el-form :rules="rules" ref="loginForm" :model="loginForm" class="loginContainer">
<h3 class="loginTitle">登录云办公</h3>
<el-form-item prop="username">
<el-input type="text" v-model="loginForm.username" placeholder="请输入用户名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="loginForm.password" placeholder="请输入密码" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="code">
<el-input type="text" v-model="loginForm.code" placeholder="点击图片更换验证码" auto-complete="off"
style="width: 250px;margin-right: 5px"></el-input>
<img :src="captchaUrl" @click="updateCaptcha">
</el-form-item>
<el-checkbox v-model="checked" class="loginRememberMe">记住我</el-checkbox>
<el-button type="primary" style="width: 100%;" @click="submitLogin">登录</el-button>
</el-form>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
loginForm: {
username: 'admin',
password: '123',
code: ''
},
checked: true,
rules: {
username: [{required: true, message:'请输入用户名', trigger: 'blur'}],
password: [{required: true, message:'请输入密码', trigger: 'blur'}],
code: [{required: true, message:'请输入验证码', trigger: 'blur'}]
},
captchaUrl: '/captcha/getCaptcha?time=' + new Date()
}
},
methods: {
updateCaptcha(){
this.captchaUrl = '/captcha/getCaptcha?time=' + new Date()
},
submitLogin(){
this.$refs.loginForm.validate((valid) => {
if (valid) {
alert('submit!');
} else {
this.$message.error('请输入所有字段!');
return false;
}
});
}
}
}
</script>
<style>
.loginContainer{
border-radius: 15px;
background-clip: padding-box;
margin: 300px auto;
width: 350px;
padding: 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.loginTitle{
margin: 0px auto 40px auto;
text-align: center;
}
.loginRememberMe{
text-align: left;
margin: 0px 0px 20px 0px;
}
.el-form-item__content{
display: flex;
align-items: center;
}
</style>
4. 配置登录请求
① 安装axios
npm i axios
② 封装api.js
// 封装axios
import axios from 'axios'
import { Message } from 'element-ui'
import router from '../router'
axios.interceptors.response.use(success=>{
if(success.status && success.status == 200){
if(success.data.code==500 || success.data.code==401 || success.data.code==403){
// 业务逻辑错误
//500:服务器内部错误,无法完成请求
//401:请求要求用户的身份认证
//403:服务器理解请求客户端的请求,但是拒绝执行此请求
Message.error({message: success.data.message})
return;
}
if(success.data.message){
Message.success({message: success.data.message})
}
}
return success.data;
}, error=>{
if(error.response.code==504 || error.response.code==404){
Message.error({message: '服务器被吃了o(╥﹏╥)o'})
}else if(error.response.code==403){
Message.error({message: '权限不足,请联系管理员'})
}else if(error.response.code==401){
Message.error({message: '尚未登录,请登录'})
router.replace('/') //跳转登录页面
}else{
if(error.response.data.message){
Message.error({message: error.response.data.message})
}else{
Message.error({message: '未知错误'})
}
}
return;
})
③ 配置POST请求
// api.js
...
let baseUrl = '';
// 传送json格式的post请求
export const postRequest = (url, params)=>{
return axios({
method: 'POST',
url: `${baseUrl}${url}`,
data: params
})
}
④ 配置请求转发解决跨域
- 上边的POST会存在请求跨域问题,所以需要通过Vue的代理,将8080端口的请求转发到8081端口!
// vue.config.js
...
// 请求经过nodejs时候,会通过这个代理对象,转发到8081端口
let proxyObj = {}
proxyObj['/'] = { // 所有要代理的路径是/
// websocket
ws: false,
// 代理到哪里去,目标地址
target: 'http://localhost:8081',
// 表示发生请求头host会被设置为target
changeOrigin: true,
// 假如后端有前端路径,这里会不重写请求路径
pathReWrite: {
'^/': '/'
}
}
module.exports = defineConfig({
...
// 转发到8081端口
devServer: {
host: 'localhost',
port: 8080,
proxy: proxyObj
}
})
- 登录点击跳转
- 通过监听登录按钮的点击事件,发送api.js文件中的postRequest请求
- 请求后接收响应体中的token,将它存储在sessionStorage中
- 设置请求拦截器,判断sessionStorage中是否存在token,有则携带这个token再发送请求
// Login.vue
...
submitLogin(){
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
this.postRequest('/login/doLogin', this.loginForm).then(resp => {
// alert(JSON.stringify(resp));
if(resp){
// 存储token
const tokenStr = resp.obj.tokenHead+resp.obj.token;
window.sessionStorage.setItem('tokenStr', tokenStr);
// 跳转首页
this.$router.replace('/home')
}
// 重置数据
this.loading = false;
this.updateCaptcha();
this.loginForm.code = ''
})
} else {
this.$message.error('请输入所有字段!');
return false;
}
});
}
// api.js
...
// 请求拦截器
axios.interceptors.request.use(config=>{
// 如果存在token,请求携带这个token
if(window.sessionStorage.getItem('tokenStr')){
config.headers['Authorization'] = window.sessionStorage.getItem('tokenStr');
}
return config;
}, error=>{
console.log(error)
})
...
- 全局引入api
- 如果每个组件都去引入api,显得有点麻烦,这里直接在main.js中全局引入
- 以后发送请求时,使用this.postRequest形式即可
...
// 引入接口
import { postRequest } from './utils/api';
import { putRequest } from './utils/api';
import { getRequest } from './utils/api';
import { deleteRequest } from './utils/api';
...
// 添加到原型中
Vue.prototype.postRequest = postRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.deleteRequest = deleteRequest;
...
四、导航开发
1. 导航菜单
① 使用ElementUI模板
<!-- Home.vue -->
<template>
<div>
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">
<el-menu>
<el-submenu index="1">
<template slot="title"><i class="el-icon-location"></i>导航一</template>
<el-menu-item index="/test1">选项一</el-menu-item>
<el-menu-item index="/test2">选项二</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>main</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "Home",
};
</script>
<style scoped>
</style>
② 开启router模式
<el-menu router>
...
</el-menu>
<el-main>
<!-- 这个是对应子路由的,因为它的外层APP.js也有<router-view/> -->
<router-view/>
</el-main>
③ 编写路由
// index.js
...
const routes = [
...
{
path: '/home',
name: '导航一',
component: Home,
children: [
{
path: '/test1',
name: '选项一',
component: Test1
},
{
path: '/test2',
name: '选项二',
component: Test2
}
]
}
]
...
④ 修改模板
- 通过v-for遍历路由,实现导航栏与路由一一对应
- 因为/login这个路由不需要展示,通过给它添加hidden属性实现隐藏
<!-- Home.vue -->
<template>
<div>
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">
<el-menu router>
<el-submenu index="1" v-for="(item, index) in this.$router.options.routes"
:key="index" v-show="!item.hidden">
<template slot="title">
<i class="el-icon-location"></i>{{item.name}}
</template>
<el-menu-item :index="children.path"
v-for="(children, indexj) in item.children"
:key="indexj">{{children.name}}
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>
...
⑤ 效果展示
2. 动态导航栏
因为直接写死路由的话,将来后端的菜单数据改变时,就需要重新更改路由,工作量将不可估量;这里通过Vuex将后端返回的JSON数据动态的添加到路由中,之所以存在Vuex中是因为其中可能涉及隐私,存在Vuex就相当于存入这个网页应用的内存里;
① 安装Vuex
npm install vuex@3.6.2 --save
② 编写index.js
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
routes: []
},
mutations: {
initRoutes(state, payload){
state.routes = payload;
}
},
actions: {}
})
③ 全局引入store
// main.js
...
import store from './store';
...
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
④ 封装菜单请求类
思路分析:因为后端返回的是字符串,通过将它们格式化为一个“路由数组”,并存入vuex中实现状态管理
// utils/menu.js
import { getRequest } from "./api";
export default initMenu = (router, store)=>{
// 判断store.routes是否有值
if(store.state.routes.length > 0){
return;
}
// 发送请求并处理
getRequest('/system/cfg/menu').then(data=>{
if(data){
// 格式化路由
let fmtRoutes = formatRoutes(data.obj);
// 添加路由
router.addRoutes(fmtRoutes);
// 将数据存入vuex
store.commit('initRoutes', fmtRoutes);
}
})
}
// 格式化路由的函数
export const formatRoutes = (routes)=>{
let fmtRoutes = [];
// 将routes转换为真正的数组
routes = Array.from(routes);
routes.forEach(router=>{
let {
path,
component,
name,
iconCls,
children
} = router;
if(children && children instanceof Array){
children = formatRoutes(children)
}
// 格式化好后的对象
let fmRouter = {
path: path,
name: name,
iconCls: iconCls,
children: children,
// 路由组件懒加载ES5
component(resolve){
// 判断开头并修改路径
if(component.startsWith('Home')){
require(['../views/'+component+'.vue'], resolve)
}else if(component.startsWith('Emp')){
require(['../views/emp/'+component+'.vue'], resolve)
}else if(component.startsWith('Per')){
require(['../views/per/'+component+'.vue'], resolve)
}else if(component.startsWith('Sal')){
require(['../views/sal'+component+'.vue'], resolve)
}else if(component.startsWith('Sta')){
require(['../views/sta'+component+'.vue'], resolve)
}else if(component.startsWith('Sys')){
require(['../views/sys'+component+'.vue'], resolve)
}
}
// ES6
// component: () => import ('../views/'+component+'.vue')
}
// 存入数组并返回
fmtRoutes.push(fmRouter)
});
return fmtRoutes;
}
⑤ 路由导航守卫
封装好菜单请求类后,这时候就需要思考什么时候去调用这个初始化方法?登录时?不,如果在登录时初始化,那么页面一刷新,state里的routes就会被清空,那么导航栏就会被清空!这里通过路由导航守卫实现。
// main.js
// 全局前置导航守卫
router.beforeEach((to, from, next) => {
// 判断用户是否登录
if(window.sessionStorage.getItem('tokenStr')){
// 初始化导航栏
initMenu(router, store);
next();
}else if(to.path == '/'){
next()
}
})
⑥ 修改图标
- 安装font-awesome
npm install font-awesome
- 导入CSS样式
// main.js
import 'font-awesome/css/font-awesome.css'
- 修改模板
<i :class="item.iconCls"></i>
⑦ 修改模板
因为需要动态读取store.state中的routes,所以不再遍历router.routes,而是通过计算属性的方式,读取store.state中的routes;需要注意的是,el-submenu中的index属性需要的是字符串类型
<template>
<div>
<el-container>
<el-container>
...
<el-aside width="200px">
<el-menu router unique-opened>
<el-submenu :index="index+''" v-for="(item, index) in routes" :key="index" v-show="!item.hidden">
<template slot="title"><i class="el-icon-location"></i>{{item.name}}</template>
<el-menu-item :index="children.path" v-for="(children, indexj) in item.children" :key="indexj">{{children.name}}</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
...
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "Home",
computed: {
routes(){
return this.$store.state.routes
}
}
};
</script>
...
⑧ 创建组件
⑨ 效果展示
五、用户信息开发
1. 首页用户信息
① 获取用户信息
// main.js
// 全局前置导航守卫
router.beforeEach((to, from, next) => {
// 判断用户是否登录
if(window.sessionStorage.getItem('tokenStr')){
// 初始化导航栏
initMenu(router, store);
// 获取用户信息
if(!window.sessionStorage.getItem('user')){
// 判断用户信息是否存在
return getRequest('/login/getUserInfo').then(resp=>{
if(resp){
window.sessionStorage.setItem('user', JSON.stringify(resp.obj))
next()
}
})
}
next();
}else{
next()
}
})
② 修改模板
<template>
<div>
<el-container>
<el-header class="homeHeader">
<div class="title">
<i class="el-icon-cloudy"></i>
云办工
</div>
<el-dropdown class="userInfo">
<span class="el-dropdown-link">
{{user.name}}<img :src="user.userFace">
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>设置</el-dropdown-item>
<el-dropdown-item>注销登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
...
</div>
</template>
<script>
export default {
name: "Home",
data(){
return {
user: JSON.parse(window.sessionStorage.getItem('user'))
}
},
computed: {
routes(){
return this.$store.state.routes
}
}
};
</script>
<style scoped>
.homeHeader{
background: #409eff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
box-sizing: border-box;
}
.homeHeader .title{
font-size: 30px;
font-family: 楷体;
color: #fff;
font-weight: 500;
}
.userInfo{
cursor: pointer;
color: #fff;
text-align: center;
}
.el-dropdown-link img{
background: #fff;
width: 24px;
height: 24px;
border-radius: 24px;
margin-left: 10px;
}
</style>
④ 效果展示
2. 注销登录
① 监听点击事件
因为使用了ElementUI的下拉框,这个下拉框可以通过@command="方法"去监听组件中的item项的command属性,以此来监听点击事件对象并做相应处理;在此期间,添加了确认弹出框;退出后,需要清空sessionStorage中的tokenStr和user、vuex中的state.routes
<template>
<div>
<el-container>
<el-header class="homeHeader">
...
<el-dropdown class="userInfo" @command="handleCommand">
<span class="el-dropdown-link">
{{user.name}}
<img :src="user.userFace">
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="userinfo">个人中心</el-dropdown-item>
<el-dropdown-item command="setting">设置</el-dropdown-item>
<el-dropdown-item command="logout">注销登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
...
</div>
</template>
<script>
export default {
...
methods: {
handleCommand(command){
if(command == 'logout'){
// 确认框
this.$confirm('此操作将注销登录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 发送退出登录请求
this.postRequest('/login/dologout');
// 清空用户信息
window.sessionStorage.removeItem('tokenStr');
window.sessionStorage.removeItem('user');
// 清空vuex中的菜单
this.$store.commit('initRoutes',[]);
// 跳转登陆页
this.$router.replace('/');
}).catch(() => {
this.$message({
type: 'info',
message: '已取消操作'
});
});
}
}
}
};
</script>
<style scoped>
...
</style>
六、首页补充
1. 面包屑
...
<el-main>
<el-breadcrumb v-if="this.$route.path!='/home'">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{this.$route.name}}</el-breadcrumb-item>
</el-breadcrumb>
<div class="homeWelcome" v-else>
欢迎来到云办工系统
</div>
<router-view></router-view>
</el-main>
...
2. 直接输入页面跳转
情况解析:当用户直接访问登录后的页面,但是未登录,通过下面的处理,实现登录后直接跳转到用户所输入的页面;
// main.js
...
// 全局前置导航守卫
router.beforeEach((to, from, next) => {
// 判断用户是否登录
if(window.sessionStorage.getItem('tokenStr')){
// 初始化导航栏
initMenu(router, store);
if(!window.sessionStorage.getItem('user')){
// 判断用户信息是否存在
return getRequest('/login/getUserInfo').then(resp=>{
if(resp){
window.sessionStorage.setItem('user', JSON.stringify(resp.obj))
next()
}
})
}
next();
}else{
// 判断是否属于直接输入网址
// - 是,则将网址保存为参数,等登录成功后直接跳转到相应页面
// - 否,则常规跳转
if(to.path == '/'){
next()
}else{
next('/?redirect='+to.path)
}
}
})
...
// Login.vue/methods
submitLogin(){
this.$refs.loginForm.validate((valid) => {
if (valid) {
...
this.postRequest('/login/doLogin', this.loginForm).then(resp => {
if(resp){
// 存储token
...
// 跳转首页
// this.$router.replace('/home')
// 直接输入页面地址,登录后跳转到相应页面
let path = this.$route.query.redirect;
this.$router.replace( (path=='/' || path == undefined) ? '/home' : path )
}
// 重置数据
...
})
} else {
...
}
});
}
...
七、基础信息设置
1. 标签页
① 创建组件
② 使用ElementUI的Tags
<!-- SysBasic.vue -->
<template>
<div>
<el-tabs v-model="activeName" type="card">
<el-tab-pane label="部门管理" name="DepMana">
<DepMana></DepMana>
</el-tab-pane>
<el-tab-pane label="职位管理" name="PosMana">
<PosMana></PosMana>
</el-tab-pane>
<el-tab-pane label="职称管理" name="JoblevelMana">
<JoblevelMana></JoblevelMana>
</el-tab-pane>
<el-tab-pane label="奖惩规则" name="EcMana">
<EcMana></EcMana>
</el-tab-pane>
<el-tab-pane label="权限组" name="PermissMana">
<PermissMana></PermissMana>
</el-tab-pane>
</el-tabs>
</div>
</template>
③ 引入组件
<!-- SysBasic.vue -->
<script>
import DepMana from '@/components/sys/basic/DepMana.vue'
import PosMana from '@/components/sys/basic/PosMana.vue'
import JoblevelMana from '@/components/sys/basic/JoblevelMana.vue'
import EcMana from '@/components/sys/basic/EcMana.vue'
import PermissMana from '@/components/sys/basic/PermissMana.vue'
export default {
name: 'SysBasic',
data(){
return {
activeName: 'DepMana'
}
},
components: {
DepMana,
PosMana,
JoblevelMana,
EcMana,
PermissMana
}
}
</script>
<style scoped>
</style>
④ 效果展示
2. 职位管理
① 页面设计
<template>
<div>
<div>
<el-input
class="addPosInput"
size="small"
placeholder="添加职位..."
suffix-icon="el-icon-plus"
v-model="pos.name">
</el-input>
<el-button type="primary" size="small">添加</el-button>
</div>
<div class="posManaMain">
<el-table :data="positions" stripe border style="width: 70%">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column
prop="id"
label="编号"
width="55">
</el-table-column>
<el-table-column
prop="name"
label="职位"
width="120">
</el-table-column>
<el-table-column
prop="createDate"
label="创建时间"
width="200">
</el-table-column>
<el-table-column
label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
name: 'PosMana',
data(){
return {
pos: {
name: ''
},
positions:[]
}
},
methods: {
handleEdit(index, data){},
handleDelete(index, data){}
}
}
</script>
<style scoped>
.addPosInput{
width: 300px;
margin-right: 10px;
}
.posManaMain{
margin-top: 10px;
}
</style>
② 接口调用
...
<script>
export default {
name: 'PosMana',
data(){
return {
...
positions:[]
}
},
methods: {
initPositions(){
this.getRequest('/system/basic/pos/getAllPositions').then(resp=>{
if(resp){
// console.log(resp);
this.positions = resp.obj;
}
})
},
...
},
mounted(){
this.initPositions()
}
}
</script>
...
③ 添加职位
<template>
<div>
<div>
<el-input
...
@keydown.enter.native="addPosition"
v-model="pos.name">
</el-input>
<el-button type="primary" size="small" @click="addPosition">添加</el-button>
</div>
...
</div>
</template>
<script>
export default {
name: 'PosMana',
data(){
return {
pos: {
name: ''
},
...
}
},
methods: {
addPosition(){
if(this.pos.name){
this.postRequest('/system/basic/pos/', this.pos).then(resp=>{
if(resp){
// 刷新列表
this.initPositions()
// 输入框置空
this.pos.name = ''
}
})
}else{
this.$message.error('职位名称不能为空')
}
},
...
},
...
}
</script>
④ 删除职位
<template>
<div>
...
<div class="posManaMain">
<el-table :data="positions" stripe border style="width: 70%">
...
<el-table-column
label="操作">
<template slot-scope="scope">
...
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
...
methods: {
...
handleDelete(index, data){
this.$confirm('此操作将永久删除[ '+data.name+' ]职位,是否继续', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/basic/pos/'+data.id).then(resp=>{
if(resp){
// 刷新列表
this.initPositions()
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
...
}
</script>
⑤ 编辑职位
<template>
<div>
...
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="25%">
<el-tag>职位名称</el-tag>
<el-input size="small" class="updatePosInput" v-model="updatePos.name"></el-input>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogVisible = false">取 消</el-button>
<el-button size="small" type="primary" @click="doUpdatePos">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'PosMana',
data(){
return {
...
dialogVisible: false,
updatePos: { //编辑职位的变量
name: ''
}
}
},
methods: {
...
// 编辑职位
handleEdit(index, data){
Object.assign(this.updatePos, data); //直接赋值会造成未确定修改,列表中的值就改变
this.updatePos.createDate = ''; //后端不允许修改创建时间
this.dialogVisible = true;
},
doUpdatePos(){
this.putRequest('/system/basic/pos/',this.updatePos).then(resp=>{
if(resp){
this.initPositions();
this.dialogVisible = false;
}
})
},
...
},
...
}
</script>
<style scoped>
...
.updatePosInput{
width: 200px;
margin-left: 8px;
}
</style>
⑥ 批量删除
思路分析:通过给el-table绑定监听checkbox修改的事件@selection-change="handleSelectionChange",修改数组multipleSelection;通过判断这个数组的长度决定是否启用批量删除按钮,通过给按钮添加点击事件,发送删除请求进行批量删除。
<template>
<div>
...
<div class="posManaMain">
<el-table :data="positions" stripe border style="width: 70%"
@selection-change="handleSelectionChange">
...
</el-table>
</div>
<el-button size="small" style="margin-top: 8px;" type="danger"
:disabled="this.multipleSelection.length == 0"
@click="deleteMany">批量删除
</el-button>
...
</div>
</template>
<script>
export default {
name: 'PosMana',
data(){
return {
...
multipleSelection: [] //批量删除数组
}
},
methods: {
...
// 批量删除
handleSelectionChange(val){
this.multipleSelection = val;
},
deleteMany(){
this.$confirm('此操作将永久删除[ '+this.multipleSelection.length+' ]条职位,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let ids = '?'
this.multipleSelection.forEach(item=>{
ids += 'ids='+item.id+'&';
})
this.deleteRequest('/system/basic/pos/'+ids).then(resp=>{
if(resp){
// 刷新列表
this.initPositions()
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
...
}
</script>
...
3. 职称管理
① 页面设计
<template>
<div>
<div>
<el-input class="addPosInput" size="small" placeholder="添加职称..."
prefix-icon="el-icon-plus" @keydown.enter.native="addJobLevel"
v-model="jl.name" style="width: 300px;">
</el-input>
<el-select size="small" v-model="jl.titleLevel" placeholder="职称等级"
style="margin-left: 8px;margin-right: 8px;">
<el-option v-for="item in titleLevels" :key="item"
:label="item" :value="item"
></el-option>
</el-select>
<el-button type="primary" size="small" @click="addJobLevel">添加</el-button>
</div>
<div style="margin-top: 10px">
<el-table :data="jls" stripe border style="width: 70%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column
prop="id"
label="编号"
width="55">
</el-table-column>
<el-table-column
prop="name"
label="职称名称"
width="150">
</el-table-column>
<el-table-column
prop="titleLevel"
label="职称等级"
width="150">
</el-table-column>
<el-table-column
prop="createDate"
label="创建时间"
width="150">
</el-table-column>
<el-table-column
prop="enabled"
label="是否启用"
width="150">
</el-table-column>
<el-table-column
label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
name: 'JoblevelMana',
data() {
return {
jl: {
name: '',
titleLevel: ''
},
titleLevels: ['正高级', '副高级', '中级', '初级', '员级'],
jls: []
}
},
...
}
</script>
...
② 接口调用
...
<script>
export default {
...
methods: {
// 初始化列表数据
initJls(){
this.getRequest('/system/basic/joblevel/getAllJoblevels').then(resp=>{
if(resp){
this.jls = resp.obj;
// 数据置空
this.jl.name = '';
this.jl.titleLevel = '';
}
})
},
...
},
mounted(){
// 模板挂载后再初始化列表
this.initJls();
}
}
</script>
...
③ 添加职称
...
<script>
export default {
...
data() {
return {
jl: {
name: '',
titleLevel: ''
},
...
}
},
methods: {
...
,
...
},
...
}
</script>
...
④ 删除职称
<template>
<div>
...
<div style="margin-top: 10px">
<el-table :data="jls" stripe border style="width: 75%" @selection-change="handleSelectionChange">
...
<el-table-column label="操作">
<template slot-scope="scope">
...
<el-button size="mini" type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
...
methods: {
...
handleDelete(index, data){
this.$confirm('此操作将永久删除[ '+data.name+' ]职称,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/basic/joblevel/'+data.id).then(resp=>{
if(resp){
// 刷新列表
this.initJls()
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
...
},
...
}
</script>
...
⑤ 修改职称
<template>
<div>
...
<div style="margin-top: 10px">
<el-table :data="jls" stripe border style="width: 75%" @selection-change="handleSelectionChange">
...
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
...
</template>
</el-table-column>
</el-table>
</div>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="25%">
<table>
<tr>
<td><el-tag>职称名称</el-tag></td>
<td><el-input size="small" v-model="updateJl.name"
style="margin-left: 8px;">
</el-input>
</td>
</tr>
<tr>
<td><el-tag >职称等级</el-tag></td>
<td>
<el-select size="small" v-model="updateJl.titleLevel"
style="margin-left: 8px;">
<el-option v-for="item in titleLevels" :key="item"
:label="item" :value="item">
</el-option>
</el-select>
</td>
</tr>
<tr>
<td><el-tag>是否启用</el-tag></td>
<td>
<el-switch
v-model="updateJl.enabled" active-color='#13ce66'
inactive-color="#ff4949" active-text="已启用"
inactive-text="未启用" style="margin-left: 6px">
</el-switch>
</td>
</tr>
</table>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogVisible = false">取 消</el-button>
<el-button size="small" type="primary" @click="doUpdate">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'JoblevelMana',
data() {
return {
...
dialogVisible: false,
updateJl: {
name: '',
titleLevel: '',
enabled: ''
}
}
},
methods: {
...
handleEdit(index, data){
Object.assign(this.updateJl, data);
this.updateJl.createDate = ''; //后端不允许修改创建时间
this.dialogVisible = true;
},
doUpdate(){
this.putRequest('/system/basic/joblevel/', this.updateJl).then(resp=>{
if(resp){
this.initJls();
this.dialogVisible = false;
}
})
}
},
...
}
</script>
...
⑥ 批量删除
<template>
<div>
...
<div style="margin-top: 10px">
<el-table :data="jls" stripe border style="width: 75%" @selection-change="handleSelectionChange">
...
</el-table>
</div>
<el-button size="small" style="margin-top: 8px;" type="danger"
:disabled="this.multipleSelection.length == 0"
@click="deleteMany">批量删除
</el-button>
...
</div>
</template>
<script>
export default {
name: 'JoblevelMana',
data() {
return {
...
multipleSelection: []
}
},
methods: {
...
handleSelectionChange(val){
this.multipleSelection = val;
},
deleteMany(){
this.$confirm('此操作将永久删除[ '+this.multipleSelection.length+' ]条职称,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let ids = '?'
this.multipleSelection.forEach(item=>{
ids += 'ids='+item.id+'&';
})
this.deleteRequest('/system/basic/joblevel/'+ids).then(resp=>{
if(resp){
// 刷新列表
this.initJls()
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
...
}
</script>
...
4. 权限组管理
① 页面设计
<template>
<div>
<div class="permissManaTool">
<el-input size="small" placeholder="请输入角色英文名" v-model="role.name">
<template slot="prepend">ROLE_</template>
</el-input>
<el-input size="small" placeholder="请输入角色中文名" v-model="role.nameZh"></el-input>
<el-button size="small" type="primary" icon="el-icon-plus">添加角色</el-button>
</div>
<div class="permissManaMain">
<el-collapse v-model="activeName" accordion>
<el-collapse-item title="一致性 Consistency" name="1">
<div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div>
<div>在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</div>
</el-collapse-item>
<el-collapse-item title="反馈 Feedback" name="2">
<div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div>
<div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div>
</el-collapse-item>
<el-collapse-item title="效率 Efficiency" name="3">
<div>简化流程:设计简洁直观的操作流程;</div>
<div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div>
<div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div>
</el-collapse-item>
<el-collapse-item title="可控 Controllability" name="4">
<div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div>
<div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
name: 'PermissMana',
data(){
return {
role: {
name: '',
nameZh: ''
},
activeName: '1'
}
}
}
</script>
<style scoped>
.permissManaTool{
display: flex;
justify-content: flex-start;
}
.permissManaTool .el-input{
width: 300px;
margin-right: 8px;
}
.permissManaMain{
margin-top: 10px;
width: 700px;
}
</style>
② 权限树
<template>
<div>
...
<div class="permissManaMain">
<el-collapse accordion @change="change">
<el-collapse-item :title="r.remark" :name="r.id" v-for="(r, index) in roles" :key="index">
<el-card class="box-card">
...
<div>
<el-tree :data="allMenus" :props="defaultProps" show-checkbox></el-tree>
</div>
</el-card>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
name: 'PermissMana',
data(){
return {
...
roles: [], //所有角色
allMenus: [], //权限树
defaultProps: { //决定子树、展示名称
children: 'children',
label: 'name'
}
}
},
methods: {
initRoles(){
this.getRequest('/system/basic/permiss/getAllRoles').then(resp=>{
if(resp){
this.roles = resp.obj;
}
})
},
initAllMenu(){
this.getRequest('/system/basic/permiss/menus').then(resp=>{
if(resp){
this.allMenus = resp.obj
}
})
},
change(rid){ //监听是否打开折叠卡
if(rid){
this.initAllMenu()
}
}
},
...
}
</script>
...
③ 根据用户展示
思路分析:根据点击的用户的id去后端查询权限,将返回的数组赋值给selectedMenus,通过将它动态el-tree的default-checked-keys属性,实现根据用户id展示权限树。
<template>
...
<div class="permissManaMain">
<el-collapse accordion @change="change" v-model="acticvName">
<el-collapse-item :title="r.remark" :name="r.id" v-for="(r, index) in roles" :key="index">
<el-card class="box-card">
...
<div>
<el-tree :data="allMenus" :props="defaultProps" show-checkbox
node-key="id" :default-checked-keys="selectedMenus"
ref="tree">
</el-tree>
</div>
</el-card>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
name: 'PermissMana',
data(){
return {
...
selectedMenus: []
}
},
methods: {
...
// 根据用户id查询角色权限
initSelectedMenus(rid){
this.getRequest('/system/basic/permiss/mid/'+rid).then(resp=>{
if(resp){
this.selectedMenus = resp.obj;
}
})
}
},
...
}
</script>
...
④ 修改权限
思路分析:通过监听折叠选项卡的change事件,传递点击的选项卡索引,当点击确认修改时,发送修改请求到后端,后端响应后刷新菜单并关闭选项卡。
<template>
<div>
...
<div class="permissManaMain">
<el-collapse accordion @change="change" v-model="acticvName">
<el-collapse-item :title="r.remark" :name="r.id" v-for="(r, index) in roles" :key="index">
<el-card class="box-card">
...
<div>
<el-tree :data="allMenus" :props="defaultProps" show-checkbox
node-key="id" :default-checked-keys="selectedMenus"
ref="tree">
</el-tree>
<div style="display: flex; justify-content: flex-end;">
<el-button size="mini" @click="cancelUpdate">取消修改</el-button>
<el-button size="mini" type="primary" @click="doUpdate(r.id, index)">确认修改</el-button>
</div>
</div>
</el-card>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
name: 'PermissMana',
data(){
return {
...
acticvName: -1 //修改后关闭的变量
}
},
methods: {
...
change(rid){ //监听是否打开折叠卡
if(rid){
// console.log(rid);
this.initAllMenu();
this.initSelectedMenus(rid);
}
},
// 修改权限
doUpdate(rid, index){
let tree = this.$refs.tree[index];
let selectedKeys = tree.getCheckedKeys(true); //获取选中的节点
console.log(selectedKeys);
// 注意这里后端参数为rId、menuIds
let url = '/system/basic/permiss/updateMenuRoles/?rId='+rid;
selectedKeys.forEach(key=>{
url += '&menuIds='+key;
})
this.putRequest(url).then(resp=>{
if(resp){
this.initRoles();
this.acticvName = -1;
}
})
},
cancelUpdate(){
this.acticvName = -1;
}
},
...
}
</script>
...
⑤ 添加、删除角色
<template>
<div>
<div class="permissManaTool">
...
<el-button size="small" type="primary" icon="el-icon-plus" @click="doAddRole">添加角色</el-button>
</div>
<div class="permissManaMain">
<el-collapse accordion @change="change" v-model="acticvName">
<el-collapse-item :title="r.remark" :name="r.id"
v-for="(r, index) in roles" :key="index">
<el-card class="box-card">
<div slot="header" class="clearfix">
...
<el-button style="float: right; padding: 3px 0; color: red;" type="text"
icon="el-icon-delete" @click="doDeleteRole(r)">
</el-button>
</div>
...
</el-card>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
name: 'PermissMana',
data(){
return {
role: { //添加角色
name: '',
remark: ''
},
...
}
},
methods: {
...
// 添加角色
doAddRole(){
if(this.role.name && this.role.remark){
this.postRequest('/system/basic/permiss/',this.role).then(resp=>{
if(resp){
this.initRoles();
this.role.name = '';
this.role.remark = '';
}
})
}else{
this.$message.error('所有字段不能为空 ')
}
},
// 删除角色
doDeleteRole(role){
this.$confirm('此操作将永久删除[ '+role.remark+' ]角色,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/basic/permiss/'+role.id).then(resp=>{
if(resp){
// 刷新列表
this.initRoles();
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
...
},
...
}
</script>
...
5. 部门管理
① 基本展示
思路分析:通过监听搜索输入框的变化,触发节点树对节点的筛选。
<template>
<div style="width: 500px;">
<el-input
placeholder="请输入部门名称进行搜索..."
prefix-icon="el-icon-search"
v-model="filterText">
</el-input>
<el-tree
:data="deps"
:props="defaultProps"
:filter-node-method="filterNode"
ref="tree">
</el-tree>
</div>
</template>
<script>
export default {
name: 'DepMana',
data() {
return {
filterText: '', //输入框的值
deps: [], //接口返回的数据,用于展示
defaultProps: { //节点树中子节点的字段配置
children: 'children',
label: 'name'
}
}
},
watch: {
filterText(val) {
// 通过监听输入框的改变,触发节点树的展示
this.$refs.tree.filter(val);
}
},
methods: {
// 初始化部门菜单
initDeps(){
this.getRequest('/system/basic/department/').then(resp=>{
if(resp){
this.deps = resp.obj;
}
})
},
// 用于搜索过滤节点
filterNode(value, data) {
// val->value:搜索值 data->返回的搜索结果
// 函数返回TRUE展示全部数据,否则展示搜索结果
if (!value) return true;
return data.name.indexOf(value) !== -1;
}
},
// 模板渲染完成后初始化菜单
mounted(){
this.initDeps();
}
}
</script>
<style scoped>
</style>
② 操作设计
<template>
<div style="width: 500px;">
...
<el-tree
...
:expand-on-click-node="false"
...>
<span class="custom-tree-node" slot-scope="{ node, data }"
style="display: flex; justify-content: space-between; width: 100%">
<span>{{ node.label }}</span>
<span>
<el-button type="primary" size="mini" class="depBtn"
@click="() => showAddDep(data)">
添加部门
</el-button>
<el-button type="danger" size="mini" class="depBtn"
@click="() => deleteDep(node, data)">
删除部门
</el-button>
</span>
</span>
</el-tree>
</div>
</template>
...
<style scoped>
.depBtn{
padding: 2px;
}
</style>
③ 添加部门
思路分析:为了提高用户体验,这里采用了“伪动态”添加节点到节点树的操作,需要注意的是,当添加子节点时,后端自然会修改isParent,但是前端因为不刷新,所以需要手动修改;
<template>
<div style="width: 500px;">
...
<el-tree ...>
<span ...>
<span>{{ node.label }}</span>
<span>
<el-button type="primary" size="mini" class="depBtn"
@click="() => showAddDep(data)">
添加部门
</el-button>
<el-button type="danger" size="mini" class="depBtn"
@click="() => deleteDep(node, data)">
删除部门
</el-button>
</span>
</span>
</el-tree>
<el-dialog title="添加部门" :visible.sync="dialogVisible" width="30%"
:before-close="handleClose">
<div>
<table>
<tr>
<td><el-tag>上级部门</el-tag></td>
<td>{{preDep}}</td>
</tr>
<tr>
<td><el-tag>部门名称</el-tag></td>
<td><el-input v-model="addDep.name" placeholder="请输入部门名称..."></el-input></td>
</tr>
</table>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="cancelAddDep">取 消</el-button>
<el-button type="primary" @click="doAddDep">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'DepMana',
data() {
return {
...
deps: [], //接口返回的数据,用于展示
dialogVisible: false, //弹出框是否可见
addDep: { //添加的部门对象
name: '',
parentId: ''
},
preDep: '' //添加时的上级部门名称
}
},
...
methods: {
...
// 添加部门
doAddDep(){
this.postRequest('/system/basic/department/', this.addDep).then(resp=>{
if(resp){
// 因为刷新整个页面会关闭部门树,所以这里手动添加到界面
this.addDep2Deps(this.deps, resp.obj);
this.dialogVisible = false;
this.initAddDep()
}
})
},
// 初始化添加部门输入框
initAddDep(){
this.addDep = {
name: '',
parentId: -1
}
this.preDep = ''
},
// 手动添加新部门进部门树
addDep2Deps(deps, dep){
for(let i = 0; i < deps.length; i++){
let d = deps[i];
if(d.id == dep.parentId){
d.children = d.children.concat(dep);
// 修改isParent
if(d.children.length > 0){
d.isParent = true;
}
return;
}else{
this.addDep2Deps(d.children, dep);
}
}
}
},
cancelAddDep(){
this.dialogVisible = false;
this.initAddDep();
},
...
}
</script>
...
④ 删除部门
思路分析:这里也采用了“伪动态”的删除方式,也需要注意isParent的修改。
<template>
<div style="width: 500px;">
...
<el-tree ...>
<span ...>
<span>{{ node.label }}</span>
<span>
...
<el-button type="danger" size="mini" class="depBtn"
@click="() => deleteDep(data)">
删除部门
</el-button>
</span>
</span>
</el-tree>
...
</div>
</template>
<script>
export default {
...
methods: {
...
// 删除部门
deleteDep(data){
if(data.isParent){
this.$message.error('父部门删除失败!')
}else{
this.$confirm('此操作将永久删除[ '+data.name+' ]该部门,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/basic/department/'+data.id).then(resp=>{
if(resp){
// 手动删除列表节点
this.removeDepFromDeps(null, this.deps, data.id)
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
removeDepFromDeps(p, deps, id){
for(let i = 0; i < deps.length; i++){
let d = deps[i];
if(d.id == id){
deps.splice(i, 1);
if(deps.length==0){
p.isParent = false;
}
return;
}else{
this.removeDepFromDeps(d, d.children, id)
}
}
}
},
...
}
</script>
...
八、操作员管理
1. 页面设计
<template>
<div>
<div class="search-container">
<el-input placeholder="通过用户名搜索用户..." prefix-icon="el-icon-search"
style="width: 400px;margin-right: 10px" size="small"></el-input>
<el-button type="primary" icon="el-icon-search" size="small">搜索</el-button>
</div>
<div class="admin-container">
<el-card class="admin-card" v-for="(admin, index) in admins" :key="index">
<div slot="header" class="clearfix">
<span>{{admin.name}}</span>
<el-button style="float: right; padding: 3px 0; color: red;"
type="text" icon="el-icon-delete"></el-button>
</div>
<div class="userface-container">
<img :src="admin.userFace" :alt="admin.name" :title="admin.name" class="admin-userface">
</div>
<div class="userinfo-container">
<div>用户名:{{admin.name}}</div>
<div>手机号码:{{admin.phone}}</div>
<div>电话号码:{{admin.telphone}}</div>
<div>地址:{{admin.address}}</div>
<div>用户状态:
<el-switch v-model="admin.enabled"
active-color="#13ce66" inactive-color="#ff4949"
active-text="启用" inactive-text="禁用">
</el-switch>
</div>
<div>用户角色:
<el-tag type="success" v-for="(role, indexj) in admin.roles" :key="indexj"
style="margin-right: 5px;">{{role.remark}}</el-tag>
<el-button type="text" icon="el-icon-more"></el-button>
</div>
<div>备注:
{{admin.remark}}
</div>
</div>
</el-card>
</div>
</div>
</template>
<script>
export default {
name: 'SysAdmin',
data() {
return {
admins: []
}
},
methods: {
initAdmins(){
this.getRequest('/system/admin/').then(resp=>{
if(resp){
this.admins = resp.obj;
}
})
}
},
mounted(){
this.initAdmins()
}
}
</script>
<style scoped>
.search-container{
display: flex;
justify-content: center;
margin-top: 10px;
}
.admin-container{
display: flex;
justify-content: space-around;
flex-wrap: wrap;
margin-top: 10px;
}
.userface-container{
display: flex;
justify-content: center;
}
.admin-userface{
width: 72px;
height: 72px;
border-radius: 72px;
}
.admin-card{
width: 420px;
margin-top: 10px;
}
.userinfo-container{
margin-top: 10px;
font-size: 12px;
}
</style>
2. 搜索功能
思路分析:通过给搜索框和变量keywords进行双向绑定,监听搜索按钮的点击事件,触发初始化界面的请求,即可刷新用户列表;
<template>
<div>
<div class="search-container">
<el-input v-model="keywords" ...></el-input>
<el-button @click="doSearch" ...>搜索</el-button>
</div>
...
</div>
</template>
<script>
export default {
name: 'SysAdmin',
data() {
return {
admins: [],
keywords: ''
}
},
methods: {
initAdmins(){
this.getRequest('/system/admin/?keyWords='+this.keywords).then(resp=>{
if(resp){
this.admins = resp.obj;
}
})
},
doSearch(){
this.initAdmins()
}
},
// watch:{
// keywords(){
// this.initAdmins()
// }
// },
mounted(){
this.initAdmins()
}
}
</script>
...
3. 删除操作
<template>
<div>
...
<div class="admin-container">
<el-card v-for="(admin, index) in admins" ...>
<div ...>
...
<el-button @click="deleteAdmin(admin)" ...></el-button>
</div>
...
</el-card>
</div>
</div>
</template>
<script>
export default {
...
methods: {
...
deleteAdmin(admin){
this.$confirm('此操作将永久删除[ '+admin.name+' ]该角色,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/admin/'+admin.id).then(resp=>{
if(resp){
// 刷新列表
this.initAdmins();
}
})
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
...
}
</script>
...
4. 修改角色
思路分析:将用户角色后边的三个小点按钮,嵌套进弹出框Popover中,当点击时,向后端发送get请求,获取所有角色展示在下拉框中;因为前边已经遍历了这个管理员,所以可以在弹出框的show的对应showPopover方法中传入admin这个参数,获取已取得的角色,存入selectedRoles中,该变量用于下拉框的选中展示;当弹出框隐藏时,触发hidePopover方法,比对admin.roles和selectedRoles,通过flag决定是否发送更新请求。
<template>
<div>
...
<div class="admin-container">
<el-card class="admin-card" v-for="(admin, index) in admins" :key="index">
...
<div class="userinfo-container">
...
<div>用户角色:
...
<el-popover placement="right" width="300" title="角色列表"
trigger="click" @show="showPopover(admin)" @hide="hidePopover(admin)">
<el-select v-model="selectedRoles" multiple placeholder="请选择"
style="width: 100%">
<el-option v-for="(r, index) in allRoles"
:key="index" :label="r.remark" :value="r.id">
</el-option>
</el-select>
<el-button type="text" icon="el-icon-more" slot="reference"></el-button>
</el-popover>
</div>
...
</div>
</el-card>
</div>
</div>
</template>
<script>
export default {
name: 'SysAdmin',
data() {
return {
...
allRoles: [], //所有角色
selectedRoles: [] //选中的角色
}
},
methods: {
...
initAllRoles(){ //获取所有角色
this.getRequest('/system/admin/roles').then(resp=>{
if(resp){
this.allRoles = resp.obj;
}
})
},
showPopover(admin){ //弹出时,类似于编辑角色的选择框
this.initAllRoles();
let roles = admin.roles; //获取已经选中的角色,这是一个对象
this.selectedRoles = [];
roles.forEach(r => {
this.selectedRoles.push(r.id);
});
},
hidePopover(admin){
let flag = false;
let myroles = [];
Object.assign(myroles, admin.roles);
if(myroles.length != this.selectedRoles.length){
flag = true;
}else{
for(let i=0; i<myroles.length; i++){
let role = myroles[i];
for(let j=0; j<this.selectedRoles.length; j++){
let sr = this.selectedRoles[j];
if(role.id == sr){
myroles.splice(i, 1);
i--; //因为删除,后边的下标会-1,所以这里i--,让它匹配所以
break;
}
}
}
if(myroles.length != 0){
flag = true;
}
}
if(flag){
let url = '/system/admin/updateAdminRoles?adminId=' + admin.id;
this.selectedRoles.forEach(sr=>{
url += '&rids=' + sr;
})
this.putRequest(url).then(resp => {
if(resp){
this.initAdmins();
}
})
}
},
...
},
...
}
</script>
...