8000 彻底搞懂面包屑,手把手封装一个 Vue3 面包屑导航组件 · Issue #7 · bryqiu/Blog · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

彻底搞懂面包屑,手把手封装一个 Vue3 面包屑导航组件 #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
bryqiu opened this issue Dec 26, 2024 · 0 comments
Open
Labels
通俗易懂的中后台系统建设指南 该系列旨在告诉你如何来构建一个优秀的中后台管理系统

Comments

@bryqiu
Copy link
Owner
bryqiu commented Dec 26, 2024

前言

本篇文章主要讲解如何来封装一个面包屑组件,充当基本的导航角色

本文也是《通俗易懂的中后台系统建设指南》系列的第七篇文章,该系列旨在告诉你如何来构建一个优秀的中后台管理系统

在本文中,你可以了解到面包屑概念、应用场景等知识点,学到面包屑的的封装思路及实践应用

文章最后也会给到本文中示例的全部源码

什么是面包屑

面包屑导航(Breadcrumb Navigation)这个概念来自童话故事“汉赛尔和格莱特”,当汉赛尔和格莱特穿过森林时,不小心迷路了,但是他们发现沿途走过的地方都撒下了面包屑,让这些面包屑来帮助他们找到回家的路

面包屑的作用如何定义?

面包屑导航被当作一种有效的视觉救援,指引用户在网站层级中所处的位置,你需要了解以下三种信息

  • 我在哪儿? 面包屑导航提醒浏览者他当前处于整个网站层级中具体位置
  • 我能去到哪里? 面包屑导航能够提高用户对网站的章节和页面的搜寻能力,比起只是放置一个菜单,放置一排面包屑导航更容易让人理解网站的结构
  • 我将去哪里? 面包屑导航能将内容进行关联,井且促进浏览(比如电商网站的用户可能进到一个商品详情页之后,发现这个商品并不是自己想要的,这个时候用户可能有意愿浏览同类的商品),同时,又反过来降低了整个网站的跳出率

面包屑的应用场景

面包屑组件在 B 端产品是比较常见的元素,它扮演着重要的导航角色,主要应用场景包括:

  1. 层级导航清晰展示
    • 快速展示用户当前所处页面的路径
    • 帮助用户理解系统的层级结构
  2. 快速回溯
    • 提供快捷的上级页面跳转入口
    • 减少用户多次点击返回的操作成本
  3. 提升用户体验
    • 增强系统的可用性和导航性
    • 降低用户在复杂系统中的迷失感

Ant Design面包屑设计板块中,有这么一段话:Breadcrumb 的本质是了解当前所处页面的位置,并能向上导航

面包屑的封装思路及目标

在本文中,我们会按照以下思路进行逐步分析实现:

  1. 满足导航数据的基本层级展现
  2. 满足 ElBreadcrumb 的原有配置属性
  3. 增强面包屑组件配置项,比如支持图标的隐现、可配置路由跳转方式
  4. ElBreadcrumb 的样式美化
  5. 提供流畅的路由切换动画、保证用户体验的连贯性

本文默认使用 Element Plus 的 ElBreadcrumb 作为二次封装的基础组件,你可以先了解一下 ElBreadcrumb 组件及 Api

本文开发环境是: Vue3 + TS + Tailwindcss + scss

面包屑的基本导航功能

首先,先要来了解一个属性,Vue Router 中的 router.matched,它是一个数组,表示当前路由对象中与当前路径匹配的所有路由记录(RouteRecord),简单一点来说,router.matched 用于存储匹配的路由记录

<script setup lang="ts">
import { useRoute } from 'vue-router';

const currentRoute = useRoute();
console.log(currentRoute);
</script>

image

利用 matched,可以动态生成页面的面包屑,展示从父级到子级的层级关系

我们新建一个 breadcrumb.vue 文件,表示这个文件用于二次封装 ElBreadcrumb,然后写入以下内容:

<script setup lang="ts">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus';
import { useRoute } from 'vue-router';
import type { RouteLocationMatched } from 'vue-router';

import { computed } from 'vue';

defineOptions({
  name: 'Breadcrumb',
});

const currentRoute = useRoute();

/** 获取路由路径 */
const getPath = (item: RouteLocationMatched): string | Object => {
  if (!item) return '';
  if (item.meta?.isReadonlyBreadcrumb) return '';
  return { path: item.redirect ? item.redirect : item.path };
};

/** 面包屑列表 */
const breadcrumbList = computed(() =>
  currentRoute.matched.filter((item) => !item.meta.isHideBreadcrumb),
);
</script>

<template>
  <ElBreadcrumb>
    <ElBreadcrumbItem v-for="item in breadcrumbList" :key="item.path" :to="getPath(item)">
        <span>{{ item.meta.title }}</span>
    </ElBreadcrumbItem>
  </ElBreadcrumb>
</template>

上面内容中实现了一个基本的面包屑:

  • breadcrumbList:面包屑列表,主要是通过 router.matched Api 实现
  • getPath:获取路由路径的函数

满足原有配置属性

满足原有配置属性比较简单,利用 $attrs 即可实现,具有的参数配置,参阅 Breadcrumb 面包屑

  <ElBreadcrumb v-bind="$attrs">
  //...
  </ElBreadcrumb>

面包屑个性化(图标隐现、路由跳转方式)

面包屑除了基本的导航文本展示外,可以有更多丰富的内容,比如每个路由的元信息中可能会存储一个 icon 图标文本,再比如点击路由时的跳转方式,下面我们会来丰富这些配置:

创建一个 typing.ts 文件,表示类型文件,定义一个类型 BreadcrumbProps

export interface BreadcrumbProps {
  /**
   * 如果设置该属性为 true, 导航将不会留下历史记录
   * @default false
   * @see https://element-plus.org/zh-CN/component/breadcrumb.html#breadcrumbitem-attributes
   */
  replace?: boolean;

  /**
   * 是否显示面包屑图标
   * @default true
   */
  isShowIcon?: boolean;
}

基本实现:

<script setup lang="ts">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus';
import { useRoute } from 'vue-router';
import type { RouteLocationMatched } from 'vue-router';

import type { BreadcrumbProps } from './typing';
import { computed, h } from 'vue';
import { AppIcon } from '@/components/common/app-icon';

defineOptions({
  name: 'Breadcrumb',
});
const currentRoute = useRoute();

const props = withDefaults(defineProps<BreadcrumbProps>(), {
  replace: false,
  isHideIcon: false,
});

//...

/** 渲染图标 */
const renderIcon = (item: RouteLocationMatched) => {
  if (!props.isShowIcon || !item.meta.icon) return null;
  return h(AppIcon, { icon: item.meta.icon });
};

</script>

<template>
  <ElBreadcrumb v-bind="$attrs" :class="breadcrumbClassName">
      <ElBreadcrumbItem
        v-for="item in breadcrumbList"
        :key="item.path"
        :to="getPath(item)"
        :replace
      >
        <div class="space-x-1">
          <Component :is="renderIcon(item)" />
          <span>{{ item.meta.title }}</span>
        </div>
      </ElBreadcrumbItem>
  </ElBreadcrumb>
</template>

注意,请确保你的路由 meta 信息中拥有一个 icon 属性,否则图标相关的内容将不起效

上面代码中,实现了图标的显隐配置、ElBreadcrumbItemreplace 属性配置等

需要注意的是文中的 AppIcon 组件是内部实现的组件,你可以在这里找到它。当然,你也可以替换成 ElIcon

面包屑的样式美化及切换动画

样式美化

Element Plus 中面包屑组件的样式变化不多,算是文本形式,顶多配置一下图标分隔符,我们下面来对基本的面包屑进行样式美化

Pasted image 20241225222201

还记得我们上面定义了一个 BreadcrumbProps 嘛,在这基础上,我们新添一个 styleType,它接受一个联合类型:

  • default:默认面包屑文本样式
  • arrow:箭头面包屑样式
  • parallelogram:平行四边形面包屑样式
type BreadcrumbStyleType = 'default' | 'arrow' | 'parallelogram';

export type BreadcrumbStyleObj = {
  [key in BreadcrumbStyleType]: string;
};

export interface BreadcrumbProps {
  //...
  
  /**
   * 面包屑样式
   * @default default
   */
  type?: BreadcrumbStyleType;
}
<script setup lang="ts">
import type { BreadcrumbEmits, BreadcrumbProps, BreadcrumbStyleObj } from './typing';

//...

const props = withDefaults(defineProps<BreadcrumbProps>(), {
  replace: false,
  isHideIcon: false,
  styleType: 'default',// 默认为文本样式
});

/** 获取面包屑Class样式 */
const breadcrumbClassName = computed(() => {
  const className: BreadcrumbStyleObj = {
    arrow: 'breadcrumb-arrow',
    default: 'breadcrumb-default',
    parallelogram: 'breadcrumb-parallelogram',
  };
  return className[props.styleType];
});
</script>

<template>
  <ElBreadcrumb v-bind="$attrs" :class="breadcrumbClassName">
      <ElBreadcrumbItem
        v-for="item in breadcrumbList"
        :key="item.path"
        :to="getPath(item)"
        :replace
      >
        <div class="space-x-1">
          <Component :is="renderIcon(item)" />
          <span>{{ item.meta.title }}</span>
        </div>
      </ElBreadcrumbItem>
  </ElBreadcrumb>
</template>

在上面这一步,我们主要做的事,是给 ElBreadcrumb 加上不同的类名来应用样式

既然类名加上了,我们需要给到各类名对应的样式,复制如下样式即可

注意,这里默认你使用了 Scss

<style scoped lang="scss">
$height: 24px;

@mixin breadcrumb__inner($padding: 0 4px 0 16px, $bgColor: var(--el-fill-color-light)) {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  height: $height;
  padding: $padding;
  text-decoration: none;
  background-color: $bgColor;
}

.breadcrumb {
  //箭头样式
  &-arrow {
    :deep(.el-breadcrumb__item) {
      position: relative;
      margin-right: 12px;

      .el-breadcrumb__inner {
        @include breadcrumb__inner();

        &::before,
        &::after {
          position: absolute;
          top: 0;
          z-index: -1;
          content: '';
          border: calc($height/2) solid transparent;
        }

        &::before {
          left: -1px;
          border-left-color: var(--el-bg-color);
        }

        &::after {
          right: -23px;
          border-left-color: var(--el-fill-color-light);
        }

        &:hover {
          background: var(--el-fill-color);

          &::after {
            border-left-color: var(--el-fill-color);
          }
        }
      }
    }

    :deep(.el-breadcrumb__separator) {
      display: none;
    }
  }

  //平行四边形样式
  &-parallelogram {
    :deep(.el-breadcrumb__item) {
      position: relative;
      margin-right: 8px;

      .el-breadcrumb__inner {
        @include breadcrumb__inner(4px 10px, transparent);

        &::before {
          position: absolute;
          top: 0;
          left: 0;
          z-index: -1;
          width: 100%;
          height: 100%;
          content: '';
          background-color: var(--el-fill-color-light);
          transform: skew(-20deg);
        }
      }
    }

    :deep(.el-breadcrumb__separator) {
      display: none;
    }
  }
}
</style>

然后,根据属性 styleType 传入不同的值,即可得到默认文本、箭头、平行四边形的面包屑样式

默认面包屑:

Pasted image 20241225222710

箭头面包屑:

Image

平行四边形面包屑:

Pasted image 20241225222616

切换动画

面包屑根据你的路由、地址会进行不断变化,我们给它一个平滑的动画效果来更符合视觉感受

这里需要用到 Vue 的内置组件 <TransitionGroup> ,你可以在 Vue 官网的 TransitionGroup 章节找到更多信息

写入以下动画

/* 面包屑切换动画 */
.breadcrumb-basic-enter-active {
  transition: all 0.25s;
}

.breadcrumb-basic-enter-from,
.breadcrumb-basic-leave-active {
  opacity: 0;
  transform: translateX(20px) skewX(-20deg);
}

然后在封装的组件中使用 <TransitionGroup> 包裹

<template>
  <ElBreadcrumb v-bind="$attrs" :class="breadcrumbClassName">
    <TransitionGroup name="breadcrumb-basic">
      //...
    </TransitionGroup>
  </ElBreadcrumb>
</template>

这个动画的最终效果是这样的:

Kapture 2024-12-25 at 22 47 42

参考资料

源码

本文中的所有实例源码,你可以在这里找到

了解更多

系列专栏地址:GitHub 博客 | 掘金专栏 | 思否专栏

实战项目:vue-clean-admin

专栏往期回顾:

  1. 收下这份 Vue + TS + Vite 中后台系统搭建指南,从此不再害怕建项目
  2. 中后台开发必修课:Vue 项目中 Pinia 与 Router 完全攻略
  3. 用了这些 Vite 配置技巧,同事都以为我开挂了
  4. 受够了团队代码风格不统一?7千字教你从零搭建代码规范体系
  5. 开发者必看!在团队中我是这样实现 Git 提交规范化的
  6. 告别繁琐!Vue3 组合式函数解锁 Echarts 封装新姿势

交流讨论

文章如有错误或需要改进之处,欢迎指正

@bryqiu bryqiu added the 通俗易懂的中后台系统建设指南 该系列旨在告诉你如何来构建一个优秀的中后台管理系统 label Dec 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
通俗易懂的中后台系统建设指南 该系列旨在告诉你如何来构建一个优秀的中后台管理系统
Projects
None yet
Development

No branches or pull requests

1 participant
0