2025-12-18 13:50:39 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div :class="['stat-icon', iconClass]">
|
2025-12-25 08:40:12 -08:00
|
|
|
<component v-if="icon" :is="icon" class="h-6 w-6" aria-hidden="true" />
|
2025-12-18 13:50:39 +08:00
|
|
|
</div>
|
2025-12-25 08:40:12 -08:00
|
|
|
<div class="min-w-0 flex-1">
|
2025-12-18 13:50:39 +08:00
|
|
|
<p class="stat-label truncate">{{ title }}</p>
|
2025-12-25 08:40:12 -08:00
|
|
|
<div class="mt-1 flex items-baseline gap-2">
|
2026-02-13 15:59:30 +08:00
|
|
|
<p class="stat-value" :title="String(formattedValue)">{{ formattedValue }}</p>
|
2025-12-25 08:40:12 -08:00
|
|
|
<span v-if="change !== undefined" :class="['stat-trend', trendClass]">
|
refactor(frontend): 完成所有组件的内联SVG统一替换为Icon组件
- 扩展 Icon.vue 组件,新增 60+ 图标路径
- 导航类: arrowRight, arrowLeft, arrowUp, arrowDown, chevronUp, externalLink
- 状态类: checkCircle, xCircle, exclamationCircle, exclamationTriangle, infoCircle
- 用户类: user, userCircle, userPlus, users
- 文档类: document, clipboard, copy, inbox
- 操作类: download, upload, filter, sort
- 安全类: key, lock, shield
- UI类: menu, calendar, home, terminal, gift, creditCard, mail
- 数据类: chartBar, trendingUp, database, cube
- 其他: bolt, sparkles, cloud, server, sun, moon, book 等
- 重构 56 个 Vue 组件,用 Icon 组件替换内联 SVG
- 净减少约 2200 行代码
- 提升代码可维护性和一致性
- 统一图标样式和尺寸管理
2026-01-05 20:22:48 +08:00
|
|
|
<Icon
|
2025-12-18 13:50:39 +08:00
|
|
|
v-if="changeType !== 'neutral'"
|
refactor(frontend): 完成所有组件的内联SVG统一替换为Icon组件
- 扩展 Icon.vue 组件,新增 60+ 图标路径
- 导航类: arrowRight, arrowLeft, arrowUp, arrowDown, chevronUp, externalLink
- 状态类: checkCircle, xCircle, exclamationCircle, exclamationTriangle, infoCircle
- 用户类: user, userCircle, userPlus, users
- 文档类: document, clipboard, copy, inbox
- 操作类: download, upload, filter, sort
- 安全类: key, lock, shield
- UI类: menu, calendar, home, terminal, gift, creditCard, mail
- 数据类: chartBar, trendingUp, database, cube
- 其他: bolt, sparkles, cloud, server, sun, moon, book 等
- 重构 56 个 Vue 组件,用 Icon 组件替换内联 SVG
- 净减少约 2200 行代码
- 提升代码可维护性和一致性
- 统一图标样式和尺寸管理
2026-01-05 20:22:48 +08:00
|
|
|
name="arrowUp"
|
|
|
|
|
size="xs"
|
|
|
|
|
:class="changeType === 'down' && 'rotate-180'"
|
|
|
|
|
/>
|
2025-12-18 13:50:39 +08:00
|
|
|
{{ formattedChange }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed } from 'vue'
|
|
|
|
|
import type { Component } from 'vue'
|
refactor(frontend): 完成所有组件的内联SVG统一替换为Icon组件
- 扩展 Icon.vue 组件,新增 60+ 图标路径
- 导航类: arrowRight, arrowLeft, arrowUp, arrowDown, chevronUp, externalLink
- 状态类: checkCircle, xCircle, exclamationCircle, exclamationTriangle, infoCircle
- 用户类: user, userCircle, userPlus, users
- 文档类: document, clipboard, copy, inbox
- 操作类: download, upload, filter, sort
- 安全类: key, lock, shield
- UI类: menu, calendar, home, terminal, gift, creditCard, mail
- 数据类: chartBar, trendingUp, database, cube
- 其他: bolt, sparkles, cloud, server, sun, moon, book 等
- 重构 56 个 Vue 组件,用 Icon 组件替换内联 SVG
- 净减少约 2200 行代码
- 提升代码可维护性和一致性
- 统一图标样式和尺寸管理
2026-01-05 20:22:48 +08:00
|
|
|
import Icon from '@/components/icons/Icon.vue'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
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>
|