项目效果展示:

1. 项目概述

1.1 电商项目基本业务概述

一般情况下客户使用的业务服务包括:PC端,小程序,移动web,移动app。

管理员使用的业务服务:PC后台管理端;

  • PC后台管理端的功能

管理用户账号(登录,退出,用户管理,权限管理),商品管理(商品分类,分类参数,商品信息,订单),数据统计;

  • 开发模式

本项目(电商后台管理系统)采用 前、后端分离 的开发模式。

前端项目是基于 Vue 的 SPA(单页应用程序)项目;

  • 本项目技术选型

前端项目技术栈()

  • Vue
  • Vue-Router
  • Element-UI
  • Axios
  • Echarts

后端项目技术栈

  • Node.js
  • Express
  • Jwt(模拟session);
  • Mysql
  • Sequelize(操作数据库的框架)

1.2 电商后台管理系统的功能

电商后台管理系统用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能。

1.3 电商后台管理系统的开发模式(前、后端分离)

电商后台管理系统整体采用前后端分离的开发模式,其中前端项目是 基于 Vue 技术栈的 SPA 项目 。

什么是 前后端分离 的开发模式?

  • 前端:主要 负责绘制页面,同时,基于 Ajax 技术,调用后端提供的 API 接口;
  • 后端:主要负责 操作数据库,并且向前端 暴露 API 接口。

前、后端分离的开发模式,是目前 主流 的 开发模式 。

优点: 开发效率高、项目易于维护。

2. 项目初始化

2.1 前端项目初始化步骤

  1. 安装 Vue 脚手架

    安装 Vue CLI 交互式项目脚手架(一个基于 Vue.js 进行快速开发的完整系统)。

  2. 1)cmd 中执行以下命令安装 Vue CLI 包:

    npm install -g @vue/cli

    2)安装完成后,检查版本是否正确:

    vue --version
    

    Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g 或 yarn global remove vue-cli 卸载它。

Node 版本要求:
Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)。你可以使用 n,nvm 或 nvm-windows 在同一台电脑中管理多个 Node 版本。

2. 通过 Vue 脚手架创建项目(本项目开发采用 “ 可视化面板 ” 创建)

1)cmd 终端中输入 :

vue ui

运行完成后,自动打开如下页面:

2)点击 “ + 创建 ” 进入到目录选择面板,选择目录后。再点击 “ + 在此创建新目录 ”按钮。

填写项目信息:

3)进入“ 预设 ”面板,并按如下图示勾选:

4)点击下一步,进入“ 功能 ”选择面板:

上图中,勾选了 “ 使用配置文件 ” 后,就会将不同的配置 单独 地存放为一个配置文件。点击 下一步,打开 配置 面板。

5)完成如下操作:

6)单击 下一步。提示是否保存新预设(方便下次直接选择该配置 / 也可不保存)。

点击 “保存预设并创建项目” 按钮后,系统自动创建项目如下:

3. 配置 Vue 路由

在上面步骤中已自动配置。

4. 配置 Element-UI 组件库:在插件中安装,搜索vue-cli-plugin-element

1)打开“仪表盘”,单击左侧 “ 插件 ”按钮,再单击右上角的 “ + 添加插件 ”

2)搜索插件 vue-cli-plugin-element 并安装。

3)跳转到 “ 配置插件 ”面板,操作如下:

5. 配置 axios 库:在依赖中安装,搜索axios(运行依赖)

1)点击 左侧边栏 “ 依赖 ” 按钮,再点击右上角 “ 安装依赖 ”

2)安装依赖

6. 初始化 git 远程仓库

7. 将本地项目托管到 Github 或 码云中(方便团队成员协作开发)

码云相关操作

1. 注册登录码云账号,注册地址:https://gitee.com/signup

2. 安装 git

在Windows上使用 Git,可以从Git 官网直接下载安装程序进行安装。

测试:git --version(终端中打印出版本号 即为安装成功 )

3. 点击网站右上角“登录”,登录码云,并进行账号设置

下一步:


下一步:

4. 在本地创建公钥(终端中运行如下命令)

ssh-keygen -t rsa -C "xxx@xxx.com"

 :上述命令(示例)中的 “ xxx@xxx.com” 字样,请务必替换为自己注册 gitee 时的真实邮箱后,再回车执行!

然后回车,接着连敲 3 次回车(中间不需任何操作)即可生成公钥。如图:

5. 找到公钥地址

Your identification has been saved in /c/Users/My/.ssh/id_rsa;
Your public key has been saved in /c/Users/My/.ssh/ id_rsa.pub。

当创建公钥完毕后,请注意打印出来的信息“Your public key has been saved in”

/c/Users/My/.ssh/id_rsa.pub : c盘下面的Users下面的My下面的.ssh下面的 id_rsa.pub 就是创建好的 公钥 了。

6. 用 记事本 或其它编辑器打开id_rsa.pub文件,复制文件中的所有代码:

再点击码云中的 SSH 公钥按钮,将复制好的的公钥粘贴到公钥文本框中。

点击”确定“按钮,再根据提示输入验证密码后,即完成ssh公钥添加:

7.  测试公钥是否添加成功

在完成 gitee 设置中添加公钥后,再 cmd 终端中输入:

ssh -T git@gitee.com

过程中出现如下图所示的询问(是否继续连接?)时,输入 “yes” 回车继续,直到命令执行完成。

首次使用需要确认并添加主机到本机SSH可信列表。若返回 Hi XXX! You’ve successfully authenticated, but Gitee.com does not provide shell access. 内容,则证明添加成功。如下图所示:

再次运行命令ssh -T git@gitee.com,同样也可看到如下所示信息:

8. 将本地代码托管到码云中

点击码云右上角的+号 -> 新建仓库

9. 进行git配置

注:执行如下命令前,必须确保本机已安装过 git,否则终端中会 报错 。

10 项目首次提交

1)检查状态:项目根目录下输入以下命令

git status

运行结果显示项目中存在未跟踪的文件没有被提交 ,如下所示:

此时需要做一下处理,即把所有的文件都添加到 暂存区。

2)添加到 暂存区:运行如下命令:

git add .

注意:命令中的 add 和后面的.(小圆点)中间有个空格,否则会报错。

3)本地提交:将暂存区中的文件提交至 本地仓库中 :

git commit -m "add files"

再次检查状态:

git status

运行结果如下:

提示当前 “ 处于主分支,工作目录是干净的 ”,即没有要提交的文件。

但当前的这些操作只是在本地操作仓库,仓库还没上传到码云中,

4)将本地仓库与远程 git仓库 关联

找到并在终端中运行(你新建的码云仓库 vue_shop 页面最底部提供的那两句)代码,如下所示:

git remote add origin https://gitee.com/XXXXX/vue_shop.git
git push -u origin master

注: XXXX 为你的码云 帐户名称 ( 非邮箱名称)。如果运行 报错,请点击 这里 查看解决办法。

执行第二句命令时,会弹出如下安全验证,输入用户名和密码确认后,等待完成提交即可。

说明:如果是第一次向码云中提交代码,会弹出码云的帐号和密码输入窗口(以后不会再出现)

5)检查是否上传(远程仓库)成功

在远程仓库中点击 “刷新”,即可看到提交信息

类似这样,表示本地仓库已成功上传到了码云中。

2.2 后台项目的环境安装配置

2.2.1 安装 MySQL 数据库

① 安装素材中提供的 phpStudy ,傻瓜式安装。

② 将素材中的压缩包解压,记住解压路径。

③ 运行phpStudy,单击“ MySQL管理器 ” 按钮,选择 MySQL导入导出 菜单项。
④ 按上图所示,找到对应路径下已解压得到的 db 文件夹中的 mydb.sql 数据库脚本文件,点击 “ 导入” 按钮,自动弹出黑色的命令行窗口,开始还原数据库(此时间稍长,请耐心等待~);
温馨提示! 还原结束时,黑色的命令行窗口会自动关闭,此时可按如下所示查看生成的数据库。

如在数据库目录下能够看到如下图所示的路径、文件,表示 数据库还原成功!

注: 由于开发过程中不需要用到 Apache,可将其 “ 停止 ” 服务,如下图所示:

2.2.2 配置后台项目

在前面已经解压出来的 vue_api_server,就是后台 API 项目 。但需要先 安装依赖包 才能正常运行。

A. 安装 nodeJS 环境,配置后台项目

B. 安装项目依赖包

进入 vue_api_server 目录中,shift+右键 在弹出的菜单中选择 “在此处打开 Powershell 窗口 ” 打开终端,输入命令安装项目依赖包:

npm install

C.启动项目

继续在终端中输入如下命令,启动项目:

node .\app.js

注意:启动前,必须先将 phpStudy 的 MySQL 服务开启。

D. 使用 postman 测试 API 接口是否正常。

安装 postman 软件(点此下载),启动 PostMan 填写相关参数(首次使用该软件需进行简单注册),如下所示:

注意:输入登录请求地址、用户字段名、密码字段名时,请务必与 API 文档保持一致;

点击 “ Send ” 后,服务端返回如下信息:

电商管理后台 API 接口文档 下载:https://pan.baidu.com/s/1OGxh05B0BocQm9cP3BWO7w  提取码:

3. 登录 / 退出 功能

3.1 登录概述

1. 登录业务流程

  • 在登录页面输入用户名和密码

  • 调用后台接口进行验证

  • 通过验证之后,根据后台的响应状态跳转到项目主页

2. 登录业务的相关技术点

  • http 是无状态的;

  • 通过 cookie 在客户端记录状态;

  • 通过 session 在服务器端记录状态;

  • 通过 token 方式维持状态(推荐垮域 时采用)

3.2 登录 - token 原理分析

3.3 实现登录功能

  • 一、登录逻辑:

    在登录页面输入账号和密码进行登录,将数据发送给服务器 ==> 服务器返回登录的结果,登录成功则返回数据中带有token ==> 客户端得到 token 并进行保存,后续的请求都需要将此 token 发送给服务器,服务器会验证 token 以保证用户身份。

二、登录状态保持

1)如果服务器和客户端 同源 1,建议可以使用cookie或者session来保持登录状态;

2)如果客户端和服务器 跨域 2,建议使用token进行维持登录状态。

http://www.123.com/index.html 调用 http://www.123.com/abc.do ( 非跨域 )
http://www.123.com/index.html 调用 http://www.456.com/abc.do ( 主域名不同:123/456,跨域 )
http://abc.123.com/index.html 调用 http://def.123.com/server.do ( 子域名不同:abc/def,跨域 )
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.do( 端口不同:8080/8081,跨域 )
http://www.123.com/index.html 调用 https://www.123.com/server.do ( 协议不同:http/https,跨域 )

因合作方域名与我方域名不同,当从合作方加载页面调用我方接口时,会出现跨域的报错。

三、添加新分支 login,在 login分支 中开发当前项目 vue_shop

1)项目根目录中打开 vue_shop 终端(shift + 右键 通过 vs code打开),使用git status命令确定当前项目状态(是否干净)。

git status

运行结果如下所示:

表明当前工作区是干净的,可以进行登录页面的绘制。

—— 此时需要创建一个新分支。

2)确定当前工作目录是干净的之后,创建一个新分支并切换到该分支进行开发,开发完毕之后将其合并到 master

git checkout -b login

注: 在开发中,只要进行一个新功能开发的时候,尽量把它放到一个新分支上,当这个功能开发完毕后,再把它合并到主分支 master 上。

3)然后git branch命令查看新创建的分支,确定我们正在使用 login分支 进行开发。

绿色 表示当前所处的分支。

4)接着,执行vue ui命令打开 ui 界面,然后运行 serve,运行 app 查看当前项目效果。

四、登录页面的布局

点击 “ 启动App ” ,打开项目:

此时,我们可以看到现在它只是一个默认页面,需要把它重置为空白页面:

1)打开项目的 src 目录,点击查看main.js文件(这是整个项目的 入口文件):

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'Vue.config.productionTip = falsenew Vue({// 把 router 路由挂载到实例router,// 通过 render 函数,把 App 根组件渲染到页面上render: h => h(App)
}).$mount('#app')

2)再打开 App.vue (根组件),将根组件的内容进行清理(template 中只留下根节点,script 中留下默认导出,去掉组件,style 中去掉所有样式),清理完成后如下所示:

<template><div id="app"><router-view></router-view></div>
</template><script>
export default {name: 'app'
}
</script><style>
</style>

3)再打开路由文件夹下的 index.js(有些版本的 Vue ui 所创建的工程项目,其 router 文件夹下的路由文件名为router.js),将routes数组中默认的路由规则全部清除,然后将views删除:

import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)export default new Router({routes: []
})

五、新建 Login.vue 组件

1)将文件夹components中的helloworld.vue删除,并新建 Login.vue 单文件组件,添加 template、script、style 标签,style 标签中的 scoped 可以防止组件之间的样式冲突(没有scoped则样式是全局的)。

<template><div class="login_container"></div>
</template><script>
export default {}
</script><style lang="less" scoped>
.login_container {background-color: #2b4b6b;height: 100%;
}</style>

scoped:是 vue 指令,用来控制组件生效的范围(表示只在当前组件内生效,只要是单文件组件,都应加上)

:当添加背景样式并保存代码修改后,浏览器会报错 “ 找不到 less-loader”,如下图所示:

这是由于 Vue 的 cli 工具创建的项目默认并没有安装 less 相关的 loader,如果要使用 less 语法(如<style lang="less" scoped>),此时则需要配置 less加载器(开发依赖),安装 less (开发依赖)。

因此,接下来打开可视化面板安装依赖 less-loader 和 less :

如上图所示,搜索并安装好 less-loader以后,此时浏览器仍然报错,如下所示:

这时,回到安装依赖项界面,再次搜索 less 并安装好(因为 less-loader 依赖于 less)。

此时刷新网页并不能生效(仍然是报错状态),接下来,先关闭网页,停止 server,再次点击 运行 即可。

2)在路由文件 index.js 中导入Login.vue 组件并设置规则;

const router = new Router({routes: [// 当用户访问 /login 这个地址时,通过 component 属性指定要展示的组件 Loging{ path: '/login', component: Login }]
})

3)在 App.vue 中添加路由占位符:

<template><div id="app"><!-- 路由点位符 --><router-view></router-view></div>
</template>

4)由于当前默认访问的是 localhost:8080/#/ (即根路径),需要在其后手动添加 /login才能访问登录组件。

因此为了实现“只要用户访问了/(斜线)根路径,就自动重定向到 /login 地 址”,这里就必须添加一个重定向路由规则。即在路由文件 index.js 文件中添加一句 { path: '/', redirect: '/login' },如下所示:

const router = new Router({routes: [// 重定向路由{ path: '/', redirect: '/login' },// 当用户访问 /login 这个地址时,通过 component 属性指定要展示的组件 Loging{ path: '/login', component: Login }]
})

5)然后需要添加公共样式,在 assets 文件夹下面添加 css文件夹,创建global.css文件,添加全局样式。

/* 全局样式表 */
html,body,#app{width: 100%;height: 100%;margin: 0;padding: 0;
}

6)在入口文件 main.js 中导入 global.css,使得全局样式生效

import "./assets/css/global.css"

7)然后,将 Login.vue 中的根元素也设置为撑满全屏(height:100%

8)在 Login.vue 中绘制登录框

<div class="login_box"></div>

添加样式:

.login_box {width: 450px;height: 300px;background-color: #fff;border-radius: 3px;position: absolute;left: 50%;top: 50%;transform: translate(-50%,-50%); /* 位移,x轴 y轴*/
}

transform 属性,定义 2D 或 3D 转换,进行 旋转、缩放、移动、倾斜。其中,取值为translate(x,y) 是 2D 转换( CSS3 transform 属性的更多详情点击 这里 )。

添加样式后效果如下:

9)绘制顶部的默认头像盒子 avatar_box

<div class="login_box"><div class="avatar_box"><img src="../assets/logo.png" alt=""></div>
</div>

avatar_box 的 css 样式(嵌套到 login_box的样式内):

.avatar_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;}}

box-shadow 属性向盒子添加一个或多个阴影,该属性是由逗号分隔的阴影列表,每个阴影由 2-4 个长度值、可选的颜色值以及可选的 inset 关键词来规定。省略长度的值是 0( 更多详情点击 这里 )。

效果如下:

10)登录页面的布局

通过 Element-UI 实现 页面布局:

  • el-form
  • el-form-item
  • el-input
  • el-button
  • 字体图标

打开官网 element-cn.eleme.io/#/zh-CN 找到组件:

点击顶部导航的 “ 组件 ”,侧边栏中选择 Form 表单,再点击 “ 显示代码 ”:

复制一个 item 项的代码(如下所示),粘贴到 Login 组件中并将不需要用到属性绑定删除、添加结束标签后如下所示:

<el-form label-width="80px"><el-form-item label="活动名称"><el-input ></el-input></el-form-item>
</el-form>

保存后查看页面,发现控制台报错访问不到这几个元素:

这是因为 element-UI 是通过按需导入来使用的,必须先导入才能正常使用。

此时,打开 plugins 文件下的 element.js 文件,导入需要的组件(如果分几次导入可能会报错):

import { Button, Form, FormItem, Input } from 'element-ui'

把 Form 组件 input 输入框代码里不需要的文本 label="活动名称"去掉,再把占位的 label-width="80px"重置为 0,并复制 2 组 el-form-item 元素结构代码(增加一个密码输入框和一个按钮区) :

 <!-- 登录表单区 -->
<el-form label-width="0"><!-- 用户名 --><el-form-item><el-input ></el-input></el-form-item><!-- 密码 --><el-form-item><el-input ></el-input></el-form-item><!-- 按钮区 --><el-form-item></el-form-item>
</el-form>

保存后页面的效果如下:

在 Element 官网 找到 button 组件,复制 button 按钮的代码:

将复制的代码粘贴到按钮区,并给它添加一个btns的类名,以便设置样式,代码如下:

<!-- 登录表单区 -->
<el-form label-width="0"><!-- 用户名 --><el-form-item><el-input ></el-input></el-form-item><!-- 密码 --><el-form-item><el-input ></el-input></el-form-item><!-- 按钮区 --><el-form-item class="btns"><el-button type="primary">登录</el-button><el-button type="info">重置</el-button>
</el-form-item>

由于 <el-button> 按钮默认是靠左对齐,实际需要它 靠右对齐,在 Login.vue 的 <style> 标签内部写上css 样式:

.btns {display: flex;  /* 弹性布局 */justify-content: flex-end; /* 横轴 项目位于容器的尾部*/
}

Flex 弹性布局,可以简便、完整、响应式地实现各种页面布局:通过给父盒子添加flex属性,来控制子盒子的位置和排列方式,点击 >> 查看详情

justify-content 用于设置或检索弹性盒子元素在主轴(横轴)方向上的对齐方式,访问 W3Cschool >>查看详情

注: 当将父盒子设为 flex 布局后,子元素的 float、clear 、 vertical-align 属性将失效。

此时,页面效果如下:

接下来,将整个 Form 表单区域底部对齐,这需要给 <el-form>标签添加一个类名login_form,并添加样式:

.login_form {position: absolute;bottom: 0;width:100%;padding: 0 20px;
}

效果如图:padding: 0 20px 后,撑大了盒子,这是因为form表单的boxsizing 属性值默认为 content-box(即传统盒模型),需将其设置为 border-box(即css3盒模型) :

.login_form {position: absolute;bottom: 0;width:100%;padding: 0 20px;box-sizing: border-box; /* C3盒模型 */
}

box-sizing 属性定义了如何计算一个元素的 总宽度 和 总高度。只要在CSS中加上“box-sizing: border-box;”这句,那么就将一个普通的盒子变成CSS3盒模型,padding 和 border就不会再撑大盒子。详情点击 >> 这里 。

添加后,效果如下所示:

接下来,绘制用户名和密码输入框前面的小图标

在 Element UI 官网 找到 input 组件 菜单项,再找到对应的样式:

将复制的代码粘贴到项目文件 login.vue 中:

 <!-- 用户名 -->
<el-form-item><el-input prefix-icon="el-icon-search"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item><el-input prefix-icon="el-icon-search"></el-input>
</el-form-item>

生效后效果如下:

再从 Element UI 官网 菜单中,点击侧边栏 icon 图标 菜单项,查找是否有对应的用户和密码图标:

再从 Element UI 官网 菜单中,点击侧边栏 icon 图标 菜单项,查找是否有对应的用户和密码图标:

由于在这里我们没有找到所需图标,因此要用到第三方图标库。

这里我们用 阿里图标库 ,将所需字体图标选定后,下载到本地,并在该字体压缩文件解压后,将所得文件夹命名为 fonts ,放到 src \ assets \ 目录下。
接着,在入口文件 main.js 中,导入字体图标的 css 样式表:

// 导入字体图标
import './assets/fonts/iconfont.css'

在浏览器中打开 fonts 文件夹下的 demo_index.html HTML文件,查看使用示例,并将图标分别放置到 Loging.vue 组件文件相应的用户名、密码input标签内,替换原来的放大镜图标:
在浏览器中打开 fonts 文件夹下的 demo_index.html HTML文件,查看使用示例,并将图标分别放置到 Loging.vue 组件文件相应的用户名、密码input标签内,替换原来的放大镜图标:

代码如下:
(注: iconfont 是基础类,不能缺少。icon-xxx 是图标名称)

<!-- 用户名 -->
<el-form-item><el-input prefix-icon="iconfont icon-user"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item><el-input prefix-icon="iconfont icon-3702mima"></el-input>
</el-form-item>

替换原图标类名并保存,此时登录框 UI 效果如下:

11)登录表单的数据绑定(把用户名和密码对应的值自动绑定到数据源上):

打开 Element UI 官网,找到 Form 表单的定义,在 典型表单 中展开代码结构,能看到第一行 <el-form> 上,有个属性绑定 :model就代表数据绑定:

即,示例中表示的是 <el-form> 表单中填写的所有数据,都会自动同步到 :model指向的 "form"对象上,form 是个数据对象,其定义如下所示:

归纳:表单添加数据绑定的步骤如下:

1)第一步:先给 <el-form> 添加 :model 属性进行数据绑定,指向一个数据对象;

2)第二步:为每个表单项里的文本输入框,通过v-model绑定到数据对象上对应的属性中。

实现如下

1)打开 Login.vue 文件,通过:model 绑定一个新命名的数据对象 loginForm ,代码如下:

<!-- 登录表单区 -->
<el-form :model="loginForm" label-width="0" class="login_form"><!-- 省略不相关代码-->
</el-form>

2)接下来,在 script 标签所在的行为区域中,定义 loginForm 这个数据对象:

<script>
export default {data () {return {// 登录表单的数据绑定对象loginForm: {username: 'admin',password: '123456'}}}
}
</script>

3)通过v-model 将 loginForm 里的对象 username 和 password ,分别双向绑定到用户名和密码输入框上,代码如下:

<!-- 登录表单区 -->
<el-form :model="loginForm" label-width="0" class="login_form"><!-- 用户名 --><el-form-item><el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input></el-form-item><!-- 密码 --><el-form-item><el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></el-input></el-form-item><!-- 此处省略按钮区代码-->
<el-form>

此时,实现效果如下图:

再为密码输入框添加一个type属性,属性值设为 password,以使密码以 * 号显示,保证用户账户的安全性。

<el-input type="password" v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></el-input>

12)实现表单的数据验证

目标:当填写完用户名和密码,只要鼠标一离开文本框,就会立即对填写的数据进行合法性的校验。要如何添加数据验证行为呢?

方法:打开 Element UI 官网, Form表单,找到该组件页面后面的表单验证:

分析: 如上图所示展开的结构代码,解读如下,

首先,要为<el-form>组件,进行属性绑定,绑定rules 属性,其属性值是一个表单验证规则对象;

其次,该验证规则对象要在<script> 标签行为区域中的 data 数据里进行定义,其中,每一个属性都是一个验证规则。

最后,为不同的表单 item 项,通过 prop 属性来指定不同的验证规则,来进行表单的验证。

具体实现:

1)打开登录组件 login.vue ,在 <el-form>元素上通过:rules 绑定 loginFormRules这个新的验证规则对象:

 <!-- 登录表单区 -->
<el-form :model="loginForm" :rules="loginFormRules" label-width="0" class="login_form">

2)复制 loginFormRules 对象名称,到 login.vue文件的 script 行为区域中的 data 中进行定义:

 // 表单的验证规则对象
loginFormRules: {// 验证用户名是否合法username: [],// 验证密码是否合法password: []
}

验证规则可直接到 Element UI 官网中进行复制:

将复制过来的代码里的提示文字做适当修改,整理后代码如下:

// 表单的验证规则对象
loginFormRules: {// 验证用户名是否合法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' }]
}

3)将定义的规则应用到表单中

分别在用户名和密码输入框的 <el-form-item>元素标签中,添加 prop 属性,并指向username 和 password

<!-- 用户名 -->
<el-form-item prop="username"><el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input>
</el-form-item><!-- 密码 -->
<el-form-item prop="password"><el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima" type="password"></el-input>
</el-form-item>

此时完成效果如下图:

13)实现表单的重置功能

目标: 当点击 “ 重置 ”按钮时,能够重置校验结果

如何实现 ? 查看 Element UI 官方文档,在Form组件对应的页面中,底部提供了一个方法:

只要我们获取到了表单的实例对象,就可通过实例对象直接访问(调用) resetFields 函数。从而重置整个表单,将所有字段值重置为初始值并移除校验结果。

如何拿到表单的实例对象 ?

分析:

① 要拿到 <el-form>组件的实例对象,需要给它添加一个ref 引用,引用名称我们定义为 loginFormRef;

② 接下来,只要能够获取到 loginFormRef,就能拿到 el-form 表单的实例对象,即 loginFormRef 就是表单的实例对象,可以直接通过它来调用 resetFields 函数,以重置表单。

实现过程

① 为组件添加 ref 引用:

<!-- 登录表单区 -->
<el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0" class="login_form">

② 通过@click为 重置 按钮绑定 单击事件 resetLoginForm :

<!-- 按钮区 -->
<el-form-item class="btns"><el-button type="primary">登录</el-button><el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>

③ 在 script 的export default 中定义* resetLoginForm 方法,先打印出 this ,查看其具体指向。

 methods: {// 点击重置按钮,重置登录表单resetLoginForm () {console.log(this)}
}

从打印结果可以看到,this 指向的是一个组件的实例对象 VueComponent{...},展开该对象下,可见一个数据对象 $refs: {loginFormRef: VueComponent},其中的一个属性 loginFormRef 就是前面为 el-form 定义的 ref 引用名称,如下图所示:

由此可知,通过 this.$refs 可以直接获取到 resetLoginForm 这个引用对象,该引用对象就是 el-form 的实例。代码及效果如下:

 methods: {// 点击重置按钮,重置登录表单resetLoginForm () {// console.log(this)this.$refs.loginFormRef.resetFields()}
}

这样,就实现了当点击 “ 重置 ”按钮时,重置校验结果的功能。而文本框好像没有被清空是什么原因呢 ?其实这是一种错觉。那是因为文本框是双向绑定到 data 中的数据中,而该数据是设置了默认值的,重置后,就又把默认值写进了已清空的文本框。

13)登录前的预校验

当我们点击登录按钮时,不应该直接发起数据请求,而是在请求之前先对表单数据进行预验证,验证通过时,才允许发起网络请求。否则,应该直接提示用户表单数据 不合法 。

实现思路: 当点击登录时,通过调用表单的某个函数来验证。具体调用哪个函数呢 ? 同样是在 Element UI 官网中,找到 组件 => Form表单 => Form Methods 节点的 validate函数:

Function(callback: Function(boolean, object))

该函数接收一个 callback 回调函数,回调函数的第 1 个行参是布尔值代表校验的结果。如果校验通过,则返回 true;反之,则返回false

如何调用 validate 函数 ?

调用方法:

① 通过@click 给 登录 按钮绑定单击事件(事件处理函数命名为 login);

<el-button type="primary" @click="login">登录</el-button>

② 通过 ref 获取到表单的引用对象,再用该引用对象调用 validate 函数:

login () {this.$refs.loginFormRef.validate(valid => {console.log(valid)   // 测试能否获取验证结果})

接着,判断验证结果,决定是否发起登录请求。

login () {
this.$refs.loginFormRef.validate(valid => {// console.log(valid)if (!valid) return false})
}

注意! 由于此时项目中还没有 全局配置 axios 包,无法发起请求,因此,要先在入口文件 main.js 中对 axios 进行全局配置:

① 导入 axios 包:

import axios from 'axios'

② 把包挂载到 Vue 原型对象上:

Vue.prototype.$http = axios

这样,每个 Vue 的组件都可以通过 this 直接访问到 $http,从而发起 ajax 请求 。

③ 当挂载为原型的属性之后,回头再为 ajax 设置请求的根路径:

axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'

这个根路径在项目的 API 文档中能找到

此 3 个步骤的完整代码如下:

import axios from 'axios'
// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
Vue.prototype.$http = axios

配置完 ajax ,现在回到 Login.vue 登录组件中,通过 this 就可以访问到原型上的 $http 成员,从而通过它发起 Ajax 请求 (请求地址 login,请求方式 post):

login () {
this.$refs.loginFormRef.validate(valid => {// console.log(valid)if (!valid) return falseconst result = this.$http.post('login', this.loginForm)  // eslint-disable-line no-unused-varsconsole.log(result)})
}

注解: 在 data 中,有个 loginFrom,也就是登录表单的数据绑定对象,由于用户在el-form表单中填写的数据都会自动同步到该对象。因此可以直接将这个 loginFrom 当做请求参数。

接下来,启动 MySQL 数据库服务~

现在,可以测试一下:在登录表单中随便填入用户名和密码,点击 登录 ,看看请求结果 result 输出了什么,如下图所示:

这样,就实现了当点击 “ 重置 ”按钮时,重置校验结果的功能。而文本框好像没有被清空是什么原因呢 ?其实这是一种错觉。那是因为文本框是双向绑定到 data 中的数据中,而该数据是设置了默认值的,重置后,就又把默认值写进了已清空的文本框。

可以看到在控制台输出的是 Promise 对象,我们知道,如果某个方法的返回值是 Promise,那么就可以用 asyncawait 来简化这次 Promise 操作,因此前面的代码可以写成:

login () {this.$refs.loginFormRef.validate(async valid => {// console.log(valid)if (!valid) return falseconst result = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-varsconsole.log(result)})
}

注: 如果仅添加 await 会报错:‘await’ is only allowed within async functions。这是因为 await 只能用在被 async 修饰的方法中(这里就是把箭头函数 valid 修饰成 异步 的函数)。

修改完毕再次打印时,可发现 result 已不再是一个 Promise 对象,而是一个具体的响应对象,对象中包含了 6 个属性,都是 axios 帮我们封装好的,其中,data 才是服务器返回的真实数据(其它 5 个属性我们不需要),如下图所示:

此时,我们可以从 result 对象身上的 data 属性给解构赋值出来并重命名为 res ,这样就表示能访问到真实的数据了:

const {data: res} = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars
console.log(res)

随意输入用户名、密码,点击 登录 按钮后,控制台打印如下:


现在,我们来对登录状态进行判断:

login () {this.$refs.loginFormRef.validate(async valid => {// console.log(valid)if (!valid) return false  // 阻止登录请求const { data: res } = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars// console.log(res)if (res.meta.status !== 200) return alert('登录失败!')alert('登录成功!')})
}

页面测试如下:

接着,借助 Element UI 的 Message 弹出层,给登录添加登录成功与否的消息提示,这样显得更加友好,如下所示:

消息组件的使用

1)在 element.js中导入 Message 组件:

import { Button, Form, FormItem, Input, Message } from 'element-ui'
// Vue.use(Message)  //  错误的写法

注意:Message的配置和其它组件不一样,它需要进行全局挂载,即把 Message 挂载为 Vue 原型上的一个属性;

2)配置 Message :

// import { Button, Form, FormItem, Input, Message } from 'element-ui'
Vue.prototype.$Message = Message

注: 这里的 $Message 是我们的自定义属性名,可取任意合法名。“=” 后面的 Message 是组件名,必须按这个拼写来写(即,把弹框组件挂载到了原型对象上,这样的话,每一个组件都可以通过 this 来访问到 $Message进行弹窗提示! )。

3) Message 的使用

// if (res.meta.status !== 200) return alert('登录失败!')
// alert('登录成功!')
// 修改为
if (res.meta.status !== 200) return this.$message.error('登录失败!')
this.$message.success('登录成功!')

此时,效果如下图所示:

最终,完整的 Login.vue 组件代码,如下所示:

<template><div class="login_container"><!-- 登录盒子  --><div class="login_box"><!-- 头像 --><div class="avatar_box"><img src="../assets/logo.png" alt=""></div><!-- 登录表单 --><el-form :model="loginForm" ref="LoginFormRef" :rules="loginFormRules" label-width="0px" class="login_form"><!-- 用户名 --><el-form-item prop="username"><el-input v-model="loginForm.username" prefix-icon="iconfont icon-user" ></el-input></el-form-item> <!-- 密码 --><el-form-item prop="password"><el-input type="password" v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></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><script>
export default {data() {return {//数据绑定loginForm: {username: 'admin',password: '123456'},//表单验证规则loginFormRules: {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'}]}}
},//添加行为,
methods: {//添加表单重置方法resetLoginForm() {// this=>当前组件对象,其中的属性$refs包含了设置的表单ref// console.log(this)this.$refs.LoginFormRef.resetFields()},login() {//点击登录的时候先调用validate方法验证表单内容是否有误this.$refs.LoginFormRef.validate(async valid => {console.log(this.loginFormRules)//如果valid参数为true则验证通过if (!valid) {return}//发送请求进行登录const { data: res } = await this.$http.post('login', this.loginForm)//  console.log(res);if (res.meta.status !== 200) {//console.log("登录失败:"+res.meta.msg)return this.$message.error('登录失败:' + res.meta.msg)}this.$message.success('登录成功')console.log(res)//保存tokenwindow.sessionStorage.setItem('token', res.data.token)// 导航至/homethis.$router.push('/home')})}}
}
</script><style lang="less" scoped>.login_container {background-color: #2b5b6b;height: 100%;
}.login_box {width: 450px;height: 300px;background: #fff;border-radius: 3px;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);.avatar_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>

其中,有用到以下内容,需要进行进一步处理:

Ⅰ. 添加 element-ui 的表单组件

在 plugins 文件夹中打开 element.js 文件,进行 elementui 的按需导入:

import Vue from 'vue'
import { Button } from 'element-ui'
import { Form, FormItem } from 'element-ui'
import { Input } from 'element-ui'Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)

Ⅱ. 添加第三方字体

复制素材中的 fonts 文件夹到 assets 中,在入口文件 main.js 中导入:

import './assets/fonts/iconfont.css'

然后直接 <el-input prefix-icon="iconfont icon-3702mima"></el-input>

接着添加登录盒子

Ⅲ. 添加表单验证的步骤

1)给<el-form>添加属性:rules="rules",rules 是一堆验证规则,定义在 script 中。

2)在 script 中添加 rules:export default{ data(){return{…, rules: {

name: [{ required: true, message: '请输入活动名称', trigger: 'blur' },{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }],region: [{ required: true, message: '请选择活动区域', trigger: 'change' }]

3)通过<el-form-item>的 prop 属性设置验证规则<el-form-item label="活动名称" prop="name">

4)导入axios 以发送 ajax 请求

打开main.js,导入 axios:

import axios from 'axios';

设置请求的根路径:

axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/';

挂载axios:

Vue.prototype.$http = axios;

5)配置弹窗提示

在 plugins 文件夹中打开 element.js 文件,进行 elementui 的按需导入:

import {Message} from 'element-ui'

进行全局挂载:

Vue.prototype.$message = Message;

在 login.vue 组件中编写弹窗代码:

this.$message.error('登录失败')

六、登录成功之后的操作

实现思路:

1.将登录成功之后的 token ,保存到客户端的 sessionStorage 中;

原因如下:

  • 项目中除了 登录 以外的其它 API 接口 ,必须在登录之后才能访问(访问接口时提供 token,表明已登录);
  • token 只应在当前网站打开期间生效,所以将 token 保存在 sessionStorage 中。
  • 之所以保存在 sessionStorage 而不是 localStorage 中,是因为localStorage是持久化的存贮机制,而sessionStorage是会话期间的存贮机制。

2.通过 编程式导航 跳转到后台主页,路由地址是 /home (编程式导航:通过 $router对象,调用 push 方法来发生跳转)。

代码实现

A. 保持用户token信息

要保存 token ,先要能访问到它:

console.log(res)

可以看到 res 上有个 data 属性,data 属性中包含 token字符串,可以调用sessionStorage.setItem这个 API 接口,将这个 token 保存在 sessionStorage 中:

window.sessionStorage.setItem('token', res.data.token)

括号中的参数是键值对的形式(键:token ,值:res.data.token)。

因此,当登录成功之后,我们将后台返回的 token 保存到 sessionStorage 中,操作完毕之后,需要跳转到/home目录:

this.$router.push('/home')

此时 login 方法的代码如下:

login () {this.$refs.loginFormRef.validate(async valid => {// console.log(valid)if (!valid) return falseconst { data: res } = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars// console.log(res)if (res.meta.status !== 200) return this.$message.error('登录失败!')this.$message.success('登录成功!')// console.log(res)window.sessionStorage.setItem('token', res.data.token)this.$router.push('/home')})
}

调试运行程序,当点击登录后,Application 里的 session Storage 中保存到了 token 值。同时,通过这次 编程式导航,发生了页面跳转。如下所示:


但此时的 home 页面是空白的。

创建 home 页面并完善路由规则

1)创建 home 页面:在 components 目录中,新建一个 Home.vue 组件文件;


修改完毕再次打印时,可发现 result 已不再是一个 Promise 对象,而是一个具体的响应对象,对象中包含了 6 个属性,都是 axios 帮我们封装好的,其中,data 才是服务器返回的真实数据(其它 5 个属性我们不需要),如下图所示:

2)用 template 标签书写好基本结构代码:

<template><div>Home 组件</div>
</template><script>
export default {}
</script><style lang="less" scoped>
</style>

3)在路由文件夹下的index.js中导入组件并添加 路由规则:

import Home from '../components/Home.vue'export default new VueRouter({routes: [{ path: '/', redirect: '/login' },{ path: '/login', component: Login },{ path: '/home', component: Home }]
})

路由导航卫士控制页面访问权限

当前“ /home ” 所对应的页面只有在登录的时候才允许被访问。

因此,如果用户未登录,直接通过 URL 访问特定(需要权限)的页面,则需要重新导航(即强制跳转 )到登录页面。

那么,如何进行导航 ?

这就需要用到路由 导航守卫:为路由对象 router 调用一个 beforeEach 函数,这个 beforeEach 就叫做 导航守卫 。

router.beforeEach ((to, from, next) => { })

该函数接收一个 回调函数,包括 3 个行参,分别为tofromnext,其中:

  • to:将要访问的路径;
  • from:从哪个页面跳转过来;
  • next:放行的一个函数,next()表示直接放行;next('/login')表示强制跳转。

路由导航卫士的用法

判断 to 对应的地址是否为/login

  • 如果是,则表示用户要访问登录页,而登录页是不需要权限的,这时就直接调用 next 函数(即 return next())放行,允许访问登录页;
  • 如果不是,则要判断 sessionStorage 中是否存在 token。

先把 token 取出来,然后判断是否有 token ,如果没有 token,证明用户没有登录,此时需要强制跳转到登录页(/login),让用户登录后再进行访问。如果有 token 则直接放行(next ())

代码实现(导航跳转):

打开路由所对应的文件(有些版本的 Vue ui 生成的路由文件名是 router.js),我这里是 index.js,我们在前面已完成的代码:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/Login.vue'
import Home from '../components/Home.vue'Vue.use(VueRouter)export default new VueRouter({routes: [{ path: '/', redirect: '/login' },{ path: '/login', component: Login },{ path: '/home', component: Home }]
})

这里需要对代码进行改造,当前export default new VueRouter({}) 表示是直接 new 了一个 VueRouter 对象并默认导出。
此时需要将 export default 和 VueRouter拆分开,先拿到 VueRouter 对象,给它挂载一个 导航守卫,然后再用 export default 暴露出去。修改如下:

const router = new VueRouter({routes: [{ path: '/', redirect: '/login' },{ path: '/login', component: Login },{ path: '/home', component: Home }]
})// 暴露路由对象之前,要挂载路由守卫
router.beforeEach((to, from, next) => {if (to.push === '/login') return next()// 获取 tokenconst tokenStr = window.sessionStorage.getItem('token')if (!tokenStr) return next('/login')next()
})export default router

3.4 实现退出功能

1、实现原理

基于 token 的方式实现 退出 比较简单,只需要 销毁本地的 token 即可。这样,后续的请求就不会携带 token ,必须重新登录生成一个新的 token 之后才可以访问页面。

2、功能实现

在 Home 组件中添加一个退出功能按钮,给 退出按钮 添加 点击事件,添加事件处理代码如下:

export default {methods:{logout(){// 清空tokenwindow.sessionStorage.clear();// 跳转到登录页this.$router.push('/login');}}
}

处理 ESLint 警告

补充

A、处理 ESLint 警告

打开脚手架面板,查看 警告 信息

默认情况下,ESLint 和 VS code 格式化工具有 冲突,需要添加配置文件解决冲突。

1)在项目根目录新建 .prettierrc.json 文件(注意prettierrc前面有小点):

{"semi":false,  // 结尾处的分号(;)"singleQuote":true  // 单引号
}

2)打开.eslintrc.js文件,禁用对 space-before-function-paren 的检查:

rules: {'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off','no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off','space-before-function-paren' : 0},

3)*其它方法(注:此方法来源于网络,未亲测!仅记录于此供参考)

如果在使用 vue-cli 创建项目时已经选择了 babeleslint,那么只需要安装缺少的包:

npm i prettier prettier-eslint --save-dev

这样也能得到正确的格式,其原理是先把代码用 prettier 格式化,然后再用 ESLint fix。

B、合并按需导入的 element-ui

import Vue from 'vue'
import { Button, Form, FormItem, Input, Message } from 'element-ui'Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
// 进行全局挂载:
Vue.prototype.$message = Message

C.将代码提交到码云

① 新建一个项目终端,输入命令查看修改过的或新增的文件内容:

git status

② 将所有文件添加到暂存区:

git add.

此时,所有文件变成绿色,表示已经都添加到了暂存 区

③ 将所有代码提交到本地仓库:

git commit -m "添加登录功能以及/home的基本结构"

④ 查看所处分支(所有代码都被提交到了 login 分支):

git branch

⑤ 将 login 分支代码合并到 master 主分支:

a、先切换到 master:

git checkout master

b、再 master 分支进行代码合并:

git merge login

⑥ 将本地最新的 master 推送到远端的码云:

git push

打开码云中的仓库如下图(表示已把本地的master 分支推送到了云端仓库中进行了保存)。

⑦ 推送本地的子分支到码云

如下图所示,当前云端中只有一个 master 分支,并没有 login 子分支:

因此还需要将 login 分支推送到云端:

a. 先切换到子分支:

git checkout 分支名

b. 然后推送到码云:

此时如果直接用 git push 推送是不会成功的,因为云端并没有记录 login 子分支。

由于是首次把 login 分支推送到云端分支,此时,需要在 push 加上一个参数 -u,完整命令如下:

git push -u origin 远端分支名

刷新后,即可看到仓库中多了一个 login 子分支:

关于推送

我们写的源代码,经过测试之后没问题,一定要先合并到主分支,然后再将主分支推送到云端仓库中,同时,也不要忘了再把新建的子分支推送到云端仓库中。

4. 主页布局

  • 实现后台首页的基本布局
  • 实现左侧菜单栏
  • 实现用户列表展示
  • 实现添加用户

4.1 后台首页基本布局

整体布局: 先上下划分,再左右划分。

借助 Element UI 中的布局容器进行布局:

  1. 进入官网 ,找到 Container 布局容器组件:

  2. 找到符合主页设计图的样式:

  3. 找到该布局结构的代码,展开后复制粘贴到项目中

基本布局:打开 Home.vue 组件

  • 原来 Home.vue 文件中的结构代码:

    <template><div><el-button type="info" @click="logout">退出</el-button></div>
    </template>
    

    修改为官方提供的布局容器的结构:

    <template><el-container><!-- 头部区域 --><el-header>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>
    </template>
    

    保存后,打开页面并没有看到想要的效果,并且终端里有报错,如下图所示:

    这是因为我们还没有注册 el-container 这些组件。

    在plugins 目录下的 element.js 中导入组件:

    保存后,页面效果如下图:

    此时,虽然很丑,但是已初具雏形,并且也解决了报错问题。

    接下来,在 Home.vue 文件中的 <style>标签内部添加css样式。

    默认情况下,类似 element-ui 组件的名称就是 类名,利用这个类名可直接给对应的组件添加样式 。

    .home-container {height: 100%;
    }
    .el-header{background-color:#373D41;
    }
    .el-aside{background-color:#333744;
    }
    .el-main{background-color:#eaedf1;
    }
    

    保存并刷新页面后如下图:

    接下来要解决一个问题,就是让整个页面主体区域撑满屏幕。需要先检查元素看是什么原因导致的

    然后你可以看到,section 元素其实就是布局容器组件结构代码中最外层的 <el-container>,只要让它全屏,就能实现页面的布局效果。

    给 Home.vue 里的<el-container> 添加一个类名home-container并设置高为100%:

    <el-container class="home-container">
    
    .home-container {height:100%
    }
    

    这样就实现了充满整个屏幕的效果,如下图所示:

4.2 顶部布局,侧边栏布局

4.2.1. 顶部布局

1)HTML:顶部原来的结构:

<!-- Home.vue 头部区域 -->
<el-header>Header<el-button type="info" @click="logout">退出</el-button></el-header>

HTML:修改后的结构为:

<!-- Home.vue 头部区域 -->
<el-header><div><img src="../assets/heima.png" alt=""><span>电商后台管理系统</span></div><el-button type="info" @click="logout">退出</el-button>
</el-header>

2). 添加CSS样式

CSS:顶部原来的样式:

.el-header{background-color:#373D41;
}

CSS:顶部修改后的样式:

.el-header{background-color:#373D41;display:flex;justify-content: space-between;padding-left: 10px;align-items: center;font-size: 20px;color:#f1d277;div {display: flex;align-items: center;}span {margin-left: 15px;user-select:none;}
}

【注】 align-items: center :纵向上居中对齐;justify-content: space-between:横向上两端对齐;user-select:none:文本禁止被选中。

效果如下图:

4.2.2 侧边栏菜单布局

菜单分为二级,并且可以折叠。

主要结构如下:

<el-menu><el-submenu><!-- 这个 template 是一级菜单的内容模板 --><i class="el-icon-menu"></i><span>一级菜单</span><!-- 在一级菜单中,可以嵌套二级菜单 --><el-menu-item><i class="el-icon-menu"></i><span slot="title">二级菜单</span></el-menu-item></el-submenu>
</el-menu>

最外层的<el-menu>是一个包裹性质的容器,整个菜单项最外层必须用<el-menu>进行包裹。一级菜单项中,用 <i>指定图标项,<span>指定一级菜单文本。二级菜单为 <el-menu-item>,也有图标和文本。

在 element UI 官网找到导航菜单组件:

点击 显示代码 ,找到 “ 自定义颜色 ” 菜单对应的代码:

选中所有的UI结构后,粘贴到Home.vue中的“侧边栏区域”。

侧边栏原代码:

 <!-- 侧边栏 -->
<el-aside width="200px">Aside</el-aside>

删除 标签中的 “ Aside ” 文本,并复制 官网中的 UI 结构代码粘贴进来,同时,在将不需要的属性删掉后,侧边栏代码如下:

<!-- Home.vue 文件 -->
<!-- 侧边栏 -->
<el-aside width="200px"><!-- 侧边栏菜单区域 --><el-menu  background-color="#545c64" 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-group><template slot="title">分组一</template><el-menu-item index="1-1">选项1</el-menu-item><el-menu-item index="1-2">选项2</el-menu-item></el-menu-item-group><el-menu-item-group title="分组2"><el-menu-item index="1-3">选项3</el-menu-item></el-menu-item-group><el-submenu index="1-4"><template slot="title">选项4</template><el-menu-item index="1-4-1">选项1</el-menu-item></el-submenu></el-submenu><el-menu-item index="2"><i class="el-icon-menu"></i><span slot="title">导航二</span></el-menu-item><el-menu-item index="3" disabled><i class="el-icon-document"></i><span slot="title">导航三</span></el-menu-item><el-menu-item index="4"><i class="el-icon-setting"></i><span slot="title">导航四</span></el-menu-item></el-menu>
</el-aside>

删掉的 <el-menu>中不需要的属性,包括:default-active="2"class="el-menu-vertical-demo"@open="handleOpen" 和 @close="handleClose" 。

此时,由于侧边栏中用到的组件还没有 “ 按需 ” 导入,接下来打开plugins路径下的 element.js文件:

导入并注册以下组件:

  • Menu
  • Submenu
  • MenuItemGroup(菜单分组项);
  • MenuItem

代码如下:

// element.js 文件
import { Menu, Submenu, MenuItemGroup, MenuItem } from 'element-ui'
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItemGroup)
Vue.use(MenuItem)

此时,实现的效果如下:

但是,当前侧边栏的颜色和整个侧边栏的颜色不一致,<el-menu>标签的background-color属性的值需要修改#333744

<el-menu  background-color="#333744" text-color="#fff" active-text-color="#ffd04b">

修改后的效果:

这个官方提供的侧边栏,菜单中默认有禁用、三级菜单等,而本项目只需要一级、二级菜单,多余的不需要。因此,需再次梳理,将不需要的去除。

  1. 将导航二、三、四的代码全部删除后,结构如下:

    <!-- 侧边栏 -->
    <el-aside width="200px"><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">选项1</el-menu-item></el-submenu></el-menu>
    </el-aside>
    

    如下图:

    但是,此时二级菜单没有图标,需要将一级菜单中的 <i>标签和<span>标签复制后,粘贴到二级菜单 <el-menu-item>标签内:

4.3 通过接口获取菜单数据

  1.  axios请求拦截器

后台除了登录接口之外,都需要 token 权限验证,通过添加 axios请求拦截器来添加 token,以保证拥有获取数据的权限。

在 main.js 中添加代码,在将 axios 挂载到 vue 原型 之前添加相应代码:

打开 入口文件 main.js ,找axios配置节点:

// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
Vue.prototype.$http = axios

在挂载到原型对象之前,先为它设置拦截器:

// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
axios.interceptors.request.use(config => {console.log(config)return config
})
Vue.prototype.$http = axios

config 即是请求对象,里面包含了很多的属性。通过打印,可以看到它包含了请求头headers

接口说明:

根据 API 接口说明,需要为请求对象挂载一个Authorization字段,但是目前并没有 headers字段,需要手动为其添加,其值为已经保存在SessionStorage里的token字符串。

//请求在到达服务器之前,先会调用use中的这个回调函数来添加请求头信息
// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
axios.interceptors.request.use(config => {// console.log(config)config.headers.Author = window.sessionStorage.getItem('token')// 最后必须 return configreturn config
})
Vue.prototype.$http = axios

Authorization字段添加成功,只是它的值是null。原因是我们发起的是 “登录” 请求,登录期间服务器还没有颁发令牌。如果是登录后,调用其它接口时,再次监听这次请求,就会发现Authorization的值是一个真正的 token 令牌。

这样,就为每一次的 API 请求,挂载了Authorization请求头,对于有权限要求的 API ,就能成功调用了。

注释:
request 就是请求拦截器,为请求拦截器挂载一个回调函数,只要用户通过axios向服务器端发送了数据请求,就必然会在请求期间,优先调用 use 回调函数,在请求到达服务器之前,对请求数据做预处理。

return config —— 最后必须写上这句,代表已经把请求头做了一次预处理(固定写法)。

请求拦截器相当于一个预处理的过程。

此时,main.js文件完整代码如下:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
// 导入全局样式表
import './assets/css/global.css'
// 导入字体图标
import './assets/fonts/iconfont.css'import axios from 'axios'
// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
axios.interceptors.request.use(config => {// console.log(config)config.headers.Authorization = window.sessionStorage.getItem('token')// 最后必须 return config,固定写法return config
})
Vue.prototype.$http = axios
Vue.config.productionTip = falsenew Vue({router,render: h => h(App)
}).$mount('#app')

发起请求,获取左侧导航菜单

API 接口:

  • 请求路径:menus
  • 请求方法:get
  • 响应数据:

1)打开Home.vue文件,添加相关代码

分析: 网页刚一加载时,就应立即获取左侧菜单,因此需在行为区域定义一个生命周期函数:

添加前:

<script>
export default {methods: {logout () {window.sessionStorage.clear()this.$router.push('/login')}}
}
</script>

添加后:

<script>
export default {created() {this.getMenuList()},methods: {logout () {window.sessionStorage.clear()this.$router.push('/login')},// 获取所有菜单async getMenuList () {const { data: res } = await this.$http.get('menus')console.log(res)}}
}
</script>

res控制台打印结果如下:

如上图,得到的是一个对象,显示 “获取菜单列表成功”。共5个一级菜单,而且在一级菜单中,通过children属性嵌套了自己的二级菜单。

为了下一步在页面中渲染出来,应把获取到的数据立即挂载到自己的 data 中,需定义一个组件的私有数据 data

data () {return {// 左侧菜单数据menulist: []}}

判断:

// 获取所有菜单
async getMenuList () {const { data: res } = await this.$http.get('menus')if (res.meta.status !== 200) return this.$message.error(res.meta.msg)this.menulist = res.dataconsole.log(res)
}

4.4 动态渲染菜单数据并进行路由控制

A、请求侧边栏数据

<script>
export default {data() {return {// 左侧菜单数据menuList: null}},created() {// 在created阶段请求左侧菜单数据this.getMenuList()},methods: {logout() {window.sessionStorage.clear()this.$router.push('/login')},async getMenuList() {// 发送请求获取左侧菜单数据const { data: res } = await this.$http.get('menus')if (res.meta.status !== 200) return this.$message.error(res.meta.msg)this.menuList = res.dataconsole.log(res)}}
}
</script

B、通过v-for双重循环渲染左侧菜单

如上图所示,由于左侧菜单数据的menuList组数中,存在一、二级菜单项,因此需要双层循环来渲染菜单,外层获取一级菜单,内层获取二级菜单:

1)一级菜单

添加循环渲染之前:

<!-- Home.vue 结构区域:一级菜单 -->
<el-submenu index="1"><!-- 一级菜单的模板区 --><template slot="title"><i class="el-icon-location"></i><span>导航一</span></template>

添加循环渲染之后:

<!-- 一级菜单 -->
<el-submenu index="1" v-for="item in menulist" :key="item.id"><!-- 一级菜单的模板区 --><template slot="title"><i class="el-icon-location"></i><span>{{item.authName}}</span></template>

效果如下:

但是可以发现,现在是点开一级菜单中的任何一个,所有的菜单项都会全部同时展开,这不符合要求(只能展开自己的菜单项,不能影响其它的)。

此时需要为<el-submenu> 指定一个唯一的 index(目前所有的都为 index="1",相同了!),我们知道menulist获取的数据中,item.id 是唯一的,所以可以给 index 绑定一个动态的值,即,将 index="1",修改为:index="item.id",如下:

<!-- <el-submenu index="1" v-for = "item in menulist" :key = "item.id"> -->
<el-submenu :index="item.id" v-for="item in menulist" :key="item.id">

此时,当点击当前菜单项时,其它的菜单项就不会再同步展开了,如下图所示:

but,我们也可以看到,控制台报错了,报错的原因是 index 只接收 “ 字符串 ” ,但是item.id是一个数值,最简单的方法,是给它拼接一个空字符串,item.id + '',如下所示:

<el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id">

至此,一级菜单渲染完成。

2)二级菜单

二级菜单就是循环所有一级菜单的 children 属性

添加循环前:

<!-- 二级菜单 -->
<el-menu-item index="1-4-1">
<i class="el-icon-location"></i>
<span>导航一</span>
</el-menu-item>

添加循环渲染后:

 <!-- 二级菜单 -->
<el-menu-item index="subItem.id + ''" v-for="subItem in item.children" :key="subItem.id"><i class="el-icon-location"></i><span>{{subItem.authName}}</span>
</el-menu-item>

到此为止,左侧菜单的 结构 渲染完毕。

本部分的结构代码如下:

<el-menubackground-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>

4.5 设置激活子菜单样式

通过更改 el-menu 的 active-text-color 属性可以设置侧边栏菜单中点击的激活项的文字颜色;

通过更改菜单项模板(template)中的i标签的类名,可以将左侧菜单栏的图标进行设置,我们需要在项目中使用第三方字体图标;

在数据中添加一个 iconsObj:

1 )左侧菜单美化

  • ① 修改左侧菜单激活菜单项文本的颜色

    将最外层 <el-menu> 中的 active-text-color 属性的值修改为目标颜色#409eff即可

    ② 修改二级菜单项图标
    在 Element UI 官网中找到 icon 图标,将原来默认的图标类名修改即可:

  • <!-- 二级菜单 --><el-menu-item :index="subItem.id + ''" v-for="subItem in item.children" :key="subItem.id"><!-- <i class="el-icon-location"></i> --><i class="el-icon-menu"></i>
    

    ③ 修改一级菜单图标

  • 由于每个一级菜单项的图标都不一样,不能像二级菜单那样统一添加一种图标。所以需要通过自定义图标的形式来解决。

    这里仍然要用到前面使用过的第三方的字体图标库,之前已下载好放在 \assets\fonts 中:

    问题 】:

    前面我们的一级菜单的每一项,都是通过 for循环 自动生成的。那么,怎样让它在生成一级菜单期间,自动生成图标 ?

  • 解决方案】:

    在 data 中,先定义一个字体图标对象 iconsObj,将每个菜单项的 id 做为 Key, id 对应的图标做为值 。

    找到一级菜单各项的 id 值,将其放在 iconsObj 中分别指向字体图标的类名

  • 定义字体图标对象的代码如下:

    data () {return {// 左侧菜单数据menulist: [],iconsObj: {'125': 'iconfont icon-user','103': 'iconfont icon-showpassword','101': 'iconfont icon-shangpin','102': 'iconfont icon-danju','145': 'iconfont icon-baobiao'}}
    },
    

    注: 如果语法报错,可把包裹 Key 的引号去掉,如:125: 'iconfont icon-user'。

    然后将图标类名进行数据绑定,绑定 iconsObj 中的数据:

    接下来,修改原来的字体图标部分的代码,进行动态绑定,即每循环一次,根据 iconsObj 对象,把它 id 所对应的类取出来,放在图标标签<i>中:

    <!-- 一级菜单 -->
    <el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id"><!-- 一级菜单的模板区 --><template slot="title"><i :class="iconsObj[item.id]"></i><span>{{item.authName}}</span></template>
    

    保存后,图标就有了如下效果:

  • 需要给图标和菜单项文本增加间距。因为这些字体图标有一个共同的类 iconfont,因此只需在<style></style> 样式区域标签内,写上:

    .iconfont {margin-right: 10px;
    }
    

2)每次只能展开一个菜单项并解决边框问题

  • ① 解决多个菜单项能同时展开的问题

问题】:当前所有的菜单都能被同时展开,而实际需求只允许每次展开一个,其余的折叠。

【解决】

这个在 Element UI 官方的 NavMenu导航菜单 组件中,为<el-menu> 提供了unique-opened 属性,其值类型为布尔值,默认值为 false,即,可同时展开若干菜单项。

因此,为了保持左侧菜单每次只能打开一个,显示其中的子菜单,可在 el-menu 中添加一个属性 unique-opened;

把 unique-opened 属性值重置为 true 即可实现(每次只展开一个菜单项)。添加属性并重置为 true 如下:

注: 或者也可以数据绑定进行设置(此时true认为是一个bool值,而不是字符串) :unique-opened="true"

这样就实现了每次只展开唯一一个菜单项的效果,如下图所示:

② 解决边框线未对齐的问题

通过检查元素,发现el-menu类的样式中,存在一个border-right,其值为 1px 的边框,如下图所示:

通过类名选择器,将其重置为 none :

.el-aside {background-color:#333744;.el-menu {border-right: none;}
}

4.6 制作侧边菜单栏的伸缩(展开/折叠)功能

  1. 打开 Home.vue 文件,在侧边栏内部、菜单之前的上方添加一个 div 盒子:

    <!-- 侧边栏 -->
    <el-aside width="200px"><div class="toggle-button">|||</div><el-menu  background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened>
    
  2. Home.vue 中的<style>样式区域中,给展开折叠按钮盒子设置样式:
    .toggle-button {background-color: #4a5064;font-size: 10px;line-height: 34px;color: #fff;text-align: center;letter-spacing: 0.4em;cursor: pointer;
    }
    

    注: letter-spacing 属性增加或减少字符间的空白(字符间距),详见 W3school 关于 letter-spacing 的说明文档 。

  3. 效果如下:

要实现折叠与展开功能,在按钮盒子上,定义一个事件名称 toggleCollapse,给按钮盒子绑定 单击事件

<div class="toggle-button"  @click="toggleCollapse">|||</div>

接下来,需要在方法 methods 中 定义事件函数

分析】:

由于 Element UI 官网中,NavMenu导航菜单 Menu 属性上提供了一个 collapse 参数,值类型为 布尔值

因此,我们只需要在点击按钮时,切换 <el-menu> 标签的 collapse 属性值为 true 或 false 即可实现展开与折叠,回到代码中:

1)在 data 中,定义一个布尔值 isCollapse :

data () {return {// 左侧菜单数据menulist: [],iconsObj: {...},// 左侧菜单栏是否水平折叠:默认不折叠isCollapse: false}
},

再将这个布尔值isCollapse绑定到 <el-Menu> :

<!-- 侧边栏 -->
<el-aside width="200px"><div class="toggle-button"  @click="toggleCollapse">|||</div><el-menu  background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened :collapse="isCollapse">

3)事件处理函数:

// 点击按钮,切换菜单的折叠与展开
toggleCollapse() {this.isCollapse = !this.isCollapse
}

运行效果如下:

可以发现,默认的展开和折叠的动画效果很丑,为了让它流畅一些,需要把默认的动画效果关闭。

在 Element UI NavMenu 导航菜单 的 Menu 属性中,有个 collapse-transition 参数用于控制动画显示与否,默认参数值为 true ,因此,如将其值设为 false 即可关闭动画。

继续在<el-menu>中,绑定collapse-transition 属性,以关闭动画:

<el-menu  background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened :collapse="isCollapse" :collapse-transition="false">

但是,此时通过运行发现,菜单栏的背景并没有因为菜单的折叠而变小,如下所示:

这是因为 模板结构中,侧边栏的宽度是写死的:

<!-- 侧边栏 -->
<el-aside width="200px">

正常的是,当折叠后,即 isCollapse 值为 true 时,侧边栏宽度变小,其值为 false 时重置为200px即可。

由元素检查可知最小宽度为 64px ,所以,要绑定的宽度大小,用三元表达式判断的代码如下:

<!-- 侧边栏,宽度根据是否折叠进行设置 -->
<el-aside :width="isCollapse ? '64px':'200px'"><!-- 伸缩侧边栏按钮 --><div class="toggle-button" @click="toggleCollapse">|||</div><!-- 侧边栏菜单,:collapse="isCollapse"(设置折叠菜单为绑定的 isCollapse 值),:collapse-transition="false"(关闭默认的折叠动画) --><el-menu:collapse="isCollapse":collapse-transition="false"......

效果如下图:

4.7 在后台首页添加子级路由

4.7.1 主页 - 实现首页路由的重定向

需求】: 当我们登录成功后,需重定向到一个 欢迎页,并在 Main 页面主体区域展示 Welcome 组件。

实现过程】:

  1. 新增子级路由组件 Welcome.vue。

在 components 路径下新建 Welcome.vue 组件文件,如图:

同时在 Welcome.vue 中定义一个基本的 template 结构:

<template><div><h3>Welcome</h3></div>
</template>

注: 如果组件中暂时没有需要的样式和行为可不写,单写一个 template 也不会报错。
在 router.js 中导入子级路由组件,并设置路由规则以及子级路由的默认重定向;

import Welcome from '../components/Welcome.vue'
...
{path: '/home',component: Home,redirect: '/welcome',children: [{ path: '/welcome', component: Welcome }]
}

注意,Welcome 是以子路由的形式存在于 Home 规则中(在 Home 页面中嵌套显示 欢迎页面)的,因此要把 Welcome 做为 Home 的子路由规则。所以这里是新增一个 children 属性(即子路由,是个数组,数组里面再放子路由的规则对象)。

  • path:路由规则地址;
  • component:用于指定要显示的组件。
  • redirect:重定向。

如图所示:

主体结构中添加一个路由占位符

打开 Home.vue,在 main 的主体结构中添加一个路由占位符<router-view>

<!-- 右侧内容主体 -->
<el-main>Main</el-main>

添加路由点位符后,如下所示:

<!-- 右侧内容主体 -->
<el-main><router-view></router-view>
</el-main>

至此,实现了首页路由的重定向。

制作好了 Welcome 子级路由之后,我们需要将所有的侧边栏二级菜单都改造成子级路由链接;

将 Welcome 路由设置为 Home 路由的子路由规则

【目       的】:点击左侧菜单栏中的二级菜单,可以在右侧主体区域切换显示不同的组件页面。

【实现原理】:将每一个二级菜单改为路由链接实现跳转 —— ps:当然不是单独为其设置 router-link ,而是有更简单的方式 。

解决方法】:

在 Element UI 的 Menu 属性中,有个 router 参数,用于控制 “ 是否使用 vue-router 的模式 ” 其参数值的类型为布尔。默认值为 false,即未开启。如下图所示:

因此,只需要将 el-menu 的 router 属性设置为true即可,此时当我们点击二级菜单的时候,就会根据菜单的index属性进行路由跳转,如:/110

<el-menu  background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened :collapse="isCollapse" :collapse-transition="false" router>

如图:

但是,使用index id来作为跳转的路径不合适,我们可以重新绑定index的值为:index="'/'+subItem.path",设置时,需要在 path 地址名称前手动补上斜线(“/”)。

修改前:

<!-- 二级菜单 -->
<el-menu-item :index="subItem.id + ''" v-for="subItem in item.

将 index 绑定值修改为 path 做为跳转地址:

<!-- 二级菜单 -->
<el-menu-item :index= "'/'+ subItem.path + ''" v-for="subItem in item.

至此,实现了侧边栏路由改造,由于尚未对这些路由链接进行监听,因此打开的页面都是空白的。

效果如下图:

5. 用户管理

5.1 用户管理概述

通过后台管理用户的账号信息,具体包括用户信息的 展示添加修改删除角色分配账号启用 / 注销 等功能。

  • 用户信息列表展示

  • 添加用户

  • 修改用户

  • 删除用户

  • 启用或禁用用户

  • 用户角色分配

5.2 用户管理-列表展示

5.2.1 用户列表布局

用户列表主体区域主要分两部分,具体布局划分如下:

页面布局实现:

  1. 创建 “ 用户列表 ” 链接对应的组件页面

    • 在组件目录 conponets 中,新建 user 目录,再在此路径下新建 Users.vue 组件,如图:

    1. 在新组件中创建基本的页面结构:

      <template><div><h3>用户列表组件</h3> </div>
      </template><script>
      export default {}
      </script><style lang="less" scoped>
      </style>
      

      3. 通过路由的形式,在右侧主体区展示用户列表

    • 在 router 目录里的 index.js 路由文件中,导入用户列表组件、定义路由规则:

      ... // 导入的其它组件
      import Users from '../components/user/Users.vue'Vue.use(VueRouter)const router = new VueRouter({routes: [... // 其它路由规则{path: '/home',component: Home,redirect: '/welcome',children: [{ path: '/welcome', component: Welcome },{ path: '/users', component: Users }]}]
      })
      

      但是,此时发现激活的菜单文本并没有高亮显示:

      在 Menu 属性中有个 default-active 参数,表示当前激活菜单的 index 可以赋值给 default-active。

      即,如果想让菜单中的某一项被激活时高亮,就把该项对应的 index 赋值给整个 Menu 菜单的属性 default-active 。

      打开 Home.vue ,在 <el-menu>中添加 default-active 属性,为便于测试,直接将其值写死为 /users,即default-active="/users",效果如下:

如果要把激活项的 index 换成动态的,要如何实现 ???

实现思路:

当我们点击链接时,把对应的地址先保存到 Session Storage 中。当刷新页面(即Home组件刚被创建)时,再把这个值取出来,动态的赋值给 el-menu 中的 default-active 属性 。

第一步:给所有的二级菜单绑定一个 单击事件,命名为 saveNavState,在单击事件中把 path 值存贮起来:

<!-- 二级菜单 -->
<el-menu-item :index="'/'+subItem.path + ''" v-for="subItem in item.children" :key="subItem.id" @click="saveNavState('/'+subItem.path)">

第二步: 定义 saveNavState 函数

// 保存链接的激活状态
saveNavState(activePath) {window.sessionStorage.setItem('activePath', activePath)}

运行程序后,即可保存path 值:

接下来,需要把保存的值取出来

第三步: 在 data 中定义 activePath 来保存数据,默认为空:activePath: ''。再将 activePath 绑定到 <el-menu> 的 default-active 属性上。替换原来写死的值/users。

第四步: 动态赋值

当整个 Home 组件一被创建的时候,就立即从 Session Storage 中把值取出来赋给 activePath ;

由于组件被创建时,第一时间执行的是 Created 生命周期函数,所以就直接在 Created 里进行赋值:

created() {this.getMenuList()this.activePath = window.sessionStorage.getItem('ctivePath')
}

我们发现,当点击别的链接之后,再次点击 用户列表 时,对应的链接文本并没有高亮,原因是缺少了一步。应该是点击不同链接时,也要为当前的 activePath 重新赋值( saveNavState 点击事件里):

// 保存链接的激活状态
saveNavState(activePath) {window.sessionStorage.setItem('activePath', activePath)// 点击链接时,重新赋值this.activePath = activePath}

2. 绘制用户列表基本 UI 结构

  • 面包屑导航:el-breadcrumb
  • Element-UI 栅格系统基本使用:el-row
  • 表格布局:el-tableel-pagination

在 Element UI 官方的 面包屑 导航组件中,找到对应的代码并复制。

打开 Users.vue 文件,将复制好的代码粘贴到该组件文件 template 模板区中:

<template><div><!-- 面包屑导航区域 --><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></div>
</template>

接下来,在element.js 中按需导入 Breadcrumb 和 BreadcrumbItem 组件并注册(否则不生效):

import Vue from 'vue'
... // 其它组件
import { Breadcrumb, breadcrumbItem } from 'element-ui'// 注册全局组件
... // 注册的其它组件
Vue.use(Breadcrumb)
Vue.use(breadcrumbItem)Vue.prototype.$message = Message
  • 此时,面包屑导航功能完成如下:

绘制卡片视图区域

在 Element UI 官网 中找到 card卡片 组件,

将复制的代码粘贴到 users.vue 文件中:

<!-- 卡片视图区 -->
<el-card class="box-card"><div v-for="o in 4" :key="o" class="text item">{{'列表内容 ' + o }}</div>
</el-card>

整理代码,将不需要的 for 循环和 box-card 类删掉后,结构代码如下:

<!-- 卡片视图区 -->
<el-card>123
</el-card>

再在 element.js 中按需导入 card 卡片后,效果如下:

目视可见卡片离面包屑导航太近。在 assets 中的 css 目录下,找到 global.css,为卡片视图区设置上部的间距,这里通过面包屑的类名选择器 el-breadcrumb ,给面包屑增加一个 margin -bottom 值,把卡片盒子挤下来一点:

.el-breadcrumb {margin-bottom: 15px;font-size: 12px;
}

保存后,基本的效果如下图: 
为卡片视图重置阴影样式。

.el-car {box-shadow: 0 1px 1px rgba(0, 0 ,0.15) !important;
}

5.2.2 用户状态列和操作列处理

  • 作用域插槽
  • 接口调用
<template slot-scope="scope"><!-- 开关 --><el-switch v-model="scope.row.mg_state" @change="stateChanged(scope.row.id, scope.row.mg_state)"><!--  --></el-switch>
</template>

5.2.3 表格数据填充

  • 调用后台接口
  • 表格数据初填充
const { data: res } = await this.$http.get('users', { params: this.queryInfo })
if (res.meta.status !== 200) {return this.$message.error('查询用户列表失败!')
}
this.total = res.data.total
this.userlist = res.data.users

5.2.4 表格数据分页

分页组件用法:

  1. 当前页码:pagenum

  2. 每页条数:pagesize

  3. 记录总数:total

  4. 页码变化事件

  5. 每页条数变化事件

  6. 分页条菜单控制

<el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="queryInfo.pagenum":page-sizes="[2, 3, 5, 10]":page-size="queryInfo.pagesize"layout="total, sizes, prev, pager, next, jumper":total="total">
</el-pagination>

5.2.5 搜索功能

将搜索 关键字,作为 参数 添加到列表查询的参数中。

<el-input placeholder="请输入搜索的内容"   v-model="queryInfo.query" clearable @clear="getUserList"><el-button slot="append" icon="el-icon-search" @click="getUserList"> </el-button>
</el-input>

5.3 用户管理-用户状态控制

  • 开关组件的用法

  • 接口调用更改用户的状态

<el-switch v-model="scope.row.mg_state" @change="stateChanged(scope.row.id, scope.row.mg_state)">
</el-switch>
async stateChanged(id, newState) {const { data: res } = await this.$http.put(`users/${id}/state/${newState}`)if (res.meta.status !== 200) {return this.$message.error('修改状态失败!')}
}

5.4 用户管理-添加用户

5.4.1 添加用户表单弹窗布局

  • 弹窗组件用法

  • 控制弹窗显示和隐藏

<el-dialog title="添加用户" :visible.sync="addDialogVisible" width="50%"><el-form :model="addForm" label-width="70px"><el-form-item label="用户名" prop="username"><el-input v-model="addForm.username"></el-input></el-form-item><!-- 更多表单项 --></el-form><span slot="footer" class="dialog-footer"><el-button @click="resetAddForm">取 消</el-button><el-button type="primary" @click="addUser">确 定</el-button></span>
</el-dialog>

5.4.2 表单验证

  1. 内置 表单验证规则
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" ><!-- 表单 -->
</el-form>
addFormRules: {username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
this.$refs.addFormRef.validate(async valid => {if (!valid) return
})

自定义 表单验证规则

手机号验证规则

const checkMobile = (rule, value, cb) => {let reg = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/if (reg.test(value)) {cb()} else {cb(new Error('手机号码格式不正确'))}
}
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' },{ validator: checkMobile, trigger: 'blur' }
]

5.4.3 表单提交

将用户信息作为参数,调用后台接口添加用户 。

this.$refs.addFormRef.validate(async valid => {if (!valid) returnconst { data: res } = await this.$http.post('users', this.addForm)if (res.meta.status !== 201) {return this.$message.error('添加用户失败!')}this.$message.success('添加用户成功!')this.addDialogVisible = falsethis.getUserList()
})

5.5 用户管理-编辑用户

5.5.1 根据 ID 查询用户信息

<el-button type="primary" size="mini" icon="el-icon-edit" @click="showEditDialog(scope.row.id)">
</el-button>
async showEditDialog(id) {const { data: res } = await this.$http.get('users/' + id)if (res.meta.status !== 200) {return this.$message.error('查询用户信息失败!')}// 把获取到的用户信息对象,保存到 编辑表单数据对象中this.editForm = res.datathis.editDialogVisible = true
}

5.5.2 编辑提交表单

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})if (res.meta.status !== 200) {return this.$message.error('编辑用户信息失败!')}this.$message.success('编辑用户信息成功!')this.getUserList()this.editDialogVisible = false
})

5.6 用户管理-删除用户

在点击删除按钮的时候,应跳出提示信息框,让用户确认要进行删除操作。

如果想要使用确认取消提示框,需要先将提示信息框挂载到vue中。

  • A. 导入MessageBox组件,并将MessageBox组件挂载到实例。

Vue.prototype.$confirm = MessageBox.confirm

B. 给用户列表中的删除按钮添加事件,并在事件处理函数中弹出确定取消窗,最后再根据id发送删除用户的请求

<el-button type="danger" size="mini" icon="el-icon-delete" @click="remove(scope.row.id)"></el-button>
async remove(id) {// 询问是否要删除const confirmResult = await this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).catch(err => err)//如果用户点击确认,则confirmResult 为'confirm'//如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'if(confirmResult != "confirm"){return this.$message.info("已经取消删除")}//发送请求,根据id完成删除操作const { data: res } = await this.$http.delete('users/' + id)if (res.meta.status !== 200) return this.$message.error('删除用户失败!')this.$message.success('删除用户成功!')this.getUserList()
},

6 权限管理

6.1 权限管理业务分析

通过权限管理模块控制不同的用户可以进行哪些操作,具体可以通过角色的方式进行控制,即每个用户分配一个特定的角色,角色包括不同的功能权限。

6.2 添加权限列表路由

创建权限管理组件**(Rights.vue),并在router.js添加对应的路由规则

import Rights from './components/power/Rights.vue'
......path: '/home', component: Home, redirect: '/welcome', children: [{ path: "/welcome", component: Welcome },{ path: "/users", component: Users },{ path: "/rights", component: Rights }]
......

6.3 添加面包屑导航

在 Rights.vue 中添加面包屑组件展示导航路径。

6.4 权限列表展示

data中添加一个rightsList数据,在methods中提供一个getRightsList方法发送请求获取权限列表数据,在created中调用这个方法获取数据。

<el-table :data="rightsList" stripe><el-table-column type="index"></el-table-column><el-table-column label="权限名称" prop="authName"></el-table-column><el-table-column label="路径" prop="path"></el-table-column><el-table-column label="权限等级" prop="level"><template slot-scope="scope"> <el-tag v-if="scope.row.level === 0">一级权限</el-tag><el-tag v-if="scope.row.level === 1" type="success">二级权限</el-tag><el-tag v-if="scope.row.level === 2" type="warning">三级权限</el-tag></template></el-table-column>
</el-table>
<script>
export default {data(){return {//列表形式的权限rightsList:[]}},created(){this.getRightsList();},methods:{async getRightsList(){const {data:res} = await this.$http.get('rights/list')//如果返回状态为异常状态则报错并返回if (res.meta.status !== 200)return this.$message.error('获取权限列表失败')//如果返回状态正常,将请求的数据保存在data中this.rightsList = res.data}}
}
</script>

完整的添加权限,删除权限,编辑权限功能,这里不再列出,请参照前面编写过的角色管理的代码还有接口文档完成。

获取权限列表数据

// 获取权限列表数据
async getRightsList() {const { data: res } = await this.$http.get('rights/list')if (res.meta.status !== 200) {return this.$message.error('获取权限列表失败!')}this.rightsList = res.data
}

6.5 角色列表展示

  1. 调用后台接口获取角色列表数据
  2. 角色列表展示
// 获取所有角色列表
async getRolesList() {const { data: res } = await this.$http.get('roles')if (res.meta.status !== 200) {return this.$message.error('获取角色列表失败!')}this.rolesList = res.data
},

6.6 用户角色分配

6.6.1 展示角色对话框

① 实现用户角色对话框布局
② 控制角色对话框显示和隐藏
③ 角色对话框显示时,加载角色列表数据

async showSetRoleDialog(userInfo) {this.userInfo = userInfo// 发起请求,获取所有角色的列表const { data: res } = await this.$http.get('roles')if (res.meta.status !== 200) {return this.$message.error('获取角色列表失败!')}this.rolesList = res.datathis.setRoleDialogVidible = true
}

6.6.2 完成角色分配功能

async saveNewRole() {if (this.selectedRoleId === '') {return this.$message.error('请选择新角色后再保存!')}const { data: res } = await this.$http.put(`users/${this.userInfo.id}/role`, {rid: this.selectedRoleId})if (res.meta.status !== 200) {return this.$message.error('分配角色失败!')}this.$message.success('分配角色成功!')this.getUserList()this.setRoleDialogVidible = false
}

6.7 角色权限分配

注 解:


  1. 同源:即协议相同、域名相同、端口相同

    同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。 ↩︎

  2. 跨域:是指浏览器不能执行其它网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript实施的 安全限制

Vue全家桶-电商后台管理系统项目开发相关推荐

  1. Vue全家桶 - 电商后台管理系统项目开发实录(详)

    目录 1. 项目概述 1.1 电商项目基本业务概述 1.2 电商后台管理系统的功能 1.3 电商后台管理系统的开发模式(前.后端分离) 2. 项目初始化 2.1 前端项目初始化步骤 码云相关操作 2. ...

  2. Vue电商后台管理系统项目开发实战(一)

    前言 当下根据不同的应用场景,电商系统一般都提供了PC端,移动APP,移动Web,微信等多种访问方式.如下图. 不同的客户端共用同一个服务器,数据库,API.本次项目着重设计PC后台管理,供电商后台管 ...

  3. html全局布局 vue_基于Vue+Element的电商后台管理系统

    前言 mall项目后台管理系统的前端项目. 项目介绍 mall-admin-web是一个电商后台管理系统的前端项目,基于Vue+Element实现. 主要包括商品管理.订单管理.会员管理.促销管理.运 ...

  4. 电商后台管理系统项目的优化

    在面试过程中,面试官除了对基础知识的盘查之外,还会了解你所做的项目,单单知识谈项目所用到的技术已经满足不了面试官对我们的好奇心了,每次面试问到优化这一方面,因为没怎么准备,含糊的说辞只会让面试官连连摇 ...

  5. Vue前后端分离的电商后台管理系统项目的概述

    (1)该项目是基于前后端分离的开发模式,基于Vue技术栈的SPA单页面项目, 后端主要操作数据库并向前端暴露一些API接口 前端主要负责绘制页面同时,利用Ajax调用后端提供的接口. 这样的开发模式使 ...

  6. vue与elementUI电商后台管理系统笔记05

    实现首页的路由重定向 实现进入home页面在main那里路由跳转到welcome页面,也就是welcome页面嵌套在home页面里面 welcome是以子路由形式存在于home页面中 首先在compo ...

  7. 电商后台管理系统前端开发总结

    前端和服务器存在跨域问题使用token维持状态,否则使用cookie或者session记录状态

  8. 【VUE项目】VUE+ElementUI电商后台管理系统

    电商后台管理系统 项目来源:https://www.bilibili.com/video/BV1x64y1S7S7?p=191&spm_id_from=333.1007.top_right_b ...

  9. 【Vue】实战项目:电商后台管理系统(Element-UI)(一)前后端搭建 - 登录界面 - 主页界面

    文章目录 0. 项目介绍 电商管理系统(Element-UI) 开发模式 前端技术栈 后端技术栈 1. 配置--初始化 前端项目 ① 安装 Vue 脚手架 ② 通过 Vue 脚手架创建项目 ③ 配置 ...

最新文章

  1. 「基于GNN的图分类研究」最新2022综述
  2. android button背景随心搭配
  3. 什么原因?全球许多网络提供商推迟部署IPv6
  4. 模拟计算机 电磁,校园机房电磁模拟环境及计算机模拟.ppt
  5. cuda7.5 和cuda8共存
  6. myeclipse打开jsp页面慢或者卡死
  7. java 点餐界面_Java小项目点餐系统(二)之服务端 | 学步园
  8. oracle的function的语法,Oracle function语法
  9. 街上第一台电子计算机是,南京信息工程大学滨江学院2009级《计算机基础》(文科)a试卷(含答案)【最新】.doc...
  10. 知道标签html中的标签,我的取包括标签的标签内的内容使用的正则表达式()
  11. aria2最新tracker服务器,Aria2自动更新BT Tracker服务器列表的方法
  12. python怎样定义数组_终于知道python如何定义二维数组
  13. 把ip导入mysql_纯真IP数据库导入mysql
  14. jQuery——滚动条位置的获取与设置
  15. 支持在线大数据SQL查询平台开源项目
  16. key_t键和ftok函数
  17. SVM学习笔记-对偶形式的SVM
  18. 12306自动抢票及自动识别验证码功能(二)
  19. EN 45545-2:2020 T11烟毒性检测简介
  20. Oracle EBS OPM创建会计科目告警:日记帐分录不平衡

热门文章

  1. mysql获取前一个月的日期和前一年的日期
  2. c语言char数组的对比,char数组 、char指针与字符串常量的比较
  3. solidworks装配体另存为时报错 如题,提示:输入的文件名无效,无法被发现、被锁住或者为不兼容的类型
  4. 学习cad之后体会心得
  5. 离不离,什么时候离职,离职的原因
  6. 会声会影2023导出视频的清晰度一般选哪个 导出视频怎么保持清晰度
  7. 构建一个WooCommerce付款扩展
  8. 小程序和公众号商城【免费开源+小白式安装】
  9. 叉乘与反对称矩阵(以i,j,k为基然后写成反对称矩阵与向量相称的形式)[w]_x
  10. 直播虚拟背景怎么弄?这几种虚拟直播工具很好用