管理页功能
先给出用户管理响应的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(提交新建和搜索),内容如下
CommonForm
CommonTable.vue(显示用户信息),内容如下
CommonTable
新建一个UserManage.vue页面的样式,在scss文件夹下新建common.scss,内容如下
.manage {
background-color: yellow;
}
在UserManage.vue中引入这两个组件,并新建一个scss来作为他的样式
分析表单组成
考虑基本参数
插槽拓展组件
在CommonForm.vue中完成我们表单的初步封装,在页面那种加入了三种控件,分别为input、select和switch、date-picker,需要传入对应的formLabel才会显示出来,比如我们要显示swith与input
data() {
return {
searchFrom: {
keyword: ''
},
formLabel: [
{
model: 'keyword',
label: '' // 组件描述的内容
},
{
type: 'switch', // 用在项目中,是去掉这个的
label: '' // 组件描述的内容
}
]
}
}
现在CommonForm.vue的代码如下,
// 按钮的插槽,调用时候传入
我们到UserManage.vue中去正式使用他
+ 新增
搜索
现在使用额度common.scss样式,只是为了更加符合表单的效果
.manage {
height: 90%;
padding-bottom: 20px;
overflow: hidden;
&-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
}
表格基本参数分析
表格可选参数分析
表格扩展
修改CommonTable.vue控件,将需要的表格属性渲染的table-column定义出来
{
{ scope.row[item.prop] }}
在UserManage.vue页面中调用时,需要传入tableData和tableLabel还有config
+ 新增
搜索
...
tableLabel在data数据区域中直接定义,tableData先定义出来,在methods中去通过访问接口进行请求。config为存放的一些表格参数,预先定义,也是通过接口数据返回后进行设定
...
...
加入表格的序号,序号是通过分页加上当前index得到的
{
{ (config.page - 1) * 20 + scope.$index + 1 }}
...
表格中的最后一列是操作按钮,他们的click事件在后面补充
...
编辑
删除
...
下来还有一个分页控件
...
编辑
删除
调整一下common-table和pager控件的样式
表格加载状态只需要在el-table中绑定属性v-loading即可,config.loading是我们后台传过来的
...
父组件接收这个changePage,响应他的是getList,config.page是一个双向绑定的变量,当子组件中改变值以后,父组件也会改变,所以请求getList接口的this.config.page此时也改变了
...
编辑功能比较好实现,编辑按钮是在CommonTable.vue组件中的,只需要在,只需要对两个按钮进行事件绑定,然后通过消息传递到UserManage.vue中,顺带把删除的消息传递一块写了
...
编辑
删除
...
...
在页面UserManage.vue中,去响应传过来的消息,在editUser与delUser中先建个坑
...
新增功能需要做的只是新建一个form,填入对应的内容,这个form可以重复利用之前的组件CommonForm.vue,为此我们需要在data部分新增需要的内容,大概也就这几个
下面是这些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现在的网页部分代码为
+ 新增
搜索
接着来改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: '已取消删除'
});
});
},
什么是权限管理理
为了实现权限管理,需要先加入一个登陆页面,只有当登陆成功后,才能进行后续的操作
修改之前views/Login下的Login.vue代码
登录
在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,可以看到如下
更改路由表
vuex里补充mutation
生成路由的时机
点击退出时,清除cookie后,刷新下页面
先要安装js-cookie
yarn add js-cookie -S
之前的左侧菜单项都是在CommonAside.vue中的data写死的,现在想让在登录后自动维护一下动态菜单,之前在tab.js中定义的menu这个时候就能用起来了
先来屡一下,我们一共需要这几个操作
...
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
...
...
在浏览器中做一下测试http://localhost:3333/#/user
如果不把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即可
...
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
})
},
...
项目总结
项目当中遇到的坑以及解决思路
- 通过vue-devtool调试
- 通过console输出调试
组件的封装思路路
- 判断的基本参数
- 哪些写死
- 哪些是传进来
- 拓展
- 自定义事件,判断传出哪些参数
- 插槽扩展
- 优化
- 提高他的适应性
- vif,velse根据⽗父组件传⼊入的条件来生成对应的模板
学习一个新技术
- EChart
- 大局观,直接看快速教程
- 分成几部分,在对应部分查找文档
全文所涉及的代码下载地址
- csdn下载链接