一、概述
基于
Vue
和Element-UI
的电商后台管理系统
1.1 实现功能
-
用户登录/退出
-
用户管理
- 用户列表
- 实现用户的增删改查、分页查询以及分配角色功能
- 用户列表
-
权限管理
- 角色列表
- 实现角色的增删改查以及分配权限、删除权限功能
- 权限列表
- 展示所有权限以及权限等级
- 角色列表
-
商品管理
- 商品列表
- 实现商品的增删改、分页查询
- 分类参数
- 实现分类参数的增删改
- 商品分类
- 实现商品分类的增删改以及查看该分类下的所有子分类
- 商品列表
-
订单管理
- 订单列表
- 实现修改地址
- 实现查看物流进度
- 订单列表
-
数据统计
- 数据报表
1.2 前端技术栈
电商后台管理里系统整体采用前后端分离的开发模式,其中前端是基于
Vue
技术栈的 SPA 项目
- vue
- vue-router
- Element-UI
- Axios
- Echarts
1.3 前端项目初始化步骤
- 安装
Vue
脚手架 - 通过
Vue
脚手架创建项目 - 配置
Vue
路由 - 配置
Element-UI
组件库 - 配置
axios
库 - 初始化
git
远程仓库 - 将本地项目托管到
Gitee
中
二、登录与退出
2.1 登录布局
布局代码
<template>
<div class="login_container">
<!-- 登录区域 -->
<div class="login_box">
<div class="logo_box">
<img src="../assets/logo.png" alt="">
</div>
<!-- 表单区域 -->
<el-form class="login_form" ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0px">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input prefix-icon="el-icon-user" v-model="loginForm.username"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input prefix-icon="el-icon-lock" v-model="loginForm.password" type="password"></el-input>
</el-form-item>
<!-- 按钮 -->
<el-form-item class="btns">
<el-button type="primary" @click="login">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<style lang="less" scoped>
.login_container {
background-color: #2b4b6b;
height: 100%;
}
// 登录部分
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
// 图表盒子
.logo_box {
height: 130px;
width: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
// 表单
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
// 按钮
.btns {
display: flex;
justify-content: flex-end;
}
}
}
</style>
实现页面
2.2 登录业务逻辑
2.2.1 表单预验证
用户在输入账号和密码后,点击登录时表单会进行预验证,判断用户输入的账号和密码是否符合规范,验证通过后向服务器发送
axios
请求
验证规则
// 用户名的验证规则
username: [
{
required: true, message: '请输入用户名称', trigger: 'blur' },
{
min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
// 密码的验证规则
password: [
{
required: true, message: '请输入用户密码', trigger: 'blur' },
{
min: 6, max: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
]
实现效果
2.2.2 关于 Token
在登录成功后,服务器会向我们返回一个
token
,我们需要将这个token
保存到客户端的sessionStorage
中
发送请求并保存 token
<script>
const { data: res } = await this.$http.post('login', this.loginForm)
if (res.meta.status === 200) {
this.$msg.success('登录成功')
window.sessionStorage.setItem('token', res.data.token)
this.$router.push('/home')
} else {
this.$msg.error('用户名或密码输入错误')
}
</script>
注意
- 为什么要保存
token
- 因为项目中除了登录之外的其他 API 接口必须在登录之后才能访问,在访问的时候携带这个
token
就证明我们已经登录了
- 因为项目中除了登录之外的其他 API 接口必须在登录之后才能访问,在访问的时候携带这个
- 为什么要将
token
保存在sessionStorage
中- 因为
sessionStorage
是会话期间的存储机制,关闭浏览器过后,sessionStorage
就会被清空,token
只应在当前网站打开期间生效,所以将token
保存在sessionStorage
中
- 因为
2.2.3 路由导航守卫
如果用户没有登录,但是直接通过 URL 访问特定页面,需要重新导航到登录页面。
在 index.js
中挂载路由导航守卫
// 挂载路由导航守卫
// to 代表将要访问的页面路径,from 代表从哪个页面路径跳转而来,next 代表一个放行的函数
router.beforeEach((to, from, next) => {
// 如果用户访问的是登录页,那么直接放行
if (to.path === '/login') return next()
// 获取 token
const tokenStr = window.sessionStorage.getItem('token')
// 没有 token,强制跳转到登录页面
if (!tokenStr) return next('/login')
next()
})
2.3 退出功能
基于
token
的方式在退出时需要销毁本地的token
。这样,后续的请求就不会携带token
,必须重新登录生成一个新的token
之后才可以访问页面。
退出代码
logout() {
// 销毁本地 token
window.sessionStorage.removeItem('token')
// 通过编程式导航返回到上一页
this.$router.go(-1)
}
三、主页部分
3.1 主页布局
引入
Element-UI
中的Header
Aside
Main
组件
样式代码
<style lang="less" scoped>
.home_container {
height: 100%;
}
// 头部区域
.el-header {
display: flex;
justify-content: space-between;
background-color: #373d41;
.el-button {
align-self: center;
}
}
// 侧边栏区域
.el-aside {
background-color: #333744;
.el-menu {
border-right: none
}
}
// 主题内容区域
.el-main {
background-color: #eaedf1;
}
.toggle-button {
background-color: #4a5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
letter-spacing: 0.2em;
cursor: pointer;
}
</style>
实现效果
3.2 菜单部分
向服务器发送
axios
请求获取菜单数据
注意
- 需要授权的 API 必须在请求头中使用
Authorization
字段提供的token
令牌,那些授权的 API 才能被正常调用 - 如何在每一个的 API 请求头中添加
Authorization
字段- 通过
axios
请求拦截器添加token
,保证拥有获取数据的权限
- 通过
在 main.js
中添加拦截器
// axios 请求拦截
axios.interceptors.request.use(config => {
// 为请求头对象,添加 Token 验证的 Authorization 字段
config.headers.Authorization = window.sessionStorage.getItem('token')
// 最后必须 return config
return config
})
发起请求获取所有菜单数据
<script>
methods: {
// 获取所有菜单数据
async getMenuList() {
const { data: res } = await this.$http.get('menus')
if (res.meta.status !== 200) return this.$msg.error('获取菜单列表失败')
this.menulist = res.data
}
},
created() {
this.getMenuList()
}
</script>
渲染到页面
<el-menu
background-color="#333744"
text-color="#fff"
active-text-color="#409Eff"
<!-- 只保持一个子菜单的展开 -->
unique-opened
<!-- 水平折叠收起菜单 -->
:collapse="isCollapse"
:collapse-transition="false"
router
:default-active="activePath">
<!-- 一级菜单 -->
<!-- index 只接收字符串,所以在后面拼接一个空字符串 -->
<el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id">
<template slot="title">
<i :class="iconObj[item.id]"></i>
<span>{
{ item.authName }}</span>
</template>
<!-- 二级菜单 -->
<el-menu-item :index="'/' + secitem.path" v-for="secitem in item.children" :key="secitem.id" @click="savaNavState('/' + secitem.path)">
<i class="el-icon-menu"></i>
<span>{
{ secitem.authName }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
通过
Element-UI
为菜单名称添加图标
实现效果
3.3 用户管理模块
3.3.1 用户列表
1、渲染用户列表
引入
Element-UI
中的Breadcrumb
,BreadcrumbItem
,Card
,Row
,Col
组件,实现面包屑导航和卡片视图
样式代码
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片区域 -->
<el-card>
</el-card>
<style>
.el-breadcrumb {
margin-bottom: 15px;
font-size: 12px;
}
.el-card {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) !important;
}
</style>
实现效果
向服务器发送请求获取用户数据列表
<script>
data() {
return {
// 查询参数,实现分页
queryInfo: {
// 查询字段
query: '',
// 当前页码
./pagenum: 1,
// 每页显示的条数
./pagesize: 5
}
}
}
methods: {
// 获取用户列表
async getUserList() {
const { data: res } = await this.$http.get('users', { params: this.queryInfo })
if (res.meta.status !== 200) {
return this.$msg.error('获取用户列表失败')
}
this.userTable = res.data.users
this.total = res.data.total
},
// 每页显示条数发生变化触发此事件
handleSizeChange(val) {
this.queryInfo../pagesize = val
this.getUserList()
},
// 页码值发生变化触发此事件
handleCurrentChange(val) {
this.queryInfo../pagenum = val
this.getUserList()
}
},
created() {
this.getUserList()
}
</script>
引入
Table
,TableColumn
将用户数据渲染到表格中,引入Pagination
实现分页效果
实现效果
2、 实现用户的增删改查
- 添加用户
引入
Dialog
结合表单展示一个添加用户的对话框
实现效果
为表单添加验证规则
<script>
data() {
// 自定义校验规则
// 邮箱验证
var checkEmail = (rule, val, cb) => {
const regEmail = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (regEmail.test(val)) return cb()
cb(new Error('请输入合法的邮箱'))
}
//