| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- <!--
- StationYearCompare.vue - 单站多年水质对比组件
- 功能说明:
- 一河三湖专题下的子组件。同一站点不同年份的水质数据对比。
- 1. 站点选择 + 多年份选择(2020-2026)
- 2. ECharts 折线图:各年份月度水温(wt) 均值对比
- 3. 数据表格 + 分页
- 4. 数据来源:mock 生成(generateMockData)
- -->
- <script setup>
- import { ref, onMounted, nextTick, watch } from 'vue'
- import * as echarts from 'echarts'
- import { ElSelect, ElOption, ElButton, ElTable, ElTableColumn, ElPagination } from 'element-plus'
- // 站点列表
- const stations = ref([
- { value: 'TPL001', label: '太浦河水保区' },
- { value: 'JSS001', label: '吴江水质站' },
- { value: 'QPS001', label: '青浦水质站' },
- { value: 'JXS001', label: '嘉善水质站' },
- { value: 'SZS001', label: '苏州水质站' }
- ])
- // 年份列表
- const years = ref([
- { value: '2020', label: '2020年' },
- { value: '2021', label: '2021年' },
- { value: '2022', label: '2022年' },
- { value: '2023', label: '2023年' },
- { value: '2024', label: '2024年' },
- { value: '2025', label: '2025年' },
- { value: '2026', label: '2026年' }
- ])
- // 查询条件
- const selectedStation = ref('TPL001')
- const selectedYears = ref(['2024', '2025', '2026'])
- // 图表实例
- const chartRef = ref(null)
- let chartInstance = null
- // 表格数据
- const tableData = ref([])
- const total = ref(0)
- const pageSize = ref(10)
- const currentPage = ref(1)
- // 模拟数据生成函数
- function generateMockData(stationCode, year) {
- const baseData = {
- 'TPL001': { name: '太浦河水保区', unit: '浙江省生态环境厅', baseTemp: 17, basePh: 8, baseCond: 9.3, baseDo: 7.2 },
- 'JSS001': { name: '吴江水质站', unit: '江苏省生态环境厅', baseTemp: 18, basePh: 7.8, baseCond: 8.5, baseDo: 6.8 },
- 'QPS001': { name: '青浦水质站', unit: '上海市生态环境局', baseTemp: 16.5, basePh: 8.2, baseCond: 9.0, baseDo: 7.5 },
- 'JXS001': { name: '嘉善水质站', unit: '浙江省生态环境厅', baseTemp: 17.5, basePh: 7.9, baseCond: 8.8, baseDo: 7.0 },
- 'SZS001': { name: '苏州水质站', unit: '江苏省生态环境厅', baseTemp: 18.2, basePh: 7.7, baseCond: 8.6, baseDo: 6.5 }
- }
-
- const stationInfo = baseData[stationCode] || baseData['TPL001']
- const data = []
-
- // 每年生成12个月的数据
- for (let month = 1; month <= 12; month++) {
- const monthStr = `${year}-${String(month).padStart(2, '0')}-15`
- // 添加季节性波动
- const seasonFactor = Math.sin((month - 3) * Math.PI / 6) * 2
- const temp = stationInfo.baseTemp + seasonFactor + (Math.random() - 0.5) * 2
- const ph = stationInfo.basePh + (Math.random() - 0.5) * 0.3
- const cond = stationInfo.baseCond + seasonFactor * 0.3 + (Math.random() - 0.5) * 1
- const doVal = stationInfo.baseDo - seasonFactor * 0.3 + (Math.random() - 0.5) * 0.8
- const codmn = 3.5 + Math.random() * 1.5
- const mn = 0.05 + Math.random() * 0.2
- const tn = 1.2 + Math.random() * 0.8
- const tp = 0.03 + Math.random() * 0.08
-
- data.push({
- year: year,
- month: month,
- monthStr: `${year}年${month}月`,
- stnm: stationInfo.name,
- unit: stationInfo.unit,
- tm: monthStr,
- wt: temp.toFixed(1),
- ph: ph.toFixed(1),
- cond: cond.toFixed(1),
- dox: doVal.toFixed(1),
- codmn: codmn.toFixed(2),
- mn: mn.toFixed(2),
- tn: tn.toFixed(2),
- tp: tp.toFixed(2)
- })
- }
-
- return {
- stationName: stationInfo.name,
- unit: stationInfo.unit,
- year: year,
- rows: data
- }
- }
- // 初始化图表
- function initChart() {
- if (!chartRef.value) return
- chartInstance = echarts.init(chartRef.value)
- }
- // 更新图表
- function updateChart(allYearData) {
- if (!chartInstance || !allYearData || allYearData.length === 0) return
-
- const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
- const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F']
-
- const series = []
-
- selectedYears.value.forEach((year, idx) => {
- const yearData = allYearData.find(d => d.year === year)
- if (yearData && yearData.rows) {
- const codmnData = yearData.rows.map(row => parseFloat(row.codmn))
- series.push({
- name: `${year}年`,
- type: 'line',
- data: codmnData,
- smooth: true,
- symbol: 'circle',
- symbolSize: 6,
- itemStyle: { color: colors[idx % colors.length] },
- lineStyle: { width: 2 }
- })
- }
- })
-
- const option = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross'
- }
- },
- legend: {
- data: selectedYears.value.map(y => `${y}年`),
- top: 10,
- textStyle: { fontSize: 11 },
- itemWidth: 15,
- itemHeight: 10
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- top: 60,
- containLabel: true
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- data: months,
- axisLabel: { fontSize: 10 }
- },
- yAxis: {
- type: 'value',
- name: '高锰酸盐指数(mg/L)',
- axisLabel: { fontSize: 10 }
- },
- series: series
- }
-
- chartInstance.setOption(option)
- }
- // 查询数据
- function queryData() {
- const allYearData = []
- let allRows = []
-
- selectedYears.value.forEach(year => {
- const yearData = generateMockData(selectedStation.value, year)
- allYearData.push(yearData)
- allRows = allRows.concat(yearData.rows)
- })
-
- total.value = allRows.length
-
- // 分页处理
- const start = (currentPage.value - 1) * pageSize.value
- const end = start + pageSize.value
- tableData.value = allRows.slice(start, end)
-
- // 更新图表
- nextTick(() => {
- if (!chartInstance) {
- initChart()
- }
- updateChart(allYearData)
- })
- }
- // 分页变化
- function handlePageChange(page) {
- currentPage.value = page
- queryData()
- }
- // 处理窗口resize
- function handleResize() {
- chartInstance && chartInstance.resize()
- }
- watch([selectedStation, selectedYears], () => {
- currentPage.value = 1
- })
- onMounted(() => {
- nextTick(() => {
- initChart()
- queryData()
- })
- window.addEventListener('resize', handleResize)
- })
- </script>
- <template>
- <div class="year-compare-container">
- <!-- 查询栏 -->
- <div class="query-bar">
- <div class="query-item">
- <span class="query-label">站点选择:</span>
- <ElSelect v-model="selectedStation" class="query-select" style="width: 180px;">
- <ElOption v-for="station in stations" :key="station.value" :label="station.label" :value="station.value" />
- </ElSelect>
- </div>
- <div class="query-item">
- <span class="query-label">年份选择:</span>
- <ElSelect v-model="selectedYears" multiple class="query-select" style="width: 280px;">
- <ElOption v-for="year in years" :key="year.value" :label="year.label" :value="year.value" />
- </ElSelect>
- </div>
- <ElButton type="primary" @click="queryData" class="query-btn">查询</ElButton>
- </div>
-
- <!-- 图表区域 -->
- <div class="chart-section">
- <div class="chart-header">
- <span class="chart-title">[{{ stations.find(s => s.value === selectedStation)?.label || '' }}] 多年度水质数据对比(高锰酸盐指数)</span>
- </div>
- <div ref="chartRef" class="chart-container"></div>
- </div>
-
- <!-- 表格区域 -->
- <div class="table-section">
- <ElTable :data="tableData" border class="data-table" :show-header="true">
- <ElTableColumn prop="year" label="年份" align="center" />
- <ElTableColumn prop="month" label="月份" align="center" />
- <ElTableColumn prop="stnm" label="站名" align="center" />
- <ElTableColumn prop="unit" label="共享单位" align="center" />
- <ElTableColumn prop="wt" label="水温(℃)" align="center" />
- <ElTableColumn prop="ph" label="PH" align="center" />
- <ElTableColumn prop="cond" label="电导率" align="center" />
- <ElTableColumn prop="dox" label="溶解氧(mg/L)" align="center" />
- <ElTableColumn prop="codmn" label="高锰酸盐(mg/L)" align="center" />
- <ElTableColumn prop="mn" label="氨氮(mg/L)" align="center" />
- <ElTableColumn prop="tn" label="总氮(mg/L)" align="center" />
- <ElTableColumn prop="tp" label="总磷(mg/L)" align="center" />
- </ElTable>
-
- <!-- 分页 -->
- <div class="pagination-wrapper">
- <span class="pagination-info">共 {{ total }} 条</span>
- <ElPagination
- :current-page="currentPage"
- :page-size="pageSize"
- :total="total"
- @current-change="handlePageChange"
- layout="prev, pager, next, jumper"
- />
- </div>
- </div>
- </div>
- </template>
- <style scoped>
- .year-compare-container {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- padding: 15px;
- background: #fff;
- overflow: hidden;
- }
- .query-bar {
- display: flex;
- align-items: center;
- gap: 15px;
- padding: 15px 20px;
- background: #f8f9fa;
- border-radius: 4px;
- margin-bottom: 15px;
- flex-wrap: wrap;
- }
- .query-item {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .query-label {
- font-size: 14px;
- color: #333;
- }
- .query-select {
- font-size: 14px;
- }
- .query-btn {
- margin-left: auto;
- }
- .chart-section {
- flex: 1;
- min-height: 300px;
- margin-bottom: 15px;
- display: flex;
- flex-direction: column;
- }
- .chart-header {
- padding: 10px 15px;
- background: linear-gradient(90deg, #1E9FFF 0%, #4DA3FF 100%);
- border-radius: 4px 4px 0 0;
- }
- .chart-title {
- font-size: 14px;
- font-weight: bold;
- color: #fff;
- }
- .chart-container {
- flex: 1;
- border: 1px solid #e8e8e8;
- border-top: none;
- border-radius: 0 0 4px 4px;
- min-height: 280px;
- }
- .table-section {
- flex-shrink: 0;
- max-height: 250px;
- display: flex;
- flex-direction: column;
- }
- .data-table {
- flex: 1;
- overflow-y: auto;
- }
- .data-table th {
- background: #f5f5f5;
- font-weight: bold;
- font-size: 13px;
- }
- .data-table td {
- font-size: 12px;
- padding: 8px 5px;
- }
- .pagination-wrapper {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 15px;
- padding: 10px 15px;
- background: #f8f9fa;
- border-top: 1px solid #e8e8e8;
- }
- .pagination-info {
- font-size: 13px;
- color: #666;
- }
- </style>
|