| | |
| | | <div class="center-metric-unit">家</div> |
| | | </div> |
| | | <div class="center-metric m4"> |
| | | <div class="center-metric-label">总销售区</div> |
| | | <div class="center-metric-value">{{ totalSalesAreaCount }}</div> |
| | | <div class="center-metric-unit">区</div> |
| | | <div class="center-metric-label">销售方数</div> |
| | | <div class="center-metric-value">{{ totalSalesAreaCount.toFixed(0) }}</div> |
| | | <div class="center-metric-unit">方</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 左下:产品类型销量 --> |
| | | <div class="bi-panel bi-panel-bottom-left"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="销量数据-排名分析" /> |
| | | title="销量数据统计" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item" |
| | | :class="{ active: blockTimeDimension === 'year' }" |
| | |
| | | <table class="scroll-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>排名</th> |
| | | <th>序号</th> |
| | | <th>产品类型</th> |
| | | <th>年月</th> |
| | | <th>销售区</th> |
| | |
| | | title="新增客户趋势分析" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item" |
| | | :class="{ active: customerTimeDimension === 'year' }" |
| | | @click="handleCustomerTimeDimensionChange('year')">年</span> |
| | | :class="{ active: customerTimeDimension === '年' }" |
| | | @click="handleCustomerTimeDimensionChange('年')">年</span> |
| | | <span class="tab-item" |
| | | :class="{ active: customerTimeDimension === 'month' }" |
| | | @click="handleCustomerTimeDimensionChange('month')">月</span> |
| | | :class="{ active: customerTimeDimension === '月' }" |
| | | @click="handleCustomerTimeDimensionChange('月')">月</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-unit-row chart-unit-single"> |
| | | <span>单位:人</span> |
| | | <span>单位:家</span> |
| | | </div> |
| | | <div ref="productTypeTrendChart" |
| | | class="echart-fill"></div> |
| | |
| | | <!-- 右下:销售区域销量 --> |
| | | <div class="bi-panel bi-panel-bottom-right"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="销售额数据-排名分析" /> |
| | | title="销售额数据统计" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item" |
| | | :class="{ active: boardTimeDimension === 'year' }" |
| | |
| | | <table class="scroll-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>排名</th> |
| | | <th>序号</th> |
| | | <th>年月</th> |
| | | <th>销售区</th> |
| | | <th>销售额(万元)</th> |
| | |
| | | import * as echarts from "echarts"; |
| | | import dayjs from "dayjs"; |
| | | import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue"; |
| | | import { findAllQualifiedStockOutRecordTypeOptions } from "../../../api/basicData/enum"; |
| | | import { |
| | | getDashboardStatistics, |
| | | getCustomerTrends, |
| | | } from "@/api/reportAnalysis/salesStatistics"; |
| | | |
| | | const router = useRouter(); |
| | | const screenRoot = ref(null); |
| | |
| | | const boardTimeDimension = ref("year"); |
| | | const boardSelectedArea = ref("全部"); |
| | | const boardProductType = ref("板材"); |
| | | const customerTimeDimension = ref("year"); |
| | | const customerTimeDimension = ref("年"); |
| | | |
| | | const salesAreas = [ |
| | | "全部", |
| | |
| | | }); |
| | | |
| | | const totalSalesAmount = ref(1299); |
| | | const centerNewCustomerCount = ref(112); |
| | | const completedOrders = ref(1829); |
| | | const totalSalesAreaCount = ref(12); |
| | | |
| | | const newCustomerCount = computed(() => { |
| | | return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0); |
| | |
| | | return Object.values(customerMap).reduce((sum, count) => sum + count, 0); |
| | | }); |
| | | |
| | | // 中间中心环指标(用于大屏展示,使用现有统计数据做映射) |
| | | const centerNewCustomerCount = computed(() => 112); |
| | | const completedOrders = computed(() => 1829); |
| | | const salesOrderCount = computed(() => 34); |
| | | const totalSalesAreaCount = computed(() => 12); |
| | | // 客户趋势数据 |
| | | const customerTrendsData = ref([]); |
| | | |
| | | // 变化率计算(模拟) |
| | | const salesVolumeChange = ref("+5.2"); |
| | | const salesAmountChange = ref("+7.8"); |
| | | const customerCountChange = ref("+3.5"); |
| | | const totalCustomerChange = ref("+2.1"); |
| | | |
| | | // 获取中心看板数据 |
| | | const fetchDashboardData = async () => { |
| | | try { |
| | | const response = await getDashboardStatistics(); |
| | | if (response && response.data) { |
| | | totalSalesAmount.value = response.data.price || 0; |
| | | completedOrders.value = response.data.delivery || 0; |
| | | centerNewCustomerCount.value = response.data.customer || 0; |
| | | totalSalesAreaCount.value = response.data.volume || 0; |
| | | } |
| | | } catch (error) { |
| | | console.error("获取中心看板数据失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 获取客户趋势数据 |
| | | const fetchCustomerTrendsData = async () => { |
| | | try { |
| | | const response = await getCustomerTrends({ |
| | | days: customerTimeDimension.value, |
| | | }); |
| | | if (response && response.data) { |
| | | // API返回的数据结构如下: |
| | | // { |
| | | // "dates": ["2026-01-01", "2025-01-01", ...], |
| | | // "customerTrends": [{"ALLIN": 4, "银川": 3, ...}, ...] |
| | | // } |
| | | customerTrendsData.value = response.data; |
| | | updateCharts(); |
| | | } |
| | | } catch (error) { |
| | | console.error("获取客户趋势数据失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 表格数据 |
| | | const tableData = computed(() => { |
| | |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(9), |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(9), |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | |
| | | |
| | | // 新增客户趋势图表配置(按销售区和年月维度) |
| | | const productTypeTrendChartOption = computed(() => { |
| | | // 为每个销售区生成数据 |
| | | const salesAreas = [ |
| | | "全部", |
| | | "A销售区", |
| | | "B销售区", |
| | | "C销售区", |
| | | "D销售区", |
| | | "E销售区", |
| | | ]; |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | "#FF8B6B", |
| | | "#FFCB6B", |
| | | "#8BC34A", |
| | | "#4CAF50", |
| | | ]; |
| | | const year = 2024; |
| | | const periodType = customerTimeDimension.value; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // 年度数据:12个月 |
| | | for (let month = 1; month <= 12; month++) { |
| | | periods.push(`${year}-${month.toString().padStart(2, "0")}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push( |
| | | `${year}-${month.toString().padStart(2, "0")}-${day |
| | | .toString() |
| | | .padStart(2, "0")}` |
| | | ); |
| | | } |
| | | } |
| | | let salesAreas = []; |
| | | let series = []; |
| | | |
| | | // 为每个销售区生成数据 |
| | | const series = salesAreas.map((area, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 10) + 2 |
| | | : Math.floor(Math.random() * 3) + 1; |
| | | if ( |
| | | customerTrendsData.value && |
| | | customerTrendsData.value.dates && |
| | | customerTrendsData.value.customerTrends |
| | | ) { |
| | | // 使用API返回的数据 |
| | | periods = customerTrendsData.value.dates; |
| | | |
| | | // 提取所有销售区域 |
| | | const areaSet = new Set(); |
| | | customerTrendsData.value.customerTrends.forEach(item => { |
| | | Object.keys(item).forEach(key => { |
| | | areaSet.add(key); |
| | | }); |
| | | }); |
| | | salesAreas = Array.from(areaSet); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | lineStyle: { width: getResponsiveValue(1), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | }; |
| | | }); |
| | | // 为每个销售区域生成数据 |
| | | series = salesAreas.map((area, index) => { |
| | | const data = customerTrendsData.value.customerTrends.map((item, i) => { |
| | | return item[area] || 0; |
| | | }); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | lineStyle: { |
| | | width: getResponsiveValue(1), |
| | | color: colors[index % colors.length], |
| | | }, |
| | | itemStyle: { color: colors[index % colors.length] }, |
| | | }; |
| | | }); |
| | | } else { |
| | | // 模拟数据 |
| | | const year = 2024; |
| | | if (periodType === "year") { |
| | | // 年度数据:12个月 |
| | | for (let month = 1; month <= 12; month++) { |
| | | periods.push(`${year}-${month.toString().padStart(2, "0")}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push( |
| | | `${year}-${month.toString().padStart(2, "0")}-${day |
| | | .toString() |
| | | .padStart(2, "0")}` |
| | | ); |
| | | } |
| | | } |
| | | |
| | | salesAreas = []; |
| | | series = salesAreas.map((area, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 10) + 2 |
| | | : Math.floor(Math.random() * 3) + 1; |
| | | }); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | lineStyle: { width: getResponsiveValue(1), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | }; |
| | | }); |
| | | } |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | |
| | | formatter: function (params) { |
| | | let result = params[0].name + "<br/>"; |
| | | params.forEach(param => { |
| | | result += `${param.marker}${param.seriesName}: ${param.value} 人<br/>`; |
| | | result += `${param.marker}${param.seriesName}: ${param.value} 家<br/>`; |
| | | }); |
| | | return result; |
| | | }, |
| | |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(9), |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | |
| | | // 处理新增客户趋势时间维度切换 |
| | | const handleCustomerTimeDimensionChange = dimension => { |
| | | customerTimeDimension.value = dimension; |
| | | updateCharts(); |
| | | fetchCustomerTrendsData(); |
| | | }; |
| | | |
| | | // 生成砌块销售数据 |
| | |
| | | }; |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | onMounted(async () => { |
| | | // 启动顶部栏时间刷新 |
| | | if (!timeTicker) { |
| | | timeTicker = setInterval(() => { |
| | |
| | | // 生成初始数据 |
| | | generateBlockSalesData(); |
| | | generateBoardSalesData(); |
| | | |
| | | // 获取数据 |
| | | await fetchDashboardData(); |
| | | await fetchCustomerTrendsData(); |
| | | |
| | | // 等待DOM更新后初始化图表 |
| | | nextTick(() => { |
| | |
| | | } |
| | | |
| | | /* .scroll-table tbody tr:nth-child(odd) { |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | | |
| | | .scroll-table tbody tr:nth-child(even) { |
| | | background-color: rgba(64, 158, 255, 0.1); |
| | | } */ |
| | | .scroll-table tbody tr:nth-child(even) { |
| | | background-color: rgba(64, 158, 255, 0.1); |
| | | } */ |
| | | .oddTableTr { |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |