mirror of
https://github.com/fantastic-admin/basic.git
synced 2026-10-06 12:53:21 +08:00
feat(badge): 新增徽章组件及示例
This commit is contained in:
@@ -29,6 +29,14 @@ const routes: RouteRecordRaw = {
|
||||
title: '头像',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'badge',
|
||||
name: 'componentExampleBadge',
|
||||
component: () => import('@/views/component_example/badge.vue'),
|
||||
meta: {
|
||||
title: '徽章',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'button',
|
||||
name: 'componentExampleButton',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { badge } from '@fantastic-admin/components/examples'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<FaPageHeader title="徽章" description="FaBadge" />
|
||||
<FaPageMain
|
||||
v-for="example in badge"
|
||||
:key="example.title"
|
||||
:code="example.componentRaw"
|
||||
:title="example.title"
|
||||
>
|
||||
<component :is="example.component" />
|
||||
</FaPageMain>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
# FaBadge 徽章
|
||||
|
||||
用于展示数量、状态的小徽章,支持数字、文本和点状模式,带有入场动画。
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 消息通知数量提示
|
||||
- 未读消息数显示
|
||||
- 状态指示器(在线/离线)
|
||||
- 新内容标记(NEW、HOT 等)
|
||||
- 购物车商品数量
|
||||
|
||||
## Props
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `value` | `string \| number \| boolean` | - | 徽章显示内容,为空/0/false 时自动隐藏 |
|
||||
| `variant` | `'default' \| 'secondary' \| 'destructive'` | `'default'` | 徽章颜色变体 |
|
||||
| `class` | `HTMLAttributes['class']` | - | 容器 CSS 类 |
|
||||
| `badgeClass` | `HTMLAttributes['class']` | - | 徽章本身的 CSS 类 |
|
||||
|
||||
## Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| `default` | 被包裹的内容(如图标、头像等) |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **自动隐藏**:
|
||||
- 当 `value` 为 `0`、`false`、空字符串、`null` 或 `undefined` 时,徽章会自动隐藏
|
||||
- 布尔值 `true` 会显示为一个小圆点
|
||||
|
||||
2. **动画效果**:徽章显示/隐藏时有淡入淡出动画
|
||||
|
||||
3. **变体说明**:
|
||||
- `default`:主色(蓝色)
|
||||
- `secondary`:次要色(灰色)
|
||||
- `destructive`:危险色(红色)
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
// 组件实际使用时无需手动导入,框架会自动导入
|
||||
import FaIcon from '../../../basic/icon/index.vue'
|
||||
import FaBadge from '../index.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-8">
|
||||
<FaBadge :value="true">
|
||||
<FaIcon name="i-ri:notification-3-line" />
|
||||
</FaBadge>
|
||||
<FaBadge :value="99">
|
||||
<FaIcon name="i-ri:notification-3-line" />
|
||||
</FaBadge>
|
||||
<FaBadge value="噢">
|
||||
<FaIcon name="i-ri:notification-3-line" />
|
||||
</FaBadge>
|
||||
<FaBadge value="9" variant="secondary">
|
||||
<FaIcon name="i-ri:notification-3-line" />
|
||||
</FaBadge>
|
||||
<FaBadge value="9" variant="destructive">
|
||||
<FaIcon name="i-ri:notification-3-line" />
|
||||
</FaBadge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
import Basic from './_basic.vue'
|
||||
import BasicRaw from './_basic.vue?raw'
|
||||
|
||||
export default [
|
||||
{
|
||||
title: '基础',
|
||||
component: Basic,
|
||||
componentRaw: BasicRaw,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { BadgeVariants } from '.'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '#utils'
|
||||
import { badgeVariants } from '.'
|
||||
|
||||
const props = defineProps<PrimitiveProps & {
|
||||
variant?: BadgeVariants['variant']
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
data-slot="badge"
|
||||
:class="cn(badgeVariants({ variant }), props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Badge } from './Badge.vue'
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { computed, ref } from 'vue'
|
||||
import { cn } from '#utils'
|
||||
import { Badge } from './badge'
|
||||
|
||||
defineOptions({
|
||||
name: 'BuiltInBadge',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
variant?: BadgeVariant['variant']
|
||||
value: string | number | boolean
|
||||
class?: HTMLAttributes['class']
|
||||
badgeClass?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const badgeDotVariant = cva(
|
||||
'absolute start-[100%] h-1.5 w-1.5 rounded-full px-0 ring-1 ring-background before:(absolute inset-0 bg-inherit block h-full w-full animate-ping rounded-full content-empty) -translate-x-[50%] -translate-y-[50%] rtl:(translate-x-[50%]) -indent-9999',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary hover:bg-primary/80',
|
||||
secondary:
|
||||
'bg-secondary hover:bg-secondary/80',
|
||||
destructive:
|
||||
'bg-destructive hover:bg-destructive/80',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
type BadgeVariant = VariantProps<typeof badgeDotVariant>
|
||||
|
||||
const show = computed(() => {
|
||||
switch (typeof props.value) {
|
||||
case 'string':
|
||||
return props.value.length > 0
|
||||
case 'number':
|
||||
return props.value > 0
|
||||
case 'boolean':
|
||||
return props.value
|
||||
default:
|
||||
return props.value !== undefined && props.value !== null
|
||||
}
|
||||
})
|
||||
|
||||
const transitionClass = ref({
|
||||
enterActiveClass: 'ease-in-out duration-500',
|
||||
enterFromClass: 'opacity-0',
|
||||
enterToClass: 'opacity-100',
|
||||
leaveActiveClass: 'ease-in-out duration-500',
|
||||
leaveFromClass: 'opacity-100',
|
||||
leaveToClass: 'opacity-0',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('relative inline-flex', props.class)">
|
||||
<slot />
|
||||
<Transition v-bind="transitionClass">
|
||||
<div v-if="show" class="h-full w-full absolute">
|
||||
<div v-if="value === true" :class="badgeDotVariant({ variant })" />
|
||||
<Badge v-else :variant :class="cn('absolute start-[50%] top-0 z-20 whitespace-nowrap px-1.5 py-0 ring-1 ring-primary-foreground -translate-y-[50%] hover:bg-none!', props.badgeClass)">
|
||||
{{ value }}
|
||||
</Badge>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as alert } from './basic/alert/_examples'
|
||||
export { default as avatar } from './basic/avatar/_examples'
|
||||
export { default as badge } from './basic/badge/_examples'
|
||||
export { default as buttonGroup } from './basic/button-group/_examples'
|
||||
export { default as button } from './basic/button/_examples'
|
||||
export { default as card } from './basic/card/_examples'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 基础版组件
|
||||
export { default as FaAlert } from './basic/alert/index.vue'
|
||||
export { default as FaAvatar } from './basic/avatar/index.vue'
|
||||
export { default as FaBadge } from './basic/badge/index.vue'
|
||||
export { default as FaButtonGroup } from './basic/button-group/index.vue'
|
||||
export { default as FaButton } from './basic/button/index.vue'
|
||||
export { default as FaCard } from './basic/card/index.vue'
|
||||
|
||||
Reference in New Issue
Block a user