Files
GEAR-OA/gear-ui3/src/views/components/Statistic.vue
2025-09-18 16:22:49 +08:00

247 lines
7.9 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.

<template>
<div class="dashboard-cards">
<!-- 循环渲染4个统计卡片通过index控制特殊样式第三张无数值第四张无图表 -->
<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>
<!-- 第三张卡片库存排行不显示数值用v-if控制显隐 -->
<p class="card-value" v-if="index !== 2">{{ card.value }}</p>
</div>
<!-- 右侧图标通过动态组件渲染颜色绑定卡片配置 -->
<el-icon class="card-icon" :style="{ color: card.color }">
<component :is="card.icon" />
</el-icon>
</div>
<!-- 第四张卡片系统状态不显示图表用v-if控制显隐 -->
<div class="chart-container" v-if="index !== 3">
<div :ref="el => chartRefs[index] = el" class="chart"></div>
</div>
</el-card>
</div>
</template>
<script setup>
// 1. 导入依赖图标、接口、echarts、Vue工具函数
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';
// 2. 图表颜色生成函数:支持透明度,适配折线图区域填充
const getColor = (index, alpha = 1) => {
const colors = [
`rgba(59, 130, 246, ${alpha})`, // 蓝色(订单总量)
`rgba(245, 158, 11, ${alpha})`, // 黄色(薪资成本)
`rgba(34, 197, 94, ${alpha})`, // 绿色(库存排行)
`rgba(168, 85, 247, ${alpha})` // 紫色(系统状态)
];
return colors[index % colors.length];
};
// 3. 初始化图表函数仅处理前3张有图表的卡片避免空引用
const initCharts = () => {
nextTick(() => {
dataCards.value.slice(0, 3).forEach((card, index) => {
const chartDom = chartRefs.value[index];
if (!chartDom) return; // 防止DOM未渲染导致报错
const chart = echarts.init(chartDom);
const isBarChart = index === 2; // 第三张用柱状图,其余用折线图
// 图表配置:紧凑化优化(隐藏坐标轴、压缩网格、调整柱子宽度)
const option = {
animation: false, // 关闭动画提升性能
grid: { left: 0, right: 0, top: 0, bottom: 0 }, // 占满容器
xAxis: { type: 'category', show: false }, // 隐藏X轴
yAxis: { type: 'value', show: false }, // 隐藏Y轴
series: [{
data: card.chartData,
type: isBarChart ? 'bar' : 'line',
smooth: !isBarChart, // 折线图平滑,柱状图不需要
showSymbol: false, // 隐藏数据点
// 折线图样式:细线更紧凑
lineStyle: !isBarChart ? { color: getColor(index), width: 1.5 } : undefined,
// 柱状图样式:调整宽度+小圆角
itemStyle: isBarChart ? { color: getColor(index), borderRadius: 2 } : undefined,
barWidth: isBarChart ? '65%' : undefined, // 柱子占比65%,减少间距
// 折线图区域填充:增强视觉不占空间
areaStyle: !isBarChart ? {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: getColor(index, 0.2) },
{ offset: 1, color: getColor(index, 0.05) }
])
} : undefined
}]
};
chart.setOption(option);
// 监听窗口resize确保图表自适应
window.addEventListener('resize', () => chart.resize());
});
});
};
// 4. 卡片数据初始化:默认值+接口待填充字段
const dataCards = ref([
{
title: '本周订单总量',
value: '2,384',
icon: ShoppingCart,
color: '#3B82F6',
chartData: [30, 40, 20, 50, 40, 60, 70]
},
{
title: '本周薪资成本',
value: '48',
icon: List,
color: '#F59E0B',
chartData: [20, 40, 30, 50, 40, 60, 50]
},
{
title: '库存排行',
value: '-', // 第三张卡片默认隐藏数值
icon: Box,
color: '#10B981',
chartData: [40, 30, 50, 40, 60, 50, 70]
},
{
title: '系统状态',
value: '正常',
icon: Monitor,
color: '#8B5CF6',
chartData: [] // 第四张卡片无图表数据
}
]);
// 5. 图表DOM引用存储前3张卡片的图表容器
const chartRefs = ref([]);
// 6. 页面挂载:请求接口数据+初始化图表
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();
})
.catch(err => {
console.error('仪表盘数据请求失败:', err);
// 失败时仍初始化默认图表,避免页面空白
initCharts();
});
});
</script>
<style lang="scss" scoped>
// 1. 卡片容器:网格布局紧凑化
.dashboard-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem; // 间距从1.5rem减至0.75rem减少50%
margin-bottom: 1.25rem; // 底部外间距从2rem减至1.25rem
padding: 0 0.5rem; // 轻微左右内边距,避免贴边
}
// 2. 单个卡片:整体压缩
.stats-card {
border-radius: 0.375rem !important; // 圆角从0.5rem减至0.375rem
backdrop-filter: blur(4px);
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 0.75rem !important; // 内边距从1.5rem减至0.75rem减少50%
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); // 弱化阴影
transform: scale(1);
transition: all 0.2s ease; // 缩短过渡时间
// hover效果轻微放大不占用过多空间
&:hover {
transform: scale(1.01); // 放大比例从1.02减至1.01
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
}
// 3. 卡片头部:垂直压缩
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0.4rem; // 头部与图表间距从1rem减至0.4rem
}
// 4. 标题+数值容器:行高压缩
.card-info {
line-height: 1.15; // 减少垂直占用
}
// 5. 卡片标题:字体缩小+间距压缩
.card-title {
color: #6B7280;
font-size: 0.725rem; // 字体从0.875rem减至0.725rem
margin-bottom: 0.1rem; // 与数值间距从0.25rem减至0.1rem
font-weight: 500;
margin: 0 0 0.1rem 0; // 清除默认margin
}
// 6. 卡片数值:字体缩小+行高优化
.card-value {
font-size: 1.15rem; // 字体从1.5rem减至1.15rem
font-weight: 600;
line-height: 1.25; // 避免换行
color: #1F2937;
margin: 0; // 清除默认margin
}
// 7. 卡片图标:尺寸缩小+位置优化
.card-icon {
font-size: 1.15rem; // 图标从1.5rem减至1.15rem
margin-top: 0.05rem; // 轻微上移,与标题对齐
}
// 8. 图表容器:高度压缩
.chart-container {
height: 2.2rem; // 图表高度从3rem减至2.2rem减少27%
width: 100%;
margin-top: 0.15rem; // 顶部微小间距,避免贴边
overflow: hidden; // 防止图表溢出
}
// 9. 图表DOM占满容器
.chart {
width: 100%;
height: 100%;
}
// 10. 特殊卡片处理:第四张无图表,清除底部间距
.stats-card:nth-child(4) .card-header {
margin-bottom: 0;
}
// 11. 特殊卡片处理:第三张无数值,图标居中
.stats-card:nth-child(3) .card-header {
align-items: center; // 避免顶部留白
}
// 12. 响应式适配:小屏幕保持紧凑
@media (max-width: 1200px) {
.dashboard-cards {
gap: 0.6rem; // 进一步缩小间距
}
.card-title {
font-size: 0.7rem;
}
.card-value {
font-size: 1.1rem;
}
}
</style>