本文基于vue3+ts+tailwindcss,从逻辑拆解到代码细节带你实现一个简易版的右上角提示徽标组件
一、需求分析与TS类型定义
在实现组件前我们先明确Badge的能力范围,并通过TS将其规范化,这个步骤是我们实现所有组件都必须要有的,根据这一步骤再像拼积木一样一点一点"搭起来"。
首先在badge组件文件夹下创建一个types.ts,导出组件的各个api来控制每一个细节
ts
export interface BadgeProps{
value?:number|string //显示的值(数字或文本)
max?:number //最大显示值,超过显示max+
type?:'primary'|'success'|'warning'|'danger'|'info' //其实就是默认可选集中常见颜色
color?:string //自定义文本颜色
backgroundColor?:string //自定义背景色
isDot?:boolean //是否为小圆点(简洁版不显示文字)
hidden?:boolean //是否隐藏(比如已读情况)
showZero?:boolean //这个主要用于处理需要显示0的情况,少见但是还是要有
}
特别说明:这里的types是一个典型的联合类型(ts),保证types只能是这五种,既限制了错误,又给予编译器完美的提示
ts
type BadgeType='primary'|'success'|'warning'|'danger'|'info'
二、组件实现总体思路
要写好一个Badge,需要处理以下核心部分:
1.内容逻辑
- isDot和显示数字要做出那些判断?
- 需要实现哪些部分的计算?
- 需要考虑哪些类型特殊判断(检查如非0、null等)?
2.显示条件
- hidden是否隐藏
- 内容为空时是否显示
3.样式逻辑
- type类型切换颜色
- 自定义颜色与默认的优先级
- 根据内容长度决定尺寸
- 默认位置为右上角
4.动画体验
- 显示/隐藏使用缩放过渡
拆解完问题后,下面按模块实现
三、内容逻辑:displayValue
内容逻辑决定当前Badge应该显示什么内容。
ts
const displayValue=computed(()=>{
if(props.isDot) return '' //点状不需要考虑数字逻辑
const val=props.value //获取值
if(val===''||val===null||val===undefined) return ''//特殊值返回空
if(typeof val==='number'){
if(val<0) return '0' //负值(特殊值)返回0
if(val===0&&!props.showZero) return '' //是否显示0
if(val>props.max) return `${props.max}+` //超出正常值显示max+
return val.toString() //正常值正常返回
}
return val.toString()
})
这一块逻辑清晰地处理了 Badge 最常见的展示需求。
四、显示控制:shouldShow
Badge有一些特殊场景需要隐藏,例如消息已读、需要动画时先不显示等。
ts
const shouldShow=computed(()=>{
if(props.hidden) return false
if(props.isDot) return true
return displayValue.value!==''
})
这一层的逻辑避免了空内容闪烁问题。
五、样式处理:类型+自定义颜色+动态大小
1.类型class(typeClasses)
ts
const typeClasses=computed(()=>{
//自定义优先级高于默认
if(props.color||props.backgroundColor){
return ''
}
//默认就根据types来获取对应颜色
const types={
primary:'bg-blue-500 text-white',
success: 'bg-green-500 text-white',
warning: 'bg-yellow-500 text-white',
danger: 'bg-red-500 text-white',
info: 'bg-gray-500 text-white',
}
return types[props.type]
})
2.自定义样式
ts
const customStyle=computed(()=>{
const style:Record<string,string>={}
if(props.color) style.color=props.color
if(props.backgroundColor) style.backgroundColor=props.backgroundColor
return style
})
3.尺寸逻辑(sizeClasses)
Badge的宽应随内容长度自动变化,比如9+、99+长度或者是点。这个逻辑保证badge不会因为数字长度不好看。
ts
const sizeClasses=computed(()=>{
//设置属于点的小盒子宽度
if(props.isDot){
return 'w-2 h-2 min-w-0'
}
//设置字长不同的盒子自适应
const content=displayValue.value
if(content.length===1){
return 'w-5 h-5 min-w-[20px]'
}else if(content.length===2){
return 'w-6 h-5 min-w-[24px]'
}else{
return 'h-5 min-w-[24px] px-1'
}
})
六、Badge模板
Badge会自动叠加在插槽内容的右上角,并带有平滑缩放动画
ts
<template>
<div class="relative inline-block">
<slot></slot>
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 transform scale-0"
enter-to-class="opacity-100 transform scale-100"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 transform scale-100"
leave-to-class="opacity-0 transform scale-0"
>
<span
v-if="shouldShow"
:class="[
'text-white',
'absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2 flex items-center justify-center rounded-full text-xs font-medium leading-none z-10',
typeClasses,
sizeClasses,
props.isDot ? '' : 'border-2 border-white',
]"
:style="customStyle"
>
{{ displayValue }}
</span>
</Transition>
</div>
</template>
七、细节优化与思考
1. 负数显示为 0 :避免出现 -1 条消息 这种不符合直觉的结果。
2. 超出最大值显示 max+:限制宽度,防止 UI 崩坏:
99+ ✔
12999 ✘
3. 自定义颜色优先级更高:使 Badge 可高度定制:
ini
<Badge backgroundColor="#ff8800" color="#fff" />
4. 小圆点模式省略所有内容检查:非常适合在线状态指示。
5. 过渡动画提升体验:从 "出现/消失" 变为 "缩放展开",更柔和自然。
八、使用实例
这样我们就完成一个简易的徽标组件,下面在外面的父组件这样使用就可以啦!
js
<Badge :value="3">
<button class="relative px-3 py-1 bg-gray-200 rounded">消息</button>
</Badge>
<Badge :isDot="true" type="success">
<div class="w-8 h-8 bg-gray-300 rounded-full"></div>
</Badge>
<Badge :value="120" :max="99" type="danger"></Badge>
<Badge value="New" backgroundColor="#f60" color="#fff"></Badge>