feat(tag): 新增标签组件及示例

This commit is contained in:
Hooray
2026-06-02 09:10:20 +08:00
parent f6516782c0
commit 574d0fcccd
12 changed files with 270 additions and 0 deletions
@@ -294,6 +294,14 @@ const routes: RouteRecordRaw = {
title: '开关',
},
},
{
path: 'tag',
name: 'componentExampleTag',
component: () => import('@/views/component_example/tag.vue'),
meta: {
title: '标签',
},
},
{
path: 'tabs',
name: 'componentExampleTabs',
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { tag } from '@fantastic-admin/components/examples'
</script>
<template>
<div>
<FaPageHeader title="标签" description="FaTag" />
<FaPageMain
v-for="example in tag"
:key="example.title"
:code="example.componentRaw"
:title="example.title"
>
<component :is="example.component" />
</FaPageMain>
</div>
</template>
@@ -0,0 +1,42 @@
# FaTag 标签
基础标签组件,用于标记和分类信息,支持多种样式变体和可关闭功能。
## 使用场景
- 状态标记(成功、失败、进行中)
- 分类标签
- 筛选条件标签
- 展示多个属性或特征
- 可删除的标签列表
## Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `variant` | `'default' \| 'destructive' \| 'outline' \| 'secondary'` | `'default'` | 标签样式变体 |
| `icon` | `string` | - | 图标名称,使用 FaIcon 渲染 |
| `closable` | `boolean` | `false` | 是否显示关闭按钮 |
| `class` | `HTMLAttributes['class']` | - | 自定义 CSS 类 |
## Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| `close` | `event: MouseEvent` | 点击关闭按钮时触发 |
## Slots
| 名称 | 说明 |
|------|------|
| `default` | 标签内容 |
## 注意事项
1. **变体选择**:
- `default`:主色调标签,用于重要标记
- `destructive`:危险/错误状态标签
- `outline`:边框样式,用于次要标记
- `secondary`:次级标签,用于一般分类
2. **可关闭标签**:设置 `closable` 为 `true` 时,标签右侧会显示关闭按钮,点击触发 `close` 事件
3. **关闭事件处理**:需要监听 `close` 事件来自定义关闭逻辑(如从列表中移除标签)
@@ -0,0 +1,21 @@
<script setup lang="ts">
// 组件实际使用时无需手动导入,框架会自动导入
import FaTag from '../index.vue'
</script>
<template>
<div class="flex flex-wrap gap-4">
<FaTag>
默认标签
</FaTag>
<FaTag variant="destructive">
危险标签
</FaTag>
<FaTag variant="outline">
边框标签
</FaTag>
<FaTag variant="secondary">
次要标签
</FaTag>
</div>
</template>
@@ -0,0 +1,30 @@
<script setup lang="ts">
// 组件实际使用时无需手动导入,框架会自动导入
import { ref } from 'vue'
import FaTag from '../index.vue'
const tags = ref([
{ id: 1, label: '标签一', variant: 'default' as const },
{ id: 2, label: '标签二', variant: 'destructive' as const },
{ id: 3, label: '标签三', variant: 'outline' as const },
{ id: 4, label: '标签四', variant: 'secondary' as const },
])
function handleClose(id: number) {
tags.value = tags.value.filter(tag => tag.id !== id)
}
</script>
<template>
<div class="flex flex-wrap gap-4">
<FaTag
v-for="tag in tags"
:key="tag.id"
:variant="tag.variant"
closable
@close="handleClose(tag.id)"
>
{{ tag.label }}
</FaTag>
</div>
</template>
@@ -0,0 +1,21 @@
<script setup lang="ts">
// 组件实际使用时无需手动导入,框架会自动导入
import FaTag from '../index.vue'
</script>
<template>
<div class="flex flex-wrap gap-4">
<FaTag icon="i-lucide:check">
成功
</FaTag>
<FaTag icon="i-lucide:alert-triangle" variant="destructive">
警告
</FaTag>
<FaTag icon="i-lucide:info" variant="outline">
信息
</FaTag>
<FaTag icon="i-lucide:star" variant="secondary">
收藏
</FaTag>
</div>
</template>
@@ -0,0 +1,24 @@
import Basic from './_basic.vue'
import BasicRaw from './_basic.vue?raw'
import Closable from './_closable.vue'
import ClosableRaw from './_closable.vue?raw'
import Icon from './_icon.vue'
import IconRaw from './_icon.vue?raw'
export default [
{
title: '基础',
component: Basic,
componentRaw: BasicRaw,
},
{
title: '图标',
component: Icon,
componentRaw: IconRaw,
},
{
title: '可关闭',
component: Closable,
componentRaw: ClosableRaw,
},
]
@@ -0,0 +1,30 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import type { TagVariants } from './tag'
import { Tag } from './tag'
defineOptions({
name: 'BuiltInTag',
})
const props = defineProps<{
variant?: TagVariants['variant']
class?: HTMLAttributes['class']
icon?: string
closable?: boolean
}>()
const emit = defineEmits<{
close: [event: MouseEvent]
}>()
function handleClose(event: MouseEvent) {
emit('close', event)
}
</script>
<template>
<Tag :variant :icon :closable :class="props.class" @close="handleClose">
<slot />
</Tag>
</template>
@@ -0,0 +1,48 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { TagVariants } from '.'
import { Primitive } from 'reka-ui'
import { cn } from '#utils'
import { tagVariants } from '.'
import FaIcon from '../../icon/index.vue'
interface Props extends PrimitiveProps {
variant?: TagVariants['variant']
icon?: string
closable?: boolean
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<Props>(), {
as: 'span',
})
const emit = defineEmits<{
close: [event: MouseEvent]
}>()
function handleClose(event: MouseEvent) {
emit('close', event)
}
</script>
<template>
<Primitive
data-slot="tag"
:as="as"
:as-child="asChild"
:class="cn(tagVariants({ variant }), props.class)"
>
<FaIcon v-if="icon" :name="icon" class="size-3" />
<slot />
<button
v-if="closable"
type="button"
class="me--1 outline-none rounded-full inline-flex ring-offset-background items-center justify-center focus:ring-2 focus:ring-ring focus:ring-offset-2"
@click="handleClose"
>
<FaIcon name="i-lucide:x" class="size-3" />
</button>
</Primitive>
</template>
@@ -0,0 +1,27 @@
import type { VariantProps } from 'class-variance-authority'
import { cva } from 'class-variance-authority'
export { default as Tag } from './Tag.vue'
export const tagVariants = cva(
'inline-flex items-center gap-1 rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground',
destructive:
'border-transparent bg-destructive text-white dark:bg-destructive/60',
outline:
'border bg-background text-foreground dark:bg-input/30 dark:border-input',
secondary:
'border-transparent bg-secondary text-secondary-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)
export type TagVariants = VariantProps<typeof tagVariants>
+1
View File
@@ -36,6 +36,7 @@ export { default as select } from './basic/select/_examples'
export { default as slider } from './basic/slider/_examples'
export { default as switch } from './basic/switch/_examples'
export { default as tabs } from './basic/tabs/_examples'
export { default as tag } from './basic/tag/_examples'
export { default as textarea } from './basic/textarea/_examples'
export { default as toast } from './basic/toast/_examples'
export { default as tooltip } from './basic/tooltip/_examples'
+1
View File
@@ -44,6 +44,7 @@ export { default as FaSelect } from './basic/select/index.vue'
export { default as FaSlider } from './basic/slider/index.vue'
export { default as FaSwitch } from './basic/switch/index.vue'
export { default as FaTabs } from './basic/tabs/index.vue'
export { default as FaTag } from './basic/tag/index.vue'
export { default as FaTextarea } from './basic/textarea/index.vue'
export { useToast as useFaToast } from './basic/toast/index'
export { default as FaToast } from './basic/toast/index.vue'