1.1静态页面搭建
javascript
<template>
<div class="tabbar">
<div class="tabbar_left">
<!-- 面包屑 -->
<Breadcrumb />
</div>
<div class="tabbar_right">
<!-- 设置 -->
<Setting />
</div>
</div>
</template>
<script setup lang="ts">
import Breadcrumb from './breadcrumb/index.vue'
import Setting from './setting/index.vue'
</script>
<script lang="ts">
export default {
name: 'Tabbar',
}
</script>
<style scoped lang="scss">
.tabbar {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
// background-image: linear-gradient(to right, rgb(232, 223, 223), rgb(201, 178, 178), rgb(197, 165, 165));
.tabbar_left {
display: flex;
align-items: center;
margin-left: 20px;
}
.tabbar_right {
display: flex;
align-items: center;
}
}
</style>
面包屑
javascript
<template>
<!-- 顶部左侧静态 -->
<el-icon style="margin-right: 10px" @click="changeIcon">
<!-- <component :is="LayOutSettingStore.fold ? 'Fold' : 'Expand'" /> -->
</el-icon>
<!-- 左侧面包屑 -->
<el-breadcrumb separator-icon="ArrowRight">
<!-- 面包动态展示路由名字与标题 -->
<el-breadcrumb-item v-for="(item, index) in $route.matched" v-show="item.meta.title" :key="index" :to="item.path">
<!-- 图标 -->
<el-icon>
<component :is="item.meta.icon" />
</el-icon>
<!-- 面包屑展示匹配路由的标题 -->
<span>{{ item.meta.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
// import useLayOutSettingStore from '@/store/moudles/setting'
// //获取layout配置相关的仓库
// const LayOutSettingStore = useLayOutSettingStore()
//获取路由对象
const $route = useRoute()
console.log($route.matched, '111')
//点击图标的方法
const changeIcon = () => {
//图标进行切换
LayOutSettingStore.fold = !LayOutSettingStore.fold
}
</script>
<script lang="ts">
export default {
name: 'Breadcrumb'
}
</script>
<style scoped></style>
设置
javascript
<template>
<el-button size="small" icon="Refresh" circle @click="updateRefsh" />
<el-button size="small" icon="FullScreen" circle @click="fullScreen" />
<el-popover placement="bottom" title="主题设置" :width="300" trigger="hover">
<!-- 表单元素 -->
<el-form>
<el-form-item label="主题颜色">
<!-- <el-color-picker v-model="color" size="small" show-alpha :predefine="predefineColors" @change="setColor" /> -->
</el-form-item>
<el-form-item label="暗黑模式">
<el-switch
v-model="dark"
class="mt-2"
style="margin-left: 24px"
inline-prompt
active-icon="MoonNight"
inactive-icon="Sunny"
@change="changeDark"
/>
</el-form-item>
</el-form>
<template #reference>
<el-button size="small" icon="Setting" circle />
</template>
</el-popover>
<img :src="userStore.avatar" style="width: 24px; height: 24px; margin: 0px 10px; border-radius: 50%" />
<!-- 下拉菜单 -->
<el-dropdown>
<span class="el-dropdown-link">
admin
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// import { useRouter, useRoute } from 'vue-router'
//获取用户相关的小仓库
import useUserStore from '@/store/modules/user'
//获取骨架的小仓库
// import useLayOutSettingStore from '@/store/modules/setting'
// const layoutSettingStore = useLayOutSettingStore()
const userStore = useUserStore()
//获取路由器对象
// let $router = useRouter()
//获取路由对向
// let $route = useRoute()
//收集开关的数据
const dark = ref<boolean>(false)
//刷新按钮点击回调
const updateRefsh = () => {
layoutSettingStore.refsh = !layoutSettingStore.refsh
}
//全屏按钮点击的回调
const fullScreen = () => {
//DOM对象的一个属性:可以用来判断当前是不是全屏模式[全屏:true,不是全屏:false]
const full = document.fullscreenElement
//切换为全屏模式
if (!full) {
//文档根节点的方法requestFullscreen,实现全屏模式
document.documentElement.requestFullscreen()
} else {
//变为不是全屏模式->退出全屏模式
document.exitFullscreen()
}
}
//退出登录点击回调
const logout = async () => {
//第一件事情:需要向服务器发请求[退出登录接口]******
//第二件事情:仓库当中关于用于相关的数据清空[token|username|avatar]
//第三件事情:跳转到登录页面
//跳转到登录页面
}
//颜色组件组件的数据
const color = ref('rgba(255, 69, 0, 0.68)')
const predefineColors = ref([
'#ff4500',
'#ff8c00',
'#ffd700',
'#90ee90',
'#00ced1',
'#1e90ff',
'#c71585',
'rgba(255, 69, 0, 0.68)',
'rgb(255, 120, 0)',
'hsv(51, 100, 98)',
'hsva(120, 40, 94, 0.5)',
'hsl(181, 100%, 37%)',
'hsla(209, 100%, 56%, 0.73)',
'#c7158577'
])
//switch开关的chang事件进行暗黑模式的切换
const changeDark = () => {
//获取HTML根节点
const html = document.documentElement
//判断HTML标签是否有类名dark
dark.value ? (html.className = 'dark') : (html.className = '')
}
//主题颜色的设置
const setColor = () => {
//通知js修改根节点的样式对象的属性与属性值
const html = document.documentElement
html.style.setProperty('--el-color-primary', color.value)
}
</script>
<script lang="ts">
export default {
name: 'Setting'
}
</script>
<style scoped></style>
1.2菜单折叠效果实现
javascript
//小仓库:layout组件相关配置仓库
import { defineStore } from 'pinia'
const useLayOutSettingStore = defineStore('SettingStore', {
state: () => {
return {
fold: false, //用户控制菜单折叠还是收起控制
refsh: false, //仓库这个属性用于控制刷新效果
}
},
})
export default useLayOutSettingStore
动态判断菜单是否折叠,然后情况修改样式
javascript
<template>
<div class="layout_container">
<!-- 左侧导航 -->
<div
class="layout_slider"
:class="{ fold: LayOutSettingStore.fold ? true : false }"
>
<Logo />
<!-- 展示菜单 -->
<!-- 滚动组件 -->
<el-scrollbar class="scrollbar">
<!-- 菜单组件-->
<el-menu
:collapse="LayOutSettingStore.fold ? true : false"
:default-active="$route.path"
background-color="#001529"
text-color="white"
active-text-color="yellowgreen"
>
<!-- 根据路由动态生成菜单 -->
<Menu :menuList="userStore.menuRoutes"></Menu>
</el-menu>
</el-scrollbar>
</div>
<!-- 顶部导航 -->
<div
class="layout_tabbar"
:class="{ fold: LayOutSettingStore.fold ? true : false }"
>
<Tabbar></Tabbar>
</div>
<!-- 内容展示区 -->
<div
class="layout_main"
:class="{ fold: LayOutSettingStore.fold ? true : false }"
>
<router-view></router-view>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './components/menu/index.vue'
import Logo from './components/logo/index.vue'
import Tabbar from './components/tabbar/index.vue'
//获取用户相关的小仓库
import useUserStore from '@/store/moudules/user'
let userStore = useUserStore()
// 獲取路有對象
import { useRoute } from 'vue-router'
import useLayOutSettingStore from '@/store/moudules/setting'
//获取layout配置仓库
let LayOutSettingStore = useLayOutSettingStore()
//获取路由器
let $route = useRoute()
console.log($route.path)
</script>
<script lang="ts">
export default {
name: 'Layout',
}
</script>
<style scoped lang="scss">
.layout_container {
width: 100%;
height: 100vh;
.layout_slider {
color: white;
width: $base-menu-width;
height: 100vh;
background: $base-menu-background;
transition: all 0.3s;
.scrollbar {
width: 100%;
height: calc(100vh - $base-menu-logo-height);
.el-menu {
border-right: none;
}
}
}
.layout_tabbar {
position: fixed;
width: calc(100% - $base-menu-width);
height: $base-tabbar-height;
top: 0px;
left: $base-menu-width;
transition: all 0.3s;
&.fold {
width: calc(100vw - $base-menu-min-width);
left: $base-menu-min-width;
background-color: #fff;
}
}
.layout_main {
transition: all 0.3s;
position: absolute;
width: calc(100% - $base-menu-width);
height: calc(100vh - $base-tabbar-height);
left: $base-menu-width;
top: $base-tabbar-height;
padding: 20px;
overflow: auto;
background-color: yellowgreen;
&.fold {
width: calc(100vw - $base-menu-min-width);
left: $base-menu-min-width;
}
}
}
</style>
1.3面包屑的实现
javascript
<template>
<!-- 顶部左侧静态 -->
<el-icon style="margin-right: 10px" @click="changeIcon">
<component
:is="LayOutSettingStore.fold ? 'Fold' : 'Expand'"
></component>
</el-icon>
<!-- 左侧面包屑 -->
<el-breadcrumb separator-icon="ArrowRight">
<!-- 面包动态展示路由名字与标题 -->
<el-breadcrumb-item
v-for="(item, index) in $route.matched"
:key="index"
v-show="item.meta.title"
:to="item.path"
>
<!-- 图标 -->
<el-icon>
<component :is="item.meta.icon"></component>
</el-icon>
<!-- 面包屑展示匹配路由的标题 -->
<span>{{ item.meta.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import useLayOutSettingStore from '@/store/moudules/setting'
//获取layout配置相关的仓库
let LayOutSettingStore = useLayOutSettingStore()
//获取路由对象
let $route = useRoute()
console.log($route)
console.log($route.matched, '111')
let $router = useRouter()
console.log($router, '111')
//点击图标的方法
const changeIcon = () => {
//图标进行切换
LayOutSettingStore.fold = !LayOutSettingStore.fold
}
</script>
<script lang="ts">
export default {
name: 'Breadcrumb',
}
</script>
<style scoped></style>
1.4刷新业务的实现
设置一个全局变量
javascript
//获取骨架的小仓库
import useLayOutSettingStore from '@/store/modules/setting'
const layoutSettingStore = useLayOutSettingStore()
//刷新按钮点击回调
const updateRefsh = () => {
layoutSettingStore.refsh = !layoutSettingStore.refsh
}
javascript
<template>
<!-- 路由组件出口的位置 -->
<router-view v-slot="{ Component }">
<transition name="fade">
<!-- 渲染layout一级路由组件的子路由 -->
<component :is="Component" v-if="flag" />
</transition>
</router-view>
</template>
<script setup lang="ts">
import { watch, ref, nextTick } from 'vue'
import useLayOutSettingStore from '@/store/moudules/setting'
let layOutSettingStore = useLayOutSettingStore()
//控制当前组件是否销毁重建
let flag = ref(true)
//监听仓库内部数据是否发生变化,如果发生变化,说明用户点击过刷新按钮
watch(
() => layOutSettingStore.refsh,
() => {
//点击刷新按钮:路由组件销毁
flag.value = false
nextTick(() => {
flag.value = true
})
},
)
</script>
<script lang="ts">
export default {
// eslint-disable-next-line vue/no-reserved-component-names
name: 'Main',
}
</script>
<style scoped>
.fade-enter-from {
opacity: 0;
transform: scale(0);
}
.fade-enter-active {
transition: all 0.3s;
}
.fade-enter-to {
opacity: 1;
transform: scale(1);
}
</style>
1.5全屏功能的实现
javascript
//全屏按钮点击的回调
const fullScreen = () => {
//DOM对象的一个属性:可以用来判断当前是不是全屏模式[全屏:true,不是全屏:false]
const full = document.fullscreenElement
//切换为全屏模式
if (!full) {
//文档根节点的方法requestFullscreen,实现全屏模式
document.documentElement.requestFullscreen()
} else {
//变为不是全屏模式->退出全屏模式
document.exitFullscreen()
}
}
1.6获取用户信息
javascript
//获取用户信息方法
async userInfo() {
//获取用户信息进行存储仓库当中[用户头像、名字]
const result: userInfoReponseData = await reqUserInfo()
console.log(result)
//如果获取用户信息成功,存储一下用户信息
if (result.code == 200) {
this.username = result.data.checkUser.username
this.avatar = result.data.checkUser.avatar
}
},
javascript
import axios from 'axios'
import { ElMessage } from 'element-plus'
//引入用户相关的仓库
import useUserStore from '@/store/modules/user'
//创建axios实例
const request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_URL,
timeout: 5000
})
//请求拦截器
request.interceptors.request.use((config) => {
//获取用户相关的小仓库:获取仓库内部token,登录成功以后携带给服务器
const userStore = useUserStore()
if (userStore.token) {
config.headers.token = userStore.token
}
//config配置对象,headers属性请求头,经常给服务器端携带公共参数
//返回配置对象
return config
})
//响应拦截器
request.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
//处理网络错误
let msg = ''
const status = error.response.status
switch (status) {
case 401:
msg = 'token过期'
break
case 403:
msg = '无权访问'
break
case 404:
msg = '请求地址错误'
break
case 500:
msg = '服务器出现问题'
break
default:
msg = '无网络'
}
ElMessage({
type: 'error',
message: msg
})
return Promise.reject(error)
}
)
export default request
1.7退出登录业务
javascript
// 存储
export const SET_TOKEN = (token: string) => {
localStorage.setItem('TOKEN', token)
}
// 读取
export const GET_TOKEN = () => {
return localStorage.getItem('TOKEN')
}
// 移除token
export const REMOVE_TOKEN = () => {
localStorage.removeItem('TOKEN')
}
javascript
userLogout() {
this.token = ''
this.username = ''
this.avatar = ''
REMOVE_TOKEN()
},
javascript
//退出登录点击回调
const logout = async () => {
//第一件事情:需要向服务器发请求[退出登录接口]******
//第二件事情:仓库当中关于用于相关的数据清空[token|username|avatar]
//第三件事情:跳转到登录页面
//跳转到登录页面
userStore.userLogout()
$router.push({ path: '/login', query: { redirect: $route.path } })
}
再设置一个退出登录再登录返回原来的路由