Bilibili Cloud E Office Vue+SpringBoot front-end and back-end separation project - Home menu function

 Project front-end study notes directory

B station cloud E office Vue+SpringBoot front and back end separation project - build vue.js project

Bilibili Cloud E Office Vue+SpringBoot front-end and back-end separation project - Home menu function

Project backend study notes directory

B station cloud E office Vue+SpringBoot front and back end separation project - MVC three-tier architecture to build a background project

1. Home page design

The previous article Bilibili Cloud E Office Vue+SpringBoot Front-end Separation Project - Building a vue.js Project introduced the project background, front-end Vue project structure construction and the design and implementation of the login module. In this article, we will enter into the development of the homepage, and mainly realize the dynamic loading function of the catalog. The dynamic loading here means that the browser can judge the menu role owned by the logged-in user according to the user name information of the logged-in user, and load the menu dynamically.

The data menu is obtained from the backend by the routing mode to automatically display the corresponding menu. In order to realize the hierarchical structure shown in the figure below, create a corresponding directory in the code, the content can be a simple line of text first, build the skeleton first, and then fill in the content. When implementing this page, the back-end interface design is a bit complicated, and the back-end logic will be added in subsequent updates. In order to write a blog more logically and hierarchically, the interface implementation part of the backend will be written separately. While writing the front-end and working on the back-end, there are still a bunch of bugs from time to time, with a big head.

1. Code directory structure

First of all, build up the skeleton of the menu, and create the following pages. The specific implementation will be added later.

views/emp basic information Create a new EmpBasic.vue EmpAdv.vue

views/per Employee profile New PerEmp.vu PerEc.vue PerTrain.vue PerSalary.vue PerMv.vue

views/sal 工资账着新增SalSob.vue SalSobcfg.vue SalTable.vue SalMonth.vue SalSearch.vue

views/sta comprehensive information statistics added StaAll.vue StaScore.vue StaPers.vue StaRecord.vue

views/sys System management new structure SysBasic.vue SysConfig.vue SysLog.vue SysAdmin.vue SysData.vue SysInit.vue

2. The backend requests the menu interface to return information

The menu we designed is the routing information loaded according to user information, that is, different users may have different menu permissions. The menu information returned by the interface is as follows. The submenu is represented by children, and when the parentId in the submenu is equal to the id of the parent menu, it indicates a certain parent-child menu relationship. The following relationship indicates that there is a hierarchical menu "employee profile/basic profile".

component indicates the file name where the component is located.

3. Package menu request tool src/utils/menus.js

In order to facilitate the management of menu data, the global state management of vuex is used to store the menu data in store.state. If store.state.routes has data, use it directly. Otherwise, initialize the routing menu, obtain routing data from the backend through the getRequest('/system/config/menu') method, and split according to the hierarchical relationship.

import {getRequest} from "@/utils/api";

// 菜单请求工具类

// router 路由; store Vuex
export const initMenu = (router, store) => {
    // 如果有数据,初始化路由菜单
    if (store.state.routes.length > 0) {
        return;
    }

    getRequest('/system/config/menu').then(data => {
        // 如果数据存在 格式化路由
        if (data) {
            // 格式化好路由
            let fmtRoutes = formatRoutes(data)
            // 添加到 router
            router.addRoutes(fmtRoutes)
            // 将数据存入 Vuex
            store.commit('initRoutes',fmtRoutes)
            // 连接 WebSocket
            store.dispatch('connect')
        }
    })
}

export const formatRoutes = (routes) => {
    let fmtRoutes = []
    routes.forEach(router => {
        let {
            path,
            component,
            name,
            iconCls,
            children
        } = router;
        // 如果有 children 并且类型是数组
        if (children && children instanceof Array) {
            // 递归
            children = formatRoutes(children)
        }
        // 单独对某一个路由格式化 component
        let fmRouter = {
            path: path,
            name: name,
            iconCls: iconCls,
            children: children,
            component(resolve) {
                // 判断组件以什么开头,到对应的目录去找
                if (component.startsWith('Home')) {
                    require(['@/views/' + component + '.vue'], resolve);
                }else if (component.startsWith('Emp')) {
                    require(['@/views/emp/' + component + '.vue'], resolve);
                }else if (component.startsWith('Per')) {
                    require(['@/views/per/' + component + '.vue'], resolve);
                }else if (component.startsWith('Sal')) {
                    require(['@/views/sal/' + component + '.vue'], resolve);
                }else if (component.startsWith('Sta')) {
                    require(['@/views/sta/' + component + '.vue'], resolve);
                }else if (component.startsWith('Sys')) {
                    require(['@/views/sys/' + component + '.vue'], resolve);
                }
            }
        }
        fmtRoutes.push(fmRouter)
    })
    return fmtRoutes
}

How to find the corresponding code path according to the component field in the interface?

Find by classifying the component fields in the interface object, for example, the component starts with Home, and the source code is in src/views/Home.vue.

if (component.startsWith('Home')) {

require(['@/views/' + component + '.vue'], resolve);

}

The initMenu method stores routing data in the store. If there is data in the store, it does not need to be initialized; otherwise, it is initialized.

When is it called? Every page needs to call the initialization menu method. We can put it in the route interceptor and execute it every time we access the route. Next design the route interceptor

4. Add routing interceptor - update main.js

Use router.beforeEach to register a global beforeEach guard. (to, from, next) three parameters: to the route to go; from the route from where; next() release. When the user logs in successfully, store the token in sessionStorage. If the token is carried, initialize the menu and let it go.

When the user logs in for the first time, save the current user information in the user of sessionStorage, and obtain the user's login information every time the route is switched.

// 使用 router.beforeEach 注册一个全局前置守卫
router.beforeEach((to, from, next) => {
  // to 要去的路由; from 来自哪里的路由 ; next() 放行
  // 用户登录成功时,把 token 存入 sessionStorage,如果携带 token,初始化菜单,放行
  if (window.sessionStorage.getItem('tokenStr')) {
      initMenu(router, store)
      // 如果用户不存在
      if (!window.sessionStorage.getItem('user')
      ) {
          // 判断用户信息是否存在
          return getRequest('/admin/info').then(resp => {
              if (resp) {
                  // 存入用户信息,转字符串,存入 sessionStorage
                  window.sessionStorage.setItem('user', JSON.stringify(resp))
                  // 同步用户信息 编辑用户
                  store.commit('INIT_ADMIN',resp)
                  next();
              }
          })
      }
      next();
  } else {
      if (to.path === '/') {
          next()
      } else {
          next('/?redirect=' + to.path)
      }
  }
})

 5. Configure store/index.js

Routing state management through vuex

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 导入 Vuex
const store = new Vuex.Store({
    state: {
        routes: []
    },
    mutations: { // 与 state 同步执行;可以改变 state 对应的值的方法
        // 初始化路由 菜单
        initRoutes(state, data) {
            state.routes = data
        },
    },
    // 异步执行
    actions: {
        }
})

export default store;

Introduce store in Main.js

import store from './store'
new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')

6. Home page function and style realization

The navigation menu function is implemented using the container layout container that comes with element-ui. The selection interface style is as follows:

Introduction of related components

el-container outer container

el-header top bar container

el-aside sidebar container el-menu navigation area

el-main main area container

el-footer bottom bar container

Add router attribute in el-menu navigation to realize dynamic rendering of menu routing

Home.vue realizes the acquisition and display of the menu on the left side of the homepage as a whole, and the setting of the personal center in the upper right corner. Get the current menu information and login information of the current user from store.state.

Add the router attribute in the el-menu navigation to realize the dynamic rendering of the menu routing; the home page navigation menu uses the NavMenu navigation menu control of element-ui. Use the attribute unique-opened: to ensure that only one menu is expanded each time the menu is clicked. Use the router attribute to use index as the path for routing jumps when activating navigation.

Bind the command in el-dropdown-item with the event callback method triggered by clicking the menu item @command of el-dropdown to realize logout and login and enter the personal center function. Element's MessageBox bullet box realizes the logout and login prompt box. Clear the menu information in vuex after logging out.

Use the el-breadcrumb breadcrumb control to display the path of the current page and quickly return to any previous page. For non-home page v-if="this.$router.currentRoute.path!=='/home'" display level: first/current page.

For home page v-if="this.$router.currentRoute.path==='/home'", display welcome font.

7. Home.vue file - home page 

<template>
  <div>
    <el-container>
      <el-header class="homeHeader">
        <div class="title">云办公</div>
        <!-- 1-1 添加在线聊天入口 -->
        <div>
          <el-button type="text" icon="el-icon-bell" size="normal"
                     style="margin-right: 8px;color: black;" @click="goChar"></el-button>
          <el-dropdown class="userInfo" @command="commandHandler">
          <span class="el-dropdown-link">
            {
   
   { user.name }}<i><img :src="user.userFace"></i>
          </span>
            <el-dropdown-menu slot="dropdown">
              <el-dropdown-item command="userinfo">个人中心</el-dropdown-item>
              <el-dropdown-item command="setting">设置</el-dropdown-item>
              <el-dropdown-item command="logout">注销登录</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
        </div>
      </el-header>
      <el-container>
        <el-aside width="200px">

          <!-- 1、添加 router  -->
          <el-menu router unique-opened>
            <!-- 2、循环整个路由组件,不展示 hidden: true 的路由组件 -->
            <el-submenu :index="index +''" v-for="(item,index) in routes"
                        :key="index" v-if="!item.hidden">
              <template slot="title"><i :class="item.iconCls" style="color: black;margin-right: 5px"></i>
                <span>{
   
   { item.name }}</span>
              </template>
              <!-- 3、循环遍历子路由 -->
              <el-menu-item :index="children.path"
                            v-for="(children,index) in item.children" :key="index">{
   
   { children.name }}
              </el-menu-item>
            </el-submenu>
          </el-menu>
        </el-aside>
        <el-main>
          <!-- 面包屑导航区域 -->
          <el-breadcrumb separator-class="el-icon-arrow-right"
                         v-if="this.$router.currentRoute.path!=='/home'">
            <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>{
   
   { this.$router.currentRoute.name }}</el-breadcrumb-item>
          </el-breadcrumb>
          <div class="homeWelcome" v-if="this.$router.currentRoute.path==='/home'">
            欢迎来到云办公系统!
          </div>
          <!-- 路由点位符 -->
          <router-view class="homeRouterView"/>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<script>
  export default {
    name: 'Home',
    data() {
      return {
        // 获取用户信息,将字符串转对象
        // user: JSON.parse(window.sessionStorage.getItem('user'))
      }
    },
    computed: {
      // 从 vuex 获取 routes
      routes() {
        return this.$store.state.routes
      },
      user() {
        return this.$store.state.currentAdmin
      }
    },
    methods: {
      // 1-2 进入在线聊天页面
      goChar() {
        this.$router.push('/chat')
      },
      // 注销登录
      commandHandler(command) {
        if (command === 'logout') {
          // 弹框提示用户是否要删除
          this.$confirm('此操作将注销登录, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            // 注销登录
            this.postRequest('/logout')
            // 清空用户信息
            window.sessionStorage.removeItem('tokenStr')
            window.sessionStorage.removeItem('user')
            // 路由替换到登录页面
            // this.$router.replace('/')
            // 清空菜单信息;在src/utils/menus.js 中初始化菜单信息
            this.$store.commit('initRoutes', [])
            this.$router.replace('/')
          }).catch(() => {
            this.$message({
              type: 'info',
              message: '已取消注销登录'
            });
          });
        }
        if (command === 'userinfo') {
          this.$router.push('/userinfo')
        }
      }
    }
  }
</script>
<style scoped>
  .homeHeader {
    background: #3e9ef5;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 15px;
    box-sizing: border-box;

  }

  .homeHeader .title {
    font-size: 30px;
    /*font-family: 微软雅黑;*/
    font-family: 华文楷体;
    color: white;
  }

  .homeHeader .userInfo {
    cursor: pointer;
  }

  .el-dropdown-link img {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    margin-left: 8px;
  }

  .homeWelcome {
    text-align: center;
    font-size: 30px;
    font-family: 华文楷体;
    color: #409ef4;
    padding-top: 50px;
  }

  .homeRouterView {
    margin-top: 10px;
  }
</style>

8. Update the route router/index.js

Ignore the hidden:true of router/index.js

The /home route is obtained from the home page

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from "@/views/Login";
Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'Login',
        component: Login,
        hidden: true // 不会被循环遍历出来
    }
]

const router = new VueRouter({
    routes
})

export default router

9. index.html eliminate margins

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>yeb-front</title>
  </head>
  <body style="margin:0px;padding:0px">
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

Guess you like

Origin blog.csdn.net/qq_36384657/article/details/124655783