前言
弹出框的五个标签功能,重新加载、关闭标签、关闭其他标签、关闭所有标签都已经实现了,现就剩下当前标签全屏标签还没有实现。
在BuildAdmin中,一共实现了两种全屏。一种是main区域全屏,即边栏消失,页面占据整个浏览器页面,是在弹出框的实现的。
另一种全屏是页面占据整个显示器屏幕,是在后面的导航菜单栏实现的。
本篇文章要讲的是第一种全屏方式的实现。
全屏Fullscreen
根据我们的对全屏(例如浏览器全屏、播放器全屏)的一些使用经验,全屏的功能主要分为两部分:全屏和退出全屏。我们从图中可以看到,这里的全屏指的是:header和aside区域隐藏,main占据整个页面,即100% 。
如果想要隐藏一个html元素(组件),在css中,将display属性设置为none即可。在vue中,v-if和v-show同样也是用于决定组件是否渲染(展示),BuildAdmin中使用的是v-if。
tabFullScreen
如果想要多个组件同时隐藏/展示,在vue中只需要将多个元素的v-if属性指向同一个boolean变量,当变量为true时都展示;为false都隐藏;如果有的隐藏有的展示,用!取反即可。如何定义这个变量,多个组件能同时访问的当然是之前讲到的状态变量了,即pinia。
在之前讲的tabs中所有的状态变量都定义在了navTabs中,这里也不例外。定义了tabFullScreen变量来控制全屏。
我们先看看onContextmenuItem中全屏逻辑是如何定义的。
case 'fullScreen':
if (route.path !== menu?.path) {
router.push(menu?.path as string)
}
navTabs.setFullScreen(true)
break
case中也是处理了两种情况,一是将当前激活的tab全屏,二是将非激活的tab全屏。针对于第二种情况,将当前route与传入的menu比较,如果不同,先进行跳转。
然后调用navTabs的setFullScreen方法。
const setFullScreen = (fullScreen: boolean): void => {
state.tabFullScreen = fullScreen
}
此时tabFullScreen由默认值false变成了true。
隐藏aside、header
去看aside.vue中菜单栏aside是如何隐藏的。
el-aside中v-if条件,瑟吉欧对navTabs中的tabFullScreen进行了取反,当tabFullScreen为true时,aside就为false被隐藏。
header和aside同样的实现方式。
这样就实现了header和aisde隐藏、main全屏的功能。接下来就是实现取消全屏。
取消全屏
从全屏的实现过程来反推,取消全屏就是将tabFullScreen设置为false就行了。
有人就会说了,取消全屏不都是按ESC吗。ESC用于取消整个屏幕的那种全屏,对于这种全屏BuildAdmin中定义了一个取消按钮按钮组件,来实现取消全屏。
如图,取消全屏是一个居中的位置可变的按钮,鼠标放到上面和离开时,会以浏览器窗口为参照进行位置改变。(position:fixed)
closeFullScreen组件
BuildAdmin中定义了closeFullScreen.vue来实现取消全屏的组件。
<template>
<div title="layouts.Exitfullscreen" @mouseover.stop="onMouseover" @mouseout.stop="onMouseout">
<div @click.stop="onCloseFullScreen" class="close-full-screen" :style="{ top: state.closeBoxTop + 'px' }">
<Icon name="el-icon-Close"/>
</div>
<div class="close-full-screen-on"></div>
</div>
</template>
取消全屏组件的主要部分,就是<Icon>d定义的关闭图标,其他的div元素都是用来触发事件和改变元素位置。
在最外层的第一个div中,绑定了mouseover和mouseout鼠标进入进出的方法。
const onMouseover = () => {
state.closeBoxTop = 20
}
const onMouseout = () => {
state.closeBoxTop = -30
}
这两个方法,都对closeBoxTop变量进行的修改,当鼠标进入时,修改为20,当鼠标移开时,设置为-30。我们看看closeBoxTop是用来干什么的。
close-full-screen
第二个div(.close-full-screen)就相当于取消全屏按钮本体了。其中style属性的top绑定了closeBoxTop变量。众所周知,top被用来修改元素的位置。
平时我们知道top位置的改变是针对于父元素的,这里位置相当于的是浏览器,所以要设置position: fixed; ,使其变成相对于浏览器的固定定位。
<style scoped lang="scss">
.close-full-screen {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
right: calc(50% - 20px);
z-index: 9999999;
height: 40px;
width: 40px;
background-color: rgba($color: #000000, $alpha: 0.1);
border-radius: 50%;
box-shadow: var(--el-box-shadow-light);
transition: all 0.3s ease;
.icon {
color: rgba($color: #000000, $alpha: 0.6) !important;
}
&:hover {
background-color: rgba($color: #000000, $alpha: 0.3);
.icon {
color: rgba($color: #ffffff, $alpha: 0.6) !important;
}
}
}
-30px就相当于向上移动了30px,即隐藏了30px。
const state = reactive({
closeBoxTop: 20,
})
onMounted(() => {
setTimeout(() => {
state.closeBoxTop = -30
}, 300)
})
我们在组件挂载完成时,在生命周期函数中使用setTimeout将closeBoxTop设置为-30px自动将取消全屏按钮隐藏在浏览器中。其实在新建closeBoxTop时直接设置为-30px是一样的效果....
至于为什么是-30px,因为Icon的大小为40px,想要保留多少可以自己决定的,-29px和-31px都无所谓。
同时这个div绑定了一个点击事件onCloseFullScreen,即点击这个取消全屏按钮会发生什么,当然是取消全屏了,就是将tabFullScreen设置为false就行了。
import {useNavTabs} from '@/stores/navTabs'
const navTabs = useNavTabs()
const onCloseFullScreen = () => {
navTabs.setFullScreen(false)
}
这时候aside和header就会显示了,两个组件会重新新建渲染。
close-full-screen-on
第三个div(.close-full-screen-on),刚开始看代码的时候,我没明白这个div是干什么的,后来在自己实现这一块代码时,才恍然大悟这个div是用来增加mouseover和mouseout事件触发面积的。
因为第二个div上移30px,留在浏览器内的大小只有10px了。如果没有这个100 * 60的div,鼠标只要稍微移动,就会触发mouseout事件,取消全屏按钮就会隐藏。
.close-full-screen-on {
position: fixed;
top: 0;
z-index: 9999998;
height: 60px;
width: 100px;
left: calc(50% - 50px);
}
z-index设置得很大,元素优先级就很高,就可以在最上面。
引入组件
最后就是在layouts/index.vue中引入取消全屏按钮组件。
使用v-if,当tabFullScree为true全屏时,这个取消全屏按钮组件才会显示。
优化
当我取消全屏之后,会发现tab页的白色滑动块没了。后来我分析了一下原因,使用v-if来控制组件的隐藏,实际上会触发组件的销毁。所以,取消全屏会触发tabs新建并重新渲染,会调用生命周期函数onMounted。
虽然组件是新建的,但是数据还在,在此之前渲染过tabs的tabsView不是空的,所以无法触发原本onMounted中activeRoute的赋值,也就无法触发watch中的selectNavTab。
所以加了最后三行代码,在取消全屏重新渲染的时候,会触发selectNavTab来渲染滑动块。
结语
至此,弹出框的设计和功能实现已经全部完成了,在BuildAdmin管理系统页面设计架构,只剩下导航菜单栏这部分还没有写。后端接口的开发、前后端api交互模块的设计、菜单页面的开发都属于内容填充了。