提示:前端新人,初来乍到,若文章写的不好大家多包涵。
文章目录
- 前言
- 一、左侧菜单
- 二、右侧头部区域
- 三、右侧标签区域
- 四、效果图
- 五、结束语
上一篇(VUE+ElementUI搭建框架-3)文章完善了登录页面功能,主页面搭建基本完成,接下来开始完成左侧菜单、头部菜单和菜单页区域。
注:之后的功能需要用到vuex,想了解的朋友可以先去看一看有个初步的了解(VueX的介绍)
左侧菜单在做的时候,我添加了一些模拟数据,通过模拟数据将菜单循环出来,这里我只做了二级菜单的效果。
页面需要注意以下几点(菜单页面东西虽然不多,但是细节很多,非常重要):
1、菜单的状态(展开、折叠),collapse属性控制菜单的收缩功能。我在头部组件里面添加了一个按钮来控制菜单状态,所以使用了vuex进行操作this.$store.state.isCollapse;
2、菜单的中的路由,点击菜单选项时要跳转到对应页面,此时就需要配置一下路由来实现这个功能。在这里我是用到了菜单的两个属性:default-active(默认打开的菜单页)、router(点击菜单选项后跳转);
3、菜单选项点击事件,点击后将菜单信息存储到vuex中,需要注意一点就是路由配置中的path、name最好与菜单数据中的保持一致,如果不一致可能跳出会出问题;
4、Logo在展开、折叠时的不同,也是根据菜单状态来判断的;
5、菜单对应的图标,我用的是ElementUI中的 (Icon 图标),如果是引用其他图库的图标那就需要格式调整;
菜单组件的代码如下:
/*左侧菜单页面*/
<template>
<div class="leftmenu">
<el-menu class="el-menu-vertical" :collapse="this.$store.state.isCollapse" :default-active="$route.name" unique-opened
router>
<div class="logo">
<span v-if="this.$store.state.isCollapse">Logo</span>
<span v-else>Logo</span>
</div>
<template v-for="item in navlist">
<template v-if="item.children.length > 0">
<el-submenu :index="item.name">
<template slot="title">
<i class="el-icon-location"></i>
<span>{{item.lable}}</span>
</template>
<el-menu-item :index="itemchilder.name" v-for="itemchilder in item.children"
:key="itemchilder.menuid" @click=clickMenu(itemchilder)>
<i :class="itemchilder.icon"></i>
<span slot="title">{{itemchilder.lable}}</span>
</el-menu-item>
</el-submenu>
</template>
<template v-else>
<el-menu-item :index="item.name" @click=clickMenu(item)>
<i :class="item.icon"></i>
<span slot="title">{{item.lable}}</span>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</template>
<script>
export default {
data() {
return {
navlist: [
{
"menuid": 1,
"path": "/index",
"name": "index",
"lable": "首页",
"icon": "el-icon-house",
"children": []
},
{
"menuid": 5,
"path": "",
"name": "power",
"lable": "权限配置",
"icon": "el-icon-user-solid",
"children": [
{
"menuid": 6,
"path": "/userinfo",
"name": "userinfo",
"lable": "用户管理",
"icon": "el-icon-user-solid"
},
{
"menuid": 7,
"path": "/roleinfo",
"name": "roleinfo",
"lable": "角色管理",
"icon": "el-icon-s-custom"
},
{
"menuid": 8,
"path": "/setpower",
"name": "setpower",
"lable": "权限分配",
"icon": "el-icon-s-platform"
}
]
},
{
"menuid": 2,
"path": "/poetry",
"name": "poetry",
"lable": "诗词收藏",
"icon": "el-icon-picture",
"children": []
},
{
"menuid": 3,
"path": "/imagefile",
"name": "imagefile",
"lable": "图片管理",
"icon": "el-icon-picture",
"children": []
},
{
"menuid": 4,
"path": "/docfile",
"name": "docfile",
"lable": "文件管理",
"icon": "el-icon-folder-opened",
"children": []
},
{
"menuid": 5,
"path": "/log",
"name": "log",
"lable": "日志记录",
"icon": "el-icon-document",
"children": []
},
]
}
},
computed: {
},
methods: {
clickMenu(item) {
// 将数据存入全局变量中
this.$store.commit('selectMenu', item);
}
}
}
</script>
<style scoped>
.leftmenu {
width: auto;
height: 100%;
overflow-x: hidden;
}
.leftmenu::-webkit-scrollbar {
width: 0;
}
.el-menu-vertical:not(.el-menu--collapse) {
width: 200px;
}
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 60px;
width: auto;
font-size: 24px;
}
</style>
顺便把路由配置和vuex的配置也贴出来,第一个是路由的配置,第二个是vuex中的配置:
/*路由配置*/
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
const routes = [
{
path: "/",
redirect: "/login" /*重定向页面*/
},
{
path: "/login",
name: "login",
component: () => import("@/views/login.vue"),
meta: { title: "登录" }
},
{
path: "/", //跳转后路径显示
name: "home",
component: () => import("@/views/home.vue"),
meta: { title: "主页" },
children: [
{
path: "/",
name: "index",
component: () => import("@/views/public/index.vue"),
meta: { keepAlive: true, title: "首页" }
},
{
path: "/index",
name: "index",
component: () => import("@/views/public/index.vue"),
meta: { keepAlive: true, title: "首页" }
},
{
path: "/userinfo",
name: "userinfo",
component: () => import("@/views/power/userinfo.vue"),
meta: { keepAlive: true, title: "用户信息" }
},
{
path: "/roleinfo",
name: "roleinfo",
component: () => import("@/views/power/roleinfo.vue"),
meta: { keepAlive: true, title: "角色信息" }
},
{
path: "/setpower",
name: "setpower",
component: () => import("@/views/power/setpower.vue"),
meta: { keepAlive: true, title: "权限分配" }
},
{
path: "/poetry",
name: "poetry",
component: () => import("@/views/file/poetry.vue"),
meta: { keepAlive: true, title: "诗词收藏" }
},
{
path: "/imagefile",
name: "imagefile",
component: () => import("@/views/file/imagefile.vue"),
meta: { keepAlive: true, title: "图片管理" }
},
{
path: "/docfile",
name: "docfile",
component: () => import("@/views/file/docfile.vue"),
meta: { keepAlive: true, title: "文件管理" }
},
{
path: "/log",
name: "log",
component: () => import("@/views/public/log.vue"),
meta: { keepAlive: true, title: "日志记录" }
},
{
path: "/404",
name: "404",
component: () => import("@/views/public/404.vue"),
meta: { keepAlive: false, title: "空白页" }
}
]
}
];
const router = new Router({
base: process.env.BASE_URL,
routes
});
export default router;
/*vuex配置*/
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 如果在组件中,想要访问store中的数据,只能通过 this.$store.state.*** 来访问
isCollapse: false, //左侧菜单是否折叠
taglist: [{ "menuid": 1, "path": "/index","name": "index","lable": "首页","icon": "el-icon-house","children": []}] //标签数组
},
mutations: {
//如果组件想要调用 mutations中的方法,只能使用this.$store.commit('方法名')
collapse(state) {
state.isCollapse = !state.isCollapse;
},
//添加标签到集合中
selectMenu(state, value) {
if (value.name != "index") {
const result = state.taglist.findIndex(
item => item.name === value.name
);
if (result === -1) {
state.taglist.push(value);
}
}
},
//删除集合中的标签
closeTag(state, value) {
const result = state.taglist.findIndex(item => item.name === value.name);
state.taglist.splice(result, 1);
}
},
actions: {},
getters: {
//这里只负责对外提供数据,不负责修改数据,如果想要修改state中的数据,请去找mutations
}
});
export default store;
这部分主要功能有两个,第一个是点击图标后菜单的收缩功能,第二个是用户头像的显示及下拉菜单,包括:个人信息、退出登录(已实现)。
<template>
<!-- 页面头部部分 -->
<div class="header">
<div class="shrink">
<i v-if="this.$store.state.isCollapse" class="el-icon-s-unfold" @click="isCollapses"></i>
<i v-else class="el-icon-s-fold" @click="isCollapses"></i>
</div>
<div class="header-right">
<div class="header-user-con">
<!-- 用户头像 -->
<div class="user-avator">
<img src="../assets/Background.jpg" />
</div>
<!-- 用户下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link"> {{ username }} <i class="el-icon-caret-bottom"></i></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
}
},
computed: {
username() {
return localStorage.getItem('ms_username') || '测试';
},
},
created() {
},
methods: {
//菜单是否收缩
isCollapses() {
this.$store.commit('collapse');
},
handleCommand(command) { // 用户下拉菜单选择事件
if (command == 'loginout') {
// localStorage.removeItem('ms_username');
this.$router.push({ path: "/login" });
}
}
}
}
</script>
<style scoped>
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
height: 60px;
font-size: 22px;
}
.shrink {
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
cursor: pointer;
}
.shrink i{
padding: 0 15px;
}
/* --------------- 用户头像区域的样式 ---------------- */
.header-right {
display: flex;
padding-right: 30px;
}
.header-user-con {
display: flex;
align-items: center;
justify-content: center;
height: 60px;
}
.user-avator {
margin-left: 20px;
}
.user-avator img {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
}
.user-name {
margin-left: 10px;
}
.el-dropdown-link {
cursor: pointer;
}
.el-dropdown-menu__item {
text-align: center;
}
</style>
标签区域的功能相对来说也比较重要:
1、左侧菜单选中之后需要通过vuex将数据存储,右侧标签通过vuex获取数据并展示出来;
2、标签的删除功能(vuex中数据也需要删除),点击标签后跳转到对应页面,并且选中对应的左侧菜单;
<template>
<div class="tags">
<el-tag v-for="item,index in tagdata" :key="item.name" :closable="item.name!='index'"
:effect="$route.name === item.name ? 'dark':'plain'" @close="handleClose(item,index)"
@click="handleClick(item,index)">
{{item.lable}}
</el-tag>
</div>
</template>
<script>
export default {
data() {
return {
/*tagdata中的数据格式
tagdata: [{"menuid": 1,"path": "/index","name": "index","lable": "首页","icon": "el-icon-house","children": [] }]
*/
};
},
computed: {
tagdata() {
return this.$store.state.taglist;
}
},
methods: {
//删除标签
handleClose(tag, index) {
//删除集合中的标签数据
this.$store.commit('closeTag', tag);
const length = this.tagdata.length;
if (tag.name !== this.$route.name) {
return;
}
if (length === 1) {
this.$router.push({ path: "/index" });
} else if (index === length) {
// 如果这两个值相等,就表明当前我们点击的是最后一个tag标签,则事件触发后跳转到左侧的标签
this.$router.push({ name: this.tagdata[index - 1].name })
}
else {
// 如果不相等,即只有一种可能性,即当前事件触发的索引不是最后一个,且只会是中间的,那么就跳转到右侧正序的标签
this.$router.push({ name: this.tagdata[index].name });
}
},
//标签点击
handleClick(tag, index) {
if (index == 0) { this.$router.push({ path: "/index" }); return; }
this.$router.push({ name: tag.name });
}
}
}
</script>
<style scoped>
.tags {
width: 100%;
height: 30px;
background-color: #fff;
}
.tags span {
height: 30px;
font-size: 13px;
line-height: 27px;
cursor: pointer;
}
</style>
做到这里前端的基本框架已经完成了,这是我自己练习的一个程序,主要还是为了熟悉一下vue方便以后开发。项目里面我还添加了一些静态页面,就不细说了,没有什么具体的知识点。最后我把项目源码上传了,大家想看了可以下载下来,下载地址vue项目。