<template>
|
<div class="sales-statistics-container">
|
<div class="data-dashboard">
|
<!-- 页面标题 -->
|
<!-- <div class="dashboard-header">
|
<div class="factory-name">销售统计看板</div>
|
</div> -->
|
|
<!-- 筛选条件 -->
|
<div class="filter-area">
|
<div class="filter-section">
|
<span class="filter-label">时间范围:</span>
|
<el-date-picker
|
v-model="dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="YYYY-MM-DD"
|
@change="handleDateChange"
|
style="width: 240px;"
|
/>
|
</div>
|
<div class="filter-section">
|
<span class="filter-label">产品类型:</span>
|
<el-select v-model="productType" placeholder="请选择产品类型" @change="handleFilterChange" style="width: 160px;">
|
<el-option label="全部" value="" />
|
<el-option label="砌块" value="block" />
|
<el-option label="板材" value="board" />
|
<el-option label="型材" value="profile" />
|
</el-select>
|
</div>
|
<div class="filter-section">
|
<span class="filter-label">销售区域:</span>
|
<el-select v-model="salesArea" placeholder="请选择销售区域" @change="handleFilterChange" style="width: 160px;">
|
<el-option label="全部" value="" />
|
<el-option label="华东" value="east" />
|
<el-option label="华北" value="north" />
|
<el-option label="华南" value="south" />
|
<el-option label="西南" value="southwest" />
|
<el-option label="西北" value="northwest" />
|
</el-select>
|
</div>
|
<div class="filter-section">
|
<span class="filter-label">统计维度:</span>
|
<el-select v-model="statDimension" placeholder="请选择统计维度" @change="handleFilterChange" style="width: 160px;">
|
<el-option label="月度" value="month" />
|
<el-option label="年度" value="year" />
|
</el-select>
|
</div>
|
</div>
|
|
<div class="dashboard-content">
|
<!-- 核心指标卡片 -->
|
<div class="row row-1">
|
<div class="panel-card card-1">
|
<div class="panel-title">合计销量</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-value">{{ totalSalesVolume }}</div>
|
<div class="stat-unit">立方米</div>
|
<div class="stat-change">{{ salesVolumeChange }}%</div>
|
</div>
|
</div>
|
</div>
|
<div class="panel-card card-2">
|
<div class="panel-title">销售金额</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-value">{{ totalSalesAmount }}</div>
|
<div class="stat-unit">万元</div>
|
<div class="stat-change">{{ salesAmountChange }}%</div>
|
</div>
|
</div>
|
</div>
|
<div class="panel-card card-3">
|
<div class="panel-title">新增客户</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-value">{{ newCustomerCount }}</div>
|
<div class="stat-unit">个</div>
|
<div class="stat-change">{{ customerCountChange }}%</div>
|
</div>
|
</div>
|
</div>
|
<div class="panel-card card-4">
|
<div class="panel-title">合计客户</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-value">{{ totalCustomerCount }}</div>
|
<div class="stat-unit">个</div>
|
<div class="stat-change">{{ totalCustomerChange }}%</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 销量和销售金额趋势 -->
|
<div class="row row-2">
|
<div class="panel-card card-5">
|
<div class="panel-title">销量趋势</div>
|
<div class="chart-container">
|
<div ref="salesVolumeChart" style="width: 100%; height: 100%;"></div>
|
</div>
|
</div>
|
<div class="panel-card card-6">
|
<div class="panel-title">销售金额趋势</div>
|
<div class="chart-container">
|
<div ref="salesAmountChart" style="width: 100%; height: 100%;"></div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 累计数据趋势 -->
|
<div class="row row-3">
|
<div class="panel-card card-10">
|
<div class="panel-title">累计销量趋势</div>
|
<div class="chart-container">
|
<div ref="cumulativeSalesVolumeChart" style="width: 100%; height: 100%;"></div>
|
</div>
|
</div>
|
<div class="panel-card card-11">
|
<div class="panel-title">累计销售金额趋势</div>
|
<div class="chart-container">
|
<div ref="cumulativeSalesAmountChart" style="width: 100%; height: 100%;"></div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 图表区域和表格 -->
|
<div class="row row-4">
|
<!-- 左边:详细数据表格 -->
|
<div class="panel-card card-9" style="flex: 2;">
|
<div class="panel-title">销售统计详细数据</div>
|
<div class="table-container">
|
<el-table :data="tableData" style="width: 100%">
|
<el-table-column prop="productType" label="产品类型" width="120" />
|
<el-table-column prop="salesArea" label="销售区域" width="120" />
|
<el-table-column prop="period" label="统计周期" width="120" />
|
<el-table-column prop="salesVolume" label="销量(立方米)" />
|
<el-table-column prop="salesAmount" label="销售金额(万元)" />
|
<el-table-column prop="newCustomers" label="新增客户(个)" width="150" />
|
<el-table-column prop="totalCustomers" label="合计客户(个)" width="150" />
|
</el-table>
|
</div>
|
</div>
|
|
<!-- 右边:产品类型分布和销售区域分布 -->
|
<div class="chart-column" style="flex: 1; display: flex; flex-direction: column; gap: 20px;">
|
<div class="panel-card card-7" style="flex: 1;">
|
<div class="panel-title">产品类型分布</div>
|
<div class="chart-container">
|
<div ref="productTypeChart" style="width: 100%; height: 100%;"></div>
|
</div>
|
</div>
|
<div class="panel-card card-8" style="flex: 1;">
|
<div class="panel-title">销售区域分布</div>
|
<div class="chart-container">
|
<div ref="salesAreaChart" style="width: 100%; height: 100%;"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
|
import { useRouter } from 'vue-router';
|
import * as echarts from 'echarts';
|
import dayjs from 'dayjs';
|
|
const router = useRouter();
|
|
// 筛选条件
|
const dateRange = ref([]);
|
const productType = ref('');
|
const salesArea = ref('');
|
const statDimension = ref('month');
|
|
// 图表引用
|
const salesVolumeChart = ref(null);
|
const salesAmountChart = ref(null);
|
const productTypeChart = ref(null);
|
const salesAreaChart = ref(null);
|
const cumulativeSalesVolumeChart = ref(null);
|
const cumulativeSalesAmountChart = ref(null);
|
|
// 图表实例
|
let salesVolumeChartInstance = null;
|
let salesAmountChartInstance = null;
|
let productTypeChartInstance = null;
|
let salesAreaChartInstance = null;
|
let cumulativeSalesVolumeChartInstance = null;
|
let cumulativeSalesAmountChartInstance = null;
|
|
// 模拟数据
|
const mockData = [
|
// 2026年1月数据
|
{ productType: '砌块', salesArea: '华东', period: '2026-01', salesVolume: 1200, salesAmount: 180, newCustomers: 5, totalCustomers: 120 },
|
{ productType: '砌块', salesArea: '华北', period: '2026-01', salesVolume: 800, salesAmount: 120, newCustomers: 3, totalCustomers: 80 },
|
{ productType: '砌块', salesArea: '华南', period: '2026-01', salesVolume: 600, salesAmount: 90, newCustomers: 2, totalCustomers: 60 },
|
{ productType: '板材', salesArea: '华东', period: '2026-01', salesVolume: 900, salesAmount: 270, newCustomers: 4, totalCustomers: 100 },
|
{ productType: '板材', salesArea: '华北', period: '2026-01', salesVolume: 500, salesAmount: 150, newCustomers: 2, totalCustomers: 70 },
|
{ productType: '型材', salesArea: '华东', period: '2026-01', salesVolume: 400, salesAmount: 200, newCustomers: 3, totalCustomers: 50 },
|
// 2026年2月数据
|
{ productType: '砌块', salesArea: '华东', period: '2026-02', salesVolume: 1300, salesAmount: 195, newCustomers: 4, totalCustomers: 124 },
|
{ productType: '砌块', salesArea: '华北', period: '2026-02', salesVolume: 850, salesAmount: 127.5, newCustomers: 2, totalCustomers: 82 },
|
{ productType: '砌块', salesArea: '华南', period: '2026-02', salesVolume: 650, salesAmount: 97.5, newCustomers: 1, totalCustomers: 61 },
|
{ productType: '板材', salesArea: '华东', period: '2026-02', salesVolume: 950, salesAmount: 285, newCustomers: 3, totalCustomers: 103 },
|
{ productType: '板材', salesArea: '华北', period: '2026-02', salesVolume: 550, salesAmount: 165, newCustomers: 1, totalCustomers: 71 },
|
{ productType: '型材', salesArea: '华东', period: '2026-02', salesVolume: 450, salesAmount: 225, newCustomers: 2, totalCustomers: 52 },
|
// 2026年3月数据
|
{ productType: '砌块', salesArea: '华东', period: '2026-03', salesVolume: 1400, salesAmount: 210, newCustomers: 6, totalCustomers: 130 },
|
{ productType: '砌块', salesArea: '华北', period: '2026-03', salesVolume: 900, salesAmount: 135, newCustomers: 3, totalCustomers: 85 },
|
{ productType: '砌块', salesArea: '华南', period: '2026-03', salesVolume: 700, salesAmount: 105, newCustomers: 2, totalCustomers: 63 },
|
{ productType: '板材', salesArea: '华东', period: '2026-03', salesVolume: 1000, salesAmount: 300, newCustomers: 5, totalCustomers: 108 },
|
{ productType: '板材', salesArea: '华北', period: '2026-03', salesVolume: 600, salesAmount: 180, newCustomers: 2, totalCustomers: 73 },
|
{ productType: '型材', salesArea: '华东', period: '2026-03', salesVolume: 500, salesAmount: 250, newCustomers: 3, totalCustomers: 55 },
|
// 西南和西北地区数据
|
{ productType: '砌块', salesArea: '西南', period: '2026-03', salesVolume: 500, salesAmount: 75, newCustomers: 2, totalCustomers: 40 },
|
{ productType: '板材', salesArea: '西南', period: '2026-03', salesVolume: 300, salesAmount: 90, newCustomers: 1, totalCustomers: 30 },
|
{ productType: '砌块', salesArea: '西北', period: '2026-03', salesVolume: 400, salesAmount: 60, newCustomers: 1, totalCustomers: 35 },
|
{ productType: '板材', salesArea: '西北', period: '2026-03', salesVolume: 200, salesAmount: 60, newCustomers: 1, totalCustomers: 25 },
|
];
|
|
|
// 计算属性
|
const filteredData = computed(() => {
|
let result = [...mockData];
|
|
// 按产品类型筛选
|
if (productType.value) {
|
result = result.filter(item => {
|
const typeMap = { block: '砌块', board: '板材', profile: '型材' };
|
return item.productType === typeMap[productType.value];
|
});
|
}
|
|
// 按销售区域筛选
|
if (salesArea.value) {
|
result = result.filter(item => {
|
const areaMap = { east: '华东', north: '华北', south: '华南', southwest: '西南', northwest: '西北' };
|
return item.salesArea === areaMap[salesArea.value];
|
});
|
}
|
|
// 按时间范围筛选
|
if (dateRange.value && dateRange.value.length === 2) {
|
const startDate = dayjs(dateRange.value[0]);
|
const endDate = dayjs(dateRange.value[1]);
|
|
result = result.filter(item => {
|
const itemDate = dayjs(item.period);
|
return itemDate.isAfter(startDate.subtract(1, 'day')) && itemDate.isBefore(endDate.add(1, 'day'));
|
});
|
}
|
|
return result;
|
});
|
|
// 核心指标计算
|
const totalSalesVolume = computed(() => {
|
return filteredData.value.reduce((sum, item) => sum + item.salesVolume, 0);
|
});
|
|
const totalSalesAmount = computed(() => {
|
return filteredData.value.reduce((sum, item) => sum + item.salesAmount, 0).toFixed(2);
|
});
|
|
const newCustomerCount = computed(() => {
|
return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0);
|
});
|
|
const totalCustomerCount = computed(() => {
|
// 计算每个区域和产品类型的最大客户数
|
const customerMap = {};
|
filteredData.value.forEach(item => {
|
const key = `${item.productType}-${item.salesArea}`;
|
if (!customerMap[key] || item.totalCustomers > customerMap[key]) {
|
customerMap[key] = item.totalCustomers;
|
}
|
});
|
return Object.values(customerMap).reduce((sum, count) => sum + count, 0);
|
});
|
|
// 变化率计算(模拟)
|
const salesVolumeChange = ref('+5.2');
|
const salesAmountChange = ref('+7.8');
|
const customerCountChange = ref('+3.5');
|
const totalCustomerChange = ref('+2.1');
|
|
// 表格数据
|
const tableData = computed(() => {
|
return filteredData.value.map(item => {
|
// 计算累计值(模拟)
|
const cumulativeSalesVolume = item.salesVolume * 1.5;
|
const cumulativeSalesAmount = item.salesAmount * 1.5;
|
const cumulativeNewCustomers = item.newCustomers * 2;
|
|
return {
|
...item,
|
cumulativeSalesVolume,
|
cumulativeSalesAmount,
|
cumulativeNewCustomers
|
};
|
});
|
});
|
|
// 销量趋势图表配置
|
const salesVolumeChartOption = computed(() => {
|
// 按周期分组
|
const periodMap = {};
|
filteredData.value.forEach(item => {
|
if (!periodMap[item.period]) {
|
periodMap[item.period] = 0;
|
}
|
periodMap[item.period] += item.salesVolume;
|
});
|
|
const periods = Object.keys(periodMap).sort();
|
const values = periods.map(period => periodMap[period]);
|
|
return {
|
tooltip: {
|
trigger: 'axis',
|
formatter: '{b}: {c} 立方米'
|
},
|
xAxis: {
|
type: 'category',
|
data: periods
|
},
|
yAxis: {
|
type: 'value',
|
name: '销量(立方米)'
|
},
|
series: [{
|
data: values,
|
type: 'line',
|
smooth: true,
|
lineStyle: {
|
width: 3
|
},
|
itemStyle: {
|
color: '#409EFF'
|
}
|
}]
|
};
|
});
|
|
// 销售金额趋势图表配置
|
const salesAmountChartOption = computed(() => {
|
// 按周期分组
|
const periodMap = {};
|
filteredData.value.forEach(item => {
|
if (!periodMap[item.period]) {
|
periodMap[item.period] = 0;
|
}
|
periodMap[item.period] += item.salesAmount;
|
});
|
|
const periods = Object.keys(periodMap).sort();
|
const values = periods.map(period => periodMap[period]);
|
|
return {
|
tooltip: {
|
trigger: 'axis',
|
formatter: '{b}: {c} 万元'
|
},
|
xAxis: {
|
type: 'category',
|
data: periods
|
},
|
yAxis: {
|
type: 'value',
|
name: '销售金额(万元)'
|
},
|
series: [{
|
data: values,
|
type: 'bar',
|
itemStyle: {
|
color: '#67C23A'
|
}
|
}]
|
};
|
});
|
|
// 产品类型分布图表配置
|
const productTypeChartOption = computed(() => {
|
// 按产品类型分组
|
const typeMap = {};
|
filteredData.value.forEach(item => {
|
if (!typeMap[item.productType]) {
|
typeMap[item.productType] = 0;
|
}
|
typeMap[item.productType] += item.salesVolume;
|
});
|
|
const types = Object.keys(typeMap);
|
const values = types.map(type => typeMap[type]);
|
|
return {
|
tooltip: {
|
trigger: 'item',
|
formatter: '{b}: {c} 立方米 ({d}%)'
|
},
|
series: [{
|
type: 'pie',
|
radius: '60%',
|
data: types.map((type, index) => ({
|
name: type,
|
value: values[index]
|
})),
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
}
|
}
|
}]
|
};
|
});
|
|
// 销售区域分布图表配置
|
const salesAreaChartOption = computed(() => {
|
// 按销售区域分组
|
const areaMap = {};
|
filteredData.value.forEach(item => {
|
if (!areaMap[item.salesArea]) {
|
areaMap[item.salesArea] = 0;
|
}
|
areaMap[item.salesArea] += item.salesVolume;
|
});
|
|
const areas = Object.keys(areaMap);
|
const values = areas.map(area => areaMap[area]);
|
|
return {
|
tooltip: {
|
trigger: 'item',
|
formatter: '{b}: {c} 立方米 ({d}%)'
|
},
|
series: [{
|
type: 'pie',
|
radius: '60%',
|
data: areas.map((area, index) => ({
|
name: area,
|
value: values[index]
|
})),
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
}
|
}
|
}]
|
};
|
});
|
|
// 累计销量趋势图表配置
|
const cumulativeSalesVolumeChartOption = computed(() => {
|
// 按周期分组
|
const periodMap = {};
|
let cumulativeValue = 0;
|
|
// 按周期排序
|
const sortedData = [...filteredData.value].sort((a, b) => a.period.localeCompare(b.period));
|
|
sortedData.forEach(item => {
|
cumulativeValue += item.salesVolume;
|
periodMap[item.period] = cumulativeValue;
|
});
|
|
const periods = Object.keys(periodMap).sort();
|
const values = periods.map(period => periodMap[period]);
|
|
return {
|
tooltip: {
|
trigger: 'axis',
|
formatter: '{b}: {c} 立方米'
|
},
|
xAxis: {
|
type: 'category',
|
data: periods
|
},
|
yAxis: {
|
type: 'value',
|
name: '累计销量(立方米)'
|
},
|
series: [{
|
data: values,
|
type: 'line',
|
smooth: true,
|
areaStyle: {
|
opacity: 0.3
|
},
|
itemStyle: {
|
color: '#E6A23C'
|
},
|
lineStyle: {
|
width: 3
|
}
|
}]
|
};
|
});
|
|
// 累计销售金额趋势图表配置
|
const cumulativeSalesAmountChartOption = computed(() => {
|
// 按周期分组
|
const periodMap = {};
|
let cumulativeValue = 0;
|
|
// 按周期排序
|
const sortedData = [...filteredData.value].sort((a, b) => a.period.localeCompare(b.period));
|
|
sortedData.forEach(item => {
|
cumulativeValue += item.salesAmount;
|
periodMap[item.period] = cumulativeValue;
|
});
|
|
const periods = Object.keys(periodMap).sort();
|
const values = periods.map(period => periodMap[period]);
|
|
return {
|
tooltip: {
|
trigger: 'axis',
|
formatter: '{b}: {c} 万元'
|
},
|
xAxis: {
|
type: 'category',
|
data: periods
|
},
|
yAxis: {
|
type: 'value',
|
name: '累计销售金额(万元)'
|
},
|
series: [{
|
data: values,
|
type: 'bar',
|
itemStyle: {
|
color: '#F56C6C'
|
}
|
}]
|
};
|
});
|
|
// 方法
|
const goBack = () => {
|
router.back();
|
};
|
|
const handleDateChange = () => {
|
// 处理日期变化
|
updateCharts();
|
};
|
|
const handleFilterChange = () => {
|
// 处理筛选条件变化
|
updateCharts();
|
};
|
|
// 初始化图表
|
const initCharts = () => {
|
// 初始化销量趋势图表
|
if (salesVolumeChart.value && !salesVolumeChartInstance) {
|
salesVolumeChartInstance = echarts.init(salesVolumeChart.value);
|
}
|
|
// 初始化销售金额趋势图表
|
if (salesAmountChart.value && !salesAmountChartInstance) {
|
salesAmountChartInstance = echarts.init(salesAmountChart.value);
|
}
|
|
// 初始化产品类型分布图表
|
if (productTypeChart.value && !productTypeChartInstance) {
|
productTypeChartInstance = echarts.init(productTypeChart.value);
|
}
|
|
// 初始化销售区域分布图表
|
if (salesAreaChart.value && !salesAreaChartInstance) {
|
salesAreaChartInstance = echarts.init(salesAreaChart.value);
|
}
|
|
// 初始化累计销量趋势图表
|
if (cumulativeSalesVolumeChart.value && !cumulativeSalesVolumeChartInstance) {
|
cumulativeSalesVolumeChartInstance = echarts.init(cumulativeSalesVolumeChart.value);
|
}
|
|
// 初始化累计销售金额趋势图表
|
if (cumulativeSalesAmountChart.value && !cumulativeSalesAmountChartInstance) {
|
cumulativeSalesAmountChartInstance = echarts.init(cumulativeSalesAmountChart.value);
|
}
|
|
updateCharts();
|
};
|
|
// 更新图表
|
const updateCharts = () => {
|
// 更新销量趋势图表
|
if (salesVolumeChartInstance) {
|
salesVolumeChartInstance.setOption(salesVolumeChartOption.value);
|
}
|
|
// 更新销售金额趋势图表
|
if (salesAmountChartInstance) {
|
salesAmountChartInstance.setOption(salesAmountChartOption.value);
|
}
|
|
// 更新产品类型分布图表
|
if (productTypeChartInstance) {
|
productTypeChartInstance.setOption(productTypeChartOption.value);
|
}
|
|
// 更新销售区域分布图表
|
if (salesAreaChartInstance) {
|
salesAreaChartInstance.setOption(salesAreaChartOption.value);
|
}
|
|
// 更新累计销量趋势图表
|
if (cumulativeSalesVolumeChartInstance) {
|
cumulativeSalesVolumeChartInstance.setOption(cumulativeSalesVolumeChartOption.value);
|
}
|
|
// 更新累计销售金额趋势图表
|
if (cumulativeSalesAmountChartInstance) {
|
cumulativeSalesAmountChartInstance.setOption(cumulativeSalesAmountChartOption.value);
|
}
|
};
|
|
// 监听窗口大小变化
|
const handleResize = () => {
|
if (salesVolumeChartInstance) {
|
salesVolumeChartInstance.resize();
|
}
|
if (salesAmountChartInstance) {
|
salesAmountChartInstance.resize();
|
}
|
if (productTypeChartInstance) {
|
productTypeChartInstance.resize();
|
}
|
if (salesAreaChartInstance) {
|
salesAreaChartInstance.resize();
|
}
|
if (cumulativeSalesVolumeChartInstance) {
|
cumulativeSalesVolumeChartInstance.resize();
|
}
|
if (cumulativeSalesAmountChartInstance) {
|
cumulativeSalesAmountChartInstance.resize();
|
}
|
};
|
|
// 生命周期
|
onMounted(() => {
|
// 设置默认日期范围为最近3个月
|
const endDate = dayjs();
|
const startDate = endDate.subtract(3, 'month');
|
dateRange.value = [startDate.format('YYYY-MM-DD'), endDate.format('YYYY-MM-DD')];
|
|
// 等待DOM更新后初始化图表
|
nextTick(() => {
|
initCharts();
|
});
|
|
// 添加窗口大小变化监听
|
window.addEventListener('resize', handleResize);
|
});
|
|
// 组件卸载时销毁图表实例
|
onBeforeUnmount(() => {
|
if (salesVolumeChartInstance) {
|
salesVolumeChartInstance.dispose();
|
}
|
if (salesAmountChartInstance) {
|
salesAmountChartInstance.dispose();
|
}
|
if (productTypeChartInstance) {
|
productTypeChartInstance.dispose();
|
}
|
if (salesAreaChartInstance) {
|
salesAreaChartInstance.dispose();
|
}
|
if (cumulativeSalesVolumeChartInstance) {
|
cumulativeSalesVolumeChartInstance.dispose();
|
}
|
if (cumulativeSalesAmountChartInstance) {
|
cumulativeSalesAmountChartInstance.dispose();
|
}
|
|
// 移除窗口大小变化监听
|
window.removeEventListener('resize', handleResize);
|
});
|
</script>
|
|
<style scoped>
|
/* 外部容器 - 占据整个视口 */
|
.sales-statistics-container {
|
position: relative;
|
width: 100%;
|
/* 页面在常规布局下(有顶栏)默认减去 84px,避免内容被裁切 */
|
min-height: calc(100vh - 84px);
|
background-color: #f5f7fa;
|
overflow: hidden;
|
}
|
|
/* 内部内容区域 - 自适应宽度 */
|
.data-dashboard {
|
position: relative;
|
width: 100%;
|
min-height: 100%;
|
background-color: #ffffff;
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
}
|
|
.filter-area {
|
padding: 20px;
|
background-color: #ffffff;
|
border-bottom: 1px solid #e4e7ed;
|
display: flex;
|
gap: 40px;
|
align-items: center;
|
flex-wrap: wrap;
|
}
|
|
.filter-section {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.filter-label {
|
font-size: 14px;
|
font-weight: 500;
|
color: #303133;
|
white-space: nowrap;
|
}
|
|
.dashboard-content {
|
position: relative;
|
z-index: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
padding: 20px;
|
min-height: 800px;
|
overflow: hidden;
|
}
|
|
/* 行布局 */
|
.row {
|
display: flex;
|
gap: 20px;
|
align-items: stretch;
|
}
|
|
/* 第一行:4个指标卡片 */
|
.row-1 {
|
height: 180px;
|
}
|
|
/* 第二行:2个趋势图表 */
|
.row-2 {
|
height: 350px;
|
}
|
|
/* 第三行:累计数据趋势 */
|
.row-3 {
|
height: 350px;
|
}
|
|
/* 第四行:表格和图表 */
|
.row-4 {
|
height: 600px;
|
}
|
|
/* 卡片样式 */
|
.panel-card {
|
background-color: #ffffff;
|
border-radius: 8px;
|
border: 1px solid #e4e7ed;
|
overflow: hidden;
|
display: flex;
|
flex-direction: column;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
transition: all 0.3s ease;
|
}
|
|
.panel-card:hover {
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
transform: translateY(-2px);
|
}
|
|
/* 卡片布局 */
|
.card-1 {
|
flex: 1;
|
}
|
|
.card-2 {
|
flex: 1;
|
}
|
|
.card-3 {
|
flex: 1;
|
}
|
|
.card-4 {
|
flex: 1;
|
}
|
|
.card-5 {
|
flex: 1;
|
}
|
|
.card-6 {
|
flex: 1;
|
}
|
|
.card-7 {
|
flex: 1;
|
}
|
|
.card-8 {
|
flex: 1;
|
}
|
|
.card-9 {
|
flex: 1;
|
}
|
|
.card-10 {
|
flex: 1;
|
}
|
|
.card-11 {
|
flex: 1;
|
}
|
|
.panel-title {
|
padding: 15px 20px;
|
font-size: 16px;
|
font-weight: 500;
|
color: #303133;
|
border-bottom: 1px solid #e4e7ed;
|
background-color: #fafafa;
|
}
|
|
.chart-container {
|
flex: 1;
|
padding: 20px;
|
}
|
|
.table-container {
|
flex: 1;
|
padding: 20px;
|
overflow: auto;
|
}
|
|
.stats-grid {
|
flex: 1;
|
padding: 15px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.stat-item {
|
background-color: #fafafa;
|
border-radius: 8px;
|
padding: 15px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
border: 1px solid #e4e7ed;
|
min-height: 80px;
|
width: 100%;
|
}
|
|
.stat-value {
|
font-size: 24px;
|
font-weight: 600;
|
color: #303133;
|
margin-bottom: 5px;
|
}
|
|
.stat-unit {
|
font-size: 12px;
|
color: #909399;
|
margin-bottom: 3px;
|
}
|
|
.stat-change {
|
font-size: 12px;
|
color: #67c23a;
|
}
|
|
/* 表格样式 */
|
:deep(.el-table) {
|
border-radius: 8px;
|
overflow: hidden;
|
}
|
|
:deep(.el-table th) {
|
background-color: #fafafa;
|
font-weight: 500;
|
}
|
|
:deep(.el-table tr:hover > td) {
|
background-color: #ecf5ff;
|
}
|
|
/* 下拉选择框样式 */
|
:deep(.el-select) {
|
width: 100%;
|
}
|
|
:deep(.el-date-picker) {
|
width: 100%;
|
}
|
</style>
|