feat: 数据统计分析模块前端实现
前端实现:
- dashboard.js: API接口封装(13行)
- dashboard/index.vue: 仪表盘页面(576行)
页面布局:
1. 概览卡片(4个)
- 项目总数(含本月新增)
- 客户总数(含本月新增)
- 合同总数
- 需求工单数
2. 收支概览(3个)
- 总收入(含本月收入)
- 总支出(含本月支出)
- 净利润(含利润率)
3. 应收款概览(3个)
- 应收款总额
- 待收款金额(橙色警示)
- 逾期金额(红色警示)
4. 图表区域(4个)
- 收支趋势折线图(最近12个月)
- 项目状态分布饼图
- 支出类型分布饼图
- 应收款状态分布饼图
技术特点:
- ECharts图表库:折线图、环形饼图
- 响应式设计:窗口大小变化自动重绘
- 渐变色卡片:现代化UI设计
- 金额格式化:千分位分隔、保留2位小数
- 图表tooltip:金额/百分比格式化显示
- 组件销毁时清理资源
模块状态:✅ 完整(前端+后端)
This commit is contained in:
parent
39577a9b11
commit
81e919ad3c
12
fund-admin/src/api/dashboard.js
Normal file
12
fund-admin/src/api/dashboard.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import request from '../utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取仪表盘统计数据
|
||||||
|
*/
|
||||||
|
export const getDashboardData = (params) => {
|
||||||
|
return request({
|
||||||
|
url: '/proj/api/v1/dashboard',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,77 +1,139 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard-container">
|
||||||
<el-row :gutter="20">
|
<!-- 概览卡片 -->
|
||||||
|
<el-row :gutter="20" class="overview-cards">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-card class="stat-card">
|
<el-card shadow="hover" class="stat-card">
|
||||||
<div class="stat-icon" style="background: #409EFF;">
|
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||||
<el-icon><User /></el-icon>
|
<el-icon size="28"><Folder /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">1,234</div>
|
<div class="stat-value">{{ data.projectCount || 0 }}</div>
|
||||||
<div class="stat-label">客户总数</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card class="stat-card">
|
|
||||||
<div class="stat-icon" style="background: #67C23A;">
|
|
||||||
<el-icon><FolderOpened /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="stat-info">
|
|
||||||
<div class="stat-value">56</div>
|
|
||||||
<div class="stat-label">项目总数</div>
|
<div class="stat-label">项目总数</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-extra">本月新增 +{{ data.monthNewProjects || 0 }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-card class="stat-card">
|
<el-card shadow="hover" class="stat-card">
|
||||||
<div class="stat-icon" style="background: #E6A23C;">
|
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||||
<el-icon><Document /></el-icon>
|
<el-icon size="28"><User /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">89</div>
|
<div class="stat-value">{{ data.customerCount || 0 }}</div>
|
||||||
|
<div class="stat-label">客户总数</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-extra">本月新增 +{{ data.monthNewCustomers || 0 }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card shadow="hover" class="stat-card">
|
||||||
|
<div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
||||||
|
<el-icon size="28"><Document /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-value">{{ data.contractCount || 0 }}</div>
|
||||||
<div class="stat-label">合同总数</div>
|
<div class="stat-label">合同总数</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-card class="stat-card">
|
<el-card shadow="hover" class="stat-card">
|
||||||
<div class="stat-icon" style="background: #F56C6C;">
|
<div class="stat-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
|
||||||
<el-icon><Money /></el-icon>
|
<el-icon size="28"><Tickets /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">¥2.5M</div>
|
<div class="stat-value">{{ data.requirementCount || 0 }}</div>
|
||||||
<div class="stat-label">合同总金额</div>
|
<div class="stat-label">需求工单</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="20" class="mt-20">
|
<!-- 收支概览 -->
|
||||||
<el-col :span="12">
|
<el-row :gutter="20" class="finance-cards">
|
||||||
<el-card>
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="finance-card income">
|
||||||
|
<div class="finance-title">总收入</div>
|
||||||
|
<div class="finance-value">¥ {{ formatMoney(data.totalIncome) }}</div>
|
||||||
|
<div class="finance-sub">本月收入:¥ {{ formatMoney(data.monthIncome) }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="finance-card expense">
|
||||||
|
<div class="finance-title">总支出</div>
|
||||||
|
<div class="finance-value">¥ {{ formatMoney(data.totalExpense) }}</div>
|
||||||
|
<div class="finance-sub">本月支出:¥ {{ formatMoney(data.monthExpense) }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="finance-card profit">
|
||||||
|
<div class="finance-title">净利润</div>
|
||||||
|
<div class="finance-value" :class="{ negative: (data.netProfit || 0) < 0 }">
|
||||||
|
¥ {{ formatMoney(data.netProfit) }}
|
||||||
|
</div>
|
||||||
|
<div class="finance-sub">利润率:{{ getProfitRate() }}%</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 应收款概览 -->
|
||||||
|
<el-row :gutter="20" class="receivable-cards">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="receivable-card">
|
||||||
|
<div class="receivable-title">应收款总额</div>
|
||||||
|
<div class="receivable-value">¥ {{ formatMoney(data.totalReceivable) }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="receivable-card warning">
|
||||||
|
<div class="receivable-title">待收款金额</div>
|
||||||
|
<div class="receivable-value">¥ {{ formatMoney(data.totalUnpaid) }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="receivable-card danger">
|
||||||
|
<div class="receivable-title">逾期金额</div>
|
||||||
|
<div class="receivable-value">¥ {{ formatMoney(data.overdueAmount) }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 图表区域 -->
|
||||||
|
<el-row :gutter="20" class="chart-row">
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<span>收支趋势(最近12个月)</span>
|
||||||
|
</template>
|
||||||
|
<div ref="trendChartRef" class="chart-container"></div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>项目状态分布</span>
|
<span>项目状态分布</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="chart-placeholder">
|
<div ref="projectPieRef" class="chart-container"></div>
|
||||||
[图表区域]
|
</el-card>
|
||||||
</div>
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20" class="chart-row">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<span>支出类型分布</span>
|
||||||
|
</template>
|
||||||
|
<div ref="expensePieRef" class="chart-container"></div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card>
|
<el-card shadow="hover">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>最近活动</span>
|
<span>应收款状态分布</span>
|
||||||
</template>
|
</template>
|
||||||
<el-timeline>
|
<div ref="receivablePieRef" class="chart-container"></div>
|
||||||
<el-timeline-item
|
|
||||||
v-for="(activity, index) in activities"
|
|
||||||
:key="index"
|
|
||||||
:timestamp="activity.time"
|
|
||||||
>
|
|
||||||
{{ activity.content }}
|
|
||||||
</el-timeline-item>
|
|
||||||
</el-timeline>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -79,65 +141,498 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { User, FolderOpened, Document, Money } from '@element-plus/icons-vue'
|
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
import { Folder, User, Document, Tickets } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { getDashboardData } from '../../api/dashboard'
|
||||||
|
|
||||||
const activities = [
|
// 统计数据
|
||||||
{ content: '创建了新项目 "XX企业资金管理系统"', time: '2026-02-15 10:30' },
|
const data = reactive({
|
||||||
{ content: '客户 "ABC科技" 信息更新', time: '2026-02-15 09:15' },
|
projectCount: 0,
|
||||||
{ content: '合同 "HT2024001" 签署完成', time: '2026-02-14 16:45' },
|
customerCount: 0,
|
||||||
{ content: '新增用户 "张三"', time: '2026-02-14 14:20' }
|
contractCount: 0,
|
||||||
]
|
requirementCount: 0,
|
||||||
|
totalIncome: 0,
|
||||||
|
totalExpense: 0,
|
||||||
|
netProfit: 0,
|
||||||
|
totalReceivable: 0,
|
||||||
|
totalUnpaid: 0,
|
||||||
|
overdueAmount: 0,
|
||||||
|
monthIncome: 0,
|
||||||
|
monthExpense: 0,
|
||||||
|
monthNewProjects: 0,
|
||||||
|
monthNewCustomers: 0,
|
||||||
|
incomeTrend: [],
|
||||||
|
expenseTrend: [],
|
||||||
|
projectStatusDistribution: [],
|
||||||
|
expenseTypeDistribution: [],
|
||||||
|
receivableStatusDistribution: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图表引用
|
||||||
|
const trendChartRef = ref(null)
|
||||||
|
const projectPieRef = ref(null)
|
||||||
|
const expensePieRef = ref(null)
|
||||||
|
const receivablePieRef = ref(null)
|
||||||
|
|
||||||
|
// 图表实例
|
||||||
|
let trendChart = null
|
||||||
|
let projectPieChart = null
|
||||||
|
let expensePieChart = null
|
||||||
|
let receivablePieChart = null
|
||||||
|
|
||||||
|
// 格式化金额
|
||||||
|
const formatMoney = (value) => {
|
||||||
|
if (value === null || value === undefined) return '0.00'
|
||||||
|
return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算利润率
|
||||||
|
const getProfitRate = () => {
|
||||||
|
const income = data.totalIncome || 0
|
||||||
|
if (income === 0) return '0.00'
|
||||||
|
return ((data.netProfit / income) * 100).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getDashboardData()
|
||||||
|
Object.assign(data, res)
|
||||||
|
|
||||||
|
// 渲染图表
|
||||||
|
await nextTick()
|
||||||
|
renderTrendChart()
|
||||||
|
renderProjectPieChart()
|
||||||
|
renderExpensePieChart()
|
||||||
|
renderReceivablePieChart()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载仪表盘数据失败:', error)
|
||||||
|
ElMessage.error(error.message || '加载数据失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染收支趋势图
|
||||||
|
const renderTrendChart = () => {
|
||||||
|
if (!trendChartRef.value) return
|
||||||
|
|
||||||
|
if (trendChart) {
|
||||||
|
trendChart.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
trendChart = echarts.init(trendChartRef.value)
|
||||||
|
|
||||||
|
const months = data.incomeTrend?.map(item => item.month) || []
|
||||||
|
const incomeData = data.incomeTrend?.map(item => item.amount || 0) || []
|
||||||
|
const expenseData = data.expenseTrend?.map(item => item.amount || 0) || []
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params) => {
|
||||||
|
let result = params[0].axisValue + '<br/>'
|
||||||
|
params.forEach(param => {
|
||||||
|
result += `${param.marker} ${param.seriesName}: ¥${formatMoney(param.value)}<br/>`
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['收入', '支出'],
|
||||||
|
bottom: 0
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '15%',
|
||||||
|
top: '10%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: months,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value) => {
|
||||||
|
if (value >= 10000) return (value / 10000) + 'w'
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '收入',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
data: incomeData,
|
||||||
|
itemStyle: { color: '#67c23a' },
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(103, 194, 58, 0.3)' },
|
||||||
|
{ offset: 1, color: 'rgba(103, 194, 58, 0.05)' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '支出',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
data: expenseData,
|
||||||
|
itemStyle: { color: '#f56c6c' },
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(245, 108, 108, 0.3)' },
|
||||||
|
{ offset: 1, color: 'rgba(245, 108, 108, 0.05)' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
trendChart.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染项目状态分布图
|
||||||
|
const renderProjectPieChart = () => {
|
||||||
|
if (!projectPieRef.value) return
|
||||||
|
|
||||||
|
if (projectPieChart) {
|
||||||
|
projectPieChart.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPieChart = echarts.init(projectPieRef.value)
|
||||||
|
|
||||||
|
const pieData = data.projectStatusDistribution?.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.count || 0
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b}: {c} ({d}%)'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 10,
|
||||||
|
top: 'center'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['40%', '70%'],
|
||||||
|
center: ['40%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: pieData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
projectPieChart.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染支出类型分布图
|
||||||
|
const renderExpensePieChart = () => {
|
||||||
|
if (!expensePieRef.value) return
|
||||||
|
|
||||||
|
if (expensePieChart) {
|
||||||
|
expensePieChart.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
expensePieChart = echarts.init(expensePieRef.value)
|
||||||
|
|
||||||
|
const pieData = data.expenseTypeDistribution?.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.amount || 0
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: (params) => {
|
||||||
|
return `${params.name}: ¥${formatMoney(params.value)} (${params.percent}%)`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 10,
|
||||||
|
top: 'center'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['40%', '70%'],
|
||||||
|
center: ['40%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: pieData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
expensePieChart.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染应收款状态分布图
|
||||||
|
const renderReceivablePieChart = () => {
|
||||||
|
if (!receivablePieRef.value) return
|
||||||
|
|
||||||
|
if (receivablePieChart) {
|
||||||
|
receivablePieChart.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
receivablePieChart = echarts.init(receivablePieRef.value)
|
||||||
|
|
||||||
|
const pieData = data.receivableStatusDistribution?.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.amount || 0
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: (params) => {
|
||||||
|
return `${params.name}: ¥${formatMoney(params.value)} (${params.percent}%)`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 10,
|
||||||
|
top: 'center'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['40%', '70%'],
|
||||||
|
center: ['40%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: pieData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
receivablePieChart.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口大小变化时重绘图表
|
||||||
|
const handleResize = () => {
|
||||||
|
trendChart?.resize()
|
||||||
|
projectPieChart?.resize()
|
||||||
|
expensePieChart?.resize()
|
||||||
|
receivablePieChart?.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
fetchData()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
trendChart?.dispose()
|
||||||
|
projectPieChart?.dispose()
|
||||||
|
expensePieChart?.dispose()
|
||||||
|
receivablePieChart?.dispose()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.dashboard {
|
.dashboard-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 概览卡片 */
|
||||||
|
.overview-cards {
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
|
position: relative;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card :deep(.el-card__body) {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 28px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-info {
|
.stat-info {
|
||||||
margin-left: 15px;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 24px;
|
font-size: 28px;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
margin-top: 5px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-20 {
|
.stat-extra {
|
||||||
margin-top: 20px;
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #67c23a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-placeholder {
|
/* 收支卡片 */
|
||||||
height: 300px;
|
.finance-cards {
|
||||||
display: flex;
|
margin-bottom: 20px;
|
||||||
align-items: center;
|
}
|
||||||
justify-content: center;
|
|
||||||
background: #f5f7fa;
|
.finance-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-card :deep(.el-card__body) {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-value {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-value.negative {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-sub {
|
||||||
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.finance-card.income .finance-value {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-card.expense .finance-value {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-card.profit .finance-value {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 应收款卡片 */
|
||||||
|
.receivable-cards {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-card :deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-card.warning .receivable-value {
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receivable-card.danger .receivable-value {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表区域 */
|
||||||
|
.chart-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user