refactor(frontend): 优化 UI 基础组件

- 更新 avatar-image, badge, checkbox, input, switch 等组件
- 优化 dialog, pagination, select-item, tabs 等组件
- 调整 table-card, refresh-button 组件
This commit is contained in:
fawney19
2025-12-12 16:15:07 +08:00
parent e902595d58
commit 44e7117d4a
12 changed files with 74 additions and 25 deletions

View File

@@ -19,5 +19,9 @@ const imageClass = computed(() =>
</script>
<template>
<AvatarImagePrimitive :class="imageClass" :src="src || ''" :alt="alt" />
<AvatarImagePrimitive
:class="imageClass"
:src="src || ''"
:alt="alt"
/>
</template>

View File

@@ -3,6 +3,10 @@ import { cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { computed } from 'vue'
const props = withDefaults(defineProps<Props>(), {
variant: 'default',
})
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
@@ -34,10 +38,6 @@ interface Props {
class?: string
}
const props = withDefaults(defineProps<Props>(), {
variant: 'default',
})
const badgeClass = computed(() =>
cn(badgeVariants({ variant: props.variant }), props.class)
)

View File

@@ -5,7 +5,7 @@
:checked="isChecked"
v-bind="$attrs"
@change="handleChange"
/>
>
</template>
<script setup lang="ts">

View File

@@ -1,6 +1,10 @@
<template>
<Teleport to="body">
<div v-if="isOpen" class="fixed inset-0 overflow-y-auto" :style="{ zIndex: containerZIndex }">
<div
v-if="isOpen"
class="fixed inset-0 overflow-y-auto"
:style="{ zIndex: containerZIndex }"
>
<!-- 背景遮罩 -->
<Transition
enter-active-class="duration-200 ease-out"
@@ -30,21 +34,38 @@
>
<div
v-if="isOpen"
@click.stop
class="relative transform rounded-lg bg-background text-left shadow-2xl transition-all sm:my-8 sm:w-full border border-border"
:style="{ zIndex: contentZIndex }"
:class="maxWidthClass"
@click.stop
>
<!-- Header 区域优先使用 slot否则使用 title prop -->
<slot name="header">
<div v-if="title" class="border-b border-border px-6 py-4">
<div
v-if="title"
class="border-b border-border px-6 py-4"
>
<div class="flex items-center gap-3">
<div v-if="icon" class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 flex-shrink-0" :class="iconClass">
<component :is="icon" class="h-5 w-5 text-primary" />
<div
v-if="icon"
class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 flex-shrink-0"
:class="iconClass"
>
<component
:is="icon"
class="h-5 w-5 text-primary"
/>
</div>
<div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold text-foreground leading-tight">{{ title }}</h3>
<p v-if="description" class="text-xs text-muted-foreground">{{ description }}</p>
<h3 class="text-lg font-semibold text-foreground leading-tight">
{{ title }}
</h3>
<p
v-if="description"
class="text-xs text-muted-foreground"
>
{{ description }}
</p>
</div>
</div>
</div>

View File

@@ -5,7 +5,7 @@
:autocomplete="autocompleteAttr"
v-bind="$attrs"
@input="handleInput"
/>
>
</template>
<script setup lang="ts">

View File

@@ -48,7 +48,10 @@
</Button>
<!-- 页码按钮智能省略 -->
<template v-for="page in pageNumbers" :key="page">
<template
v-for="page in pageNumbers"
:key="page"
>
<Button
v-if="typeof page === 'number'"
:variant="page === current ? 'default' : 'outline'"
@@ -59,7 +62,10 @@
>
{{ page }}
</Button>
<span v-else class="px-2 text-muted-foreground select-none">{{ page }}</span>
<span
v-else
class="px-2 text-muted-foreground select-none"
>{{ page }}</span>
</template>
<Button

View File

@@ -7,7 +7,10 @@
:title="title"
@click="handleClick"
>
<RefreshCcw class="w-3.5 h-3.5" :class="loading ? 'animate-spin' : ''" />
<RefreshCcw
class="w-3.5 h-3.5"
:class="loading ? 'animate-spin' : ''"
/>
</Button>
</template>

View File

@@ -21,7 +21,11 @@ const itemClass = computed(() =>
</script>
<template>
<SelectItemPrimitive :class="itemClass" :value="value" :disabled="disabled">
<SelectItemPrimitive
:class="itemClass"
:value="value"
:disabled="disabled"
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectItemIndicator>
<Check class="h-4 w-4" />

View File

@@ -3,15 +3,15 @@
type="button"
role="switch"
:aria-checked="modelValue"
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
modelValue ? 'bg-primary' : 'bg-muted'
]"
@click="$emit('update:modelValue', !modelValue)"
>
<span
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
modelValue ? 'translate-x-6' : 'translate-x-1'
]"
/>

View File

@@ -1,14 +1,22 @@
<template>
<Card class="overflow-hidden">
<!-- 标题和操作栏 -->
<div v-if="$slots.header || title" class="px-6 py-3.5 border-b border-border/60">
<div
v-if="$slots.header || title"
class="px-6 py-3.5 border-b border-border/60"
>
<slot name="header">
<div class="flex items-center justify-between gap-4">
<!-- 左侧标题 -->
<h3 class="text-base font-semibold">{{ title }}</h3>
<h3 class="text-base font-semibold">
{{ title }}
</h3>
<!-- 右侧操作区 -->
<div v-if="$slots.actions" class="flex items-center gap-2">
<div
v-if="$slots.actions"
class="flex items-center gap-2"
>
<slot name="actions" />
</div>
</div>

View File

@@ -1,5 +1,8 @@
<template>
<div :class="listClass" ref="listRef">
<div
ref="listRef"
:class="listClass"
>
<!-- 滑动指示器 - 放在按钮前面 -->
<div
class="tabs-indicator"

View File

@@ -3,8 +3,8 @@
:class="triggerClass"
:data-state="isActive ? 'active' : 'inactive'"
:data-value="props.value"
@click="handleClick"
type="button"
@click="handleClick"
>
<slot />
</button>