281 lines
9.9 KiB
Vue
281 lines
9.9 KiB
Vue
<template>
|
|
<div class="dashboard">
|
|
<!-- 车辆统计卡片 -->
|
|
<el-row :gutter="20" class="stats-row">
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: #e6f7ff;">
|
|
<span style="color: #1890ff; font-size: 24px;">📦</span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">{{ stats.totalVehicles }}</div>
|
|
<div class="stat-label">车辆总数</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: #f6ffed;">
|
|
<span style="color: #52c41a; font-size: 24px;">🚗</span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">{{ stats.rentedVehicles }}</div>
|
|
<div class="stat-label">出租中</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: #fff7e6;">
|
|
<span style="color: #fa8c16; font-size: 24px;">✅</span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">{{ stats.availableVehicles }}</div>
|
|
<div class="stat-label">空闲车辆</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: #fff1f0;">
|
|
<span style="color: #f5222d; font-size: 24px;">🔧</span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">{{ stats.maintenanceVehicles }}</div>
|
|
<div class="stat-label">维修中</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<!-- 财务统计 - 收支明细模式 -->
|
|
<el-row :gutter="20" style="margin-top: 20px;">
|
|
<el-col :span="6">
|
|
<el-card class="finance-card income">
|
|
<template #header><span>💰 总收入</span></template>
|
|
<div class="finance-value">¥{{ financeStats.totalIncome.toLocaleString() }}</div>
|
|
<div class="finance-detail">收入 {{ financeStats.incomeCount }} 笔</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<el-card class="finance-card expense">
|
|
<template #header><span>💸 总支出</span></template>
|
|
<div class="finance-value">¥{{ financeStats.totalExpense.toLocaleString() }}</div>
|
|
<div class="finance-detail">支出 {{ financeStats.expenseCount }} 笔</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<el-card class="finance-card balance" :class="{ positive: financeStats.balance >= 0, negative: financeStats.balance < 0 }">
|
|
<template #header><span>📊 结余</span></template>
|
|
<div class="finance-value">¥{{ financeStats.balance.toLocaleString() }}</div>
|
|
<div class="finance-detail">{{ financeStats.balance >= 0 ? '盈利' : '亏损' }}</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<el-card class="finance-card">
|
|
<template #header><span>📋 收支记录</span></template>
|
|
<div class="finance-value">{{ financeStats.totalCount }}</div>
|
|
<div class="finance-detail">总收入 {{ financeStats.totalCount }} 笔</div>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<!-- 图表 -->
|
|
<el-row :gutter="20" style="margin-top: 20px;">
|
|
<el-col :span="12">
|
|
<el-card>
|
|
<template #header><span>📊 收支分布</span></template>
|
|
<div ref="pieChartRef" style="width: 100%; height: 300px;"></div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-card>
|
|
<template #header><span>📈 收支对比</span></template>
|
|
<div ref="barChartRef" style="width: 100%; height: 300px;"></div>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<!-- 最近收支明细 -->
|
|
<el-card style="margin-top: 20px;">
|
|
<template #header>
|
|
<span>📋 最近收支明细</span>
|
|
</template>
|
|
<el-table :data="recentPayments" stripe size="small">
|
|
<el-table-column label="类型" width="80">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.type === 'income' ? 'success' : 'danger'" size="small">
|
|
{{ row.type === 'income' ? '收入' : '支出' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="party" label="对方" width="120" />
|
|
<el-table-column prop="amount" label="金额" width="100">
|
|
<template #default="{ row }">
|
|
<span :style="{ color: row.type === 'income' ? '#52c41a' : '#f5222d', fontWeight: 'bold' }">
|
|
{{ row.type === 'income' ? '+' : '-' }}¥{{ row.amount }}
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="method" label="方式" width="80" />
|
|
<el-table-column prop="category" label="分类" width="100" />
|
|
<el-table-column prop="remark" label="备注" />
|
|
<el-table-column prop="createdAt" label="时间" width="170">
|
|
<template #default="{ row }">
|
|
{{ new Date(row.createdAt).toLocaleString() }}
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-card>
|
|
|
|
<!-- 快捷操作 -->
|
|
<el-row :gutter="20" style="margin-top: 20px;">
|
|
<el-col :span="24">
|
|
<el-card>
|
|
<template #header><span>⚡ 快捷操作</span></template>
|
|
<el-button type="primary" @click="$router.push('/finance')">💰 财务管理</el-button>
|
|
<el-button type="success" @click="$router.push('/vehicles')">🚗 车辆管理</el-button>
|
|
<el-button type="warning" @click="$router.push('/orders')">📋 订单管理</el-button>
|
|
<el-button type="info" @click="$router.push('/customers')">👤 客户管理</el-button>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, nextTick } from 'vue'
|
|
import * as echarts from 'echarts'
|
|
|
|
const stats = ref({
|
|
totalVehicles: 0,
|
|
rentedVehicles: 0,
|
|
availableVehicles: 0,
|
|
maintenanceVehicles: 0,
|
|
todayIncome: 0,
|
|
monthIncome: 0,
|
|
totalIncome: 0,
|
|
pendingPayment: 0
|
|
})
|
|
|
|
const financeStats = ref({
|
|
totalIncome: 0,
|
|
totalExpense: 0,
|
|
balance: 0,
|
|
incomeCount: 0,
|
|
expenseCount: 0,
|
|
totalCount: 0
|
|
})
|
|
|
|
const recentPayments = ref([])
|
|
const pieChartRef = ref(null)
|
|
const barChartRef = ref(null)
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
// 加载车辆数据
|
|
const vehicleRes = await fetch('http://localhost:3000/api/vehicles')
|
|
const vehicleData = await vehicleRes.json()
|
|
if (vehicleData.success) {
|
|
const vehicles = vehicleData.data
|
|
stats.value.totalVehicles = vehicles.length
|
|
stats.value.availableVehicles = vehicles.filter(v => v.status === '空闲').length
|
|
stats.value.rentedVehicles = vehicles.filter(v => v.status === '出租中' || v.status === '在租').length
|
|
stats.value.maintenanceVehicles = vehicles.filter(v => v.status === '维修中').length
|
|
}
|
|
|
|
// 加载财务数据
|
|
const financeRes = await fetch('http://localhost:3000/api/finance')
|
|
const financeData = await financeRes.json()
|
|
if (financeData.success) {
|
|
const list = financeData.data.list || []
|
|
const summary = financeData.data.summary || {}
|
|
|
|
financeStats.value.totalIncome = summary.totalIncome || 0
|
|
financeStats.value.totalExpense = summary.totalExpense || 0
|
|
financeStats.value.balance = summary.balance || 0
|
|
financeStats.value.totalCount = list.length
|
|
financeStats.value.incomeCount = list.filter(p => p.type === 'income').length
|
|
financeStats.value.expenseCount = list.filter(p => p.type === 'expense').length
|
|
|
|
// 最近5笔
|
|
recentPayments.value = list.slice(0, 5)
|
|
}
|
|
|
|
nextTick(() => {
|
|
initCharts()
|
|
})
|
|
} catch (e) {
|
|
console.error('加载失败:', e)
|
|
}
|
|
}
|
|
|
|
const initCharts = () => {
|
|
if (pieChartRef.value) {
|
|
const pieChart = echarts.init(pieChartRef.value)
|
|
pieChart.setOption({
|
|
tooltip: { trigger: 'item' },
|
|
series: [{
|
|
type: 'pie',
|
|
radius: ['40%', '70%'],
|
|
data: [
|
|
{ value: financeStats.value.totalIncome, name: '收入', itemStyle: { color: '#52c41a' } },
|
|
{ value: financeStats.value.totalExpense, name: '支出', itemStyle: { color: '#f5222d' } }
|
|
]
|
|
}]
|
|
})
|
|
}
|
|
|
|
if (barChartRef.value) {
|
|
const barChart = echarts.init(barChartRef.value)
|
|
barChart.setOption({
|
|
tooltip: { trigger: 'axis' },
|
|
xAxis: { type: 'category', data: ['收入', '支出', '结余'] },
|
|
yAxis: { type: 'value' },
|
|
series: [{
|
|
type: 'bar',
|
|
data: [
|
|
financeStats.value.totalIncome,
|
|
financeStats.value.totalExpense,
|
|
financeStats.value.balance
|
|
],
|
|
itemStyle: {
|
|
color: (params) => ['#52c41a', '#f5222d', financeStats.value.balance >= 0 ? '#1890ff' : '#f5222d'][params.dataIndex]
|
|
}
|
|
}]
|
|
})
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadData()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dashboard { padding: 20px; }
|
|
.stats-row { margin-bottom: 20px; }
|
|
.stat-card {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20px;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
.stat-icon { margin-right: 15px; }
|
|
.stat-value { font-size: 28px; font-weight: bold; color: #333; }
|
|
.stat-label { color: #999; font-size: 14px; }
|
|
.finance-card { text-align: center; padding: 10px; }
|
|
.finance-card.income { border-top: 3px solid #52c41a; }
|
|
.finance-card.expense { border-top: 3px solid #f5222d; }
|
|
.finance-card.balance { border-top: 3px solid #1890ff; }
|
|
.finance-card.balance.negative { border-top-color: #f5222d; }
|
|
.finance-value { font-size: 24px; font-weight: bold; margin: 10px 0; }
|
|
.finance-card.income .finance-value { color: #52c41a; }
|
|
.finance-card.expense .finance-value { color: #f5222d; }
|
|
.finance-card.balance .finance-value { color: #1890ff; }
|
|
.finance-card.balance.negative .finance-value { color: #f5222d; }
|
|
.finance-detail { color: #999; font-size: 12px; }
|
|
</style>
|