17、springboot3 vue3开发平台-前端-主页面布局搭建

1. 修改App.vue

项目采用vue router 来管理路由, 作为单页面应用, 其页面为vue组件,通过路由动态渲染,App.vue作为根组件,需要配置路由出口
在这里插入图片描述

2. 布局页面

2.1 src\views\layout\Layout.vue


<template>
    <el-container style="height: 100vh">
        <el-aside width="auto">
            <el-scrollbar>
                <LeftLayout :isCollapse='isCollapse' />
            </el-scrollbar>
        </el-aside>
        <el-container>
            <el-header style="height: 5vh; padding: 0 5px 0 5px"><Header @parentClick='parentClick' /></el-header>
            <el-main style="padding: 0;"><MainView /></el-main>
            <el-footer style="height: 3vh; padding: 0;"><Footer/></el-footer>
        </el-container>
    </el-container>
</template>

<script setup lang="ts">
import LeftLayout from './Aside.vue'
import Header from './Header.vue'
import MainView from './MainView.vue'
import Footer from './Footer.vue'

import { ref } from 'vue'
const isCollapse = ref<any>(false)
const parentClick = (isCollapseValue: any) => {
    isCollapse.value = isCollapseValue
}
</script>

<style lang="scss" scoped>
</style>

2.2 src\views\layout\Aside.vue

使用前边自定义菜单组件

<template>
    <div class="menu-container">
        <div class="up-title" v-if="!isCollapse">
            <!-- <div>
                <svg-icon name="" width="20px" height="20px" />
            </div> -->
            <div class="title-text">{{ !isCollapse ? "Vue3 boot3 快速开发平台" : "" }}</div>
        </div>
        <div class="down-menu">
            <el-menu :default-active="route.path" class="el-menu-vertical-demo" :collapse="isCollapse" :router="true" 
            :collapse-transition="false">
                <MenuTree :menuList="menuList"></MenuTree>
            </el-menu>
        </div>
    </div>
</template>

<script setup  lang="ts">
import { onMounted} from 'vue'
//渲染菜单的组件
import MenuTree from "@/components/menuTree/index.vue"
import { useMenuStore } from '@/stores/menu.js'
import { useRoute } from 'vue-router'

const menuStore = useMenuStore()
const route = useRoute()

// 获取pinia的缓存的菜单数据
const menuList = menuStore.menuList

const props = defineProps({
    isCollapse: Boolean
})


</script>

<style lang="scss">
.menu-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    .up-title {
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;
        height: 5vh;
        // padding: 3px;
        /* 内边距 */
        border-bottom: 2px solid;
        border-right: 1px solid;
        /* 设置下边框宽度和样式 */
        border-color: #f5f5f5;

        .title-text {
            display: inline-block;
            vertical-align: middle;
            /* 文字与图片垂直居中对齐 */
            font-weight: bold;
            /* 加粗文字 */
            font-size: 13px;
            margin-left: 5px;
        }
    }

    .down-menu {
        flex-grow: 1;

    }
}

.el-menu-vertical-demo {
    height: 100%;
    overflow: auto;
}
</style>

2.3 src\views\layout\Header.vue



<template>
  <div class="header-container">
    <!-- div left -->
    <div class="left">
      <!-- 折叠按钮-->
      <div class="div-item"  @click="toggleCollapse()">
        <el-icon :size="22" v-show="!isMenuOpen"><Fold /></el-icon>
        <el-icon :size="22" v-show="isMenuOpen"><Expand /></el-icon>
      </div>
      <!-- 面包屑 -->
      <div class="div-item">
        <el-breadcrumb separator="/">
          <el-breadcrumb-item :to="{ path: '/' }"></el-breadcrumb-item>
          <template v-for="(item, index) in breadList">
            <el-breadcrumb-item v-if="item.menuName" :key="index" >{{ item.menuName }}</el-breadcrumb-item>
          </template>
        </el-breadcrumb>
      </div>
    </div>
    <!-- div right -->
    <div class="right">
        <div>
            <el-icon size="20" @click="fullScreenHander"><FullScreen /></el-icon>   
        </div>
      <div>
        <!-- <el-avatar> {{ userData.username }} </el-avatar> -->
      </div>
      <div>
        <span style="font-size: 18px;">{{ userData.username }}</span>
      </div>
      <div>
        <el-dropdown>
          <!-- <el-icon size="24"><MoreFilled /></el-icon> -->
          <el-icon size="20"><i-ep-User /></el-icon>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item  @click="selfInfoHander">
                <el-icon><UserFilled /></el-icon>个人信息
              </el-dropdown-item>
              <el-dropdown-item  @click="exitHander">
                <el-icon><ArrowLeft /></el-icon>退出登录
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      
    </div>
  </div>
</template>

<script setup lang="ts">
import { Fold, Expand, FullScreen, ArrowDown, ArrowLeft, UserFilled } from '@element-plus/icons-vue';

import { ref } from 'vue';
import { useRoute } from 'vue-router'

import {loginOutService } from '@/api/auth/index'

// 从stors中获取tab数据
import { useTokenStore } from "@/stores/token"
const tokenStore = useTokenStore()
import { useRouter } from 'vue-router';
const router = useRouter()

import { useMenuStore } from '@/stores/menu'
import { storeToRefs } from 'pinia';
const menuStore = useMenuStore();
let { breadList } = storeToRefs(menuStore)
const route = useRoute();

import {useUserInfoStore } from '@/stores/userInfo';
const userInfoStore = useUserInfoStore();

// 用户数据模型
let userData = ref({
  id: '',
  username: '',
  nickname: '',
})


// 获取登录用户信息
const getUser = async () => {
  //console.log('getUser userInfo:', userInfoStore.userInfo)
  userData.value.username = userInfoStore.userInfo!.username
  userData.value.id = userInfoStore.userInfo!.id
}
getUser()

// 折叠按钮处理
const emits = defineEmits(['parentClick']);
const isMenuOpen = ref(false);
const toggleCollapse = () => {
  isMenuOpen.value = !isMenuOpen.value;
  //console.log(isMenuOpen.value);
  emits('parentClick', isMenuOpen.value);
}

// 全屏----------------
const fullScreenHander = () => {
    let full = document.fullscreenElement
    //console.log(full)
    if(!full) {
        // document自带的全屏方法
        document.documentElement.requestFullscreen()
    }else {
        // document自带的推出全屏方法
        document.exitFullscreen()
    }
}

// 退出登录
const exitHander = async () => {
    await loginOutService()
    //console.log("exit ==================")
    // 清空 token 用户 菜单 路由
    tokenStore.removeToke()
    userInfoStore.removeInfo()
    menuStore.removeMenuRouter()
    // 跳转登录页面
    router.push("/login")
}


// 个人信息
const selfInfoHander = () => {
    let data = { title: '个人中心', path: '/userInfo', isClose: false, menuName: '个人中心', parentId: 0 }
        menuStore.tabList.push(data)
        menuStore.activeTab = "/userInfo"
        router.push("/userInfo")

}

</script>

<style lang="scss" scope>
.header-container {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    height: 5vh;
    border-bottom: 2px solid;
    border-color: #f5f5f5;
}

.left {
  display: flex;
  align-items: center; /* 垂直居中子项 */
  justify-content: center; /* 水平居中子项(如果需要)*/
  height: 100%;
}

.left > div {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    height: 100%;
    margin: 5px;
}

.right {
  display: flex;
  justify-content: center; /* 水平居中子项(如果需要)*/
  align-items: center; /* 垂直居中子项 */
}
.right > div {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    height: 100%;
    margin: 3px;
    margin-left: 5px;
}
.right > div:last-child {
    margin-right: 10px;
}
</style>

2.4 src\views\layout\MainView.vue

使用自定义tabs

 <template>
    <el-container style="height: 99%">
        <el-header style="height: 30px; padding: 0;">
                <div class="tabs-container">
                   <tabs :tagsList="tabList" :activePath="activeTab" @childEvent="handChildEvent"></tabs>
                </div>
                <ul v-show="contextMenuVisible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
                    <li @click="closeAllTabs">关闭所有</li>
                    <li @click="closeOtherTabs">关闭其他</li>
                </ul>
        </el-header>
        <el-main>
                <el-scrollbar>
                    <RouterView/>
                </el-scrollbar>
        </el-main>
    </el-container>

</template>
<script setup lang="ts">
import tabs from '@/components/tabs/index.vue'

// 从stors中获取tab数据
import { useMenuStore } from '@/stores/menu'
import { storeToRefs } from 'pinia'
import { useRouter, useRoute  } from 'vue-router'
import { nextTick, onMounted, ref, watch } from 'vue';
const router = useRouter()
const menuStore = useMenuStore();
let { tabList, activeTab } = storeToRefs(menuStore)

const route = useRoute()
watch(() => route.path, newRoute=> {
    console.log("watch route: ", newRoute)
    menuStore.routeCheage(newRoute)
})

// 选中
function gotoTab(path: string) {
    // 修改activeTab
    menuStore.setActiveTab(path);
    // 路由页面
    router.push(path);
    console.log("current tablist======", tabList.value.filter(item => item.path == path))
    let currentTab = tabList.value.filter(item => item.path == path)
    // 添加面包屑
    menuStore.addBreadList(currentTab[0])

}
// 移除
function closeTab(path: string) {
    // 移除的是否是当前激活的tab,激活首页
    console.log("closetab path=", path)
    if (path == menuStore.activeTab) {
        menuStore.setActiveTab("index")
        router.push("index")
    }
    menuStore.delTabList(path)
}

// 关闭contextMenu
const closeContextMenu = () => {
    contextMenuVisible.value = false
}

// 关闭所有标签页
const closeAllTabs = () => {
    contextMenuVisible.value = false;
    menuStore.initBreadcrumbAndTabs()
    router.push("/index")
}
// 关闭其它标签页
const closeOtherTabs = () => {
    menuStore.colseOthersTabs(currrentPath.value)
    contextMenuVisible.value = false
}

// 子组件传值
const handChildEvent = (data: any) => {
    console.log("handChildEvent,", data)
    if (data.type == 'close') {
        menuStore.delTabList(data.path)
    } 
    if (data.type == 'active') {
        menuStore.setActiveTab(data.path)
        router.push(data.path)
    }
    if (data.type == 'rightClick') {
        currrentPath.value = data.path
        contextMenuVisible.value = true
        left.value = data.left
        top.value = data.top
    }
}

// 右键标签
const contextMenuVisible = ref(false)
const left = ref()
const top = ref()
const currrentPath = ref()


watch(contextMenuVisible, (newValue, oldValue) => {
    if (newValue) {
        document.body.addEventListener("click", closeContextMenu)
    } else {
        document.body.removeEventListener("click", closeContextMenu)
    }
})

</script>

<style lang="scss" >
    .tabs-container {
        height: 30px;
        margin: 5px;
    }

.el-tabs {
    --el-tabs-header-height: 30px;
}

.demo-tabs > .el-tabs__content {
  padding: 0;
}
.demo-tabs > .el-tabs__header {
  margin: 0 0 5px 0;
}

/* 弹出框 */
.contextmenu {
    width: 100px;
    margin: 0;
    border: 1px solid #ccc;
    background: #fff;
    z-index: 3000;
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    font-size: 14px;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2);
}

.contextmenu li {
    margin: 0;
    padding: 7px 16px;
}

.contextmenu li:hover {
    background: #f2f2f2;
    cursor: pointer;
}
</style>

2.5 src\views\layout\Footer.vue

<template>
    <div class="footer-container">
        <span class="footer-span">Vue3 TS Vite Springboot3 快速开发平台 ©2024 Created by cc</span>
    </div>
</template>

<style lang="scss">
.footer-container {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    height: 90%;
    border-top: 2px solid;
    border-color: #f5f5f5;
    .footer-span {
        font-size: 11px;
    }
}
</style>
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不知所云,

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值