1.1静态页面搭建
<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>
面包屑
<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>
设置
<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-switchv-model="dark"class="mt-2"style="margin-left: 24px"inline-promptactive-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标签是否有类名darkdark.value ? (html.className = 'dark') : (html.className = '')
}//主题颜色的设置
const setColor = () => {//通知js修改根节点的样式对象的属性与属性值const html = document.documentElementhtml.style.setProperty('--el-color-primary', color.value)
}
</script><script lang="ts">
export default {name: 'Setting'
}
</script>
<style scoped></style>
1.2菜单折叠效果实现
//小仓库:layout组件相关配置仓库
import { defineStore } from 'pinia'const useLayOutSettingStore = defineStore('SettingStore', {state: () => {return {fold: false, //用户控制菜单折叠还是收起控制refsh: false, //仓库这个属性用于控制刷新效果}},
})export default useLayOutSettingStore
动态判断菜单是否折叠,然后情况修改样式
<template><div class="layout_container"><!-- 左侧导航 --><divclass="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><!-- 顶部导航 --><divclass="layout_tabbar":class="{ fold: LayOutSettingStore.fold ? true : false }"><Tabbar></Tabbar></div><!-- 内容展示区 --><divclass="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面包屑的实现
<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-itemv-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刷新业务的实现
设置一个全局变量
//获取骨架的小仓库
import useLayOutSettingStore from '@/store/modules/setting'
const layoutSettingStore = useLayOutSettingStore()
//刷新按钮点击回调
const updateRefsh = () => {layoutSettingStore.refsh = !layoutSettingStore.refsh
}
<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 = falsenextTick(() => {flag.value = true})},
)
</script>
<script lang="ts">
export default {// eslint-disable-next-line vue/no-reserved-component-namesname: '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全屏功能的实现
//全屏按钮点击的回调
const fullScreen = () => {//DOM对象的一个属性:可以用来判断当前是不是全屏模式[全屏:true,不是全屏:false]const full = document.fullscreenElement//切换为全屏模式if (!full) {//文档根节点的方法requestFullscreen,实现全屏模式document.documentElement.requestFullscreen()} else {//变为不是全屏模式->退出全屏模式document.exitFullscreen()}
}
1.6获取用户信息
//获取用户信息方法async userInfo() {//获取用户信息进行存储仓库当中[用户头像、名字]const result: userInfoReponseData = await reqUserInfo()console.log(result)//如果获取用户信息成功,存储一下用户信息if (result.code == 200) {this.username = result.data.checkUser.usernamethis.avatar = result.data.checkUser.avatar}},
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.statusswitch (status) {case 401:msg = 'token过期'breakcase 403:msg = '无权访问'breakcase 404:msg = '请求地址错误'breakcase 500:msg = '服务器出现问题'breakdefault:msg = '无网络'}ElMessage({type: 'error',message: msg})return Promise.reject(error)}
)
export default request
1.7退出登录业务
// 存储
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')
}
userLogout() {this.token = ''this.username = ''this.avatar = ''REMOVE_TOKEN()},
//退出登录点击回调
const logout = async () => {//第一件事情:需要向服务器发请求[退出登录接口]******//第二件事情:仓库当中关于用于相关的数据清空[token|username|avatar]//第三件事情:跳转到登录页面//跳转到登录页面userStore.userLogout()$router.push({ path: '/login', query: { redirect: $route.path } })
}
再设置一个退出登录再登录返回原来的路由