| | |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span v-for="area in salesAreas" |
| | | :key="area" |
| | | <span v-for="name in blockMaterialList" |
| | | :key="name" |
| | | class="cf-tab" |
| | | :class="{ active: blockSelectedArea === area }" |
| | | @click="handleBlockAreaChange(area)">{{ area }}</span> |
| | | :class="{ active: blockSelectedMaterial === name }" |
| | | @click="selectBlockMaterial(name)">{{ name }}</span> |
| | | </div> |
| | | <div class="material-info-card"> |
| | | <div v-if="blockSelectedMaterial !== '全部'" |
| | | class="material-info-card"> |
| | | <div class="material-icon"> |
| | | <svg width="24" |
| | | height="24" |
| | |
| | | </svg> |
| | | </div> |
| | | <div class="material-details"> |
| | | <div class="material-name">{{ blockMaterialType }}AAA</div> |
| | | <div class="material-name">{{ blockMaterialSummary.materialName || "—" }}</div> |
| | | <div class="material-stats"> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">月累计单耗</span> |
| | | <span class="stat-value">78/12</span> |
| | | <span class="stat-value">{{ blockMaterialSummary.monthlyConsumption }}</span> |
| | | <span class="stat-unit">吨</span> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">年累计单耗</span> |
| | | <span class="stat-value">78/12</span> |
| | | <span class="stat-value">{{ blockMaterialSummary.yearlyConsumption }}</span> |
| | | <span class="stat-unit">吨</span> |
| | | </div> |
| | | </div> |
| | |
| | | @click="handleProductionTimeDimensionChange('month')">月</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span v-for="cat in productionCategories" |
| | | :key="cat" |
| | | class="cf-tab" |
| | | :class="{ active: productionCategory === cat }" |
| | | @click="selectProductionCategory(cat)">{{ cat }}</span> |
| | | </div> |
| | | <div class="chart-unit-row"> |
| | | <span>单位:件</span> |
| | | </div> |
| | |
| | | </div> |
| | | <!-- 中间中心环 --> |
| | | <div class="center-ring"> |
| | | <!-- <div class="center-ring-box"> |
| | | <div class="center-metric m1"> |
| | | <div class="center-metric-label">项目产量</div> |
| | | <div class="center-metric-value">{{ projectProduction }}</div> |
| | | <div class="center-metric-unit">件</div> |
| | | <div class="center-ring-box"> |
| | | <div class="ring-box-topright"> |
| | | <div class="topright-label">固废处理量</div> |
| | | </div> |
| | | <div class="center-metric m2"> |
| | | <div class="center-metric-label">固体处理量</div> |
| | | <div class="center-metric-value">{{ solidWaste处理量 }}</div> |
| | | <div class="center-metric-unit">吨</div> |
| | | <div class="ring-box-left"> |
| | | <div class="left-label">粉煤灰</div> |
| | | <div class="left-value">月处理 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.flyAshMonth }}</span> 吨 年处理 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.flyAshYear }}</span> 吨</div> |
| | | <div class="left-label" |
| | | style="margin-top: 2vh;">石膏</div> |
| | | <div class="left-value">月处理 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.gypsumMonth }}</span> 吨 年处理 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.gypsumYear }}</span> 吨</div> |
| | | </div> |
| | | <div class="center-metric m3"> |
| | | <div class="center-metric-label">砌块产量</div> |
| | | <div class="center-metric-value">{{ blockProduction }}</div> |
| | | <div class="center-metric-unit">件</div> |
| | | <div class="ring-box-topleft"> |
| | | <div class="topleft-label">项目产量</div> |
| | | </div> |
| | | <div class="center-metric m4"> |
| | | <div class="center-metric-label">板材产量</div> |
| | | <div class="center-metric-value">{{ boardProduction }}</div> |
| | | <div class="center-metric-unit">件</div> |
| | | <div class="ring-box-right"> |
| | | <div class="right-label">砌块产量</div> |
| | | <div class="right-value">月产量 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.blockMonth }}</span> 吨 年产量 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.blockYear }}</span> 吨</div> |
| | | <div class="right-label" |
| | | style="margin-top: 2vh;">板材产量</div> |
| | | <div class="right-value">月产量 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.plateMonth }}</span> 吨 年产量 <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.plateYear }}</span> 吨</div> |
| | | </div> |
| | | </div> --> |
| | | </div> |
| | | </div> |
| | | <!-- 左下:生产成本单耗统计(板材) --> |
| | | <div class="bi-panel bi-panel-bottom-left"> |
| | |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span v-for="area in salesAreas" |
| | | :key="area" |
| | | <span v-for="name in boardMaterialList" |
| | | :key="name" |
| | | class="cf-tab" |
| | | :class="{ active: blockSelectedArea === area }" |
| | | @click="handleBlockAreaChange(area)">{{ area }}</span> |
| | | :class="{ active: boardSelectedMaterial === name }" |
| | | @click="selectBoardMaterial(name)">{{ name }}</span> |
| | | </div> |
| | | <div class="material-info-card"> |
| | | <div v-if="boardSelectedMaterial !== '全部'" |
| | | class="material-info-card"> |
| | | <div class="material-icon"> |
| | | <svg width="24" |
| | | height="24" |
| | |
| | | </svg> |
| | | </div> |
| | | <div class="material-details"> |
| | | <div class="material-name">{{ boardMaterialType }}AAA</div> |
| | | <div class="material-name">{{ boardMaterialSummary.materialName || "—" }}</div> |
| | | <div class="material-stats"> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">月累计单耗</span> |
| | | <span class="stat-value">78/12</span> |
| | | <span class="stat-value">{{ boardMaterialSummary.monthlyConsumption }}</span> |
| | | <span class="stat-unit">吨</span> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">年累计单耗</span> |
| | | <span class="stat-value">78/12</span> |
| | | <span class="stat-value">{{ boardMaterialSummary.yearlyConsumption }}</span> |
| | | <span class="stat-unit">吨</span> |
| | | </div> |
| | | </div> |
| | |
| | | :class="{ active: customerTimeDimension === 'month' }" |
| | | @click="handleCustomerTimeDimensionChange('month')">月</span> |
| | | </div> |
| | | <div ref="customerTrendChart" |
| | | class="echart-fill"></div> |
| | | <!-- <div class="bi-panel-body"> |
| | | <div class="chart-unit-row chart-unit-single"> |
| | | <span>单位:家</span> |
| | |
| | | computed, |
| | | onMounted, |
| | | onBeforeUnmount, |
| | | watch, |
| | | nextTick, |
| | | } from "vue"; |
| | | import * as echarts from "echarts"; |
| | | import dayjs from "dayjs"; |
| | | import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue"; |
| | | import { |
| | | getMaterialProductionAnalysis, |
| | | getProductionMaterials, |
| | | getProductionStatisticsBlocks, |
| | | getProductionStatisticsPlates, |
| | | getProductionStatisticsMiddle, |
| | | getProductionStatisticsSolidWaste, |
| | | getProductionStatisticsEnergy, |
| | | } from "@/api/reportAnalysis/productionStatistics.js"; |
| | | |
| | | const screenRoot = ref(null); |
| | | const isFullscreen = ref(false); |
| | |
| | | |
| | | // 选择器数据 |
| | | const blockTimeDimension = ref("year"); |
| | | const blockMaterialType = ref("石灰"); |
| | | const boardTimeDimension = ref("year"); |
| | | const boardMaterialType = ref("石灰"); |
| | | const productionTimeDimension = ref("year"); |
| | | const customerTimeDimension = ref("year"); |
| | | const salesTimeDimension = ref("year"); |
| | | const selectedArea = ref("全部"); |
| | | |
| | | const salesAreas = [ |
| | | "全部", |
| | | "石灰", |
| | | "水泥", |
| | | "铝粉膏", |
| | | "脱模剂", |
| | | "防腐剂", |
| | | "氧化镁", |
| | | "冷拔丝", |
| | | ]; |
| | | const productionCategories = ["全部", "砌块", "板材"]; |
| | | const productionCategory = ref("全部"); |
| | | |
| | | // 中心环数据 |
| | | const projectProduction = ref(12345); |
| | | const solidWaste处理量 = ref(6789); |
| | | const blockProduction = ref(7812); |
| | | const boardProduction = ref(7812); |
| | | const blockMaterialList = ref([]); |
| | | const blockSelectedMaterial = ref(""); |
| | | const boardMaterialList = ref([]); |
| | | const boardSelectedMaterial = ref(""); |
| | | |
| | | const blockMaterialSummary = ref({ |
| | | materialName: "", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }); |
| | | const boardMaterialSummary = ref({ |
| | | materialName: "", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }); |
| | | |
| | | /** xMode: time=横轴为时间;material=选「全部」时横轴为各物料名称 */ |
| | | const blockCostChartSeries = ref({ |
| | | xMode: "time", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }); |
| | | const boardCostChartSeries = ref({ |
| | | xMode: "time", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }); |
| | | const productionChartSeries = ref({ |
| | | categories: [], |
| | | /** single:单线;dual:全部时砌块+板材双线 */ |
| | | mode: "single", |
| | | values: [], |
| | | blockValues: [], |
| | | plateValues: [], |
| | | }); |
| | | |
| | | // 固废处理量折线图(/home/productionStatistics/solidWaste) |
| | | const solidWasteChartSeries = ref({ |
| | | categories: [], |
| | | total: [], |
| | | flyAsh: [], |
| | | gypsum: [], |
| | | lime: [], |
| | | }); |
| | | |
| | | // 能耗统计(/home/productionStatistics/energy) |
| | | const energyChartSeries = ref({ |
| | | categories: [], |
| | | water: [], |
| | | electricity: [], |
| | | steam: [], |
| | | }); |
| | | |
| | | // 中心环:固废(粉煤灰/石膏)+ 项目产量(砌块/板材),接口 /home/productionStatistics/middle |
| | | const middleRingStats = ref({ |
| | | flyAshMonth: 0, |
| | | flyAshYear: 0, |
| | | gypsumMonth: 0, |
| | | gypsumYear: 0, |
| | | blockMonth: 0, |
| | | blockYear: 0, |
| | | plateMonth: 0, |
| | | plateYear: 0, |
| | | }); |
| | | |
| | | // 图表实例 |
| | | let blockCostChartInstance = null; |
| | |
| | | let productionChartInstance = null; |
| | | let customerTrendChartInstance = null; |
| | | let salesRankingChartInstance = null; |
| | | let blockCostChartResizeObserver = null; |
| | | let boardCostChartResizeObserver = null; |
| | | |
| | | // 生产单耗图表配置 |
| | | // 生产单耗图表配置(砌块,接口数据) |
| | | const blockCostChartOption = computed(() => { |
| | | const materials = ["消耗量"]; |
| | | const colors = ["#8A6BFF"]; |
| | | const year = 2024; |
| | | const periodType = blockTimeDimension.value; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // 年度数据:6个月 |
| | | for (let month = 9; month <= 12; month++) { |
| | | periods.push(`${month}/${year.toString().slice(2)}`); |
| | | } |
| | | for (let month = 1; month <= 3; month++) { |
| | | periods.push(`${month}/${(year + 1).toString().slice(2)}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push(`${month}/${day}`); |
| | | } |
| | | } |
| | | |
| | | // 为每种材料生成数据 |
| | | const series = materials.map((material, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 50) + 150 |
| | | : Math.floor(Math.random() * 5) + 15; |
| | | }); |
| | | |
| | | return { |
| | | name: material, |
| | | data: data, |
| | | const xMode = blockCostChartSeries.value.xMode || "time"; |
| | | const periods = blockCostChartSeries.value.categories || []; |
| | | const inputData = blockCostChartSeries.value.input || []; |
| | | const outputData = blockCostChartSeries.value.output || []; |
| | | const legendNames = ["投入量", "产出量"]; |
| | | const colors = ["#6B9DFF", "#8A6BFF"]; |
| | | const series = [ |
| | | { |
| | | name: legendNames[0], |
| | | data: inputData, |
| | | type: "bar", |
| | | itemStyle: { color: colors[index] }, |
| | | }; |
| | | }); |
| | | itemStyle: { color: colors[0] }, |
| | | }, |
| | | { |
| | | name: legendNames[1], |
| | | data: outputData, |
| | | type: "bar", |
| | | itemStyle: { color: colors[1] }, |
| | | }, |
| | | ]; |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | // legend: { |
| | | // data: materials, |
| | | // top: "10%", |
| | | // right: "1%", |
| | | // textStyle: { |
| | | // color: "#B8C8E0", |
| | | // fontSize: getResponsiveValue(9), |
| | | // }, |
| | | // itemWidth: getResponsiveValue(10), |
| | | // itemHeight: getResponsiveValue(10), |
| | | // }, |
| | | legend: { |
| | | data: legendNames, |
| | | top: "2%", |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(9), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | | }, |
| | | grid: { |
| | | left: "1%", |
| | | right: "1%", |
| | | bottom: "1%", |
| | | top: "8%", |
| | | top: "18%", |
| | | bottom: xMode === "material" ? "0%" : "1%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | |
| | | axisLabel: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(10), |
| | | margin: |
| | | xMode === "material" |
| | | ? getResponsiveValue(6) |
| | | : getResponsiveValue(10), |
| | | rotate: xMode === "material" && periods.length > 5 ? 28 : 0, |
| | | interval: 0, |
| | | }, |
| | | splitLine: { show: false }, |
| | | }, |
| | |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: series, |
| | | series, |
| | | }; |
| | | }); |
| | | |
| | | // 板材单耗图表配置 |
| | | // 板材单耗图表配置(接口数据,与砌块结构一致) |
| | | const boardCostChartOption = computed(() => { |
| | | const materials = ["消耗量"]; |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | ]; |
| | | const year = 2024; |
| | | const periodType = boardTimeDimension.value; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // 年度数据:6个月 |
| | | for (let month = 9; month <= 12; month++) { |
| | | periods.push(`${month}/${year.toString().slice(2)}`); |
| | | } |
| | | for (let month = 1; month <= 3; month++) { |
| | | periods.push(`${month}/${(year + 1).toString().slice(2)}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push(`${month}/${day}`); |
| | | } |
| | | } |
| | | |
| | | // 为每种材料生成数据 |
| | | const series = materials.map((material, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 50) + 150 |
| | | : Math.floor(Math.random() * 5) + 15; |
| | | }); |
| | | |
| | | return { |
| | | name: material, |
| | | data: data, |
| | | const xMode = boardCostChartSeries.value.xMode || "time"; |
| | | const periods = boardCostChartSeries.value.categories || []; |
| | | const inputData = boardCostChartSeries.value.input || []; |
| | | const outputData = boardCostChartSeries.value.output || []; |
| | | const legendNames = ["投入量", "产出量"]; |
| | | const colors = ["#00A4ED", "#34D8F7"]; |
| | | const series = [ |
| | | { |
| | | name: legendNames[0], |
| | | data: inputData, |
| | | type: "bar", |
| | | itemStyle: { color: colors[index] }, |
| | | }; |
| | | }); |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | itemStyle: { color: colors[0] }, |
| | | }, |
| | | // legend: { |
| | | // data: materials, |
| | | // top: "10%", |
| | | // right: "1%", |
| | | // textStyle: { |
| | | // color: "#B8C8E0", |
| | | // fontSize: getResponsiveValue(9), |
| | | // }, |
| | | // itemWidth: getResponsiveValue(10), |
| | | // itemHeight: getResponsiveValue(10), |
| | | // }, |
| | | grid: { |
| | | left: "1%", |
| | | right: "1%", |
| | | bottom: "1%", |
| | | top: "8%", |
| | | containLabel: true, |
| | | { |
| | | name: legendNames[1], |
| | | data: outputData, |
| | | type: "bar", |
| | | itemStyle: { color: colors[1] }, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(10), |
| | | }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisLabel: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(8), |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: series, |
| | | }; |
| | | }); |
| | | |
| | | // 产量分析图表配置 |
| | | const productionChartOption = computed(() => { |
| | | const salesAreas = ["全部", "砌块", "板材"]; |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | ]; |
| | | const year = 2024; |
| | | const periodType = productionTimeDimension.value; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // 年度数据:6个月 |
| | | for (let month = 9; month <= 12; month++) { |
| | | periods.push(`${month}/${year.toString().slice(2)}`); |
| | | } |
| | | for (let month = 1; month <= 3; month++) { |
| | | periods.push(`${month}/${(year + 1).toString().slice(2)}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push(`${month}/${day}`); |
| | | } |
| | | } |
| | | |
| | | // 为每个销售区生成数据 |
| | | const series = salesAreas.map((area, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 50) + 150 |
| | | : Math.floor(Math.random() * 5) + 15; |
| | | }); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: true, |
| | | lineStyle: { width: getResponsiveValue(2), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | areaStyle: { |
| | | opacity: 0.3, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: colors[index] + "80" }, |
| | | { offset: 1, color: colors[index] + "00" }, |
| | | ]), |
| | | }, |
| | | }; |
| | | }); |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | legend: { |
| | | data: salesAreas, |
| | | top: "10%", |
| | | data: legendNames, |
| | | top: "2%", |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | |
| | | grid: { |
| | | left: "1%", |
| | | right: "1%", |
| | | top: "18%", |
| | | bottom: xMode === "material" ? "0%" : "1%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | margin: |
| | | xMode === "material" |
| | | ? getResponsiveValue(6) |
| | | : getResponsiveValue(10), |
| | | rotate: xMode === "material" && periods.length > 5 ? 28 : 0, |
| | | interval: 0, |
| | | }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisLabel: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(8), |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series, |
| | | }; |
| | | }); |
| | | |
| | | // 物料生产量分析(接口数据) |
| | | const productionChartOption = computed(() => { |
| | | const periods = productionChartSeries.value.categories || []; |
| | | const mode = productionChartSeries.value.mode || "single"; |
| | | const blockLineColor = "#4A8BFF"; |
| | | const plateLineColor = "#52C9A0"; |
| | | |
| | | const buildAreaLine = (name, data, lineColor) => ({ |
| | | name, |
| | | data, |
| | | type: "line", |
| | | smooth: true, |
| | | lineStyle: { width: getResponsiveValue(2), color: lineColor }, |
| | | itemStyle: { color: lineColor }, |
| | | areaStyle: { |
| | | opacity: 0.3, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: lineColor + "80" }, |
| | | { offset: 1, color: lineColor + "00" }, |
| | | ]), |
| | | }, |
| | | }); |
| | | |
| | | let series; |
| | | if (mode === "dual") { |
| | | series = [ |
| | | buildAreaLine( |
| | | "砌块", |
| | | productionChartSeries.value.blockValues || [], |
| | | blockLineColor |
| | | ), |
| | | buildAreaLine( |
| | | "板材", |
| | | productionChartSeries.value.plateValues || [], |
| | | plateLineColor |
| | | ), |
| | | ]; |
| | | } else { |
| | | const cat = productionCategory.value; |
| | | const seriesName = cat === "砌块" ? "砌块" : "板材"; |
| | | series = [ |
| | | buildAreaLine( |
| | | seriesName, |
| | | productionChartSeries.value.values || [], |
| | | blockLineColor |
| | | ), |
| | | ]; |
| | | } |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | legend: { show: false }, |
| | | grid: { |
| | | left: "1%", |
| | | right: "1%", |
| | | bottom: "1%", |
| | | top: "28%", |
| | | top: "10%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: series, |
| | | series, |
| | | }; |
| | | }); |
| | | |
| | | // 新增客户趋势图表配置 |
| | | // 固废处理量图表(接口 solidWaste) |
| | | const customerTrendChartOption = computed(() => { |
| | | const customerTypes = ["全部", "石灰", "水泥", "铝粉膏", "脱模剂"]; |
| | | const colors = ["#00A4ED", "#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447"]; |
| | | const year = 2024; |
| | | const periodType = customerTimeDimension.value; |
| | | const legendNames = ["全部", "粉煤灰", "石膏", "石灰"]; |
| | | const colors = ["#00A4ED", "#4A8BFF", "#8A6BFF", "#C8C447"]; |
| | | const periods = solidWasteChartSeries.value.categories || []; |
| | | const dataBySeries = [ |
| | | solidWasteChartSeries.value.total || [], |
| | | solidWasteChartSeries.value.flyAsh || [], |
| | | solidWasteChartSeries.value.gypsum || [], |
| | | solidWasteChartSeries.value.lime || [], |
| | | ]; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // 年度数据:6个月 |
| | | for (let month = 9; month <= 12; month++) { |
| | | periods.push(`${month}/${year.toString().slice(2)}`); |
| | | } |
| | | for (let month = 1; month <= 5; month++) { |
| | | periods.push(`${month}/${(year + 1).toString().slice(2)}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push(`${month}/${day}`); |
| | | } |
| | | } |
| | | |
| | | // 为每种客户类型生成数据 |
| | | const series = customerTypes.map((type, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 10) + 5 |
| | | : Math.floor(Math.random() * 3) + 1; |
| | | }); |
| | | |
| | | return { |
| | | name: type, |
| | | data: data, |
| | | type: "line", |
| | | smooth: true, |
| | | lineStyle: { width: getResponsiveValue(2), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | areaStyle: { |
| | | opacity: 0.3, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: colors[index] + "80" }, |
| | | { offset: 1, color: colors[index] + "00" }, |
| | | ]), |
| | | }, |
| | | }; |
| | | }); |
| | | const series = legendNames.map((name, index) => ({ |
| | | name, |
| | | data: dataBySeries[index] || [], |
| | | type: "line", |
| | | smooth: true, |
| | | lineStyle: { width: getResponsiveValue(2), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | areaStyle: { |
| | | opacity: 0.3, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: colors[index] + "80" }, |
| | | { offset: 1, color: colors[index] + "00" }, |
| | | ]), |
| | | }, |
| | | })); |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | legend: { |
| | | data: customerTypes, |
| | | data: legendNames, |
| | | top: "10%", |
| | | right: "1%", |
| | | textStyle: { |
| | |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: series, |
| | | series, |
| | | }; |
| | | }); |
| | | |
| | | // 能耗统计图表配置 |
| | | // 能耗统计图表配置(接口 energy) |
| | | const salesRankingChartOption = computed(() => { |
| | | const energyTypes = ["水", "电", "蒸汽"]; |
| | | const colors = ["#00A4ED", "#AC43C2", "#F5BC4A"]; |
| | | const year = 2024; |
| | | const periodType = salesTimeDimension.value; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | if (periodType === "year") { |
| | | // 年度数据:6个月 |
| | | for (let month = 9; month <= 12; month++) { |
| | | periods.push(`${month}/${year.toString().slice(2)}`); |
| | | } |
| | | for (let month = 1; month <= 3; month++) { |
| | | periods.push(`${month}/${(year + 1).toString().slice(2)}`); |
| | | } |
| | | } else { |
| | | // 月度数据:7天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 7; day++) { |
| | | periods.push(`${month}/${day}`); |
| | | } |
| | | } |
| | | |
| | | // 为每种能源类型生成数据 |
| | | const waterData = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 300) + 400 |
| | | : Math.floor(Math.random() * 30) + 40; |
| | | }); |
| | | const steamData = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 400) + 500 |
| | | : Math.floor(Math.random() * 40) + 50; |
| | | }); |
| | | const electricityData = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 200) + 300 |
| | | : Math.floor(Math.random() * 20) + 30; |
| | | }); |
| | | const periods = energyChartSeries.value.categories || []; |
| | | const waterData = energyChartSeries.value.water || []; |
| | | const electricityData = energyChartSeries.value.electricity || []; |
| | | const steamData = energyChartSeries.value.steam || []; |
| | | |
| | | const series = [ |
| | | { |
| | |
| | | return Math.round((baseValue * window.innerWidth) / baseWidth.value); |
| | | }; |
| | | |
| | | const mapTimeDimensionToDateType = dim => (dim === "year" ? "2" : "1"); |
| | | |
| | | const productionOutputKey = { |
| | | 全部: "totalOutput", |
| | | 砌块: "blockOutput", |
| | | 板材: "plateOutput", |
| | | }; |
| | | |
| | | /** 全部:按 dateStr 合并砌块/板材两条序列 */ |
| | | /** 将某物料时间序列 chartData 汇总为投入/产出合计(用于「全部」横轴=物料对比) */ |
| | | const sumChartDataInputOutput = chartData => { |
| | | const arr = Array.isArray(chartData) ? chartData : []; |
| | | const input = arr.reduce((s, c) => s + (Number(c.inputSum) || 0), 0); |
| | | const output = arr.reduce((s, c) => s + (Number(c.outputSum) || 0), 0); |
| | | return { input, output }; |
| | | }; |
| | | |
| | | const mergeBlockPlateProductionSeries = (blockList, plateList) => { |
| | | const blocks = Array.isArray(blockList) ? blockList : []; |
| | | const plates = Array.isArray(plateList) ? plateList : []; |
| | | const blockByDate = new Map(blocks.map(i => [i.dateStr, i])); |
| | | const plateByDate = new Map(plates.map(i => [i.dateStr, i])); |
| | | const dates = [ |
| | | ...new Set([...blockByDate.keys(), ...plateByDate.keys()]), |
| | | ].sort(); |
| | | return { |
| | | categories: dates, |
| | | blockValues: dates.map(d => { |
| | | const row = blockByDate.get(d); |
| | | return row ? Number(row.blockOutput) || 0 : 0; |
| | | }), |
| | | plateValues: dates.map(d => { |
| | | const row = plateByDate.get(d); |
| | | return row ? Number(row.plateOutput) || 0 : 0; |
| | | }), |
| | | }; |
| | | }; |
| | | |
| | | const loadBlockCost = async () => { |
| | | const materialName = blockSelectedMaterial.value; |
| | | if (!materialName) { |
| | | blockMaterialSummary.value = { |
| | | materialName: "", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | blockCostChartSeries.value = { |
| | | xMode: "time", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }; |
| | | updateCharts(); |
| | | return; |
| | | } |
| | | const dateType = mapTimeDimensionToDateType(blockTimeDimension.value); |
| | | try { |
| | | if (materialName === "全部") { |
| | | const names = blockMaterialList.value.filter(n => n !== "全部"); |
| | | if (!names.length) { |
| | | blockMaterialSummary.value = { |
| | | materialName: "全部", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | blockCostChartSeries.value = { |
| | | xMode: "material", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }; |
| | | updateCharts(); |
| | | return; |
| | | } |
| | | const res = await getProductionStatisticsBlocks({ dateType }); |
| | | const rows = Array.isArray(res.data) ? res.data : []; |
| | | const rowByName = new Map( |
| | | rows.filter(r => r && r.materialName).map(r => [r.materialName, r]) |
| | | ); |
| | | const useBulk = names.every(n => rowByName.has(n)); |
| | | |
| | | let categories; |
| | | let input; |
| | | let output; |
| | | if (useBulk) { |
| | | categories = []; |
| | | input = []; |
| | | output = []; |
| | | for (const name of names) { |
| | | const row = rowByName.get(name); |
| | | const chartData = Array.isArray(row.chartData) ? row.chartData : []; |
| | | const sums = sumChartDataInputOutput(chartData); |
| | | categories.push(name); |
| | | input.push(sums.input); |
| | | output.push(sums.output); |
| | | } |
| | | } else { |
| | | const parts = await Promise.all( |
| | | names.map(async name => { |
| | | const r = await getProductionStatisticsBlocks({ |
| | | dateType, |
| | | materialName: name, |
| | | }); |
| | | const rows2 = Array.isArray(r.data) ? r.data : []; |
| | | const row = |
| | | rows2.find(x => x.materialName === name) || rows2[0] || {}; |
| | | const chartData = Array.isArray(row.chartData) ? row.chartData : []; |
| | | const sums = sumChartDataInputOutput(chartData); |
| | | return { name, ...sums }; |
| | | }) |
| | | ); |
| | | categories = parts.map(p => p.name); |
| | | input = parts.map(p => p.input); |
| | | output = parts.map(p => p.output); |
| | | } |
| | | |
| | | blockMaterialSummary.value = { |
| | | materialName: "全部", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | blockCostChartSeries.value = { |
| | | xMode: "material", |
| | | categories, |
| | | input, |
| | | output, |
| | | }; |
| | | updateCharts(); |
| | | return; |
| | | } |
| | | |
| | | const res = await getProductionStatisticsBlocks({ |
| | | dateType, |
| | | materialName, |
| | | }); |
| | | const rows = Array.isArray(res.data) ? res.data : []; |
| | | const row = |
| | | rows.find(r => r.materialName === materialName) || rows[0] || {}; |
| | | const chartData = Array.isArray(row.chartData) ? row.chartData : []; |
| | | blockMaterialSummary.value = { |
| | | materialName: row.materialName || materialName, |
| | | monthlyConsumption: row.monthlyConsumption ?? "--", |
| | | yearlyConsumption: row.yearlyConsumption ?? "--", |
| | | }; |
| | | blockCostChartSeries.value = { |
| | | xMode: "time", |
| | | categories: chartData.map(c => c.date), |
| | | input: chartData.map(c => c.inputSum ?? 0), |
| | | output: chartData.map(c => c.outputSum ?? 0), |
| | | }; |
| | | updateCharts(); |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadBoardCost = async () => { |
| | | const materialName = boardSelectedMaterial.value; |
| | | if (!materialName) { |
| | | boardMaterialSummary.value = { |
| | | materialName: "", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | boardCostChartSeries.value = { |
| | | xMode: "time", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }; |
| | | updateCharts(); |
| | | return; |
| | | } |
| | | const dateType = mapTimeDimensionToDateType(boardTimeDimension.value); |
| | | try { |
| | | if (materialName === "全部") { |
| | | const names = boardMaterialList.value.filter(n => n !== "全部"); |
| | | if (!names.length) { |
| | | boardMaterialSummary.value = { |
| | | materialName: "全部", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | boardCostChartSeries.value = { |
| | | xMode: "material", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }; |
| | | updateCharts(); |
| | | return; |
| | | } |
| | | const res = await getProductionStatisticsPlates({ dateType }); |
| | | const rows = Array.isArray(res.data) ? res.data : []; |
| | | const rowByName = new Map( |
| | | rows.filter(r => r && r.materialName).map(r => [r.materialName, r]) |
| | | ); |
| | | const useBulk = names.every(n => rowByName.has(n)); |
| | | |
| | | let categories; |
| | | let input; |
| | | let output; |
| | | if (useBulk) { |
| | | categories = []; |
| | | input = []; |
| | | output = []; |
| | | for (const name of names) { |
| | | const row = rowByName.get(name); |
| | | const chartData = Array.isArray(row.chartData) ? row.chartData : []; |
| | | const sums = sumChartDataInputOutput(chartData); |
| | | categories.push(name); |
| | | input.push(sums.input); |
| | | output.push(sums.output); |
| | | } |
| | | } else { |
| | | const parts = await Promise.all( |
| | | names.map(async name => { |
| | | const r = await getProductionStatisticsPlates({ |
| | | dateType, |
| | | materialName: name, |
| | | }); |
| | | const rows2 = Array.isArray(r.data) ? r.data : []; |
| | | const row = |
| | | rows2.find(x => x.materialName === name) || rows2[0] || {}; |
| | | const chartData = Array.isArray(row.chartData) ? row.chartData : []; |
| | | const sums = sumChartDataInputOutput(chartData); |
| | | return { name, ...sums }; |
| | | }) |
| | | ); |
| | | categories = parts.map(p => p.name); |
| | | input = parts.map(p => p.input); |
| | | output = parts.map(p => p.output); |
| | | } |
| | | |
| | | boardMaterialSummary.value = { |
| | | materialName: "全部", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | boardCostChartSeries.value = { |
| | | xMode: "material", |
| | | categories, |
| | | input, |
| | | output, |
| | | }; |
| | | updateCharts(); |
| | | return; |
| | | } |
| | | |
| | | const res = await getProductionStatisticsPlates({ |
| | | dateType, |
| | | materialName, |
| | | }); |
| | | const rows = Array.isArray(res.data) ? res.data : []; |
| | | const row = |
| | | rows.find(r => r.materialName === materialName) || rows[0] || {}; |
| | | const chartData = Array.isArray(row.chartData) ? row.chartData : []; |
| | | boardMaterialSummary.value = { |
| | | materialName: row.materialName || materialName, |
| | | monthlyConsumption: row.monthlyConsumption ?? "--", |
| | | yearlyConsumption: row.yearlyConsumption ?? "--", |
| | | }; |
| | | boardCostChartSeries.value = { |
| | | xMode: "time", |
| | | categories: chartData.map(c => c.date), |
| | | input: chartData.map(c => c.inputSum ?? 0), |
| | | output: chartData.map(c => c.outputSum ?? 0), |
| | | }; |
| | | updateCharts(); |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadBlockMaterials = async () => { |
| | | try { |
| | | const res = await getProductionMaterials({ materialType: "1" }); |
| | | const raw = Array.isArray(res.data) ? [...res.data] : []; |
| | | const list = ["全部", ...raw]; |
| | | blockMaterialList.value = list; |
| | | if (list.length) { |
| | | if (!list.includes(blockSelectedMaterial.value)) { |
| | | blockSelectedMaterial.value = list[0]; |
| | | } |
| | | await loadBlockCost(); |
| | | } else { |
| | | blockSelectedMaterial.value = ""; |
| | | blockMaterialSummary.value = { |
| | | materialName: "", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | blockCostChartSeries.value = { |
| | | xMode: "time", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }; |
| | | updateCharts(); |
| | | } |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadBoardMaterials = async () => { |
| | | try { |
| | | const res = await getProductionMaterials({ materialType: "2" }); |
| | | const raw = Array.isArray(res.data) ? [...res.data] : []; |
| | | const list = ["全部", ...raw]; |
| | | boardMaterialList.value = list; |
| | | if (list.length) { |
| | | if (!list.includes(boardSelectedMaterial.value)) { |
| | | boardSelectedMaterial.value = list[0]; |
| | | } |
| | | await loadBoardCost(); |
| | | } else { |
| | | boardSelectedMaterial.value = ""; |
| | | boardMaterialSummary.value = { |
| | | materialName: "", |
| | | monthlyConsumption: "--", |
| | | yearlyConsumption: "--", |
| | | }; |
| | | boardCostChartSeries.value = { |
| | | xMode: "time", |
| | | categories: [], |
| | | input: [], |
| | | output: [], |
| | | }; |
| | | updateCharts(); |
| | | } |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadMaterialProduction = async () => { |
| | | const dateType = mapTimeDimensionToDateType(productionTimeDimension.value); |
| | | try { |
| | | const res = await getMaterialProductionAnalysis({ dateType }); |
| | | const payload = res.data && typeof res.data === "object" ? res.data : {}; |
| | | const cat = productionCategory.value; |
| | | |
| | | if (cat === "全部") { |
| | | const merged = mergeBlockPlateProductionSeries( |
| | | payload["砌块"], |
| | | payload["板材"] |
| | | ); |
| | | productionChartSeries.value = { |
| | | categories: merged.categories, |
| | | mode: "dual", |
| | | values: [], |
| | | blockValues: merged.blockValues, |
| | | plateValues: merged.plateValues, |
| | | }; |
| | | } else { |
| | | const list = Array.isArray(payload[cat]) ? payload[cat] : []; |
| | | const field = productionOutputKey[cat] || "totalOutput"; |
| | | productionChartSeries.value = { |
| | | categories: list.map(i => i.dateStr), |
| | | mode: "single", |
| | | values: list.map(i => Number(i[field]) || 0), |
| | | blockValues: [], |
| | | plateValues: [], |
| | | }; |
| | | } |
| | | updateCharts(); |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadMiddleRingStats = async () => { |
| | | try { |
| | | const res = await getProductionStatisticsMiddle(); |
| | | const d = res.data && typeof res.data === "object" ? res.data : {}; |
| | | middleRingStats.value = { |
| | | flyAshMonth: Number(d.flyAshMonth) || 0, |
| | | flyAshYear: Number(d.flyAshYear) || 0, |
| | | gypsumMonth: Number(d.gypsumMonth) || 0, |
| | | gypsumYear: Number(d.gypsumYear) || 0, |
| | | blockMonth: Number(d.blockMonth) || 0, |
| | | blockYear: Number(d.blockYear) || 0, |
| | | plateMonth: Number(d.plateMonth) || 0, |
| | | plateYear: Number(d.plateYear) || 0, |
| | | }; |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadSolidWasteData = async () => { |
| | | const dateType = mapTimeDimensionToDateType(customerTimeDimension.value); |
| | | try { |
| | | const res = await getProductionStatisticsSolidWaste({ dateType }); |
| | | const list = Array.isArray(res.data) ? res.data : []; |
| | | solidWasteChartSeries.value = { |
| | | categories: list.map(i => i.dateStr), |
| | | total: list.map(i => Number(i.total) || 0), |
| | | flyAsh: list.map(i => Number(i.flyAsh) || 0), |
| | | gypsum: list.map(i => Number(i.gypsum) || 0), |
| | | lime: list.map(i => Number(i.lime) || 0), |
| | | }; |
| | | updateCharts(); |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const loadEnergyData = async () => { |
| | | const dateType = mapTimeDimensionToDateType(salesTimeDimension.value); |
| | | try { |
| | | const res = await getProductionStatisticsEnergy({ dateType }); |
| | | const list = Array.isArray(res.data) ? res.data : []; |
| | | energyChartSeries.value = { |
| | | categories: list.map(i => i.dateStr), |
| | | water: list.map(i => Number(i.water) || 0), |
| | | electricity: list.map(i => Number(i.electricity) || 0), |
| | | steam: list.map(i => Number(i.steam) || 0), |
| | | }; |
| | | updateCharts(); |
| | | } catch (e) { |
| | | console.error(e); |
| | | } |
| | | }; |
| | | |
| | | const selectBlockMaterial = name => { |
| | | blockSelectedMaterial.value = name; |
| | | loadBlockCost(); |
| | | }; |
| | | |
| | | const selectBoardMaterial = name => { |
| | | boardSelectedMaterial.value = name; |
| | | loadBoardCost(); |
| | | }; |
| | | |
| | | const selectProductionCategory = cat => { |
| | | productionCategory.value = cat; |
| | | loadMaterialProduction(); |
| | | }; |
| | | |
| | | // 初始化图表 |
| | | const initCharts = () => { |
| | | // 初始化砌块成本图表 |
| | |
| | | updateCharts(); |
| | | }; |
| | | |
| | | /** 砌块/板材容器高度变化后(如隐藏汇总卡)需 resize,否则底部留白 */ |
| | | const resizeCostPanelCharts = () => { |
| | | nextTick(() => { |
| | | requestAnimationFrame(() => { |
| | | blockCostChartInstance?.resize(); |
| | | boardCostChartInstance?.resize(); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 更新图表 |
| | | const updateCharts = () => { |
| | | // 更新砌块成本图表 |
| | | // 更新砌块成本图表(replaceMerge:全部↔单物料时横轴时间/物料切换) |
| | | if (blockCostChartInstance) { |
| | | blockCostChartInstance.setOption(blockCostChartOption.value); |
| | | blockCostChartInstance.setOption(blockCostChartOption.value, { |
| | | replaceMerge: ["series", "xAxis"], |
| | | }); |
| | | } |
| | | |
| | | // 更新板材成本图表 |
| | | if (boardCostChartInstance) { |
| | | boardCostChartInstance.setOption(boardCostChartOption.value); |
| | | boardCostChartInstance.setOption(boardCostChartOption.value, { |
| | | replaceMerge: ["series", "xAxis"], |
| | | }); |
| | | } |
| | | |
| | | // 更新产量分析图表 |
| | | // 更新产量分析图表(replaceMerge:全部↔砌块/板材切换时去掉多余折线,避免合并残留) |
| | | if (productionChartInstance) { |
| | | productionChartInstance.setOption(productionChartOption.value); |
| | | productionChartInstance.setOption(productionChartOption.value, { |
| | | replaceMerge: ["series"], |
| | | }); |
| | | } |
| | | |
| | | // 更新新增客户趋势图表 |
| | |
| | | if (salesRankingChartInstance) { |
| | | salesRankingChartInstance.setOption(salesRankingChartOption.value); |
| | | } |
| | | |
| | | resizeCostPanelCharts(); |
| | | }; |
| | | |
| | | // 处理时间维度选择 |
| | | const handleBlockTimeDimensionChange = dimension => { |
| | | blockTimeDimension.value = dimension; |
| | | updateCharts(); |
| | | loadBlockCost(); |
| | | }; |
| | | |
| | | const handleBoardTimeDimensionChange = dimension => { |
| | | boardTimeDimension.value = dimension; |
| | | updateCharts(); |
| | | loadBoardCost(); |
| | | }; |
| | | |
| | | const handleProductionTimeDimensionChange = dimension => { |
| | | productionTimeDimension.value = dimension; |
| | | updateCharts(); |
| | | loadMaterialProduction(); |
| | | }; |
| | | |
| | | const handleCustomerTimeDimensionChange = dimension => { |
| | | customerTimeDimension.value = dimension; |
| | | updateCharts(); |
| | | loadSolidWasteData(); |
| | | }; |
| | | |
| | | const handleSalesTimeDimensionChange = dimension => { |
| | | salesTimeDimension.value = dimension; |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // 处理材料类型选择 |
| | | const handleBlockMaterialTypeChange = type => { |
| | | blockMaterialType.value = type; |
| | | updateCharts(); |
| | | }; |
| | | |
| | | const handleBoardMaterialTypeChange = type => { |
| | | boardMaterialType.value = type; |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // 处理销售区选择 |
| | | const handleAreaChange = area => { |
| | | selectedArea.value = area; |
| | | updateCharts(); |
| | | loadEnergyData(); |
| | | }; |
| | | |
| | | // 监听窗口大小变化 |
| | |
| | | }, 1000); |
| | | } |
| | | |
| | | // 等待DOM更新后初始化图表 |
| | | nextTick(() => { |
| | | // 等待DOM更新后初始化图表并拉取接口数据 |
| | | nextTick(async () => { |
| | | initCharts(); |
| | | if (typeof ResizeObserver !== "undefined") { |
| | | if (blockCostChart.value) { |
| | | blockCostChartResizeObserver = new ResizeObserver(() => { |
| | | blockCostChartInstance?.resize(); |
| | | }); |
| | | blockCostChartResizeObserver.observe(blockCostChart.value); |
| | | } |
| | | if (boardCostChart.value) { |
| | | boardCostChartResizeObserver = new ResizeObserver(() => { |
| | | boardCostChartInstance?.resize(); |
| | | }); |
| | | boardCostChartResizeObserver.observe(boardCostChart.value); |
| | | } |
| | | } |
| | | await Promise.all([ |
| | | loadBlockMaterials(), |
| | | loadBoardMaterials(), |
| | | loadMaterialProduction(), |
| | | loadMiddleRingStats(), |
| | | loadSolidWasteData(), |
| | | loadEnergyData(), |
| | | ]); |
| | | resizeCostPanelCharts(); |
| | | }); |
| | | |
| | | // 添加窗口大小变化监听 |
| | |
| | | clearInterval(timeTicker); |
| | | timeTicker = null; |
| | | } |
| | | |
| | | blockCostChartResizeObserver?.disconnect(); |
| | | blockCostChartResizeObserver = null; |
| | | boardCostChartResizeObserver?.disconnect(); |
| | | boardCostChartResizeObserver = null; |
| | | |
| | | if (blockCostChartInstance) { |
| | | blockCostChartInstance.dispose(); |
| | |
| | | } |
| | | |
| | | /* .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); |
| | | } |
| | |
| | | margin-bottom: 0.2vh; |
| | | } |
| | | |
| | | /* 砌块/板材:图表占满 Tab 与汇总卡下方的剩余高度;隐藏汇总卡时自动拉高 */ |
| | | .bi-panel-top-left, |
| | | .bi-panel-bottom-left { |
| | | min-height: 0; |
| | | } |
| | | |
| | | .bi-panel-top-left .bi-panel-body, |
| | | .bi-panel-bottom-left .bi-panel-body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .bi-panel-top-left .chart-filter-tabs, |
| | | .bi-panel-bottom-left .chart-filter-tabs { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .bi-panel-top-left .echart-fill, |
| | | .bi-panel-bottom-left .echart-fill { |
| | | height: 24vh; |
| | | flex: 1; |
| | | min-height: 0; |
| | | height: auto; |
| | | width: 100%; |
| | | } |
| | | |
| | | .bi-panel-bottom-right .echart-fill { |
| | |
| | | grid-row: 1 / span 2; |
| | | position: absolute; |
| | | background: url("@/assets/BI/imageSS@2x.png") no-repeat bottom center; |
| | | background-size: 100% 30%; |
| | | background-size: 80% 30%; |
| | | left: 25%; |
| | | top: 25%; |
| | | transform: translate(-50%, -50%); |
| | | width: 60vh; |
| | | transform: translate(-50%, -45%); |
| | | width: 50%; |
| | | height: 40.5vh; |
| | | z-index: 3; |
| | | pointer-events: none; |
| | |
| | | .center-ring-box { |
| | | position: absolute; |
| | | /* inset: 0; */ |
| | | height: 100%; |
| | | height: 88%; |
| | | width: 100%; |
| | | margin-top: 3%; |
| | | /* background-color: #fff; */ |
| | | background: url("@/assets/BI/imageSStop.png") no-repeat center center; |
| | | background-size: 80% 90%; |
| | | background: url("@/assets/BI/SCbg.png") no-repeat center center; |
| | | background-size: 100% 100%; |
| | | } |
| | | .ring-box-topright { |
| | | position: absolute; |
| | | top: 6vh; |
| | | right: 0; |
| | | width: 25%; |
| | | height: 15%; |
| | | background: url("@/assets/BI/SCbgright.png") no-repeat center center; |
| | | background-size: 100% 100%; |
| | | text-align: right; |
| | | } |
| | | .ring-box-topleft { |
| | | position: absolute; |
| | | top: 6vh; |
| | | left: 0; |
| | | width: 25%; |
| | | height: 15%; |
| | | background: url("@/assets/BI/SCbgleft.png") no-repeat center center; |
| | | background-size: 100% 100%; |
| | | text-align: left; |
| | | } |
| | | .topright-label { |
| | | font-size: 1.8vh; |
| | | font-weight: 500; |
| | | color: rgba(234, 246, 255, 0.9); |
| | | margin-top: 0; |
| | | position: relative; |
| | | bottom: 3vh; |
| | | right: 1vh; |
| | | } |
| | | |
| | | .topleft-label { |
| | | font-size: 1.8vh; |
| | | font-weight: 500; |
| | | color: rgba(234, 246, 255, 0.9); |
| | | margin-top: 0; |
| | | position: relative; |
| | | bottom: 3vh; |
| | | left: 1vh; |
| | | } |
| | | .ring-box-left { |
| | | /* background-color: #ebebeb; */ |
| | | width: 30%; |
| | | position: absolute; |
| | | left: 1vh; |
| | | top: 56%; |
| | | transform: translateY(-50%); |
| | | } |
| | | .left-label { |
| | | font-size: 1.4vh; |
| | | font-weight: 500; |
| | | color: #0effef; |
| | | margin-top: 0; |
| | | position: relative; |
| | | bottom: 3vh; |
| | | } |
| | | .left-value { |
| | | font-size: 1.2vh; |
| | | font-weight: 500; |
| | | color: rgba(234, 246, 255, 0.9); |
| | | margin-top: 0; |
| | | position: relative; |
| | | bottom: 3vh; |
| | | margin-top: 0.4vh; |
| | | } |
| | | .ring-box-right { |
| | | /* background-color: #ebebeb; */ |
| | | width: 30%; |
| | | float: right; |
| | | position: absolute; |
| | | right: -1vh; |
| | | top: 56%; |
| | | transform: translateY(-50%); |
| | | } |
| | | .right-label { |
| | | font-size: 1.4vh; |
| | | font-weight: 500; |
| | | color: #ffa60e; |
| | | margin-top: 0; |
| | | position: relative; |
| | | bottom: 3vh; |
| | | } |
| | | |
| | | .right-value { |
| | | font-size: 1.2vh; |
| | | font-weight: 500; |
| | | color: rgba(234, 246, 255, 0.9); |
| | | margin-top: 0; |
| | | position: relative; |
| | | bottom: 3vh; |
| | | margin-top: 0.4vh; |
| | | } |
| | | |
| | | .center-ring-bg { |