Files
GEAR-OA/gear-ui3/src/views/components/Statistic.vue

247 lines
7.9 KiB
Vue
Raw Normal View History

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