style(ui): introduce StatusBadge and use it in LogDetailCard
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import {
|
||||
X, Trash2, Maximize2, CheckCircle2, XCircle, AlertCircle, Clock, Ban,
|
||||
Zap as ZapIcon, Search, MinusCircle
|
||||
X, Trash2, Maximize2, Ban, Search
|
||||
} from 'lucide-vue-next'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import LogContent from './LogContent.vue'
|
||||
import { TASK_STATUS, TASK_STATUS_TEXT } from '@/constants'
|
||||
import StatusBadge from './StatusBadge.vue'
|
||||
import { TASK_STATUS } from '@/constants'
|
||||
import type { TaskLog } from '@/api'
|
||||
|
||||
interface Props {
|
||||
@@ -92,27 +91,6 @@ watch(() => props.log?.duration, (newVal) => {
|
||||
currentDuration.value = newVal || 0
|
||||
}
|
||||
})
|
||||
|
||||
function getStatusBadgeClass(status: string) {
|
||||
switch (status) {
|
||||
case TASK_STATUS.SUCCESS:
|
||||
return 'bg-emerald-500/5 text-emerald-500 border-emerald-500/15 dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20'
|
||||
case TASK_STATUS.FAILED:
|
||||
return 'bg-rose-500/5 text-rose-500 border-rose-500/15 dark:bg-rose-500/10 dark:text-rose-400 dark:border-rose-500/20'
|
||||
case TASK_STATUS.RUNNING:
|
||||
return 'bg-sky-500/5 text-sky-500 border-sky-500/15 dark:bg-sky-500/10 dark:text-sky-400 dark:border-sky-500/20'
|
||||
case TASK_STATUS.PENDING:
|
||||
return 'bg-amber-500/5 text-amber-500 border-amber-500/15 dark:bg-amber-500/10 dark:text-amber-400 dark:border-amber-500/20'
|
||||
case TASK_STATUS.TIMEOUT:
|
||||
return 'bg-orange-500/5 text-orange-500 border-orange-500/15 dark:bg-orange-500/10 dark:text-orange-400 dark:border-orange-500/20'
|
||||
case TASK_STATUS.CANCELLED:
|
||||
return 'bg-zinc-500/5 text-zinc-400 border-zinc-500/15 dark:bg-zinc-500/10 dark:text-zinc-400 dark:border-zinc-500/20'
|
||||
case 'UNEXECUTED':
|
||||
return 'bg-zinc-500/5 text-zinc-400 border-zinc-500/15 border-dashed dark:bg-zinc-500/10 dark:text-zinc-400 dark:border-zinc-500/20'
|
||||
default:
|
||||
return 'bg-secondary text-secondary-foreground border-transparent'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -126,12 +104,7 @@ function getStatusBadgeClass(status: string) {
|
||||
<span class="text-xs font-medium text-foreground whitespace-nowrap truncate">{{ title }}</span>
|
||||
|
||||
<!-- Simple 模式下的状态显示 -->
|
||||
<Badge v-if="variant === 'simple'" variant="outline" :class="[
|
||||
'px-2 py-0.5 font-normal rounded-full border text-[10px] flex',
|
||||
getStatusBadgeClass(log.status)
|
||||
]">
|
||||
{{ TASK_STATUS_TEXT[log.status] || log.status }}
|
||||
</Badge>
|
||||
<StatusBadge v-if="variant === 'simple'" :status="log.status" />
|
||||
|
||||
<!-- 详情模式下的停止按钮 (始终在第一行进入) -->
|
||||
<Button v-if="log.status === TASK_STATUS.RUNNING && variant === 'full'" variant="destructive" size="sm"
|
||||
@@ -201,26 +174,7 @@ function getStatusBadgeClass(status: string) {
|
||||
</div>
|
||||
<div class="flex justify-between items-center h-8">
|
||||
<span class="text-sm font-normal text-muted-foreground">状态</span>
|
||||
<Badge variant="outline" :class="[
|
||||
'capitalize px-2.5 py-0.5 font-normal rounded-full border transition-all duration-300',
|
||||
getStatusBadgeClass(log.status)
|
||||
]">
|
||||
<div class="flex items-center gap-1">
|
||||
<CheckCircle2 v-if="log.status === TASK_STATUS.SUCCESS" class="h-3 w-3 fill-emerald-500/10" />
|
||||
<XCircle v-else-if="log.status === TASK_STATUS.FAILED" class="h-3 w-3 fill-rose-500/10" />
|
||||
<span v-else-if="log.status === TASK_STATUS.RUNNING" class="relative flex h-1.5 w-1.5 mr-0.5">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-1.5 w-1.5 bg-sky-500"></span>
|
||||
</span>
|
||||
<Clock v-else-if="log.status === TASK_STATUS.PENDING" class="h-3 w-3 fill-amber-500/10" />
|
||||
<AlertCircle v-else-if="log.status === TASK_STATUS.TIMEOUT" class="h-3 w-3 fill-orange-500/10" />
|
||||
<Ban v-else-if="log.status === TASK_STATUS.CANCELLED" class="h-3 w-3" />
|
||||
<MinusCircle v-else-if="log.status === 'UNEXECUTED'" class="h-3 w-3 opacity-40" />
|
||||
<span class="text-[10px] font-normal tracking-wide">
|
||||
{{ TASK_STATUS_TEXT[log.status] || log.status }}
|
||||
</span>
|
||||
</div>
|
||||
</Badge>
|
||||
<StatusBadge :status="log.status" />
|
||||
</div>
|
||||
<div class="flex justify-between items-center h-6">
|
||||
<span class="text-sm font-normal text-muted-foreground">耗时</span>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { TASK_STATUS, TASK_STATUS_TEXT } from '@/constants'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
status: string
|
||||
showDot?: boolean
|
||||
}>(),
|
||||
{
|
||||
showDot: true
|
||||
}
|
||||
)
|
||||
|
||||
const badgeClasses = computed(() => {
|
||||
switch (props.status) {
|
||||
case TASK_STATUS.SUCCESS:
|
||||
return 'border-green-500/20 text-green-600 dark:text-green-400 bg-green-500/10 dark:bg-green-500/20'
|
||||
case TASK_STATUS.FAILED:
|
||||
return 'border-red-500/20 text-red-600 dark:text-red-400 bg-red-500/10 dark:bg-red-500/20'
|
||||
case TASK_STATUS.RUNNING:
|
||||
return 'border-amber-500/20 text-amber-600 dark:text-amber-400 bg-amber-500/10 dark:bg-amber-500/20'
|
||||
case TASK_STATUS.PENDING:
|
||||
case 'queued':
|
||||
return 'border-blue-400/20 text-blue-500 dark:text-blue-400 bg-blue-400/10 dark:bg-blue-400/20'
|
||||
case TASK_STATUS.TIMEOUT:
|
||||
return 'border-orange-500/20 text-orange-600 dark:text-orange-400 bg-orange-500/10 dark:bg-orange-500/20'
|
||||
case TASK_STATUS.CANCELLED:
|
||||
return 'border-muted-foreground/10 text-muted-foreground bg-muted/50'
|
||||
default:
|
||||
return 'border-transparent text-secondary-foreground bg-secondary'
|
||||
}
|
||||
})
|
||||
|
||||
const dotClasses = computed(() => {
|
||||
switch (props.status) {
|
||||
case TASK_STATUS.SUCCESS:
|
||||
return 'bg-green-500'
|
||||
case TASK_STATUS.FAILED:
|
||||
return 'bg-red-500'
|
||||
case TASK_STATUS.RUNNING:
|
||||
return 'bg-amber-500'
|
||||
case TASK_STATUS.PENDING:
|
||||
case 'queued':
|
||||
return 'bg-blue-400'
|
||||
case TASK_STATUS.TIMEOUT:
|
||||
return 'bg-orange-500'
|
||||
case TASK_STATUS.CANCELLED:
|
||||
return 'bg-muted-foreground'
|
||||
default:
|
||||
return 'bg-muted-foreground'
|
||||
}
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
return TASK_STATUS_TEXT[props.status as keyof typeof TASK_STATUS_TEXT] || props.status
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-0.5 text-[10px] font-normal rounded-full border transition-all duration-300 select-none"
|
||||
:class="badgeClasses"
|
||||
>
|
||||
<!-- 正在运行状态使用双层波纹微动效的呼吸灯 -->
|
||||
<span v-if="showDot && status === TASK_STATUS.RUNNING" class="relative flex h-1.5 w-1.5 shrink-0">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-1.5 w-1.5 bg-amber-500"></span>
|
||||
</span>
|
||||
<!-- 其他状态使用常规实心圆点 -->
|
||||
<span
|
||||
v-else-if="showDot"
|
||||
class="h-1.5 w-1.5 rounded-full shrink-0"
|
||||
:class="dotClasses"
|
||||
></span>
|
||||
|
||||
<span class="tracking-wide text-[10px]">{{ statusText }}</span>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user