From 8a1e3bc7ea351d4fceac4185c5fe7f7f32a76974 Mon Sep 17 00:00:00 2001
From: xiaoyi <908147524@qq.com>
Date: 星期五, 30 十月 2026 20:21:08 +0800
Subject: [PATCH] style(dashboard): 出勤指标与库存采购改为标准 KPI 卡片,与首页统一

---
 src/views/dashboard/analytics/modules/hr-stock-cards.vue |  367 +++++++++++++++++++++++++++++++--------------------
 src/views/dashboard/analytics/index.vue                  |    2 
 2 files changed, 224 insertions(+), 145 deletions(-)

diff --git a/src/views/dashboard/analytics/index.vue b/src/views/dashboard/analytics/index.vue
index a0792a0..14c87a5 100644
--- a/src/views/dashboard/analytics/index.vue
+++ b/src/views/dashboard/analytics/index.vue
@@ -84,7 +84,7 @@
     <BizCards @navigate="handleNavigate" />
 
     <!-- 鍑哄嫟鎸囨爣 + 搴撳瓨閲囪喘 -->
-    <HrStockCards class="mb-4 block" @navigate="handleNavigate" />
+    <HrStockCards @navigate="handleNavigate" />
 
     <!-- AI 鏅鸿兘鍒嗘瀽 -->
     <AiInsights
diff --git a/src/views/dashboard/analytics/modules/hr-stock-cards.vue b/src/views/dashboard/analytics/modules/hr-stock-cards.vue
index 36ccdbb..c8a12cf 100644
--- a/src/views/dashboard/analytics/modules/hr-stock-cards.vue
+++ b/src/views/dashboard/analytics/modules/hr-stock-cards.vue
@@ -4,7 +4,7 @@
 import { CountTo } from '@vben/common-ui';
 import { IconifyIcon } from '@vben/icons';
 
-import { Col, Progress, Row, Tooltip } from 'ant-design-vue';
+import { Col, Row } from 'ant-design-vue';
 
 import { getAttendanceSummaryPage } from '#/api/hrm/attendance/summary';
 import { getMaterialStockPage } from '#/api/mes/wm/materialstock';
@@ -17,15 +17,19 @@
 const emit = defineEmits<{ navigate: [name: string] }>();
 
 const { canNavigate, canQuery } = useHomeAccess();
-const canViewHr = computed(() => canQuery('hrm:attendance-summary:query') || canQuery('hrm:attendance-record:query'));
-const canViewStock = computed(() => canQuery('mes:wm-material-stock:query') && canNavigate('MesWmMaterialStock'));
-const canViewPurchase = computed(() => canQuery('erp:purchase-order:query') && canNavigate('ErpPurchaseOrder'));
+
+const canViewAttendance =
+  computed(() => canQuery('hrm:attendance-summary:query') || canQuery('hrm:attendance-record:query'));
+const canViewStock =
+  computed(() => canQuery('mes:wm-material-stock:query') && canNavigate('MesWmMaterialStock'));
+const canViewPurchase =
+  computed(() => canQuery('erp:purchase-order:query') && canNavigate('ErpPurchaseOrder'));
 
 // ========== 鍑哄嫟鎸囨爣 ==========
-const hrLoading = ref(false);
 const headCount = ref(0);
-const actualDaysAvg = ref(0);
-const attendanceRate = ref(0);
+const attendanceRate = ref<number | null>(null);
+const averageDays = ref(0);
+const abnormalCount = ref(0);
 const lateCount = ref(0);
 const absentCount = ref(0);
 const leaveHours = ref(0);
@@ -36,66 +40,107 @@
 }
 
 async function loadAttendance() {
-  if (!canViewHr.value) return;
-  hrLoading.value = true;
+  if (!canViewAttendance.value) return;
   try {
     const res = await getAttendanceSummaryPage({
       pageNo: 1,
       pageSize: 200,
       yearMonth: currentYearMonth(),
     } as any);
-    const list = res?.list ?? [];
-    headCount.value = res?.total ?? list.length;
-    if (list.length > 0) {
-      let workSum = 0, actualSum = 0, late = 0, absent = 0, leave = 0;
-      for (const r of list) {
+    const rows = res?.list ?? [];
+    headCount.value = res?.total ?? rows.length;
+    if (rows.length > 0) {
+      let workSum = 0;
+      let actualSum = 0;
+      let late = 0;
+      let absent = 0;
+      let leave = 0;
+      for (const r of rows) {
         workSum += Number(r.workDays ?? 0);
         actualSum += Number(r.actualDays ?? 0);
         late += Number(r.lateCount ?? 0);
         absent += Number(r.absentCount ?? 0);
         leave += Number(r.leaveHours ?? 0);
       }
-      actualDaysAvg.value = workSum > 0 ? Number((actualSum / list.length).toFixed(1)) : 0;
-      attendanceRate.value = workSum > 0 ? Math.min(100, Math.round((actualSum / workSum) * 100)) : 0;
+      averageDays.value = Number((actualSum / rows.length).toFixed(1));
+      attendanceRate.value = workSum > 0 ? Number(((actualSum / workSum) * 100).toFixed(1)) : null;
       lateCount.value = late;
       absentCount.value = absent;
       leaveHours.value = Number(leave.toFixed(1));
+      abnormalCount.value = late + absent;
     }
-  } catch { /* 蹇界暐 */ } finally { hrLoading.value = false; }
+  } catch {
+    /* 蹇界暐 */
+  }
 }
 
-// ========== 搴撳瓨閲囪喘 ==========
-const stockLoading = ref(false);
-const stockKinds = ref(0);
+// ========== 搴撳瓨 / 閲囪喘 ==========
+const stockSkuCount = ref(0);
 const stockAvailable = ref(0);
-const purchaseCount = ref(0);
+const purchaseTotal = ref(0);
 const purchasePending = ref(0);
 
 async function loadStock() {
   if (!canViewStock.value) return;
-  stockLoading.value = true;
   try {
     const res = await getMaterialStockPage({ pageNo: 1, pageSize: 200 } as any);
-    const list = res?.list ?? [];
-    stockKinds.value = res?.total ?? list.length;
-    stockAvailable.value = Number(list.reduce((s: number, r: any) => s + Number(r.availableQuantity ?? 0), 0).toFixed(0));
-  } catch { /* 蹇界暐 */ } finally { stockLoading.value = false; }
+    const rows = res?.list ?? [];
+    stockSkuCount.value = res?.total ?? rows.length;
+    stockAvailable.value = Math.round(
+      rows.reduce((sum: number, r: any) => sum + Number(r.availableQuantity ?? 0), 0),
+    );
+  } catch {
+    /* 蹇界暐 */
+  }
 }
 
 async function loadPurchase() {
   if (!canViewPurchase.value) return;
   try {
     const res = await getPurchaseOrderPage({ pageNo: 1, pageSize: 1 } as any);
-    purchaseCount.value = res?.total ?? 0;
-    // 寰呭鎵规暟閲忥紙鐘舵�� 10 = 瀹℃壒涓紝鎸� yudao ERP 绾﹀畾锛�
+    purchaseTotal.value = res?.total ?? 0;
     const pending = await getPurchaseOrderPage({ pageNo: 1, pageSize: 1, status: 10 } as any);
     purchasePending.value = pending?.total ?? 0;
-  } catch { /* 蹇界暐 */ }
+  } catch {
+    /* 蹇界暐 */
+  }
 }
 
 function goto(name: string) {
   if (canNavigate(name)) emit('navigate', name);
 }
+
+/** 3D 鍊炬枩 */
+interface TiltState {
+  x: number;
+  y: number;
+}
+const tilts = ref<Record<string, TiltState>>({});
+
+function onMouseMove(e: MouseEvent, id: string) {
+  const el = e.currentTarget as HTMLElement;
+  const rect = el.getBoundingClientRect();
+  const x = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
+  const y = ((e.clientY - rect.top) / rect.height - 0.5) * 2;
+  tilts.value[id] = { x: x * -6, y: y * 6 };
+}
+
+function onMouseLeave(id: string) {
+  tilts.value[id] = { x: 0, y: 0 };
+}
+
+function tiltStyle(id: string) {
+  const t = tilts.value[id] ?? { x: 0, y: 0 };
+  return {
+    transform: `perspective(600px) rotateX(${t.y}deg) rotateY(${t.x}deg)`,
+    transition: t.x === 0 && t.y === 0 ? 'transform 0.5s ease' : 'transform 0.1s ease',
+  };
+}
+
+/** 鍑哄嫟鐜囧睍绀猴細鏃犳暟鎹敤銆屸�斻�� */
+const attendanceRateText = computed(() =>
+  attendanceRate.value === null ? '鈥�' : attendanceRate.value.toFixed(1),
+);
 
 onMounted(() => {
   loadAttendance();
@@ -105,138 +150,172 @@
 </script>
 
 <template>
-  <Row :gutter="[16, 16]">
-    <!-- ============ 鍑哄嫟鎸囨爣 ============ -->
-    <Col v-if="canViewHr" :xs="24" :sm="24" :md="24" :lg="12" :xl="12" :xxl="12">
-      <div class="glass-card h-full overflow-hidden rounded-2xl p-5">
-        <div class="mb-4 flex items-center justify-between">
-          <div class="flex items-center gap-2">
-            <div class="flex size-9 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500 to-purple-400 shadow-md shadow-violet-500/25">
-              <IconifyIcon class="size-5 text-white" icon="lucide:user-check" />
-            </div>
-            <div>
-              <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">鍑哄嫟鎸囨爣</h3>
-              <p class="text-xs text-gray-400">鏈湀鑰冨嫟姹囨��</p>
-            </div>
+  <Row :gutter="16">
+    <!-- 鏈湀鍑哄嫟鐜� -->
+    <Col v-if="canViewAttendance" :xs="24" :sm="12" :md="8" :lg="8" :xl="6" :xxl="6" class="mb-4">
+      <div
+        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0"
+        :style="tiltStyle('attendance')"
+        @mousemove="(e: MouseEvent) => onMouseMove(e, 'attendance')"
+        @mouseleave="onMouseLeave('attendance')"
+      >
+        <div class="relative z-10 flex items-center gap-4 p-5">
+          <div
+            class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-violet-500 to-purple-400 shadow-lg shadow-violet-500/25 transition-transform duration-300 group-hover:scale-110"
+          >
+            <IconifyIcon class="size-6 text-white" icon="lucide:user-check" />
           </div>
-          <a-spin v-if="hrLoading" :size="16" />
-        </div>
-
-        <div class="mb-4 flex items-center gap-5">
-          <Progress
-            type="circle"
-            :percent="attendanceRate"
-            :size="88"
-            :stroke-color="{ '0%': '#8b5cf6', '100%': '#c084fc' }"
-          />
           <div class="min-w-0 flex-1">
-            <div class="text-xs text-gray-500 dark:text-gray-400">鍑哄嫟鐜�</div>
-            <div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
-              鍦ㄥ唽 <span class="font-semibold text-violet-600 dark:text-violet-400">{{ headCount }}</span> 浜�
-              路 浜哄潎鍑哄嫟 <span class="font-semibold text-violet-600 dark:text-violet-400">{{ actualDaysAvg }}</span> 澶�
+            <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">鏈湀鍑哄嫟鐜�</div>
+            <div class="flex items-baseline gap-1">
+              <CountTo
+                v-if="attendanceRate !== null"
+                class="text-2xl font-bold leading-tight text-violet-600 dark:text-violet-400"
+                :end-val="attendanceRate"
+                :duration="1200"
+                :decimals="1"
+              />
+              <span
+                v-else
+                class="text-2xl font-bold leading-tight text-gray-400 dark:text-gray-500"
+                >{{ attendanceRateText }}</span
+              >
+              <span v-if="attendanceRate !== null" class="text-xs text-gray-400">%</span>
+            </div>
+            <div class="mt-1 flex items-center gap-1 text-xs text-gray-400">
+              <span class="inline-block size-1.5 rounded-full bg-violet-400" /> 鍦ㄥ唽 {{ headCount }}
+              <span class="mx-1">|</span>
+              <span class="inline-block size-1.5 rounded-full bg-emerald-400" /> 浜哄潎
+              {{ averageDays }} 澶�
             </div>
           </div>
         </div>
-
-        <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
-          <Tooltip title="鏈湀杩熷埌娆℃暟鍚堣">
-            <div class="rounded-xl bg-amber-50/70 p-3 text-center dark:bg-amber-500/10">
-              <div class="text-lg font-bold text-amber-600 dark:text-amber-400">
-                <CountTo :end-val="lateCount" :duration="1000" />
-              </div>
-              <div class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">杩熷埌</div>
-            </div>
-          </Tooltip>
-          <Tooltip title="鏈湀缂哄崱娆℃暟鍚堣">
-            <div class="rounded-xl bg-rose-50/70 p-3 text-center dark:bg-rose-500/10">
-              <div class="text-lg font-bold text-rose-600 dark:text-rose-400">
-                <CountTo :end-val="absentCount" :duration="1000" />
-              </div>
-              <div class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">缂哄崱</div>
-            </div>
-          </Tooltip>
-          <Tooltip title="鏈湀璇峰亣宸ユ椂鍚堣">
-            <div class="rounded-xl bg-sky-50/70 p-3 text-center dark:bg-sky-500/10">
-              <div class="text-lg font-bold text-sky-600 dark:text-sky-400">
-                <CountTo :end-val="leaveHours" :duration="1000" :decimals="1" />
-              </div>
-              <div class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">璇峰亣(h)</div>
-            </div>
-          </Tooltip>
-          <Tooltip title="浜哄潎鍑哄嫟澶╂暟">
-            <div class="rounded-xl bg-emerald-50/70 p-3 text-center dark:bg-emerald-500/10">
-              <div class="text-lg font-bold text-emerald-600 dark:text-emerald-400">
-                <CountTo :end-val="actualDaysAvg" :duration="1000" :decimals="1" />
-              </div>
-              <div class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">鍑哄嫟(澶�)</div>
-            </div>
-          </Tooltip>
-        </div>
+        <div
+          class="absolute inset-0 -z-0 bg-gradient-to-br from-violet-500/5 to-purple-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
+        />
+        <div
+          class="absolute -right-6 -top-6 size-24 rounded-full bg-violet-400/10 blur-xl transition-all duration-300 group-hover:scale-150"
+        />
       </div>
     </Col>
 
-    <!-- ============ 搴撳瓨閲囪喘 ============ -->
-    <Col v-if="canViewStock || canViewPurchase" :xs="24" :sm="24" :md="24" :lg="12" :xl="12" :xxl="12">
-      <div class="glass-card h-full overflow-hidden rounded-2xl p-5">
-        <div class="mb-4 flex items-center justify-between">
-          <div class="flex items-center gap-2">
-            <div class="flex size-9 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500 to-teal-400 shadow-md shadow-cyan-500/25">
-              <IconifyIcon class="size-5 text-white" icon="lucide:package" />
+    <!-- 鏈湀鑰冨嫟寮傚父 -->
+    <Col v-if="canViewAttendance" :xs="24" :sm="12" :md="8" :lg="8" :xl="6" :xxl="6" class="mb-4">
+      <div
+        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0 cursor-pointer"
+        :style="tiltStyle('attendance-abnormal')"
+        @mousemove="(e: MouseEvent) => onMouseMove(e, 'attendance-abnormal')"
+        @mouseleave="onMouseLeave('attendance-abnormal')"
+        @click="goto('HrmAttendanceRecord')"
+      >
+        <div class="relative z-10 flex items-center gap-4 p-5">
+          <div
+            class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-amber-500 to-orange-400 shadow-lg shadow-amber-500/25 transition-transform duration-300 group-hover:scale-110"
+          >
+            <IconifyIcon class="size-6 text-white" icon="lucide:alert-triangle" />
+          </div>
+          <div class="min-w-0 flex-1">
+            <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">鏈湀鑰冨嫟寮傚父</div>
+            <div class="flex items-baseline gap-1">
+              <CountTo
+                class="text-2xl font-bold leading-tight text-amber-600 dark:text-amber-400"
+                :end-val="abnormalCount"
+                :duration="1200"
+              />
+              <span class="text-xs text-gray-400">娆�</span>
             </div>
-            <div>
-              <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">搴撳瓨閲囪喘</h3>
-              <p class="text-xs text-gray-400">浠撳偍涓庨噰璐瑙�</p>
+            <div class="mt-1 flex items-center gap-1 text-xs text-gray-400">
+              <span class="inline-block size-1.5 rounded-full bg-amber-400" /> 杩熷埌 {{ lateCount }}
+              <span class="mx-1">|</span>
+              <span class="inline-block size-1.5 rounded-full bg-rose-400" /> 缂哄崱 {{ absentCount }}
             </div>
           </div>
-          <a-spin v-if="stockLoading" :size="16" />
         </div>
+        <div
+          class="absolute inset-0 -z-0 bg-gradient-to-br from-amber-500/5 to-orange-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
+        />
+        <div
+          class="absolute -right-6 -top-6 size-24 rounded-full bg-amber-400/10 blur-xl transition-all duration-300 group-hover:scale-150"
+        />
+      </div>
+    </Col>
 
-        <Row :gutter="[12, 12]">
-          <Col v-if="canViewStock" :xs="12" :sm="12" :md="12" :lg="12" :xl="12" class="cursor-pointer" @click="goto('MesWmMaterialStock')">
-            <div class="rounded-xl bg-cyan-50/70 p-4 transition hover:bg-cyan-100/70 dark:bg-cyan-500/10">
-              <div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
-                <IconifyIcon class="size-4 text-cyan-500" icon="lucide:boxes" /> 搴撳瓨鍝佺
-              </div>
-              <div class="mt-1 text-2xl font-bold text-cyan-600 dark:text-cyan-400">
-                <CountTo :end-val="stockKinds" :duration="1000" />
-              </div>
-              <div class="text-xs text-gray-400">绱鍦ㄥ簱鐗╂枡琛�</div>
+    <!-- 搴撳瓨鍙敤閲� -->
+    <Col v-if="canViewStock" :xs="24" :sm="12" :md="8" :lg="8" :xl="6" :xxl="6" class="mb-4">
+      <div
+        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0 cursor-pointer"
+        :style="tiltStyle('stock')"
+        @mousemove="(e: MouseEvent) => onMouseMove(e, 'stock')"
+        @mouseleave="onMouseLeave('stock')"
+        @click="goto('MesWmMaterialStock')"
+      >
+        <div class="relative z-10 flex items-center gap-4 p-5">
+          <div
+            class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500 to-teal-400 shadow-lg shadow-cyan-500/25 transition-transform duration-300 group-hover:scale-110"
+          >
+            <IconifyIcon class="size-6 text-white" icon="lucide:package" />
+          </div>
+          <div class="min-w-0 flex-1">
+            <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">搴撳瓨鍙敤閲�</div>
+            <div class="flex items-baseline gap-1">
+              <CountTo
+                class="text-2xl font-bold leading-tight text-cyan-600 dark:text-cyan-400"
+                :end-val="stockAvailable"
+                :duration="1200"
+              />
             </div>
-          </Col>
-          <Col v-if="canViewStock" :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
-            <div class="rounded-xl bg-teal-50/70 p-4 dark:bg-teal-500/10">
-              <div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
-                <IconifyIcon class="size-4 text-teal-500" icon="lucide:warehouse" /> 鍙敤鏁伴噺
-              </div>
-              <div class="mt-1 text-2xl font-bold text-teal-600 dark:text-teal-400">
-                <CountTo :end-val="stockAvailable" :duration="1000" />
-              </div>
-              <div class="text-xs text-gray-400">鎵i櫎鍐荤粨涓庡崰鐢�</div>
+            <div class="mt-1 flex items-center gap-1 text-xs text-gray-400">
+              <span class="inline-block size-1.5 rounded-full bg-cyan-400" /> 鍝佺 {{ stockSkuCount }}
+              <span class="mx-1">|</span> 鎵i櫎鍐荤粨鍗犵敤
             </div>
-          </Col>
-          <Col v-if="canViewPurchase" :xs="12" :sm="12" :md="12" :lg="12" :xl="12" class="cursor-pointer" @click="goto('ErpPurchaseOrder')">
-            <div class="rounded-xl bg-indigo-50/70 p-4 transition hover:bg-indigo-100/70 dark:bg-indigo-500/10">
-              <div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
-                <IconifyIcon class="size-4 text-indigo-500" icon="lucide:shopping-cart" /> 閲囪喘璁㈠崟
-              </div>
-              <div class="mt-1 text-2xl font-bold text-indigo-600 dark:text-indigo-400">
-                <CountTo :end-val="purchaseCount" :duration="1000" />
-              </div>
-              <div class="text-xs text-gray-400">绱璁㈠崟绗旀暟</div>
+          </div>
+        </div>
+        <div
+          class="absolute inset-0 -z-0 bg-gradient-to-br from-cyan-500/5 to-teal-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
+        />
+        <div
+          class="absolute -right-6 -top-6 size-24 rounded-full bg-cyan-400/10 blur-xl transition-all duration-300 group-hover:scale-150"
+        />
+      </div>
+    </Col>
+
+    <!-- 閲囪喘璁㈠崟 -->
+    <Col v-if="canViewPurchase" :xs="24" :sm="12" :md="8" :lg="8" :xl="6" :xxl="6" class="mb-4">
+      <div
+        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0 cursor-pointer"
+        :style="tiltStyle('purchase')"
+        @mousemove="(e: MouseEvent) => onMouseMove(e, 'purchase')"
+        @mouseleave="onMouseLeave('purchase')"
+        @click="goto('ErpPurchaseOrder')"
+      >
+        <div class="relative z-10 flex items-center gap-4 p-5">
+          <div
+            class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-blue-400 shadow-lg shadow-indigo-500/25 transition-transform duration-300 group-hover:scale-110"
+          >
+            <IconifyIcon class="size-6 text-white" icon="lucide:shopping-cart" />
+          </div>
+          <div class="min-w-0 flex-1">
+            <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">閲囪喘璁㈠崟</div>
+            <div class="flex items-baseline gap-1">
+              <CountTo
+                class="text-2xl font-bold leading-tight text-indigo-600 dark:text-indigo-400"
+                :end-val="purchaseTotal"
+                :duration="1200"
+              />
+              <span class="text-xs text-gray-400">绗�</span>
             </div>
-          </Col>
-          <Col v-if="canViewPurchase" :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
-            <div class="rounded-xl bg-orange-50/70 p-4 dark:bg-orange-500/10">
-              <div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
-                <IconifyIcon class="size-4 text-orange-500" icon="lucide:clock" /> 寰呭鎵�
-              </div>
-              <div class="mt-1 text-2xl font-bold text-orange-600 dark:text-orange-400">
-                <CountTo :end-val="purchasePending" :duration="1000" />
-              </div>
-              <div class="text-xs text-gray-400">瀹℃壒涓殑閲囪喘鍗�</div>
+            <div class="mt-1 flex items-center gap-1 text-xs text-gray-400">
+              <span class="inline-block size-1.5 rounded-full bg-orange-400" /> 寰呭鎵�
+              {{ purchasePending }}
             </div>
-          </Col>
-        </Row>
+          </div>
+        </div>
+        <div
+          class="absolute inset-0 -z-0 bg-gradient-to-br from-indigo-500/5 to-blue-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
+        />
+        <div
+          class="absolute -right-6 -top-6 size-24 rounded-full bg-indigo-400/10 blur-xl transition-all duration-300 group-hover:scale-150"
+        />
       </div>
     </Col>
   </Row>

--
Gitblit v1.9.3