今天开始使用 vue3 + ts 搭建一个项目管理的后台,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本专栏,学习更多前端vue知识,然后开篇先简单介绍一下本项目用到的技术栈都有哪几个方面(阅读本文章能够学习到的技术):
vite:快速轻量且功能丰富的前端构建工具,帮助开发人员更高效构建现代Web应用程序。
pnpm:高性能、轻量级npm替代品,帮助开发人员更加高效地处理应用程序的依赖关系。
Vue3:Vue.js最新版本的用于构建用户界面的渐进式JavaScript框架。
TypeScript:JavaScript的超集,提供了静态类型检查,使得代码更加健壮。
Animate:基于JavaScript的动画框架,它使开发者可以轻松创建各种炫酷的动画效果。
vue-router:Vue.js官方提供的路由管理器与Vue.js紧密耦合,非常方便与Vue.js一同使用。
Pinia:Vue3构建的Vuex替代品,具有响应式能力,提供非常简单的 API,进行状态管理。
element-plus:基于Vue.js 3.0的UI组件库,用于构建高品质的响应式Web应用程序。
axios:基于Promise的HTTP客户端,可以在浏览器和node.js中使用。
three:基于JavaScript的WebGL库,开发者可以编写高性能、高质量的3D场景呈现效果。
echarts:基于JavaScript的可视化图表库,支持多种类型的图表,可根据需要自行安装。
当然还有许多其他的需要安装的第三方库,这里就不再一一介绍了,在项目中用到的地方自行会进行讲解,大家自行学习即可,现在就让我们走进vue3+ts的实战项目吧。
目录
面包屑功能的实现
刷新业务的实现
全屏功能模式的实现
获取用户信息功能的实现
退出登录业务的实现
路由鉴权相关操作
真实接口替换mock接口
面包屑功能的实现
我们在之前编写面包屑组件的时候,是把数据进行写死了,接下来我们想实现的就是如何根据路由的切换动态展现当前的路由标题。我们在面包屑处添加一个button按钮,打印一下当前的路由组件
<button @click="handler">获取到匹配的路由</button>
import { useRoute } from 'vue-router'
// 获取路由对象
const $route = useRoute()
const handler = () => {
console.log($route)
}
打印出路由的一些重要数据,我们选择我们想要的数据进行处理即可:
通过v-for循环遍历我们想要的数据进行处理:
<!-- 左侧面包屑 -->
<el-breadcrumb separator-icon="ArrowRight">
<!-- 面包屑动态展示路由名字与标题 -->
<el-breadcrumb-item
v-for="(item, index) in $route.matched"
:key="index"
>
<!-- 图标 -->
<el-icon style="margin: 0 5px">
<component :is="item.meta.icon"></component>
</el-icon>
<!-- 面包屑展示路由的标题 -->
<span>{{ item.meta.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
准确的来说首页也是二级路由,所以它也会显示两个标题,如下:
但是我们只想显示首页有个标题怎么办,简单只需将首页的父路由标题删掉,然后通过v-show判断即可。这里不能使用v-if,因为优先级比v-for更高:
那么面包屑的路由跳转怎么解决?这里处理的办法就是采用面包屑的to属性:
单极路由不进行跳转,多级路由跳转的话点击只会默认跳转到当前众多子路由的第一个子路由:
最终效果如下:
刷新业务的实现
刷新按钮的功能实现很简单,就是当我们点击刷新按钮的时候,将对应的组件进行销毁然后重新创建,再向服务器去拿数据然后进行展示数据,这样就实现了刷新的功能。因为刷新的功能是子组件向父组件进行请求刷新页面,这里的话我们可以将请求刷新的标识存放到仓库中去:
将数据存放到仓库中后,接下来找到刷新按钮的那个组件,进行刷新标识数据的修改,如下:
在能进行数据的修改之后,接下来可以在路由出口处进行组件动态的销毁与创建从而达到实现刷新页面的效果,这里可以先通过watch监听器监听数据发生变化,然后通过nextTick(用于在 DOM 更新完毕后执行回调函数):
<script setup lang="ts">
import { watch, ref, nextTick } from 'vue'
import useLayOutSettingStore from '@/store/settings'
const layoutSettingStore = useLayOutSettingStore()
// 控制当前组件是否需要进行销毁重建
let flag = ref(true)
// 监听仓库中的数据内容是否发生变化,如果发生变化说明用户点击过刷新按钮
watch(
() => layoutSettingStore.refsh,
() => {
// 点击刷新按钮,路由组件销毁
flag.value = false
nextTick(() => {
flag.value = true
})
},
)
</script>
刷新效果如下所示:
全屏功能模式的实现
全屏模式的实现功能很简单,在前端实现全屏模式有几种方法,下面列举其中较为常用的几种:
使用 Fullscreen API:
Fullscreen API 是 HTML5 新增的一种浏览器 API,可以让 Web 应用进入全屏模式。具体使用方式是在要全屏的元素上调用 requestFullscreen() 方法,例如 document.documentElement.requestFullscreen() 将整个文档进入全屏模式。
使用 Webkit/Blink CSS 属性:
Webkit 内核和 Blink 内核的浏览器提供了最简单的全屏实现方式,只需要给想要全屏的元素设置特定的 CSS 属性:-webkit-full-screen 或者 -moz-full-screen。例如:div:-webkit-full-screen { width:100%; height: 100%; }。
使用浏览器全屏插件或扩展程序:
一些浏览器提供了全屏插件或扩展程序,可以为网页提供全屏功能。例如,Chrome 中有一个叫做 Fullscreen Anything 的插件,可以将页面中的任何元素变成全屏模式。
需要注意的是:全屏模式应该谨慎使用,因为它会影响用户体验。如果用户没有明确地请求进入全屏模式,不应该强制将页面进入全屏模式。此外,在某些环境下(例如移动端),用户直接使用操作系统提供的全屏模式可能更为合适,而不是使用前端实现的方式。
我们采用第一种的方式,给tabbar组件中的切换全屏按钮设置点击事件,如下:
// 切换全面模式展现
const fullScreen = () => {
// DOM对象的有个属性,用来判断当前是不是全屏模式[全屏:true,不是全屏:null]
let full = document.fullscreenElement
// 切换全屏模式
if (!full) {
// 文档根节点的方法requestFullscreen,实现全屏模式
document.documentElement.requestFullscreen()
} else {
// 退出全屏模式
document.exitFullscreen()
}
}
功能实现结果如下:
全屏功能后面的设置按钮是对整个页面的主题颜色的设置,这里的话因为整体页面还没有搭建完成,所以这个功能点会放到后期讲解。
获取用户信息功能的实现
获取用户信息的功能我们在之前的API中已经给出了相关的调用函数,如下:
接下来我们在user仓库中进行引入该函数:
因为token在很多项目中都是公共参数,在登录项目中所有的请求,必须把token带上,意思就是说我们在登录成功之后,只要是发起请求就得把token带上,所以我们必须在请求拦截器处进行token的绑定。
处理完成之后,我们就可以在登录成功之后获取登录的相关信息并将部分数据进行展示:
我们在登录完成之后,在首页进行数据的挂载
最后结果如下:
退出登录业务的实现
我们在tabbar组件的退出登录按钮设置点击事件,进行退出登录时相关数据清楚的操作,如下:
清楚数据完成之后,接下来我们需要在login登录组件进行相关的判断,这里我们增加的需求就是,如果用户退出登录后是会保留其当前退出登录的路由路径的的,并以query的方式返回给登录页面,这样一旦跳转到登录页面后再进行登录,登录之后的页面就是我们之前退出离开的页面不再是默认的home页面了,如下:
路由鉴权相关操作
我们在之前的文章就讲解过路由鉴权的操作,当然也手写了一份进度条的案例样式,但是不是很正规,接下来我们将我们进行路由鉴权的操作抽离出来封装成一个ts文件,然后再入口文件 main.ts 处进行引入。
因为我们之前手写过进度条业务,所以接下来我们可以使用npm包来进行实现,其官方文档为:网址 ,我们执行如下命令下载第三方包:
pnpm i nprogress
其官方文档也给出了我们使用该插件的方法,比我们自己封装快多了:
我们将之前在main.ts中的路由导航守卫注释掉,然后在当前的文件中进行书写相关的代码文件:
// 路由鉴权文件
import router from '@/router'
// 引入进度条插件
import nprogress from 'nprogress'
// 引入进度条样式
import 'nprogress/nprogress.css'
// 全局前置守卫(项目中任意路由的切换都会触发的钩子)
router.beforeEach((to: any, from: any, next: any) => {
nprogress.start()
next()
})
// 全局后置守卫
router.afterEach((to: any, from: any) => {
nprogress.done()
})
接下来我们将我们原本在入口文件写的路由导航守卫的操作,移植到该文件处:
// 路由鉴权文件
import router from '@/router'
// 引入进度条插件
import nprogress from 'nprogress'
// 引入进度条样式
import 'nprogress/nprogress.css'
// 取消加载的小圆球
nprogress.configure({ showSpinner: false })
// 获取用户相关信息数据的仓库,去判断用户是否登录
import useUserStore from '@/store/user'
import pinia from '@/store'
const userStore = useUserStore(pinia)
// 全局前置守卫(项目中任意路由的切换都会触发的钩子)
router.beforeEach(async (to: any, from: any, next: any) => {
nprogress.start()
// 获取token,去判断用户是否登录
const token = userStore.token
// 获取用户名字
const username = userStore.username
if (token) {
// 用户登录判断
// 用户登录成功如果想去访问登录页面,我们是不能让你访问的直接跳转到首页
if (to.path == '/login') {
next({ path: '/' })
} else {
if (username) {
// 放行
next()
} else {
// 如果没有用户信息,在守卫这里发请求获取到用户信息再放行
try {
// 获取用户信息成功后放行
await userStore.useInfo()
next()
} catch (error) {
// 两种情况:token过期获取不到用户信息;用户手动修改了本地存储的token
userStore.userLogout()
next({ path: '/login', query: { redirect: to.path } })
}
}
}
} else {
// 用户未登录判断
if (to.path == '/login') {
next()
} else {
next({ path: '/login' })
}
}
})
// 全局后置守卫
router.afterEach((to: any, from: any) => {
// 设置标题
document.title = to.meta.title
nprogress.done()
})
真实接口替换mock接口
我们之前讲解的登录退出获取用户信息等接口,仅仅是模拟数据的接口,真实项目中还是需要借助真实的后端编写的接口给我们,因为后面会真正涉及到一些和后端数据库交互的功能,这里还是需要我们使用真实接口的,接下来我们将配置好真实接口数据来替换mock接口。
loadEnv 是一个在 Vue.js 项目中用于加载环境变量配置的函数,通常在项目启动时进行调用。它会根据当前环境读取不同的配置文件,并将配置信息挂载到 process.env 对象上,以便其他模块可以访问和使用。
使用 loadEnv 函数可以让我们方便地切换环境,并且可以避免在代码中暴露敏感信息。同时,也可以通过不同的环境配置文件来管理不同环境下的变量值,提高项目的可维护性和扩展性。
注意:我们在使用真实接口的时候可能与我们模拟的接口数据有相关出入的地方,这里的话需要我们进行相关的排查然后进行相关的修改,(具体操作就不再赘述)如下:
设置完成之后,我们在api下处理用户信息的user接口文件中进行相关修改:
// 统一管理我们项目用户的相关接口
import requset from '@/utils/request'
// 约束ts类型
import type {
loginFormData,
loginResponseData,
userInfoReponseData,
} from './type'
// 统一管理接口
enum API {
LOGIN_URL = '/admin/acl/index/login',
USERINFO_URL = '/admin/acl/index/info',
LOGOUT_URL = '/admin/acl/index/logout',
}
// 暴露请求函数
// 登录接口的方法
export const reqLogin = (data: loginFormData) =>
requset.post<any, loginResponseData>(API.LOGIN_URL, data)
// 获取用户信息接口方法
export const reqUserInfo = () =>
requset.get<any, userInfoReponseData>(API.USERINFO_URL)
// 退出登录
export const reqLogout = () => requset.post<any, any>(API.LOGOUT_URL)
设置约束ts文件:
// 定义用户相关数据的ts类型
// 用户登录接口携带参数的ts类型
export interface loginFormData {
username: string
password: string
}
// 定义全部接口返回数据都拥有的ts类型
export interface ResponseData {
code: number
message: string
ok: boolean
}
// 定义登录接口返回数据类型
export interface loginResponseData extends ResponseData {
data: string
}
//定义获取用户信息返回数据类型
export interface userInfoReponseData extends ResponseData {
data: {
routes: string[]
buttons: string[]
roles: string[]
name: string
avatar: string
}
}
然后在在仓库中进行更新完善即可:
本项目的tabbar页面功能的搭建就讲解到这,下一篇文章将讲解main主体内容,关注博主学习更多前端vue知识,您的支持就是博主创作的最大动力!