| | |
| | | <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(); |
| | | }); |