Vue3徽标(Badge)

简介: 该组件库包含 `Descriptions` 和 `DescriptionsItem` 两种组件,需配合使用。

效果如下图:在线预览

Badge

参数 说明 类型 默认值
color 自定义小圆点的颜色,优先级高于 status PresetColor | string undefined
value 展示的数字或文字,为数字时大于 max 显示为 max+,为 0 时隐藏 number | string | slot undefined
max 展示封顶的数字值 number 99
showZero 当数值为 0 时,是否展示 Badge boolean false
dot 不展示数字,只有一个小红点 boolean false
offset 设置状态点的位置偏移,距默认位置左侧、上方的偏移量 [x, y]: [水平偏移, 垂直偏移] [number | string, number | string] undefined
status 设置 Badge 为状态点 Status undefined
text 在设置了 status 的前提下有效,设置状态点的文本 string | slot undefined
valueStyle 设置徽标的样式 CSSProperties {}
zIndex 设置徽标的 z-index number 9
title 设置鼠标放在状态点上时显示的文字 string undefined
ripple 是否开启涟漪动画效果 boolean true

PresetColor Enum Type

成员名
pink ‘pink’
red ‘red’
yellow ‘yellow’
orange ‘orange’
cyan ‘cyan’
green ‘green’
blue ‘blue’
purple ‘purple’
geekblue ‘geekblue’
magenta ‘magenta’
volcano ‘volcano’
gold ‘gold’
lime 'lime

Status Enum Type

成员名
success ‘success’
processing ‘processing’
default ‘default’
error ‘error’
warning ‘warning’

创建徽标数组件Badge.vue

其中引入使用了以下工具函数:

<script setup lang="ts">
import {
    computed } from 'vue'
import type {
    CSSProperties } from 'vue'
import {
    useSlotsExist } from '../utils'
enum PresetColor {
   
  pink = 'pink',
  red = 'red',
  yellow = 'yellow',
  orange = 'orange',
  cyan = 'cyan',
  green = 'green',
  blue = 'blue',
  purple = 'purple',
  geekblue = 'geekblue',
  magenta = 'magenta',
  volcano = 'volcano',
  gold = 'gold',
  lime = 'lime'
}
enum Status {
   
  success = 'success',
  processing = 'processing',
  default = 'default',
  error = 'error',
  warning = 'warning'
}
interface Props {
   
  color?: PresetColor | string // 自定义小圆点的颜色,优先级高于 status
  value?: number | string // 展示的数字或文字,为数字时大于 max 显示为 max+,为 0 时隐藏 number | string | slot
  max?: number // 展示封顶的数字值
  showZero?: boolean // 当数值为 0 时,是否展示 Badge
  dot?: boolean // 不展示数字,只有一个小红点
  offset?: [number | string, number | string] // 设置状态点的位置偏移,距默认位置左侧、上方的偏移量 [x, y]: [水平偏移, 垂直偏移]
  status?: Status // 设置 Badge 为状态点
  text?: string // 在设置了 status 或 color 的前提下有效,设置状态点的文本 string | slot
  valueStyle?: CSSProperties // 设置徽标的样式
  zIndex?: number // 设置徽标的 z-index
  title?: string // 设置鼠标放在状态点上时显示的文字
  ripple?: boolean // 是否开启涟漪动画效果
}
const props = withDefaults(defineProps<Props>(), {
   
  color: undefined,
  value: undefined,
  max: 99,
  showZero: false,
  dot: false,
  offset: undefined,
  status: undefined,
  text: undefined,
  valueStyle: () => ({
   }),
  zIndex: 9,
  title: undefined,
  ripple: true
})
const customStyle = computed(() => {
   
  if (props.color && !Object.keys(PresetColor).includes(props.color)) {
   
    if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0)) {
   
      return {
   
        backgroundColor: props.color
      }
    } else {
   
      return {
   
        color: props.color,
        backgroundColor: props.color
      }
    }
  }
})
const presetClass = computed(() => {
   
  if (props.color) {
   
    if (Object.keys(PresetColor).includes(props.color)) {
   
      if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0)) {
   
        return `color-${
     props.color} white`
      } else {
   
        return 'color-' + props.color
      }
    }
  }
  if (props.status) {
   
    if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0)) {
   
      return `status-${
     props.status} white`
    } else {
   
      return 'status-' + props.status
    }
  }
  return
})
const slotsExist = useSlotsExist(['default', 'value'])
const showContent = computed(() => {
   
  if (props.value !== undefined || props.dot || (!props.color && !props.status)) {
   
    return slotsExist.default
  }
  return false
})
const showValue = computed(() => {
   
  if (!props.color && !props.status) {
   
    return slotsExist.value
  }
  return false
})
const showBadge = computed(() => {
   
  if ((props.value !== undefined && props.value !== 0) || (props.showZero && props.value === 0) || props.dot) {
   
    return true
  }
  return false
})
const dotOffestStyle = computed(() => {
   
  if (props.offset?.length) {
   
    return {
   
      right: isNumber(props.offset[0]) ? -props.offset[0] + 'px' : handleOffset(props.offset[0] as string),
      marginTop: isNumber(props.offset[1]) ? props.offset[1] + 'px' : props.offset[1]
    }
  }
  return {
   }
})
function isNumber(value: number | string): boolean {
   
  return typeof value === 'number'
}
function handleOffset(value: string): string {
   
  if (value.includes('-')) {
   
    return value.replace('-', '')
  } else {
   
    return `-${
     value}`
  }
}
</script>
<template>
  <div
    class="m-badge"
    :class="{ 'badge-status-color': value === undefined && (color || status) }"
    :style="[`--z-index: ${zIndex}`, value === undefined && !dot ? dotOffestStyle : null]"
  >
    <template v-if="value === undefined && !dot && (color || status)">
      <span class="status-dot" :class="[presetClass, { 'dot-ripple': ripple }]" :style="customStyle"></span>
      <span class="status-text">
        <slot>{
   {
    text }}</slot>
      </span>
    </template>
    <template v-else>
      <template v-if="showContent">
        <slot></slot>
      </template>
      <span v-if="showValue" class="m-value" :class="{ 'only-number': !showContent }">
        <slot name="value"></slot>
      </span>
      <Transition name="zoom" v-else>
        <div
          v-show="showBadge"
          class="m-badge-value"
          :class="[
            {
   
              'small-num': typeof value === 'number' && value < 10,
              'only-number': !showContent,
              'only-dot': showBadge && (value === undefined || (value === 0 && !showZero) || dot)
            },
            presetClass
          ]"
          :style="[customStyle, dotOffestStyle, valueStyle]"
          :title="title || (value !== undefined ? String(value) : '')"
        >
          <span v-if="!dot" class="m-number" style="transition: none 0s ease 0s">
            <span class="u-number">{
   {
    typeof value === 'number' && value > max ? max + '+' : value }}</span>
          </span>
        </div>
      </Transition>
    </template>
  </div>
</template>
<style lang="less" scoped>
.zoom-enter-active {
   
  animation: zoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46);
  animation-fill-mode: both;
}
.zoom-leave-active {
   
  animation: zoomBadgeOut 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46);
  animation-fill-mode: both;
}
@keyframes zoomBadgeIn {
   
  0% {
   
    transform: scale(0) translate(50%, -50%);
    opacity: 0;
  }
  100% {
   
    transform: scale(1) translate(50%, -50%);
  }
}
@keyframes zoomBadgeOut {
   
  0% {
   
    transform: scale(1) translate(50%, -50%);
  }
  100% {
   
    transform: scale(0) translate(50%, -50%);
    opacity: 0;
  }
}
.m-badge {
   
  position: relative;
  display: inline-block;
  width: fit-content;
  font-size: 14px;
  color: rgba(0, 0, 0, 0.88);
  line-height: 1;
  .status-dot {
   
    position: relative;
    top: -1px;
    display: inline-block;
    vertical-align: middle;
    width: 6px;
    height: 6px;
    border-radius: 50%;
  }
  .dot-ripple {
   
    &::after {
   
      box-sizing: border-box;
      position: absolute;
      top: 0;
      inset-inline-start: 0;
      width: 100%;
      height: 100%;
      border-width: 1px;
      border-style: solid;
      border-color: inherit;
      border-radius: 50%;
      animation-name: dotRipple;
      animation-duration: 1.2s;
      animation-iteration-count: infinite;
      animation-timing-function: ease-in-out;
      content: '';
    }
    @keyframes dotRipple {
   
      0% {
   
        transform: scale(0.8);
        opacity: 0.5;
      }
      100% {
   
        transform: scale(2.4);
        opacity: 0;
      }
    }
  }
  .status-text {
   
    margin-inline-start: 8px;
    color: rgba(0, 0, 0, 0.88);
    font-size: 14px;
  }
  .m-value {
   
    position: absolute;
    top: 0;
    z-index: var(--z-index);
    inset-inline-end: 0;
    transform: translate(50%, -50%);
    transform-origin: 100% 0%;
  }
  .m-badge-value {
   
    .m-value();
    overflow: hidden;
    padding: 0 8px;
    min-width: 20px;
    height: 20px;
    color: #ffffff;
    font-weight: normal;
    font-size: 12px;
    line-height: 20px;
    white-space: nowrap;
    text-align: center;
    background: #ff4d4f;
    border-radius: 10px;
    box-shadow: 0 0 0 1px #ffffff;
    transition: background 0.2s;
    .m-number {
   
      position: relative;
      display: inline-block;
      height: 20px;
      transition: all 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46);
      transform-style: preserve-3d;
      -webkit-transform-style: preserve-3d; // 设置元素的子元素是位于 3D 空间中还是平面中 flat | preserve-3d
      backface-visibility: hidden;
      -webkit-backface-visibility: hidden; // 当元素背面朝向观察者时是否可见 hidden | visible
      .u-number {
   
        display: inline-block;
        height: 20px;
        margin: 0;
        transform-style: preserve-3d;
        -webkit-transform-style: preserve-3d;
        backface-visibility: hidden;
        -webkit-backface-visibility: hidden;
      }
    }
  }
  .small-num {
   
    padding: 0;
  }
  .only-number {
   
    position: relative;
    top: auto;
    display: block;
    transform-origin: 50% 50%;
    transform: none;
  }
  .only-dot {
   
    width: 6px;
    min-width: 6px;
    height: 6px;
    background: #ff4d4f;
    border-radius: 100%;
    box-shadow: 0 0 0 1px #ffffff;
    padding: 0;
    transition: background 0.3s;
  }
  .status-success {
   
    color: #52c41a;
    background-color: #52c41a;
  }
  .status-error {
   
    color: #ff4d4f;
    background-color: #ff4d4f;
  }
  .status-default {
   
    color: rgba(0, 0, 0, 0.25);
    background-color: rgba(0, 0, 0, 0.25);
  }
  .status-processing {
   
    color: @themeColor;
    background-color: @themeColor;
  }
  .status-warning {
   
    color: #faad14;
    background-color: #faad14;
  }
  .color-pink {
   
    color: #eb2f96;
    background-color: #eb2f96;
  }
  .color-red {
   
    color: #f5222d;
    background-color: #f5222d;
  }
  .color-yellow {
   
    color: #fadb14;
    background-color: #fadb14;
  }
  .color-orange {
   
    color: #fa8c16;
    background-color: #fa8c16;
  }
  .color-cyan {
   
    color: #13c2c2;
    background-color: #13c2c2;
  }
  .color-green {
   
    color: #52c41a;
    background-color: #52c41a;
  }
  .color-blue {
   
    color: @themeColor;
    background-color: @themeColor;
  }
  .color-purple {
   
    color: #722ed1;
    background-color: #722ed1;
  }
  .color-geekblue {
   
    color: #2f54eb;
    background-color: #2f54eb;
  }
  .color-magenta {
   
    color: #eb2f96;
    background-color: #eb2f96;
  }
  .color-volcano {
   
    color: #fa541c;
    background-color: #fa541c;
  }
  .color-gold {
   
    color: #faad14;
    background-color: #faad14;
  }
  .color-lime {
   
    color: #a0d911;
    background-color: #a0d911;
  }
  .white {
   
    color: #ffffff;
  }
}
.badge-status-color {
   
  line-height: inherit;
  vertical-align: baseline;
}
</style>
AI 代码解读

在要使用的页面引入

<script setup lang="ts">
import Badge from './Badge.vue'
import {
    ref } from 'vue'
import {
    ClockCircleOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons-vue'
const value = ref(5)
const dot = ref(true)
const colors = [
  'pink',
  'red',
  'yellow',
  'orange',
  'cyan',
  'green',
  'blue',
  'purple',
  'geekblue',
  'magenta',
  'volcano',
  'gold',
  'lime'
]
function decline() {
   
  if (value.value >= 1) {
   
    value.value--
  }
}
function increase() {
   
  value.value++
}
</script>
<template>
  <div>
    <h1>{
   {
    $route.name }} {
   {
    $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Space>
      <Badge :value="5">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge :value="0" show-zero>
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge>
        <template #value>
          <ClockCircleOutlined style="color: #f5222d" />
        </template>
        <Avatar shape="square" size="large" />
      </Badge>
    </Space>
    <h2 class="mt30 mb10">独立使用</h2>
    <Space>
      <Badge :value="25" />
      <Badge
        :value="4"
        :value-style="{
   
          backgroundColor: '#fff',
          color: '#999',
          boxShadow: '0 0 0 1px #d9d9d9 inset'
        }"
      />
      <Badge :value="109" :value-style="{ backgroundColor: '#52c41a' }" />
    </Space>
    <h2 class="mt30 mb10">封顶数字</h2>
    <Space gap="large">
      <Badge :value="99">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge :value="100">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge :value="99" :max="10">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge :value="1000" :max="999">
        <Avatar shape="square" size="large" />
      </Badge>
    </Space>
    <h2 class="mt30 mb10">自定义内容</h2>
    <Space gap="large">
      <Badge value="hello" :value-style="{ backgroundColor: '#1677FF' }">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge>
        <template #value>
          <span class="u-value">world</span>
        </template>
        <Avatar shape="square" size="large" />
      </Badge>
    </Space>
    <h2 class="mt30 mb10">自定义徽标样式</h2>
    <Space gap="large">
      <Badge :value="99" :value-style="{ backgroundColor: 'magenta' }">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge value="hello" :value-style="{ backgroundColor: 'gold' }">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge dot :value-style="{ width: '10px', height: '10px', backgroundColor: 'purple' }">
        <Avatar shape="square" size="large" />
      </Badge>
    </Space>
    <h2 class="mt30 mb10">徽标偏移</h2>
    <Space gap="large">
      <Badge value="9" :offset="[-20, 10]">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge dot :offset="[-15, 10]">
        <Avatar shape="square" size="large" />
      </Badge>
      <Badge dot status="success" :offset="['-50%', '30%']">
        <Avatar shape="square" size="large" />
      </Badge>
    </Space>
    <h2 class="mt30 mb10">小红点</h2>
    <Badge dot>
      <a href="#">Link something</a>
    </Badge>
    <h2 class="mt30 mb10">状态点</h2>
    <Space>
      <Badge status="success" />
      <Badge status="error" />
      <Badge status="default" />
      <Badge status="processing" />
      <Badge status="warning" />
    </Space>
    <br />
    <Space style="margin-top: 10px" vertical>
      <Badge status="success" text="Success" />
      <Badge status="error" text="Error" />
      <Badge status="default" text="Default" />
      <Badge status="processing" text="Processing" />
      <Badge status="warning" text="warning" />
    </Space>
    <h2 class="mt30 mb10">动态</h2>
    <Flex vertical>
      <Space gap="large" align="center">
        <Badge :value="value">
          <Avatar shape="square" size="large" />
        </Badge>
        <Button @click="decline">
          <MinusOutlined />
        </Button>
        <Button @click="increase">
          <PlusOutlined />
        </Button>
      </Space>
      <Space gap="large" align="center">
        <Badge :dot="dot">
          <Avatar shape="square" size="large" />
        </Badge>
        <Switch v-model="dot" />
      </Space>
    </Flex>
    <h2 class="mt30 mb10">自定义悬浮状态点的显示文字</h2>
    <Badge :value="5" title="Custom hover text">
      <Avatar shape="square" size="large" />
    </Badge>
    <h2 class="mt30 mb10">多彩徽标</h2>
    <h4 class="mb10">Presets</h4>
    <Space>
      <Badge v-for="color in colors" :key="color" :color="color" :text="color" />
    </Space>
    <h4 class="mt10 mb10">Custom</h4>
    <Space>
      <Badge color="#f50" text="#f50" />
      <Badge color="#2db7f5" text="#2db7f5" />
      <Badge color="#87d068" text="#87d068" />
      <Badge color="#108ee9" text="#108ee9" />
    </Space>
  </div>
</template>
<style lang="less" scoped>
.u-value {
   
  display: inline-block;
  line-height: 20px;
  padding: 0 6px;
  background-color: #faad14;
  color: #fff;
  border-radius: 10px;
  box-shadow: 0 0 0 1px #ffffff;
}
</style>
AI 代码解读
相关文章
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
420 0
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
380 5
Vue 3 中的 nextTick 使用详解与实战案例
Vue 3 中的 nextTick 使用详解与实战案例 在 Vue 3 的日常开发中,我们经常需要在数据变化后等待 DOM 更新完成再执行某些操作。此时,nextTick 就成了一个不可或缺的工具。本文将介绍 nextTick 的基本用法,并通过三个实战案例,展示它在表单验证、弹窗动画、自动聚焦等场景中的实际应用。
169 17
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
183 6
vue2和vue3的响应式原理有何不同?
大家好,我是V哥。本文详细对比了Vue 2与Vue 3的响应式原理:Vue 2基于`Object.defineProperty()`,适合小型项目但存在性能瓶颈;Vue 3采用`Proxy`,大幅优化初始化、更新性能及内存占用,更高效稳定。此外,我建议前端开发者关注鸿蒙趋势,2025年将是国产化替代关键期,推荐《鸿蒙 HarmonyOS 开发之路》卷1助你入行。老项目用Vue 2?不妨升级到Vue 3,提升用户体验!关注V哥爱编程,全栈开发轻松上手。
231 2
高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图
mermaid是一款非常优秀的基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。非常适合新手学习或者做一些弱交互且自定义要求不高的图表 除了流程图以外,mermaid还支持序列图、类图、状态图、实体关系图等图表可供探索。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
管理数据必备;侦听器watch用法详解,vue2与vue3中watch的变化与差异
一篇文章同时搞定Vue2和Vue3的侦听器,是不是很棒?不要忘了Vue3中多了一个可选项watchEffect噢。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
onMounted作为vue3中最常用的钩子函数之一,能够灵活、随心应手的使用是每个Vue开发者的必修课,同时根据其不同写法的特性,来选择最合适最有利于维护的写法。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Pinia 如何在 Vue 3 项目中进行安装和配置?
Pinia 如何在 Vue 3 项目中进行安装和配置?
302 4

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问