100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Vue全家桶-电商后台管理系统项目开发

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

时间:2023-12-23 23:20:43

相关推荐

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

项目效果展示:

1. 项目概述

1.1 电商项目基本业务概述

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

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

PC后台管理端的功能

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

开发模式

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

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

本项目技术选型

前端项目技术栈()

VueVue-RouterElement-UIAxiosEcharts

后端项目技术栈

Node.jsExpressJwt(模拟session);MysqlSequelize(操作数据库的框架)

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

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

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

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

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

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

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

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

2. 项目初始化

2.1 前端项目初始化步骤

安装 Vue 脚手架

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

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 -gyarn 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. 注册登录码云账号,注册地址:/signup

2. 安装 git

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

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

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

下一步:

下一步:

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

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

:上述命令(示例)中的 “ xxx@” 字样,请务必替换为自己注册 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@

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

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

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

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 /XXXXX/vue_shop.gitgit 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 接口文档下载:/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进行维持登录状态。

/index.html 调用 /abc.do ( 非跨域 )/index.html 调用 /abc.do ( 主域名不同:123/456,跨域 )/index.html 调用 /server.do ( 子域名不同:abc/def,跨域 ):8080/index.html 调用 :8081/server.do( 端口不同:8080/8081,跨域 )/index.html 调用 /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 转换( CSS3transform属性的更多详情点击这里)。

添加样式后效果如下:

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-formel-form-itemel-inputel-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.htmlHTML文件,查看使用示例,并将图标分别放置到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里的对象usernamepassword,分别双向绑定到用户名和密码输入框上,代码如下:

<!-- 登录表单区 --><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 属性,并指向usernamepassword

<!-- 用户名 --><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-varsconsole.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 defaultVueRouter拆分开,先拿到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中的布局容器进行布局:

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

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

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

基本布局:打开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文件:

导入并注册以下组件:

MenuSubmenuMenuItemGroup(菜单分组项);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">

修改后的效果:

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

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

<!-- 侧边栏 --><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 通过接口获取菜单数据

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 = axiosVue.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 制作侧边菜单栏的伸缩(展开/折叠)功能

打开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>

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的说明文档 。

效果如下:

要实现折叠与展开功能,在按钮盒子上,定义一个事件名称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 组件。

实现过程】:

新增子级路由组件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 用户列表布局

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

页面布局实现:

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

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

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

<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-breadcrumbElement-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.totalthis.userlist = res.data.users

5.2.4 表格数据分页

分页组件用法:

当前页码:pagenum

每页条数:pagesize

记录总数:total

页码变化事件

每页条数变化事件

分页条菜单控制

<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 表单验证

内置表单验证规则

<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角色列表展示

调用后台接口获取角色列表数据角色列表展示

// 获取所有角色列表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角色权限分配

注 解:

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

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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。