| | |
| | | </div> |
| | | </template> |
| | | <div class="chart-wrap"> |
| | | <div class="chart-tools chart-tools-inline" @click.stop> |
| | | <button class="chart-tool" type="button" @click="openLargeChart">查看大图</button> |
| | | <button class="chart-tool" type="button" @click="downloadChartImage">下载图表</button> |
| | | </div> |
| | | <div ref="chartRef" class="chart-content"></div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <el-dialog |
| | | v-model="largeChartVisible" |
| | | title="标准/实际成本对比大图" |
| | | width="88%" |
| | | top="6vh" |
| | | destroy-on-close |
| | | @opened="initLargeChart" |
| | | @closed="disposeLargeChart" |
| | | > |
| | | <div ref="largeChartRef" class="large-chart-content"></div> |
| | | </el-dialog> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <template #header> |
| | |
| | | |
| | | const uploadRef = ref(); |
| | | const chartRef = ref(null); |
| | | const largeChartRef = ref(null); |
| | | let chartInstance = null; |
| | | let largeChartInstance = null; |
| | | const largeChartVisible = ref(false); |
| | | const currentChartOption = ref(null); |
| | | |
| | | const actualCostSource = ref([ |
| | | { month: "2026-01", category: "瓷砖", costType: "能耗成本", actualCost: 182000 }, |
| | |
| | | return { xAxis, standard, actual, diffRate }; |
| | | }; |
| | | |
| | | const updateChart = () => { |
| | | if (!chartInstance) return; |
| | | const buildChartOption = () => { |
| | | const { xAxis, standard, actual, diffRate } = getChartData(); |
| | | chartInstance.setOption({ |
| | | return { |
| | | tooltip: { |
| | | trigger: "axis", |
| | | axisPointer: { type: "shadow" }, |
| | |
| | | lineStyle: { width: 2 }, |
| | | }, |
| | | ], |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | const updateChart = () => { |
| | | const option = buildChartOption(); |
| | | currentChartOption.value = option; |
| | | chartInstance?.setOption(option); |
| | | largeChartInstance?.setOption(option); |
| | | }; |
| | | |
| | | const normalizeCostType = (value) => { |
| | |
| | | |
| | | const handleResize = () => { |
| | | chartInstance?.resize?.(); |
| | | largeChartInstance?.resize?.(); |
| | | }; |
| | | |
| | | const openLargeChart = () => { |
| | | if (!tableData.value.length) { |
| | | ElMessage.warning("暂无图表数据可查看"); |
| | | return; |
| | | } |
| | | largeChartVisible.value = true; |
| | | }; |
| | | |
| | | const initLargeChart = () => { |
| | | nextTick(() => { |
| | | if (!largeChartRef.value) return; |
| | | if (!largeChartInstance) { |
| | | largeChartInstance = echarts.init(largeChartRef.value); |
| | | } |
| | | if (currentChartOption.value) { |
| | | largeChartInstance.setOption(currentChartOption.value); |
| | | } else { |
| | | updateChart(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const disposeLargeChart = () => { |
| | | largeChartInstance?.dispose?.(); |
| | | largeChartInstance = null; |
| | | }; |
| | | |
| | | const downloadChartImage = () => { |
| | | const sourceChart = chartInstance || largeChartInstance; |
| | | if (!sourceChart) { |
| | | ElMessage.warning("图表尚未加载完成"); |
| | | return; |
| | | } |
| | | const url = sourceChart.getDataURL({ |
| | | type: "png", |
| | | pixelRatio: 2, |
| | | backgroundColor: "#ffffff", |
| | | }); |
| | | const link = document.createElement("a"); |
| | | link.href = url; |
| | | link.download = `标准实际成本对比图_${new Date().toISOString().slice(0, 10)}.png`; |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | document.body.removeChild(link); |
| | | ElMessage.success("图表下载成功"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | window.removeEventListener("resize", handleResize); |
| | | chartInstance?.dispose?.(); |
| | | chartInstance = null; |
| | | disposeLargeChart(); |
| | | }); |
| | | |
| | | watch(tableData, () => { |
| | |
| | | } |
| | | |
| | | .chart-wrap { |
| | | position: relative; |
| | | padding-top: 34px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | |
| | | height: 360px; |
| | | } |
| | | |
| | | .chart-tools { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .chart-tools-inline { |
| | | position: absolute; |
| | | top: 4px; |
| | | right: 6px; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .chart-tool { |
| | | font-size: 11px; |
| | | font-weight: 650; |
| | | line-height: 1; |
| | | padding: 6px 10px; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(15, 23, 42, 0.1); |
| | | background: rgba(255, 255, 255, 0.78); |
| | | color: rgba(15, 23, 42, 0.72); |
| | | cursor: pointer; |
| | | transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease; |
| | | } |
| | | |
| | | .chart-tool:hover { |
| | | background: rgba(47, 111, 237, 0.08); |
| | | border-color: rgba(47, 111, 237, 0.22); |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .large-chart-content { |
| | | height: 70vh; |
| | | min-height: 520px; |
| | | } |
| | | |
| | | .pagination-container { |
| | | display: flex; |
| | | justify-content: flex-end; |