「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页

lxf2023-05-15 01:34:07

一起养成写作习惯!这是我参与「编程 · 4 月更文挑战」的第1天,点击查看活动详情。

常见的PC端首页,包括合理的布局、清晰明了的导航、快捷的面包屑指引;这些再熟悉不过的功能,实际在开发中需要注意很多细节。今天这篇文章,想从vue3与ts的实现细节上,来谈一谈如何实现系统首页。

一、实现中的细节

常见的页面结构,从整体角度来看,分为上下与左右两种形式。在上下结构的布局中,导航可以与用户入口信息放置在顶部,也可以放置在下方内容区的侧边。而左右结构的布局中,往往将导航放置在页面最左边,内容区内再细分具体结构。成熟的UI框架,都会将不同布局考虑进去,Element-plus中对具体布局使用到的组件进行了定义。

在本次实践中,使用到的页面布局如图所示:

「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页

1.1 SFC单文件组件

SFC单文件组件,是以.vue结尾的文件,其由<script><template><style>三部分构成,结合setup语法糖,可以为我们开发带来许多便利;比如在setup函数中,需要return显式返回定义的变量或方法,而利用单文件+setup结合形式则不需要显式返回,同时,在子组件中我们还能够利用ts声明props及抛出事件,其对ts天然的支持能力也格外优秀。当然,单文件+setup结合的方式带来的优势远不止这些,大家可参阅vue官网上的介绍

小tip: 在vscode中利用SFC结合volar则可以使我们的开发更加流畅。

1.2 组合式API与选项式API

选项式API采用类似对象字面量的方式构建属性,将属性都暴露在this上,通常使用this去访问属性或方法,选项式API在低复杂度场景中使用较多。而组合式API,提供了更多便利的应用场景,其基于函数式的写法能够对ts天然支持,同时如果项目足够大和复杂,而且考虑到生产构建的话,vue3推荐大家使用组合式API。

组合式API通常结合setup一起使用,在使用过程中,对ts能够提供全方位的支撑。但这里要注意,组合式API已经没有this的概念了,如果需要全局去挂载,需要使用app.config.globalProperties属性。

1.3 ref与reactive

在实践过程中,我使用了refreactive两种方式定义变量,然后总结出以下细节。 总的来说,就是如果你想定义基础变量使用ref更合适,如果是引用类型,则使用reactive,如果想使用ts泛型,则更加推荐ref

「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页

1.4 ts泛型在ref中的使用

ts泛型,提供了类似<T>结构的形式,我们可以把泛型理解成一种占位符,泛型的应用,在很大程度上使得函数的复用性更高,因为其允许在调用函数时传入具体类型,而不用事先定义好。 在本次实践中,用到泛型的地方,主要是对菜单数据进行定义。

  • 菜单数据结构 - 一个数组对象
"menus": [
  {
    "id": 1000,
    "code": "xtsy",
    "name": "系统首页",
    "subMenus": [],
    "route": "/homepage",
    "parentId": 0
  },
  {
    "id": 1001,
    "code": "grbg",
    "name": "个人办公",
    "subMenus": [
      {
        "id": 10010,
        "code": "gzt",
        "name": "工作台",
        "subMenus": [],
        "route": "/personal-work/workbench",
        "parentId": 1001,
        "parentName": "个人办公"
      }
    ],
    "route": "/personal-work",
    "parentId": 0
  },
  ]
  • ts定义接口

注意,这里subMenus中,多了parentName属性,因此我们使用了extends接口方式。由于该接口定义会在多个组件间使用,因此我将接口抽取出来放在了单独的interface.ts文件中。

// interface/IMenu.ts文件
export interface IMenu {
	id: number
	code: string
	name: string
	// eslint-disable-next-line no-use-before-define
	subMenus: submenuObjs[]
	route: string
	parentId: number
}
// submenu扩展
interface submenuObjs extends IMenu {
	parentName: string
}
  • vue文件中使用

在vue文件中,利用ref定义接收菜单数据的变量,这里不选择reactive是因为在深层次 ref 解包后,返回值将与泛型参数的类型不同。下面代码中的refMenus可使用refMenus.value的形式获取接口中取得的数据结果 。

import { IMenu } from "../interface/IMenu"
const refMenus = ref<IMenu[]>([])

1.5 el-icon引入

在导航菜单中,通常会使用到icon图标,el-icon可以通过局部引入与全局注册的方式接入到项目中。

  1. yarn add @element-plus/icons-vue安装el-icon包。
  2. 局部引入,直接在.vue文件中,加载对应组件,组件命名可以在官网上查看。「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页
  3. 全局引入,在main.ts文件中注册。注册完成后,可以直接在vue文件中使用。「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页

1.6 动态加载组件

组建的动态加载,主要应用在根据不同导航名称获取icon组件上,这里定义过滤方法,对不同导航名称进行单独处理,处理之后,函数将会返回一个引入的具体icon组件,vue中使用<component>特殊元素结合is来加载动态组件,除了自定义的动态组件可以通过<component>加载之外,其还能够加载html元素、内置的组件。

1.7 vue3中是否还有过滤器

vue2中通过filters对数据过滤处理,在vue3中,则将filters删除了,vue3中更建议使用computed与方法处理,但是在实践过程中,我发现如果给computed传参处理的话,和方法没有啥区别。比如,前面提到的根据菜单名称加载不同的icon图标,使用computed时,需要将menu.name传给computed,这种与定义方法去处理没有任何差别。因此,computed更加强调对某个变化数据的依赖处理,而方法则强调对数据的灵活操作。

<script>
    const iconProps = (name: string | number) => {
            const iconRel = {
                    系统首页: HomeFilled,
                    个人办公: UserFilled,
                    系统菜单一: Management,
                    系统菜单二: Grid,
                    系统菜单三: Checked,
                    系统管理: Tools,
            }
            return iconRel[name]
    }
</script>
<template>
    <el-icon>
        <component :is="iconProps(menu.name)"></component>
    </el-icon>
</template>

1.8 组件间传值

组件的传值,在vue官网上已经有详细介绍,这里主要讲下在项目过程中遇到的两个问题,第一个是使用ts泛型对defineProps方法标注类型,第二个是嵌套结构深的属性获取,ts会报错。 针对第一个问题,可以采用与传入props定义类型一致的方式去定义。第二个问题,要注意在访问属性中,不能嵌套多层,比如对象.props1.props2.props3,多级结构的访问ts会不识别该属性。

const props = defineProps<{
	menus: IMenu[]  // 与传入props类型保持一致
	rnumber: number
	rArray: number[]
	sMenus: any[]
	isCollapse: boolean
}>()

二、菜单与路由、可伸缩

这里我想从两个方面介绍菜单,第一是系统方面,菜单与路由如何匹配设计?第二是页面方面,菜单组件间有什么样的关系。

  • 2.1 菜单与路由

如下图所示,在菜单与路由的匹配方面,我们需要根据菜单的层级去设置路由的匹配关系,由于系统含有二级菜单,很显然需要使用到嵌套路由;这里需要注意的是,加载一级菜单的<router-view>与二级菜单的<router-view>是不同的,一级菜单path匹配的是顶层<router-view>,而二级菜单想要在正确的视图中展示,需要在各自对应的一级菜单中设置<router-view>。除此之外,在path属性的设置上,需要注意二级菜单,不需要使用/分隔符,直接定义名称即可。

「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页

在页面组件方面,Elment-plus提供了menu、menu-item、sub-menu、menu-item-group四个组件,这四个组件之间的关系,大家可以去官网查看。我在这里需要指出的细节是,一级菜单menu提供router属性之后,二级菜单或者子菜单sub-menu和菜单内容menu-item需要将index属性设置为匹配的路由path信息,这样才能建立菜单与路由的关系,同时,>展开符只有在menu、sub-menu上才有,除此之外,在sub-menu上需要使用#title插槽显示菜单标题,否则页面上不会显示。

// 一级菜单对应vue文件中的router-view
<el-main class="content-wrapper-main">
    <router-view></router-view>
</el-main>
// 子菜单对应vue文件中的router-view,嵌套路由中必须按照层级关系设置router-view
<template>
    <div id="personalWork">
	<router-view></router-view>
    </div>
</template>
// router设置
{
    name: "main",   
    path: "/",
    component: Main,
    redirect: "homepage",
    children: [
        {
            name: "personalWork",
            path: "personal-work",
            component: PersonalWork,
            meta: { title: "个人办公" },
            children: [
		{
                    name: "workbench",
                    path: "workbench",
                    component: WorkBench,
                    meta: { title: "工作台"},
		}
	],
    }] 
},
  • 2.2 伸缩菜单

element-ui中提供了伸缩菜单组件,顾名思义,伸缩菜单能够控制的就是内容区宽度的尺寸。在开发应用中,通常需要一个触发菜单伸缩按钮,同时,需要将该触发元素放置在menu组件中,这样与menu是一体的,不会出现伸缩后尺寸有问题。在实现过程中,使用slot插槽,定义伸缩按钮,通过控制按钮伸缩状态,来处理菜单伸缩。

// 菜单组件中
<el-menu
    class="el-menu-vertical-demo"
    :router="true"
    :collapse="isCollapse" // 是否支持伸缩
>
    <slot name="collasepanel"></slot>
</el-menu>
// 父级组件中,控制伸缩状态
const switchCollapse = () => {
    isCollapse.value = !isCollapse.value
    return isCollapse.value
}

<template #collasepanel>
    <div class="collapse-panel">
         <el-icon :size="18" @click="switchCollapse">
            <expand v-if="isCollapse" />
            <fold v-else />
        </el-icon>
    </div>
</template>

三、面包屑与路由

面包屑一般在二级菜单以上的时候使用,必须一目了然的看见菜单的层级关系,同时能够快速返回到对应路由。我们知道,面包屑需要完成路由的匹配,同时还需要在页面中显示对应层级关系。那,如何根据当前浏览器的路由信息去获取面包屑组件所需数据呢?需要从以下几步开始

  • 获取当前路由信息

vue-router中,可以通过useRouteruseRoute获取路由相关信息,前者用于全局的导航守卫编程,后者提供当前路由的对象信息,通过useRoute能够根据matched属性,匹配对应路由信息,值得注意的是,当前路由的路径信息可以匹配到它的所有嵌套路由信息,如/personal-work/workbench可以匹配出/personal-work/路径对应的路有对象,此时,我们可以在router配置meta元信息中配置面包屑相关标识,以过滤面包屑需要的数据。

  • watch监听路由变化

由于操作菜单,当前路由会不停改变,面包屑展示内容依赖于路径信息的变化,因此需要使用watch去实时获取面包屑数据。watch监听属性如何使用,大家可参阅vue3官网,这里不做赘述。需要强调的是,面包屑默认值可以在onMounted属性中设置。

// 获取面包屑数据
const getBreadCrumbList = () => {
	// 匹配title属性的route
	const matchedRoute = currentRoute.matched.filter(
		(item) => item.meta && item.meta.title
	)
	datas.breadcrumbList = matchedRoute
}
// 监听路由变化
watch( () => currentRoute.path,
        (path) => {
            if (path === "/") { return }
            getBreadCrumbList()
	}
)

四、 效果

最终这样一个基础版本的首页菜单就完成了。

「Vue3实践」- 组合式 API+TS+Element-Plus实现基础版系统首页

  • 本文主要以常见系统首页为主,讲了下实现细节。目前大多数公司都是依赖某个数字中台,或者由某个技术leader创建好了项目框架,其余前端开发则只专注于业务代码实现,没有完整的从0-1的实践经验。其实当我们去剖析这些常见又基础的页面或功能的时候,才发现也是细节满满。 vue3实践系列,后续会根据项目实际开发持续更新。 同时对vue3框架的搭建,在上篇文章中也做了详细介绍,可参阅后实践。
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!