Files
fin_buddy_fe/src/Pages/Dashboard.vue
2026-06-25 22:39:06 +08:00

143 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, onMounted, computed } from "vue"; // 💡 補上 ref, onMounted
import axios from "axios"; // 💡 引入你的 axios 或全域 API 工具
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'
import { Pie, Bar } from 'vue-chartjs'
import {
Chart as ChartJS,
ArcElement,
Tooltip,
Legend,
CategoryScale,
LinearScale,
BarElement,
} from 'chart.js'
ChartJS.register(ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement)
// 🟢 響應式狀態,初始為預設值,防止載入中畫面崩潰
const monthlyTotal = ref(0)
const monthlyCount = ref(0)
const categoryStats = ref([])
const monthlyTrend = ref([])
const loading = ref(true)
// 圓餅圖資料
const pieData = computed(() => ({
labels: categoryStats.value.map(c => c.name),
datasets: [{
data: categoryStats.value.map(c => c.total),
backgroundColor: categoryStats.value.map(c => c.color),
}],
}))
const pieOptions = {
responsive: true,
plugins: {
legend: { position: 'bottom' },
},
}
// 長條圖資料
const barData = computed(() => ({
labels: monthlyTrend.value.map(m => m.month),
datasets: [{
label: '每月花費',
data: monthlyTrend.value.map(m => m.total),
backgroundColor: '#3B82F6',
borderRadius: 4,
}],
}))
const barOptions = {
responsive: true,
plugins: {
legend: { display: false },
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: (val) => '$' + val.toLocaleString(),
},
},
},
}
// 🟢 組件掛載時,主動去向後端 API 撈取統計數據
onMounted(async () => {
try {
// 🟢 修正:精準指定後端 Laragon 的完整 API 網址
const response = await axios.get('http://127.0.0.1:8000/api/dashboard/stats', {
// 如果你是用 localStorage 存 token 的話,要在這邊帶上 Bearer Token
headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}` // 💡 依據你儲存 Token 的 key 調整
},
withCredentials: true // 如果是用 Cookie/Sanctum 驗證則需要這行
})
monthlyTotal.value = response.data.monthlyTotal
monthlyCount.value = response.data.monthlyCount
categoryStats.value = response.data.categoryStats
monthlyTrend.value = response.data.monthlyTrend
} catch (error) {
console.error("無法加載總覽數據:", error)
} finally {
loading.value = false
}
})
</script>
<template>
<AuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold">總覽</h2>
</template>
<div class="py-8 max-w-5xl mx-auto px-4 space-y-6">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div class="bg-white rounded-lg shadow p-6">
<p class="text-sm text-gray-500">本月花費</p>
<p class="text-3xl font-bold text-blue-500 mt-1">
${{ monthlyTotal.toLocaleString() }}
</p>
</div>
<div class="bg-white rounded-lg shadow p-6">
<p class="text-sm text-gray-500">本月記帳筆數</p>
<p class="text-3xl font-bold text-gray-700 mt-1">
{{ monthlyCount }} <span class="text-lg font-normal text-gray-500"></span>
</p>
</div>
<div class="bg-white rounded-lg shadow p-6">
<p class="text-sm text-gray-500">最高花費分類</p>
<p class="text-3xl font-bold text-gray-700 mt-1">
{{ categoryStats.length > 0
? categoryStats.reduce((a, b) => a.total > b.total ? a : b).name
: '—' }}
</p>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium mb-4">本月分類佔比</h3>
<div v-if="categoryStats.length > 0" class="max-w-xs mx-auto">
<Pie :data="pieData" :options="pieOptions" />
</div>
<div v-else class="py-12 text-center text-gray-400">
本月還沒有記錄
</div>
</div>
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium mb-4">近6個月趨勢</h3>
<Bar :data="barData" :options="barOptions" />
</div>
</div>
</div>
</AuthenticatedLayout>
</template>