inspector/frontend/src/components/pulse/HealthDonut.vue

82 lines
2.7 KiB
Vue

<template>
<div class="flex flex-col items-center justify-center py-5">
<div
class="relative w-[180px] h-[180px]"
:class="{ spinning: spinning }"
:style="spinning ? '' : 'animation: donutPulse 3s ease-in-out infinite, donutBreathe 4s ease-in-out infinite'"
>
<svg viewBox="0 0 180 180" class="w-[180px] h-[180px]" :style="spinning ? 'animation: ringSpin 1.5s linear infinite' : 'transform: rotate(-90deg)'">
<defs>
<linearGradient id="healthGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="var(--ins-negative)" />
<stop offset="40%" stop-color="var(--ins-warning)" />
<stop offset="100%" stop-color="var(--ins-positive)" />
</linearGradient>
</defs>
<circle
cx="90" cy="90" r="76"
fill="none"
stroke="var(--bg-base)"
stroke-width="14"
/>
<circle
cx="90" cy="90" r="76"
fill="none"
stroke="var(--ins-indigo)"
stroke-width="18"
opacity="0.1"
:stroke-dasharray="circumference"
:stroke-dashoffset="glowOffset"
style="filter: blur(4px)"
/>
<circle
cx="90" cy="90" r="76"
fill="none"
:stroke="spinning ? 'var(--ins-indigo)' : 'url(#healthGrad)'"
stroke-width="14"
stroke-linecap="round"
:stroke-dasharray="circumference"
:stroke-dashoffset="spinning ? 380 : fillOffset"
:style="spinning ? '' : 'transition: stroke-dashoffset 1s cubic-bezier(.22,1,.36,1)'"
/>
</svg>
<div class="absolute inset-0 flex flex-col items-center justify-center">
<div class="text-5xl font-extrabold tracking-tighter leading-none text-[var(--ins-text-primary)]">
{{ score }}
</div>
<div class="text-[10px] font-bold text-[var(--ins-text-tertiary)] uppercase tracking-widest mt-1">
Health Score
</div>
</div>
</div>
<button
class="mt-2.5 px-3.5 py-1 rounded-full text-xs font-bold cursor-pointer transition-all duration-150 hover:scale-105"
:class="
score >= 80
? 'bg-[var(--ins-positive-bg)] text-[var(--ins-positive)]'
: score >= 50
? 'bg-[var(--ins-warning-bg)] text-[var(--ins-warning)]'
: 'bg-[var(--ins-negative-bg)] text-[var(--ins-negative)]'
"
@click="$emit('attention')"
>
{{ score >= 80 ? '✓ Хорошо' : score >= 50 ? '⚠ Требует внимания' : '✕ Критично' }}
</button>
</div>
</template>
<script setup>
import { computed } from "vue"
const props = defineProps({
score: { type: Number, default: 75 },
spinning: { type: Boolean, default: false },
})
defineEmits(["attention"])
const circumference = 2 * Math.PI * 76 // ~478
const fillOffset = computed(() => circumference * (1 - props.score / 100))
const glowOffset = computed(() => circumference * (1 - props.score / 100))
</script>