组件库开发:yk-badge (徽标)组件

693 阅读4分钟

badge组件

在上一节我们已经开发了头像组件,有时候随着头像的诞生,会产出一些额外的组件,也就是badge(徽标)组件

效果大致是这样的

image.png

但现在徽标也不仅仅用于此了

它可以应用在别的组件之上,也可以脱离组件,单独使用,例如这样

image.png

总之,badge组件是很灵活的,具体如何使用需要根据实际场景去判断

好的,那我们开始吧~

badge组件属性

我们思考一下,我们大致需要什么功能

首先是正常的使用,也分为小红点的形式,和显示数字的形式,也就是下面这样

image.png

这两者我们通过is-dot(展示为小红点)count(徽标显示的数字)来进行控制

其次,我们的小红点默认是在外面的,我们可以控制它在外部还是内部,像下面这样

image.png

这样,我们通过in-dot来控制它是否显示在内部

然后,我们希望可以给badge一些状态,像下面这样

image.png

于是我们可以通过status 进行控制

但是有时候我们想自定义颜色怎么办,那么我们就添加一个color属性来进行控制

image.png

这里注意:color 的优先级会高于 status

在之后,我们可以通过offset设置badge的位置

image.png

然后我们需要进行一些细节的控制,overflowCount(徽标显示的最大数字) 、showZero(徽标是否展示 0,也就是从零开启)、border(圆角的角度)

image.png

image.png

image.png

以及最后,我们想隐藏badge,可以通过一个hidden来进行控制

动画.gif

于是,我们根据属性,定义出props

export type BadgeProps = {
  color?: string;
  count?: number;
  border?: number;
  isDot?: boolean;
  inDot?: boolean;
  offset?: 'left' | 'right' | [number, number];
  overflowCount?: number;
  showZero?: boolean;
  status?: Status;
  hidden?: boolean;
};

组件模板

<template>
  <div ref="badgeRef" class="yk-badge">
    <!-- 徽标 -->
    <div ref="supRef" class="yk-badge__sup">
      <transition name="modal">
        <div
          v-if="isShowDot"
          :class="['yk-badge__dot', dotClass]"
          :style="dotStyle"
        ></div>
      </transition>
      <transition name="modal">
        <div
          v-if="isShowCount"
          :class="['yk-badge__count', countClass]"
          :style="countStyle"
        >
          {{ showCount }}
        </div>
      </transition>
    </div>
    <slot></slot>
  </div>
</template>

我们使用transition包裹圆点徽标,通过v-if来判断,当isShowDot为true时才渲染

同理我们再次使用transition包裹数字徽标,当isShowCount为true时才渲染

transition在这里的作用是为徽标的出现和消失添加过渡效果,也就是上面隐藏的效果

逻辑部分

我们首先设置一些默认值

const props = withDefaults(defineProps<BadgeProps>(), {
  count: 0,
  border: 0,
  overflowCount: 99,
  showZero: false,
  status: 'danger',
  hidden: false,
})

首先我们先书写小红点部分的逻辑

const isDot = computed(() => props?.isDot ?? false)

const isShowDot = computed(() => {
  return isDot.value && !props.hidden
})

const dotStyle = computed<CSSProperties>(() => {
  const styles: CSSProperties = {}
  if (props.color) {
    styles.background = props.color
  }

  if (props.border !== 2) {
    styles.boxShadow = `#ffffff 0 0 0 ${props.border}px`
  }

  return styles
})

const dotClass = computed(() => {
  return {
    'yk-badge__dot--inner': props.inDot,
    [`yk-badge__dot--${props.status}`]: props.status !== undefined,
    'yk-badge__dot--stand': !!useSlots().default === false,
  }
})

isDot用于判断是否是点类型的徽标

isShowDot用于判断是否展示徽标

dotStyle则是用于设置点类型徽标的样式,并且根据props.color的值设置背景颜色。当props.border不等于2时,设置盒子阴影效果。

然后就到了数字徽标了

const isShowCount = computed(() => {
  const countZeroHidden = props.count === 0 && !props.showZero
  const countNormalHidden = props.count < 0
  if (props.hidden || isShowDot.value || countZeroHidden || countNormalHidden) {
    return false
  }
  return true
})

const showCount = computed(() => {
  if (props.count && props.count > props.overflowCount) {
    return Math.min(props.count!, props.overflowCount) + '+'
  } else {
    return props.count + ''
  }
})

这两段用于判断是否展示数字徽标以及确定展示的数字

const isRound = () => {
  if (isDot.value) {
    return true
  } else {
    return showCount.value && showCount.value.toString().length === 1
  }
}

这段定义了一个函数 isRound,用于判断展示的数字徽标是否是圆形

const countStyle = computed<CSSProperties>(() => {
  const styles: CSSProperties = {}

  // 默认右上角
  styles.translate = `50% -50%`

  // count的方位  定位右边,就左移动,左边,就右边移动
  if (props.offset && props.offset === 'right') {
    styles.translate = `-16px ${offsetValue.value}px`
  } else if (props.offset && props.offset === 'left') {
    styles.translate = `16px ${offsetValue.value}px`
  }

  // 自定义border时候
  if (props.border !== 2) {
    styles.boxShadow = `#ffffff 0 0 0 ${props.border}px`
  }

  // 自定义offset时候
  if (Array.isArray(props.offset)) {
    styles.translate = `${props.offset[0]}px ${props.offset[1]}px`
  }

  // color
  if (props.color) {
    styles.background = props.color
  }

  if (!!useSlots().default === false) {
    return { background: props.color }
  }

  return styles
})
  • 创建一个空对象 styles 来存储样式属性。
  • 默认情况下,数字徽标被定位在容器右上角
  • 如果 props.offset 存在且等于 'right',则将徽标向左移动
  • 如果 props.offset 存在且等于 'left',则将徽标向右移动
  • props.offset 是一个数组,通过使用数组中的值来设置数字徽标的偏移量
  • 如果 props.color 存在,则将背景颜色设置为 props.color。如果没有默认插槽内容存在(即 useSlots().default 返回 false),则返回一个只包含背景颜色的样式对象 { background: props.color }
let badgeHeight = ref(0)
const offsetValue = computed({
  get() {
    return badgeHeight.value
  },
  set(v) {
    badgeHeight.value = v
  },
})

这段用于存储数字徽标的高度

const badgeRef = ref()

onMounted(() => {
  const badgeDom: HTMLDivElement = badgeRef.value
  const supDomHeight = ref(0)
  if (props.count && props.count > props.overflowCount) {
    supDomHeight.value = 10
  } else {
    supDomHeight.value = 10
  }
  // 移动父级的50% - 自身高度的 50%
  offsetValue.value = badgeDom.offsetHeight / 2 - supDomHeight.value
})

获取了数字徽标的 DOM badgeDom,并定义了一个变量 supDomHeight 用于存储高度值。

const countClass = computed(() => {
  return {
    isRound: isRound(),
    [`yk-badge__count--${props.status}`]: props.status !== undefined,
    [`yk-badge__count--${props.offset}`]:
      props.offset !== undefined && typeof props.offset == 'string',
    'yk-badge__count--stand': !!useSlots().default === false,
  }
})
  • isRound: isRound() 将 isRound() 的返回值作为 isRound 类名的存在与否。
  • 如果 props.status 存在,则生成 yk-badge__count--${props.status} 类名,用于根据状态设置样式。
  • 如果 props.offset 存在且类型为字符串,则生成 yk-badge__count--${props.offset} 类名,用于根据偏移位置设置样式。
  • 如果没有默认插槽内容存在(即 useSlots().default 返回 false),则生成 yk-badge__count--stand 类名,用于设置没有内容时的样式。

样式

@import '../../styles/color/colors.less';
.modal {
  &-enter {
    &-from {
      scale: 0;
      opacity: 0;
    }

    &-active {
      transition: all 0.2s ease-in;
    }

    &-to {
      scale: 1;
      opacity: 1;
    }
  }

  &-leave {
    &-from {
      scale: 1;
      opacity: 1;
    }

    &-active {
      transition: all 0.2s ease-out;
    }

    &-to {
      scale: 0;
      opacity: 0;
    }
  }
}

.yk-badge {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  box-sizing: border-box;
  line-height: 1;

  .yk-badge__dot {
    position: absolute;
    top: -@space-ss;
    right: -@space-ss;
    z-index: 1;

    width: 8px;
    height: 8px;

    border-radius: 8px;

    background: @ecolor;
    box-shadow: 0 0 0 2px #ffffff;

    &--inner {
      top: @space-ss;
      right: @space-ss;
    }

    &--stand {
      position: static;
    }
  }

  .yk-badge__dot.yk-badge__dot--primary {
    background: @pcolor;
  }

  .yk-badge__dot.yk-badge__dot--success {
    background: @scolor;
  }

  .yk-badge__dot.yk-badge__dot--warning {
    background: @wcolor;
  }

  .yk-badge__dot.yk-badge__dot--danger {
    background: @ecolor;
  }

  .yk-badge__count {
    position: absolute;
    top: 0px;
    right: 0px;
    z-index: 1;
    // 由外到内 设置margin -> padding -> width -> height
    padding: 0 6px;

    font-size: @size-ss;
    font-family: PingFangSC-Regular;
    font-weight: 400;
    border-radius: 10px;
    text-align: center;

    color: @white;
    background: @ecolor;
    box-shadow: 0 0 0 2px #ffffff;
    line-height: 20px;

    &--left {
      top: 0;
      left: 0;
    }

    &--right {
      top: 0;
      right: 0;
    }

    &--stand {
      position: static;
    }
  }

  .yk-badge__count.yk-badge__count--primary {
    background: @pcolor;
  }

  .yk-badge__count.yk-badge__count--success {
    background: @scolor;
  }

  .yk-badge__count.yk-badge__count--warning {
    background: @wcolor;
  }

  .yk-badge__count.yk-badge__count--danger {
    background: @ecolor;
  }

  .yk-badge__count.isRound {
    padding: 0;
    width: 20px;
    height: 20px;

    border-radius: 10px;
  }
}

首先,我们把进入和离开动画的相关样式完善了

.yk-badge:表示徽标组件的根元素样式。

.yk-badge__dot:表示徽标组件的小圆点样式。

.yk-badge__count:表示徽标组件的数字计数样式

stand是独立使用时的样式,像下面这样

image.png

这样我们所有的功能就都完善啦