B站云E办Vue+SpringBoot前后端分离项目——首页菜单功能

 项目前端学习笔记目录

B站云E办Vue+SpringBoot前后端分离项目——搭建vue.js项目

B站云E办Vue+SpringBoot前后端分离项目——首页菜单功能

项目后端学习笔记目录

B站云E办Vue+SpringBoot前后端分离项目——MVC三层架构搭建后台项目

一、首页设计

上一篇B站云E办Vue+SpringBoot前后端分离项目——搭建vue.js项目介绍了项目背景、前端vue项目结构搭建及登录模块的设计与实现。在这一篇我们将进入首页的开发中,主要实现了目录的动态加载功能。这里的动态加载是指浏览器可以根据登录用户的用户名信息判断其拥有的菜单角色,动态加载菜单。

数据菜单由路由模式从后端获取,实现自动展示相应的菜单。为了实现下图所示的层级结构,在代码里创建相应的目录,内容可以先是简单的一行文字,先把骨架搭建起来,后续进行内容的填充。在实现这个页面时后端的接口设计实在有点复杂,在后续的更新中会加入后端的逻辑一并进行。为了更有逻辑和层次的写博客,后端的接口实现部分将分开写。边写前端边搞后端,还时不时一堆bug,脑袋大。

1.代码目录结构

首先先把菜单的骨架搭建起来,新建以下若干页面,具体实现待后面继续补充。

views/emp基本资料 新建 EmpBasic.vue EmpAdv.vue

views/per 员工资料 新建 PerEmp.vu PerEc.vue PerTrain.vue PerSalary.vue PerMv.vue

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

views/sta 综合信息统计 新增StaAll.vue StaScore.vue StaPers.vue StaRecord.vue

views/sys 系统管理 新增 SysBasic.vue SysConfig.vue SysLog.vue SysAdmin.vue SysData.vue SysInit.vue

2.后端请求菜单接口返回信息

我们设计的菜单是根据用户信息加载的路由信息,即不同用户可能有不同的菜单权限。接口返回的菜单信息如下。通过children表示子菜单,子菜单中的parentId与父菜单的id相等时表示一个确定的父子菜单关系。如下的关系表示有一个层级菜单“员工资料/基本资料”。

component表示组件所在的文件名称。

3.封装菜单请求工具src/utils/menus.js

为了方便管理菜单数据,使用vuex的全局状态管理,将菜单数据存于store.state中。如果store.state.routes有数据,则直接使用。否则初始化路由菜单,通过getRequest('/system/config/menu')方法从后端获取路由数据,按照层次关系拆分。

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
}

如何根据接口中的component字段找到对应的代码路径呢?

通过对接口对象中的component字段分类查找,例如component以Home开头,源代码在src/views/Home.vue中。

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

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

}

initMenu方法将路由数据存于store中,如果store中有数据则无需初始化,否则,初始化。

什么时候调用?每一个页面都需要调用初始化菜单方法。我们可以放在路由拦截器里,每次访问路由都要执行一次。接下来设计路由拦截器

4.添加路由拦截器——更新main.js

使用 router.beforeEach 注册一个全局前置守卫。(to, from, next)三个参数:to 要去的路由; from 来自哪里的路由 ; next() 放行。用户登录成功时,把 token 存入 sessionStorage,如果携带 token,初始化菜单,放行。

用户首次登陆时,将当前用户信息保存在sessionStorage的user中,每次路由切换时获取用户的登陆信息。

// 使用 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.配置store/index.js

通过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;

在Main.js中引入store

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

6.首页功能及样式实现

导航菜单功能实现使用element-ui自带的container布局容器。选择界面样式如下:

相关组件介绍

el-container 外层容器

el-header 顶栏容器

el-aside 侧边栏容器 el-menu导航区域

el-main 主要区域容器

el-footer底栏容器

在el-menu导航里添加router属性实现菜单路由的动态渲染

Home.vue整体上实现了首页左侧菜单的获取和展示,右上角的个人中心的设置。从store.state中获取当前菜单信息、当前用户的登陆信息.

在el-menu导航里添加router属性实现菜单路由的动态渲染;首页导航菜单使用element-ui的NavMenu导航菜单控件。使用属性unique-opened:保证每次点击菜单只有一个菜单的展开。使用router属性,在激活导航时以 index 作为 path 进行路由跳转。

通过el-dropdown的@command点击菜单项触发的事件回调方法绑定el-dropdown-item中的command,实现注销登陆和进入个人中心功能。element的MessageBox弹框实现注销登陆提示弹框。退出登陆后清除vuex中的菜单信息。

使用el-breadcrumb面包屑控件实现显示当前页面的路径,快速返回之前的任意页面功能。对于非首页的页面v-if="this.$router.currentRoute.path!=='/home'"显示层级:首先/当前页。

对于首页v-if="this.$router.currentRoute.path==='/home'",显示欢迎字体。

7.Home.vue文件-首页 

<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.更新路由router/index.js

忽略router/index.js的hidden:true的

/home路由从首页获取

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消除边距

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

当然,我可以为您提供一个详细教程来帮助您部署Vue+SpringBoot前后端分离项目服务器上使用Docker。 首先,确保您已经完成以下准备工作: - 注册一个服务提供商的账号,并创建一个服务器实例。 - 在本地环境中安装了Docker,并熟悉Docker的基本操作。 - 本地已经安装了Node.js和npm,以及Vue CLI和Java开发环境。 以下是详细的步骤: 1. 登录到服务器: 使用SSH工具连接到您的服务器。例如,使用命令行工具执行以下命令: ``` ssh username@server_ip_address ``` 2. 安装Docker: 根据您的服务器的操作系统,选择对应的安装方式进行Docker安装。以下是一些常见操作系统的安装命令: - Ubuntu: ``` sudo apt-get update sudo apt-get install docker.io ``` - CentOS: ``` sudo yum update sudo yum install docker ``` 3. 验证Docker安装是否成功: 执行以下命令来验证Docker是否已经成功安装: ``` docker version ``` 4. 构建Vue项目: 在本地开发环境中,使用Vue CLI创建Vue项目,并进行开发和测试。确保项目可以正常运行。 ``` vue create myproject cd myproject npm run serve ``` 5. 打包Vue项目: 在Vue项目根目录下执行以下命令,将Vue项目打包成静态文件。 ``` npm run build ``` 6. 创建SpringBoot项目: 使用Spring Initializr创建SpringBoot项目,并进行开发和测试。确保项目可以正常运行。 - 访问Spring Initializr网站:https://start.spring.io/ - 选择项目的基本设置,如使用的编程语言、构建工具、Spring Boot版本等。 - 添加所需的依赖项,如Spring Web、Spring Data JPA等。 - 点击"Generate"按钮下载生成的SpringBoot项目压缩包。 - 解压缩项目压缩包,并使用您喜欢的集成开发环境(IDE)打开项目。 7. 创建Dockerfile: 在SpringBoot项目的根目录下创建一个名为`Dockerfile`的文件,用于定义Docker镜像的构建步骤。在`Dockerfile`中添加以下内容: ``` FROM openjdk:8-jdk-alpine VOLUME /tmp ADD target/myproject.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] ``` 8. 构建Docker镜像: 在SpringBoot项目的根目录下执行以下命令,构建Docker镜像: ``` docker build -t myproject . ``` 9. 运行Docker容器: 执行以下命令,在Docker中运行SpringBoot项目的Docker容器: ``` docker run -d -p 80:8080 myproject ``` 10. 访问应用: 使用浏览器访问您的服务器的公网IP地址,即可查看部署好的前后端分离项目。 希望这个详细教程能够帮助您成功部署Vue+SpringBoot前后端分离项目服务器上使用Docker。如果您有任何问题,请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三月的一天

你的鼓励将是我前进的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值