205 lines
4.9 KiB
Vue
205 lines
4.9 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="dashboard-cards">
|
|||
|
|
<el-card v-for="(card, index) in dataCards" :key="index" class="stats-card">
|
|||
|
|
<div class="card-header">
|
|||
|
|
<div class="card-info">
|
|||
|
|
<h3 class="card-title">{{ card.title }}</h3>
|
|||
|
|
<!-- 第三个卡片不显示value -->
|
|||
|
|
<p class="card-value">{{ card.value }}</p>
|
|||
|
|
</div>
|
|||
|
|
<el-icon class="card-icon" :style="{ color: card.color }">
|
|||
|
|
<component :is="card.icon" />
|
|||
|
|
</el-icon>
|
|||
|
|
</div>
|
|||
|
|
<!-- 第四个卡片不显示图表 -->
|
|||
|
|
<div class="chart-container" v-if="index !== 3">
|
|||
|
|
<div :ref="el => chartRefs[index] = el" class="chart"></div>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ShoppingCart, Box, List, Monitor } from '@element-plus/icons-vue';
|
|||
|
|
import { overview } from '@/api/oa/dashboard';
|
|||
|
|
import * as echarts from 'echarts';
|
|||
|
|
import { ref, onMounted, nextTick } from 'vue';
|
|||
|
|
|
|||
|
|
const getColor = (index, alpha = 1) => {
|
|||
|
|
const colors = [
|
|||
|
|
`rgba(59, 130, 246, ${alpha})`,
|
|||
|
|
`rgba(34, 197, 94, ${alpha})`,
|
|||
|
|
`rgba(234, 179, 8, ${alpha})`,
|
|||
|
|
`rgba(168, 85, 247, ${alpha})`
|
|||
|
|
];
|
|||
|
|
return colors[index % colors.length];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const initCharts = () => {
|
|||
|
|
nextTick(() => {
|
|||
|
|
// 只初始化前3个卡片的图表
|
|||
|
|
dataCards.value.slice(0, 3).forEach((card, index) => {
|
|||
|
|
const chart = echarts.init(chartRefs.value[index]);
|
|||
|
|
// 判断是否为第三个图表(index=2),使用柱状图配置
|
|||
|
|
const isBarChart = index === 2;
|
|||
|
|
|
|||
|
|
const option = {
|
|||
|
|
animation: false,
|
|||
|
|
grid: {
|
|||
|
|
left: 0,
|
|||
|
|
right: 0,
|
|||
|
|
top: 0,
|
|||
|
|
bottom: 0
|
|||
|
|
},
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
show: false
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
show: false
|
|||
|
|
},
|
|||
|
|
series: [{
|
|||
|
|
data: card.chartData,
|
|||
|
|
type: isBarChart ? 'bar' : 'line', // 第三个图表用柱状图,其他用折线图
|
|||
|
|
smooth: !isBarChart, // 柱状图不需要平滑效果
|
|||
|
|
showSymbol: false,
|
|||
|
|
lineStyle: !isBarChart ? { // 折线图样式(柱状图不需要)
|
|||
|
|
color: getColor(index)
|
|||
|
|
} : undefined,
|
|||
|
|
// 柱状图颜色配置
|
|||
|
|
itemStyle: isBarChart ? {
|
|||
|
|
color: getColor(index)
|
|||
|
|
} : undefined,
|
|||
|
|
// 折线图区域填充(柱状图不需要)
|
|||
|
|
areaStyle: !isBarChart ? {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
|||
|
|
offset: 0,
|
|||
|
|
color: getColor(index, 0.2)
|
|||
|
|
}, {
|
|||
|
|
offset: 1,
|
|||
|
|
color: getColor(index, 0.1)
|
|||
|
|
}])
|
|||
|
|
} : undefined
|
|||
|
|
}]
|
|||
|
|
};
|
|||
|
|
chart.setOption(option);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
|
|||
|
|
const dataCards = ref([
|
|||
|
|
{
|
|||
|
|
title: '本周订单总量',
|
|||
|
|
value: '2,384',
|
|||
|
|
icon: ShoppingCart,
|
|||
|
|
color: '#3B82F6', // blue-500
|
|||
|
|
chartData: [30, 40, 20, 50, 40, 60, 70]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '本周薪资成本',
|
|||
|
|
value: '48',
|
|||
|
|
icon: List,
|
|||
|
|
color: '#F59E0B', // yellow-500
|
|||
|
|
chartData: [20, 40, 30, 50, 40, 60, 50]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '库存排行',
|
|||
|
|
// 第三个卡片不需要value
|
|||
|
|
value: '-',
|
|||
|
|
icon: Box,
|
|||
|
|
color: '#10B981', // green-500
|
|||
|
|
chartData: [40, 30, 50, 40, 60, 50, 70]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '系统状态',
|
|||
|
|
value: '正常',
|
|||
|
|
icon: Monitor,
|
|||
|
|
color: '#8B5CF6', // purple-500
|
|||
|
|
// 第四个卡片不需要图表数据
|
|||
|
|
chartData: []
|
|||
|
|
}
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const chartRefs = ref([]);
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
overview().then(res => {
|
|||
|
|
dataCards.value[0].value = res.data.orderStatistics.weekOrderCount;
|
|||
|
|
dataCards.value[0].chartData = res.data.orderStatistics.weeklyTrend.map(item => item.value);
|
|||
|
|
|
|||
|
|
dataCards.value[1].value = res.data.salaryStatistics.weekSalary;
|
|||
|
|
dataCards.value[1].chartData = res.data.salaryStatistics.weeklyTrend.map(item => item.value);
|
|||
|
|
|
|||
|
|
// 处理库存排行数据
|
|||
|
|
dataCards.value[2].chartData = res.data.stockRanking.map(item => item.quantity);
|
|||
|
|
|
|||
|
|
initCharts();
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss">
|
|||
|
|
.dashboard-cards {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(4, 1fr);
|
|||
|
|
gap: 1.5rem;
|
|||
|
|
margin-bottom: 2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.stats-card {
|
|||
|
|
border-radius: 0.5rem !important;
|
|||
|
|
backdrop-filter: blur(4px);
|
|||
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|||
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|||
|
|
transform: scale(1);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
|
|||
|
|
&:hover {
|
|||
|
|
transform: scale(1.02);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
margin-bottom: 1rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-info {
|
|||
|
|
// 用于包裹标题和值
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-title {
|
|||
|
|
color: #6B7280; // gray-500
|
|||
|
|
font-size: 0.875rem;
|
|||
|
|
margin-bottom: 0.25rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-value {
|
|||
|
|
font-size: 1.5rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-icon {
|
|||
|
|
font-size: 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.chart-container {
|
|||
|
|
height: 3rem;
|
|||
|
|
width: 100%;
|
|||
|
|
|
|||
|
|
.chart {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第四个卡片没有图表,调整一下底部边距
|
|||
|
|
.stats-card:nth-child(4) .card-header {
|
|||
|
|
margin-bottom: 0;
|
|||
|
|
}
|
|||
|
|
</style>
|