Files
sub2api/frontend/src/components/common/StatCard.vue
wucm667 be56a282f2 修复:StatCard 数值溢出问题
- 添加 title 属性,鼠标悬停时显示完整数值
- 添加 truncate 类防止数值溢出
- 优化长数值的显示效果
2026-02-13 15:59:30 +08:00

82 lines
2.1 KiB
Vue

<template>
<div class="stat-card">
<div :class="['stat-icon', iconClass]">
<component v-if="icon" :is="icon" class="h-6 w-6" aria-hidden="true" />
</div>
<div class="min-w-0 flex-1">
<p class="stat-label truncate">{{ title }}</p>
<div class="mt-1 flex items-baseline gap-2">
<p class="stat-value" :title="String(formattedValue)">{{ formattedValue }}</p>
<span v-if="change !== undefined" :class="['stat-trend', trendClass]">
<Icon
v-if="changeType !== 'neutral'"
name="arrowUp"
size="xs"
:class="changeType === 'down' && 'rotate-180'"
/>
{{ formattedChange }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { Component } from 'vue'
import Icon from '@/components/icons/Icon.vue'
type ChangeType = 'up' | 'down' | 'neutral'
type IconVariant = 'primary' | 'success' | 'warning' | 'danger'
interface Props {
title: string
value: number | string
icon?: Component
iconVariant?: IconVariant
change?: number
changeType?: ChangeType
formatValue?: (value: number | string) => string
}
const props = withDefaults(defineProps<Props>(), {
changeType: 'neutral',
iconVariant: 'primary'
})
const formattedValue = computed(() => {
if (props.formatValue) {
return props.formatValue(props.value)
}
if (typeof props.value === 'number') {
return props.value.toLocaleString()
}
return props.value
})
const formattedChange = computed(() => {
if (props.change === undefined) return ''
const absChange = Math.abs(props.change)
return `${absChange}%`
})
const iconClass = computed(() => {
const classes: Record<IconVariant, string> = {
primary: 'stat-icon-primary',
success: 'stat-icon-success',
warning: 'stat-icon-warning',
danger: 'stat-icon-danger'
}
return classes[props.iconVariant]
})
const trendClass = computed(() => {
const classes: Record<ChangeType, string> = {
up: 'stat-trend-up',
down: 'stat-trend-down',
neutral: 'text-gray-500 dark:text-dark-400'
}
return classes[props.changeType]
})
</script>