mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-02 15:52:26 +08:00
refactor(frontend): 优化 UI 基础组件
- 更新 avatar-image, badge, checkbox, input, switch 等组件 - 优化 dialog, pagination, select-item, tabs 等组件 - 调整 table-card, refresh-button 组件
This commit is contained in:
@@ -19,5 +19,9 @@ const imageClass = computed(() =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AvatarImagePrimitive :class="imageClass" :src="src || ''" :alt="alt" />
|
<AvatarImagePrimitive
|
||||||
|
:class="imageClass"
|
||||||
|
:src="src || ''"
|
||||||
|
:alt="alt"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { cva } from 'class-variance-authority'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
variant: 'default',
|
||||||
|
})
|
||||||
|
|
||||||
const badgeVariants = cva(
|
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',
|
'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
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
variant: 'default',
|
|
||||||
})
|
|
||||||
|
|
||||||
const badgeClass = computed(() =>
|
const badgeClass = computed(() =>
|
||||||
cn(badgeVariants({ variant: props.variant }), props.class)
|
cn(badgeVariants({ variant: props.variant }), props.class)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:checked="isChecked"
|
:checked="isChecked"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
/>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<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
|
<Transition
|
||||||
enter-active-class="duration-200 ease-out"
|
enter-active-class="duration-200 ease-out"
|
||||||
@@ -30,21 +34,38 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isOpen"
|
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"
|
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 }"
|
:style="{ zIndex: contentZIndex }"
|
||||||
:class="maxWidthClass"
|
:class="maxWidthClass"
|
||||||
|
@click.stop
|
||||||
>
|
>
|
||||||
<!-- Header 区域:优先使用 slot,否则使用 title prop -->
|
<!-- Header 区域:优先使用 slot,否则使用 title prop -->
|
||||||
<slot name="header">
|
<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 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">
|
<div
|
||||||
<component :is="icon" class="h-5 w-5 text-primary" />
|
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>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h3 class="text-lg font-semibold text-foreground leading-tight">{{ title }}</h3>
|
<h3 class="text-lg font-semibold text-foreground leading-tight">
|
||||||
<p v-if="description" class="text-xs text-muted-foreground">{{ description }}</p>
|
{{ title }}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
v-if="description"
|
||||||
|
class="text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:autocomplete="autocompleteAttr"
|
:autocomplete="autocompleteAttr"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
/>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -48,7 +48,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<!-- 页码按钮(智能省略) -->
|
<!-- 页码按钮(智能省略) -->
|
||||||
<template v-for="page in pageNumbers" :key="page">
|
<template
|
||||||
|
v-for="page in pageNumbers"
|
||||||
|
:key="page"
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
v-if="typeof page === 'number'"
|
v-if="typeof page === 'number'"
|
||||||
:variant="page === current ? 'default' : 'outline'"
|
:variant="page === current ? 'default' : 'outline'"
|
||||||
@@ -59,7 +62,10 @@
|
|||||||
>
|
>
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</Button>
|
</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>
|
</template>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
:title="title"
|
:title="title"
|
||||||
@click="handleClick"
|
@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>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ const itemClass = computed(() =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
<SelectItemIndicator>
|
<SelectItemIndicator>
|
||||||
<Check class="h-4 w-4" />
|
<Check class="h-4 w-4" />
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
:aria-checked="modelValue"
|
:aria-checked="modelValue"
|
||||||
|
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
|
||||||
:class="[
|
:class="[
|
||||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
|
||||||
modelValue ? 'bg-primary' : 'bg-muted'
|
modelValue ? 'bg-primary' : 'bg-muted'
|
||||||
]"
|
]"
|
||||||
@click="$emit('update:modelValue', !modelValue)"
|
@click="$emit('update:modelValue', !modelValue)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
|
||||||
:class="[
|
:class="[
|
||||||
'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
|
|
||||||
modelValue ? 'translate-x-6' : 'translate-x-1'
|
modelValue ? 'translate-x-6' : 'translate-x-1'
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<Card class="overflow-hidden">
|
<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">
|
<slot name="header">
|
||||||
<div class="flex items-center justify-between gap-4">
|
<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" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="listClass" ref="listRef">
|
<div
|
||||||
|
ref="listRef"
|
||||||
|
:class="listClass"
|
||||||
|
>
|
||||||
<!-- 滑动指示器 - 放在按钮前面 -->
|
<!-- 滑动指示器 - 放在按钮前面 -->
|
||||||
<div
|
<div
|
||||||
class="tabs-indicator"
|
class="tabs-indicator"
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
:class="triggerClass"
|
:class="triggerClass"
|
||||||
:data-state="isActive ? 'active' : 'inactive'"
|
:data-state="isActive ? 'active' : 'inactive'"
|
||||||
:data-value="props.value"
|
:data-value="props.value"
|
||||||
@click="handleClick"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="handleClick"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user