2025-09-05 09:28:13 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<!-- 图表容器(含滚动) -->
|
|
|
|
|
|
<div class="charts-container">
|
2025-09-05 14:05:07 +08:00
|
|
|
|
<!-- Element 布局:行容器(不变) -->
|
2025-09-05 14:47:32 +08:00
|
|
|
|
<el-row class="charts-row" :gutter="20" :style="{ height: `calc(100% - 40px)` }">
|
2025-09-05 14:05:07 +08:00
|
|
|
|
<!-- 动态渲染图表:应用高度配置(核心修改) -->
|
2025-09-05 14:47:32 +08:00
|
|
|
|
<el-col v-for="(chartConfig, index) in persistedChartConfigs" :key="chartConfig.id" class="chart-col"
|
|
|
|
|
|
:xs="chartConfig.layout.xs" :sm="chartConfig.layout.sm" :md="chartConfig.layout.md" :lg="chartConfig.layout.lg"
|
|
|
|
|
|
:xl="chartConfig.layout.xl" :style="{ height: `${chartConfig.height || 400}px`, marginBottom: '20px' }">
|
2025-09-05 14:05:07 +08:00
|
|
|
|
<!-- 动态加载图表组件:确保组件高度100% -->
|
2025-09-05 14:47:32 +08:00
|
|
|
|
<component :is="chartComponentMap[chartConfig.componentName]" class="chart-item"
|
|
|
|
|
|
v-bind="getChartProps(chartConfig)" :is-refreshing="isRefreshing" :chart-height="chartConfig.height || 400" />
|
2025-09-05 09:28:13 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-09-05 14:05:07 +08:00
|
|
|
|
import { useStorage } from '@vueuse/core';
|
|
|
|
|
|
import { ElRow, ElCol } from 'element-plus';
|
2025-09-05 09:28:13 +08:00
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
// 1. 导入所有图表子组件(不变)
|
2025-09-05 14:47:32 +08:00
|
|
|
|
import OrderTrendChart from '../components/OrderTrendChart.vue';
|
2025-09-05 09:28:13 +08:00
|
|
|
|
import ProductSalesRank from '../components/ProductSalesRank.vue';
|
|
|
|
|
|
import CustomerFollowStatus from '../components/CustomerFollowStatus.vue';
|
|
|
|
|
|
import ReturnExchangeAnalysis from '../components/ReturnExchangeAnalysis.vue';
|
|
|
|
|
|
import SalesByManagerChart from '../components/SalesByManagerChart.vue';
|
|
|
|
|
|
import SalesByCustomerChart from '../components/SalesByCustomerChart.vue';
|
2025-09-05 14:05:07 +08:00
|
|
|
|
import SalesMetricsCard from '../components/SalesMetricsCard.vue';
|
|
|
|
|
|
import RecentOrdersTable from '../components/RecentOrdersTable.vue';
|
2025-09-05 09:28:13 +08:00
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
// 2. 图表组件映射表(不变)
|
2025-09-05 09:28:13 +08:00
|
|
|
|
const chartComponentMap = {
|
|
|
|
|
|
OrderTrendChart,
|
|
|
|
|
|
ProductSalesRank,
|
|
|
|
|
|
CustomerFollowStatus,
|
|
|
|
|
|
ReturnExchangeAnalysis,
|
|
|
|
|
|
SalesByManagerChart,
|
2025-09-05 14:05:07 +08:00
|
|
|
|
SalesByCustomerChart,
|
|
|
|
|
|
SalesMetricsCard,
|
|
|
|
|
|
RecentOrdersTable
|
2025-09-05 09:28:13 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
// 3. 图表默认配置数组:新增height默认值(核心修改)
|
2025-09-05 09:28:13 +08:00
|
|
|
|
const DEFAULT_CHART_CONFIGS = [
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "product-rank",
|
|
|
|
|
|
"componentName": "ProductSalesRank",
|
|
|
|
|
|
"title": "产品销售排行图表",
|
|
|
|
|
|
"dataKey": "orderDetails",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 8,
|
|
|
|
|
|
"lg": "8",
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 09:28:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "customer-follow",
|
|
|
|
|
|
"componentName": "CustomerFollowStatus",
|
|
|
|
|
|
"title": "客户跟进状态图表",
|
|
|
|
|
|
"dataKey": "customers",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 8,
|
|
|
|
|
|
"lg": "8",
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 09:28:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "sales-metrics",
|
|
|
|
|
|
"componentName": "SalesMetricsCard",
|
|
|
|
|
|
"title": "销售指标图表",
|
|
|
|
|
|
"dataKey": [
|
|
|
|
|
|
"orders",
|
|
|
|
|
|
"customers",
|
|
|
|
|
|
"returnExchanges"
|
|
|
|
|
|
],
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 8,
|
|
|
|
|
|
"lg": "8",
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 200
|
2025-09-05 09:28:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "order-trend",
|
|
|
|
|
|
"componentName": "OrderTrendChart",
|
|
|
|
|
|
"title": "订单趋势图表",
|
|
|
|
|
|
"dataKey": "orders",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 12,
|
|
|
|
|
|
"lg": 12,
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 09:28:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "sales-manager",
|
|
|
|
|
|
"componentName": "SalesByManagerChart",
|
|
|
|
|
|
"title": "负责人订单汇总",
|
|
|
|
|
|
"dataKey": "orders",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 12,
|
|
|
|
|
|
"lg": 12,
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 09:28:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "return-exchange",
|
|
|
|
|
|
"componentName": "ReturnExchangeAnalysis",
|
|
|
|
|
|
"title": "退换货分析图表",
|
|
|
|
|
|
"dataKey": "returnExchanges",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 12,
|
|
|
|
|
|
"lg": 12,
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 14:05:07 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "sales-customer",
|
|
|
|
|
|
"componentName": "SalesByCustomerChart",
|
|
|
|
|
|
"title": "客户订单汇总",
|
|
|
|
|
|
"dataKey": "orders",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": 12,
|
|
|
|
|
|
"lg": 12,
|
|
|
|
|
|
"xl": 12
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 14:05:07 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-05 14:47:32 +08:00
|
|
|
|
"id": "recent-orders",
|
|
|
|
|
|
"componentName": "RecentOrdersTable",
|
|
|
|
|
|
"title": "最近订单",
|
|
|
|
|
|
"dataKey": "orders",
|
|
|
|
|
|
"layout": {
|
|
|
|
|
|
"xs": 24,
|
|
|
|
|
|
"sm": 24,
|
|
|
|
|
|
"md": "24",
|
|
|
|
|
|
"lg": "24",
|
|
|
|
|
|
"xl": "24"
|
|
|
|
|
|
},
|
|
|
|
|
|
"height": 400
|
2025-09-05 09:28:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
// 4. 持久化图表配置(不变,但默认值包含height)
|
2025-09-05 09:28:13 +08:00
|
|
|
|
const persistedChartConfigs = useStorage(
|
2025-09-05 14:05:07 +08:00
|
|
|
|
'saleDashboardChartConfigs',
|
|
|
|
|
|
DEFAULT_CHART_CONFIGS,
|
|
|
|
|
|
localStorage,
|
|
|
|
|
|
{ mergeDefaults: true }
|
2025-09-05 09:28:13 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
// 5. 接收父组件传入的数据源与状态(不变)
|
2025-09-05 09:28:13 +08:00
|
|
|
|
const props = defineProps({
|
2025-09-05 14:05:07 +08:00
|
|
|
|
orders: { type: Array, required: true, default: () => [] },
|
|
|
|
|
|
orderDetails: { type: Array, required: true, default: () => [] },
|
|
|
|
|
|
customers: { type: Array, required: true, default: () => [] },
|
|
|
|
|
|
returnExchanges: { type: Array, required: true, default: () => [] },
|
|
|
|
|
|
isRefreshing: { type: Boolean, required: true, default: false }
|
2025-09-05 09:28:13 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
// 6. 工具函数:传递props(不变)
|
2025-09-05 09:28:13 +08:00
|
|
|
|
const getChartProps = (chartConfig) => {
|
|
|
|
|
|
const dataMap = {
|
|
|
|
|
|
orders: props.orders,
|
|
|
|
|
|
orderDetails: props.orderDetails,
|
|
|
|
|
|
customers: props.customers,
|
|
|
|
|
|
returnExchanges: props.returnExchanges
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
if (Array.isArray(chartConfig.dataKey)) {
|
|
|
|
|
|
const o = { title: chartConfig.title };
|
|
|
|
|
|
chartConfig.dataKey.forEach(key => {
|
|
|
|
|
|
o[key.replace(/([A-Z])/g, '-$1').toLowerCase()] = dataMap[key];
|
|
|
|
|
|
});
|
|
|
|
|
|
return o;
|
2025-09-05 14:47:32 +08:00
|
|
|
|
}
|
2025-09-05 09:28:13 +08:00
|
|
|
|
return {
|
|
|
|
|
|
[chartConfig.dataKey.replace(/([A-Z])/g, '-$1').toLowerCase()]: dataMap[chartConfig.dataKey],
|
2025-09-05 14:05:07 +08:00
|
|
|
|
title: chartConfig.title
|
2025-09-05 09:28:13 +08:00
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-09-05 14:05:07 +08:00
|
|
|
|
/* 图表容器(不变) */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
.charts-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-09-05 14:05:07 +08:00
|
|
|
|
background-color: #0f172a;
|
2025-09-05 09:28:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
/* 行容器(不变) */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
.charts-row {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
2025-09-05 14:05:07 +08:00
|
|
|
|
align-content: flex-start;
|
2025-09-05 09:28:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
/* 列容器:移除固定高度,由配置动态控制(核心修改) */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
.chart-col {
|
|
|
|
|
|
box-sizing: border-box;
|
2025-09-05 14:05:07 +08:00
|
|
|
|
/* 高度由父组件style动态设置,此处不固定 */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
/* 图表项:100%高度继承列容器(核心修改) */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
.chart-item {
|
|
|
|
|
|
width: 100%;
|
2025-09-05 14:47:32 +08:00
|
|
|
|
height: 100%;
|
|
|
|
|
|
/* 关键:让组件填满列容器高度 */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
background-color: #1e293b;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 14:05:07 +08:00
|
|
|
|
/* 小屏幕优化(不变) */
|
2025-09-05 09:28:13 +08:00
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.charts-container {
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
}
|
2025-09-05 14:47:32 +08:00
|
|
|
|
|
2025-09-05 09:28:13 +08:00
|
|
|
|
.chart-item {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|