vue2.x从零到一的搭建自己风格的后台管理系统 - 项目整体布局

1,286 阅读5分钟

前言

继续上篇的文章,本文继续进行搭建自己风格的后台管理系统,自定义页面布局和侧边栏,面包屑等页面基础功能。

时隔一个月,我回来续更了,主要是我也是个普通的coder,需要努力工作,期待更好的生活。

项目文件树

上次文章只是提到了文件名,这次就把整个的文件树给结构放出来,及自己对文件夹的理解,顺便为以后的文章做做预告。

api

这里是存放所有的请求接口,先分公共数据接口和普通数据接口,然后普通数据接口再进行模块划分。

assets

这里是配置一些公共的样式和图片,assets图片和static文件下的图片在HTML都是正常使用的,但是在js中使用的话,路径要经过webpack中的file-loader编译,
路径不能直接写,需要用require引用。

举个栗子

 <img :src="require('@/assets/img/loginImg.png')" />   
 data() {
    return {
        sysList: [
            {
                id: 1,
                logo: require("@/assets/img/stage/service.png"),
                title: "系统",
                hidden: true
            },
            {
                id: 2,
                logo: require("@/assets/img/stage/user.png"),
                title: "中心",
                hidden: true
            }
        ]
    }
 }

components

这里是放置公共组件的地方,写后台管理系统,一般elementUI的组件就基本上足够使用了,但是避免不了有些需求会写一些新组件,或者更多复用组件,减少CV
大法。我对于这里的理解是,全局用的组件就直接以组件名为文件夹的命名写一个组件,模块组件就以模块名为文件夹的命名,在里面写组件。

layout

这里就是项目的整体布局的文件夹,如果有关页面的整体布局就是在这里修改。等下就回着重说一下这里的内容。

router

这里是vue的路由配置的地方。关于路由配置按照自己的项目实际情况进行调试。

store

这里是放全局数据的地方,使用的是vuex,状态管理模式。我的理解和组件那边类似,按照模块去给每一个modules命名,公共数据写在单独的modules中,getters
引入所有的数据,同时对数据进行处理。

utils

这里是放置公共方法,request封装请求等的地方。

views

这里是写项目中每一个页面的地方,我对此的理解是,按模块划分不同的文件夹,不同模块下定义对应页面名称的文件夹,然后在里面添加2个文件, 对应的名称分别
是index.vue,index.less,每个模块下的页面,重复性比较大的页面,可以共用一个页面,路由切换可以通过wacth去监听$route(to, from), 值得注意的地方
是,刷新页面不会触发wacth监听的$route

App.vue

这是根组件,就不过多介绍了,懂得都懂。

main.js

这里是项目的入口文件,所有页面都会加载这个文件,它的作用是实例化Vue,引入一些常用的公共功能和全局组件等。常用的公共功能有axios,vuex,router,权
限控制等。

permission.js

这里是路由的钩子beforeEach,通过对权限的判断,来确定能不能跳转。我对此处的理解是,vuex状态管理模式,但是当页面刷新的时候,所有数据就会消失,所以
需要在此处配置,以便刷新是继续获取到数据。

theme

这里是的在elementui官网,主题里面自定义的主题,后引用到项目中。

.env系列

这里是开发环境、测试环境和生产环境的配置。

.gitlab-ci.yml

这里是gitlab的CI工作流的配置,配置了一些语法指令当代码上传到gitlab后,通过CI/CD调起dockerfile文件,去执行不同操作

dockerfile系列

这里是dockerfile配置,通过gitlab的CI/CD工作流执行不同的dockerfile操作,最终实现自动化打包上测试环境/生产环境。

babel.config.js

这里是babel的配置

vue.config.js

这里是一个可选的vue配置文件,比如打包后的输出文件目录、放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录、devServer代理
和disableHostCheck等。

布局配置

开始今天的正文,自定义项目的页面布局。

页面文件布局和vue-element-admin保持高度一致,但是内容相对减少了许多,还是那句话,根据vue-element-admin修改成适用自己的框架。 这个是页面布局大致样子,可以根据自己的喜好去调整。简单介绍一下,左边aside是侧边栏,header中,左边是面包屑,右边是用户头像和一下拉选项卡等操作。main是项目页面的主要显示地方。

layout index.vue

<template>
    <div>
        <el-container>
            <el-aside width="210px" class="side">
                <side-bar></side-bar>
            </el-aside>
            <el-container>
                <el-header height="63px">
                    <nav-bar></nav-bar>
                </el-header>
                <el-main>
                    <keep-alive>
                        <router-view />
                    </keep-alive>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>
<script>
import NavBar from "./components/NavBar"
import SideBar from "./components/Sidebar/index"
export default {
    name: "layout",
    components: {
        "nav-bar": NavBar,
        "side-bar": SideBar
    }
}
</script>
<style lang="less" scoped>
// 清除el-headerpadding0 20px;的默认样式
/deep/ .el-header{
    padding: 0;
}

/deep/ .el-main {
    background: #F0F4FA;
    overflow: auto;
    height: calc(100vh - 63px);
}
.side{
    min-height: 100vh;
    box-shadow: 2px 0px 8px 0 #d2dbe8;
    z-index: 1;
}
</style>

layout-components-navbar.vue

<template>
    <div mode="horizontal" class="headerBar">
        <div class="headerBar__loginSwitch">
            <img src="@/assets/img/switch.png"  @click="changeCollapse" />
            <el-breadcrumb separator-class="el-icon-arrow-right">
                <el-breadcrumb-item to="/xxx">{{ navbar }}</el-breadcrumb-item>
                <el-breadcrumb-item v-for="(item, index) in matchedRoute" :key="index" :to="item.path">	
                {{item.name}}</el-breadcrumb-item>
            </el-breadcrumb>
        </div>
        <div class="headerBar__loginInfo">
            <img :src="require('@/assets/img/loginImg.png')" />
            <el-dropdown class="user" trigger="click" @command="handleCommand">
                <span>
                    zhongshi<i class="el-icon-arrow-down el-icon--right"></i>
                </span>
                <el-dropdown-menu slot="dropdown">
                    <!-- <el-dropdown-item command="user">个人中心</el-dropdown-item> -->
                    <el-dropdown-item command="back">返回首页</el-dropdown-item>
                    <el-dropdown-item command="quit">退出登录</el-dropdown-item>
                </el-dropdown-menu>
            </el-dropdown>
        </div>
    </div>
</template>

layout-components-sidebar-index.vue

<template>
    <div class="navWrap">
        <div class="logo">
            <img src="@/assets/img/logo.png" alt="" @click="toStage">
        </div>
        <el-scrollbar>
            <el-menu
                :default-active="activeMenu"
                :unique-opened="true"
                :collapse-transition="false"
                :collapse="false" 
                mode="vertical"
                router>
                <sidebar-item v-for="route in routes" :key="route.path" :item="route" />
            </el-menu>
        </el-scrollbar>
    </div>
</template>
<script>
import SidebarItem from './SidebarItem'
import { mapGetters } from 'vuex'
export default {
    components:{
        SidebarItem
    },
    computed: {
        ...mapGetters([
            "routes"
        ]),
        activeMenu() {
            const route = this.$route
            const { meta, path } = route
            return path
        },
        collapse() {
            return this.$store.state.settings.collapse
        }
    },
    methods: {
        toStage() {
            this.$router.replace('/stage')
        }
    }
}
</script>
<style lang="less" scoped >
.navWrap {
    height: 100%;
    background: #fff;
    box-shadow: 2px 0 8px 0 #D2DBE8;

    .logo {
        width: 200px;
        height: 63px;
        text-align: center;
        line-height: 63px;

        img{
            width: 171px;
            height: 27px;
            vertical-align: middle;
            cursor: pointer;
        }
    }

    .el-menu {
        height: 100%;
        border: none;
    }
    .el-menu-item.is-active {
        background-color: #f0f4fa;
        color: #3e4552;
        font-size: 14px;
        border-left: 3px solid #3d71ff;
    }
}
</style>

layout-components-sidebar-sidebaritem.vue

这里是展示侧边栏最主要的地方,我目前只做到了能够使用,但是对于完美使用还有一些距离,侧边栏小图片暂时还没加入进去,然后无限循环也没有加入进去,也就是vue-element-admin递归配置每一项我没有摸透,就没有采用。处于待优化状态

<template>
    <div v-if="!item.hidden">
        <template v-if="hasOneShowingChildren(item.children)">
            <el-menu-item :index="item.path" :key="item.path">
                <span slot="title">
                    {{ item.name }}</span>
            </el-menu-item>
        </template>

        <template v-else>
            <el-submenu :index="item.path + '/' + item.children[0].path" :key = "item.children[0].name">
                <template slot="title">
                    <span slot="title">{{ item.name }}</span>
                </template>
                <template v-for="child in filterChildren(item.children)">
                    <el-submenu v-if="child.children && child.children.length > 0 && hasOneShowingChildren(child.children)"
                                :index="item.path+'/'+child.path"
                                :key="child.name">
                        <template slot="title">
                            <span>{{child.name}}</span>
                        </template>
                    </el-submenu>
                    <el-menu-item v-else
                                :index="item.path+'/'+child.path"
                                :key="child.name">
                        <span>{{ child.name }}</span>
                    </el-menu-item>
                </template>
            </el-submenu>
        </template>
    </div>    
</template>
<script>
import path from "path"
export default {
    name: "SidebarItem",
    props: {
        item: {
            type: Object,
            required: true
        },
    },
    computed: {
        filterChildren() {
            return function(children) {
                return children.filter(i => !i.hidden)
            }
        }
    },
    data() {
        this.onlyOneChild = null;
        return {}
    },
    methods: {
        hasOneShowingChildren(children = []) {
            const showingChildren = children.filter(item => {
                return !item.hidden;
            });
            if (showingChildren.length === 1) return false
            return false;
        }
    }
}
</script>
<style lang="less" scoped>
.el-menu-item.is-active {
    background-color: #f0f4fa;
    color: #3d71ff;
    font-size: 14px;
    border-left: 3px solid #3d71ff;
}
</style>

其他

侧边栏都出来了,肯定是要配置路由了呀,我这边采用的是动态路由配置。

router-index.js

import Vue from "vue";
import Router from "vue-router";
// 第三方库需要use一下才能用
Vue.use(Router)

const originalPush = Router.prototype.push
   Router.prototype.push = function push(location) {
   return originalPush.call(this, location).catch(err => err)
}
export const defaultRoutes = [
    {
      path: "/",
      component: () => import("@/views/login"),
      hidden: true,
    },
    {
        path: "/login",
        name: "登录",
        component: () => import("@/views/login"),
        meta: { title: "登录", icon: "dashboard", affix: true },
        hidden: true,
    },

]

const router = new Router({
    routes: defaultRoutes
});

export default router

router-user.js

export default [{
    path: "/user",
    component: "layout/index",
    redirect: "/user/set",
    hidden: false,
    name: "个人管理",
    children: [
        {
            path: "set",
            name: "个人设置",
            component: "views/user/set/index",
            meta: { title: "个人设置", icon: "dashboard", affix: true },
        }
    ]
}]

总结

本文到这里就结束了,下一篇文章就是关于动态路由的配置和使用。相信就在不太久将来。