文章目录
- 用户管理理页及详解权限管理
- 用户管理理页介绍及页面实现思路讲解
- 更完善的表单组件封装及思路讲解
- 通用表格组件封装思路以及完成表格组件的封装
- 用户管理页面功能实现
- 实现表格加载状态
- 实现分页功能
- 实现编辑功能
- 实现新增功能
- 实现删除功能
- 企业开发之权限管理思路路讲解
- 权限管理之动态返回菜单的实现
- 权限管理之路由守卫判断用户登录状态
- 最后再来补充一些未完成的
- 为这些接口加入模拟api
- 保存taglist到cookie中
- 完成搜索功能
- 项目总结
- 全文所涉及的代码下载地址
用户管理理页及详解权限管理
用户管理理页介绍及页面实现思路讲解
管理页功能
- 新增用户
- 搜索用户
- 更新用户
- 删除用户
- 分页展示用户列表
先给出用户管理响应的mock接口数据,在mock文件夹下新建一个user.js,内容如下
import Mock from 'mockjs'
// get请求从config.url获取参数,post从config.body中获取参数
function param2Obj(url) {
const search = url.split('?')[1]
if (!search) {
return {}
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"') +
'"}'
)
}
let List = []
const count = 200
for (let i = 0; i < count; i++) {
List.push(
Mock.mock({
id: Mock.Random.guid(),
name: Mock.Random.cname(),
addr: Mock.mock('@county(true)'),
'age|18-60': 1,
birth: Mock.Random.date(),
sex: Mock.Random.integer(0, 1)
})
)
}
export default {
/**
* 获取列表
* 要带参数 name, page, limt; name可以不填, page,limit有默认值。
* @param name, page, limit
* @return {{code: number, count: number, data: *[]}}
*/
getUserList: config => {
const {
name,
page = 1,
limit = 20
} = param2Obj(config.url)
console.log('name:' + name, 'page:' + page, '分页大小limit:' + limit)
const mockList = List.filter(user => {
if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false
return true
})
const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
return {
code: 20000,
count: mockList.length,
list: pageList
}
},
/**
* 增加用户
* @param name, addr, age, birth, sex
* @return {{code: number, data: {message: string}}}
*/
createUser: config => {
const {
name,
addr,
age,
birth,
sex
} = JSON.parse(config.body)
console.log(JSON.parse(config.body))
List.unshift({
id: Mock.Random.guid(),
name: name,
addr: addr,
age: age,
birth: birth,
sex: sex
})
return {
code: 20000,
data: {
message: '添加成功'
}
}
},
/**
* 删除用户
* @param id
* @return {*}
*/
deleteUser: config => {
const {
id
} = param2Obj(config.url)
if (!id) {
return {
code: -999,
message: '参数不正确'
}
} else {
List = List.filter(u => u.id !== id)
return {
code: 20000,
message: '删除成功'
}
}
},
/**
* 批量删除
* @param config
* @return {{code: number, data: {message: string}}}
*/
batchremove: config => {
let {
ids
} = param2Obj(config.url)
ids = ids.split(',')
List = List.filter(u => !ids.includes(u.id))
return {
code: 20000,
data: {
message: '批量删除成功'
}
}
},
/**
* 修改用户
* @param id, name, addr, age, birth, sex
* @return {{code: number, data: {message: string}}}
*/
updateUser: config => {
const {
id,
name,
addr,
age,
birth,
sex
} = JSON.parse(config.body)
const sex_num = parseInt(sex)
List.some(u => {
if (u.id === id) {
u.name = name
u.addr = addr
u.age = age
u.birth = birth
u.sex = sex_num
return true
}
})
return {
code: 20000,
data: {
message: '编辑成功'
}
}
}
}
在mock下的index.js就要去引入这些返回
import Mock from 'mockjs'
import homeApi from './home.js'
// 设置200-2000毫秒延时请求数据
Mock.setup({
timeout: '200-2000',
})
// 首页相关
// 拦截的是 /home/getData
Mock.mock(/\/home\/getData/, 'get', homeApi.getStatisticalData)
// 用户相关
Mock.mock(/\/user\/getUser/, 'get', userApi.getUserList)
Mock.mock(/\/user\/del/, 'get', userApi.deleteUser)
Mock.mock(/\/user\/batchremove/, 'get', userApi.batchremove)
Mock.mock(/\/user\/add/, 'post', userApi.createUser)
Mock.mock(/\/user\/edit/, 'post', userApi.updateUser)
Mock.mock(/\/home\/getData/, 'get', homeApi.getStatisticalData)
考虑到用户管理需要两个组件来完成,一个是表头部分,另一个是表单内容部分,所以新建两个组件,分别为CommonForm.vue(提交新建和搜索),内容如下
<template>
<div>CommonForm</div>
</template>
<script>
</script>
<style lang="scss" scoped></style>
CommonTable.vue(显示用户信息),内容如下
<template>
<div>CommonTable</div>
</template>
<script>
</script>
新建一个UserManage.vue页面的样式,在scss文件夹下新建common.scss,内容如下
.manage {
background-color: yellow;
}
在UserManage.vue中引入这两个组件,并新建一个scss来作为他的样式
<template>
<div class="manage">
<CommonForm></CommonForm>
<CommonTable></CommonTable>
</div>
</template>
<script>
import CommonForm from '../../components/CommonForm'
import CommonTable from '../../components/CommonTable'
export default {
components: {
CommonForm,
CommonTable,
},
}
</script>
<style lang="scss" scoped>
@import "@/assets/scss/common";
</style>
目前看到占坑的效果
更完善的表单组件封装及思路讲解
分析表单组成
- 输⼊框
- 下拉框
- 日期选择器
- 单选列表
- 多选列表
- 。。。
考虑基本参数
- 绑定的表单字段
- 表单描述文本
插槽拓展组件
- 按钮组
在CommonForm.vue中完成我们表单的初步封装,在页面那种加入了三种控件,分别为input、select和switch、date-picker,需要传入对应的formLabel才会显示出来,比如我们要显示swith与input
data() {
return {
searchFrom: {
keyword: ''
},
formLabel: [
{
model: 'keyword',
label: '' // 组件描述的内容
},
{
type: 'switch', // 用在项目中,是去掉这个的
label: '' // 组件描述的内容
}
]
}
}
现在CommonForm.vue的代码如下,
<template>
<el-form :inline="inline" :model="form" ref="form" label-width="100px">
<el-form-item v-for="item in formLabel" :key="item.model" :label="item.label">
<el-input v-model="form[item.model]" :placeholder="'请输入' + item.label" v-if="!item.type"></el-input>
<el-select v-model="form[item.model]" placeholder="请选择" v-if="item.type === 'select'">
<el-option
v-for="item in item.opts"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
<el-switch v-model="form[item.model]" v-if="item.type === 'switch'"></el-switch>
<el-date-picker
v-model="form[item.model]"
type="date"
placeholder="选择日期"
v-if="item.type === 'date'"
value-format="yyyy-MM-dd"
></el-date-picker>
</el-form-item>
<el-form-item>
<slot></slot> // 按钮的插槽,调用时候传入
</el-form-item>
</el-form>
</template>
<script>
export default {
props: {
inline: Boolean,
form: Object,
formLabel: Array
}
}
</script>
<style lang="scss" scoped></style>
我们到UserManage.vue中去正式使用他
<template>
<div class="manage">
<div class="manage-header">
<el-button type="primary">+ 新增</el-button>
<CommonForm inline :formLabel="formLabel" :form="searchFrom">
<el-button type="primary">搜索</el-button>
</CommonForm>
</div>
<CommonTable></CommonTable>
</div>
</template>
<script>
import CommonForm from '../../components/CommonForm'
import CommonTable from '../../components/CommonTable'
export default {
components: {
CommonForm,
CommonTable,
},
data() {
return {
searchFrom: {
keyword: ''
},
formLabel: [
{
model: 'keyword',
label: '' // 组件描述的内容
},
]
}
}
}
</script>
<style lang="scss" scoped>
@import "@/assets/scss/common";
</style>
现在使用额度common.scss样式,只是为了更加符合表单的效果
.manage {
height: 90%;
padding-bottom: 20px;
overflow: hidden;
&-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
}
目前的效果如下
通用表格组件封装思路以及完成表格组件的封装
表格基本参数分析
- data: 传⼊的数据列表
- prop: 传入的数据字段
- label: 列名
表格可选参数分析
- width:列宽
- type:类型
表格扩展
- 分页参数
- total: 数据条数总计
- page: 当前页数
- 加载状态
- loading:布尔值
修改CommonTable.vue控件,将需要的表格属性渲染的table-column定义出来
<template>
<div class="common-table">
<el-table :data="tableData">
<el-table-column label="序号" width="85"></el-table-column>
<el-table-column
show-overflow-tooltip
v-for="item in tableLabel"
:key="item.prop"
:label="item.label"
>
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
props: {
tableData: Array,
tableLabel: Array,
config: Object // 接收分页等配置
},
}
</script>
在UserManage.vue页面中调用时,需要传入tableData和tableLabel还有config
<template>
<div class="manage">
<div class="manage-header">
<el-button type="primary">+ 新增</el-button>
<CommonForm inline :formLabel="formLabel" :form="searchFrom">
<el-button type="primary">搜索</el-button>
</CommonForm>
</div>
<common-table :tableData="tableData" :tableLabel="tableLabel" :config="config"></common-table>
</div>
</template>
...
tableLabel在data数据区域中直接定义,tableData先定义出来,在methods中去通过访问接口进行请求。config为存放的一些表格参数,预先定义,也是通过接口数据返回后进行设定
<template>
...
</template>
<script>
...
data() {
return {
tableData: [],
tableLabel: [
{
prop: 'name',
label: '姓名'
},
{
prop: 'age',
label: '年龄'
},
{
prop: 'sexLabel',
label: '性别'
},
{
prop: 'birth',
label: '出生日期',
width: 200
},
{
prop: 'addr',
label: '地址',
width: 320
}
],
config: { // 分页等配置参数
page: 1,
total: 30,
loading: false
},
searchFrom: {
keyword: ''
},
formLabel: [
{
model: 'keyword',
label: '' // 组件描述的内容
},
]
}
},
methods: {
getList() {
this.config.loading = true
this.$http
.get('/api/user/getUser', {
params: {
page: this.config.page,
name
}
})
.then(res => {
this.tableData = res.data.list.map(item => { // 处理一下sex的中文显示
item.sexLabel = item.sex === 0 ? '女' : '男'
return item
})
this.config.total = res.data.count
this.config.loading = false
})
},
},
created() {
this.getList()
}
}
</script>
...
目前的效果如下
加入表格的序号,序号是通过分页加上当前index得到的
<template>
<div class="common-table">
<el-table :data="tableData" height="90%" stripe>
<el-table-column label="序号" width="85">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ (config.page - 1) * 20 + scope.$index + 1 }}</span>
</template>
</el-table-column>
...
</template>
表格中的最后一列是操作按钮,他们的click事件在后面补充
...
<el-table :data="tableData" height="90%" stripe>
<el-table-column label="序号" width="85">
<el-table-column label="操作" min-width="80">
<template>
<el-button size="mini">编辑</el-button>
<el-button size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
...
下来还有一个分页控件
<template>
...
<el-table-column label="操作" min-width="60">
<template>
<el-button size="mini">编辑</el-button>
<el-button size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pager"
layout="prev, pager, next"
:total="config.total"
:current-page.sync="config.page"
:page-size="20"
></el-pagination>
</div>
</template>
现在的效果为
调整一下common-table和pager控件的样式
<style lang="scss" scoped>
.common-table {
height: calc(100% - 62px); // 62px是上面的搜索
background-color: #fff;
position: relative;
.pager {
position: absolute;
bottom: 0;
right: 20px;
}
}
</style>
效果就出来了
用户管理页面功能实现
- 表格加载状态
- 分页功能
- 编辑功能
- 删除功能
实现表格加载状态
表格加载状态只需要在el-table中绑定属性v-loading即可,config.loading是我们后台传过来的
<template>
<div class="common-table">
<el-table :data="tableData" height="90%" stripe v-loading="config.loading">
...
效果可以看到
实现分页功能
在CommonTable.vue组件中,加入分页的监听事件current-change,让其调用父组件的changePage消息
<template>
...
<el-pagination
class="pager"
layout="prev, pager, next"
:total="config.total"
:current-page.sync="config.page"
:page-size="20"
@current-change="changePage"
></el-pagination>
</div>
</template>
<script>
export default {
props: {
tableData: Array,
tableLabel: Array,
config: Object // 接收分页等配置
},
methods: {
changePage(page) {
this.$emit('changePage', page) // 传给父组件的changePage
}
}
}
</script>
...
父组件接收这个changePage,响应他的是getList,config.page是一个双向绑定的变量,当子组件中改变值以后,父组件也会改变,所以请求getList接口的this.config.page此时也改变了
...
<common-table
:tableData="tableData"
:tableLabel="tableLabel"
:config="config"
@changePage="getList"
></common-table>
</div>
</template>
...
export default {
...
methods: {
getList() {
this.config.loading = true
this.$http
.get('/api/user/getUser', {
params: {
page: this.config.page,
name
}
})
.then(res => {
this.tableData = res.data.list.map(item => { // 处理一下sex的中文显示
item.sexLabel = item.sex === 0 ? '女' : '男'
return item
})
this.config.total = res.data.count
this.config.loading = false
})
},
},
created() {
this.getList()
}
}
</script>
...
现在也是第二页的显示
实现编辑功能
编辑功能比较好实现,编辑按钮是在CommonTable.vue组件中的,只需要在,只需要对两个按钮进行事件绑定,然后通过消息传递到UserManage.vue中,顺带把删除的消息传递一块写了
<template>
...
<el-table-column label="操作" min-width="80">
<template slot-scope="scope">
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
...
</template>
<script>
export default {
...
methods: {
changePage(page) {
this.$emit('changePage', page) // 传给父组件的changePage
},
handleEdit(row) {
console.log("handleEdit")
this.$emit("edit", row)
},
handleDelete(row) {
console.log("handleDelete")
this.$emit("delete", row)
},
}
}
</script>
...
在页面UserManage.vue中,去响应传过来的消息,在editUser与delUser中先建个坑
<template>
...
<common-table
:tableData="tableData"
:tableLabel="tableLabel"
:config="config"
@changePage="getList"
@edit="editUser"
@delete="delUser"
></common-table>
</div>
</template>
...
editUser(row) {
console.log("editUser")
console.log(row)
},
delUser(row) {
console.log("delUser")
},
...
实现新增功能
新增功能需要做的只是新建一个form,填入对应的内容,这个form可以重复利用之前的组件CommonForm.vue,为此我们需要在data部分新增需要的内容,大概也就这几个
- operateType:我们需要一个类型,在后面提交数据的时候还需要区分是编辑还是新增
- isShow:表单是否显示出来
- operateForm:定义表单需要提交的字段(与接口中返回的内容需要一致)
- operateFormLabel:表示在CommonForm.vue组件上面我们需要出现的控件
下面是这些data数据的数据部分
data() {
return {
...
operateType: 'add',
isShow: false,
// 定义表单需要编辑的字段
operateForm: {
name: '',
addr: '',
age: '',
birth: '',
sex: ''
},
operateFormLabel: [
{
model: 'name',
label: '姓名'
},
{
model: 'age',
label: '年龄'
},
{
model: 'sex',
label: '性别',
type: 'select',
opts:
[
{
label: '男',
value: 1
},
{
label: '女',
value: 0
}
]
},
{
model: 'birth',
label: '出生日期',
type: 'date'
},
{
model: 'addr',
label: '地址'
}
],
...
}
},
现在来加入表单的部分,因为想把新增和编辑是在同一个表单中进行,所以就只用一个el-dialog来显示,所以UserManage.vue现在的网页部分代码为
<template>
<div class="manage">
<el-dialog :title="operateType === 'add' ? '新增用户' : '更新用户'" :visible.sync="isShow">
<common-form :formLabel="operateFormLabel" :form="operateForm" ref="form"></common-form>
<div slot="footer" class="dialog-footer">
<el-button @click="isShow = false">取 消</el-button>
<el-button type="primary" @click="confirm">确 定</el-button>
</div>
</el-dialog>
<div class="manage-header">
<el-button type="primary" @click="addUser">+ 新增</el-button>
<CommonForm inline :formLabel="formLabel" :form="searchFrom">
<el-button type="primary">搜索</el-button>
</CommonForm>
</div>
<common-table
:tableData="tableData"
:tableLabel="tableLabel"
:config="config"
@changePage="getList"
@edit="editUser"
@delete="delUser"
></common-table>
</div>
</template>
接着来改editUser部分代码、增加addUser代码,以及实现confirm的提交,先用输出占坑
...
addUser() {
// this.operateType = 'edit'
// this.isShow = true
// this.operateForm = row
// console.log("addUser")
console.log("addUser")
this.operateType = 'add'
this.isShow = true
},
editUser(row) {
// console.log("editUser")
// console.log(row)
// 显示编辑框
this.operateType = 'edit'
this.isShow = true
this.operateForm = row
},
...
confirm() {
// console.log(row)
if (this.operateType === 'edit') {
console.log("edit", this.operateForm)
}
else {
console.log("add", this.operateForm)
}
// 无论是新增还是编辑都需要重新刷新表格
this.getList()
}
},
...
现在所能看到的新增点击事件如下
编辑后可以看到的内容
实现删除功能
之前给删除留了坑,我们想要在删除的时候,进行一下消息提示,也就是使用eleme组件的"Message Box弹框"
delUser(row) {
// console.log("editUser")
console.log(row)
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
// 提交给后台进行删除
console.log("提交给后台进行删除")
// 删除后进行刷新
this.getList()
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
企业开发之权限管理思路路讲解
什么是权限管理理
- 根据不同用户,返回不同菜单
- 严格控制用户权限
- 实现思路
- 动态路由
- 后端返回的数据格式要求
- 触发时机
- 登陆成功的时候触发操作
- Cookie中存在对应数据,首次进入页面时
为了实现权限管理,需要先加入一个登陆页面,只有当登陆成功后,才能进行后续的操作
修改之前views/Login下的Login.vue代码
<template>
<div style="padding: 20px">
<el-form :model="form" label-width="120">
<el-form-item label="用户名">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password"></el-input>
</el-form-item>
<el-form-item align="center">
<el-button type="primary" @click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
password: ''
}
}
},
methods: {
login() {
this.$http.post('api/permission/getMenu', this.form).then(res => {
res = res.data
if (res.code === 20000) {
console.log("20000")
} else {
console.log("other")
}
})
}
}
}
</script>
<style lang="scss" scoped>
.el-form {
width: 50%;
margin: auto;
padding: 45px;
height: 450px;
background-color: #fff;
}
</style>
在mock中新建一个permission.js,响应这个登陆请求
import Mock from 'mockjs'
export default {
getMenu: config => {
const { username, password } = JSON.parse(config.body)
console.log(JSON.parse(config.body))
// 先判断用户是否存在
if (username === 'admin' || username === 'wp') {
// 判断账号和密码是否对应
if (username === 'admin' && password === '123456') {
return {
code: 20000,
data: {
menu: [
{
path: '/',
name: 'home',
label: '首页',
icon: 's-home',
url: 'Home/Home'
},
{
path: '/video',
name: 'video',
label: '视频管理页',
icon: 'video-play',
url: 'VideoManage/VideoManage'
},
{
path: '/user',
name: 'user',
label: '用户管理页',
icon: 'user',
url: 'UserManage/UserManage'
},
{
label: '其他',
icon: 'location',
children: [
{
path: '/page1',
name: 'page1',
label: '页面1',
icon: 'setting',
url: 'Other/PageOne'
},
{
path: '/page2',
name: 'page2',
label: '页面2',
icon: 'setting',
url: 'Other/PageTwo'
}
]
}
],
message: '获取成功'
}
}
} else if (username === 'wp' && password === '123456') {
return {
code: 20000,
data: {
menu: [
{
path: '/',
name: 'home',
label: '首页',
icon: 's-home',
url: 'Home/Home'
},
{
path: '/video',
name: 'video',
label: '视频管理页',
icon: 'video-play',
url: 'VideoManage/VideoManage'
}
],
message: '获取成功'
}
}
} else {
return {
code: -999,
data: {
message: '密码错误'
}
}
}
} else {
return {
code: -999,
data: {
message: '用户不存在'
}
}
}
}
}
在router/index.js引入这个permission.js
import Mock from 'mockjs'
import homeApi from './home.js'
import userApi from './user.js'
import permissionApi from './permission.js'
...
// 权限相关
Mock.mock(/\/permission\/getMenu/, 'post', permissionApi.getMenu)
在路由中定义一个/login,找到router/index.js,加入如下新增内容
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [{
path: '/',
component: () => import('@/views/Main.vue'),
children: [{
...
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login/Login')
}
]
...
测试一下登陆,访问链接http://localhost:3333/#/login,可以看到如下
输入admin、123456后提交,可以看到后台输出
权限管理之动态返回菜单的实现
更改路由表
- 根据是否需要权限的路由分类
vuex里补充mutation
- 保存菜单
- 动态添加菜单
生成路由的时机
- 登录时
- 刷新时
点击退出时,清除cookie后,刷新下页面
先要安装js-cookie
yarn add js-cookie -S
之前的左侧菜单项都是在CommonAside.vue中的data写死的,现在想让在登录后自动维护一下动态菜单,之前在tab.js中定义的menu这个时候就能用起来了
先来屡一下,我们一共需要这几个操作
- clearMenu:在退出登录的时候情况菜单
- setMenu:当接口回复菜单的json数据时,保存到cookie中
- addMenu:从cookie中获取菜单的数据,通过router.addRoutes添加到路由中
...
state: {
isCollapse: false,
menu: [],
currentMenu: {},
...
clearMenu(state) {
state.menu = []
Cookie.remove('menu')
},
setMenu(state, val) {
state.menu = val
Cookie.set('menu', JSON.stringify(val))
},
addMenu(state, router) {
// 从cookie中取出来menu数据,将这个数据添加到router中
if (!Cookie.get('menu')) {
return
}
let menu = JSON.parse(Cookie.get('menu'))
state.menu = menu
let currentMenu = [
{
path: '/',
component: () => import(`@/views/Main`),
children: [],
},
]
menu.forEach((item) => {
if (item.children) {
item.children = item.children.map((item) => {
item.component = () => import(`@/views/${item.url}`)
return item
})
currentMenu[0].children.push(...item.children)
} else {
item.component = () => import(`@/views/${item.url}`)
currentMenu[0].children.push(item)
}
})
router.addRoutes(currentMenu) // 动态添加到路由中
},
...
在CommonAside.vue中,我们需要用这个store中的menu替换下之前使用的asideMenu
...
<script>
export default {
computed: {
noChildren() {
// return this.asideMenu.filter((item) => !item.children)
return this.menu.filter((item) => !item.children)
},
hasChildren() {
// return this.asideMenu.filter((item) => item.children)
return this.menu.filter((item) => item.children)
},
isCollapse() {
return this.$store.state.tab.isCollapse
},
menu() {
return this.$store.state.tab.menu
},
},
...
在views/Login/Login.vue登录成功时,需要完成菜单的操作
...
methods: {
login() {
this.$http.post('api/permission/getMenu', this.form).then((res) => {
res = res.data
if (res.code === 20000) {
console.log('20000')
// 接收菜单
this.$store.commit('clearMenu')
this.$store.commit('setMenu', res.data.menu)
this.$store.commit('addMenu', this.$router)
this.$router.push({ name: 'home' })
} else {
console.log('other')
...
接着我们再来做测试,首先用管理员admin/123456登入系统
再用wp/123456登入
可以看到菜单是不一样的
权限管理之路由守卫判断用户登录状态
permission.js中返回token可以通过mock来实现
...
if (username === 'admin' && password === '123456') {
return {
code: 20000,
data: {
menu: [{
path: '/',
name: 'home',
label: '首页',
icon: 's-home',
url: 'Home/Home'
},
...
token: Mock.Random.guid(),
message: '获取成功'
}
}
} else if (username === 'wp' && password === '123456') {
...
token: Mock.Random.guid(),
message: '获取成功'
...
在store中新建一个user.js去保存这个token以及相关的处理
import Cookie from 'js-cookie'
export default {
state: {
token: ''
},
mutations: {
setToken(state, val) {
state.token = val
Cookie.set('token', val)
},
clearToken(state) {
state.token = ''
Cookie.remove('token')
},
getToken(state) {
state.token = Cookie.get('token')
}
},
actions: {},
}
要使用还得在store/index.js去注册他
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab'
import user from './user'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
tab,
user
}
})
那么我们需要完成以后这些事,登录时保存token
- 保存在vuex里
- 保存在cookie里
在Login/Login.vue中登录后,设置cookie
...
methods: {
login() {
this.$http.post('api/permission/getMenu', this.form).then((res) => {
res = res.data
if (res.code === 20000) {
console.log('20000')
// 接收菜单
this.$store.commit('clearMenu')
this.$store.commit('setMenu', res.data.menu)
this.$store.commit('setToken', res.data.token)
this.$store.commit('addMenu', this.$router)
this.$router.push({ name: 'home' })
} else {
console.log('other')
...
router/index.js,现在的内容如下
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'login',
component: () => import('@/views/Login/Login')
}
]
const router = new VueRouter({
routes
})
export default router
要完成在所以的路由中都去判断是否有token,要在路由中加一个守卫,找到main.js,进入路由守卫,并且在路由开始时创建菜单
...
router.beforeEach((to, from, next) => {
// 防止刷新后vuex里丢失token
store.commit('getToken')
// 防止刷新后vuex里丢失标签列表tagList
store.commit('getMenu')
let token = store.state.user.token
// 过滤登录页,防止死循环
if (!token && to.name !== 'login') {
next({
name: 'login'
})
} else {
next()
}
})
new Vue({
router,
store,
render: (h) => h(App),
created() {
store.commit('addMenu', router)
}
}).$mount('#app')
我们来完成退出删除token事件,这样才能测试路由守卫是否设置成功,找到components/CommonHeader.vue,加入退出按钮的事件,在退出按钮清除cookie的时候,我们希望能对页面进行刷新,所以还使用reload
<template>
...
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item @click.native="logOut">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
import { mapState } from 'vuex'
export default {
...
methods: {
collapseMenu() {
this.$store.commit('collapseMenu')
},
logOut() {
this.$store.commit('clearToken')
this.$store.commit('clearMenu')
location.reload()
},
},
}
...
来测试一下使用退出,已经自动跳转到login页面了
最后再来补充一些未完成的
为这些接口加入模拟api
之前都只是进行输出占坑,既然在mock/user.js中已经定义了很多的api,就用起来。这几个接口在views/UserManage/UserManage.vue中
...
addUser() {
// this.operateType = 'edit'
// this.isShow = true
// this.operateForm = row
// console.log("addUser")
console.log('addUser')
this.operateType = 'add'
this.isShow = true
},
editUser(row) {
// console.log("editUser")
// console.log(row)
// 显示编辑框
this.operateType = 'edit'
this.isShow = true
this.operateForm = row
},
delUser(row) {
// console.log("editUser")
console.log(row)
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
let id = row.id
this.$http
.get('/api/user/del', {
params: {
id,
},
})
.then((res) => {
console.log(res.data)
this.$message({
type: 'success',
message: '删除成功!',
})
this.getList()
})
// this.$message({
// type: 'success',
// message: '删除成功!',
// })
// 提交给后台进行删除
console.log('提交给后台进行删除')
// 删除后进行刷新
this.getList()
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消删除',
})
})
},
confirm() {
if (this.operateType === 'edit') {
this.$http.post('/api/user/edit', this.operateForm).then((res) => {
console.log(res.data)
this.isShow = false
this.getList()
})
} else {
this.$http.post('/api/user/add', this.operateForm).then((res) => {
console.log(res.data)
this.isShow = false
this.getList()
})
}
// 无论是新增还是编辑都需要重新刷新表格
this.getList()
},
},
created() {
this.getList()
},
}
</script>
...
在浏览器中做一下测试http://localhost:3333/#/user
保存taglist到cookie中
如果不把taglist保存到cookie中,每当刷新页面,这些信息就会丢失,总是vuex中的数据感觉总是那么不靠谱,因为之前在main.js中已经在路由守卫中使用了getMenu,但之前没有保存
...
router.beforeEach((to, from, next) => {
// 防止刷新后vuex里丢失token
store.commit('getToken')
// 防止刷新后vuex里丢失标签列表tagList
store.commit('getMenu')
let token = store.state.user.token
// 过滤登录页,防止死循环
...
所以我们只需要在store/tab.js中selectMenu选择是保存到Cookie中
...
mutations: {
selectMenu(state, val) {
if (val.name !== 'home') {
state.currentMenu = val
let result = state.tabsList.findIndex((item) => item.name === val.name)
result === -1 ? state.tabsList.push(val) : ''
Cookie.set('tagList', JSON.stringify(state.tabsList))
} else {
state.currentMenu = null
}
},
...
getMenu(state) {
if (Cookie.get('tagList')) {
let tagList = JSON.parse(Cookie.get('tagList'))
state.tabsList = tagList
}
},
},
...
}
这时再刷新就不会丢失tag了
完成搜索功能
最后还差一下UserManage.vue页面中的搜索功能,我们之前已经为搜索文本框绑定了keyword属性,只需要利用这个值,传入到接口getList即可
<template>
<div class="manage">
<el-dialog
:title="operateType === 'add' ? '新增用户' : '更新用户'"
:visible.sync="isShow"
>
<common-form
:formLabel="operateFormLabel"
:form="operateForm"
ref="form"
></common-form>
<div slot="footer" class="dialog-footer">
<el-button @click="isShow = false">取 消</el-button>
<el-button type="primary" @click="confirm">确 定</el-button>
</div>
...
methods: {
getList(name = '') {
console.log(name)
this.config.loading = true
// 搜索时,页码需要设置为1,才能正确返回数据,因为数据是从第一页开始返回的
name ? (this.config.page = 1) : ''
this.$http
.get('/api/user/getUser', {
params: {
page: this.config.page,
name,
},
})
.then((res) => {
this.tableData = res.data.list.map((item) => {
// 处理一下sex的中文显示
item.sexLabel = item.sex === 0 ? '女' : '男'
return item
})
this.config.total = res.data.count
this.config.loading = false
})
},
...
来看看搜索,当然现在只能搜索name
项目总结
项目当中遇到的坑以及解决思路
- 通过vue-devtool调试
- 通过console输出调试
组件的封装思路路
- 判断的基本参数
- 哪些写死
- 哪些是传进来
- 拓展
- 自定义事件,判断传出哪些参数
- 插槽扩展
- 优化
- 提高他的适应性
- vif,velse根据⽗父组件传⼊入的条件来生成对应的模板
学习一个新技术
- EChart
- 大局观,直接看快速教程
- 分成几部分,在对应部分查找文档