Proyecto de separación de front-end y back-end de Bilibili Cloud E Office Vue + SpringBoot: función de menú de inicio

 Directorio de notas de estudio del front-end del proyecto

B station cloud E office Vue + SpringBoot proyecto de separación frontal y posterior: compilación del proyecto vue.js

Proyecto de separación de front-end y back-end de Bilibili Cloud E Office Vue + SpringBoot: función de menú de inicio

Directorio de notas de estudio de backend del proyecto

B station cloud E office Vue + SpringBoot proyecto de separación frontal y posterior: arquitectura de tres niveles MVC para construir un proyecto en segundo plano

1. Diseño de la página de inicio

El artículo anterior Proyecto de separación front-end de Bilibili Cloud E Office Vue+SpringBoot: creación de un proyecto vue.js presentó los antecedentes del proyecto, la construcción de la estructura del proyecto front-end Vue y el diseño e implementación del módulo de inicio de sesión. En este artículo, entraremos en el desarrollo de la página de inicio y principalmente implementaremos la función de carga dinámica del catálogo. La carga dinámica aquí significa que el navegador puede juzgar la función del menú que posee el usuario que inició sesión de acuerdo con la información del nombre de usuario del usuario que inició sesión y cargar el menú dinámicamente.

El menú de datos se obtiene del backend mediante el modo de enrutamiento para mostrar automáticamente el menú correspondiente. Para implementar la estructura jerárquica que se muestra en la figura siguiente, cree un directorio correspondiente en el código, el contenido puede ser primero una línea simple de texto, primero construya el esqueleto y luego complete el contenido. Al implementar esta página, el diseño de la interfaz de back-end es un poco complicado y la lógica de back-end se agregará en actualizaciones posteriores. Para escribir un blog de manera más lógica y jerárquica, la parte de implementación de la interfaz del backend se escribirá por separado. Mientras escribo el front-end y trabajo en el back-end, todavía hay un montón de errores de vez en cuando, con una gran cabeza.

1. Estructura del directorio de códigos

En primer lugar, construya el esqueleto del menú y cree las siguientes páginas. La implementación específica se agregará más adelante.

vistas/emp información básica Crear un nuevo EmpBasic.vue EmpAdv.vue

vistas/por Perfil de empleado Nuevo PerEmp.vu PerEc.vue PerTrain.vue PerSalary.vue PerMv.vue

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

vistas/sta información completa estadísticas agregadas StaAll.vue StaScore.vue StaPers.vue StaRecord.vue

views/sys Nueva estructura de gestión del sistema SysBasic.vue SysConfig.vue SysLog.vue SysAdmin.vue SysData.vue SysInit.vue

2. El backend solicita a la interfaz del menú que devuelva información.

El menú que diseñamos es la información de enrutamiento cargada de acuerdo con la información del usuario, es decir, diferentes usuarios pueden tener diferentes permisos de menú. La información del menú devuelta por la interfaz es la siguiente. El submenú está representado por elementos secundarios, y cuando el ID de padre en el submenú es igual al ID del menú principal, indica una determinada relación del menú principal-hijo. La siguiente relación indica que existe un menú jerárquico "perfil de empleado/perfil básico".

componente indica el nombre del archivo donde se encuentra el componente.

3. Herramienta de solicitud de menú de paquete src/utils/menus.js

Para facilitar la gestión de los datos del menú, la gestión del estado global de vuex se utiliza para almacenar los datos del menú en store.state. Si store.state.routes tiene datos, utilícelos directamente. De lo contrario, inicialice el menú de enrutamiento, obtenga datos de enrutamiento del backend a través del método getRequest('/system/config/menu') y divídalos según la relación jerárquica.

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
}

¿Cómo encontrar la ruta del código correspondiente según el campo del componente en la interfaz?

Busque clasificando los campos del componente en el objeto de interfaz, por ejemplo, el componente comienza con Inicio y el código fuente está en src/views/Home.vue.

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

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

}

El método initMenu almacena datos de enrutamiento en la tienda. Si hay datos en la tienda, no es necesario inicializarlos; de lo contrario, se inicializan.

¿Cuándo se llama? Cada página necesita llamar al método del menú de inicialización. Podemos ponerlo en el interceptor de ruta y ejecutarlo cada vez que accedamos a la ruta. Siguiente diseño del interceptor de ruta.

4. Agregue un interceptor de enrutamiento: actualice main.js

Utilice router.beforeEach para registrar un guardia global beforeEach. (hacia, desde, siguiente) tres parámetros: a la ruta a ir; desde la ruta desde donde; siguiente() lanzamiento. Cuando el usuario inicie sesión correctamente, almacene el token en sessionStorage. Si el token se lleva, inicialice el menú y suéltelo.

Cuando el usuario inicia sesión por primera vez, guarde la información del usuario actual en el usuario de sessionStorage y obtenga la información de inicio de sesión del usuario cada vez que se cambia la ruta.

// 使用 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. Configurar tienda/index.js

Gestión del estado de enrutamiento a través de 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;

Introducir tienda en Main.js

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

6. Función de la página de inicio y realización del estilo.

La función del menú de navegación se implementa utilizando el contenedor de diseño de contenedor que viene con element-ui. El estilo de la interfaz de selección es el siguiente:

Introducción de componentes relacionados.

contenedor exterior el-container

contenedor de barra superior el-header

el-aside barra lateral contenedor el-menú área de navegación

contenedor del área principal el-main

contenedor de barra inferior el-footer

Agregue el atributo de enrutador en la navegación del menú el para realizar una representación dinámica del enrutamiento del menú

Home.vue realiza la adquisición y visualización del menú en el lado izquierdo de la página de inicio en su conjunto y la configuración del centro personal en la esquina superior derecha. Obtenga la información del menú actual y la información de inicio de sesión del usuario actual de store.state.

Agregue el atributo del enrutador en la navegación del menú el para realizar la representación dinámica de la ruta del menú; el menú de navegación de la página de inicio utiliza el control del menú de navegación NavMenu de element-ui. Utilice el atributo abierto único: para garantizar que solo se expanda un menú cada vez que se haga clic en él. Utilice el atributo de enrutador para utilizar el índice como ruta para los saltos de enrutamiento al activar la navegación.

Vincule el comando en el-dropdown-item con el método de devolución de llamada de evento activado al hacer clic en el elemento del menú @command de el-dropdown para cerrar sesión e iniciar sesión e ingresar a la función del centro personal. El cuadro de viñetas MessageBox de Element realiza el cuadro de solicitud de cierre de sesión e inicio de sesión. Borre la información del menú en vuex después de cerrar sesión.

Utilice el control de ruta de navegación el-breadcrumb para mostrar la ruta de la página actual y volver rápidamente a cualquier página anterior. Para la página que no es de inicio v-if="this.$router.currentRoute.path!=='/home'" nivel de visualización: primera/página actual.

Para la página de inicio v-if="this.$router.currentRoute.path==='/home'", muestra la fuente de bienvenida.

7. Archivo Home.vue - página de inicio 

<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. Actualice la ruta router/index.js

Ignore lo oculto: verdadero de router/index.js

La ruta /home se obtiene desde la página de inicio

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 elimina los márgenes

<!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>

Supongo que te gusta

Origin blog.csdn.net/qq_36384657/article/details/124655783
Recomendado
Clasificación