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

川长思鸟来 2022-12-28 09:21 520阅读 0赞

目录

    1. 项目概述
    • 1.1 电商项目基本业务概述
    • 1.2 电商后台管理系统的功能
    • 1.3 电商后台管理系统的开发模式(前、后端分离)
    1. 项目初始化
    • 2.1 前端项目初始化步骤
      • 码云相关操作
    • 2.2 后台项目的环境安装配置
    1. 登录 / 退出 功能
    • 3.1 登录概述
    • 3.2 登录 - token 原理分析
    • 3.3 实现登录功能
    • 3.4 实现退出功能
      • 处理 ESLint 警告
    1. 主页布局
    • 4.1 后台首页基本布局
    • 4.2 顶部布局,侧边栏布局
      • 4.2.1. 顶部布局
      • 4.2.2 侧边栏菜单布局
    • 4.3 通过接口获取菜单数据
    • 4.4 动态渲染菜单数据并进行路由控制
    • 4.5 设置激活子菜单样式
    • 4.6 制作侧边菜单栏的伸缩(展开/折叠)功能
    • 4.7 在后台首页添加子级路由
      • 4.7.1 主页 - 实现首页路由的重定向
    1. 用户管理
    • 5.1 用户管理概述
    • 5.2 用户管理-列表展示
      • 5.2.1 用户列表布局
      • 5.2.2 用户状态列和操作列处理
      • 5.2.3 表格数据填充
      • 5.2.4 表格数据分页
      • 5.2.5 搜索功能
    • 5.3 用户管理-用户状态控制
      • 5.4 用户管理-添加用户
      • 5.4.1 添加用户表单弹窗布局
      • 5.4.2 表单验证
      • 5.4.3 表单提交
    • 5.5 用户管理-编辑用户
      • 5.5.1 根据 ID 查询用户信息
      • 5.5.2 编辑提交表单
    • 5.6 用户管理-删除用户
  • 6 权限管理
    • 6.1 权限管理业务分析
    • 6.2 添加权限列表路由
    • 6.3 添加面包屑导航
    • 6.2 权限列表展示
    • 6.3 角色列表展示
    • 6.4 用户角色分配
      • 6.4.1 展示角色对话框
      • 6.4.2 完成角色分配功能
    • 6.5 角色权限分配
  • ~ 未完待续 ~

项目效果展示:

在这里插入图片描述

1. 项目概述

1.1 电商项目基本业务概述

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

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

  • PC后台管理端的功能

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

  • 开发模式

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

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

  • 本项目技术选型

    • 前端项目技术栈()

      • Vue
      • Vue-Router
      • Element-UI
      • Axios
      • Echarts
  1. * **后端项目技术栈**(做一般了解)
  2. * `Node.js`
  3. * `Express`
  4. * `Jwt`(模拟`session`);
  5. * `Mysql`
  6. * `Sequelize`(操作数据库的框架)

在这里插入图片描述

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

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

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

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

在这里插入图片描述

2. 项目初始化

2.1 前端项目初始化步骤

  1. 安装 Vue 脚手架(如在此之前本机已 全局 安装过,则可略过)
  1. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70_pic_center 3]
  2. 安装 Vue CLI 交互式项目脚手架(一个基于 Vue.js 进行快速开发的完整系统)。
  3. 1cmd 中执行以下命令安装 **Vue CLI** 包:
  4. npm install -g @vue/cli
  5. 2)安装完成后,检查版本是否正确:
  6. vue --version
  7. > Vue CLI 的包名称由 vue-cli 改成了 @vue/cli 如果你已经全局安装了旧版本的 vue-cli (1.x 2.x),你需要先通过 `npm uninstall vue-cli -g` `yarn global remove vue-cli` 卸载它。
  8. > Node 版本要求:
  9. > Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)。你可以使用 nnvm nvm-windows 在同一台电脑中管理多个 Node 版本。
  10. 更多内容请参见 Vue CLI 官方文档:[https://cli.vuejs.org/zh/guide/][https_cli.vuejs.org_zh_guide]
  1. 通过 Vue 脚手架创建项目(本项目开发采用 “ 可视化面板 ” 创建)

    1)cmd 终端中输入 :

    1. vue ui

    在这里插入图片描述
    运行完成后,自动打开如下页面:
    在这里插入图片描述
    2)点击 “+ 创建 ” 进入到目录选择面板,选择目录后。再点击 “+ 在此创建新目录 ”按钮。

    填写项目信息:
    在这里插入图片描述
    3)进入“ 预设”面板,并按如下图示勾选:
    在这里插入图片描述

  1. 4)点击下一步,进入“ **功能** ”选择面板:
  2. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 3]
  3. 上图中,勾选了 使用配置文件 后,就会将不同的配置 单独 地存放为一个配置文件。点击 **下一步**,打开 配置 面板。
  4. 5)完成如下操作:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 4]
  5. 6)单击 下一步。提示是否保存新预设(方便下次直接选择该配置 / 也可不保存)。
  6. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 5]
  7. 点击 “**保存预设并创建项目**” 按钮后,系统自动创建项目如下:
  8. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 6]
  1. 配置 Vue 路由

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

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

    1)打开“仪表盘”,单击左侧 “ 插件 ”按钮,再单击右上角的 “ + 添加插件在这里插入图片描述

  1. 2)搜索插件 `vue-cli-plugin-element` 并安装。
  2. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 8]
  3. 3)跳转到 **配置插件** ”面板,操作如下:
  4. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 9]
  1. 配置 axios 库:在依赖中安装,搜索axios(运行依赖)

    1)点击 左侧边栏 “ 依赖 ” 按钮,再点击右上角 “ 安装依赖 ”
    在这里插入图片描述

  1. 2)安装依赖
  2. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 11]
  1. 初始化 git 远程仓库
  2. 将本地项目托管到 Github 或 码云中(方便团队成员协作开发)

码云相关操作

  1. 注册登录码云账号,注册地址:https://gitee.com/signup
    在这里插入图片描述
  2. 安装 git

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

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

  1. 点击网站右上角“登录”,登录码云,并进行账号设置
    在这里插入图片描述
    在这里插入图片描述

下一步:

在这里插入图片描述


下一步:
在这里插入图片描述

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

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

    :上述命令(示例)中的 “ xxx@xxx.com” 字样,请务必替换为自己注册 gitee 时的真实邮箱后,再回车执行!
    在这里插入图片描述
    然后回车,接着连敲 3 次回车(中间不需任何操作)即可生成公钥。如图:
    在这里插入图片描述

  1. 找到公钥地址

    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 就是创建好的 公钥 了。

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

    在这里插入图片描述
    再点击码云中的 SSH 公钥按钮,将复制好的的公钥粘贴到公钥文本框中。在这里插入图片描述
    点击”确定“按钮,再根据提示输入验证密码后,即完成ssh公钥添加:
    在这里插入图片描述

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

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

    1. 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,同样也可看到如下所示信息:
    在这里插入图片描述

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

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

    在这里插入图片描述
    在这里插入图片描述

  2. 进行git配置

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

    在这里插入图片描述

  1. 10 **项目首次提交**
  2. 1)**检查状态**:项目根目录下输入以下命令:
  3. git status
  4. 运行结果显示项目中存在未跟踪的文件没有被提交 ,如下所示:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 21]
  5. 此时需要做一下处理,即把所有的文件都添加到 暂存区。
  6. 2)**添加到 暂存区**:运行如下命令:
  7. git add .
  8. 注意:命令中的 `add` 和后面的`.`(小圆点)中间有个空格,否则会报错。
  9. 3)**本地提交**:将暂存区中的文件提交至 本地仓库中
  10. git commit -m "add files"
  11. 再次检查状态:
  12. git status
  13. 运行结果如下:
  14. ![在这里插入图片描述][20201218222504593.png]
  15. 提示当前 处于主分支,工作目录是干净的 ”,即没有要提交的文件。
  16. 但当前的这些操作只是在本地操作仓库,仓库还没上传到码云中,
  17. 4)将本地仓库与远程 `git`仓库 关联
  18. 找到并在终端中运行(你新建的码云仓库 vue\_shop 页面最底部提供的那两句)代码,如下所示:
  19. git remote add origin https://gitee.com/XXXXX/vue_shop.git
  20. git push -u origin master
  21. **注:** XXXX 为你的码云 **帐户名称** 非邮箱名称)。如果运行 报错,请点击 这里 查看解决办法。
  22. 执行第二句命令时,会弹出如下安全验证,输入用户名和密码确认后,等待完成提交即可。
  23. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 22]
  24. 说明:如果是第一次向码云中提交代码,会弹出码云的帐号和密码输入窗口(以后不会再出现)
  25. **5)检查是否上传(远程仓库)成功**
  26. 在远程仓库中点击 “刷新”,即可看到提交信息
  27. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 23]
  28. 类似这样,表示本地仓库已成功上传到了码云中。
  29. --------------------
  30. \*\*关于 报错\*\*,如果执行上述命令时终端中报错如:
  31. fatal: not a git repository (or any of the parent directories): .git
  32. 【**解决办法**】
  33. —— 请检查项目根目录下,是否存在一个隐藏的 `.git` 文件夹,如下所示:
  34. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 24]
  35. 如果不存在,则在此项目根目录下,打开终端运行如下命令(初始化本地仓库):
  36. git init
  37. 已了解,点击 返回。

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

  • 2.2.1 安装 MySQL 数据库

    用到的素材,点击 网盘下载 提取码:8up0

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

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

  1. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 25]
  2. 运行phpStudy,单击“ MySQL管理器 按钮,选择 MySQL导入导出 菜单项。
  3. 按上图所示,找到对应路径下已解压得到的 db 文件夹中的 `mydb.sql` 数据库脚本文件,点击 导入” 按钮,自动弹出黑色的命令行窗口,开始还原数据库(此时间稍长,请耐心等待~);
  4. --------------------
  5. ***温馨提示!** 还原结束时,黑色的命令行窗口会自动关闭,此时可按如下所示查看生成的数据库。*
  6. ![在这里插入图片描述][20201219005059565.png]
  7. 如在数据库目录下能够看到如下图所示的路径、文件,表示 *数据库还原成功!*![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 26]
  8. --------------------
  9. **注:** 由于开发过程中不需要用到 Apache,可将其 停止 服务,如下图所示:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 27]
  10. --------------------
  • 2.2.2 配置后台项目

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

  1. **A**. 安装 nodeJS 环境,配置后台项目
  2. **B. 安装项目依赖包**
  3. 进入 vue\_api\_server 目录中,shift+右键 在弹出的菜单中选择 “在此处打开 Powershell 窗口 打开终端,输入命令安装项目依赖包:
  4. npm install
  5. **C.启动项目**
  6. 继续在终端中输入如下命令,启动项目:
  7. node .\app.js
  8. **注意**:启动前,必须先将 phpStudy MySQL 服务开启。
  9. **D**. 使用 postman 测试 API 接口是否正常。
  10. 安装 postman 软件([点此下载][Link 2]),启动 PostMan 填写相关参数(首次使用该软件需进行简单注册),如下所示:
  11. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70_pic_center 7]
  12. --------------------
  13. 注意:输入登录请求地址、用户字段名、密码字段名时,请务必与 API 文档保持一致;
  14. --------------------
  15. 点击 **Send**” 后,服务端返回如下信息:
  16. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 28]
  17. 电商管理后台 API 接口文档 下载:[https://pan.baidu.com/s/1OGxh05B0BocQm9cP3BWO7w][https_pan.baidu.com_s_1OGxh05B0BocQm9cP3BWO7w] 提取码:

3. 登录 / 退出 功能

3.1 登录概述

  1. 登录业务流程

    • 在登录页面输入用户名和密码
    • 调用后台接口进行验证
    • 通过验证之后,根据后台的响应状态跳转到项目主页
  1. 登录业务的相关技术点

    • http 是无状态的;
    • 通过 cookie 在客户端记录状态;
    • 通过 session 在服务器端记录状态;
    • 通过 token 方式维持状态(推荐垮域 时采用)

3.2 登录 - token 原理分析

在这里插入图片描述

3.3 实现登录功能

  • 一、登录逻辑:

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

  • 二、登录状态保持

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

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

    举例如下

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

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

  • 三、添加新分支 login,在 login分支 中开发当前项目 vue_shop
  1. 1)项目根目录中打开 vue\_shop 终端(shift + 右键 通过 vs code打开),使用`git status`命令确定当前项目状态(是否干净)。
  2. git status
  3. 运行结果如下所示:
  4. ![在这里插入图片描述][20201220213536234.png]
  5. 表明当前工作区是干净的,可以进行登录页面的绘制。
  6. —— 此时需要创建一个新分支。
  7. 2)确定当前工作目录是干净的之后,创建一个新分支并切换到该分支进行开发,开发完毕之后将其合并到 master
  8. git checkout -b login
  9. > **注:** 在开发中,只要进行一个新功能开发的时候,尽量把它放到一个新分支上,当这个功能开发完毕后,再把它合并到主分支 master 上。
  10. 3)然后`git branch`命令查看新创建的分支,确定我们正在使用 login分支 进行开发。
  11. ![在这里插入图片描述][2020121810225882.jpg_pic]
  12. **绿色** 表示当前所处的分支。
  13. 4)接着,执行`vue ui`命令打开 **ui** 界面,然后运行 serve,运行 app 查看当前项目效果。
  14. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70_pic_center 9]
  15. --------------------
  16. **四、登录页面的布局**
  17. 点击 启动App ,打开项目:
  18. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70_pic_center 10]
  19. 此时,我们可以看到现在它只是一个默认页面,需要把它重置为空白页面:
  20. 1)打开项目的 src 目录,点击查看`main.js`文件(这是整个项目的 **入口文件**):
  21. import Vue from 'vue'
  22. import App from './App.vue'
  23. import router from './router'
  24. import './plugins/element.js'
  25. Vue.config.productionTip = false
  26. new Vue({
  27. // 把 router 路由挂载到实例
  28. router,
  29. // 通过 render 函数,把 App 根组件渲染到页面上
  30. render: h => h(App)
  31. }).$mount('#app')
  32. 2)再打开 App.vue (根组件),将根组件的内容进行清理(template 中只留下根节点,script 中留下默认导出,去掉组件,style 中去掉所有样式),清理完成后如下所示:
  33. <template>
  34. <div id="app">
  35. <router-view></router-view>
  36. </div>
  37. </template>
  38. <script> export default { name: 'app' } </script>
  39. <style> </style>
  40. 3)再打开路由文件夹下的 index.js(有些版本的 Vue ui 所创建的工程项目,其 router 文件夹下的路由文件名为`router.js`),将`routes`数组中默认的路由规则全部清除,然后将`views`删除:
  41. import Vue from 'vue'
  42. import Router from 'vue-router'
  43. Vue.use(Router)
  44. export default new Router({
  45. routes: [
  46. ]
  47. })
  48. **五、新建 Login.vue 组件**
  49. 1)将文件夹`components`中的`helloworld.vue`删除,并新建 Login.vue 单文件组件,添加 templatescriptstyle 标签,style 标签中的 scoped 可以防止组件之间的样式冲突(没有`scoped`则样式是全局的)。
  50. <template>
  51. <div class="login_container">
  52. </div>
  53. </template>
  54. <script>
  55. export default {
  56. }
  57. </script>
  58. <style lang="less" scoped>
  59. .login_container {
  60. background-color: #2b4b6b;
  61. height: 100%;
  62. }
  63. </style>
  64. `scoped`:是 vue 指令,用来控制组件生效的范围(表示只在当前组件内生效,只要是单文件组件,都应加上)
  65. --------------------
  66. **注**:当添加背景样式并保存代码修改后,浏览器会报错 找不到 `less-loader`”,如下图所示:
  67. ![在这里插入图片描述][20201228222209783.png]
  68. 这是由于 Vue cli 工具创建的项目默认并没有安装 less 相关的 loader,如果要使用 less 语法(如`<style lang="less" scoped>`),此时则需要配置 less加载器(开发依赖),安装 less (开发依赖)。。
  69. 因此,接下来打开可视化面板安装依赖 less-loader less
  70. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 29]
  71. 如上图所示,搜索并安装好 `less-loader`以后,此时浏览器仍然报错,如下所示: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 30]
  72. 这时,回到安装依赖项界面,再次搜索 less 并安装好(因为 less-loader 依赖于 less)。
  73. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 31]
  74. --------------------
  75. 此时刷新网页并不能生效(仍然是报错状态),接下来,先关闭网页,**停止** server,再次点击 **运行**即可。
  76. 2)在路由文件 index.js 中导入`Login.vue` 组件并设置规则;
  77. const router = new Router({
  78. routes: [
  79. // 当用户访问 /login 这个地址时,通过 component 属性指定要展示的组件 Loging
  80. { path: '/login', component: Login }
  81. ]
  82. })
  83. 3)在 App.vue 中添加路由占位符:
  84. <template>
  85. <div id="app">
  86. <!-- 路由点位符 -->
  87. <router-view></router-view>
  88. </div>
  89. </template>
  90. 4)由于当前默认访问的是 `localhost:8080/#/` (即根路径),需要在其后手动添加 `/login`才能访问登录组件。
  91. 因此为了实现“只要用户访问了`/`(斜线)根路径,就自动重定向到 `/login` 址”,这里就必须添加一个重定向路由规则。即在路由文件 index.js 文件中添加一句 `{ path: '/', redirect: '/login' }`,如下所示:
  92. const router = new Router({
  93. routes: [
  94. // 重定向路由
  95. { path: '/', redirect: '/login' },
  96. // 当用户访问 /login 这个地址时,通过 component 属性指定要展示的组件 Loging
  97. { path: '/login', component: Login }
  98. ]
  99. })
  100. 5)然后需要添加公共样式,在 assets 文件夹下面添加 css文件夹,创建`global.css`文件,添加全局样式。
  101. /* 全局样式表 */
  102. html,body,#app{
  103. width: 100%;
  104. height: 100%;
  105. margin: 0;
  106. padding: 0;
  107. }
  108. 5)在入口文件 main.js 中导入 global.css,使得全局样式生效
  109. import "./assets/css/global.css"
  110. 6)然后,将 Login.vue 中的根元素也设置为撑满全屏(`height:100%`
  111. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 32]
  112. 7)在 `Login.vue` 中绘制登录框
  113. <div class="login_box"></div>
  114. 添加样式:
  115. .login_box {
  116. width: 450px;
  117. height: 300px;
  118. background-color: #fff;
  119. border-radius: 3px;
  120. position: absolute;
  121. left: 50%;
  122. top: 50%;
  123. transform: translate(-50%,-50%); /* 位移,x轴 y轴*/
  124. }
  125. > transform 属性,定义 2D 3D 转换,进行 旋转、缩放、移动、倾斜。其中,取值为`translate(x,y)` 2D 转换( CSS3 `transform` 属性的更多详情点击 [这里][Link 3] )。
  126. 添加样式后效果如下:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 33]
  127. 8)绘制顶部的默认头像盒子 `avatar_box`
  128. <div class="login_box">
  129. <div class="avatar_box">
  130. <img src="../assets/logo.png" alt="">
  131. </div>
  132. </div>
  133. `avatar_box` css 样式(嵌套到 `login_box`的样式内):
  134. .avatar_box {
  135. height: 130px;
  136. width: 130px;
  137. border: 1px solid #eee;
  138. border-radius: 50%;
  139. padding: 10px;
  140. box-shadow: 0 0 10px #ddd; /* 添加盒子阴影 */
  141. position: absolute;
  142. left:50%;
  143. transform: translate(-50%,-50%);
  144. background-color: #fff; img {
  145. width: 100%;
  146. height: 100%;
  147. border-radius:50%;
  148. background-color: #eee;
  149. }
  150. }
  151. > `box-shadow` 属性向盒子添加一个或多个阴影,该属性是由逗号分隔的阴影列表,每个阴影由 2-4 个长度值、可选的颜色值以及可选的 inset 关键词来规定。省略长度的值是 0 更多详情点击 [这里][Link 4] )。
  152. **效果如下:**
  153. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 34]
  154. 9)**登录页面的布局**
  155. 通过 Element-UI 实现 页面布局:
  156. * `el-form`
  157. * `el-form-item`
  158. * `el-input`
  159. * `el-button`
  160. * `字体图标`
  161. 打开官网 [element-cn.eleme.io/\#/zh-CN][element-cn.eleme.io_zh-CN] 找到组件:
  162. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 35]
  163. --------------------
  164. 点击顶部导航的 组件 ”,侧边栏中选择 Form 表单,再点击 显示代码 ”:
  165. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 36]
  166. --------------------
  167. ![在这里插入图片描述][20201230202054722.png]
  168. 复制一个 item 项的代码(如下所示),粘贴到 Login 组件中并将不需要用到属性绑定删除、添加结束标签后如下所示:
  169. <el-form label-width="80px">
  170. <el-form-item label="活动名称">
  171. <el-input ></el-input>
  172. </el-form-item>
  173. </el-form>
  174. 保存后查看页面,发现控制台报错访问不到这几个元素:
  175. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 37]
  176. 这是因为 element-UI 是通过按需导入来使用的,必须先导入才能正常使用。
  177. 此时,打开 plugins 文件下的 element.js 文件,导入需要的组件(如果分几次导入可能会报错):
  178. import { Button, Form, FormItem, Input } from 'element-ui'
  179. Form 组件 input 输入框代码里不需要的文本 label="活动名称"去掉,再把占位的 `label-width="80px"`重置为 0,并复制 2 `el-form-item` 元素结构代码(增加一个密码输入框和一个按钮区)
  180. <!-- 登录表单区 -->
  181. <el-form label-width="0">
  182. <!-- 用户名 -->
  183. <el-form-item>
  184. <el-input ></el-input>
  185. </el-form-item>
  186. <!-- 密码 -->
  187. <el-form-item>
  188. <el-input ></el-input>
  189. </el-form-item>
  190. <!-- 按钮区 -->
  191. <el-form-item>
  192. </el-form-item>
  193. </el-form>
  194. 保存后页面的效果如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 38]
  195. Element [官网][Link 5] 找到 button 组件,复制 button 按钮的代码:
  196. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 39]
  197. 将复制的代码粘贴到按钮区,并给它添加一个`btns`的类名,以便设置样式,代码如下:
  198. <!-- 登录表单区 -->
  199. <el-form label-width="0">
  200. <!-- 用户名 -->
  201. <el-form-item>
  202. <el-input ></el-input>
  203. </el-form-item>
  204. <!-- 密码 -->
  205. <el-form-item>
  206. <el-input ></el-input>
  207. </el-form-item>
  208. <!-- 按钮区 -->
  209. <el-form-item class="btns">
  210. <el-button type="primary">登录</el-button>
  211. <el-button type="info">重置</el-button>
  212. </el-form-item>
  213. 由于 `<el-button>` 按钮默认是靠左对齐,实际需要它 靠右对齐,在 `Login.vue` `<style>` 标签内部写上css 样式:
  214. .btns {
  215. display: flex; /* 弹性布局 */
  216. justify-content: flex-end; /* 横轴 项目位于容器的尾部*/
  217. }
  218. > `Flex` 弹性布局,可以简便、完整、响应式地实现各种页面布局:通过给父盒子添加`flex`属性,来控制子盒子的位置和排列方式,点击 >> [查看详情][Link 6]
  219. > `justify-content` 用于设置或检索弹性盒子元素在主轴(横轴)方向上的对齐方式,访问 W3Cschool >>[查看详情][Link 7]
  220. > 注: 当将父盒子设为 flex 布局后,子元素的 floatclear vertical-align 属性将失效。
  221. 此时,页面效果如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 40]
  222. 接下来,将整个 Form 表单区域底部对齐,这需要给 `<el-form>`标签添加一个类名`login_form`,并添加样式:
  223. .login_form {
  224. position: absolute;
  225. bottom: 0;
  226. width:100%;
  227. padding: 0 20px;
  228. }
  229. 效果如图:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 41]
  230. `padding: 0 20px` 后,撑大了盒子,这是因为`form`表单的`boxsizing` 属性值默认为 `content-box`(即传统盒模型),需将其设置为 border-box(即css3盒模型)
  231. .login_form {
  232. position: absolute;
  233. bottom: 0;
  234. width:100%;
  235. padding: 0 20px;
  236. box-sizing: border-box; /* C3盒模型 */
  237. }
  238. > box-sizing 属性定义了如何计算一个元素的 总宽度 总高度。只要在CSS中加上“box-sizing: border-box;”这句,那么就将一个普通的盒子变成CSS3盒模型,padding border就不会再撑大盒子。详情点击 >> [这里][Link 6]
  239. 添加后,效果如下所示:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 42]
  240. 接下来,绘制用户名和密码输入框前面的小图标。
  241. [Element UI 官网][Link 5] 找到 input 组件 菜单项,再找到对应的样式:
  242. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 43]
  243. --------------------
  244. 将复制的代码粘贴到项目文件 login.vue 中:
  245. <!-- 用户名 -->
  246. <el-form-item>
  247. <el-input prefix-icon="el-icon-search"></el-input>
  248. </el-form-item>
  249. <!-- 密码 -->
  250. <el-form-item>
  251. <el-input prefix-icon="el-icon-search"></el-input>
  252. </el-form-item>
  253. 生效后效果如下:
  254. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 44]
  255. 再从 [Element UI 官网][Link 5] 菜单中,点击侧边栏 icon 图标 菜单项,查找是否有对应的用户和密码图标:
  256. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 45]
  257. --------------------
  258. 由于在这里我们没有找到所需图标,因此要用到第三方图标库。
  259. 这里我们用 [*阿里图标库*][Link 8] ,将所需字体图标选定后,下载到本地,并在该字体压缩文件解压后,将所得文件夹命名为 fonts ,放到 src \\ assets \\ 目录下。
  260. 接着,在入口文件 main.js 中,导入字体图标的 css 样式表:
  261. // 导入字体图标
  262. import './assets/fonts/iconfont.css'
  263. 在浏览器中打开 fonts 文件夹下的 demo\_index.html HTML文件,查看使用示例,并将图标分别放置到 `Loging.vue` 组件文件相应的用户名、密码`input`标签内,替换原来的放大镜图标:
  264. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 46]
  265. ![在这里插入图片描述][20201231201917960.png]
  266. 代码如下:
  267. (注: `iconfont` 是基础类,不能缺少。`icon-xx`x 是图标名称)
  268. <!-- 用户名 -->
  269. <el-form-item>
  270. <el-input prefix-icon="iconfont icon-user"></el-input>
  271. </el-form-item>
  272. <!-- 密码 -->
  273. <el-form-item>
  274. <el-input prefix-icon="iconfont icon-3702mima"></el-input>
  275. </el-form-item>
  276. 替换原图标类名并保存,此时登录框 UI 效果如下:
  277. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 47]
  278. 10)**登录表单的数据绑定**(把用户名和密码对应的值自动绑定到数据源上):
  279. 打开 [Element UI][Link 5] 官网,找到 Form 表单的定义,在 典型表单 中展开代码结构,能看到第一行 `<el-form>` 上,有个属性绑定 `:model`就代表数据绑定:
  280. --------------------
  281. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 48]
  282. --------------------
  283. 即,示例中表示的是 `<el-form>` 表单中填写的所有数据,都会自动同步到 `:model`指向的 "`form`"对象上,form 是个数据对象,其定义如下所示:
  284. --------------------
  285. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 49]
  286. --------------------
  287. **归纳:表单添加数据绑定的步骤如下:**
  288. 1)第一步:先给 `<el-form>` 添加 :model 属性进行数据绑定,指向一个数据对象;
  289. 2)第二步:为每个表单项里的文本输入框,通过`v-model`绑定到数据对象上对应的属性中。
  290. ***实现如下**:*
  291. 1)打开 Login.vue 文件,通过`:model` 绑定一个新命名的数据对象 `loginForm` ,代码如下:
  292. <!-- 登录表单区 -->
  293. <el-form :model="loginForm" label-width="0" class="login_form">
  294. <!-- 省略不相关代码-->
  295. </el-form>
  296. 2)接下来,在 script 标签所在的行为区域中,定义 loginForm 这个数据对象:
  297. <script>
  298. export default {
  299. data () {
  300. return {
  301. // 登录表单的数据绑定对象
  302. loginForm: {
  303. username: 'admin',
  304. password: '123456'
  305. }
  306. }
  307. }
  308. }
  309. </script>
  310. 3)通过`v-model` loginForm 里的对象 `username` `password` ,分别双向绑定到用户名和密码输入框上,代码如下:
  311. <!-- 登录表单区 -->
  312. <el-form :model="loginForm" label-width="0" class="login_form">
  313. <!-- 用户名 -->
  314. <el-form-item>
  315. <el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input>
  316. </el-form-item>
  317. <!-- 密码 -->
  318. <el-form-item>
  319. <el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></el-input>
  320. </el-form-item>
  321. <!-- 此处省略按钮区代码-->
  322. <el-form>
  323. 此时,实现效果如下图:
  324. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 50]
  325. 再为密码输入框添加一个`type`属性,属性值设为 `password`,以使密码以 `*` 号显示,保证用户账户的安全性。
  326. <el-input type="password" v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></el-input>
  327. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 51]
  328. 11)**实现表单的数据验证**
  329. **目标**:当填写完用户名和密码,只要鼠标一离开文本框,就会立即对填写的数据进行合法性的校验。要如何添加数据验证行为呢?
  330. **方法**:打开 [Element UI ][Link 5] 官网, Form表单,找到该组件页面后面的表单验证:
  331. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 52]
  332. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 53]
  333. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 54]
  334. **分析:** 如上图所示展开的结构代码,解读如下,
  335. 首先,要为`<el-form>`组件,进行属性绑定,绑定`rules` 属性,其属性值是一个表单验证规则对象;
  336. 其次,该验证规则对象要在`<script>` 标签行为区域中的 data 数据里进行定义,其中,每一个属性都是一个验证规则。
  337. 最后,为不同的表单 item 项,通过 `prop` 属性来指定不同的验证规则,来进行表单的验证。
  338. **具体实现:**
  339. 1)打开登录组件 login.vue ,在 `<el-form>`元素上通过`:rules` 绑定 `loginFormRules`这个新的验证规则对象:
  340. <!-- 登录表单区 -->
  341. <el-form :model="loginForm" :rules="loginFormRules" label-width="0" class="login_form">
  342. 2)复制 `loginFormRules` 对象名称,到 login.vue文件的 script 行为区域中的 data 中进行定义:
  343. // 表单的验证规则对象
  344. loginFormRules: {
  345. // 验证用户名是否合法
  346. username: [],
  347. // 验证密码是否合法
  348. password: []
  349. }
  350. 验证规则可直接到 [Element UI][Link 5] 官网中进行复制: ![在这里插入图片描述][20201231223740361.png]
  351. --------------------
  352. 将复制过来的代码里的提示文字做适当修改,整理后代码如下:
  353. // 表单的验证规则对象
  354. loginFormRules: {
  355. // 验证用户名是否合法
  356. username: [
  357. { required: true, message: '请输入用户名', trigger: 'blur' },
  358. { min: 3, max: 10, message: '用户名要求长度在 3 到 10 个字符', trigger: 'blur' }
  359. ],
  360. // 验证密码是否合法
  361. password: [
  362. { required: true, message: '请输入登录密码', trigger: 'blur' },
  363. { min: 6, max: 15, message: '密码要求长度在 6 到 15 个字符', trigger: 'blur' }
  364. ]
  365. }
  366. 3)将定义的规则应用到表单中
  367. 分别在用户名和密码输入框的 `<el-form-item>`元素标签中,添加 prop 属性,并指向`username` `password`
  368. <!-- 用户名 -->
  369. <el-form-item prop="username">
  370. <el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input>
  371. </el-form-item>
  372. <!-- 密码 -->
  373. <el-form-item prop="password">
  374. <el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima" type="password"></el-input>
  375. </el-form-item>
  376. 此时完成效果如下图:![在这里插入图片描述][20201231224726729.gif_pic_center]
  377. --------------------
  378. **12)实现表单的重置功能**
  379. **目标:** 当点击 重置 ”按钮时,能够重置校验结果
  380. 如何实现 查看 [Element UI][Link 5] 官方文档,在`Form`组件对应的页面中,底部提供了一个方法:
  381. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 55]
  382. 只要我们获取到了表单的实例对象,就可通过实例对象直接访问(调用) resetFields 函数。从而重置整个表单,将所有字段值重置为初始值并移除校验结果。
  383. 如何拿到表单的实例对象
  384. **分析:**
  385. 要拿到 `<el-form>`组件的实例对象,需要给它添加一个`ref` 引用,引用名称我们定义为 loginFormRef
  386. 接下来,只要能够获取到 loginFormRef,就能拿到 `el-form` 表单的实例对象,即 loginFormRef 就是表单的实例对象,可以直接通过它来调用 resetFields 函数,以重置表单。
  387. **实现过程**:
  388. 为组件添加 `ref` 引用:
  389. <!-- 登录表单区 -->
  390. <el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0" class="login_form">
  391. 通过`@click` **重置** 按钮绑定 **单击事件** resetLoginForm
  392. <!-- 按钮区 -->
  393. <el-form-item class="btns">
  394. <el-button type="primary">登录</el-button>
  395. <el-button type="info" @click="resetLoginForm">重置</el-button>
  396. </el-form-item>
  397. script `export default` 中定义\* resetLoginForm 方法,先打印出 `this` ,查看其具体指向。
  398. methods: {
  399. // 点击重置按钮,重置登录表单
  400. resetLoginForm () {
  401. console.log(this)
  402. }
  403. }
  404. 从打印结果可以看到,`this` 指向的是一个组件的实例对象 `VueComponent{...}`,展开该对象下,可见一个数据对象 `$refs: {loginFormRef: VueComponent}`,其中的一个属性 `loginFormRef` 就是前面为 el-form 定义的 ref 引用名称,如下图所示:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 56]
  405. 由此可知,通过 `this.$refs` 可以直接获取到 resetLoginForm 这个引用对象,该引用对象就是 el-form 的实例。代码及效果如下:
  406. methods: {
  407. // 点击重置按钮,重置登录表单
  408. resetLoginForm () {
  409. // console.log(this)
  410. this.$refs.loginFormRef.resetFields()
  411. }
  412. }
  413. ![在这里插入图片描述][20210101002126923.gif_pic_center]
  414. 这样,就实现了当点击 重置 ”按钮时,重置校验结果的功能。而文本框好像没有被清空是什么原因呢 ?其实这是一种错觉。那是因为文本框是双向绑定到 `data` 中的数据中,而该数据是设置了默认值的,重置后,就又把默认值写进了已清空的文本框。
  415. --------------------
  416. 13)**登录前的预校验**
  417. 当我们点击登录按钮时,不应该直接发起数据请求,而是在请求之前先对表单数据进行预验证,验证通过时,才允许发起网络请求。否则,应该直接提示用户表单数据 不合法
  418. **实现思路:** 当点击登录时,通过调用表单的某个函数来验证。具体调用哪个函数呢 同样是在 [Element UI][Link 5] 官网中,找到 组件 => Form表单 => Form Methods 节点的 `validate`函数: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 57]
  419. Function(callback: Function(boolean, object))
  420. 该函数接收一个 `callback` 回调函数,回调函数的第 1 个行参是布尔值代表校验的结果。如果校验通过,则返回 `true`;反之,则返回`false`
  421. 如何调用 `validate` 函数
  422. **调用方法:**
  423. * 通过`@click` 登录 按钮绑定单击事件(事件处理函数命名为 `login`);
  424. <el-button type="primary" @click="login">登录</el-button>
  425. * 通过 `ref` 获取到表单的引用对象,再用该引用对象调用 `validate` 函数:
  426. login () {
  427. this.$refs.loginFormRef.validate(valid => {
  428. console.log(valid) // 测试能否获取验证结果
  429. })
  430. 接着,判断验证结果,决定是否发起登录请求。
  431. login () {
  432. this.$refs.loginFormRef.validate(valid => {
  433. // console.log(valid)
  434. if (!valid) return false
  435. })
  436. }
  437. **注意!** 由于此时项目中还没有 **全局配置** axios 包,无法发起请求,因此,要先在入口文件 main.js 中对 axios 进行全局配置:
  438. * 导入 axios 包:
  439. import axios from 'axios'
  440. * 把包挂载到 **Vue** 原型对象上:
  441. Vue.prototype.$http = axios
  442. 这样,每个 **Vue** 的组件都可以通过 `this` 直接访问到 `$http`,从而发起 ajax 请求
  443. * 当挂载为原型的属性之后,回头再为 ajax 设置请求的根路径:
  444. axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
  445. (*这个根路径在项目的 API 文档中能找到*)
  446. 3 个步骤的完整代码如下:
  447. import axios from 'axios'
  448. // 配置请求的根路径
  449. axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
  450. Vue.prototype.$http = axios
  451. 配置完 ajax ,现在回到 Login.vue 登录组件中,通过 `this` 就可以访问到原型上的 `$http` 成员,从而通过它发起 Ajax 请求 (请求地址 login,请求方式 post):
  452. login () {
  453. this.$refs.loginFormRef.validate(valid => {
  454. // console.log(valid)
  455. if (!valid) return false
  456. const result = this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars
  457. console.log(result)
  458. })
  459. }
  460. **注解:** data 中,有个 `loginFrom`,也就是登录表单的数据绑定对象,由于用户在`el-form`表单中填写的数据都会自动同步到该对象。因此可以直接将这个 loginFrom 当做请求参数。
  461. 接下来,启动 MySQL 数据库服务~
  462. 现在,可以测试一下:在登录表单中随便填入用户名和密码,点击 登录 ,看看请求结果 `result` 输出了什么,如下图所示:
  463. ![在这里插入图片描述][20210102232233259.png]
  464. 可以看到在控制台输出的是 Promise 对象,我们知道,如果某个方法的返回值是 Promise,那么就可以用 `async``await` 来简化这次 Promise 操作,因此前面的代码可以写成:
  465. login () {
  466. this.$refs.loginFormRef.validate(async valid => {
  467. // console.log(valid)
  468. if (!valid) return false
  469. const result = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars
  470. console.log(result)
  471. })
  472. }
  473. **注:** 如果仅添加 await 会报错:‘await is only allowed within async functions。这是因为 `await` 只能用在被 `async` 修饰的方法中(这里就是把箭头函数 valid 修饰成 异步 的函数)。
  474. 修改完毕再次打印时,可发现 result 已不再是一个 Promise 对象,而是一个具体的响应对象,对象中包含了 6 个属性,都是 axios 帮我们封装好的,其中,data 才是服务器返回的真实数据(其它 5 个属性我们不需要),如下图所示: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 58]
  475. 此时,我们可以从 result 对象身上的 data 属性给解构赋值出来并重命名为 res ,这样就表示能访问到真实的数据了:
  476. const { data: res} = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars
  477. console.log(res)
  478. 随意输入用户名、密码,点击 登录 按钮后,控制台打印如下:
  479. ![在这里插入图片描述][20210103000151725.png]
  480. 现在,我们来对登录状态进行判断:
  481. login () {
  482. this.$refs.loginFormRef.validate(async valid => {
  483. // console.log(valid)
  484. if (!valid) return false // 阻止登录请求
  485. const { data: res } = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars
  486. // console.log(res)
  487. if (res.meta.status !== 200) return alert('登录失败!')
  488. alert('登录成功!')
  489. })
  490. }
  491. 页面测试如下:
  492. ![在这里插入图片描述][20210103001448735.gif_pic_center]
  493. (注:演示中刷新网页时,由于加载了默认的正确用户名和密码,所以获取了登录成功的验证码)
  494. 接着,借助 [Element UI][] Message 弹出层,给登录添加登录成功与否的消息提示,这样显得更加友好,如下所示:
  495. --------------------
  496. ![在这里插入图片描述][2021010522165480.gif_pic_center]
  497. --------------------
  498. * **消息组件的使用**
  499. * 1)在 `element.js`中导入 Message 组件:
  500. import { Button, Form, FormItem, Input, Message } from 'element-ui'
  501. // Vue.use(Message) // 错误的写法
  502. **注意**:Message的配置和其它组件不一样,它需要进行全局挂载,即把 Message 挂载为 **Vue** 原型上的一个属性;
  503. * 2)**配置 Message**
  504. // import { Button, Form, FormItem, Input, Message } from 'element-ui'
  505. Vue.prototype.$Message = Message
  506. **注:** 这里的 `$Message` 是我们的自定义属性名,可取任意合法名。“`=` 后面的 Message 是组件名,必须按这个拼写来写(即,把弹框组件挂载到了原型对象上,这样的话,每一个组件都可以通过 this 来访问到 `$Message`进行弹窗提示! )。
  507. * 3 **Message 的使用**
  508. // if (res.meta.status !== 200) return alert('登录失败!')
  509. // alert('登录成功!')
  510. // 修改为
  511. if (res.meta.status !== 200) return this.$message.error('登录失败!')
  512. this.$message.success('登录成功!')
  513. 此时,效果如下图所示:![在这里插入图片描述][20210105225650760.gif_pic_center]
  514. 最终,**完整的 Login.vue 组件代码**,如下所示:
  515. --------------------
  516. <template>
  517. <div class="login_container">
  518. <!-- 登录盒子 -->
  519. <div class="login_box">
  520. <!-- 头像 -->
  521. <div class="avatar_box">
  522. <img src="../assets/logo.png" alt="">
  523. </div>
  524. <!-- 登录表单 -->
  525. <el-form :model="loginForm" ref="LoginFormRef" :rules="loginFormRules" label-width="0px" class="login_form">
  526. <!-- 用户名 -->
  527. <el-form-item prop="username">
  528. <el-input v-model="loginForm.username" prefix-icon="iconfont icon-user" ></el-input>
  529. </el-form-item>
  530. <!-- 密码 -->
  531. <el-form-item prop="password">
  532. <el-input type="password" v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></el-input>
  533. </el-form-item>
  534. <!-- 按钮 -->
  535. <el-form-item class="btns">
  536. <el-button type="primary" @click="login">登录</el-button>
  537. <el-button type="info" @click="resetLoginForm">重置</el-button>
  538. </el-form-item>
  539. </el-form>
  540. </div>
  541. </div>
  542. </template>
  543. <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) //保存token window.sessionStorage.setItem('token', res.data.token) // 导航至/home this.$router.push('/home') }) } } } </script>
  544. <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>
  545. --------------------
  546. **其中,有用到以下内容,需要进行进一步处理:**
  547. Ⅰ. **添加 element-ui 的表单组件**
  548. plugins 文件夹中打开 element.js 文件,进行 elementui 的按需导入:
  549. import Vue from 'vue'
  550. import { Button } from 'element-ui'
  551. import { Form, FormItem } from 'element-ui'
  552. import { Input } from 'element-ui'
  553. Vue.use(Button)
  554. Vue.use(Form)
  555. Vue.use(FormItem)
  556. Vue.use(Input)
  557. **Ⅱ. 添加第三方字体**
  558. 复制素材中的 fonts 文件夹到 assets 中,在入口文件 main.js 中导入:
  559. import './assets/fonts/iconfont.css'
  560. 然后直接 `<el-input prefix-icon="iconfont icon-3702mima"></el-input>`
  561. 接着添加登录盒子
  562. **Ⅲ. 添加表单验证的步骤**
  563. 1)给`<el-form>`添加属性`:rules="rules"`rules 是一堆验证规则,定义在 script 中。
  564. 2)在 script 中添加 rulesexport default\{ data()\{return\{…, rules: \{
  565. name: [
  566. { required: true, message: '请输入活动名称', trigger: 'blur' },
  567. { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
  568. ],
  569. region: [
  570. { required: true, message: '请选择活动区域', trigger: 'change' }
  571. ]
  572. 3)通过`<el-form-item>` prop 属性设置验证规则`<el-form-item label="活动名称" prop="name">`
  573. 4)导入axios 以发送 ajax 请求
  574. 打开main.js,导入 axios
  575. import axios from 'axios';
  576. 设置请求的根路径:
  577. axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/';
  578. 挂载axios
  579. Vue.prototype.$http = axios;
  580. 5)配置弹窗提示
  581. plugins 文件夹中打开 element.js 文件,进行 elementui 的按需导入:
  582. import { Message} from 'element-ui'
  583. 进行全局挂载:
  584. Vue.prototype.$message = Message;
  585. login.vue 组件中编写弹窗代码:
  586. this.$message.error('登录失败')

六、登录成功之后的操作

  • 实现思路:

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

      1. 原因如下:
      • 项目中除了 登录 以外的其它 API 接口 ,必须在登录之后才能访问(访问接口时提供 token,表明已登录);
      • token 只应在当前网站打开期间生效,所以将 token 保存在 sessionStorage 中。
      • 之所以保存在 sessionStorage 而不是 localStorage 中,是因为localStorage是持久化的存贮机制,而sessionStorage是会话期间的存贮机制。
  1. * 2.通过 编程式导航 跳转到后台主页,路由地址是 /home (编程式导航:通过 `$router`对象,调用 `push` 方法来发生跳转)。
  • 代码实现

    • A. 保持用户token信息

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

      1. console.log(res)

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

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

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

  1. 因此,当登录成功之后,我们将后台返回的 token 保存到 sessionStorage 中,操作完毕之后,需要跳转到`/home`目录:
  2. this.$router.push('/home')
  3. 此时 login 方法的代码如下:
  4. login () {
  5. this.$refs.loginFormRef.validate(async valid => {
  6. // console.log(valid)
  7. if (!valid) return false
  8. const { data: res } = await this.$http.post('login', this.loginForm) // eslint-disable-line no-unused-vars
  9. // console.log(res)
  10. if (res.meta.status !== 200) return this.$message.error('登录失败!')
  11. this.$message.success('登录成功!')
  12. // console.log(res)
  13. window.sessionStorage.setItem('token', res.data.token)
  14. this.$router.push('/home')
  15. })
  16. }
  17. 调试运行程序,当点击登录后,Application 里的 session Storage 中保存到了 `token` 值。同时,通过这次 编程式导航,发生了页面跳转。如下所示:
  18. ![在这里插入图片描述][2021010523562910.gif_pic_center]
  19. 但此时的 home 页面是空白的。
  • 创建 home 页面并完善路由规则

    • 1)创建 home 页面:在 components 目录中,新建一个 Home.vue 组件文件; 在这里插入图片描述
    • 2)用 template 标签书写好基本结构代码:

      1. <template>
      2. <div>
      3. Home 组件
      4. </div>
      5. </template>
      6. <script> export default { } </script>
      7. <style lang="less" scoped> </style>
  1. * 3)在路由文件夹下的`index.js`中导入组件并添加 路由规则:
  2. import Home from '../components/Home.vue'
  3. export default new VueRouter({
  4. routes: [
  5. { path: '/', redirect: '/login' },
  6. { path: '/login', component: Login },
  7. { path: '/home', component: Home }
  8. ]
  9. })
  10. * **路由导航卫士控制页面访问权限**
  11. 当前“ `/home` 所对应的页面只有在登录的时候才允许被访问。
  12. 因此,如果用户未登录,直接通过 URL 访问特定(需要权限)的页面,则需要重新导航(*即强制跳转* )到登录页面。
  13. 那么,如何进行导航
  14. 这就需要用到路由 **导航守卫**:为路由对象 router 调用一个 beforeEach 函数,这个 beforeEach 就叫做 **导航守卫**
  15. router.beforeEach ((to, from, next) => { })
  16. 该函数接收一个 **回调函数**,包括 3 个行参,分别为`to``from``next`,其中:
  17. * `to`:将要访问的路径;
  18. * `from`:从哪个页面跳转过来;
  19. * `next`:放行的一个函数,`next()`表示直接放行;`next('/login')`表示强制跳转。
  20. * **路由导航卫士的用法**
  21. 判断 `to` 对应的地址是否为`/login`
  22. * 如果是,则表示用户要访问登录页,而登录页是不需要权限的,这时就直接调用 `next` 函数(即 `return next()`)放行,允许访问登录页;
  23. * 如果不是,则要判断 sessionStorage 中是否存在 token
  24. 先把 token 取出来,然后判断是否有 token ,如果没有 token,证明用户没有登录,此时需要强制跳转到登录页(`/login`),让用户登录后再进行访问。如果有 token 则直接放行(`next ()`
  25. * **代码实现**(导航跳转):
  26. 打开路由所对应的文件(有些版本的 Vue ui 生成的路由文件名是 router.js),我这里是 `index.js`,我们在前面已完成的代码:
  27. import Vue from 'vue'
  28. import VueRouter from 'vue-router'
  29. import Login from '../components/Login.vue'
  30. import Home from '../components/Home.vue'
  31. Vue.use(VueRouter)
  32. export default new VueRouter({
  33. routes: [
  34. { path: '/', redirect: '/login' },
  35. { path: '/login', component: Login },
  36. { path: '/home', component: Home }
  37. ]
  38. })
  39. 这里需要对代码进行改造,当前`export default new VueRouter({})` 表示是直接 new 了一个 VueRouter 对象并默认导出。
  40. 此时需要将 `export default` `VueRouter`拆分开,先拿到 VueRouter 对象,给它挂载一个 **导航守卫**,然后再用 export default 暴露出去。修改如下:
  41. const router = new VueRouter({
  42. routes: [
  43. { path: '/', redirect: '/login' },
  44. { path: '/login', component: Login },
  45. { path: '/home', component: Home }
  46. ]
  47. })
  48. // 暴露路由对象之前,要挂载路由守卫
  49. router.beforeEach((to, from, next) => {
  50. if (to.push === '/login') return next()
  51. // 获取 token
  52. const tokenStr = window.sessionStorage.getItem('token')
  53. if (!tokenStr) return next('/login')
  54. next()
  55. })
  56. export default router

3.4 实现退出功能

  • 1、实现原理

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

  • 2、功能实现

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

    1. export default {
    2. methods:{
    3. logout(){
    4. // 清空token
    5. window.sessionStorage.clear();
    6. // 跳转到登录页
    7. this.$router.push('/login');
    8. }
    9. }
    10. }

处理 ESLint 警告

  • 补充
  1. * **A、处理 ESLint 警告**
  2. 打开脚手架面板,查看 **警告** 信息
  3. 默认情况下,ESLint VS code 格式化工具有 冲突,需要添加配置文件解决冲突。
  4. * 1)在项目根目录新建 `.prettierrc.json` 文件(注意prettierrc前面有小点):
  5. {
  6. "semi":false, // 结尾处的分号(;)
  7. "singleQuote":true // 单引号
  8. }
  9. * 2)打开`.eslintrc.js`文件,禁用对 space-before-function-paren 的检查:
  10. rules: {
  11. 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
  12. 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
  13. 'space-before-function-paren' : 0
  14. },
  15. 3)**\*其它方法**(注:此方法来源于网络,未亲测!仅记录于此供参考)
  16. 如果在使用 vue-cli 创建项目时已经选择了 `babel``eslint`,那么只需要安装缺少的包:
  17. npm i prettier prettier-eslint --save-dev
  18. 这样也能得到正确的格式,其原理是先把代码用 prettier 格式化,然后再用 ESLint fix
  19. * **B、合并按需导入的 element-ui**
  20. import Vue from 'vue'
  21. import { Button, Form, FormItem, Input, Message } from 'element-ui'
  22. Vue.use(Button)
  23. Vue.use(Form)
  24. Vue.use(FormItem)
  25. Vue.use(Input)
  26. // 进行全局挂载:
  27. Vue.prototype.$message = Message
  28. * **C.将代码提交到码云**
  29. 新建一个项目终端,输入命令查看修改过的或新增的文件内容:
  30. git status
  31. 将所有文件添加到暂存区:
  32. git add.
  33. 此时,所有文件变成绿色,表示已经都添加到了暂存
  34. 将所有代码提交到本地仓库:
  35. git commit -m "添加登录功能以及/home的基本结构"
  36. 查看所处分支(所有代码都被提交到了 login 分支):
  37. git branch
  38. login 分支代码合并到 master 主分支:
  39. a、先切换到 master
  40. git checkout master
  41. b、再 master 分支进行代码合并:
  42. git merge login
  43. 将本地最新的 master 推送到远端的码云:
  44. git push
  45. 打开码云中的仓库如下图(表示已把本地的master 分支推送到了云端仓库中进行了保存)。![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 60]
  46. 推送本地的子分支到码云
  47. 如下图所示,当前云端中只有一个 master 分支,并没有 login 子分支:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 61]
  48. 因此还需要将 login 分支推送到云端:
  49. a. 先切换到子分支:
  50. git checkout 分支名
  51. b. 然后推送到码云:
  52. 此时如果直接用 git push 推送是不会成功的,因为云端并没有记录 login 子分支。
  53. 由于是首次把 login 分支推送到云端分支,此时,需要在 `push` 加上一个参数 `-u`,完整命令如下:
  54. git push -u origin 远端分支名
  55. 刷新后,即可看到仓库中多了一个 login 子分支:
  56. ![在这里插入图片描述][20210111224818835.png]
  57. 【**关于推送**】
  58. 我们写的源代码,经过测试之后没问题,一定要先合并到主分支,然后再将主分支推送到云端仓库中,同时,也不要忘了再把新建的子分支推送到云端仓库中。

在这里插入图片描述

4. 主页布局

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

4.1 后台首页基本布局

在这里插入图片描述

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

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

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

    在这里插入图片描述

  2. 找到符合主页设计图的样式:
    在这里插入图片描述
  3. 找到该布局结构的代码,展开后复制粘贴到项目中
  1. **基本布局**:打开 Home.vue 组件
  2. * 原来 Home.vue 文件中的结构代码:
  3. <template>
  4. <div>
  5. <el-button type="info" @click="logout">退出</el-button>
  6. </div>
  7. </template>
  8. 修改为官方提供的布局容器的结构:
  9. <template>
  10. <el-container>
  11. <!-- 头部区域 -->
  12. <el-header>Header<el-button type="info" @click="logout">退出</el-button></el-header>
  13. <!-- 页面主体区域 -->
  14. <el-container>
  15. <!-- 侧边栏 -->
  16. <el-aside width="200px">Aside</el-aside>
  17. <!-- 右侧内容主体 -->
  18. <el-main>Main</el-main>
  19. </el-container>
  20. </el-container>
  21. </template>
  22. 保存后,打开页面并没有看到想要的效果,并且终端里有报错,如下图所示:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 65]
  23. 这是因为我们还没有注册 el-container 这些组件。
  24. plugins 目录下的 `element.js` 中导入组件:
  25. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 66]
  26. 保存后,页面效果如下图:
  27. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 67]
  28. --------------------
  29. 此时,虽然很丑,但是已初具雏形,并且也解决了报错问题。
  30. 接下来,在 Home.vue 文件中的 `<style>`标签内部添加css样式。
  31. 默认情况下,类似 element-ui 组件的名称就是 类名,利用这个类名可直接给对应的组件添加样式
  32. .home-container {
  33. height: 100%;
  34. }
  35. .el-header{
  36. background-color:#373D41;
  37. }
  38. .el-aside{
  39. background-color:#333744;
  40. }
  41. .el-main{
  42. background-color:#eaedf1;
  43. }
  44. 保存并刷新页面后如下图:
  45. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 68]
  46. 接下来要解决一个问题,就是让整个页面主体区域撑满屏幕。需要先检查元素看是什么原因导致的
  47. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 69]
  48. 然后你可以看到,section 元素其实就是布局容器组件结构代码中最外层的 `<el-container>`,只要让它全屏,就能实现页面的布局效果。
  49. Home.vue 里的`<el-container>` 添加一个类名`home-container`并设置高为100%:
  50. <el-container class="home-container">
  51. .home-container {
  52. height:100%
  53. }
  54. 这样就实现了充满整个屏幕的效果,如下图所示:![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 70]

4.2 顶部布局,侧边栏布局

4.2.1. 顶部布局

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

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

    HTML:修改后的结构为:

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

    2). 添加CSS样式

    CSS:顶部原来的样式:

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

    CSS:顶部修改后的样式:

    1. .el-header{
    2. background-color:#373D41;
    3. display:flex;
    4. justify-content: space-between;
    5. padding-left: 10px;
    6. align-items: center;
    7. font-size: 20px;
    8. color:#f1d277; div {
    9. display: flex;
    10. align-items: center;
    11. }
    12. span {
    13. margin-left: 15px;
    14. user-select:none;
    15. }
    16. }

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

    效果如下图:
    在这里插入图片描述

4.2.2 侧边栏菜单布局

在这里插入图片描述
菜单分为二级,并且可以折叠。

主要结构如下:

  1. <el-menu>
  2. <el-submenu>
  3. <!-- 这个 template 是一级菜单的内容模板 -->
  4. <i class="el-icon-menu"></i>
  5. <span>一级菜单</span>
  6. <!-- 在一级菜单中,可以嵌套二级菜单 -->
  7. <el-menu-item>
  8. <i class="el-icon-menu"></i>
  9. <span slot="title">二级菜单</span>
  10. </el-menu-item>
  11. </el-submenu>
  12. </el-menu>

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

在 element UI 官网找到导航菜单组件:
在这里插入图片描述
在这里插入图片描述
点击 显示代码 ,找到 “ 自定义颜色 ” 菜单对应的代码:
在这里插入图片描述
选中所有的UI结构后,粘贴到Home.vue中的“侧边栏区域”。

侧边栏原代码:

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

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

  1. <!-- Home.vue 文件 -->
  2. <!-- 侧边栏 -->
  3. <el-aside width="200px">
  4. <!-- 侧边栏菜单区域 -->
  5. <el-menu background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
  6. <el-submenu index="1">
  7. <template slot="title">
  8. <i class="el-icon-location"></i>
  9. <span>导航一</span>
  10. </template>
  11. <el-menu-item-group>
  12. <template slot="title">分组一</template>
  13. <el-menu-item index="1-1">选项1</el-menu-item>
  14. <el-menu-item index="1-2">选项2</el-menu-item>
  15. </el-menu-item-group>
  16. <el-menu-item-group title="分组2">
  17. <el-menu-item index="1-3">选项3</el-menu-item>
  18. </el-menu-item-group>
  19. <el-submenu index="1-4">
  20. <template slot="title">选项4</template>
  21. <el-menu-item index="1-4-1">选项1</el-menu-item>
  22. </el-submenu>
  23. </el-submenu>
  24. <el-menu-item index="2">
  25. <i class="el-icon-menu"></i>
  26. <span slot="title">导航二</span>
  27. </el-menu-item>
  28. <el-menu-item index="3" disabled>
  29. <i class="el-icon-document"></i>
  30. <span slot="title">导航三</span>
  31. </el-menu-item>
  32. <el-menu-item index="4">
  33. <i class="el-icon-setting"></i>
  34. <span slot="title">导航四</span>
  35. </el-menu-item>
  36. </el-menu>
  37. </el-aside>

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

此时,由于侧边栏中用到的组件还没有 “ 按需 ” 导入,接下来打开plugins路径下的 element.js文件:
在这里插入图片描述
导入并注册以下组件:

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

代码如下:

  1. // element.js 文件
  2. import { Menu, Submenu, MenuItemGroup, MenuItem } from 'element-ui'
  3. Vue.use(Menu)
  4. Vue.use(Submenu)
  5. Vue.use(MenuItemGroup)
  6. Vue.use(MenuItem)

此时,实现的效果如下:
在这里插入图片描述
但是,当前侧边栏的颜色和整个侧边栏的颜色不一致,<el-menu>标签的background-color属性的值需要修改#333744

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

修改后的效果:
在这里插入图片描述
这个官方提供的侧边栏,菜单中默认有禁用、三级菜单等,而本项目只需要一级、二级菜单,多余的不需要。因此,需再次梳理,将不需要的去除。

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

    1. <!-- 侧边栏 -->
    2. <el-aside width="200px">
    3. <el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b">
    4. <!-- 一级菜单 -->
    5. <el-submenu index="1">
    6. <!-- 一级菜单的模板区 -->
    7. <template slot="title">
    8. <i class="el-icon-location"></i>
    9. <span>导航一</span>
    10. </template>
    11. <!-- 二级菜单 -->
    12. <el-menu-item index="1-4-1">选项1</el-menu-item>
    13. </el-submenu>
    14. </el-menu>
    15. </el-aside>

    如下图:在这里插入图片描述
    但是,此时二级菜单没有图标,需要将一级菜单中的 <i>标签和<span>标签复制后,粘贴到二级菜单 <el-menu-item>标签内:

    修改前:

    1. <!-- 二级菜单 -->
    2. <el-menu-item index="1-4-1">选项1</el-menu-item>

    修改后:

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

    基本结构修改完毕后,主页界面如下图所示:在这里插入图片描述

  1. 此时的 Home.vue 组件布局全部代码如下:
  2. <template>
  3. <el-container class="home-container">
  4. <!-- 头部区域 -->
  5. <el-header>
  6. <div>
  7. <img src="../assets/heima.png" alt="">
  8. <span>电商后台管理系统</span>
  9. </div>
  10. <el-button type="info" @click="logout">退出</el-button>
  11. </el-header>
  12. <!-- 页面主体区域 -->
  13. <el-container>
  14. <!-- 侧边栏 -->
  15. <el-aside width="200px">
  16. <el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b">
  17. <!-- 一级菜单 -->
  18. <el-submenu index="1">
  19. <!-- 一级菜单的模板区 -->
  20. <template slot="title">
  21. <i class="el-icon-location"></i>
  22. <span>导航一</span>
  23. </template>
  24. <!-- 二级菜单 -->
  25. <el-menu-item index="1-4-1">
  26. <i class="el-icon-location"></i>
  27. <span>导航一</span>
  28. </el-menu-item>
  29. </el-submenu>
  30. </el-menu>
  31. </el-aside>
  32. <!-- 右侧内容主体 -->
  33. <el-main>Main</el-main>
  34. </el-container>
  35. </el-container>
  36. </template>

4.3 通过接口获取菜单数据

  1. axios请求拦截器

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

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

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

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

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

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

    config 即是请求对象,里面包含了很多的属性。通过打印,可以看到它包含了请求头headers
    在这里插入图片描述

    接口说明:
    在这里插入图片描述

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

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

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

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

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

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

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

  1. 此时,`main.js`文件完整代码如下:
  2. import Vue from 'vue'
  3. import App from './App.vue'
  4. import router from './router'
  5. import './plugins/element.js'
  6. // 导入全局样式表
  7. import './assets/css/global.css'
  8. // 导入字体图标
  9. import './assets/fonts/iconfont.css'
  10. import axios from 'axios'
  11. // 配置请求的根路径
  12. axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
  13. axios.interceptors.request.use(config => {
  14. // console.log(config)
  15. config.headers.Authorization = window.sessionStorage.getItem('token')
  16. // 最后必须 return config,固定写法
  17. return config
  18. })
  19. Vue.prototype.$http = axios
  20. Vue.config.productionTip = false
  21. new Vue({
  22. router,
  23. render: h => h(App)
  24. }).$mount('#app')
  1. 发起请求,获取左侧导航菜单

    API 接口:

    • 请求路径:menus
    • 请求方法:get
    • 响应数据:
      在这里插入图片描述
  1. . **1)打开`Home.vue`文件,添加相关代码**
  2. **分析:** 网页刚一加载时,就应立即获取左侧菜单,因此需在行为区域定义一个生命周期函数:
  3. 添加前:
  4. <script>
  5. export default {
  6. methods: {
  7. logout () {
  8. window.sessionStorage.clear()
  9. this.$router.push('/login')
  10. }
  11. }
  12. }
  13. </script>
  14. 添加后:
  15. <script>
  16. export default {
  17. created() {
  18. this.getMenuList()
  19. },
  20. methods: {
  21. logout () {
  22. window.sessionStorage.clear()
  23. this.$router.push('/login')
  24. },
  25. // 获取所有菜单
  26. async getMenuList () {
  27. const { data: res } = await this.$http.get('menus')
  28. console.log(res)
  29. }
  30. }
  31. }
  32. </script>
  33. `res`控制台打印结果如下:
  34. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 82]
  35. 如上图,得到的是一个对象,显示 “获取菜单列表成功”。共5个一级菜单,而且在一级菜单中,通过`children`属性嵌套了自己的二级菜单。
  36. 为了下一步在页面中渲染出来,应把获取到的数据立即挂载到自己的 data 中,需定义一个组件的私有数据 `data`
  37. data () {
  38. return {
  39. // 左侧菜单数据
  40. menulist: []
  41. }
  42. }
  43. 判断:
  44. // 获取所有菜单
  45. async getMenuList () {
  46. const { data: res } = await this.$http.get('menus')
  47. if (res.meta.status !== 200) return this.$message.error(res.meta.msg)
  48. this.menulist = res.data
  49. console.log(res)
  50. }

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

  • A、请求侧边栏数据

    1. <script>
    2. export default {
    3. data() {
    4. return {
    5. // 左侧菜单数据
    6. menuList: null
    7. }
    8. },
    9. created() {
    10. // 在created阶段请求左侧菜单数据
    11. this.getMenuList()
    12. },
    13. methods: {
    14. logout() {
    15. window.sessionStorage.clear()
    16. this.$router.push('/login')
    17. },
    18. async getMenuList() {
    19. // 发送请求获取左侧菜单数据
    20. const { data: res } = await this.$http.get('menus')
    21. if (res.meta.status !== 200) return this.$message.error(res.meta.msg)
    22. this.menuList = res.data
    23. console.log(res)
    24. }
    25. }
    26. }
    27. </script
  • B、通过v-for双重循环渲染左侧菜单
    在这里插入图片描述
    如上图所示,由于左侧菜单数据的menuList组数中,存在一、二级菜单项,因此需要双层循环来渲染菜单,外层获取一级菜单,内层获取二级菜单:

    • 1)一级菜单

      添加循环渲染之前:

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

      添加循环渲染之后:

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

      效果如下:
      在这里插入图片描述
      但是可以发现,现在是点开一级菜单中的任何一个,所有的菜单项都会全部同时展开,这不符合要求(只能展开自己的菜单项,不能影响其它的)。

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

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

      此时,当点击当前菜单项时,其它的菜单项就不会再同步展开了,如下图所示:
      在这里插入图片描述
      but,我们也可以看到,控制台报错了,报错的原因是 index 只接收 “ 字符串 ” ,但是item.id是一个数值,最简单的方法,是给它拼接一个空字符串,item.id + '',如下所示:

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

      在这里插入图片描述
      至此,一级菜单渲染完成。

  • 2)二级菜单

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

    添加循环前:

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

    添加循环渲染后:

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

    在这里插入图片描述
    到此为止,左侧菜单的 结构 渲染完毕。

  1. 本部分的**结构**代码如下:
  2. <el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b">
  3. <!-- 一级菜单 -->
  4. <el-submenu :index="item.id+''" v-for="item in menuList" :key="item.id">
  5. <!-- 一级菜单模板 -->
  6. <template slot="title">
  7. <!-- 图标 -->
  8. <i class="el-icon-location"></i>
  9. <!-- 文本 -->
  10. <span>{
  11. {item.authName}}</span>
  12. </template>
  13. <!-- 二级子菜单 -->
  14. <el-menu-item :index="subItem.id+''" v-for="subItem in item.children" :key="subItem.id">
  15. <!-- 二级菜单模板 -->
  16. <template slot="title">
  17. <!-- 图标 -->
  18. <i class="el-icon-location"></i>
  19. <!-- 文本 -->
  20. <span>{
  21. {subItem.authName}}</span>
  22. </template>
  23. </el-menu-item>
  24. </el-submenu>
  25. </el-menu>

4.5 设置激活子菜单样式

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

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

在数据中添加一个 iconsObj:

  • 1 )左侧菜单美化

    • ① 修改左侧菜单激活菜单项文本的颜色
      在这里插入图片描述
      将最外层 <el-menu> 中的 active-text-color 属性的值修改为目标颜色#409eff即可
      在这里插入图片描述
    • ② 修改二级菜单项图标
      在 Element UI 官网中找到 icon 图标,将原来默认的图标类名修改即可:
      在这里插入图片描述

      1. <!-- 二级菜单 -->
      2. <el-menu-item :index="subItem.id + ''" v-for="subItem in item.children" :key="subItem.id">
      3. <!-- <i class="el-icon-location"></i> -->
      4. <i class="el-icon-menu"></i>
  1. * **③ 修改一级菜单图标**
  2. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 85]
  3. 由于每个一级菜单项的图标都不一样,不能像二级菜单那样统一添加一种图标。所以需要通过自定义图标的形式来解决。
  4. 这里仍然要用到前面使用过的第三方的字体图标库,之前已下载好放在 `\assets\fonts` 中:
  5. ![在这里插入图片描述][20210123120515293.gif_pic_center]
  6. 【**问题** 】:
  7. 前面我们的一级菜单的每一项,都是通过 for循环 自动生成的。那么,怎样让它在生成一级菜单期间,自动生成图标 ?
  8. 【**解决方案**】:
  9. data 中,先定义一个**字体图标对象** `iconsObj`,将每个菜单项的 id 做为 Key id 对应的图标做为值
  10. 找到一级菜单各项的 id 值,将其放在 iconsObj 中分别指向字体图标的类名
  11. ![在这里插入图片描述][20210123132525750.png]
  12. 定义字体图标对象的代码如下:
  13. data () {
  14. return {
  15. // 左侧菜单数据
  16. menulist: [],
  17. iconsObj: {
  18. '125': 'iconfont icon-user',
  19. '103': 'iconfont icon-showpassword',
  20. '101': 'iconfont icon-shangpin',
  21. '102': 'iconfont icon-danju',
  22. '145': 'iconfont icon-baobiao'
  23. }
  24. }
  25. },
  26. **注:** 如果语法报错,可把包裹 Key 的引号去掉,如:`125: 'iconfont icon-user'`
  27. 然后将图标类名进行数据绑定,绑定 iconsObj 中的数据:
  28. 接下来,修改原来的字体图标部分的代码,进行动态绑定,即每循环一次,根据 iconsObj 对象,把它 id 所对应的类取出来,放在图标标签`<i>`中:
  29. <!-- 一级菜单 -->
  30. <el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id">
  31. <!-- 一级菜单的模板区 -->
  32. <template slot="title">
  33. <i :class="iconsObj[item.id]"></i>
  34. <span>{
  35. {item.authName}}</span>
  36. </template>
  37. 保存后,图标就有了如下效果:
  38. ![在这里插入图片描述][2021012313473973.png]
  39. 需要给图标和菜单项文本增加间距。因为这些字体图标有一个共同的类 iconfont,因此只需在`<style></style>` 样式区域标签内,写上:
  40. .iconfont {
  41. margin-right: 10px;
  42. }
  • 2)每次只能展开一个菜单项并解决边框问题

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

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

      解决

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

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

      在这里插入图片描述
      unique-opened 属性值重置为 true 即可实现(每次只展开一个菜单项)。添加属性并重置为 true 如下:
      在这里插入图片描述
      注: 或者也可以数据绑定进行设置(此时true认为是一个bool值,而不是字符串) :unique-opened="true"

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

      在这里插入图片描述

  1. * **② 解决边框线未对齐的问题**
  2. ![在这里插入图片描述][20210123142407949.png]
  3. 通过检查元素,发现`el-menu`类的样式中,存在一个`border-right`,其值为 1px 的边框,如下图所示:
  4. ![在这里插入图片描述][20210123143205970.gif_pic_center]
  5. 通过类名选择器,将其重置为 none
  6. .el-aside {
  7. background-color:#333744; .el-menu {
  8. border-right: none;
  9. }
  10. }

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

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

    1. <!-- 侧边栏 -->
    2. <el-aside width="200px">
    3. <div class="toggle-button">|||</div>
    4. <el-menu background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened>
  2. Home.vue 中的<style>样式区域中,给展开折叠按钮盒子设置样式:

    1. .toggle-button {
    2. background-color: #4a5064;
    3. font-size: 10px;
    4. line-height: 34px;
    5. color: #fff;
    6. text-align: center;
    7. letter-spacing: 0.4em;
    8. cursor: pointer;
    9. }

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

  1. 效果如下:
  2. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 87]
  3. 要实现折叠与展开功能,在按钮盒子上,定义一个事件名称 toggleCollapse,给按钮盒子绑定**单击事件**:
  4. <div class="toggle-button" @click="toggleCollapse">|||</div>
  5. 接下来,需要在方法 methods **定义事件函数**。
  6. 【**分析**】:
  7. 由于 **Element UI** 官网中,[NavMenu导航菜单][NavMenu] Menu 属性上提供了一个 collapse 参数,值类型为 **布尔值**:
  8. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 88]
  9. 因此,我们只需要在点击按钮时,切换 `<el-menu>` 标签的 collapse 属性值为 true false 即可实现展开与折叠,回到代码中:
  10. 1)在 data 中,定义一个布尔值 isCollapse
  11. data () {
  12. return {
  13. // 左侧菜单数据
  14. menulist: [],
  15. iconsObj: { ...},
  16. // 左侧菜单栏是否水平折叠:默认不折叠
  17. isCollapse: false
  18. }
  19. },
  20. 再将这个布尔值`isCollapse`绑定到 `<el-Menu>`
  21. <!-- 侧边栏 -->
  22. <el-aside width="200px">
  23. <div class="toggle-button" @click="toggleCollapse">|||</div>
  24. <el-menu background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened :collapse="isCollapse">
  25. 3)事件处理函数:
  26. // 点击按钮,切换菜单的折叠与展开
  27. toggleCollapse() {
  28. this.isCollapse = !this.isCollapse
  29. }
  30. 运行效果如下:
  31. ![在这里插入图片描述][20210123160915123.gif_pic_center]
  32. 可以发现,默认的展开和折叠的动画效果很丑,为了让它流畅一些,需要把默认的动画效果关闭。
  33. [Element UI NavMenu 导航菜单][NavMenu] Menu 属性中,有个 collapse-transition 参数用于控制动画显示与否,默认参数值为 true ,因此,如将其值设为 false 即可关闭动画。
  34. 继续在`<el-menu>`中,绑定collapse-transition 属性,以关闭动画:
  35. <el-menu background-color="#333744" text-color="#fff" active-text-color="#409eff" unique-opened :collapse="isCollapse" :collapse-transition="false">
  36. 但是,此时通过运行发现,菜单栏的背景并没有因为菜单的折叠而变小,如下所示:
  37. ![在这里插入图片描述][20210123162257819.gif_pic_center]
  38. 这是因为 模板结构中,侧边栏的宽度是写死的:
  39. <!-- 侧边栏 -->
  40. <el-aside width="200px">
  41. 正常的是,当折叠后,即 isCollapse 值为 true 时,侧边栏宽度变小,其值为 false 时重置为`200px`即可。
  42. ![在这里插入图片描述][20210123163157305.png]
  43. 由元素检查可知最小宽度为 64px ,所以,要绑定的宽度大小,用三元表达式判断的代码如下:
  44. <!-- 侧边栏,宽度根据是否折叠进行设置 -->
  45. <el-aside :width="isCollapse ? '64px':'200px'">
  46. <!-- 伸缩侧边栏按钮 -->
  47. <div class="toggle-button" @click="toggleCollapse">|||</div>
  48. <!-- 侧边栏菜单,:collapse="isCollapse"(设置折叠菜单为绑定的 isCollapse 值),:collapse-transition="false"(关闭默认的折叠动画) -->
  49. <el-menu
  50. :collapse="isCollapse"
  51. :collapse-transition="false"
  52. ......
  53. 效果如下图:
  54. ![在这里插入图片描述][20210123164003325.gif_pic_center]

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

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

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

实现过程】:

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

    在 components 路径下新建 Welcome.vue 组件文件,如图:
    在这里插入图片描述
    同时在 Welcome.vue 中定义一个基本的 template 结构:

    1. <template>
    2. <div>
    3. <h3>Welcome</h3>
    4. </div>
    5. </template>

    注: 如果组件中暂时没有需要的样式和行为可不写,单写一个 template 也不会报错。

  1. router.js 中导入子级路由组件,并设置路由规则以及子级路由的默认重定向;
  2. import Welcome from '../components/Welcome.vue'
  3. ...
  4. {
  5. path: '/home',
  6. component: Home,
  7. redirect: '/welcome',
  8. children: [{ path: '/welcome', component: Welcome }]
  9. }
  10. **注意**,Welcome 是以子路由的形式存在于 Home 规则中(在 Home 页面中嵌套显示 欢迎页面)的,因此要把 Welcome 做为 Home 的子路由规则。所以这里是新增一个 children 属性(即子路由,是个数组,数组里面再放子路由的规则对象)。
  11. * path:路由规则地址;
  12. * component:用于指定要显示的组件。
  13. * redirect:重定向。
  14. 如图所示:
  15. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 90]
  1. 主体结构中添加一个路由占位符

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

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

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

    1. <!-- 右侧内容主体 -->
    2. <el-main>
    3. <router-view></router-view>
    4. </el-main>

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

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

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

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

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

    解决方法】:

    在 Element UI 的 Menu 属性中,有个 router 参数,用于控制 “ 是否使用 vue-router 的模式 ” 其参数值的类型为布尔。默认值为 false,即未开启。如下图所示:
    在这里插入图片描述
    因此,只需要将 el-menu 的 router 属性设置为true即可,此时当我们点击二级菜单的时候,就会根据菜单的index属性进行路由跳转,如:/110

    1. <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 地址名称前手动补上斜线(“/”)。

    修改前:

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

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

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

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

    效果如下图:

    在这里插入图片描述


5. 用户管理

5.1 用户管理概述

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

5.2 用户管理-列表展示

5.2.1 用户列表布局

用户列表主体区域主要分两部分,具体布局划分如下:
在这里插入图片描述

页面布局实现:

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

    • 在组件目录 conponets 中,新建 user 目录,再在此路径下新建 Users.vue 组件,如图:
      在这里插入图片描述
    • 在新组件中创建基本的页面结构:

      1. <template>
      2. <div>
      3. <h3>用户列表组件</h3>
      4. </div>
      5. </template>
      6. <script> export default { } </script>
      7. <style lang="less" scoped> </style>
    • 通过路由的形式,在右侧主体区展示用户列表

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

        1. ... // 导入的其它组件
        2. import Users from '../components/user/Users.vue'
        3. Vue.use(VueRouter)
        4. const router = new VueRouter({
        5. routes: [
        6. ... // 其它路由规则
        7. {
        8. path: '/home',
        9. component: Home,
        10. redirect: '/welcome',
        11. children: [
        12. { path: '/welcome', component: Welcome },
        13. { path: '/users', component: Users }
        14. ]
        15. }
        16. ]
        17. })

        但是,此时发现激活的菜单文本并没有高亮显示:
        在这里插入图片描述
        在 Menu 属性中有个 default-active 参数,表示当前激活菜单的 index 可以赋值给 default-active。

        即,如果想让菜单中的某一项被激活时高亮,就把该项对应的 index 赋值给整个 Menu 菜单的属性 default-active 。
        在这里插入图片描述
        打开 Home.vue ,在 <el-menu>中添加 default-active 属性,为便于测试,直接将其值写死为 /users,即default-active="/users",效果如下:
        在这里插入图片描述
        如果要把激活项的 index 换成动态的,要如何实现 ???

  1. **实现思路:**
  2. 当我们点击链接时,把对应的地址先保存到 Session Storage 中。当刷新页面(即Home组件刚被创建)时,再把这个值取出来,动态的赋值给 el-menu 中的 default-active 属性
  3. **第一步**:给所有的二级菜单绑定一个 **单击事件**,命名为 `saveNavState`,在单击事件中把 path 值存贮起来:
  4. <!-- 二级菜单 -->
  5. <el-menu-item :index="'/'+subItem.path + ''" v-for="subItem in item.children" :key="subItem.id" @click="saveNavState('/'+subItem.path)">
  6. **第二步:** 定义 saveNavState 函数
  7. // 保存链接的激活状态
  8. saveNavState(activePath) {
  9. window.sessionStorage.setItem('activePath', activePath)
  10. }
  11. 运行程序后,即可保存path 值:![在这里插入图片描述][20210123214011217.gif_pic_center]
  12. 接下来,需要把保存的值取出来
  13. **第三步:** data 中定义 activePath 来保存数据,默认为空:`activePath: ''`。再将 activePath 绑定到 `<el-menu>` default-active 属性上。替换原来写死的值`/users`
  14. **第四步:** 动态赋值
  15. 当整个 Home 组件一被创建的时候,就立即从 Session Storage 中把值取出来赋给 activePath ;
  16. 由于组件被创建时,第一时间执行的是 `Created` 生命周期函数,所以就直接在 `Created` 里进行赋值:
  17. created() {
  18. this.getMenuList()
  19. this.activePath = window.sessionStorage.getItem('ctivePath')
  20. }
  21. ![在这里插入图片描述][20210123221020952.gif_pic_center]
  22. 我们发现,当点击别的链接之后,再次点击 **用户列表** 时,对应的链接文本并没有高亮,原因是缺少了一步。应该是点击不同链接时,也要为当前的 activePath 重新赋值( saveNavState 点击事件里):
  23. // 保存链接的激活状态
  24. saveNavState(activePath) {
  25. window.sessionStorage.setItem('activePath', activePath)
  26. // 点击链接时,重新赋值
  27. this.activePath = activePath
  28. }
  29. * **绘制用户列表基本 UI 结构**
  30. * 面包屑导航:`el-breadcrumb`
  31. * Element-UI 栅格系统基本使用:`el-row`
  32. * 表格布局:`el-table``el-pagination`
  33. [Element UI ][Element UI 2] 官方的 面包屑 导航组件中,找到对应的代码并复制。![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 97]
  34. 打开 Users.vue 文件,将复制好的代码粘贴到该组件文件 `template` 模板区中:
  35. <template>
  36. <div>
  37. <!-- 面包屑导航区域 -->
  38. <el-breadcrumb separator-class="el-icon-arrow-right">
  39. <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
  40. <el-breadcrumb-item>用户管理</el-breadcrumb-item>
  41. <el-breadcrumb-item>用户列表</el-breadcrumb-item>
  42. </el-breadcrumb>
  43. </div>
  44. </template>
  45. 接下来,在`element.js` 中按需导入 Breadcrumb BreadcrumbItem 组件并注册(否则不生效):
  46. import Vue from 'vue'
  47. ... // 其它组件
  48. import { Breadcrumb, breadcrumbItem } from 'element-ui'
  49. // 注册全局组件
  50. ... // 注册的其它组件
  51. Vue.use(Breadcrumb)
  52. Vue.use(breadcrumbItem)
  53. Vue.prototype.$message = Message
  54. 此时,面包屑导航功能完成如下:
  55. ![在这里插入图片描述][20210123224940876.gif_pic_center]
  56. **绘制卡片视图区域**:
  57. [Element UI 官网][Element UI 3] 中找到 card卡片 组件,
  58. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 98]
  59. 将复制的代码粘贴到 users.vue 文件中:
  60. <!-- 卡片视图区 -->
  61. <el-card class="box-card">
  62. <div v-for="o in 4" :key="o" class="text item">
  63. {
  64. {'列表内容 ' + o }}
  65. </div>
  66. </el-card>
  67. 整理代码,将不需要的 for 循环和 box-card 类删掉后,结构代码如下:
  68. <!-- 卡片视图区 -->
  69. <el-card>
  70. 123
  71. </el-card>
  72. 再在 element.js 中按需导入 card 卡片后,效果如下:
  73. ![在这里插入图片描述][20210125234449120.png]
  74. 目视可见卡片离面包屑导航太近。在 assets 中的 css 目录下,找到 `global.css`,为卡片视图区设置上部的间距,这里通过面包屑的类名选择器 el-breadcrumb ,给面包屑增加一个 `margin -bottom` 值,把卡片盒子挤下来一点:
  75. .el-breadcrumb {
  76. margin-bottom: 15px;
  77. font-size: 12px;
  78. }
  79. 保存后,基本的效果如下图: ![在这里插入图片描述][20210125235500797.png]
  80. 为卡片视图重置阴影样式。
  81. .el-car {
  82. box-shadow: 0 1px 1px rgba(0, 0 ,0.15) !important;
  83. }
  84. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pnbGliaw_size_16_color_FFFFFF_t_70 99]

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

  • 作用域插槽
  • 接口调用

5.2.3 表格数据填充

  • 调用后台接口
  • 表格数据初填充

    const { data: res } = await this.$http.get(‘users’, { params: this.queryInfo })
    if (res.meta.status !== 200) {

    1. 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. 分页条菜单控制

    1. <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">
    2. </el-pagination>

5.2.5 搜索功能

  1. 将搜索 关键字,作为 参数 添加到列表查询的参数中。
  2. <el-input placeholder="请输入搜索的内容" v-model="queryInfo.query" clearable @clear="getUserList">
  3. <el-button slot="append" icon="el-icon-search" @click="getUserList"> </el-button>
  4. </el-input>

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

  • 开关组件的用法
  • 接口调用更改用户的状态

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

5.4 用户管理-添加用户

5.4.1 添加用户表单弹窗布局

  • 弹窗组件用法
  • 控制弹窗显示和隐藏

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

5.4.2 表单验证

  1. 内置 表单验证规则

    1. <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" >
    2. <!-- 表单 -->
    3. </el-form>
    4. addFormRules: {
    5. username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
    6. password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
    7. }
    8. this.$refs.addFormRef.validate(async valid => {
    9. if (!valid) return
    10. })
  1. 自定义 表单验证规则

    手机号验证规则

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

5.4.3 表单提交

  1. 将用户信息作为参数,调用后台接口添加用户
  2. this.$refs.addFormRef.validate(async valid => {
  3. if (!valid) return
  4. const { data: res } = await this.$http.post('users', this.addForm)
  5. if (res.meta.status !== 201) {
  6. return this.$message.error('添加用户失败!')
  7. }
  8. this.$message.success('添加用户成功!')
  9. this.addDialogVisible = false
  10. this.getUserList()
  11. })

5.5 用户管理-编辑用户

5.5.1 根据 ID 查询用户信息

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

5.5.2 编辑提交表单

  1. this.$refs.editFormRef.validate(async valid => {
  2. if (!valid) return
  3. // 发起修改的请求
  4. const { data: res } = await this.$http.put('users/' + this.editForm.id, {
  5. email: this.editForm.email,
  6. mobile: this.editForm.mobile
  7. })
  8. if (res.meta.status !== 200) {
  9. return this.$message.error('编辑用户信息失败!')
  10. }
  11. this.$message.success('编辑用户信息成功!')
  12. this.getUserList()
  13. this.editDialogVisible = false
  14. })

5.6 用户管理-删除用户

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

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

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

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

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

6 权限管理

6.1 权限管理业务分析

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

在这里插入图片描述

6.2 添加权限列表路由

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

  1. import Rights from './components/power/Rights.vue'
  2. ......
  3. path: '/home', component: Home, redirect: '/welcome', children: [
  4. { path: "/welcome", component: Welcome },
  5. { path: "/users", component: Users },
  6. { path: "/rights", component: Rights }
  7. ]
  8. ......

6.3 添加面包屑导航

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

6.2 权限列表展示

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

  1. <el-table :data="rightsList" stripe>
  2. <el-table-column type="index"></el-table-column>
  3. <el-table-column label="权限名称" prop="authName"></el-table-column>
  4. <el-table-column label="路径" prop="path"></el-table-column>
  5. <el-table-column label="权限等级" prop="level">
  6. <template slot-scope="scope">
  7. <el-tag v-if="scope.row.level === 0">一级权限</el-tag>
  8. <el-tag v-if="scope.row.level === 1" type="success">二级权限</el-tag>
  9. <el-tag v-if="scope.row.level === 2" type="warning">三级权限</el-tag>
  10. </template>
  11. </el-table-column>
  12. </el-table>
  13. <script>
  14. export default {
  15. data(){
  16. return {
  17. //列表形式的权限
  18. rightsList:[]
  19. }
  20. },
  21. created(){
  22. this.getRightsList();
  23. },
  24. methods:{
  25. async getRightsList(){
  26. const { data:res} = await this.$http.get('rights/list')
  27. //如果返回状态为异常状态则报错并返回
  28. if (res.meta.status !== 200)
  29. return this.$message.error('获取权限列表失败')
  30. //如果返回状态正常,将请求的数据保存在data中
  31. this.rightsList = res.data
  32. }
  33. }
  34. }
  35. </script>

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

获取权限列表数据

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

6.3 角色列表展示

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

    // 获取所有角色列表
    async getRolesList() {

    1. const { data: res } = await this.$http.get('roles')
    2. if (res.meta.status !== 200) {
    3. return this.$message.error('获取角色列表失败!')
    4. }
    5. this.rolesList = res.data

    },

6.4 用户角色分配

6.4.1 展示角色对话框

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

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

6.4.2 完成角色分配功能

  1. async saveNewRole() {
  2. if (this.selectedRoleId === '') {
  3. return this.$message.error('请选择新角色后再保存!')
  4. }
  5. const { data: res } = await this.$http.put(`users/${ this.userInfo.id}/role`, {
  6. rid: this.selectedRoleId
  7. })
  8. if (res.meta.status !== 200) {
  9. return this.$message.error('分配角色失败!')
  10. }
  11. this.$message.success('分配角色成功!')
  12. this.getUserList()
  13. this.setRoleDialogVidible = false
  14. }

6.5 角色权限分配

~ 未完待续 ~

注 解:


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

    1. 同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。 ↩︎
  2. 跨域:是指浏览器不能执行其它网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript实施的 安全限制

    在这里插入图片描述

    1. 简单来讲,就是从地址A加载的页面,不能访问地址B的服务(如上图)。此时地址A与地址B不同源。 ↩︎

发表评论

表情:
评论列表 (有 0 条评论,520人围观)

还没有评论,来说两句吧...

相关阅读