代码,在线预览
最近项目使用iview来开发,iview UI设计还是蛮好的相对于element-ui,但是后台模板布局是块硬伤,所以自己写了一个通用页面,以便以后可以直接拿来用,下面贴上代码,分享一下:
view admin的后台管理系统模板,但是个人愚见感觉有点太重了,所以自己结合iview的页面布局自己写了一套通用的模板页面,方便后续开发使用。
首先,我们新建一个Layout.vue页面,这个页面就是整个布局模板的页面,我们设置好sidebar、topbar、以及中间的content就好,然后content,我们放上<router-view>就可以了,路由就随便你怎么跳转了。
Layout.vue
<style lang="less" scoped>
@import '../../../assets/gls-theme/common.less';
.ivu-layout.ivu-layout-has-sider{
height: 100%;
}
.ivu-layout-sider{
background: #fff;
}
.ivu-layout-header{
height: 100px;
line-height: 18px;
}
.ivu-menu{
height: 100%;
}
.admin-layout-container{
position: absolute;
width: 100%;
height: 100%;
.layout{
background: #f5f7f9;
position: relative;
overflow: hidden;
height: 100%;
& .dropdown-wrap{
background: rgb(73, 80, 96);
}
& .logo{
background: #4c364f80;
border-bottom: 1px solid #363e4f;
width: auto;
height: 60px;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.layout-header-bar{
background: #fff;
}
.layout-logo-left{
width: 90%;
height: 30px;
background: #5b6270;
border-radius: 3px;
margin: 15px auto;
}
.menu-icon{
transition: all .3s;
}
.rotate-icon{
transform: rotate(-90deg);
}
.menu-item span{
display: inline-block;
overflow: hidden;
width: 69px;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
transition: width .2s ease .2s;
}
.menu-item i{
transform: translateX(0px);
transition: font-size .2s ease, transform .2s ease;
vertical-align: middle;
font-size: 16px;
}
.collapsed-menu span{
width: 0px;
transition: width .2s ease;
}
.collapsed-menu i{
transform: translateX(5px);
transition: font-size .2s ease .2s, transform .2s ease .2s;
vertical-align: middle;
font-size: 22px;
}
}
</style>
<template>
<section class="admin-layout-container">
<div class="layout">
<Layout>
<Sider ref="side1" hide-trigger collapsible :collapsed-width="78" v-model="isCollapsed" style="background: rgb(73, 80, 96);">
<div class="logo" >
<img :src="logo" width="100" v-if="!isCollapsed"/>
<Avatar icon="person" size="large" v-else/>
</div>
<Menu
ref="side_menu"
:active-name="activeMenuName"
:open-names="openMenuName"
theme="dark"
width="auto"
:class="menuitemClasses"
@on-select="choosedMenu"
v-if="!isCollapsed">
<template v-for="(menu,menu_index) in menus">
<Submenu :name="menu.name" v-if="menu.children">
<template slot="title">
<Icon :size="20" :type="menu.icon"></Icon>
{{menu.title}}
</template>
<MenuItem :name="child.name" v-for="(child ,child_index) in menu.children" :key="child_index">
<Icon :size="20" :type="child.icon"></Icon>
{{child.title}}
</MenuItem>
</Submenu>
<MenuItem :name="menu.name" v-if="!menu.children && menu.showInMenus">
<Icon :size="20" :type="menu.icon"></Icon>
{{menu.title}}
</MenuItem>
</template>
</Menu>
<div class="dropdown-wrap">
<template v-for="(menu,menu_index) in menus" v-if="isCollapsed">
<Dropdown transfer placement="right-start" v-if="menu.children" @on-click="dropdownClick">
<Button style="width: 85px;margin-left: -5px;padding:10px 0;" type="text">
<Icon :size="25" color="#fff" :type="menu.icon"></Icon>
</Button>
<DropdownMenu style="width: 200px;" slot="list">
<template v-for="(child, i) in menu.children">
<DropdownItem :name="child.name">
<div style="display:flex;align-items:center;">
<Icon :size="16" :type="child.icon"></Icon>
<span style="padding-left:10px;">
{{ child.title }}
</span>
</div>
</DropdownItem>
</template>
</DropdownMenu>
</Dropdown>
<Dropdown transfer v-if="!menu.children && menu.showInMenus" placement="right-start" @on-click="dropdownClick">
<Button style="width: 85px;margin-left: -5px;padding:10px 0;" type="text">
<Icon :size="25" color="#fff" :type="menu.icon"></Icon>
</Button>
<DropdownMenu style="width: 200px;" slot="list">
<DropdownItem :name="menu.name">
<div style="display:flex;align-items:center;">
<Icon :size="16" :type="menu.icon"></Icon>
<span style="padding-left:10px;">
{{ menu.title }}
</span>
</div>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</template>
</div>
</Sider>
<Layout>
<Header :style="{position: 'fixed',
width: isCollapsed?'calc(100% - 78px)':'calc(100% - 200px)',
padding: 0,
display:'flex',
flexDirection:'column',
zIndex:20
}" class="layout-header-bar">
<div style="
display:flex;
align-tems:center;
justify-content:space-between;
position: relative;
height:60px;
line-height: 60px;
z-index: 1;
box-shadow: 0 2px 1px 1px rgba(100, 100, 100, 0.1);">
<div style="display:flex;align-items:center;">
<Icon @click.native="collapsedSider" :class="rotateIcon" :style="{margin: '0 20px 0'}" type="navicon-round" size="24"></Icon>
<span style="font-size:18px;font-weight:bold">{{user.mechanism.name}}后台管理系统</span>
</div>
<div style="margin-right:20px">
<!-- <Button type="text" icon="person" size="large">个人中心</Button>
<Button type="text" icon="android-notifications" size="large" @click="clickNotice">消息通知</Button> -->
<Button type="text" icon="android-exit" size="large" @click="quit">退出系统</Button>
</div>
</div>
<div style="display: flex;
position: relative;
padding-left:10px;
height: 40px;
background: #f5f7f9;
align-items: center;
box-shadow: 0 2px 1px 1px rgba(100, 100, 100, 0.1);">
<template v-for="(tab,tab_index) in tags">
<Tag type="dot"
:closable="tab.closable"
:color="tab.choosed ? 'blue':'#e9eaec'"
:name="tab.name"
@click.native="clickTag(tab)"
@on-close="closeTag" >
{{tab.title}}
</Tag>
</template>
</div>
</Header>
<Content :style="{
height: 'calc(100% - 100px)',
position: 'absolute',
top: '100px',
overflow: 'auto',
padding: '10px',
width:isCollapsed?'calc(100% - 78px)':'calc(100% - 200px)'
}">
<!--保存组件状态到内存,避免重新渲染-->
<keep-alive>
<router-view/>
</keep-alive>
</Content>
</Layout>
</Layout>
</div>
</section>
</template>
<script>
import {mapActions,mapState} from 'vuex'
export default {
data(){
return{
logo:`${this.$qiniuFileUrl}${process.env.LOGO}`,
isCollapsed: false,
// ------------------------------ 菜单操作开始 --------------------------------
title:'首页',
activeMenuName:'admin',
openMenuName:[],
menus:[
{
title:'首页',
num:1,
name:'admin',
icon:'home',
href:'/admin',
closable:false,
showInTags:true,
showInMenus:true,
choosed:true,
},
{
title:'课程管理',
name:'course-manage',
icon:'ios-bookmarks',
href:'/admin/course',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
},
{
title:'老师管理',
name:'teacher-manage',
icon:'person-stalker',
href:'/admin/teacher',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
},
{
title:'学生管理',
name:'student-manage',
icon:'university',
href:'/admin/student',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
},
{
title:'课堂',
name:'class-manage-parent',
icon:'easel',
children:[
{
title:'课堂管理',
name:'classroom-manage',
icon:'erlenmeyer-flask',
href:'/admin/classroom',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
},
{
title:'上课管理',
name:'class-manage',
icon:'android-time',
href:'/admin/class',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
}
]
},
{
title:'APK管理',
name:'apk-manage',
icon:'social-android',
href:'/admin/apk',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
},
{
title:'设置',
name:'setting',
icon:'gear-a',
href:'/admin/setting',
closable:true,
showInTags:false,
showInMenus:true,
choosed:false,
},
{
title:'消息通知',
name:'notice',
icon:'ios-navigate',
href:'/notice',
closable:true,
showInTags:false,
showInMenus:false,
choosed:false,
}
]
// ------------------------------ 菜单操作结束 --------------------------------
}
},
computed: {
...mapState(
{
user:state=>state.user
}
),
// 筛选menus中选中的menu
tags(){
let tags = [];
// 将menus中showInTags=true的标签放到tags数组中
this.menus.forEach(menu=>{
if(menu.showInTags){
tags.push(menu);
}else if(menu.children){
menu.children.forEach(child=>{
if(child.showInTags){
tags.push(child)
}
})
}
});
console.log('tags=>',tags)
//标签数组排序,从小到到
tags.sort((a,b)=>{
return (a.num - b.num)
})
return tags;
},
rotateIcon () {
return [
'menu-icon',
this.isCollapsed ? 'rotate-icon' : ''
];
},
menuitemClasses () {
return [
'menu-item',
this.isCollapsed ? 'collapsed-menu' : ''
]
}
},
// ------------------------------ 菜单操作开始 --------------------------------
//刷新页面之后保存并选中最后一次菜单和标签
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
let activeMenuName = localStorage.activeMenuName;
vm.activeMenuName = activeMenuName;
let tags_last_num = vm.tags[vm.tags.length - 1].num;
if(activeMenuName && activeMenuName.length != 0){
vm.menus.forEach(_menu=>{
if(activeMenuName == _menu.name){
_menu.choosed = true;
_menu.showInTags = true;
_menu.num = tags_last_num + 1;
}
else if(_menu.children){
_menu.children.forEach(child=>{
if(activeMenuName == child.name){
child.choosed = true;
child.showInTags = true;
child.num = tags_last_num + 1;
vm.openMenuName = [_menu.name];
}
})
}
else{
// 排除首页
if(_menu.name != 'admin'){
_menu.choosed = false;
_menu.showInTags = false;
}else{
_menu.choosed = false;
}
}
})
}
vm.$nextTick(()=>{
vm.$refs.side_menu.updateOpened();
vm.$refs.side_menu.updateActiveName();
});
})
},
// ------------------------------ 菜单操作结束 --------------------------------
methods: {
...mapActions([
'logout'
]),
quit(){
this.logout();
localStorage.removeItem('token');
localStorage.removeItem('activeMenuName');
this.$router.push('/login')
},
clickNotice(){
this.choosedMenu('notice');
},
collapsedSider() {
this.$refs.side1.toggleCollapse();
},
// ------------------------------ 菜单操作开始 --------------------------------
closeTag(event, name){
// 判断该标签是否是选中状态
// 如果是那么就要设置标签数组中最后一个标签成选中状态
// 如果否那么就直接删除就好
let is_choosed = false;
this.menus.forEach((menu,_index)=>{
if(menu.name == name){
is_choosed = menu.choosed;
menu.showInTags = false;
}else if(menu.children){
menu.children.forEach(child=>{
if(child.name == name){
is_choosed = child.choosed;
child.showInTags = false;
}
})
}
})
// 关闭标签并选中tags中最后一个标签高亮
if(is_choosed){
let last_tag = this.tags[this.tags.length-1];
last_tag.choosed = true;
this.$router.push(last_tag.href);
this.activeMenuName = last_tag.name;
localStorage.activeMenuName = this.activeMenuName;
}
},
clickTag(tag){
this.tags.forEach(_tag=>{
if(_tag.name == tag.name){
_tag.choosed=true;
}else{
_tag.choosed= false;
}
})
// 设置菜单选中name
this.activeMenuName = tag.name;
localStorage.activeMenuName = this.activeMenuName;
// 刷新菜单
this.$nextTick(()=>{
if(this.$refs.side_menu){
this.$refs.side_menu.updateActiveName()
}
});
//点击tab跳转
this.$router.push(`${tag.href}`);
},
choosedMenu(name){
// 获取标签数组最后一个元素的num
let tags_last_num = this.tags[this.tags.length - 1].num;
// 设置选中菜单name
this.activeMenuName = name;
localStorage.activeMenuName = this.activeMenuName;
let if_tab = false;
//根据name查找对应的菜单对象
let menu = null;
this.menus.forEach(_menu=>{
if(_menu.name == name){
// 只有不在tags数组中的元素才能设置num
if(!_menu.showInTags){
_menu.num = tags_last_num + 1;
}
menu = _menu;
_menu.showInTags = true;
_menu.choosed = true;
}
else if(_menu.children){
_menu.children.forEach(child=>{
if(child.name == name){
// 只有不在tags数组中的元素才能设置num
if(!_menu.showInTags){
child.num = tags_last_num + 1;
}
menu = child;
child.showInTags = true;
child.choosed = true;
}else{
child.choosed = false;
}
})
}
else {
_menu.choosed = false;
}
})
this.$router.push(`${menu.href}`);
},
dropdownClick(name){
this.choosedMenu(name);
}
// ------------------------------ 菜单操作结束 --------------------------------
}
}
</script>
在路由页面里面,我引入了几个页面,你们可以根据我的路径自己新建一下即可,然后把组件和路由结合起来就可以成功运行并应用我的Layout.vue页面进行后台整个页面的布局了,是不是超方便的。
Router.vue
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/admin/Home.vue'
import AdminLayout from '@/components/admin/Layout.vue'
import Admin from '@/views/admin/Admin.vue'
import UserManage from '@/views/admin/UserManage.vue'
import CityManage from '@/views/admin/CityManage.vue'
import ConditionManage from '@/views/admin/ConditionManage.vue'
import ConditionTypeManage from '@/views/admin/ConditionTypeManage.vue'
import IndustryManage from '@/views/admin/IndustryManage.vue'
import Setting from '@/views/admin/Setting.vue'
import Notice from '@/views/admin/Notice.vue'
import Login from '@/views/login/Login.vue'
Vue.use(Router)
let router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'admin',
component: AdminLayout,
children:[
{
path:'',
name:'index',
meta:{
title:'首页',
},
component:Admin
},
{
path:'usermanage',
name:'user-manage',
meta:{
title:'用户管理',
},
component:UserManage
},
{
path:'citymanage',
name:'citymanage',
meta:{
title:'城市管理'
},
component:CityManage
},
{
path:'conditiontypemanage',
name:'conditiontypemanage',
meta:{
title:'条件类型管理'
},
component:ConditionTypeManage
},
{
path:'conditionmanage',
name:'conditionmanage',
meta:{
title:'条件管理'
},
component:ConditionManage
},
{
path:'industrymanage',
name:'industrymanage',
meta:{
title:'一级行业'
},
component:IndustryManage
},
{
path:'setting',
name:'setting',
meta:{
title:'设置'
},
component:Setting
},
{
path:'notice',
name:'notice',
meta:{
title:'通知'
},
component:Notice
},
{
path:'test',
name:'test',
meta:{
title:'测试'
},
component:Setting
}
]
},
{
path:'/login',
name:'login',
meta:{
title:'登录',
},
component:Login
},
]
})
router.beforeEach((to, from, next) => {
let token = localStorage.token;
if(token && to.name != 'login'){
next()
}else if(token && to.name == 'login'){
next('/');
}else if(!token && to.name != 'login'){
next('/login')
}else{
next()
}
})
export default router;
最后,总结一下:其实网上有好多类似的模板管理页面,但是个人感觉一般,所以自己写了一个,相信在不久的将来这样的模板布局页面会越来越多的。
1、iview官网
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。