project-api/src/main/resources/static/index.html

614 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水质数据查询系统</title>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script> -->
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/echarts@5.4.0/dist/echarts.min.js"></script>
<style>
:root {
--primary-color: #1e88e5;
--secondary-color: #e3f2fd;
--text-color: #333;
--light-gray: #f5f5f5;
--border-color: #ddd;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f9f9f9;
color: var(--text-color);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--primary-color);
color: white;
padding: 20px 0;
margin-bottom: 30px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
header h1 {
text-align: center;
font-weight: 300;
font-size: 2.5rem;
}
.card {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
}
.query-panel {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.form-group {
flex: 1;
min-width: 200px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
select, input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 16px;
}
button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #0d47a1;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab.active {
border-bottom: 2px solid var(--primary-color);
font-weight: bold;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: var(--secondary-color);
}
tr:nth-child(even) {
background-color: var(--light-gray);
}
.chart-container {
height: 400px;
margin-top: 20px;
}
.statistics-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-card {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
padding: 15px;
}
.stat-card h3 {
margin-bottom: 10px;
color: var(--primary-color);
}
.stat-value {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.stat-label {
font-weight: 500;
}
.loading {
text-align: center;
padding: 30px;
font-size: 18px;
color: #666;
}
.error {
color: #d32f2f;
padding: 10px;
background-color: #ffebee;
border-radius: 4px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.query-panel {
flex-direction: column;
}
.form-group {
width: 100%;
}
.statistics-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<h1>水质数据查询系统</h1>
</div>
</header>
<div class="container" id="app">
<div class="card">
<div class="query-panel">
<div class="form-group">
<label for="queryType">查询类型</label>
<select id="queryType" v-model="queryType">
<option value="river">按河道查询</option>
<option value="section">按断面查询</option>
</select>
</div>
<div class="form-group" v-if="queryType === 'river'">
<label for="river">选择河道</label>
<select id="river" v-model="selectedRiverId">
<option value="">请选择河道</option>
<option v-for="river in rivers" :key="river.riverId" :value="river.riverId">
{{ river.riverName }}
</option>
</select>
</div>
<div class="form-group" v-if="queryType === 'section'">
<label for="section">选择断面</label>
<select id="section" v-model="selectedSectionId">
<option value="">请选择断面</option>
<option v-for="section in sections" :key="section.sectionId" :value="section.sectionId">
{{ section.sectionName }}
</option>
</select>
</div>
<div class="form-group">
<label for="startDate">开始日期</label>
<input type="date" id="startDate" v-model="startDate">
</div>
<div class="form-group">
<label for="endDate">结束日期</label>
<input type="date" id="endDate" v-model="endDate">
</div>
<div class="form-group" style="align-self: flex-end;">
<button @click="queryData">查询数据</button>
</div>
</div>
<div v-if="error" class="error">
{{ error }}
</div>
</div>
<div class="card" v-if="loading">
<div class="loading">数据加载中...</div>
</div>
<div class="card" v-if="!loading && waterQualityData.length > 0">
<div class="tabs">
<div class="tab" :class="{ active: activeTab === 'table' }" @click="activeTab = 'table'">
表格数据
</div>
<div class="tab" :class="{ active: activeTab === 'chart' }" @click="activeTab = 'chart'">
图表分析
</div>
<div class="tab" :class="{ active: activeTab === 'statistics' }" @click="activeTab = 'statistics'; fetchStatistics()">
统计分析
</div>
</div>
<div class="tab-content" :class="{ active: activeTab === 'table' }">
<table>
<thead>
<tr>
<th>日期</th>
<th>水温 (°C)</th>
<th>pH值</th>
<th>溶解氧 (mg/L)</th>
<th>电导率 (μS/cm)</th>
<th>浊度 (NTU)</th>
<th>高锰酸盐指数 (mg/L)</th>
<th>氨氮 (mg/L)</th>
<th>总磷 (mg/L)</th>
<th>总氮 (mg/L)</th>
</tr>
</thead>
<tbody>
<tr v-for="item in waterQualityData" :key="item.dataId">
<td>{{ formatDate(item.dataDate) }}</td>
<td>{{ item.temperature }}</td>
<td>{{ item.ph }}</td>
<td>{{ item.dissolvedOxygen }}</td>
<td>{{ item.conductivity }}</td>
<td>{{ item.ntu }}</td>
<td>{{ item.permanganateIndex }}</td>
<td>{{ item.ammoniaNitrogen }}</td>
<td>{{ item.totalPhosphorus }}</td>
<td>{{ item.totalNitrogen }}</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-content" :class="{ active: activeTab === 'chart' }">
<select v-model="selectedParameter" @change="updateChart">
<option value="temperature">水温</option>
<option value="ph">pH值</option>
<option value="dissolvedOxygen">溶解氧</option>
<option value="conductivity">电导率</option>
<option value="ntu">浊度</option>
<option value="permanganateIndex">高锰酸盐指数</option>
<option value="ammoniaNitrogen">氨氮</option>
<option value="totalPhosphorus">总磷</option>
<option value="totalNitrogen">总氮</option>
</select>
<div id="chart" class="chart-container"></div>
</div>
<div class="tab-content" :class="{ active: activeTab === 'statistics' }">
<div v-if="loadingStatistics" class="loading">统计数据加载中...</div>
<div v-else class="statistics-grid">
<div class="stat-card" v-for="stat in statistics" :key="stat.parameter">
<h3>{{ getParameterDisplayName(stat.parameter) }}</h3>
<div class="stat-value">
<span class="stat-label">平均值:</span>
<span>{{ stat.average }}</span>
</div>
<div class="stat-value">
<span class="stat-label">最小值:</span>
<span>{{ stat.min }}</span>
</div>
<div class="stat-value">
<span class="stat-label">最大值:</span>
<span>{{ stat.max }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="card" v-if="!loading && waterQualityData.length === 0 && !error">
<p>暂无数据。请调整查询条件后重试。</p>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
queryType: 'river',
rivers: [],
sections: [],
selectedRiverId: '',
selectedSectionId: '',
startDate: '',
endDate: '',
waterQualityData: [],
statistics: [],
loading: false,
loadingStatistics: false,
error: '',
activeTab: 'table',
selectedParameter: 'temperature',
chart: null
},
created() {
this.fetchRivers();
this.fetchSections();
// 设置默认日期范围为过去30天
const today = new Date();
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(today.getDate() - 30);
this.endDate = this.formatDateForInput(today);
this.startDate = this.formatDateForInput(thirtyDaysAgo);
},
methods: {
fetchRivers() {
axios.get('/api/rivers')
.then(response => {
this.rivers = response.data;
})
.catch(error => {
console.error('获取河道数据失败:', error);
this.error = '获取河道数据失败,请稍后重试';
});
},
fetchSections() {
axios.get('/api/sections')
.then(response => {
this.sections = response.data;
})
.catch(error => {
console.error('获取断面数据失败:', error);
this.error = '获取断面数据失败,请稍后重试';
});
},
queryData() {
this.error = '';
if (this.queryType === 'river' && !this.selectedRiverId) {
this.error = '请选择一个河道';
return;
}
if (this.queryType === 'section' && !this.selectedSectionId) {
this.error = '请选择一个断面';
return;
}
if (!this.startDate || !this.endDate) {
this.error = '请选择开始和结束日期';
return;
}
const startDateObj = new Date(this.startDate);
const endDateObj = new Date(this.endDate);
if (startDateObj > endDateObj) {
this.error = '开始日期不能晚于结束日期';
return;
}
this.loading = true;
let url;
if (this.queryType === 'river') {
url = `/api/water-quality/river/${this.selectedRiverId}/date-range?startDate=${this.startDate}&endDate=${this.endDate}`;
} else {
url = `/api/water-quality/section/${this.selectedSectionId}/date-range?startDate=${this.startDate}&endDate=${this.endDate}`;
}
axios.get(url)
.then(response => {
this.waterQualityData = response.data;
this.loading = false;
if (this.waterQualityData.length > 0) {
// 按日期排序
this.waterQualityData.sort((a, b) => new Date(a.dataDate) - new Date(b.dataDate));
// 如果在图表选项卡,更新图表
if (this.activeTab === 'chart') {
this.$nextTick(() => {
this.initChart();
this.updateChart();
});
}
}
})
.catch(error => {
console.error('查询数据失败:', error);
this.error = '查询数据失败,请稍后重试';
this.loading = false;
});
},
fetchStatistics() {
if (this.statistics.length > 0 || this.loadingStatistics) {
return; // 已有数据或正在加载中,不重复请求
}
this.loadingStatistics = true;
let url;
if (this.queryType === 'river') {
url = `/api/water-quality/river/${this.selectedRiverId}/statistics`;
} else {
url = `/api/water-quality/section/${this.selectedSectionId}/statistics`;
}
axios.get(url)
.then(response => {
this.statistics = response.data;
this.loadingStatistics = false;
})
.catch(error => {
console.error('获取统计数据失败:', error);
this.error = '获取统计数据失败,请稍后重试';
this.loadingStatistics = false;
});
},
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN');
},
formatDateForInput(date) {
if (!date) return '';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
initChart() {
if (!this.chart) {
this.chart = echarts.init(document.getElementById('chart'));
}
},
updateChart() {
if (!this.chart || this.waterQualityData.length === 0) return;
const dates = this.waterQualityData.map(item => this.formatDate(item.dataDate));
const values = this.waterQualityData.map(item => item[this.selectedParameter]);
const option = {
title: {
text: this.getParameterDisplayName(this.selectedParameter) + '变化趋势',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value',
name: this.getParameterUnit(this.selectedParameter)
},
series: [{
name: this.getParameterDisplayName(this.selectedParameter),
type: 'line',
data: values,
smooth: true,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
]
},
markLine: {
data: [
{ type: 'average', name: '平均值' }
]
}
}]
};
this.chart.setOption(option);
},
getParameterDisplayName(parameter) {
const nameMap = {
'temperature': '水温',
'ph': 'pH值',
'dissolvedOxygen': '溶解氧',
'conductivity': '电导率',
'ntu': '浊度',
'permanganateIndex': '高锰酸盐指数',
'ammoniaNitrogen': '氨氮',
'totalPhosphorus': '总磷',
'totalNitrogen': '总氮',
'Temperature': '水温',
'PH': 'pH值',
'Dissolved Oxygen': '溶解氧',
'Conductivity': '电导率',
'NTU': '浊度',
'Permanganate Index': '高锰酸盐指数',
'Ammonia Nitrogen': '氨氮',
'Total Phosphorus': '总磷',
'Total Nitrogen': '总氮'
};
return nameMap[parameter] || parameter;
},
getParameterUnit(parameter) {
const unitMap = {
'temperature': '°C',
'ph': '',
'dissolvedOxygen': 'mg/L',
'conductivity': 'μS/cm',
'ntu': 'NTU',
'permanganateIndex': 'mg/L',
'ammoniaNitrogen': 'mg/L',
'totalPhosphorus': 'mg/L',
'totalNitrogen': 'mg/L'
};
return unitMap[parameter] || '';
}
},
watch: {
activeTab(newTab) {
if (newTab === 'chart' && this.waterQualityData.length > 0) {
this.$nextTick(() => {
this.initChart();
this.updateChart();
});
}
}
}
});
</script>
</body>
</html>