天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,但不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
月下飞天镜,云生结海楼。
——《渡荆门送别》
文章目录
- `第三部分:后台主页相关功能模块开发`
- 22. 主页布局搭建(菜单栏、工具栏、主页内容)
- 22.1 主页布局(HomeIndex.vue)
- 22.2 左侧菜单栏组件(MenuBar.vue)
- 22.3 工具栏组件(ToolBar.vue)
- 22.4 页面效果展示
- 23. 动态菜单组件实现
- 23.1 后台接口(MenuController.java)
- 23.2 菜单数据组件代码(MenuBar.vue)
- 23.3 页面效果展示
- 24. 菜单和路由动态绑定
- 24.1 后端接口代码修改(MenuController.java)
- 24.2 接口返回数据
- 24.3 路由代码修改(src/router/index.ts)
- 24.4 缓存代码修改(src/store/index.ts)
- 24.5 主页代码修改(src/views/index/HomeIndex.vue)
- 24.6 菜单栏组件代码修改(src/views/index/components/MenuBar.vue)
- 24.7 工具栏组件代码修改(src/views/index/components/ToolBar.vue)
- 24.8 新增组件-工作台(src/views/index/components/WorkPlat.vue)
- 24.9 手机验证码登录组件代码修改(src/views/login/components/PhoneCodeForm.vue)
- 24.10 二维码登录组件代码修改(src/views/login/components/QcodeForm.vue)
- 24.11 账号密码登录组件代码修改(src/views/login/components/UsernameForm.vue)
- 24.12 App.vue组件代码修改(src/App.vue)
- 24.13 页面效果展示
Vue入门学习专栏
第三部分:后台主页相关功能模块开发
22. 主页布局搭建(菜单栏、工具栏、主页内容)
系统登录成功后,进入主页界面,主页界面主要包含三部分:左侧菜单栏、右侧上部工具栏以及主页主体界面部分
在scr/views/index/下创建一个components包和HomeIndex.vue组件(该组件前面已经创建,直接将删除原来的代码,将以下代码填入即可)
22.1 主页布局(HomeIndex.vue)
scr/views/index/
HomeIndex.vue组件代码如下
<script setup lang="ts">
import { ref, } from 'vue'
// import {reactive, onMounted, onUnmounted } from 'vue'
// import utils from '@/utils/utils';
// import { useRoute, useRouter } from 'vue-router';
import MenuBar from './components/MenuBar.vue';
import ToolBar from './components/ToolBar.vue';
// 左侧菜单栏宽度
const slideWidth = ref('250px');
// 从MenuBar组件中传过来的值
const menuCollapse = (value:boolean)=>{
if(value){
// 如果值为true则为收起状态,宽度设为60px
slideWidth.value = '60px';
}else{
// 如果为false则是展开状态,宽度设为250px
slideWidth.value = '250px'
}
}
</script>
<template>
<!-- 后台主页 -->
<div class="index-layout">
<el-container>
<!-- 宽度以变量形式传入,打开关闭侧边菜单栏 -->
<el-aside class="layout-aside" :width="slideWidth">
<MenuBar @menuCollapse="menuCollapse"></MenuBar>
</el-aside>
<el-container>
<el-main class="layout-main">
<!-- tab标签页 -->
<el-tabs v-model="activeName" class="main-tabs" @tab-click="handleClick">
<!-- <el-tab-pane label="User" name="first">主界面</el-tab-pane> -->
<el-tab-pane class="tabs-pane">
主界面
<template #label>
<span class="pane-label">
<!-- <el-icon class="label-icon">
<calendar />
</el-icon> -->
<el-icon class="label-icon"><Menu /></el-icon>
<span class="label-span">工作台</span>
</span>
</template>
</el-tab-pane>
</el-tabs>
</el-main>
<!-- ToolBar 头部工具栏 -->
<el-header class="layout-header">
<ToolBar></ToolBar>
</el-header>
</el-container>
</el-container>
</div>
</template>
<style scoped>
.index-layout{
/* height: 100%; */
/* width: 100%; */
font-size: 20px;
}
/* 侧边菜单栏样式 */
.layout-aside{
/* height: 100%; */
height: 100vh;
box-shadow: var(--el-box-shadow);
/* 左右侧栏之间的边框线 */
border-right: var(--el-border);
}
/* 菜单栏与右侧界面的边距设为0 */
.layout-main{
padding: 0;
margin: 0;
background: var(--el-bg-color-page);
}
/* header头工具栏相对于主界面的样式设置 */
.layout-main:deep(.el-tabs__header){
/* 让主界面与header头工具栏的距离归0 */
margin: 0;
/* 头部栏背景色设为白色 */
background-color: #fff;
/* 头部栏左侧边框距离 */
padding-left: 10px;
/* 头部栏右侧边框距离 */
padding-right: 10px;
}
/* 图标的位置调整,与文字上下和左右距离 */
.layout-main:deep(.pane-label .label-icon){
/* 图标右侧边距 */
margin-right: 4px;
/* 位置 */
/* position: relative; */
/* 图标上方距离 */
top: 2px;
}
.layout-header{
position: fixed;
top: 0;
right: 0;
width: 300px;
/* height: 60px; */
line-height: 35px;
}
</style>
22.2 左侧菜单栏组件(MenuBar.vue)
在components包下创建菜单组件MenuBar.vue
scr/views/index/components/
MenuBar.vue
<script setup lang="ts">
import { ref, } from 'vue'
// import {reactive, onMounted, onUnmounted } from 'vue'
// import utils from '@/utils/utils';
// import { useRoute, useRouter } from 'vue-router';
// import MenuBar from './components/MenuBar.vue';
// import ToolBar from './components/ToolBar.vue';
// const isCollapse = ref(true)
const isCollapse = ref(false)
const collapseController = (value:boolean)=> {
// isCollapse.value = !isCollapse.value;
isCollapse.value = value;
// 将值传到事件中
emits('menuCollapse', value);
}
// 定义事件,传值,并在主页监听
const emits = defineEmits(['menuCollapse'])
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
</script>
<template>
<div class="logo" >
<div class="logo-name" v-if="!isCollapse">
寒山李白通用系统
</div>
<!-- 动态绑定侧边栏展开收起的图标按钮,当收起时即isCollapse为真,将class值转为logo-collapse-ef并设置图标居中 -->
<div class="logo-collapse" :class="{'logo-collapse-ef': isCollapse}">
<!-- 展开按钮 如果isCollapse是真则展示按钮,触发事件传值为false -->
<el-icon v-if="isCollapse" @click="collapseController(false)">
<Expand />
</el-icon>
<!-- 收起按钮 如果isCollapse是假则展示按钮,触发事件,传值为true -->
<el-icon v-else @click="collapseController(true)">
<Fold />
</el-icon>
</div>
</div>
<!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
<el-radio-button :value="false">expand</el-radio-button>
<el-radio-button :value="true">collapse</el-radio-button>
</el-radio-group> -->
<!-- default-active="4" 设置加载时的激活项,此为4 -->
<!-- :collapse-transition="false" 取消收起展开时的动画,展开收起更快 -->
<!-- router 启用vue-router模式 激活导航时 以index作为path进行路由跳转 使用 -->
<el-menu
default-active="4"
class="el-menu-vertical-collapse"
:collapse="isCollapse"
@open="handleOpen"
@close="handleClose"
:collapse-transition="false"
router
>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator One</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<template #title>Navigator Two</template>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon><document /></el-icon>
<template #title>Navigator Three</template>
</el-menu-item>
<el-menu-item index="4">
<el-icon><setting /></el-icon>
<template #title>Navigator Four</template>
</el-menu-item>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator Five</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator Six</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu>
</el-menu>
</template>
<style scoped>
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
} */
.logo{
display: flex;
background-color: var(--el-color-info-light-7)
/* height: 60px; */
}
.logo-name{
/* position: fixed; */
/* top: 0; */
/* left: 0; */
flex: 1;
text-align: center;
font-size: 20px;
font-weight: bold;
letter-spacing: 2px;
padding: 2%;
background-image: -webkit-linear-gradient(right, rgba(78, 224, 33, 0.795), #22fc2d, rgb(236, 126, 36));
/* background-image: -webkit-background-clip(bottom, red, #fd8403, yellow); */
/* -webkit-background-clip: text; */
background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-collapse{
width: 20px;
/* margin-top: 10px; */
padding-right: 10%;
padding-top: 1%;
/* height: 30px; */
text-align: center;
cursor: pointer;
font-size: 30px;
}
.logo-collapse:hover{
color: var(--el-color-primary)
}
/* 动态绑定侧边栏收起展开图标的样式 */
.logo-collapse-ef{
/* 图标宽度居中 */
width: 100%
}
.el-menu-vertical-collapse{
/* 剔除侧边栏菜单边框,收起时无边框 */
border: none;
height: calc(100% - 60px);
overflow-y: auto;
}
/* 设置滚动条样式 */
.el-menu-vertical-collapse::-webkit-scrollbar{
width: 10px;
}
/* 滚动槽 */
.el-menu-vertical-collapse::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark);
border-radius: 8px;
}
/* 滚动条滑块 */
.el-menu-vertical-collapse::-webkit-scrollbar-thumb{
border-radius: 8px;
background: var(--el-border-color-darker);
/* -webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark); */
}
/* 滚动条上下设置 */
/* .el-menu-vertical-collapse::-webkit-scrollbar-thumb{
background: var(--el-border-color-darker);
} */
</style>
22.3 工具栏组件(ToolBar.vue)
在components包下创建菜单组件ToolBar.vue
scr/views/index/components/
ToolBar.vue
<script setup lang="ts">
</script>
<template>
<div class="toolbar">
<div class="toolbar-icon">
<el-icon>
<Search />
</el-icon>
</div>
<div class="toolbar-icon">
<el-icon>
<FullScreen />
</el-icon>
</div>
<div class="toolbar-icon">
<el-icon>
<TurnOff />
</el-icon>
</div>
<div class="toolbar-icon">
<el-dropdown>
<span class="icon-dropdown-link">
<el-icon class="link-icon--left" style="top: 2px;">
<User />
</el-icon>
<span>管理员</span>
<el-icon class="link-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item>退出系统</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<style scoped>
.toolbar{
display: flex;
}
.toolbar-icon{
flex: 1;
text-align: center;
cursor: pointer;
/* height: 20px; */
}
.toolbar-icon:hover{
color: var(--el-color-primary);
}
.toolbar-icon:deep(.icon-dropdown-link){
/* width: 100%; */
line-height: 30px;
width: 80px;
}
.icon-dropdown-link:hover{
color: var(--el-color-primary);
}
</style>
22.4 页面效果展示
通过账号密码登录成功进入主页
左侧栏收起按钮使用(左上角系统名称后面的收起图标)
菜单下拉和滚动条
右上角用户管理菜单
23. 动态菜单组件实现
实现左侧菜单栏动态绑定图标、名称和路由地址
先在后台服务中新增接口,用于创建菜单所需数据,然后通过接口调用获取菜单数据进行展示
23.1 后台接口(MenuController.java)
菜单数据接口代码
MenuController.java
package com.hslb.management.controller;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassDescription: 菜单相关接口
* @JdkVersion: 1.8
* @Author: 李白
* @Created: 2024/8/19 14:19
*/
@RestController
@CrossOrigin
@RequestMapping(value = "/menu")
public class MenuController {
@GetMapping(value = "/getMenu")
public JSONObject getMenu(){
//menu
List<JSONObject> menuList = new ArrayList<>();
//工作台-----------------------------------------------------------------------
JSONObject workPlat = new JSONObject();
workPlat.put("name","工作台");
workPlat.put("path","/Index/Home");
workPlat.put("icon","Platform");
workPlat.put("components","Home");
List<JSONObject> workPlatList = new ArrayList<>();
// JSONObject workPlatChildrenJs = new JSONObject();
// workPlatChildrenJs.put("name","列表实例");
// workPlatChildrenJs.put("path","/index/workPlat/List");
// workPlatChildrenJs.put("icon","ScaleToOriginal");
// workPlatChildrenJs.put("components","WorkPlat");
// workPlatList.add(workPlatChildrenJs);
workPlat.put("children",workPlatList);
menuList.add(workPlat);
//业务菜单-----------------------------------------------------------------------
JSONObject businessMenu = new JSONObject();
businessMenu.put("name","业务菜单");
businessMenu.put("path","/index/businessMenu");
businessMenu.put("icon","Menu");
businessMenu.put("components","BusinessMenu");
List<JSONObject> businessMenuList = new ArrayList<>();
//列表示例
JSONObject businessMenuListExam = new JSONObject();
businessMenuListExam.put("name","列表示例");
businessMenuListExam.put("path","/index/businessMenu/list");
businessMenuListExam.put("icon","Tickets");
businessMenuListExam.put("components","BusinessMenu");
businessMenuList.add(businessMenuListExam);
//详情示例
JSONObject businessMenuDetailExam = new JSONObject();
businessMenuDetailExam.put("name","详情示例");
businessMenuDetailExam.put("path","/index/businessMenu/detail");
businessMenuDetailExam.put("icon","DocumentRemove");
businessMenuDetailExam.put("components","BusinessMenu");
businessMenuList.add(businessMenuDetailExam);
//图表示例
JSONObject businessMenuChartExam = new JSONObject();
businessMenuChartExam.put("name","图表示例");
businessMenuChartExam.put("path","/index/businessMenu/chart");
businessMenuChartExam.put("icon","Postcard");
businessMenuChartExam.put("components","BusinessMenu");
businessMenuList.add(businessMenuChartExam);
//文件上传
JSONObject businessMenuFileUpload = new JSONObject();
businessMenuFileUpload.put("name","文件上传");
businessMenuFileUpload.put("path","/index/businessMenu/fileUpload");
businessMenuFileUpload.put("icon","Files");
businessMenuFileUpload.put("components","BusinessMenu");
businessMenuList.add(businessMenuFileUpload);
//富文本示例
JSONObject businessMenuRichTextExam = new JSONObject();
businessMenuRichTextExam.put("name","富文本示例");
businessMenuRichTextExam.put("path","/index/businessMenu/richText");
businessMenuRichTextExam.put("icon","Document");
businessMenuRichTextExam.put("components","BusinessMenu");
businessMenuList.add(businessMenuRichTextExam);
businessMenu.put("children",businessMenuList);
menuList.add(businessMenu);
//基础数据-----------------------------------------------------------------------
JSONObject baseData = new JSONObject();
baseData.put("name","基础数据");
baseData.put("path","/index/baseData");
baseData.put("icon","TrendCharts");
baseData.put("components","BaseData");
List<JSONObject> baseDataList = new ArrayList<>();
//基础数据-消息数据
JSONObject baseDataMsgData = new JSONObject();
baseDataMsgData.put("name","消息数据");
baseDataMsgData.put("path","/index/baseData/msgData");
baseDataMsgData.put("icon","Message");
baseDataMsgData.put("components","BaseData");
baseDataList.add(baseDataMsgData);
//基础数据-实体配置
JSONObject baseDataEntitySet = new JSONObject();
baseDataEntitySet.put("name","实体配置");
baseDataEntitySet.put("path","/index/baseData/entitySet");
baseDataEntitySet.put("icon","Operation");
baseDataEntitySet.put("components","BaseData");
baseDataList.add(baseDataEntitySet);
//基础数据-验证码数据
JSONObject baseDataValidationCode = new JSONObject();
baseDataValidationCode.put("name","验证码数据");
baseDataValidationCode.put("path","/index/baseData/validationCode");
baseDataValidationCode.put("icon","DocumentChecked");
baseDataValidationCode.put("components","BaseData");
baseDataList.add(baseDataValidationCode);
baseData.put("children",baseDataList);
menuList.add(baseData);
//系统管理-----------------------------------------------------------------------
JSONObject systemManagement = new JSONObject();
systemManagement.put("name","系统管理");
systemManagement.put("path","/Index/SystemManagement");
systemManagement.put("icon","Tools");
systemManagement.put("components","System");
List<JSONObject> systemManagementList = new ArrayList<JSONObject>();
//系统管理-用户管理
JSONObject sysMngUser = new JSONObject();
sysMngUser.put("name","用户管理");
sysMngUser.put("path","/index/SystemManagement/user");
sysMngUser.put("icon","User");
sysMngUser.put("components","System");
systemManagementList.add(sysMngUser);
//系统管理-角色管理
JSONObject sysMngRole = new JSONObject();
sysMngRole.put("name","角色管理");
sysMngRole.put("path","/index/SystemManagement/role");
sysMngRole.put("icon","Van");
sysMngRole.put("components","System");
systemManagementList.add(sysMngRole);
//系统管理-菜单管理
JSONObject sysMngMenu = new JSONObject();
sysMngMenu.put("name","菜单管理");
sysMngMenu.put("path","/index/SystemManagement/menu");
sysMngMenu.put("icon","Reading");
sysMngMenu.put("components","System");
systemManagementList.add(sysMngMenu);
//系统管理-日志管理
JSONObject sysMngLog = new JSONObject();
sysMngLog.put("name","日志管理");
sysMngLog.put("path","/index/SystemManagement/log");
sysMngLog.put("icon","Memo");
sysMngLog.put("components","System");
systemManagementList.add(sysMngLog);
//系统管理-系统配置
JSONObject sysMngSet = new JSONObject();
sysMngSet.put("name","系统配置");
sysMngSet.put("path","/index/SystemManagement/set");
sysMngSet.put("icon","DataLine");
sysMngSet.put("components","System");
systemManagementList.add(sysMngSet);
systemManagement.put("children",systemManagementList);
menuList.add(systemManagement);
JSONObject resultJson = new JSONObject();
resultJson.put("result", 200);
resultJson.put("data", menuList);
resultJson.put("msg", "左侧栏菜单数据获取");
System.out.println(resultJson);
return resultJson;
}
}
接口返回的数据如下:
{
"result": 200,
"data": [
{
"name": "工作台",
"path": "/Index/Home",
"icon": "Platform",
"components": "Home",
"children": []
},
{
"name": "业务菜单",
"path": "/index/businessMenu",
"icon": "Menu",
"components": "BusinessMenu",
"children": [
{
"name": "列表示例",
"path": "/index/businessMenu/list",
"icon": "Tickets",
"components": "BusinessMenu"
},
{
"name": "详情示例",
"path": "/index/businessMenu/detail",
"icon": "DocumentRemove",
"components": "BusinessMenu"
},
{
"name": "图表示例",
"path": "/index/businessMenu/chart",
"icon": "Postcard",
"components": "BusinessMenu"
},
{
"name": "文件上传",
"path": "/index/businessMenu/fileUpload",
"icon": "Files",
"components": "BusinessMenu"
},
{
"name": "富文本示例",
"path": "/index/businessMenu/richText",
"icon": "Document",
"components": "BusinessMenu"
}
]
},
{
"name": "基础数据",
"path": "/index/baseData",
"icon": "TrendCharts",
"components": "BaseData",
"children": [
{
"name": "消息数据",
"path": "/index/baseData/msgData",
"icon": "Message",
"components": "BaseData"
},
{
"name": "实体配置",
"path": "/index/baseData/entitySet",
"icon": "Operation",
"components": "BaseData"
},
{
"name": "验证码数据",
"path": "/index/baseData/validationCode",
"icon": "DocumentChecked",
"components": "BaseData"
}
]
},
{
"name": "系统管理",
"path": "/Index/SystemManagement",
"icon": "Tools",
"components": "System",
"children": [
{
"name": "用户管理",
"path": "/index/SystemManagement/user",
"icon": "User",
"components": "System"
},
{
"name": "角色管理",
"path": "/index/SystemManagement/role",
"icon": "Van",
"components": "System"
},
{
"name": "菜单管理",
"path": "/index/SystemManagement/menu",
"icon": "Reading",
"components": "System"
},
{
"name": "日志管理",
"path": "/index/SystemManagement/log",
"icon": "Memo",
"components": "System"
},
{
"name": "系统配置",
"path": "/index/SystemManagement/set",
"icon": "DataLine",
"components": "System"
}
]
}
],
"msg": "左侧栏菜单数据获取"
}
23.2 菜单数据组件代码(MenuBar.vue)
src/views/index/components/
MenuBar.vue
<script setup lang="ts">
import { onMounted, reactive, ref, } from 'vue'
// import {reactive, onMounted, onUnmounted } from 'vue'
import utils from '@/utils/utils';
import api from '@/api/api';
// import { useRoute, useRouter } from 'vue-router';
// import MenuBar from './components/MenuBar.vue';
// import ToolBar from './components/ToolBar.vue';
// 左侧菜单栏展开收起的标识
// const isCollapse = ref(true)
// 默认false,左侧栏展开
const isCollapse = ref(false)
const collapseController = (value:boolean)=> {
// isCollapse.value = !isCollapse.value;
isCollapse.value = value;
// 将值传到事件中
emits('menuCollapse', value);
}
// 定义事件,传值,并在主页监听
const emits = defineEmits(['menuCollapse'])
// const handleOpen = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
// }
// const handleClose = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
// }
// 菜单数据
const menuData = reactive([]);
// let menuData:any = null;
// 这里定义一个默认展示的路由地址,展示对应的菜单页面
const curMenu = ref("1");
onMounted (()=>{
loadMenuData();
});
// 加载菜单数据
const loadMenuData = () => {
utils.showLoadding("加载中");
api.get("/menu/getMenu").then((res)=>{
utils.showLoadding("加载中");
if(!res||res.status!=200){
if(res.data){
utils.showError("问题");
return;
}
utils.showError("加载失败");
return;
}
if(res.data.result==200){
utils.showSuccess("请求成功")
menuData.values = res.data;
// menuData = res.data;
console.log("111",res.data);
console.log("222",menuData);
// menuData.splice(0, menuData.length);
// menuData.push(...res.data);
}
}).catch((error)=>{
console.log(error)
utils.hideLoadding();
utils.showError("加载失败");
}).finally(()=>{
utils.hideLoadding();
});
}
// 13-8.5=4.5
// 36-4.5=31.5
// 12+10=22
// 9.5
</script>
<template>
<div class="logo" >
<div class="logo-name" v-if="!isCollapse">
寒山李白通用系统
</div>
<!-- 动态绑定侧边栏展开收起的图标按钮,当收起时即isCollapse为真,将class值转为logo-collapse-ef并设置图标居中 -->
<div class="logo-collapse" :class="{'logo-collapse-ef': isCollapse}">
<!-- 展开按钮 如果isCollapse是真则展示按钮,触发事件传值为false -->
<el-icon v-if="isCollapse" @click="collapseController(false)">
<Expand />
</el-icon>
<!-- 收起按钮 如果isCollapse是假则展示按钮,触发事件,传值为true -->
<el-icon v-else @click="collapseController(true)">
<Fold />
</el-icon>
</div>
</div>
<!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
<el-radio-button :value="false">expand</el-radio-button>
<el-radio-button :value="true">collapse</el-radio-button>
</el-radio-group> -->
<!-- default-active="4" 设置加载时的激活项,此为4 -->
<!-- :collapse-transition="false" 取消收起展开时的动画,展开收起更快 -->
<!-- router 启用vue-router模式 激活导航时 以index作为path进行路由跳转 使用 -->
<el-menu
:default-active="curMenu"
class="el-menu-vertical-collapse"
:collapse="isCollapse"
:collapse-transition="false"
router
>
<!-- @open="handleOpen"
@close="handleClose" -->
<!-- 请求接口返回数据-获取其中的菜单数据data,遍历菜单数据中的每一项 -->
<template v-for="item in menuData.values.data">
<!-- 如果该项中有子项,则为二级菜单,继续进行遍历 -->
<el-sub-menu v-if="item.children && item.children.length>0" :index="item.path">
<!-- 该项的一级菜单图标和名称 -->
<template #title>
<!-- 该项的一级菜单图标 -->
<component class="menu-icon" :is="item.icon"></component>
<!-- 该项的一级菜单名称 -->
<span>{{ item.name }}</span>
</template>
<!-- 该项的二级菜单遍历 -->
<template v-for="subItem in item.children">
<el-menu-item :index="subItem.path">
<component class="menu-icon" :is="subItem.icon"></component>
<span>{{ subItem.name }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 如果该项中没有子项,则为一级菜单,直接展示即可 -->
<el-menu-item v-else :index="item.path">
<component class="menu-icon" :is="item.icon"></component>
<span>{{ item.name }}</span>
</el-menu-item>
</template>
<!-- <el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator One</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu> -->
<!-- <el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<template #title>Navigator Two</template>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon><document /></el-icon>
<template #title>Navigator Three</template>
</el-menu-item>
<el-menu-item index="4">
<el-icon><setting /></el-icon>
<template #title>Navigator Four</template>
</el-menu-item> -->
<!-- <el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator Five</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator Six</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu> -->
</el-menu>
</template>
<style scoped>
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
} */
.logo{
display: flex;
background-color: var(--el-color-info-light-7)
/* height: 60px; */
}
.logo-name{
/* position: fixed; */
/* top: 0; */
/* left: 0; */
flex: 1;
text-align: center;
font-size: 20px;
font-weight: bold;
letter-spacing: 2px;
padding: 2%;
background-image: -webkit-linear-gradient(right, rgba(78, 224, 33, 0.795), #22fc2d, rgb(236, 126, 36));
/* background-image: -webkit-background-clip(bottom, red, #fd8403, yellow); */
/* -webkit-background-clip: text; */
background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-collapse{
width: 20px;
/* margin-top: 10px; */
padding-right: 10%;
padding-top: 1%;
/* height: 30px; */
text-align: center;
cursor: pointer;
font-size: 30px;
}
.logo-collapse:hover{
color: var(--el-color-primary)
}
/* 动态绑定侧边栏收起展开图标的样式 */
.logo-collapse-ef{
/* 图标宽度居中 */
width: 100%
}
.el-menu-vertical-collapse{
/* 剔除侧边栏菜单边框,收起时无边框 */
border: none;
height: calc(100% - 60px);
overflow-y: auto;
}
/* 设置滚动条样式 */
.el-menu-vertical-collapse::-webkit-scrollbar{
width: 10px;
}
/* 滚动槽 */
.el-menu-vertical-collapse::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark);
border-radius: 8px;
}
/* 滚动条滑块 */
.el-menu-vertical-collapse::-webkit-scrollbar-thumb{
border-radius: 8px;
background: var(--el-border-color-darker);
/* -webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark); */
}
/* 滚动条上下设置 */
/* .el-menu-vertical-collapse::-webkit-scrollbar-thumb{
background: var(--el-border-color-darker);
} */
.el-menu-vertical-collapse:deep(.menu-icon){
width: 20px;
margin: 10px;
color: var(--el-color-primary);
}
</style>
23.3 页面效果展示
优化一下ToolBar.vue中的一个图标,将主页右上角的管理员图标由User改为UserFilled
最终展示的页面效果如下
24. 菜单和路由动态绑定
将菜单和路由地址动态绑定,实现点击菜单跳转对应路由,且刷新页面依旧保持当前选中菜单界面
24.1 后端接口代码修改(MenuController.java)
这里需要先修改后端接口代码中的数据结构,修改后代码如下
MenuController.java
package com.hslb.management.controller;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassDescription: 菜单相关接口
* @JdkVersion: 1.8
* @Author: 李白
* @Created: 2024/8/19 14:19
*/
@RestController
@CrossOrigin
@RequestMapping(value = "/menu")
public class MenuController {
@GetMapping(value = "/getMenu")
public JSONObject getMenu(){
//menu
List<JSONObject> menuList = new ArrayList<>();
//工作台-----------------------------------------------------------------------
JSONObject workPlat = new JSONObject();
workPlat.put("name","工作台");
workPlat.put("path","/HomeIndex/WorkPlat");
workPlat.put("icon","Platform");
workPlat.put("component","WorkPlat");
//是否需要鉴权
// JSONObject meta = new JSONObject();
// meta
List<JSONObject> workPlatList = new ArrayList<>();
// JSONObject workPlatChildrenJs = new JSONObject();
// workPlatChildrenJs.put("name","列表实例");
// workPlatChildrenJs.put("path","/index/workPlat/List");
// workPlatChildrenJs.put("icon","ScaleToOriginal");
// workPlatChildrenJs.put("components","WorkPlat");
// workPlatList.add(workPlatChildrenJs);
workPlat.put("children",workPlatList);
menuList.add(workPlat);
//业务菜单-----------------------------------------------------------------------
JSONObject businessMenu = new JSONObject();
businessMenu.put("name","业务菜单");
businessMenu.put("path","/HomeIndex/businessMenu");
businessMenu.put("icon","Menu");
businessMenu.put("component","BusinessMenu");
List<JSONObject> businessMenuList = new ArrayList<>();
//列表示例
JSONObject businessMenuListExam = new JSONObject();
businessMenuListExam.put("name","列表示例");
businessMenuListExam.put("path","/HomeIndex/businessMenu/list");
businessMenuListExam.put("icon","Tickets");
businessMenuListExam.put("component","BusinessMenu");
businessMenuList.add(businessMenuListExam);
//详情示例
JSONObject businessMenuDetailExam = new JSONObject();
businessMenuDetailExam.put("name","详情示例");
businessMenuDetailExam.put("path","/HomeIndex/businessMenu/detail");
businessMenuDetailExam.put("icon","DocumentRemove");
businessMenuDetailExam.put("component","BusinessMenu");
businessMenuList.add(businessMenuDetailExam);
//图表示例
JSONObject businessMenuChartExam = new JSONObject();
businessMenuChartExam.put("name","图表示例");
businessMenuChartExam.put("path","/HomeIndex/businessMenu/chart");
businessMenuChartExam.put("icon","Postcard");
businessMenuChartExam.put("component","BusinessMenu");
businessMenuList.add(businessMenuChartExam);
//文件上传
JSONObject businessMenuFileUpload = new JSONObject();
businessMenuFileUpload.put("name","文件上传");
businessMenuFileUpload.put("path","/HomeIndex/businessMenu/fileUpload");
businessMenuFileUpload.put("icon","Files");
businessMenuFileUpload.put("component","BusinessMenu");
businessMenuList.add(businessMenuFileUpload);
//富文本示例
JSONObject businessMenuRichTextExam = new JSONObject();
businessMenuRichTextExam.put("name","富文本示例");
businessMenuRichTextExam.put("path","/HomeIndex/businessMenu/richText");
businessMenuRichTextExam.put("icon","Document");
businessMenuRichTextExam.put("component","BusinessMenu");
businessMenuList.add(businessMenuRichTextExam);
businessMenu.put("children",businessMenuList);
menuList.add(businessMenu);
//基础数据-----------------------------------------------------------------------
JSONObject baseData = new JSONObject();
baseData.put("name","基础数据");
baseData.put("path","/HomeIndex/baseData");
baseData.put("icon","TrendCharts");
baseData.put("component","BaseData");
List<JSONObject> baseDataList = new ArrayList<>();
//基础数据-消息数据
JSONObject baseDataMsgData = new JSONObject();
baseDataMsgData.put("name","消息数据");
baseDataMsgData.put("path","/HomeIndex/baseData/msgData");
baseDataMsgData.put("icon","Message");
baseDataMsgData.put("component","BaseData");
baseDataList.add(baseDataMsgData);
//基础数据-实体配置
JSONObject baseDataEntitySet = new JSONObject();
baseDataEntitySet.put("name","实体配置");
baseDataEntitySet.put("path","/HomeIndex/baseData/entitySet");
baseDataEntitySet.put("icon","Operation");
baseDataEntitySet.put("component","BaseData");
baseDataList.add(baseDataEntitySet);
//基础数据-验证码数据
JSONObject baseDataValidationCode = new JSONObject();
baseDataValidationCode.put("name","验证码数据");
baseDataValidationCode.put("path","/HomeIndex/baseData/validationCode");
baseDataValidationCode.put("icon","DocumentChecked");
baseDataValidationCode.put("component","BaseData");
baseDataList.add(baseDataValidationCode);
baseData.put("children",baseDataList);
menuList.add(baseData);
//系统管理-----------------------------------------------------------------------
JSONObject systemManagement = new JSONObject();
systemManagement.put("name","系统管理");
systemManagement.put("path","/HomeIndex/SystemManagement");
systemManagement.put("icon","Tools");
systemManagement.put("component","System");
List<JSONObject> systemManagementList = new ArrayList<JSONObject>();
//系统管理-用户管理
JSONObject sysMngUser = new JSONObject();
sysMngUser.put("name","用户管理");
sysMngUser.put("path","/HomeIndex/SystemManagement/user");
sysMngUser.put("icon","User");
sysMngUser.put("component","System");
systemManagementList.add(sysMngUser);
//系统管理-角色管理
JSONObject sysMngRole = new JSONObject();
sysMngRole.put("name","角色管理");
sysMngRole.put("path","/HomeIndex/SystemManagement/role");
sysMngRole.put("icon","Van");
sysMngRole.put("component","System");
systemManagementList.add(sysMngRole);
//系统管理-菜单管理
JSONObject sysMngMenu = new JSONObject();
sysMngMenu.put("name","菜单管理");
sysMngMenu.put("path","/HomeIndex/SystemManagement/menu");
sysMngMenu.put("icon","Reading");
sysMngMenu.put("component","System");
systemManagementList.add(sysMngMenu);
//系统管理-日志管理
JSONObject sysMngLog = new JSONObject();
sysMngLog.put("name","日志管理");
sysMngLog.put("path","/HomeIndex/SystemManagement/log");
sysMngLog.put("icon","Memo");
sysMngLog.put("component","System");
systemManagementList.add(sysMngLog);
//系统管理-系统配置
JSONObject sysMngSet = new JSONObject();
sysMngSet.put("name","系统配置");
sysMngSet.put("path","/HomeIndex/SystemManagement/set");
sysMngSet.put("icon","DataLine");
sysMngSet.put("component","System");
systemManagementList.add(sysMngSet);
systemManagement.put("children",systemManagementList);
menuList.add(systemManagement);
JSONObject resultJson = new JSONObject();
resultJson.put("result", 200);
resultJson.put("data", menuList);
resultJson.put("msg", "左侧栏菜单数据获取");
System.out.println(resultJson);
return resultJson;
}
}
24.2 接口返回数据
接口返回的数据如下
{
"result": 200,
"data": [
{
"name": "工作台",
"path": "/HomeIndex/WorkPlat",
"icon": "Platform",
"component": "WorkPlat",
"children": []
},
{
"name": "业务菜单",
"path": "/HomeIndex/businessMenu",
"icon": "Menu",
"component": "BusinessMenu",
"children": [
{
"name": "列表示例",
"path": "/HomeIndex/businessMenu/list",
"icon": "Tickets",
"component": "BusinessMenu"
},
{
"name": "详情示例",
"path": "/HomeIndex/businessMenu/detail",
"icon": "DocumentRemove",
"component": "BusinessMenu"
},
{
"name": "图表示例",
"path": "/HomeIndex/businessMenu/chart",
"icon": "Postcard",
"component": "BusinessMenu"
},
{
"name": "文件上传",
"path": "/HomeIndex/businessMenu/fileUpload",
"icon": "Files",
"component": "BusinessMenu"
},
{
"name": "富文本示例",
"path": "/HomeIndex/businessMenu/richText",
"icon": "Document",
"component": "BusinessMenu"
}
]
},
{
"name": "基础数据",
"path": "/HomeIndex/baseData",
"icon": "TrendCharts",
"component": "BaseData",
"children": [
{
"name": "消息数据",
"path": "/HomeIndex/baseData/msgData",
"icon": "Message",
"component": "BaseData"
},
{
"name": "实体配置",
"path": "/HomeIndex/baseData/entitySet",
"icon": "Operation",
"component": "BaseData"
},
{
"name": "验证码数据",
"path": "/HomeIndex/baseData/validationCode",
"icon": "DocumentChecked",
"component": "BaseData"
}
]
},
{
"name": "系统管理",
"path": "/HomeIndex/SystemManagement",
"icon": "Tools",
"component": "System",
"children": [
{
"name": "用户管理",
"path": "/HomeIndex/SystemManagement/user",
"icon": "User",
"component": "System"
},
{
"name": "角色管理",
"path": "/HomeIndex/SystemManagement/role",
"icon": "Van",
"component": "System"
},
{
"name": "菜单管理",
"path": "/HomeIndex/SystemManagement/menu",
"icon": "Reading",
"component": "System"
},
{
"name": "日志管理",
"path": "/HomeIndex/SystemManagement/log",
"icon": "Memo",
"component": "System"
},
{
"name": "系统配置",
"path": "/HomeIndex/SystemManagement/set",
"icon": "DataLine",
"component": "System"
}
]
}
],
"msg": "左侧栏菜单数据获取"
}
24.3 路由代码修改(src/router/index.ts)
修改路由代码新增meta参数,其中的requireAuth参数会在后续使用导航守卫时根据此参数进行路由跳转时的校验
应用了router的路由匹配机制使用:path+来匹配一级到多级的路由
src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
// 自定义路由组件或从其他文件导入,这里选择从其他文件导入
import UserLogin from "../views/login/UserLogin.vue";
// import UserLogin from '@/views/login/UserLogin.vue';
import HomeIndex from '@/views/index/HomeIndex.vue';
import WorkPlat from '@/views/index/components/WorkPlat.vue';
import ResetPwd from '@/views/resetPassword/ResetPwd.vue';
// import ResetPwd from '../views/resetPassword/ResetPwd.vue';
// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [
{
path: '/',
// component: HomeIndex,
// component: WorkPlat,
// redirect: "/HomeIndex",
redirect: "/HomeIndex/WorkPlat",
meta: {
requireAuth: true
// requireAuth: false
}
},
{
path: '/UserLogin',
component: UserLogin,
meta: {
requireAuth: false
}
},
{
// 匹配具有多个部分的路由 如/111/222/333
// :path+表示匹配一级或多级
// path: '/:HomeIndex+',
path: '/HomeIndex/:path+',
// path: '/HomeIndex',
component: HomeIndex,
meta: {
requireAuth: true
// requireAuth: false
}
},
{
path: '/ResetPwd',
component: ResetPwd,
meta: {
requireAuth: false
}
}
]
// 创建路由实例并传递‘routes’配置 你可以在这里输入更多的配置
const router = createRouter({
history: createWebHistory(),
// routes:routes可以简写成routes,不会报错
// routes:[]
routes,
// : [
// {
// path:"/HomeIndex",
// component: HomeIndex
// }
// ]
})
export default router
24.4 缓存代码修改(src/store/index.ts)
加了一些注释,并未有大的改动
src/store/index.ts
// 引入, 用于存储全局的状态数据,可供其他地方调用
import { createStore } from "vuex";
// 引入工具方法
import utils from "@/utils/utils";
// 创建一个新的store实例
const store = createStore({
state() {
return{
// count: 0
// 当前登录的用户信息
userInfo: {},
// 当前登录的标识token
token: null,
}
},
getters: {
// 获取当前用户信息
getUserInfo(state:any){
return state.userInfo;
},
// 获取当前token
getToken(state:any){
return state.token;
},
// 判断当前是否登录
// isLogin(state:any){
// console.log("---",state.token, "===",state.userInfo)
// return (state.token && state.userInfo) ? true : false;
// }
},
mutations: {
// 登出,清除缓存中的数据
logout: function(state:any){
console.log("---111---")
state.userInfo = null;
utils.removeData("userInfo");
utils.removeData("token");
// utils.removeData("username");
// utils.saveData("username","");
// utils.removeData("saveUsername");
// utils.removeData("password");
// utils.removeData("savePassword");
},
// 存储用户信息
setUserInfo: function(state:any, userInfo:any){
state.userInfo = userInfo;
utils.saveData('userInfo', userInfo);
},
// 存储token
setToken: function(state:any, token:any){
state.token = token;
utils.saveData('token', token);
}
}
})
export default store;
24.5 主页代码修改(src/views/index/HomeIndex.vue)
无大修改,简单优化
src/views/index/HomeIndex.vue
<script setup lang="ts">
import { ref, } from 'vue'
// import {reactive, onMounted, onUnmounted } from 'vue'
// import utils from '@/utils/utils';
// import { useRoute, useRouter } from 'vue-router';
import MenuBar from './components/MenuBar.vue';
import ToolBar from './components/ToolBar.vue';
// 左侧菜单栏宽度
const slideWidth = ref('250px');
// 从MenuBar组件中传过来的值
const menuCollapse = (value:boolean)=>{
if(value){
// 如果值为true则为收起状态,宽度设为60px
slideWidth.value = '60px';
}else{
// 如果为false则是展开状态,宽度设为250px
slideWidth.value = '250px'
}
}
</script>
<template>
<!-- 后台主页 -->
<div class="index-layout">
<el-container>
<!-- 宽度以变量形式传入,打开关闭侧边菜单栏 -->
<el-aside class="layout-aside" :width="slideWidth">
<MenuBar @menuCollapse="menuCollapse"></MenuBar>
</el-aside>
<el-container>
<el-main class="layout-main">
<!-- tab标签页 -->
<!-- <el-tabs v-model="activeName" class="main-tabs" @tab-click="handleClick"> -->
<el-tabs class="main-tabs" >
<!-- <el-tab-pane label="User" name="first">主界面</el-tab-pane> -->
<el-tab-pane class="tabs-pane">
主界面
<RouterView></RouterView>
<template #label>
<span class="pane-label">
<!-- <el-icon class="label-icon">
<calendar />
</el-icon> -->
<el-icon class="label-icon"><Menu /></el-icon>
<span class="label-span">工作台</span>
</span>
</template>
</el-tab-pane>
</el-tabs>
</el-main>
<!-- ToolBar 头部工具栏 -->
<el-header class="layout-header">
<ToolBar></ToolBar>
</el-header>
</el-container>
</el-container>
</div>
</template>
<style scoped>
.index-layout{
/* height: 100%; */
/* width: 100%; */
font-size: 20px;
}
/* 侧边菜单栏样式 */
.layout-aside{
/* height: 100%; */
height: 100vh;
box-shadow: var(--el-box-shadow);
/* 左右侧栏之间的边框线 */
border-right: var(--el-border);
}
/* 菜单栏与右侧界面的边距设为0 */
.layout-main{
padding: 0;
margin: 0;
background: var(--el-bg-color-page);
}
/* header头工具栏相对于主界面的样式设置 */
.layout-main:deep(.el-tabs__header){
/* 让主界面与header头工具栏的距离归0 */
margin: 0;
/* 头部栏背景色设为白色 */
background-color: #fff;
/* 头部栏左侧边框距离 */
padding-left: 10px;
/* 头部栏右侧边框距离 */
padding-right: 10px;
}
/* 图标的位置调整,与文字上下和左右距离 */
.layout-main:deep(.pane-label .label-icon){
/* 图标右侧边距 */
margin-right: 4px;
/* 位置 */
/* position: relative; */
/* 图标上方距离 */
top: 2px;
}
.layout-header{
position: fixed;
top: 0;
right: 0;
width: 300px;
/* height: 60px; */
line-height: 35px;
}
</style>
24.6 菜单栏组件代码修改(src/views/index/components/MenuBar.vue)
新增菜单栏数据获取,菜单栏路由及组件的遍历,事件的监听,选中菜单栏的事件触发等
src/views/index/components/MenuBar.vue
<script setup lang="ts">
import { onMounted, reactive, ref, } from 'vue'
// import {reactive, onMounted, onUnmounted } from 'vue'
import utils from '@/utils/utils';
import api from '@/api/api';
import { useRoute, useRouter } from 'vue-router';
// import MenuBar from './components/MenuBar.vue';
// import ToolBar from './components/ToolBar.vue';
import HomeIndex from '../HomeIndex.vue';
const router = useRouter();
// 左侧菜单栏展开收起的标识
// const isCollapse = ref(true)
// 默认false,左侧栏展开
const isCollapse = ref(false)
const collapseController = (value:boolean)=> {
// isCollapse.value = !isCollapse.value;
isCollapse.value = value;
// 将值传到事件中
emits('menuCollapse', value);
}
// 定义事件,传值,并在主页监听
const emits = defineEmits(['menuCollapse', 'select'])
// const handleOpen = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
// }
// const handleClose = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
// }
// 菜单数据
const menuData = reactive([]);
// let menuData:any = null;
// 这里定义一个默认展示的路由地址,展示对应的菜单页面
const curMenu = ref("");
onMounted (()=>{
loadMenuData();
});
// 加载菜单数据
const loadMenuData = () => {
utils.showLoadding("加载中");
api.get("/menu/getMenu").then((res)=>{
utils.showLoadding("加载中");
if(!res||res.status!=200){
if(res.data){
utils.showError("问题");
return;
}
// utils.showError("加载失败");
return;
}
if(res.data.result==200){
// utils.showSuccess("请求成功")
menuData.values = res.data;
// menuData = res.data;
console.log("111",res.data);
console.log("222",menuData.values);
// menuData.splice(0, menuData.length);
// menuData.push(res.data.path);
// 将菜单信息注册到路由中
let indexChildrens:any = [];
menuData.values.data.forEach((item:any)=>{
console.log("item: ",item)
let routerItem:any = {
path: item.path,
// 注意:这里为了能正常使用还未创建的vue组件,故意将component写成component,不然报错
component: item.components,
meta:{
requireAuth: item.requireAuth
},
children: []
};
if(item.children && item.children.length>0){
item.children.forEach((subItem:any)=>{
console.log("subItem: ",subItem)
let subRouterItem:any = {
path: subItem.path,
component: subItem.components,
meta:{
requireAuth: subItem.requireAuth
},
};
routerItem.children.push(subRouterItem);
});
}
indexChildrens.push(routerItem);
console.log("indexChildrens: ",indexChildrens)
})
router.addRoute({
// path: '/:HomeIndex+',
// path: '/HomeIndex/:path+',
path: '/HomeIndex',
component: HomeIndex,
meta: {
requireAuth: true
// requireAuth: false
},
children: indexChildrens
});
}
// 根据url中的路由信息自动选中对应的菜单
// curMenu.value = router.currentRoute.value.path;
// 选中菜单de事件触发 传入的值为当前组件的路由地址如,/HomeIndex/businessMenu/detail
selectMenu(router.currentRoute.value.path);
}).catch((error)=>{
console.log("error,",error)
utils.hideLoadding();
utils.showError("加载失败");
}).finally(()=>{
utils.hideLoadding();
});
}
// 选择当前菜事件触发的方法
const selectMenu = (value:any)=>{
if(value){
curMenu.value = value;
// 当前菜单路由
console.log("selectMenu-value: ",value);
}
let curMenuData = null;
// 遍历菜单所有路由列表
menuData.values.data.forEach((item:any)=>{
console.log("selectMenu-item: ",item);
// 如果获取的菜单路由地址和当前地址一致
if(item.path == curMenu.value){
// 将数据获取
curMenuData = item;
console.log("selectMenu-curMenuData: ",curMenuData);
}
// 如果该菜单项的子菜单不为空且子菜单数量大于0,即该项为二级菜单
if(item.children && item.children.length>0){
// 遍历子菜单
item.children.forEach((subItem:any)=>{
console.log("selectMenu-subItem: ",subItem);
// 如果子菜单路由和子项的值一致
if(subItem.path==curMenu.value){
// 获取子项数据
curMenuData = subItem;
console.log("selectMenu-sub-curMenuData: ",curMenuData);
}
});
}
})
emits('select', curMenuData);
};
// 暴露选中菜单方法,可让外部调用该方法选中对应菜单
defineExpose({
selectMenu
})
// 13-8.5=4.5
// 36-4.5=31.5
// 12+10=22
// 9.5
</script>
<template>
<div class="logo" >
<div class="logo-name" v-if="!isCollapse">
寒山李白通用系统
</div>
<!-- 动态绑定侧边栏展开收起的图标按钮,当收起时即isCollapse为真,将class值转为logo-collapse-ef并设置图标居中 -->
<div class="logo-collapse" :class="{'logo-collapse-ef': isCollapse}">
<!-- 展开按钮 如果isCollapse是真则展示按钮,触发事件传值为false -->
<el-icon v-if="isCollapse" @click="collapseController(false)">
<Expand />
</el-icon>
<!-- 收起按钮 如果isCollapse是假则展示按钮,触发事件,传值为true -->
<el-icon v-else @click="collapseController(true)">
<Fold />
</el-icon>
</div>
</div>
<!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
<el-radio-button :value="false">expand</el-radio-button>
<el-radio-button :value="true">collapse</el-radio-button>
</el-radio-group> -->
<!-- default-active="4" 设置加载时的激活项,此为4 -->
<!-- :collapse-transition="false" 取消收起展开时的动画,展开收起更快 -->
<!-- router 启用vue-router模式 激活导航时 以index作为path进行路由跳转 使用 -->
<el-menu
:default-active="curMenu"
class="el-menu-vertical-collapse"
:collapse="isCollapse"
:collapse-transition="false"
router
@select="selectMenu"
>
<!-- @open="handleOpen"
@close="handleClose" -->
<!-- 请求接口返回数据-获取其中的菜单数据data,遍历菜单数据中的每一项 -->
<template v-for="item in menuData.values.data">
<!-- 如果该项中有子项,则为二级菜单,继续进行遍历 -->
<el-sub-menu class="menu" v-if="item.children && item.children.length>0" :index="item.path">
<!-- 该项的一级菜单图标和名称 -->
<template #title>
<!-- 该项的一级菜单图标 -->
<component class="menu-icon" :is="item.icon"></component>
<!-- 该项的一级菜单名称 -->
<span>{{ item.name }}</span>
</template>
<!-- 该项的二级菜单遍历 -->
<template v-for="subItem in item.children">
<el-menu-item class="menu" :index="subItem.path">
<component class="menu-icon" :is="subItem.icon"></component>
<span>{{ subItem.name }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 如果该项中没有子项,则为一级菜单,直接展示即可 -->
<el-menu-item class="menu" v-else :index="item.path">
<component class="menu-icon" :is="item.icon"></component>
<span>{{ item.name }}</span>
</el-menu-item>
</template>
<!-- <el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator One</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu> -->
<!-- <el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<template #title>Navigator Two</template>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon><document /></el-icon>
<template #title>Navigator Three</template>
</el-menu-item>
<el-menu-item index="4">
<el-icon><setting /></el-icon>
<template #title>Navigator Four</template>
</el-menu-item> -->
<!-- <el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator Five</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator Six</span>
</template>
<el-menu-item-group>
<template #title><span>Group One1</span></template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two1">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title><span>item four</span></template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</el-sub-menu> -->
</el-menu>
</template>
<style scoped>
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
} */
.logo{
display: flex;
background-color: var(--el-color-info-light-7)
/* height: 60px; */
}
.logo-name{
/* position: fixed; */
/* top: 0; */
/* left: 0; */
flex: 1;
text-align: center;
font-size: 20px;
font-weight: bold;
letter-spacing: 2px;
padding: 2%;
background-image: -webkit-linear-gradient(right, rgba(78, 224, 33, 0.795), #22fc2d, rgb(236, 126, 36));
/* background-image: -webkit-background-clip(bottom, red, #fd8403, yellow); */
/* -webkit-background-clip: text; */
background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-collapse{
width: 20px;
/* margin-top: 10px; */
padding-right: 10%;
padding-top: 1%;
/* height: 30px; */
text-align: center;
cursor: pointer;
font-size: 30px;
}
.logo-collapse:hover{
color: var(--el-color-primary)
}
/* 动态绑定侧边栏收起展开图标的样式 */
.logo-collapse-ef{
/* 图标宽度居中 */
width: 100%
}
.el-menu-vertical-collapse{
/* 剔除侧边栏菜单边框,收起时无边框 */
border: none;
height: calc(100% - 60px);
overflow-y: auto;
}
/* 设置滚动条样式 */
.el-menu-vertical-collapse::-webkit-scrollbar{
width: 10px;
}
/* 滚动槽 */
.el-menu-vertical-collapse::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark);
border-radius: 8px;
}
/* 滚动条滑块 */
.el-menu-vertical-collapse::-webkit-scrollbar-thumb{
border-radius: 8px;
background: var(--el-border-color-darker);
/* -webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark); */
}
/* 滚动条上下设置 */
/* .el-menu-vertical-collapse::-webkit-scrollbar-thumb{
background: var(--el-border-color-darker);
} */
.el-menu-vertical-collapse:deep(.menu-icon){
width: 20px;
margin: 10px;
color: var(--el-color-primary);
}
.el-menu-vertical-collapse .menu:hover{
color: var(--el-color-primary);
}
</style>
24.7 工具栏组件代码修改(src/views/index/components/ToolBar.vue)
新增了一个消息图标和未定义的函数
src/views/index/components/ToolBar.vue
<script setup lang="ts">
const openMsg = ()=>{
}
</script>
<template>
<div class="toolbar">
<div class="toolbar-icon">
<el-icon>
<Search />
</el-icon>
</div>
<div class="toolbar-icon">
<el-icon>
<FullScreen />
</el-icon>
</div>
<div class="toolbar-icon">
<el-icon>
<TurnOff />
</el-icon>
</div>
<div class="toolbar-icon" @click="openMsg">
<el-icon>
<BellFilled />
</el-icon>
</div>
<div class="toolbar-icon">
<el-dropdown>
<span class="icon-dropdown-link">
<el-icon class="link-icon--left" style="top: 2px;">
<UserFilled />
</el-icon>
<span>管理员</span>
<el-icon class="link-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item>退出系统</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<style scoped>
.toolbar{
display: flex;
}
.toolbar-icon{
flex: 1;
text-align: center;
cursor: pointer;
/* height: 20px; */
}
.toolbar-icon:hover{
color: var(--el-color-primary);
}
.toolbar-icon:deep(.icon-dropdown-link){
/* width: 100%; */
line-height: 30px;
width: 80px;
}
.icon-dropdown-link:hover{
color: var(--el-color-primary);
}
</style>
24.8 新增组件-工作台(src/views/index/components/WorkPlat.vue)
src/views/index/components/WorkPlat.vue
<script setup lang="ts">
</script>
<template>
工作台界面
</template>
<style scoped>
</style>
24.9 手机验证码登录组件代码修改(src/views/login/components/PhoneCodeForm.vue)
仅修改了登录成功后跳转的路由地址
src/views/login/components/PhoneCodeForm.vue
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 引入状态存储工具store
import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
})
// 登录验证规则
const rules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
smscode: [
{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}
],
imgcode: [
{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}
]
}
const formSize = "";
// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}
// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')
// 获取短信验证码
const getSmsCode = () => {
// 当点击获取短信验证码时,如果其他信息没填则提示输入
if (!loginForm.username) {
utils.showError('请输入用户名')
return
}
// if(!loginForm.smscode){
// utils.showError('请输入短信验证码');
// return;
// }
// TODO 从后台获取短信验证码
// 调用接口生成短信验证码
// 1 直接使用axios请求后端完整地址请求
// axios({
// method: 'post',
// url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
// // url: 'login/redis/setMessageCode',
// // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
// params: {
// username: loginForm.username
// }
// });
// 2 使用axios实例传参请求后端接口地址的用法
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: loginForm.username
}
})
curTime = 60
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime + '秒后重新获取';
if (curTime <= 0) {
smsCodeBtnText.value = '获取验证码'
clearInterval(timer)
// 清除时,值为空,防止重复点击触发多次
timer = null
}
}, 1000)
}
// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
// const route = useRoute();
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid: string, fileds: any) => {
// 如果valid值为假,则遍历输出报错
if (!valid) {
for (let key in fileds) {
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message)
return;
}
return
}
// 登录表单的记住用户名如果被勾选
if (loginForm.saveUsername==true) {
console.log("短信验证登录1:",loginForm.saveUsername);
// 保存输入的用户名
utils.saveData('username', loginForm.username)
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername)
} else {
console.log("短信验证登录2:",loginForm.saveUsername);
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username')
utils.removeData('saveUsername')
}
// TODO 调用接口登录
// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果
utils.showLoadding('正在加载中')
api({
method: 'get',
url: '/login/redis/getMessageCode',
params: {
username: loginForm.username,
smscode: loginForm.smscode
// imgcode: loginForm.imgcode
}
})
.then((res) => {
utils.hideLoadding()
console.log(res)
// console.log(res.status)
// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {
if (res.status != 200 || res.data.result != 200 || !res.data.msgCode) {
utils.showError('登录失败-请求数据返回有误');
return;
}
// console.log(res.data.data, loginForm.smscode);
if(res.data.msgCode == loginForm.smscode){
utils.showSuccess('登陆成功')
// 存储用户token信息并转到主页
// let userInfo = res.data.data
let userInfo = res.data
let token = res.data.token
// 状态数据存储
store.commit('setUserInfo', userInfo);
store.commit('setToken', token);
// 登录成功后将页面转到主页
// router.push('/HomeIndex')
router.push('/')
}else if(res.data.msgCode != loginForm.smscode){
utils.showError('登录失败-验证码错误');
return;
}
// utils.showError('登录失败')
})
.catch((error) => {
// utils.hideLoadding();
console.log(error);
utils.showError('登录失败-出现异常')
})
// api.post("/api/login/code",{
// username: loginForm.username,
// smscode: loginForm.smscode,
// imgcode: loginForm.imgcode
// }).then((res)=>{
// utils.hideLoadding();
// console.log(res);
// console.log(res.status);
// if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){
// if(res.data.message){
// utils.showError(res.data.message);
// return;
// }
// utils.showError('登录失败');
// return;
// }
// // 存储用户token信息并转到主页
// let userInfo = res.data.data;
// let token = res.data.token;
// utils.showSuccess('登陆成功');
// }).catch((error)=>{
// // utils.hideLoadding();
// utils.showError('登录失败');
// });
// 登录成功信息提示
// utils.showSuccess("登录成功");
})
}
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername')
// 如果记住用户名被勾选,则获取用户名显示(saveUsername可能会是undefined,当为undefined时也是真,故这里不能直接使用saveUsername,而是要判断是否为true)
if (loginForm.saveUsername==true) {
loginForm.username = utils.getData('username')
}
})
// 清空定时器
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<template>
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input
prefix-icon="UserFilled"
v-model="loginForm.username"
placeholder="请输入用户名"
size="large"
/>
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="loginForm.smscode"
placeholder="请输入验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{
smsCodeBtnText
}}</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="loginForm.imgcode"
placeholder="请输入图片验证码"
size="large"
/>
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn">
<el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn {
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn {
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine {
width: 100%;
}
</style>
24.10 二维码登录组件代码修改(src/views/login/components/QcodeForm.vue)
仅修改了登录成功后跳转的路由地址
src/views/login/components/QcodeForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted, onUnmounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'
const store = useStore();
const router = useRouter();
// 二维码
// let qcodePath:any = null;
// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录
let qrToken:string = "";
// 第一次获取验证码
// api({
// method: 'post',
// url: 'login/qr/generateQrCodeAsFile'
// }).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
// qrToken = res.data.token
// });
// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';
// 二维码
let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// let qcodeSrc = new URL(qcodePath, import.meta.url).href;
// let qcodeSrc = qcodePath;
const qcodeToken = ref('');
// 当前定时器事件
const curTime = ref(0);
let timer:any = null;
let username:string = utils.getData("username");
const qrString = "100100100222";
// 后台更新获取二维码
const loadQcode = () => {
// 后续改为从服务器上获取动态图片
// const qrString = "100100100222";
console.log("9999999====== "+qrString);
// let username:string = utils.getData("username");
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
username: username,
qrContent: qrString
}
}).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
qrToken = res.data.token
});
qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// qcodeSrc = new URL(qcodePath, import.meta.url).href;
// qcodeSrc = qcodePath;
// 初始化token的值
qcodeToken.value = qrToken;
// 设定定时时间
// curTime.value = 60;
// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可
curTime.value = 10;
// 定义定时器,倒计时
timer = setInterval(() => {
curTime.value--;
// 这里获取toekn,校验是否已经被登陆过
checkLogin();
if(curTime.value<=0){
// 事件为0则清空定时器
clearInterval(timer);
timer = null;
}
}, 1000);
};
// 登录提交事件
// const onSubmit = () => {
// };
// 挂载
onMounted(() => {
// 获取二维码
loadQcode();
});
// 清空计时器
onUnmounted(()=>{
timer && clearInterval(timer);
});
// 使用qcodeToken判断当前二维码是否已经被扫码登录
const checkLogin = () => {
// TODO
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
username: username,
qrContent: qrString
}
}).then((res)=>{
if(res.data.token){
utils.showSuccess("登录成功");
store.commit('setUserInfo',res.data);
store.commit('setToken',res.data.token);
// router.push('/HomeIndex');
router.push('/');
}
// res.data.token;
}).catch((error)=>{
console.log(error);
// utils.showError("登录失败")
});
}
</script>
<template>
<!-- 扫码登录 -->
<div class="qcodeLoginBox">
<div class="qcodeBox" >
<img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决">
<div v-if="curTime<=0" class="endBox" @click="loadQcode" >
当前二维码失效,点击重新加载{{ curTime }}秒
</div>
</div>
<div class="tipInfo" >
使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新
</div>
</div>
</template>
<style scoped>
/* 二维码窗口样式 */
.qcodeBox{
width: 80%;
height: 80%;
position: relative;
/* 边框自动 */
margin: 0 auto;
}
/* 二维码图片样式 */
.qcodeBox .qcodeImg{
width: 100%;
height: 100%;
}
.qcodeBox .endBox{
width: 100%;
height: 100%;
/* 悬浮显示 */
position: absolute;
/* 靠左 */
/* left: 0%; */
/* 靠上 */
top: 0;
/* 居中 */
/* text-align: center; */
/* 字体大小 */
font-size: 14px;
/* 字体颜色 */
color: red;
display: flex;
/* 上下居中 */
align-items: center;
/* justify-items: center; */
/* 左右居中 */
justify-content: center;
/* 背景色为灰色 */
background-color: #00000055;
}
/* .endImg{
filter: brightness(10%);
} */
/* 提示信息样式 */
.tipInfo{
/* 行高 */
line-height: 30px;
/* 字体大小 */
font-size: 14px;
/* 居中 */
text-align: center;
/* 颜色 */
color: var(--el-text-color-placeholder);
}
</style>
24.11 账号密码登录组件代码修改(src/views/login/components/UsernameForm.vue)
仅修改了登录成功后跳转的路由地址
src/views/login/components/UsernameForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref();
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 密码
password: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false,
// 记住用户名,默认否
savePassword: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password:[{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = "";
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 全局状态存储
const store = useStore();
// 路由调用
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// 登录表单的记住用户名如果被勾选
if(loginForm.savePassword){
// 保存输入的用户名
utils.saveData('password', loginForm.password);
// 保存被勾选的操作
utils.saveData('savePassword', loginForm.savePassword);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('password');
utils.removeData('savePassword');
}
// TODO 调用接口登录
utils.showLoadding("正在加载中");
api({
method: 'get',
url: '/login/login',
params: {
username: loginForm.username,
password: loginForm.password
}
}).then((res)=>{
utils.hideLoadding();
if(res.status != 200 || res.data.result != 200){
utils.showError("登录失败-请求数据返回有误");
return;
}
if(res.data.login == 1){
utils.showSuccess("登录成功");
// 存储用户信息
// let userInfoLogin = res.data.login;
let userInfoLogin = res.data;
let token = res.data.token;
console.log("usernamelogin:", token);
store.commit('setUserInfo', userInfoLogin);
store.commit('setToken', token);
console.log("----------------token: ", token);
// 登录成功后跳转主页
// router.push('/HomeIndex');
router.push('/');
}else if(res.data.login == 0){
utils.showError("登录失败-用户不存在");
return;
}else if(res.data.login == 2){
utils.showError("登录失败-密码错误");
return;
}
// utils.showError("登录失败-返回数据错误")
}).catch((error)=>{
console.log(error);
utils.showError("登录失败-发生异常");
});
// 登录成功提示
// utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername==true){
loginForm.username = utils.getData('username');
}
// 获取记住密码的值
loginForm.savePassword = utils.getData('savePassword');
// 如果记住密码被勾选,则获取密码
if(loginForm.savePassword==true){
loginForm.password = utils.getData('password');
}
});
</script>
<template>
<!-- 用户密码登录 -->
<div class="usernameLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<!-- 密码 -->
<div class="flexItem" >
<!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 -->
<!-- <el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /> -->
<el-input prefix-icon="Lock" show-password type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' />
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="saveUsername"> -->
<el-form-item >
<!-- 记住账号密码的勾选 -->
<div class="flex loginLine" >
<!-- 记住用户名 -->
<div class="flexItem" >
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
</div>
<!-- 记住密码 -->
<div class="flexItem" >
<el-form-item prop="savePassword">
<el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
</el-form-item>
</div>
<div class="flexItem" >
<!-- <el-form-item prop="savePassword"> -->
<router-link to="/ResetPwd">忘记密码</router-link>
<!-- </el-form-item> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="savePassword"> -->
<!-- </el-form-item> -->
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
24.12 App.vue组件代码修改(src/App.vue)
新增路由守卫,以及注释了部分多于代码
src/App.vue
<script setup lang="ts">
import { onMounted } from 'vue';
// import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import utils from './utils/utils';
import api from './api/api';
// // // 引入暗黑主题的动态切换
// import { useDark, useToggle } from '@vueuse/core'
// const isDark = useDark()
// // // 切换主题函数
// const toggleDark = useToggle(isDark)
// 状态存储
// let store = useStore();
// 路由使用
const router = useRouter();
// 路由守卫
router.beforeEach((to)=>{
console.log("to: ",to)
// 鉴权 在router.ts中设置的requireAuth参数,true则需要鉴权,false则不需要
if(to.meta.requireAuth){
console.log("开始鉴权,===>>>")
// 进入鉴权,通过缓存中的token与接口中的token进行校验
let token = utils.getData("token");
// let userInfo = utils.getData("userInfo");
// 当前不是登录状态
console.log("20240021 token",token);
let username = utils.getData("username");
if(username==undefined){
username = "";
}
let newToken = "";
api.get('/login/tokenCheck',{
params:{username}
}).then((res)=>{
console.log("data: ",res.data.token);
newToken = res.data.token;
if(token!=newToken){
console.log("鉴权失败,返回登录界面===>>>")
// 路由跳转登录
router.push("/UserLogin")
// 以下方法跳转失败
// return {
// path:"/UserLogin",
// query: {
// redirect: router.currentRoute.value.path
// }
// }
}
console.log("---->",newToken)
});
// console.log("---->",newToken)
// 如果token不一致,则进行路由跳转,进行重新登陆
// if(token!=newToken&&to.name!=="UserLogin"){
// if(token!=newToken){
// console.log("===>>>")
// 路由跳转登录
// router.push("/UserLogin")
// router.push({
// path: "/UserLogin",
// query: {
// redirect: router.currentRoute.value.path
// }
// })
// return {path: "/UserLogin"}
// }
console.log("鉴权成功,====》》》》")
}
});
onMounted(()=>{
// let tt = localStorage.getItem("token");
// console.log("tt: ",tt);
// console.log("=== ===");
let token = "";
// 由于token可能返回undefined报错,需要进行报错处理
try {
token = utils.getData("token");
} catch (error) {
error;
}
// console.log("store-token",token);
let userInfo = utils.getData('userInfo');
// console.log("userInfoL: "+userInfo)
// console.log("userInfoL: "+token&&userInfo)
if(token && userInfo){
// console.log("token userInfo :",token," -- ", userInfo);
// 登录成功,验证
utils.showLoadding("正在加载")
const username = utils.getData('username');
if(!username){
// 登录失败,跳转到登录页
if(username===undefined){
utils.saveData("username","");
}
// token验证失败
utils.showError("用户名过期-请重新登录");
router.push('/UserLogin');
utils.hideLoadding();
}else{
// console.log("username-", username);
api.get('/login/tokenCheck',{
params:{username}
}).then((res)=>{
// console.log("res.data.token",res.data);
// newToken = res.data.token;
utils.hideLoadding();
if(res.data.token==token){
// 登陆成功
// store.commit('setUserInfo', userInfo);
// store.commit('setToken', token);
// router.push('/');
// 验证成功后保持当前页面,即刷新页面时不再跳转
// router.push(router.currentRoute.value.path);
// 也可注释掉跳转功能,此处不做跳转处理,使用MenuBar.vue中的selectMenu方法进行保持当前选中菜单路由
utils.showSuccess("登录成功");
}else{
// if(username===undefined){
// utils.saveData("username","");
// }
// 登录失败
utils.showError("Token已过期,请重新登录");
// 登录失败,跳转到登录页
router.push('/UserLogin');
}
});
utils.hideLoadding();
}
}else{
// 登录失败,跳转到登录页
utils.showError("用户登录缓存过期,请重新登录");
router.push('/UserLogin');
utils.hideLoadding();
}
});
</script>
<template>
<!-- 暗黑主题动态切换按钮实现 -->
<!-- <button @click="toggleDark()">
<i inline-block align-middle i="dark:carbon-moon carbon-sun"/>
<span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span>
</button> -->
<RouterView></RouterView>
</template>
<style scoped>
/* @import url(./styles/default.css);
@import url(./styles/theme/default-theme.css); */
/* html,
body{
margin: 0;
} */
/* #app{
width: 100%;
height: 100%;
} */
</style>
24.13 页面效果展示
登录成功后的界面
选择菜单后的路由地址界面(详情示例)
刷新页面后依旧是这个菜单被选中,路由地址不变
感谢阅读,祝君暴富!