Files
dreamweaver/frontend/src/components/ui/BaseButton.vue
zhangtuo e9d7f8832a Initial commit: clean project structure
- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+)
- Frontend: Vue 3 + TypeScript + Pinia + Tailwind
- Admin Frontend: separate Vue 3 app for management
- Docker Compose: 9 services orchestration
- Specs: design prototypes, memory system PRD, product roadmap

Cleanup performed:
- Removed temporary debug scripts from backend root
- Removed deprecated admin_app.py (embedded UI)
- Removed duplicate docs from admin-frontend
- Updated .gitignore for Vite cache and egg-info
2026-01-20 18:20:03 +08:00

88 lines
2.4 KiB
Vue

<script setup lang="ts">
import { computed, useAttrs } from 'vue'
import type { Component } from 'vue'
defineOptions({ inheritAttrs: false })
const props = withDefaults(
defineProps<{
variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
size?: 'sm' | 'md' | 'lg'
loading?: boolean
disabled?: boolean
icon?: Component
as?: string | Record<string, unknown>
}>(),
{
variant: 'primary',
size: 'md',
loading: false,
disabled: false,
as: 'button',
},
)
const attrs = useAttrs()
const isButton = computed(() => props.as === 'button' || !props.as)
const isDisabled = computed(() => props.disabled || props.loading)
const sizeClasses = computed(() => {
if (props.size === 'sm') return 'px-3 py-2 text-sm rounded-lg'
if (props.size === 'lg') return 'px-6 py-3 text-base rounded-xl'
return 'px-4 py-2.5 text-sm rounded-xl'
})
const variantClasses = computed(() => {
switch (props.variant) {
case 'secondary':
return 'bg-white border border-gray-200 text-gray-700 hover:bg-gray-50'
case 'danger':
return 'bg-red-500 text-white hover:bg-red-600'
case 'ghost':
return 'bg-transparent text-gray-600 hover:bg-gray-100'
default:
return 'btn-magic text-white'
}
})
const baseClasses = computed(() => [
'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-300',
sizeClasses.value,
variantClasses.value,
isDisabled.value ? 'opacity-60 cursor-not-allowed' : '',
])
const passthroughAttrs = computed(() => {
const { class: _class, type: _type, ...rest } = attrs
return rest
})
function handleClick(event: MouseEvent) {
if (!isButton.value && isDisabled.value) {
event.preventDefault()
event.stopPropagation()
}
}
</script>
<template>
<component
:is="props.as || 'button'"
:type="isButton ? (attrs.type as string || 'button') : undefined"
:disabled="isButton ? isDisabled : undefined"
:aria-disabled="!isButton && isDisabled ? 'true' : undefined"
:class="[baseClasses, attrs.class]"
v-bind="passthroughAttrs"
@click="handleClick"
>
<span
v-if="props.loading"
class="inline-flex h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
aria-hidden="true"
></span>
<component v-else-if="props.icon" :is="props.icon" class="h-5 w-5" aria-hidden="true" />
<slot />
</component>
</template>