2、用户列表功能开发
项目仓库:https://github.com/changeclass/vue-shop
主页布局
主页布局使用ElementUI提供的布局。
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<el-button type="info" @click="logout">退出</el-button>
</el-header>
<!-- 页面主体 -->
<el-container>
<!-- 侧边栏 -->
<el-aside width="200px">Aside</el-aside>
<!-- 页面右侧 -->
<el-main>Main</el-main>
</el-container>
</el-container>ElementUI提供的组件,其组件名就是他的class名,因此样式可以写成如下
.home-container{
height: 100%;
}
.el-header {
background-color: #373d41;
}
.el-aside {
background-color: #333744;
}
.el-main {
background-color: #eaedf1;
}header布局
header布局使用flex布局很容易实现。
对于HTML结构改造成如下
<el-header> <div> <img src="../assets/heima.png" alt=""> <span>电商后台管理系统</span> </div> <el-button type="info" @click="logout">退出</el-button> </el-header>css样式
.el-header { background-color: #373d41; display: flex; justify-content: space-between; padding-left: 0; align-items: center; color: #fff; font-size: 20px; >div{ display: flex; align-items: center; span{ margin-left: 15px; } } }
侧边栏布局
<el-menu
background-color="#333744"
text-color="#fff"
active-text-color="#ffd04b"
>
<!-- 一级菜单 -->
<el-submenu index="1">
<!-- 一级菜单模板区域 -->
<template slot="title">
<!-- 图标 -->
<i class="el-icon-location"></i>
<!-- 文本 -->
<span>导航一</span>
</template>
<!-- 二级菜单 -->
<el-menu-item index="1-4-1">
<template slot="title">
<!-- 图标 -->
<i class="el-icon-location"></i>
<!-- 文本 -->
<span>导航一</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>但由于是按需导入的ElementUI组件,因此还需要在注册相关的组件。
import {
Menu,
MenuItem,
Submenu,
MenuItemGroup
} from 'element-ui'
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Submenu)
Vue.use(MenuItemGroup)配置请求拦截器
配置请求拦截器的目的主要是因为请求数据时需要进行头部的TOKEN验证,因此在请求拦截器中设置TOKEN的无疑是最好的方式。在入口函数中对axios进行设置
// 设置拦截器
axios.interceptors.request.use(config => {
config.headers.Authorization = window.sessionStorage.getItem('token')
return config
})发起请求获取左侧菜单数据
组件一渲染就应该发起请求去获取数据,根据数据将其赋值给自己的属性。根据属性进行UI渲染。
export default {
created () {
this.getMenuList()
},
data: function () {
return {
menulist: []
}
},
methods: {
async getMenuList () {
const { data: res } = await this.$http.get('menus')
if (res.meta.status !== 200) return this.$message.error(res.meta.message)
this.menulist = res.data
}
}
}左侧菜单UI绘制
根据接口返回的数据格式,可以使用两个for循环渲染菜单。
<el-menu
background-color="#333744"
text-color="#fff"
active-text-color="#ffd04b"
>
<!-- 一级菜单 -->
<el-submenu
:index="item.id + ''"
v-for="item in menulist"
:key="item.id"
>
<!-- 一级菜单模板区域 -->
<template slot="title">
<!-- 图标 -->
<i class="el-icon-location"></i>
<!-- 文本 -->
<span>{{ item.authName }}</span>
</template>
<!-- 二级菜单 -->
<el-menu-item
:index="subItem.id + ''"
v-for="subItem in item.children"
:key="subItem.id"
>
<template slot="title">
<!-- 图标 -->
<i class="el-icon-location"></i>
<!-- 文本 -->
<span>{{ subItem.authName }}</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>图标
对于二级菜单图标使用的是相同的图标,即写死即可。但一级菜单会根据不同而变化。因此可以定义一个属性用于记录每个分类应展示的图标。
data: function () {
return {
iconObj: {
125: 'iconfont icon-user',
103: 'iconfont icon-tijikongjian',
101: 'iconfont icon-shangpin',
102: 'iconfont icon-danju',
145: 'iconfont icon-baobiao'
}
}
},渲染时只需要取出对应的键的值即可。
<i :class="iconObj[item.id]"></i>只能展开一个一级菜单
对menu添加属性unique-opened为true即可
<el-menu :unique-opened="true"></el-menu>折叠与展开
组件提供了一个属性collapse用于控制是否折叠展开,但展开与折叠时宽度也要随之变化,因此通过此值也需要变化宽度
<el-aside :width="isCollpase ? '64px' : '200px'">
<div class="toggle-button" @click="toggleCollapse">|||</div>
<el-menu
background-color="#333744"
text-color="#fff"
active-text-color="#409bff"
:unique-opened="true"
:collapse="isCollpase"
:collapse-transition="false"
></el-menu>
</el-aside>在data中定义一个属性用于控制当前是否展开
data: function () {
return {
isCollpase: false
}
},点击按钮后保持激活状态
ElementUI组件对menu提供一个属性default-active,只需要将其设置为需要高刘高亮的index即可。为了记录当前点击的状态,也将其记录在sessionstore中。并且在每次点击按钮时将当前的路径存储带sessionstore中,组件创建时自动取出其值。
<el-menu :default-active="activePath">export default {
created () {
this.getMenuList()
this.activePath = window.sessionStorage.getItem('activePath')
},
data: function () {
return {
activePath: ''
}
},
methods: {
saveNavState (activePath) {
window.sessionStorage.setItem('activePath', activePath)
this.activePath = activePath
}
}
}首页重定向
定义一个新的Welcome组件,并对home设置子路由
import Welcome from '../components/Welcome.vue'
const routes = [
// home页面
{
path: '/home',
component: Home,
redirect: '/welcome',
children: [{ path: '/welcome', component: Welcome }]
}
]为每一个子菜单设置路由,ElementUI提供了路由功能,只需要在menu组件中添加属性router即可。点击子菜单会自动转到当前item的id。因此还需要将子菜单的id改为数据返回的path属性(加/)
首页主要区域
使用的组件同样需要按需导入。
面包屑导航
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>在全局样式中覆盖此组件的样式
.el-breadcrumb {
margin-bottom: 15px;
font-size: 12px;
}卡片视图
<!-- 卡片视图 -->
<el-card>
<!-- 搜搜与添加 -->
<el-row :gutter="20">
<el-col :span="7">
<el-input placeholder="请输入内容">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary">添加用户</el-button>
</el-col>
</el-row>
</el-card>同样的为了美观也在全局样式中覆盖一下样式
.el-card {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15) !important;
}获取数据
export default {
data () {
return {
queryInfo: {
query: '',
pagenum: 1,
pagesize: 2
},
userlist: [],
total: 0
}
},
created () {
this.getUserList()
},
methods: {
async getUserList () {
const { data: res } = await this.$http.get('users', { params: this.queryInfo })
if (res.meta.status !== 200) return this.$message.error('获取用户信息失败')
this.userlist = res.data.users
this.total = res.data.total
}
}
}渲染用户数据
渲染用户数据使用table元素,
<el-table :data="userlist" border stripe>
<!-- 索引列只需要用type属性即可 -->
<el-table-column type="index" label="#"></el-table-column>
<el-table-column label="姓名" prop="username"></el-table-column>
<el-table-column label="邮箱" prop="email"></el-table-column>
<el-table-column label="电话" prop="mobile"></el-table-column>
<el-table-column label="角色" prop="role_name"></el-table-column>
<el-table-column label="状态" prop="mg_state"></el-table-column>
<el-table-column label="操作"></el-table-column>
</el-table>label表格标题
prop表格列的数据源
:data表格数据绑定的数据
border加入边框
stripe隔行变色
改造状态列显示效果
在单元格中添加一个作用域插槽,其属性slot-cope表示接收的属性
<el-table-column label="状态" prop="mg_state">
<template v-slot="scope">
<el-switch v-model="scope.row.mg_state"></el-switch>
</template>
</el-table-column>此时scope相当于当前项。
操作列的改造
基本UI效果以及el-tooltip提示。
<el-table-column label="操作" width="180px">
<template>
<!-- 修改按钮 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
></el-button>
<!-- 删除按钮 -->
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
></el-button>
<!-- 分配角色按钮 -->
<el-tooltip
effect="dark"
content="分配角色"
placement="top"
:enterable="false"
>
<el-button
type="warning"
icon="el-icon-setting"
size="mini"
></el-button>
</el-tooltip>
</template>
</el-table-column>分页
https://element.eleme.cn/#/zh-CN/component/pagination
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[1, 2, 5, 10]"
:page-size="queryInfo.pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>methods: {
// 改变 pagesize 事件
handleSizeChange (newSize) {
this.queryInfo.pagesize = newSize
this.getUserList()
},
// 监听 页码值 改变的事件
handleCurrentChange (newPage) {
this.queryInfo.pagenum = newPage
this.getUserList()
}
}用户状态修改
当switch开关被点击后会触发change事件,因此只需要在事件中请求修改状态的API即可。
<el-switch
@change="userStateChanged(scope.row)"
v-model="scope.row.mg_state"
></el-switch>methods: {
async userStateChanged (userinfo) {
console.log(userinfo)
const { data: res } = await this.$http.put(`users/${userinfo.id}/state/${userinfo.mg_state}`)
if (res.meta.status !== 200) {
userinfo.mg_state = !userinfo.mg_state
console.log(res)
return this.$message.error('更新用户状态失败')
}
this.$message.success('更新用户状态成功')
}
}搜索用户
搜索用户只需要对输入框进行双向数据绑定并提交事件即可。
输入框提供了一个clearable属性,即可以清空输入框,同时触发一个clear事件。
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
clearable
@clear="getUserList"
>
<el-button
slot="append"
icon="el-icon-search"
@click="getUserList"
></el-button>
</el-input>添加用户
对话框
<el-dialog title="提示" :visible.sync="addDialogVisible" width="50%">
<!-- 内容主体 -->
<span>这是一段信息</span>
<!-- 底部区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addDialogVisible = false"
>确 定</el-button
>
</span>
</el-dialog>通过属性addDialogVisible控制对话框的显示与隐藏。
<el-button type="primary" @click="addDialogVisible = true" >添加用户</el-button>绘制提交表单
<el-form
:model="addForm"
:rules="addFormRules"
ref="ruleFormRef"
label-width="70px"
>
<el-form-item label="用户名" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
<el-form-item label="电话" prop="mobile">
<el-input v-model="addForm.mobile"></el-input>
</el-form-item>
</el-form>data () {
return {
addForm: {
username: '',
password: '',
email: '',
mobile: ''
},
addFormRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '请输入3到10位', trigger: 'blur' }],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 15, message: '请输入6到15位', trigger: 'blur' }],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' }],
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' }]
}
}
},自定义验证规则
在data函数中定义函数用于校验规则,然后在验证规则中使用validator关键字调用即可。
data () {
// 自定义规则 - 邮箱
var checkEmail = (rule, value, cb) => {
// 验证邮箱的正则
const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/
if (regEmail.test(value)) {
return cb()
}
cb(new Error('请输入合法的邮箱哦!'))
}
// 自定义规则 - 手机号
var checkMobile = (rule, value, cb) => {
// 验证手机号的正则
const regMobile = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
if (regMobile.test(value)) { return cb() }
cb(new Error('请输入合法的手机号哦!'))
}
return {
// 添加用户表单验证规则
addFormRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '请输入3到10位', trigger: 'blur' }],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 15, message: '请输入6到15位', trigger: 'blur' }],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' }],
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ validator: checkMobile, trigger: 'blur' }]
}
}
},重置表单
当dialog关闭时会触发close事件,因此为其触发函数重置表单即可。
addDialogClosed () {
this.$refs.ruleFormRef.resetFields()
}<el-dialog @close="addDialogClosed"> </el-dialog>提交验证
点击确定会触发提交事件,提交前应该预验证是否符合格式要求。
addUser () {
this.$refs.ruleFormRef.validate(async valid => {
if (!valid) return
console.log(valid)
// 添加网路请求
const { data: res } = await this.$http.post('users', this.addForm)
if (res.meta.status !== 201) {
this.$message.error('添加用户失败')
}
this.$message.success('添加用户成功')
// 因此对话框
this.addDialogVisible = false
// 重新获取列表
this.getUserList()
})
}<el-button type="primary" @click="addUser">确 定</el-button>修改用户
修改用户逻辑与添加用户逻辑大致相同,点击编辑按钮打开对话框,此时应根据ID查询相应的数据并填充到表单中。
// 修改用户并提交
editUserInfo () {
this.$refs.editFormRef.validate(async valid => {
if (!valid) return
// 添加网路请求
const { data: res } = await this.$http.put(`users/${this.editForm.id}`, {
email: this.editForm.email,
mobile: this.editForm.mobile
})
console.log(res)
if (res.meta.status !== 200) {
this.$message.error('修改用户失败')
}
this.$message.success('修改用户成功')
// 隐藏对话框
this.editDialogVisible = false
// 重新获取列表
this.getUserList()
})
},
// 展示编辑用户的对话框
async showEditDialog (val) {
const { data: res } = await this.$http.get(`users/${val.id}`)
if (res.meta.status !== 200) return this.$message.error('查询用户信息失败!')
this.editForm = res.data
this.editDialogVisible = true
console.log(this.editForm)
}<el-dialog
title="编辑用户"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
:rules="editFormRules"
ref="editFormRef"
label-width="70px"
>
<el-form-item label="用户名">
<el-input v-model="editForm.username" disabled></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="editForm.email"></el-input>
</el-form-item>
<el-form-item label="电话" prop="mobile">
<el-input v-model="editForm.mobile"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="editUserInfo">确 定</el-button>
</span>
</el-dialog>删除用户
删除用户应该弹出提示框提示用户是否删除,从而避免误删行为。使用提示框需要全局挂载
import {
MessageBox
} from 'element-ui'
Vue.prototype.$confirm = MessageBox.confirm未删除按钮绑定事件,并将待删除的用户ID传入
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scopeEdit.row.id)"
></el-button>定义事件
// 通过ID删除用户
async removeUserById (id) {
// 弹窗询问是否删除
const confirmResult = await this.$confirm('此操作将永久删除该用户,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'waring'
}).catch(err => err)
// 如果用户确认则返回字符串 - confirm
// 如果用户确认则返回字符串 - cancel
console.log(confirmResult)
if (confirmResult !== 'confirm') {
return this.$message.info('已经取消了删除')
} else {
return this.$message.success('删除了该用户')
}
}点击取消后会抛出错误,因此我们需要用
catch捕获错误并抛出解决报错问题。












