e-scooter-rental-web/src/views/Dashboard.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>