基于iview的后台管理模板布局页面

 约 49 分钟

代码在线预览

最近项目使用iview来开发,iview UI设计还是蛮好的相对于element-ui,但是后台模板布局是块硬伤,所以自己写了一个通用页面,以便以后可以直接拿来用,下面贴上代码,分享一下:
view admin的后台管理系统模板,但是个人愚见感觉有点太重了,所以自己结合iview的页面布局自己写了一套通用的模板页面,方便后续开发使用。

clipboard.png

clipboard.png

首先,我们新建一个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官网


以下内容是2018年12月14日更新

clipboard.png

clipboard.png

阅读 21.2k

推荐阅读
全栈工程师进阶
用户专栏

日常学习总结与分享,包括:前端、后台与运维,讲解的知识点包括:javascript、vuejs、reactjs、springb...

69 人关注
37 篇文章
专栏主页
目录