feat: 数据大屏

This commit is contained in:
砂糖
2025-09-05 09:28:13 +08:00
parent c4814ed3cc
commit a44abee2f6
16 changed files with 2319 additions and 205 deletions

View File

@@ -0,0 +1,246 @@
<template>
<!-- 图表容器含滚动 -->
<div class="charts-container">
<!-- Element 布局el-row 为行容器gutter 控制间距 -->
<el-row
class="charts-row"
:gutter="20"
:style="{ height: `calc(100% - 40px)` }"
>
<!-- 动态渲染图表遍历持久化后的配置数组 -->
<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: `calc(${100 / Math.ceil(persistedChartConfigs.length / 2)}% - 10px)` }"
>
<!-- 动态加载图表组件 -->
<component
:is="chartComponentMap[chartConfig.componentName]"
class="chart-item"
v-bind="getChartProps(chartConfig)"
:is-refreshing="isRefreshing"
/>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { useStorage } from '@vueuse/core'; // 导入持久化工具
import { ElRow, ElCol } from 'element-plus'; // 导入Element布局组件
// 1. 导入所有图表子组件
import OrderTrendChart from '../components/OrderTrendChart.vue';
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';
// 2. 图表组件映射表:用于动态匹配组件(组件名 → 组件实例)
const chartComponentMap = {
OrderTrendChart,
ProductSalesRank,
CustomerFollowStatus,
ReturnExchangeAnalysis,
SalesByManagerChart,
SalesByCustomerChart
};
// 3. 图表默认配置数组:定义每个图表的基础信息、布局、数据源
const DEFAULT_CHART_CONFIGS = [
{
id: 'order-trend', // 唯一标识(不可重复)
componentName: 'OrderTrendChart', // 对应组件名与chartComponentMap匹配
title: '订单趋势图表', // 图表标题(可用于组件内部或表头)
dataKey: 'orders', // 数据源key对应props中的数据字段
layout: { // Element Col 布局配置span范围1-2424为整行
xs: 24, // 超小屏独占1行
sm: 24, // 小屏独占1行
md: 12, // 中屏占1/2行
lg: 12, // 大屏占1/2行
xl: 12 // 超大屏占1/2行
}
},
{
id: 'product-rank',
componentName: 'ProductSalesRank',
title: '产品销售排行图表',
dataKey: 'orderDetails', // 依赖orderDetails数据
layout: {
xs: 24,
sm: 24,
md: 12,
lg: 12,
xl: 12
}
},
{
id: 'sales-manager',
componentName: 'SalesByManagerChart',
title: '负责人订单汇总',
dataKey: 'orders', // 依赖orders数据
layout: {
xs: 24,
sm: 24,
md: 12,
lg: 12,
xl: 12
}
},
{
id: 'customer-follow',
componentName: 'CustomerFollowStatus',
title: '客户跟进状态图表',
dataKey: 'customers', // 依赖customers数据
layout: {
xs: 24,
sm: 24,
md: 12,
lg: 12,
xl: 12
}
},
{
id: 'return-exchange',
componentName: 'ReturnExchangeAnalysis',
title: '退换货分析图表',
dataKey: 'returnExchanges', // 依赖returnExchanges数据
layout: {
xs: 24,
sm: 24,
md: 12,
lg: 12,
xl: 12
}
},
{
id: 'sales-customer',
componentName: 'SalesByCustomerChart',
title: '客户订单汇总',
dataKey: 'orders', // 依赖orders数据
layout: {
xs: 24,
sm: 24,
md: 12,
lg: 12,
xl: 12
}
}
];
// 4. 持久化图表配置用useStorage存入localStoragekey为"saleDashboardChartConfigs"
// 逻辑优先读取localStorage中的配置若无则使用默认配置
const persistedChartConfigs = useStorage(
'saleDashboardChartConfigs', // 存储key自定义确保唯一
DEFAULT_CHART_CONFIGS, // 默认值
localStorage, // 存储介质localStorage/sessionStorage
{ mergeDefaults: true } // 合并默认值与存储值(避免字段缺失)
);
// 5. 接收父组件传入的数据源与状态
const props = defineProps({
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
}
});
// 6. 工具函数根据图表配置动态生成组件所需的props
const getChartProps = (chartConfig) => {
// 映射数据源根据chartConfig.dataKey匹配props中的数据
const dataMap = {
orders: props.orders,
orderDetails: props.orderDetails,
customers: props.customers,
returnExchanges: props.returnExchanges
};
// 返回该图表需要的props如OrderTrendChart需要:ordersProductSalesRank需要:order-details
return {
// 驼峰转连字符如orderDetails → order-details匹配组件props定义
[chartConfig.dataKey.replace(/([A-Z])/g, '-$1').toLowerCase()]: dataMap[chartConfig.dataKey],
title: chartConfig.title // 可选:传递标题给图表组件
};
};
</script>
<style scoped>
/* 图表容器(含滚动) */
.charts-container {
width: 100%;
height: 100%;
padding: 20px;
overflow: auto;
box-sizing: border-box;
background-color: #0f172a; /* 继承父组件深色背景 */
}
/* Element Row 容器清除默认margin确保高度自适应 */
.charts-row {
width: 100%;
margin: 0;
display: flex;
flex-wrap: wrap;
align-content: flex-start; /* 顶部对齐,避免空白 */
}
/* Element Col 容器:控制列的高度与间距 */
.chart-col {
margin-bottom: 20px; /* 行间距与gutter配合 */
box-sizing: border-box;
}
/* 图表项样式:保持原有设计,适配弹性布局 */
.chart-item {
width: 100%;
height: 100%;
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;
}
/* 小屏幕下优化:减少内边距 */
@media (max-width: 768px) {
.charts-container {
padding: 10px;
}
.chart-item {
padding: 12px;
}
.chart-col {
margin-bottom: 10px;
}
}
</style>