| | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onBeforeUnmount, nextTick } from "vue"; |
| | | import * as echarts from "echarts"; |
| | | import { |
| | | getSolidWasteTrends, |
| | | getSolidWasteCoreIndicators, |
| | | getSolidWasteTypeDistribution, |
| | | } from "@/api/solidWaste/index.js"; |
| | | |
| | | // 筛选条件 |
| | | const dateType = ref("month"); // month 或 year |
| | | // 筛选条件:月度 -> dateType 1,年度 -> 2 |
| | | const dateType = ref("month"); |
| | | |
| | | // 图表引用 |
| | | const trendChart = ref(null); |
| | | const distributionChart = ref(null); |
| | | |
| | | // 图表实例 |
| | | let trendChartInstance = null; |
| | | let distributionChartInstance = null; |
| | | |
| | | // 模拟数据 |
| | | const solidWasteData = ref({ |
| | | month: [ |
| | | { name: "1月", 粉煤灰: 200, 石膏: 150, 石灰: 100 }, |
| | | { name: "2月", 粉煤灰: 220, 石膏: 160, 石灰: 110 }, |
| | | { name: "3月", 粉煤灰: 190, 石膏: 140, 石灰: 95 }, |
| | | { name: "4月", 粉煤灰: 230, 石膏: 170, 石灰: 115 }, |
| | | { name: "5月", 粉煤灰: 240, 石膏: 180, 石灰: 120 }, |
| | | { name: "6月", 粉煤灰: 225, 石膏: 165, 石灰: 112 }, |
| | | ], |
| | | year: [ |
| | | { name: "2022", 粉煤灰: 2300, 石膏: 1700, 石灰: 1100 }, |
| | | { name: "2023", 粉煤灰: 2500, 石膏: 1800, 石灰: 1200 }, |
| | | { name: "2024", 粉煤灰: 2700, 石膏: 1950, 石灰: 1300 }, |
| | | { name: "2025", 粉煤灰: 2900, 石膏: 2100, 石灰: 1400 }, |
| | | ], |
| | | }); |
| | | /** @type {import('vue').Ref<Array<{ dateStr: string, total?: number, flyAsh: number, gypsum: number, lime: number }>>} */ |
| | | const trendsList = ref([]); |
| | | |
| | | // 计算属性 |
| | | const totalSolidWaste = computed(() => { |
| | | const data = solidWasteData.value[dateType.value]; |
| | | if (dateType.value === "month") { |
| | | return data.reduce( |
| | | (sum, item) => sum + item.粉煤灰 + item.石膏 + item.石灰, |
| | | 0 |
| | | ); |
| | | } else { |
| | | const lastItem = data[data.length - 1]; |
| | | return lastItem.粉煤灰 + lastItem.石膏 + lastItem.石灰; |
| | | } |
| | | }); |
| | | /** 固废类型分布 /home/solidWaste/typeDistribution */ |
| | | const typeDistributionList = ref([]); |
| | | |
| | | const totalSolidWasteSince2022 = computed(() => { |
| | | const data = solidWasteData.value.year; |
| | | return data.reduce( |
| | | (sum, item) => sum + item.粉煤灰 + item.石膏 + item.石灰, |
| | | 0 |
| | | ); |
| | | }); |
| | | /** 核心指标 /home/solidWaste/coreIndicators */ |
| | | const totalSolidWaste = ref(0); |
| | | const totalSolidWasteSince2022 = ref(0); |
| | | |
| | | const wasteTableData = computed(() => { |
| | | const data = solidWasteData.value[dateType.value]; |
| | | const list = trendsList.value || []; |
| | | const result = []; |
| | | |
| | | data.forEach(item => { |
| | | list.forEach(row => { |
| | | result.push({ |
| | | time: item.name, |
| | | time: row.dateStr, |
| | | type: "粉煤灰", |
| | | quantity: item.粉煤灰, |
| | | quantity: Number(row.flyAsh) || 0, |
| | | unit: "吨", |
| | | source: "生产过程", |
| | | }); |
| | | result.push({ |
| | | time: item.name, |
| | | time: row.dateStr, |
| | | type: "石膏", |
| | | quantity: item.石膏, |
| | | quantity: Number(row.gypsum) || 0, |
| | | unit: "吨", |
| | | source: "生产过程", |
| | | }); |
| | | result.push({ |
| | | time: item.name, |
| | | time: row.dateStr, |
| | | type: "石灰", |
| | | quantity: item.石灰, |
| | | quantity: Number(row.lime) || 0, |
| | | unit: "吨", |
| | | source: "生产过程", |
| | | }); |
| | | }); |
| | | |
| | | return result; |
| | | }); |
| | | |
| | | // 图表配置 |
| | | const trendChartOption = computed(() => { |
| | | const data = solidWasteData.value[dateType.value]; |
| | | const list = trendsList.value || []; |
| | | return { |
| | | tooltip: { |
| | | trigger: "axis", |
| | |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: data.map(item => item.name), |
| | | data: list.map(item => item.dateStr), |
| | | axisLabel: { |
| | | color: "#333", |
| | | }, |
| | |
| | | { |
| | | name: "粉煤灰", |
| | | type: "bar", |
| | | data: data.map(item => item.粉煤灰), |
| | | data: list.map(item => Number(item.flyAsh) || 0), |
| | | itemStyle: { |
| | | color: "#909399", |
| | | }, |
| | |
| | | { |
| | | name: "石膏", |
| | | type: "bar", |
| | | data: data.map(item => item.石膏), |
| | | data: list.map(item => Number(item.gypsum) || 0), |
| | | itemStyle: { |
| | | color: "#E6A23C", |
| | | }, |
| | |
| | | { |
| | | name: "石灰", |
| | | type: "bar", |
| | | data: data.map(item => item.石灰), |
| | | data: list.map(item => Number(item.lime) || 0), |
| | | itemStyle: { |
| | | color: "#F56C6C", |
| | | }, |
| | |
| | | }; |
| | | }); |
| | | |
| | | const typePieColor = name => { |
| | | const map = { 粉煤灰: "#909399", 石膏: "#E6A23C", 石灰: "#F56C6C" }; |
| | | return map[name] || "#909399"; |
| | | }; |
| | | |
| | | const distributionChartOption = computed(() => { |
| | | const data = solidWasteData.value[dateType.value]; |
| | | const lastItem = data[data.length - 1]; |
| | | const list = typeDistributionList.value || []; |
| | | const pieData = list.map(item => ({ |
| | | name: item.name, |
| | | value: parseFloat(String(item.value ?? 0)) || 0, |
| | | rate: item.rate, |
| | | })); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "item", |
| | | formatter: "{a} <br/>{b}: {c} ({d}%)", |
| | | formatter: params => { |
| | | const rate = |
| | | params.data?.rate != null && params.data.rate !== "" |
| | | ? `${params.data.rate}%` |
| | | : `${params.percent}%`; |
| | | return `${params.seriesName}<br/>${params.marker}${params.name}: ${params.value} 吨 (${rate})`; |
| | | }, |
| | | }, |
| | | legend: { |
| | | orient: "vertical", |
| | | left: "left", |
| | | data: pieData.map(d => d.name), |
| | | textStyle: { |
| | | color: "#333", |
| | | }, |
| | |
| | | type: "pie", |
| | | radius: "60%", |
| | | center: ["50%", "50%"], |
| | | data: [ |
| | | { value: lastItem.粉煤灰, name: "粉煤灰" }, |
| | | { value: lastItem.石膏, name: "石膏" }, |
| | | { value: lastItem.石灰, name: "石灰" }, |
| | | ], |
| | | data: pieData, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | |
| | | }, |
| | | }, |
| | | itemStyle: { |
| | | color: function (params) { |
| | | const colors = ["#909399", "#E6A23C", "#F56C6C"]; |
| | | return colors[params.dataIndex]; |
| | | }, |
| | | color: params => typePieColor(params.name), |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // 事件处理 |
| | | const handleDateTypeChange = () => { |
| | | const mapDateTypeParam = () => (dateType.value === "year" ? "2" : "1"); |
| | | |
| | | const loadCoreIndicators = async () => { |
| | | try { |
| | | const res = await getSolidWasteCoreIndicators({ |
| | | dateType: mapDateTypeParam(), |
| | | }); |
| | | const d = res.data && typeof res.data === "object" ? res.data : {}; |
| | | totalSolidWaste.value = Number(d.totalAmount) || 0; |
| | | totalSolidWasteSince2022.value = Number(d.cumulativeAmount) || 0; |
| | | } catch (e) { |
| | | console.error(e); |
| | | totalSolidWaste.value = 0; |
| | | totalSolidWasteSince2022.value = 0; |
| | | } |
| | | }; |
| | | |
| | | const loadTrends = async () => { |
| | | try { |
| | | const res = await getSolidWasteTrends({ dateType: mapDateTypeParam() }); |
| | | trendsList.value = Array.isArray(res.data) ? res.data : []; |
| | | } catch (e) { |
| | | console.error(e); |
| | | trendsList.value = []; |
| | | } |
| | | }; |
| | | |
| | | const loadTypeDistribution = async () => { |
| | | try { |
| | | const res = await getSolidWasteTypeDistribution({ |
| | | dateType: mapDateTypeParam(), |
| | | }); |
| | | typeDistributionList.value = Array.isArray(res.data) ? res.data : []; |
| | | } catch (e) { |
| | | console.error(e); |
| | | typeDistributionList.value = []; |
| | | } |
| | | }; |
| | | |
| | | const refreshDashboard = async () => { |
| | | await Promise.all([ |
| | | loadTrends(), |
| | | loadCoreIndicators(), |
| | | loadTypeDistribution(), |
| | | ]); |
| | | await nextTick(); |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // 初始化图表 |
| | | const initCharts = () => { |
| | | if (trendChart.value) { |
| | | trendChartInstance = echarts.init(trendChart.value); |
| | | trendChartInstance.setOption(trendChartOption.value); |
| | | } |
| | | |
| | | if (distributionChart.value) { |
| | | distributionChartInstance = echarts.init(distributionChart.value); |
| | | distributionChartInstance.setOption(distributionChartOption.value); |
| | | } |
| | | const handleDateTypeChange = () => { |
| | | refreshDashboard(); |
| | | }; |
| | | |
| | | // 更新图表 |
| | | const initCharts = () => { |
| | | if (trendChart.value && !trendChartInstance) { |
| | | trendChartInstance = echarts.init(trendChart.value); |
| | | } |
| | | if (distributionChart.value && !distributionChartInstance) { |
| | | distributionChartInstance = echarts.init(distributionChart.value); |
| | | } |
| | | updateCharts(); |
| | | }; |
| | | |
| | | const updateCharts = () => { |
| | | if (trendChartInstance) { |
| | | trendChartInstance.setOption(trendChartOption.value); |
| | | } |
| | | |
| | | if (distributionChartInstance) { |
| | | distributionChartInstance.setOption(distributionChartOption.value); |
| | | } |
| | | }; |
| | | |
| | | // 调整图表大小 |
| | | const resizeCharts = () => { |
| | | trendChartInstance?.resize(); |
| | | distributionChartInstance?.resize(); |
| | | }; |
| | | |
| | | // 窗口大小变化处理 |
| | | const handleResize = () => { |
| | | // 延迟执行,确保DOM更新完成 |
| | | setTimeout(() => { |
| | | resizeCharts(); |
| | | }, 100); |
| | | }; |
| | | |
| | | // 获取固废类型标签类型 |
| | | const getWasteTypeType = type => { |
| | | const typeMap = { |
| | | 粉煤灰: "info", |
| | |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // 生命周期钩子 |
| | | onMounted(() => { |
| | | // 使用nextTick确保DOM完全渲染后再初始化 |
| | | onMounted(async () => { |
| | | await refreshDashboard(); |
| | | nextTick(() => { |
| | | // 初始化图表 |
| | | initCharts(); |
| | | }); |
| | | |
| | | window.addEventListener("resize", handleResize); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener("resize", handleResize); |
| | | |
| | | // 销毁图表实例 |
| | | trendChartInstance?.dispose(); |
| | | distributionChartInstance?.dispose(); |
| | | }); |