<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 sales-volume-color">{{ 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 sales-amount-color">{{ 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 new-customer-color">{{ 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 total-customer-color">{{ 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"
|
align="center">
|
<template #default="scope">
|
<el-tag :type="getProductTypeType(scope.row.productType)">
|
{{ scope.row.productType }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="salesArea"
|
label="销售区域"
|
width="120"
|
align="center">
|
<template #default="scope">
|
<el-tag :type="getSalesAreaType(scope.row.salesArea)">
|
{{ scope.row.salesArea }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="period"
|
label="统计周期"
|
width="120" />
|
<el-table-column prop="salesVolume"
|
label="销量(立方米)"
|
align="right">
|
<template #default="scope">
|
<span class="data-value">{{ scope.row.salesVolume }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="salesAmount"
|
label="销售金额(万元)"
|
align="right">
|
<template #default="scope">
|
<span class="data-value">{{ scope.row.salesAmount }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="newCustomers"
|
label="新增客户(个)"
|
width="150"
|
align="right">
|
<template #default="scope">
|
<span class="data-value">{{ scope.row.newCustomers }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="totalCustomers"
|
label="合计客户(个)"
|
width="150"
|
align="right">
|
<template #default="scope">
|
<span class="data-value">{{ scope.row.totalCustomers }}</span>
|
</template>
|
</el-table-column>
|
</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);
|
});
|
|
// 获取产品类型标签类型
|
const getProductTypeType = type => {
|
const typeMap = {
|
砌块: "primary",
|
板材: "success",
|
型材: "warning",
|
};
|
return typeMap[type] || "info";
|
};
|
|
// 获取销售区域标签类型
|
const getSalesAreaType = area => {
|
const typeMap = {
|
华东: "primary",
|
华北: "success",
|
华南: "warning",
|
西南: "danger",
|
西北: "info",
|
};
|
return typeMap[area] || "info";
|
};
|
|
// 组件卸载时销毁图表实例
|
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;
|
}
|
|
.card-1 .panel-title {
|
border-left: 4px solid #409eff;
|
}
|
|
.card-2 .panel-title {
|
border-left: 4px solid #67c23a;
|
}
|
|
.card-3 .panel-title {
|
border-left: 4px solid #e6a23c;
|
}
|
|
.card-4 .panel-title {
|
border-left: 4px solid #f56c6c;
|
}
|
|
.card-5 .panel-title {
|
border-left: 4px solid #409eff;
|
}
|
|
.card-6 .panel-title {
|
border-left: 4px solid #67c23a;
|
}
|
|
.card-7 .panel-title {
|
border-left: 4px solid #e6a23c;
|
}
|
|
.card-8 .panel-title {
|
border-left: 4px solid #f56c6c;
|
}
|
|
.card-9 .panel-title {
|
border-left: 4px solid #409eff;
|
}
|
|
.card-10 .panel-title {
|
border-left: 4px solid #67c23a;
|
}
|
|
.card-11 .panel-title {
|
border-left: 4px solid #e6a23c;
|
}
|
|
.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;
|
}
|
|
.sales-volume-color {
|
color: #409eff;
|
text-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
}
|
|
.sales-amount-color {
|
color: #67c23a;
|
text-shadow: 0 2px 4px rgba(103, 194, 58, 0.3);
|
}
|
|
.new-customer-color {
|
color: #e6a23c;
|
text-shadow: 0 2px 4px rgba(230, 162, 60, 0.3);
|
}
|
|
.total-customer-color {
|
color: #f56c6c;
|
text-shadow: 0 2px 4px rgba(245, 108, 108, 0.3);
|
}
|
|
.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;
|
}
|
|
.data-value {
|
font-weight: bold;
|
color: #409eff;
|
}
|
|
/* 下拉选择框样式 */
|
:deep(.el-select) {
|
width: 100%;
|
}
|
|
:deep(.el-date-picker) {
|
width: 100%;
|
}
|
</style>
|