578 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue";
import * as echarts from "echarts";
import { ElMessageBox, ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
interface AnalysisReport {
id: number;
title: string;
type: "species" | "environment"; // 分析类型:物种/环境
timeRange: {
// 分析时间范围
start: string;
end: string;
};
dataSource: {
// 数据来源
type: string;
points: string[]; // 监测点位
}[];
analysis: {
summary: string; // 分析总结
trends: {
// 趋势分析
indicator: string; // 指标
trend: string; // 变化趋势
data: any[]; // 数据
}[];
abnormal: {
// 异常分析
type: string;
description: string;
level: string;
}[];
};
recommendations: string[]; // 建议措施
}
// 示例数据
const tableData = ref<AnalysisReport[]>([
{
id: 1,
title: "2024年第一季度水质监测分析报告",
type: "environment",
timeRange: {
start: "2024-01-01",
end: "2024-03-31",
},
dataSource: [
{
type: "水质监测",
points: ["A区-1号监测点", "A区-2号监测点", "B区-1号监测点"],
},
],
analysis: {
summary: "第一季度水质总体保持稳定但3月份出现轻微波动",
trends: [
{
indicator: "pH值",
trend: "稳定",
data: [7.1, 7.2, 7.0, 7.3],
},
{
indicator: "溶解氧",
trend: "下降",
data: [6.5, 6.3, 6.0, 5.8],
},
],
abnormal: [
{
type: "溶解氧",
description: "3月底溶解氧水平略低于标准值",
level: "轻微",
},
],
},
recommendations: ["加强对B区-1号监测点的巡查频率", "建议增加水体曝气设施"],
},
]);
// 筛选条件
const filterForm = ref({
dateRange: [],
type: "",
indicator: "",
});
// 添加详情查看功能
const detailVisible = ref(false);
const currentReport = ref<AnalysisReport | null>(null);
const handleView = (row: AnalysisReport) => {
currentReport.value = row;
detailVisible.value = true;
};
// 添加导出功能
const handleExport = (row: AnalysisReport) => {
ElMessageBox.confirm("确认导出该分析报告?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info",
})
.then(() => {
ElMessage.success("导出成功,文件已下载");
})
.catch(() => {
// 取消导出
});
};
// 添加图表resize监听
let myChart: echarts.ECharts | null = null;
const initChart = () => {
const chartDom = document.getElementById("trendChart");
if (!chartDom) return;
myChart = echarts.init(chartDom);
const option = {
backgroundColor: 'transparent',
title: {
text: '近7天报告提交趋势',
textStyle: {
fontSize: 16,
fontWeight: 500,
color: '#303133'
},
padding: [20, 0]
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#eee',
padding: [15, 20],
},
legend: {
data: ['巡护报告', '监测报告'],
right: 0,
top: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: {
color: '#666',
fontSize: 12
}
},
grid: {
left: '3%',
right: '4%',
top: '15%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['3-14', '3-15', '3-16', '3-17', '3-18', '3-19', '3-20'],
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
color: '#999',
fontSize: 12
}
},
yAxis: {
type: 'value',
min: 0,
max: 8,
interval: 2,
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
color: '#999',
fontSize: 12
}
},
series: [
{
name: '巡护报告',
type: 'line',
data: [5, 6, 4, 8, 7, 6, 5],
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2,
color: '#36A2EB'
},
itemStyle: {
color: '#36A2EB',
borderWidth: 2,
borderColor: '#fff'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(54, 162, 235, 0.15)' },
{ offset: 1, color: 'rgba(54, 162, 235, 0.02)' }
])
}
},
{
name: '监测报告',
type: 'line',
data: [3, 4, 3, 5, 4, 5, 4],
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2,
color: '#4BC0C0'
},
itemStyle: {
color: '#4BC0C0',
borderWidth: 2,
borderColor: '#fff'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(75, 192, 192, 0.15)' },
{ offset: 1, color: 'rgba(75, 192, 192, 0.02)' }
])
}
}
]
};
myChart.setOption(option);
};
onMounted(() => {
initChart();
window.addEventListener("resize", handleResize);
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
myChart?.dispose();
});
const handleResize = () => {
myChart?.resize();
};
// 添加分页功能
const currentPage = ref(1);
const pageSize = ref(10);
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
return tableData.value.slice(start, end);
});
const handleSizeChange = (val: number) => {
pageSize.value = val;
currentPage.value = 1;
};
const handleCurrentChange = (val: number) => {
currentPage.value = val;
};
// 新建报告
const dialogVisible = ref(false);
const formData = ref({
title: "",
type: "",
timeRange: [],
summary: "",
recommendations: "",
});
const handleCreate = () => {
dialogVisible.value = true;
};
const handleSubmit = () => {
// TODO: 提交表单
dialogVisible.value = false;
};
// 刷新数据
const handleRefresh = () => {
ElMessage.success("数据已更新");
// TODO: 实际刷新数据的逻辑
};
</script>
<template>
<div class="analysis-report">
<!-- 趋势图表 -->
<el-card class="chart-card">
<div id="trendChart"></div>
</el-card>
<!-- 报告列表 -->
<el-card class="mt-20">
<template #header>
<div class="card-header">
<span>分析报告</span>
<el-button type="primary" :icon="Plus" @click="handleCreate">新建报告</el-button>
</div>
</template>
<!-- 数据统计卡片 -->
<el-row :gutter="20" class="mb-20">
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-value">128</div>
<div class="stat-label">报告总数</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-value">45</div>
<div class="stat-label">本月新增</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card">
<div class="stat-value">24</div>
<div class="stat-label">待处理</div>
</el-card>
</el-col>
</el-row>
<!-- 报告列表 -->
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="title" label="报告标题" min-width="200" />
<el-table-column prop="type" label="类型" width="120">
<template #default="{ row }">
<el-tag :type="row.type === 'species' ? 'success' : 'primary'">
{{ row.type === "species" ? "物种分析" : "环境分析" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看</el-button>
<el-button type="warning" link @click="handleExport(row)">导出</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
:total="tableData.length"
/>
</div>
</el-card>
<!-- 新建报告弹窗 -->
<el-dialog v-model="dialogVisible" title="新建分析报告" width="800px">
<el-form :model="formData" label-width="100px">
<el-form-item label="报告标题" required>
<el-input v-model="formData.title" placeholder="请输入报告标题" />
</el-form-item>
<el-form-item label="分析类型" required>
<el-radio-group v-model="formData.type">
<el-radio label="species">物种分析</el-radio>
<el-radio label="environment">环境分析</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="分析周期" required>
<el-date-picker
v-model="formData.timeRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="分析总结" required>
<el-input
v-model="formData.summary"
type="textarea"
rows="4"
placeholder="请输入分析总结"
/>
</el-form-item>
<el-form-item label="建议措施" required>
<el-input
v-model="formData.recommendations"
type="textarea"
rows="4"
placeholder="请输入建议措施"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</span>
</template>
</el-dialog>
<!-- 添加详情弹窗 -->
<el-dialog v-model="detailVisible" title="分析报告详情" width="900px">
<template v-if="currentReport">
<el-descriptions :column="2" border>
<el-descriptions-item label="报告标题" :span="2">
{{ currentReport.title }}
</el-descriptions-item>
<el-descriptions-item label="分析类型">
{{ currentReport.type === "species" ? "物种分析" : "环境分析" }}
</el-descriptions-item>
<el-descriptions-item label="时间范围">
{{ currentReport.timeRange.start }} 至 {{ currentReport.timeRange.end }}
</el-descriptions-item>
<el-descriptions-item label="监测点位" :span="2">
<el-tag
v-for="point in currentReport.dataSource[0].points"
:key="point"
style="margin-right: 8px"
>
{{ point }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="分析总结" :span="2">
{{ currentReport.analysis.summary }}
</el-descriptions-item>
<el-descriptions-item label="趋势分析" :span="2">
<div v-for="trend in currentReport.analysis.trends" :key="trend.indicator">
<div class="trend-item">
<span class="indicator">{{ trend.indicator }}</span>
<el-tag :type="trend.trend === '下降' ? 'danger' : 'success'">
{{ trend.trend }}
</el-tag>
</div>
</div>
</el-descriptions-item>
<el-descriptions-item label="异常情况" :span="2">
<div v-for="item in currentReport.analysis.abnormal" :key="item.type">
<div class="abnormal-item">
<span class="type">{{ item.type }}</span>
<span>{{ item.description }}</span>
<el-tag
:type="item.level === '严重' ? 'danger' : 'warning'"
size="small"
style="margin-left: 8px"
>
{{ item.level }}
</el-tag>
</div>
</div>
</el-descriptions-item>
<el-descriptions-item label="建议措施" :span="2">
<ul class="recommendations-list">
<li v-for="(item, index) in currentReport.recommendations" :key="index">
{{ item }}
</li>
</ul>
</el-descriptions-item>
</el-descriptions>
</template>
<template #footer>
<span class="dialog-footer">
<el-button @click="detailVisible = false">关闭</el-button>
<el-button
type="warning"
@click="handleExport(currentReport!)"
:disabled="!currentReport"
>
导出
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
@import "../../../styles/variables.scss";
.analysis-report {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.mt-20 {
margin-top: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.trend-item {
margin-bottom: 8px;
display: flex;
align-items: center;
.indicator {
margin-right: 8px;
color: $text-regular;
}
}
.abnormal-item {
margin-bottom: 8px;
.type {
color: $text-regular;
}
}
.recommendations-list {
margin: 0;
padding-left: 20px;
li {
margin-bottom: 4px;
color: $text-regular;
}
}
.stat-card {
padding: 20px;
text-align: center;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
}
.stat-value {
font-size: 28px;
font-weight: 600;
color: $primary-color;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: $text-secondary;
}
}
.chart-card {
margin-bottom: 24px;
border-radius: 8px;
background: #fff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
#trendChart {
height: 400px;
padding: 20px;
}
}
}
</style>