先按顺序分析所有已上传图片内容,无需任何操作,等待进一步指令










I've analyzed the uploaded images, which show a comprehensive water quality monitoring application for lakes in Chinese. Here's a description of what I can see:
The application is a "湖泊水质多参数智能监测与预警系统" (Lake Water Quality Multi-parameter Intelligent Monitoring and Early Warning System) with the following key screens:
- A login screen with username/password fields on a green background
- A home dashboard showing monitoring statistics (12 normal points, 3 warning, 1 exceeding standard)
- Real-time monitoring data screens showing parameters like dissolved oxygen (3.2 mg/L), pH (7.5), turbidity (25 NTU), and water temperature (22.5°C)
- Detailed monitoring station information with status indicators, battery levels, and parameter trends
- Historical data analysis with graphs showing trends across multiple monitoring points
- Alert management screens showing warnings for parameters exceeding thresholds
- Alert processing interface for handling detected issues
- Statistical reports showing water quality compliance rates (66.7%) with visual representations
- A map interface showing monitoring point locations on lakes
The application appears to be designed for environmental monitoring professionals to track water quality parameters, receive alerts when values exceed safe thresholds, and manage response actions.
以上图片是《湖泊水质多参数智能监测与预警系统》系统所有功能界面截图,请仔细分析并按照下方JSON模板补充其中内容,需要补充内容如下: 1、domain(系统面向领域,不超过7个字,且需要保证完整) 2、summary(项目背景) 3、description(系统介绍) 4、significance(开发意义) 5、target(目标用户) 6、manualPurpose(用户手册编写目的) 7、purpose(开发目的,内容不少于25字,并且不超过35个字,且需要保证完整) 8、pages中description(当前页面内容描述,可以按照如下内容描述:登录成功后系统将直接进入首页。首页顶部提供了业务展示幻灯片,下方是系统工能菜单,包含“我的报修”、“维修签字”、“通知公告”以及“个人中心”等功能。菜单下方提供了当前地区天气信息。页面下方展示了系统通知公告列表,包含图片、标题、简介以及发布时间等信息。) 9、关于页面内容描述,要求不需要对状态栏和导航栏进行描述,页面中通用的内容只需描述一次,内容中不要使用英文双引号会破坏JSON格式,可以使用“”或者【】代替,如果描述需要换行的可以使用换行符【注意换行符后需要添加4个空格 】 JSON模板内容如下:{"domain":"","summary":"","description":"","significance":"","target":"","purpose":"","manualPurpose":"","pages":[{"id":"1","pid":"0","title":"用户登录","index":1,"description":"","is_home_page":false,"fileName":"1.用户登录.png","children":[]},{"id":"2","pid":"0","title":"首页","index":2,"description":"","is_home_page":false,"fileName":"2.首页.png","children":[]},{"id":"3","pid":"0","title":"数据监测","index":3,"description":"","is_home_page":false,"fileName":"3.数据监测.png","children":[{"id":"3.1","pid":"3","title":"监测点详情页","index":4,"description":"","is_home_page":false,"fileName":"3.1.监测点详情页.png","children":[]},{"id":"3.2","pid":"3","title":"历史数据","index":5,"description":"","is_home_page":false,"fileName":"3.2.历史数据.png","children":[]},{"id":"3.3","pid":"3","title":"预警记录","index":6,"description":"","is_home_page":false,"fileName":"3.3.预警记录.png","children":[]}]},{"id":"4","pid":"0","title":"预警信息","index":7,"description":"","is_home_page":false,"fileName":"4.预警信息.png","children":[{"id":"4.2","pid":"4","title":"处理预警","index":8,"description":"","is_home_page":false,"fileName":"4.2.处理预警.png","children":[]}]},{"id":"5","pid":"0","title":"湖泊地图","index":9,"description":"","is_home_page":false,"fileName":"5.湖泊地图.png","children":[]},{"id":"6","pid":"0","title":"统计报表界面","index":10,"description":"","is_home_page":false,"fileName":"6.统计报表界面.png","children":[]},{"id":"7","pid":"0","title":"个人中心","index":11,"description":"","is_home_page":false,"fileName":"7.个人中心.png","children":[]}],"platform":"2"} 请按照模板原格式返回,完成后请帮我继续使用基于VUE2的UNIAPP创建这些页面,业务功能要完整,数据要通过接口获取,并且接口连接不需要连接只要路径,代码中不需要添加注释,一个页面一个页面生成.



{
"domain": "湖泊水质监测预警",
"summary": "随着环境保护意识的提高,湖泊水质监测变得越来越重要。本系统通过多参数智能监测设备实时采集湖泊水质数据,建立完善的预警机制,为湖泊生态环境保护提供科学依据。",
"description": "湖泊水质多参数智能监测与预警系统是一套集数据采集、分析、预警和处理于一体的综合性平台。系统通过布设在湖泊各监测点的智能设备,实时监测溶解氧、pH值、浊度、水温等关键水质参数,当参数超出预设阈值时自动触发预警,并支持历史数据查询、趋势分析、统计报表等功能,为湖泊水质管理提供全方位支持。",
"significance": "本系统的开发对于保护湖泊水环境、预防水质污染事件、提高水资源管理效率具有重要意义。通过实时监测和智能预警,可以及时发现水质异常,采取有效措施,减少环境风险,保障水生态系统健康和水资源可持续利用。",
"target": "环保部门工作人员、湖泊管理机构、水质监测专业人员、环境研究人员",
"purpose": "通过实时监测湖泊水质参数,建立预警机制,及时发现并处理水质异常,保障湖泊生态环境安全。",
"manualPurpose": "本用户手册旨在帮助用户全面了解系统功能和操作方法,指导用户正确使用系统进行湖泊水质监测、数据分析和预警处理,提高工作效率和管理水平。",
"pages": [
{
"id": "1",
"pid": "0",
"title": "用户登录",
"index": 1,
"description": "系统登录页面采用绿色主题设计,页面顶部显示系统名称【湖泊水质多参数智能监测与预警系统】。中间区域为登录表单,包含用户名/手机号输入框和密码输入框,每个输入框左侧配有对应图标。底部设有【登录】按钮,点击后验证用户信息并进入系统。整体界面简洁明了,操作便捷。",
"is_home_page": false,
"fileName": "1.用户登录.png",
"children": []
},
{
"id": "2",
"pid": "0",
"title": "首页",
"index": 2,
"description": "首页是系统的主要信息展示页面,顶部为【首页】标题。页面主体分为三个主要区域:\n 1. 水质概览区域:显示监测点状态统计,包括正常监测点(12个)、预警监测点(3个)和超标监测点(1个),并展示溶解氧、pH值和浊度三项关键指标的周趋势图。\n 2. 功能快捷入口:提供【实时监测】、【历史数据】、【湖泊地图】和【预警信息】四个主要功能的快速访问入口。\n 3. 最新预警区域:展示系统检测到的最新预警信息,包括监测点名称、预警参数、预警原因和时间,便于用户快速了解当前需要关注的异常情况。\n 页面底部为系统导航栏,提供【首页】、【监测】、【地图】、【报表】和【我的】五个主要功能区的快速切换。",
"is_home_page": false,
"fileName": "2.首页.png",
"children": []
},
{
"id": "3",
"pid": "0",
"title": "数据监测",
"index": 3,
"description": "数据监测页面展示实时监测数据,顶部为【实时监测数据】标题。页面上方提供筛选功能,可按湖泊和状态进行筛选,并设有【刷新】按钮实时更新数据。\n 主体内容区域以卡片形式展示各监测点的实时数据,包括:\n 1. 监测点名称和当前状态标签(如【超标】)\n 2. 关键水质参数展示,包括溶解氧(3.2 mg/L)、pH值(7.5)、浊度(25 NTU)和水温(22.5℃),并通过上下箭头指示参数变化趋势\n 3. 参数趋势图,直观展示近期参数变化情况\n 4. 最后更新时间和【查看详情】链接\n 页面可上下滚动查看更多监测点数据,底部为系统导航栏。",
"is_home_page": false,
"fileName": "3.数据监测.png",
"children": [
{
"id": "3.1",
"pid": "3",
"title": "监测点详情页",
"index": 4,
"description": "监测点详情页展示特定监测点的详细信息,顶部为【监测点详情】标题,左上角有返回按钮。页面主要分为以下几个部分:\n 1. 监测点基本信息:显示监测点名称【东湖监测点1号】、ID编号、运行状态、位置信息【东湖北岸】、安装日期、电池电量(85%)和信号强度\n 2. 功能快捷入口:提供【历史数据】、【预警记录】和【生成报告】三个功能入口\n 3. 时间筛选:可选择【今日】、【本周】、【本月】或【自定义】时间范围查看数据\n 4. 参数详情卡片:分别展示水温(24.5℃)、pH值(7.8)、溶解氧(3.2 mg/L)和浊度(28 NTU)的当前值、状态标签和趋势图\n 5. 底部显示【24小时趋势图】,可查看各参数的变化趋势\n 整体界面信息丰富但布局清晰,便于用户全面了解监测点状况。",
"is_home_page": false,
"fileName": "3.1.监测点详情页.png",
"children": []
},
{
"id": "3.2",
"pid": "3",
"title": "历史数据",
"index": 5,
"description": "历史数据页面用于查询和分析历史监测数据,顶部为【历史数据】标题,左上角有返回按钮。页面主要包含以下内容:\n 1. 数据筛选区域:提供多种筛选条件,包括:\n - 监测点选择(可选择特定湖泊和监测点)\n - 参数选择(可选择查看的水质参数)\n - 时间范围选择(可设定起止日期)\n - 【查询数据】按钮\n 2. 数据趋势图:展示所选参数(如溶解氧)的历史变化趋势,包含多个监测点的数据曲线,并标明预警阈值线\n 3. 数据周期选择:可切换查看【日】、【周】、【月】维度的数据\n 4. 数据列表:以表格形式展示具体的历史数据记录,包含时间、监测点、参数值和状态等信息\n 该页面为用户提供了全面的数据分析工具,支持多维度的历史数据查询和趋势分析。",
"is_home_page": false,
"fileName": "3.2.历史数据.png",
"children": []
},
{
"id": "3.3",
"pid": "3",
"title": "预警记录",
"index": 6,
"description": "预警记录页面用于查询特定监测点的历史预警信息,顶部为【预警记录】标题,左上角有返回按钮。页面主要分为以下几个部分:\n 1. 监测点信息:显示当前查看的监测点名称【东湖监测点1号】及其ID编号\n 2. 预警筛选区域:提供筛选条件设置,包括:\n - 时间范围选择(可设定起止日期)\n - 预警类型选择(如【全部类型】)\n - 监测参数选择(如【全部参数】)\n - 【查询预警】按钮\n 3. 预警展示方式选择:可在【预警列表】和【预警统计】两种视图间切换\n 4. 预警记录列表:展示符合筛选条件的预警记录,包括:\n - 参数类型(如pH值、浊度等)及其图标\n - 监测值与阈值的对比\n - 预警时间\n - 处理状态(如【已解决】或【预警】)\n 页面还提供【导出Excel】和【导出PDF】功能,方便用户导出预警数据进行进一步分析或报告生成。",
"is_home_page": false,
"fileName": "3.3.预警记录.png",
"children": []
}
]
},
{
"id": "4",
"pid": "0",
"title": "预警信息",
"index": 7,
"description": "预警信息页面用于集中展示和管理系统中的各类预警,顶部为【预警信息】标题,左上角有返回按钮。页面主要包含以下内容:\n 1. 预警类型选择:提供【全部】、【预警】、【超标】和【已处理】四个选项卡,用户可快速筛选不同状态的预警\n 2. 筛选条件:包括【时间筛选】和【监测点筛选】下拉选项,以及预警信息搜索框\n 3. 预警列表:以卡片形式展示各预警信息,每条预警包含:\n - 预警类型图标和名称(如【溶解氧超标预警】)\n - 预警时间\n - 监测点信息\n - 参数名称\n - 当前值与标准值的对比\n - 【标记已读】和【处理】按钮\n 预警信息按时间倒序排列,不同类型的预警使用不同颜色图标区分(红色表示超标,橙色表示预警),便于用户快速识别严重程度。",
"is_home_page": false,
"fileName": "4.预警信息.png",
"children": [
{
"id": "4.2",
"pid": "4",
"title": "处理预警",
"index": 8,
"description": "处理预警页面用于记录和管理预警的处理过程,顶部为【预警处理】标题,左上角有返回按钮。页面主要分为两个部分:\n 1. 预警信息概览:显示当前处理的预警基本信息,包括:\n - 预警类型【溶解氧超标预警】\n - 监测点信息【东湖监测点1号】\n - 参数信息【溶解氧】\n 2. 处理信息表单:用于填写预警处理的详细信息,包括:\n - 处理类型(下拉选择)\n - 处理方法(下拉选择)\n - 处理结果(下拉选择)\n - 处理详情(文本输入框,用于详细描述处理过程)\n - 后续建议(文本输入框,用于提供改进措施建议)\n 用户填写完成后可提交处理信息,系统将记录处理结果并更新预警状态。该页面设计简洁明了,引导用户规范化记录预警处理流程。",
"is_home_page": false,
"fileName": "4.2.处理预警.png",
"children": []
}
]
},
{
"id": "5",
"pid": "0",
"title": "湖泊地图",
"index": 9,
"description": "湖泊地图页面提供基于地理位置的监测点可视化展示,顶部为【湖泊地图】标题。页面主要包含以下内容:\n 1. 筛选选项:页面上方提供两个下拉筛选框,分别用于选择【全部湖泊】和【综合水质】,便于用户按需查看特定湖泊或水质类型的监测点\n 2. 地图主体:展示湖泊及周边地理环境的电子地图,地图上标注了各监测点的位置\n 3. 地图交互:用户可通过触摸操作放大、缩小和平移地图,点击监测点图标可查看该点的基本信息\n 4. 地图元素:地图上显示湖泊水域(蓝色区域)、道路、建筑物等地理信息,以及周边的地标建筑和设施\n 页面底部为系统导航栏,当前选中【地图】选项。该页面为用户提供了直观的空间视角来了解监测点分布和湖泊整体情况。",
"is_home_page": false,
"fileName": "5.湖泊地图.png",
"children": []
},
{
"id": "6",
"pid": "0",
"title": "统计报表界面",
"index": 10,
"description": "统计报表页面提供系统数据的统计分析和可视化展示,顶部为【统计报表】标题。页面主要包含以下内容:\n 1. 监测概况卡片:以数字和图表形式展示关键统计指标,包括:\n - 监测点总数:12个\n - 预警监测点:3个(较昨日增加1个)\n - 超标监测点:1个(较昨日无变化)\n - 水质达标率:66.7%(较昨日下降8.3%)\n 2. 水质状态分布:使用环形图直观展示正常、预警和超标三种状态的监测点分布比例,并提供图例说明\n 3. 各湖泊水质参数对比:底部区域提供溶解氧、pH值、油度、水温等参数的多湖泊对比功能\n 页面设计采用卡片式布局,数据可视化效果突出,配色方案专业且易于识别(绿色表示正常,橙色表示预警,红色表示超标)。该页面为管理人员提供了全面的数据概览和趋势分析,便于决策和管理。",
"is_home_page": false,
"fileName": "6.统计报表界面.png",
"children": []
},
{
"id": "7",
"pid": "0",
"title": "个人中心",
"index": 11,
"description": "个人中心页面提供用户个人信息和系统设置功能,顶部为【个人中心】标题。页面主要包含以下内容:\n 1. 用户信息卡片:展示用户基本信息,包括:\n - 用户头像\n - 用户姓名【张瑞】\n - 用户角色【系统管理员】\n - 用户ID【10086】\n 2. 消息通知设置:提供各类通知的开关设置,包括:\n - 预警通知(已开启)\n - 邮件通知(已开启)\n - 短信通知(已关闭)\n 3. 退出登录按钮:位于页面下方,点击后退出系统回到登录页面\n 页面底部为系统导航栏,当前选中【我的】选项。整体设计简洁明了,功能分区清晰,便于用户快速设置个人偏好和管理账户。",
"is_home_page": false,
"fileName": "7.个人中心.png",
"children": []
}
],
"platform": "2"
}
现在我将使用基于VUE2的UNIAPP创建第一个页面 - 登录页面。
<template>
<view class="login-container">
<view class="login-header">
<text class="login-title">湖泊水质多参数智能监测与预警系统</text>
</view>
<view class="login-form">
<view class="form-item">
<view class="icon-wrapper">
<uni-icons type="person" size="22" color="#4CAF50"></uni-icons>
</view>
<input type="text" v-model="username" placeholder="用户名/手机号" class="input-field" />
</view>
<view class="form-item">
<view class="icon-wrapper">
<uni-icons type="locked" size="22" color="#4CAF50"></uni-icons>
</view>
<input type="password" v-model="password" placeholder="密码" class="input-field" />
</view>
<button class="login-btn" @click="handleLogin">登 录</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
username: '',
password: ''
}
},
methods: {
handleLogin() {
if (!this.username || !this.password) {
uni.showToast({
title: '用户名和密码不能为空',
icon: 'none'
})
return
}
uni.showLoading({
title: '登录中...'
})
uni.request({
url: '/api/user/login',
method: 'POST',
data: {
username: this.username,
password: this.password
},
success: (res) => {
if (res.data.code === 200) {
uni.setStorageSync('token', res.data.data.token)
uni.setStorageSync('userInfo', JSON.stringify(res.data.data.userInfo))
uni.switchTab({
url: '/pages/index/index'
})
} else {
uni.showToast({
title: res.data.message || '登录失败',
icon: 'none'
})
}
},
fail: () => {
uni.showToast({
title: '网络异常,请稍后重试',
icon: 'none'
})
},
complete: () => {
uni.hideLoading()
}
})
}
}
}
</script>
<style lang="scss">
.login-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #4CAF50;
.login-header {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 0 40rpx;
.login-title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
text-align: center;
}
}
.login-form {
background-color: #FFFFFF;
border-radius: 20rpx 20rpx 0 0;
padding: 60rpx 40rpx;
.form-item {
display: flex;
align-items: center;
background-color: #F8F8F8;
border-radius: 10rpx;
margin-bottom: 30rpx;
padding: 5rpx 20rpx;
.icon-wrapper {
padding: 20rpx 10rpx;
}
.input-field {
flex: 1;
height: 90rpx;
font-size: 30rpx;
}
}
.login-btn {
background-color: #4CAF50;
color: #FFFFFF;
height: 90rpx;
line-height: 90rpx;
font-size: 32rpx;
margin-top: 60rpx;
}
}
}
</style>
下面是首页的实现:
<template>
<view class="home-container">
<view class="header">
<text class="title">首页</text>
</view>
<scroll-view scroll-y class="content">
<view class="water-overview">
<view class="overview-title">
<uni-icons type="bar-chart" size="20" color="#4CAF50"></uni-icons>
<text>水质概览</text>
</view>
<view class="status-cards">
<view class="status-card">
<text class="status-num">{{overview.normal}}</text>
<text class="status-label">正常监测点</text>
</view>
<view class="status-card warning">
<text class="status-num">{{overview.warning}}</text>
<text class="status-label">预警监测点</text>
</view>
<view class="status-card danger">
<text class="status-num">{{overview.exceed}}</text>
<text class="status-label">超标监测点</text>
</view>
</view>
<view class="chart-legend">
<view class="legend-item">
<view class="legend-color oxygen"></view>
<text>溶解氧</text>
</view>
<view class="legend-item">
<view class="legend-color ph"></view>
<text>pH值</text>
</view>
<view class="legend-item">
<view class="legend-color turbidity"></view>
<text>浊度</text>
</view>
</view>
<view class="trend-chart">
<qiun-data-charts
type="line"
:opts="chartOpts"
:chartData="chartData"
/>
</view>
</view>
<view class="function-cards">
<view class="function-card" @click="navigateTo('/pages/monitor/monitor')">
<view class="function-icon monitor">
<uni-icons type="settings" size="24" color="#4CAF50"></uni-icons>
</view>
<text class="function-name">实时监测</text>
</view>
<view class="function-card" @click="navigateTo('/pages/history/history')">
<view class="function-icon history">
<uni-icons type="calendar" size="24" color="#4CAF50"></uni-icons>
</view>
<text class="function-name">历史数据</text>
</view>
<view class="function-card" @click="navigateTo('/pages/map/map')">
<view class="function-icon map">
<uni-icons type="map" size="24" color="#4CAF50"></uni-icons>
</view>
<text class="function-name">湖泊地图</text>
</view>
<view class="function-card" @click="navigateTo('/pages/alert/alert')">
<view class="function-icon alert">
<uni-icons type="notification" size="24" color="#4CAF50"></uni-icons>
</view>
<text class="function-name">预警信息</text>
</view>
</view>
<view class="latest-alerts">
<view class="section-header">
<uni-icons type="error" size="20" color="#000"></uni-icons>
<text class="section-title">最新预警</text>
</view>
<view class="alert-list">
<view class="alert-item" v-for="(item, index) in latestAlerts" :key="index" @click="navigateToAlert(item.id)">
<view class="alert-icon" :class="item.type === 'exceed' ? 'exceed' : 'warning'">
<uni-icons :type="item.type === 'exceed' ? 'closeempty' : 'info'" size="20" color="#FFF"></uni-icons>
</view>
<view class="alert-content">
<view class="alert-title">{{item.stationName}} - {{item.paramName}}{{item.type === 'exceed' ? '超标' : '异常'}}</view>
<view class="alert-desc">{{item.description}}</view>
<view class="alert-time">{{item.time}}</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="tab-bar">
<view class="tab-item active">
<uni-icons type="home" size="22" color="#4CAF50"></uni-icons>
<text class="tab-text active">首页</text>
</view>
<view class="tab-item" @click="switchTab('/pages/monitor/monitor')">
<uni-icons type="settings" size="22" color="#999"></uni-icons>
<text class="tab-text">监测</text>
</view>
<view class="tab-item" @click="switchTab('/pages/map/map')">
<uni-icons type="map" size="22" color="#999"></uni-icons>
<text class="tab-text">地图</text>
</view>
<view class="tab-item" @click="switchTab('/pages/report/report')">
<uni-icons type="paperplane" size="22" color="#999"></uni-icons>
<text class="tab-text">报表</text>
</view>
<view class="tab-item" @click="switchTab('/pages/user/user')">
<uni-icons type="person" size="22" color="#999"></uni-icons>
<text class="tab-text">我的</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
overview: {
normal: 12,
warning: 3,
exceed: 1
},
chartData: {
categories: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
series: [
{
name: '溶解氧',
data: [5, 6, 5, 7, 8, 7, 6]
},
{
name: 'pH值',
data: [7, 7.2, 7.1, 7.3, 7.5, 7.8, 8]
},
{
name: '浊度',
data: [15, 18, 20, 25, 30, 35, 45]
}
]
},
chartOpts: {
color: ['#4CAF50', '#FF9800', '#2196F3'],
padding: [15, 15, 0, 15],
enableScroll: false,
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2
},
extra: {
line: {
type: 'curve',
width: 2
}
}
},
latestAlerts: []
}
},
onLoad() {
this.getOverviewData()
this.getChartData()
this.getLatestAlerts()
},
methods: {
getOverviewData() {
uni.request({
url: '/api/home/overview',
method: 'GET',
success: (res) => {
if (res.data.code === 200) {
this.overview = res.data.data
}
}
})
},
getChartData() {
uni.request({
url: '/api/home/trend',
method: 'GET',
success: (res) => {
if (res.data.code === 200) {
this.chartData = res.data.data
}
}
})
},
getLatestAlerts() {
uni.request({
url: '/api/alert/latest',
method: 'GET',
success: (res) => {
if (res.data.code === 200) {
this.latestAlerts = res.data.data
}
}
})
},
navigateTo(url) {
uni.navigateTo({
url
})
},
switchTab(url) {
uni.switchTab({
url
})
},
navigateToAlert(id) {
uni.navigateTo({
url: `/pages/alert/detail?id=${id}`
})
}
}
}
</script>
<style lang="scss">
.home-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.content {
flex: 1;
padding: 20rpx;
}
.water-overview {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.overview-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
text {
font-size: 32rpx;
font-weight: bold;
margin-left: 10rpx;
}
}
.status-cards {
display: flex;
justify-content: space-between;
margin-bottom: 30rpx;
.status-card {
display: flex;
flex-direction: column;
align-items: center;
.status-num {
font-size: 48rpx;
font-weight: bold;
color: #4CAF50;
}
.status-label {
font-size: 24rpx;
color: #666;
margin-top: 10rpx;
}
&.warning .status-num {
color: #FF9800;
}
&.danger .status-num {
color: #F44336;
}
}
}
.chart-legend {
display: flex;
justify-content: center;
margin-bottom: 10rpx;
.legend-item {
display: flex;
align-items: center;
margin: 0 20rpx;
.legend-color {
width: 20rpx;
height: 6rpx;
margin-right: 8rpx;
&.oxygen {
background-color: #4CAF50;
}
&.ph {
background-color: #FF9800;
}
&.turbidity {
background-color: #2196F3;
}
}
text {
font-size: 24rpx;
color: #666;
}
}
}
.trend-chart {
height: 300rpx;
}
}
.function-cards {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 20rpx;
.function-card {
width: 48%;
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 30rpx 0;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20rpx;
.function-icon {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: rgba(76, 175, 80, 0.1);
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20rpx;
}
.function-name {
font-size: 28rpx;
color: #333;
}
}
}
.latest-alerts {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
.section-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
margin-left: 10rpx;
}
}
.alert-list {
.alert-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #EEEEEE;
&:last-child {
border-bottom: none;
}
.alert-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: #FF9800;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
&.exceed {
background-color: #F44336;
}
}
.alert-content {
flex: 1;
.alert-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.alert-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
}
.alert-time {
font-size: 24rpx;
color: #999;
}
}
}
}
}
.tab-bar {
height: 100rpx;
background-color: #FFFFFF;
display: flex;
border-top: 1px solid #EEEEEE;
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.tab-text {
font-size: 22rpx;
color: #999;
margin-top: 6rpx;
&.active {
color: #4CAF50;
}
}
}
}
}
</style>
下面是数据监测页面的实现:
<template>
<view class="monitor-container">
<view class="header">
<text class="title">实时监测数据</text>
</view>
<view class="filter-bar">
<view class="filter-item">
<picker @change="onLakeChange" :value="lakeIndex" :range="lakeOptions">
<view class="picker-content">
<text>{{lakeOptions[lakeIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
<view class="filter-item">
<picker @change="onStatusChange" :value="statusIndex" :range="statusOptions">
<view class="picker-content">
<text>{{statusOptions[statusIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
<view class="refresh-btn" @click="refreshData">
<uni-icons type="refresh" size="18" color="#FFFFFF"></uni-icons>
<text>刷新</text>
</view>
</view>
<scroll-view scroll-y class="station-list">
<view
class="station-card"
v-for="(item, index) in stationList"
:key="index"
@click="navigateToDetail(item.id)"
>
<view class="station-header">
<view class="station-info">
<uni-icons type="location" size="18" color="#4CAF50"></uni-icons>
<text class="station-name">{{item.name}}</text>
</view>
<view class="station-status" :class="getStatusClass(item.status)">
{{item.status}}
</view>
</view>
<view class="params-grid">
<view class="param-item">
<view class="param-label">溶解氧</view>
<view class="param-value">
<text class="value-num">{{item.oxygen}}</text>
<text class="value-unit">mg/L</text>
</view>
<view class="trend-icon" :class="getTrendClass(item.oxygenTrend)">
<uni-icons :type="getTrendIcon(item.oxygenTrend)" size="16" :color="getTrendColor(item.oxygenTrend)"></uni-icons>
</view>
</view>
<view class="param-item">
<view class="param-label">pH值</view>
<view class="param-value">
<text class="value-num">{{item.ph}}</text>
<text class="value-unit"></text>
</view>
<view class="trend-icon" :class="getTrendClass(item.phTrend)">
<uni-icons :type="getTrendIcon(item.phTrend)" size="16" :color="getTrendColor(item.phTrend)"></uni-icons>
</view>
</view>
<view class="param-item">
<view class="param-label">浊度</view>
<view class="param-value">
<text class="value-num">{{item.turbidity}}</text>
<text class="value-unit">NTU</text>
</view>
<view class="trend-icon" :class="getTrendClass(item.turbidityTrend)">
<uni-icons :type="getTrendIcon(item.turbidityTrend)" size="16" :color="getTrendColor(item.turbidityTrend)"></uni-icons>
</view>
</view>
<view class="param-item">
<view class="param-label">水温</view>
<view class="param-value">
<text class="value-num">{{item.temperature}}</text>
<text class="value-unit">°C</text>
</view>
<view class="trend-icon" :class="getTrendClass(item.temperatureTrend)">
<uni-icons :type="getTrendIcon(item.temperatureTrend)" size="16" :color="getTrendColor(item.temperatureTrend)"></uni-icons>
</view>
</view>
</view>
<view class="chart-container">
<qiun-data-charts
type="line"
:opts="getChartOpts(item)"
:chartData="item.chartData"
/>
</view>
<view class="station-footer">
<text class="update-time">最后更新: {{item.updateTime}}</text>
<view class="detail-link">
<text>查看详情</text>
<uni-icons type="right" size="14" color="#666"></uni-icons>
</view>
</view>
</view>
</scroll-view>
<view class="tab-bar">
<view class="tab-item" @click="switchTab('/pages/index/index')">
<uni-icons type="home" size="22" color="#999"></uni-icons>
<text class="tab-text">首页</text>
</view>
<view class="tab-item active">
<uni-icons type="settings" size="22" color="#4CAF50"></uni-icons>
<text class="tab-text active">监测</text>
</view>
<view class="tab-item" @click="switchTab('/pages/map/map')">
<uni-icons type="map" size="22" color="#999"></uni-icons>
<text class="tab-text">地图</text>
</view>
<view class="tab-item" @click="switchTab('/pages/report/report')">
<uni-icons type="paperplane" size="22" color="#999"></uni-icons>
<text class="tab-text">报表</text>
</view>
<view class="tab-item" @click="switchTab('/pages/user/user')">
<uni-icons type="person" size="22" color="#999"></uni-icons>
<text class="tab-text">我的</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
lakeOptions: ['全部湖泊', '东湖', '南湖', '西湖', '北湖'],
lakeIndex: 0,
statusOptions: ['全部状态', '正常', '预警', '超标'],
statusIndex: 0,
stationList: []
}
},
onLoad() {
this.getStationList()
},
methods: {
getStationList() {
uni.showLoading({
title: '加载中...'
})
uni.request({
url: '/api/station/list',
method: 'GET',
data: {
lake: this.lakeIndex === 0 ? '' : this.lakeOptions[this.lakeIndex],
status: this.statusIndex === 0 ? '' : this.statusOptions[this.statusIndex]
},
success: (res) => {
if (res.data.code === 200) {
this.stationList = res.data.data
}
},
complete: () => {
uni.hideLoading()
}
})
},
onLakeChange(e) {
this.lakeIndex = e.detail.value
this.getStationList()
},
onStatusChange(e) {
this.statusIndex = e.detail.value
this.getStationList()
},
refreshData() {
this.getStationList()
},
getStatusClass(status) {
if (status === '超标') return 'exceed'
if (status === '预警') return 'warning'
return 'normal'
},
getTrendClass(trend) {
if (trend === 'up') return 'up'
if (trend === 'down') return 'down'
return ''
},
getTrendIcon(trend) {
if (trend === 'up') return 'arrow-up'
if (trend === 'down') return 'arrow-down'
return 'minus'
},
getTrendColor(trend) {
if (trend === 'up') return '#F44336'
if (trend === 'down') return '#4CAF50'
return '#999'
},
getChartOpts(item) {
return {
color: ['#F44336'],
padding: [0, 0, 0, 0],
enableScroll: false,
legend: { show: false },
xAxis: { show: false },
yAxis: { show: false },
dataLabel: false,
dataPointShape: false,
extra: {
line: {
type: 'curve',
width: 2,
activeType: 'none'
}
}
}
},
navigateToDetail(id) {
uni.navigateTo({
url: `/pages/monitor/detail?id=${id}`
})
},
switchTab(url) {
uni.switchTab({
url
})
}
}
}
</script>
<style lang="scss">
.monitor-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.filter-bar {
display: flex;
padding: 20rpx;
background-color: #FFFFFF;
.filter-item {
flex: 1;
margin-right: 20rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
height: 80rpx;
.picker-content {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 28rpx;
color: #333;
}
}
}
.refresh-btn {
width: 160rpx;
height: 80rpx;
background-color: #4CAF50;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
text {
color: #FFFFFF;
font-size: 28rpx;
margin-left: 10rpx;
}
}
}
.station-list {
flex: 1;
padding: 0 20rpx 20rpx;
.station-card {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
margin-top: 20rpx;
.station-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.station-info {
display: flex;
align-items: center;
.station-name {
font-size: 32rpx;
font-weight: bold;
margin-left: 10rpx;
}
}
.station-status {
font-size: 24rpx;
color: #FFFFFF;
background-color: #4CAF50;
padding: 6rpx 20rpx;
border-radius: 30rpx;
&.warning {
background-color: #FF9800;
}
&.exceed {
background-color: #F44336;
}
}
}
.params-grid {
display: flex;
flex-wrap: wrap;
margin-bottom: 20rpx;
.param-item {
width: 50%;
padding: 15rpx 0;
.param-label {
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
}
.param-value {
display: flex;
align-items: baseline;
.value-num {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.value-unit {
font-size: 24rpx;
color: #999;
margin-left: 6rpx;
}
}
.trend-icon {
margin-top: 6rpx;
&.up {
color: #F44336;
}
&.down {
color: #4CAF50;
}
}
}
}
.chart-container {
height: 120rpx;
margin-bottom: 20rpx;
}
.station-footer {
display: flex;
justify-content: space-between;
align-items: center;
.update-time {
font-size: 24rpx;
color: #999;
}
.detail-link {
display: flex;
align-items: center;
text {
font-size: 26rpx;
color: #666;
margin-right: 6rpx;
}
}
}
}
}
.tab-bar {
height: 100rpx;
background-color: #FFFFFF;
display: flex;
border-top: 1px solid #EEEEEE;
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.tab-text {
font-size: 22rpx;
color: #999;
margin-top: 6rpx;
&.active {
color: #4CAF50;
}
}
}
}
}
</style>
接下来是监测点详情页面的实现:
<template>
<view class="detail-container">
<view class="header">
<view class="back-btn" @click="goBack">
<uni-icons type="left" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="title">监测点详情</text>
</view>
<scroll-view scroll-y class="content">
<view class="station-info">
<view class="station-icon">
<image src="/static/images/station-icon.png" mode="aspectFit"></image>
</view>
<view class="info-content">
<view class="station-name">{{stationInfo.name}}</view>
<view class="station-id">ID: {{stationInfo.id}}</view>
</view>
<view class="station-status" :class="getStatusClass(stationInfo.status)">
{{stationInfo.status}}
</view>
</view>
<view class="info-grid">
<view class="info-item">
<view class="info-icon location">
<uni-icons type="location" size="20" color="#4CAF50"></uni-icons>
</view>
<view class="info-content">
<view class="info-label">位置</view>
<view class="info-value">{{stationInfo.location}}</view>
</view>
</view>
<view class="info-item">
<view class="info-icon date">
<uni-icons type="calendar" size="20" color="#4CAF50"></uni-icons>
</view>
<view class="info-content">
<view class="info-label">安装日期</view>
<view class="info-value">{{stationInfo.installDate}}</view>
</view>
</view>
<view class="info-item">
<view class="info-icon battery">
<uni-icons type="battery" size="20" color="#4CAF50"></uni-icons>
</view>
<view class="info-content">
<view class="info-label">电池电量</view>
<view class="info-value">{{stationInfo.battery}}</view>
</view>
</view>
<view class="info-item">
<view class="info-icon signal">
<uni-icons type="wifi" size="20" color="#4CAF50"></uni-icons>
</view>
<view class="info-content">
<view class="info-label">信号强度</view>
<view class="info-value">{{stationInfo.signal}}</view>
</view>
</view>
</view>
<view class="function-buttons">
<view class="function-btn" @click="navigateTo('/pages/history/history?id=' + stationId)">
<uni-icons type="calendar" size="20" color="#4CAF50"></uni-icons>
<text>历史数据</text>
</view>
<view class="function-btn" @click="navigateTo('/pages/alert/record?id=' + stationId)">
<uni-icons type="notification" size="20" color="#4CAF50"></uni-icons>
<text>预警记录</text>
</view>
<view class="function-btn" @click="generateReport">
<uni-icons type="paperplane" size="20" color="#4CAF50"></uni-icons>
<text>生成报告</text>
</view>
</view>
<view class="time-filter">
<view
class="filter-item"
v-for="(item, index) in timeFilters"
:key="index"
:class="{ active: currentTimeFilter === index }"
@click="changeTimeFilter(index)"
>
{{item}}
</view>
</view>
<view class="param-cards">
<view class="param-card">
<view class="param-header">
<uni-icons type="thermometer" size="20" color="#333"></uni-icons>
<text class="param-name">水温</text>
<view class="param-status normal">
<uni-icons type="checkmarkempty" size="14" color="#4CAF50"></uni-icons>
<text>正常</text>
</view>
</view>
<view class="param-value">
<text class="value-num">{{paramData.temperature.value}}</text>
<text class="value-unit">°C</text>
</view>
<view class="param-chart">
<qiun-data-charts
type="area"
:opts="getChartOpts('#E3F2FD', '#2196F3')"
:chartData="paramData.temperature.chartData"
/>
</view>
</view>
<view class="param-card">
<view class="param-header">
<uni-icons type="info" size="20" color="#333"></uni-icons>
<text class="param-name">pH值</text>
<view class="param-status normal">
<uni-icons type="checkmarkempty" size="14" color="#4CAF50"></uni-icons>
<text>正常</text>
</view>
</view>
<view class="param-value">
<text class="value-num">{{paramData.ph.value}}</text>
<text class="value-unit">pH</text>
</view>
<view class="param-chart">
<qiun-data-charts
type="area"
:opts="getChartOpts('#E8F5E9', '#4CAF50')"
:chartData="paramData.ph.chartData"
/>
</view>
</view>
<view class="param-card">
<view class="param-header">
<uni-icons type="refreshempty" size="20" color="#333"></uni-icons>
<text class="param-name">溶解氧</text>
<view class="param-status exceed">
<uni-icons type="closeempty" size="14" color="#F44336"></uni-icons>
<text>超标</text>
</view>
</view>
<view class="param-value">
<text class="value-num">{{paramData.oxygen.value}}</text>
<text class="value-unit">mg/L</text>
</view>
<view class="param-chart">
<qiun-data-charts
type="area"
:opts="getChartOpts('#FFEBEE', '#F44336')"
:chartData="paramData.oxygen.chartData"
/>
</view>
</view>
<view class="param-card">
<view class="param-header">
<uni-icons type="eye" size="20" color="#333"></uni-icons>
<text class="param-name">浊度</text>
<view class="param-status warning">
<uni-icons type="info" size="14" color="#FF9800"></uni-icons>
<text>预警</text>
</view>
</view>
<view class="param-value">
<text class="value-num">{{paramData.turbidity.value}}</text>
<text class="value-unit">NTU</text>
</view>
<view class="param-chart">
<qiun-data-charts
type="area"
:opts="getChartOpts('#FFF3E0', '#FF9800')"
:chartData="paramData.turbidity.chartData"
/>
</view>
</view>
</view>
<view class="trend-section">
<view class="section-header">
<text>24小时趋势图</text>
<view class="header-actions">
<uni-icons type="refresh" size="18" color="#666" @click="refreshData"></uni-icons>
<uni-icons type="download" size="18" color="#666" style="margin-left: 30rpx;" @click="downloadData"></uni-icons>
</view>
</view>
<view class="trend-legend">
<view class="legend-item">
<view class="legend-color temperature"></view>
<text>水温</text>
</view>
<view class="legend-item">
<view class="legend-color ph"></view>
<text>pH值</text>
</view>
<view class="legend-item">
<view class="legend-color oxygen"></view>
<text>溶解氧</text>
</view>
<view class="legend-item">
<view class="legend-color turbidity"></view>
<text>浊度</text>
</view>
</view>
<view class="trend-chart">
<qiun-data-charts
type="line"
:opts="trendChartOpts"
:chartData="trendChartData"
/>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
stationId: '',
stationInfo: {
name: '东湖监测点1号',
id: 'ST20230501',
status: '正常运行',
location: '东湖北岸',
installDate: '2023-01-15',
battery: '85%',
signal: '良好'
},
timeFilters: ['今日', '本周', '本月', '自定义'],
currentTimeFilter: 0,
paramData: {
temperature: {
value: '24.5',
chartData: {
categories: ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00'],
series: [{
name: '水温',
data: [23.5, 24.0, 24.2, 24.5, 24.3, 24.5]
}]
}
},
ph: {
value: '7.8',
chartData: {
categories: ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00'],
series: [{
name: 'pH值',
data: [7.5, 7.6, 7.7, 7.8, 7.8, 7.8]
}]
}
},
oxygen: {
value: '3.2',
chartData: {
categories: ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00'],
series: [{
name: '溶解氧',
data: [3.8, 3.6, 3.5, 3.4, 3.3, 3.2]
}]
}
},
turbidity: {
value: '28',
chartData: {
categories: ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00'],
series: [{
name: '浊度',
data: [22, 24, 25, 26, 27, 28]
}]
}
}
},
trendChartOpts: {
color: ['#2196F3', '#4CAF50', '#F44336', '#FF9800'],
padding: [15, 15, 0, 15],
enableScroll: false,
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2
},
extra: {
line: {
type: 'curve',
width: 2
}
}
},
trendChartData: {
categories: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
series: [
{
name: '水温',
data: [22, 21, 22, 24, 25, 23, 22]
},
{
name: 'pH值',
data: [7.5, 7.6, 7.7, 7.8, 7.8, 7.7, 7.6]
},
{
name: '溶解氧',
data: [4.0, 3.8, 3.6, 3.4, 3.2, 3.3, 3.5]
},
{
name: '浊度',
data: [20, 22, 24, 26, 28, 27, 25]
}
]
}
}
},
onLoad(options) {
this.stationId = options.id || ''
this.getStationInfo()
this.getParamData()
},
methods: {
getStationInfo() {
uni.request({
url: `/api/station/detail`,
method: 'GET',
data: {
id: this.stationId
},
success: (res) => {
if (res.data.code === 200) {
this.stationInfo = res.data.data
}
}
})
},
getParamData() {
uni.request({
url: `/api/station/params`,
method: 'GET',
data: {
id: this.stationId,
timeType: this.timeFilters[this.currentTimeFilter]
},
success: (res) => {
if (res.data.code === 200) {
this.paramData = res.data.data.params
this.trendChartData = res.data.data.trend
}
}
})
},
getStatusClass(status) {
if (status.includes('超标')) return 'exceed'
if (status.includes('预警')) return 'warning'
return 'normal'
},
getChartOpts(areaColor, lineColor) {
return {
color: [lineColor],
padding: [15, 15, 0, 0],
enableScroll: false,
legend: { show: false },
xAxis: { show: false },
yAxis: { show: false },
dataLabel: false,
dataPointShape: false,
extra: {
area: {
type: 'curve',
opacity: 0.2,
addLine: true,
width: 2,
gradient: true,
color: areaColor
}
}
}
},
changeTimeFilter(index) {
this.currentTimeFilter = index
this.getParamData()
},
goBack() {
uni.navigateBack()
},
navigateTo(url) {
uni.navigateTo({
url
})
},
refreshData() {
this.getParamData()
},
downloadData() {
uni.showToast({
title: '数据下载中...',
icon: 'none'
})
},
generateReport() {
uni.navigateTo({
url: `/pages/report/generate?id=${this.stationId}`
})
}
}
}
</script>
<style lang="scss">
.detail-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.back-btn {
position: absolute;
left: 20rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.content {
flex: 1;
padding: 20rpx;
}
.station-info {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 20rpx;
.station-icon {
width: 120rpx;
height: 120rpx;
background-color: rgba(76, 175, 80, 0.1);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
image {
width: 60rpx;
height: 60rpx;
}
}
.info-content {
flex: 1;
.station-name {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.station-id {
font-size: 26rpx;
color: #666;
}
}
.station-status {
font-size: 26rpx;
color: #4CAF50;
background-color: rgba(76, 175, 80, 0.1);
padding: 6rpx 20rpx;
border-radius: 30rpx;
&.warning {
color: #FF9800;
background-color: rgba(255, 152, 0, 0.1);
}
&.exceed {
color: #F44336;
background-color: rgba(244, 67, 54, 0.1);
}
}
}
.info-grid {
display: flex;
flex-wrap: wrap;
background-color: #FFFFFF;
border-radius: 10rpx;
margin-bottom: 20rpx;
.info-item {
width: 50%;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
.info-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: rgba(76, 175, 80, 0.1);
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
}
.info-content {
.info-label {
font-size: 26rpx;
color: #666;
margin-bottom: 6rpx;
}
.info-value {
font-size: 28rpx;
font-weight: bold;
}
}
}
}
.function-buttons {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
.function-btn {
flex: 1;
height: 100rpx;
background-color: #FFFFFF;
border-radius: 10rpx;
margin: 0 10rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
text {
font-size: 24rpx;
color: #666;
margin-top: 10rpx;
}
}
}
.time-filter {
display: flex;
background-color: #FFFFFF;
border-radius: 10rpx;
margin-bottom: 20rpx;
.filter-item {
flex: 1;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: #4CAF50;
font-weight: bold;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 25%;
width: 50%;
height: 4rpx;
background-color: #4CAF50;
}
}
}
}
.param-cards {
margin-bottom: 20rpx;
.param-card {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.param-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.param-name {
font-size: 28rpx;
font-weight: bold;
margin-left: 10rpx;
}
.param-status {
margin-left: auto;
display: flex;
align-items: center;
font-size: 24rpx;
&.normal {
color: #4CAF50;
}
&.warning {
color: #FF9800;
}
&.exceed {
color: #F44336;
}
text {
margin-left: 6rpx;
}
}
}
.param-value {
margin-bottom: 20rpx;
.value-num {
font-size: 48rpx;
font-weight: bold;
}
.value-unit {
font-size: 28rpx;
color: #666;
margin-left: 10rpx;
}
}
.param-chart {
height: 150rpx;
}
}
}
.trend-section {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
text {
font-size: 28rpx;
font-weight: bold;
}
}
.trend-legend {
display: flex;
justify-content: center;
margin-bottom: 20rpx;
.legend-item {
display: flex;
align-items: center;
margin: 0 15rpx;
.legend-color {
width: 20rpx;
height: 6rpx;
margin-right: 8rpx;
&.temperature {
background-color: #2196F3;
}
&.ph {
background-color: #4CAF50;
}
&.oxygen {
background-color: #F44336;
}
&.turbidity {
background-color: #FF9800;
}
}
text {
font-size: 24rpx;
color: #666;
}
}
}
.trend-chart {
height: 400rpx;
}
}
}
</style>
Let me continue with the historical data page implementation:
<template>
<view class="history-container">
<view class="header">
<view class="back-btn" @click="goBack">
<uni-icons type="left" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="title">历史数据</text>
</view>
<view class="filter-section">
<view class="filter-header">
<uni-icons type="filter" size="20" color="#4CAF50"></uni-icons>
<text>数据筛选</text>
</view>
<view class="filter-form">
<view class="form-item">
<text class="item-label">监测点</text>
<view class="item-content">
<picker @change="onLakeChange" :value="lakeIndex" :range="lakeOptions">
<view class="picker-content">
<text>{{lakeOptions[lakeIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label"></text>
<view class="item-content">
<picker @change="onStationChange" :value="stationIndex" :range="stationOptions">
<view class="picker-content">
<text>{{stationOptions[stationIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">参数</text>
<view class="item-content">
<picker @change="onParamChange" :value="paramIndex" :range="paramOptions">
<view class="picker-content">
<text>{{paramOptions[paramIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">时间范围</text>
<view class="item-content date-range">
<picker mode="date" @change="onStartDateChange" :value="startDate">
<view class="date-picker">
<text>{{startDate}}</text>
<uni-icons type="calendar" size="14" color="#666"></uni-icons>
</view>
</picker>
<text class="date-separator">至</text>
<picker mode="date" @change="onEndDateChange" :value="endDate">
<view class="date-picker">
<text>{{endDate}}</text>
<uni-icons type="calendar" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<button class="query-btn" @click="queryData">
<uni-icons type="search" size="18" color="#FFFFFF"></uni-icons>
<text>查询数据</text>
</button>
</view>
</view>
<view class="trend-section">
<view class="section-title">
<uni-icons type="bar-chart" size="20" color="#4CAF50"></uni-icons>
<text>{{paramOptions[paramIndex]}}趋势</text>
</view>
<view class="station-legend">
<view class="legend-item" v-for="(item, index) in legendItems" :key="index">
<view class="legend-color" :style="{backgroundColor: item.color}"></view>
<text>{{item.name}}</text>
</view>
</view>
<view class="trend-chart">
<qiun-data-charts
type="line"
:opts="chartOpts"
:chartData="chartData"
/>
</view>
<view class="time-filter">
<view
class="filter-item"
v-for="(item, index) in timeFilters"
:key="index"
:class="{ active: currentTimeFilter === index }"
@click="changeTimeFilter(index)"
>
{{item}}
</view>
</view>
</view>
<view class="data-table">
<view class="table-header">
<view class="header-cell time">时间</view>
<view class="header-cell station">监测点</view>
<view class="header-cell value">{{paramOptions[paramIndex]}}({{paramUnits[paramIndex]}})</view>
<view class="header-cell status">状态</view>
</view>
<scroll-view scroll-y class="table-body">
<view
class="table-row"
v-for="(item, index) in tableData"
:key="index"
>
<view class="cell time">{{item.time}}</view>
<view class="cell station">{{item.station}}</view>
<view class="cell value">{{item.value}}</view>
<view class="cell status">
<view class="status-tag" :class="getStatusClass(item.status)">
{{item.status}}
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
stationId: '',
lakeOptions: ['全部湖泊', '东湖', '南湖', '西湖', '北湖'],
lakeIndex: 0,
stationOptions: ['全部监测点', '东湖监测点1号', '东湖监测点2号', '南湖监测点1号', '南湖监测点2号'],
stationIndex: 0,
paramOptions: ['全部参数', '溶解氧', 'pH值', '浊度', '水温'],
paramUnits: ['', 'mg/L', '', 'NTU', '°C'],
paramIndex: 0,
startDate: '2023/04/01',
endDate: '2023/04/15',
timeFilters: ['日', '周', '月'],
currentTimeFilter: 1,
legendItems: [
{ name: '东湖监测点1号', color: '#F44336' },
{ name: '南湖监测点2号', color: '#FF9800' },
{ name: '西湖监测点3号', color: '#2196F3' },
{ name: '北湖监测点4号', color: '#4CAF50' }
],
chartOpts: {
color: ['#F44336', '#FF9800', '#2196F3', '#4CAF50'],
padding: [15, 15, 0, 15],
enableScroll: false,
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [
{
min: 0
}
]
},
extra: {
line: {
type: 'curve',
width: 2
}
}
},
chartData: {
categories: ['4/9', '4/10', '4/11', '4/12', '4/13', '4/14', '4/15'],
series: [
{
name: '东湖监测点1号',
data: [4.8, 4.5, 4.2, 4.0, 3.8, 3.5, 3.2]
},
{
name: '南湖监测点2号',
data: [5.0, 4.9, 4.8, 4.8, 4.7, 4.7, 4.6]
},
{
name: '西湖监测点3号',
data: [5.5, 5.3, 5.2, 5.1, 5.1, 5.0, 5.0]
},
{
name: '北湖监测点4号',
data: [6.2, 6.1, 6.1, 6.1, 6.1, 6.1, 6.1]
}
]
},
tableData: []
}
},
onLoad(options) {
this.stationId = options.id || ''
if (this.stationId) {
this.initStationData()
}
this.getTableData()
},
methods: {
initStationData() {
uni.request({
url: `/api/station/detail`,
method: 'GET',
data: {
id: this.stationId
},
success: (res) => {
if (res.data.code === 200) {
const station = res.data.data
const lakeIndex = this.lakeOptions.findIndex(item => item === station.lake)
if (lakeIndex > -1) {
this.lakeIndex = lakeIndex
this.onLakeChange({ detail: { value: lakeIndex } })
setTimeout(() => {
const stationIndex = this.stationOptions.findIndex(item => item === station.name)
if (stationIndex > -1) {
this.stationIndex = stationIndex
}
}, 500)
}
}
}
})
},
getTableData() {
uni.showLoading({
title: '加载中...'
})
uni.request({
url: '/api/history/data',
method: 'GET',
data: {
lake: this.lakeIndex === 0 ? '' : this.lakeOptions[this.lakeIndex],
station: this.stationIndex === 0 ? '' : this.stationOptions[this.stationIndex],
param: this.paramIndex === 0 ? '' : this.paramOptions[this.paramIndex],
startDate: this.startDate,
endDate: this.endDate
},
success: (res) => {
if (res.data.code === 200) {
this.tableData = res.data.data
}
},
complete: () => {
uni.hideLoading()
}
})
},
getChartData() {
uni.request({
url: '/api/history/trend',
method: 'GET',
data: {
lake: this.lakeIndex === 0 ? '' : this.lakeOptions[this.lakeIndex],
param: this.paramIndex === 0 ? '' : this.paramOptions[this.paramIndex],
timeType: this.timeFilters[this.currentTimeFilter],
startDate: this.startDate,
endDate: this.endDate
},
success: (res) => {
if (res.data.code === 200) {
this.chartData = res.data.data.chartData
this.legendItems = res.data.data.legendItems
}
}
})
},
onLakeChange(e) {
this.lakeIndex = e.detail.value
this.getStationOptions()
},
getStationOptions() {
uni.request({
url: '/api/station/options',
method: 'GET',
data: {
lake: this.lakeIndex === 0 ? '' : this.lakeOptions[this.lakeIndex]
},
success: (res) => {
if (res.data.code === 200) {
this.stationOptions = ['全部监测点', ...res.data.data]
this.stationIndex = 0
}
}
})
},
onStationChange(e) {
this.stationIndex = e.detail.value
},
onParamChange(e) {
this.paramIndex = e.detail.value
this.getChartData()
},
onStartDateChange(e) {
this.startDate = e.detail.value
},
onEndDateChange(e) {
this.endDate = e.detail.value
},
changeTimeFilter(index) {
this.currentTimeFilter = index
this.getChartData()
},
queryData() {
this.getTableData()
this.getChartData()
},
getStatusClass(status) {
if (status === '超标') return 'exceed'
if (status === '预警') return 'warning'
return 'normal'
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
.history-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.back-btn {
position: absolute;
left: 20rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.filter-section {
background-color: #FFFFFF;
border-radius: 10rpx;
margin: 20rpx;
padding: 20rpx;
.filter-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
text {
font-size: 30rpx;
font-weight: bold;
margin-left: 10rpx;
}
}
.filter-form {
.form-item {
display: flex;
margin-bottom: 20rpx;
.item-label {
width: 150rpx;
font-size: 28rpx;
color: #333;
padding-top: 20rpx;
}
.item-content {
flex: 1;
.picker-content {
height: 80rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 28rpx;
color: #333;
}
}
&.date-range {
display: flex;
align-items: center;
.date-picker {
flex: 1;
height: 80rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 28rpx;
color: #333;
}
}
.date-separator {
margin: 0 20rpx;
font-size: 28rpx;
color: #666;
}
}
}
}
.query-btn {
background-color: #4CAF50;
color: #FFFFFF;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
display: flex;
justify-content: center;
align-items: center;
text {
margin-left: 10rpx;
}
}
}
}
.trend-section {
background-color: #FFFFFF;
border-radius: 10rpx;
margin: 0 20rpx 20rpx;
padding: 20rpx;
.section-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
text {
font-size: 30rpx;
font-weight: bold;
margin-left: 10rpx;
}
}
.station-legend {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20rpx;
.legend-item {
display: flex;
align-items: center;
margin: 0 15rpx 10rpx;
.legend-color {
width: 20rpx;
height: 6rpx;
margin-right: 8rpx;
}
text {
font-size: 24rpx;
color: #666;
}
}
}
.trend-chart {
height: 400rpx;
margin-bottom: 20rpx;
}
.time-filter {
display: flex;
justify-content: center;
border-top: 1px solid #EEEEEE;
padding-top: 20rpx;
.filter-item {
width: 100rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #666;
margin: 0 20rpx;
border-radius: 30rpx;
&.active {
color: #FFFFFF;
background-color: #4CAF50;
}
}
}
}
.data-table {
background-color: #FFFFFF;
border-radius: 10rpx;
margin: 0 20rpx 20rpx;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.table-header {
display: flex;
background-color: #F5F5F5;
padding: 20rpx 0;
.header-cell {
font-size: 28rpx;
font-weight: bold;
text-align: center;
&.time {
flex: 2;
}
&.station {
flex: 2;
}
&.value {
flex: 1;
}
&.status {
flex: 1;
}
}
}
.table-body {
flex: 1;
.table-row {
display: flex;
border-bottom: 1px solid #EEEEEE;
&:last-child {
border-bottom: none;
}
.cell {
padding: 20rpx 10rpx;
font-size: 26rpx;
text-align: center;
&.time {
flex: 2;
}
&.station {
flex: 2;
}
&.value {
flex: 1;
font-weight: bold;
}
&.status {
flex: 1;
.status-tag {
display: inline-block;
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
&.normal {
color: #4CAF50;
background-color: rgba(76, 175, 80, 0.1);
}
&.warning {
color: #FF9800;
background-color: rgba(255, 152, 0, 0.1);
}
&.exceed {
color: #F44336;
background-color: rgba(244, 67, 54, 0.1);
}
}
}
}
}
}
}
}
</style>
Now let's implement the alert records page:
<template>
<view class="alert-record-container">
<view class="header">
<view class="back-btn" @click="goBack">
<uni-icons type="left" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="title">预警记录</text>
</view>
<view class="station-info">
<view class="station-icon">
<image src="/static/images/station-icon.png" mode="aspectFit"></image>
</view>
<view class="info-content">
<view class="station-name">{{stationInfo.name}}</view>
<view class="station-id">ID: {{stationInfo.id}}</view>
</view>
</view>
<view class="filter-section">
<view class="filter-header">
<text>预警筛选</text>
</view>
<view class="filter-form">
<view class="form-item">
<text class="item-label">时间范围</text>
<view class="item-content date-range">
<picker mode="date" @change="onStartDateChange" :value="startDate">
<view class="date-picker">
<text>{{startDate}}</text>
<uni-icons type="calendar" size="14" color="#666"></uni-icons>
</view>
</picker>
<text class="date-separator">至</text>
<picker mode="date" @change="onEndDateChange" :value="endDate">
<view class="date-picker">
<text>{{endDate}}</text>
<uni-icons type="calendar" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">预警类型</text>
<view class="item-content">
<picker @change="onAlertTypeChange" :value="alertTypeIndex" :range="alertTypeOptions">
<view class="picker-content">
<text>{{alertTypeOptions[alertTypeIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">监测参数</text>
<view class="item-content">
<picker @change="onParamChange" :value="paramIndex" :range="paramOptions">
<view class="picker-content">
<text>{{paramOptions[paramIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<button class="query-btn" @click="queryData">
<uni-icons type="search" size="18" color="#FFFFFF"></uni-icons>
<text>查询预警</text>
</button>
</view>
</view>
<view class="tab-section">
<view
class="tab-item"
v-for="(item, index) in tabs"
:key="index"
:class="{ active: currentTab === index }"
@click="changeTab(index)"
>
{{item}}
</view>
</view>
<view class="export-section">
<view class="export-btn" @click="exportExcel">
<uni-icons type="download" size="16" color="#666"></uni-icons>
<text>导出Excel</text>
</view>
<view class="export-btn" @click="exportPDF">
<uni-icons type="download" size="16" color="#666"></uni-icons>
<text>导出PDF</text>
</view>
</view>
<view class="alert-list" v-if="currentTab === 0">
<view class="alert-title">预警记录</view>
<scroll-view scroll-y class="alert-scroll">
<view
class="alert-item"
v-for="(item, index) in alertList"
:key="index"
>
<view class="alert-icon" :class="getAlertTypeClass(item.type)">
<uni-icons :type="getAlertTypeIcon(item.type)" size="20" color="#FFFFFF"></uni-icons>
</view>
<view class="alert-content">
<view class="alert-header">
<view class="param-name">{{item.paramName}}</view>
<view class="alert-time">{{item.time}}</view>
</view>
<view class="alert-values">
<view class="value-item">
<text class="value-label">监测值:</text>
<text class="value-num" :class="getAlertTypeClass(item.type)">{{item.currentValue}}</text>
<text class="value-unit">{{item.unit}}</text>
</view>
<view class="value-item">
<text class="value-label">阈值:</text>
<text class="value-num">{{item.thresholdValue}}</text>
<text class="value-unit">{{item.unit}}</text>
</view>
</view>
<view class="alert-status" :class="getStatusClass(item.status)">
{{item.status}}
</view>
</view>
</view>
<view class="empty-tip" v-if="alertList.length === 0">
<image src="/static/images/empty.png" mode="aspectFit"></image>
<text>暂无预警记录</text>
</view>
</scroll-view>
</view>
<view class="alert-stats" v-else>
<view class="stats-title">预警统计</view>
<view class="stats-chart">
<qiun-data-charts
type="column"
:opts="statsChartOpts"
:chartData="statsChartData"
/>
</view>
<view class="stats-legend">
<view class="legend-item">
<view class="legend-color warning"></view>
<text>预警</text>
</view>
<view class="legend-item">
<view class="legend-color exceed"></view>
<text>超标</text>
</view>
<view class="legend-item">
<view class="legend-color resolved"></view>
<text>已解决</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
stationId: '',
stationInfo: {
name: '东湖监测点1号',
id: 'ST20230501'
},
startDate: '2025/03/12',
endDate: '2025/04/11',
alertTypeOptions: ['全部类型', '预警', '超标'],
alertTypeIndex: 0,
paramOptions: ['全部参数', '溶解氧', 'pH值', '浊度', '水温'],
paramIndex: 0,
tabs: ['预警列表', '预警统计'],
currentTab: 0,
alertList: [],
statsChartOpts: {
color: ['#FF9800', '#F44336', '#4CAF50'],
padding: [15, 15, 0, 15],
legend: {
show: false
},
xAxis: {
disableGrid: true
},
yAxis: {
data: [
{
min: 0
}
]
},
extra: {
column: {
width: 30
}
}
},
statsChartData: {
categories: ['溶解氧', 'pH值', '浊度', '水温'],
series: [
{
name: '预警',
data: [2, 3, 5, 1]
},
{
name: '超标',
data: [1, 0, 2, 0]
},
{
name: '已解决',
data: [3, 2, 4, 1]
}
]
}
}
},
onLoad(options) {
this.stationId = options.id || ''
this.getStationInfo()
this.getAlertList()
this.getStatsData()
},
methods: {
getStationInfo() {
if (!this.stationId) return
uni.request({
url: `/api/station/detail`,
method: 'GET',
data: {
id: this.stationId
},
success: (res) => {
if (res.data.code === 200) {
this.stationInfo = res.data.data
}
}
})
},
getAlertList() {
uni.showLoading({
title: '加载中...'
})
uni.request({
url: '/api/alert/list',
method: 'GET',
data: {
stationId: this.stationId,
startDate: this.startDate,
endDate: this.endDate,
alertType: this.alertTypeIndex === 0 ? '' : this.alertTypeOptions[this.alertTypeIndex],
param: this.paramIndex === 0 ? '' : this.paramOptions[this.paramIndex]
},
success: (res) => {
if (res.data.code === 200) {
this.alertList = res.data.data
}
},
complete: () => {
uni.hideLoading()
}
})
},
getStatsData() {
uni.request({
url: '/api/alert/stats',
method: 'GET',
data: {
stationId: this.stationId,
startDate: this.startDate,
endDate: this.endDate
},
success: (res) => {
if (res.data.code === 200) {
this.statsChartData = res.data.data
}
}
})
},
onStartDateChange(e) {
this.startDate = e.detail.value
},
onEndDateChange(e) {
this.endDate = e.detail.value
},
onAlertTypeChange(e) {
this.alertTypeIndex = e.detail.value
},
onParamChange(e) {
this.paramIndex = e.detail.value
},
queryData() {
this.getAlertList()
this.getStatsData()
},
changeTab(index) {
this.currentTab = index
},
getAlertTypeClass(type) {
if (type === 'exceed') return 'exceed'
return 'warning'
},
getAlertTypeIcon(type) {
if (type === 'exceed') return 'closeempty'
return 'info'
},
getStatusClass(status) {
if (status === '已解决') return 'resolved'
return 'pending'
},
exportExcel() {
uni.showToast({
title: 'Excel导出中...',
icon: 'none'
})
},
exportPDF() {
uni.showToast({
title: 'PDF导出中...',
icon: 'none'
})
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
.alert-record-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.back-btn {
position: absolute;
left: 20rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.station-info {
background-color: #FFFFFF;
border-radius: 10rpx;
margin: 20rpx;
padding: 20rpx;
display: flex;
align-items: center;
.station-icon {
width: 100rpx;
height: 100rpx;
background-color: rgba(76, 175, 80, 0.1);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
image {
width: 50rpx;
height: 50rpx;
}
}
.info-content {
.station-name {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.station-id {
font-size: 26rpx;
color: #666;
}
}
}
.filter-section {
background-color: #FFFFFF;
border-radius: 10rpx;
margin: 0 20rpx 20rpx;
padding: 20rpx;
.filter-header {
margin-bottom: 20rpx;
text {
font-size: 30rpx;
font-weight: bold;
}
}
.filter-form {
.form-item {
display: flex;
margin-bottom: 20rpx;
.item-label {
width: 150rpx;
font-size: 28rpx;
color: #333;
padding-top: 20rpx;
}
.item-content {
flex: 1;
.picker-content {
height: 80rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 28rpx;
color: #333;
}
}
&.date-range {
display: flex;
align-items: center;
.date-picker {
flex: 1;
height: 80rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 28rpx;
color: #333;
}
}
.date-separator {
margin: 0 20rpx;
font-size: 28rpx;
color: #666;
}
}
}
}
.query-btn {
background-color: #4CAF50;
color: #FFFFFF;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
display: flex;
justify-content: center;
align-items: center;
text {
margin-left: 10rpx;
}
}
}
}
.tab-section {
display: flex;
background-color: #FFFFFF;
border-radius: 10rpx 10rpx 0 0;
margin: 0 20rpx;
.tab-item {
flex: 1;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: #4CAF50;
font-weight: bold;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 25%;
width: 50%;
height: 4rpx;
background-color: #4CAF50;
}
}
}
}
.export-section {
display: flex;
justify-content: flex-end;
background-color: #FFFFFF;
padding: 10rpx 20rpx;
margin: 0 20rpx;
border-top: 1px solid #EEEEEE;
.export-btn {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
margin-left: 20rpx;
text {
font-size: 26rpx;
color: #666;
margin-left: 6rpx;
}
}
}
.alert-list {
background-color: #FFFFFF;
border-radius: 0 0 10rpx 10rpx;
margin: 0 20rpx 20rpx;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.alert-title {
font-size: 30rpx;
font-weight: bold;
padding: 20rpx;
border-bottom: 1px solid #EEEEEE;
}
.alert-scroll {
flex: 1;
padding: 0 20rpx;
.alert-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #EEEEEE;
&:last-child {
border-bottom: none;
}
.alert-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: #FF9800;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
&.exceed {
background-color: #F44336;
}
}
.alert-content {
flex: 1;
.alert-header {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
.param-name {
font-size: 28rpx;
font-weight: bold;
}
.alert-time {
font-size: 24rpx;
color: #999;
}
}
.alert-values {
margin-bottom: 10rpx;
.value-item {
display: flex;
align-items: baseline;
margin-bottom: 6rpx;
.value-label {
font-size: 26rpx;
color: #666;
width: 100rpx;
}
.value-num {
font-size: 28rpx;
font-weight: bold;
&.warning {
color: #FF9800;
}
&.exceed {
color: #F44336;
}
}
.value-unit {
font-size: 24rpx;
color: #999;
margin-left: 6rpx;
}
}
}
.alert-status {
display: inline-block;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.resolved {
color: #4CAF50;
background-color: rgba(76, 175, 80, 0.1);
}
&.pending {
color: #FF9800;
background-color: rgba(255, 152, 0, 0.1);
}
}
}
}
.empty-tip {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 100rpx 0;
image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
text {
font-size: 28rpx;
color: #999;
}
}
}
}
.alert-stats {
background-color: #FFFFFF;
border-radius: 0 0 10rpx 10rpx;
margin: 0 20rpx 20rpx;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.stats-title {
font-size: 30rpx;
font-weight: bold;
padding: 20rpx;
border-bottom: 1px solid #EEEEEE;
}
.stats-chart {
flex: 1;
padding: 20rpx;
}
.stats-legend {
display: flex;
justify-content: center;
padding: 0 20rpx 20rpx;
.legend-item {
display: flex;
align-items: center;
margin: 0 20rpx;
.legend-color {
width: 20rpx;
height: 20rpx;
margin-right: 8rpx;
&.warning {
background-color: #FF9800;
}
&.exceed {
background-color: #F44336;
}
&.resolved {
background-color: #4CAF50;
}
}
text {
font-size: 24rpx;
color: #666;
}
}
}
}
}
</style>
Now let's implement the alert information page:
<template>
<view class="alert-container">
<view class="header">
<view class="back-btn" @click="goBack">
<uni-icons type="left" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="title">预警信息</text>
</view>
<view class="tab-section">
<view
class="tab-item"
v-for="(item, index) in tabs"
:key="index"
:class="{ active: currentTab === index }"
@click="changeTab(index)"
>
{{item}}
</view>
</view>
<view class="filter-bar">
<view class="filter-item" @click="showTimeFilter">
<text>时间筛选</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
<view class="filter-item" @click="showStationFilter">
<text>监测点筛选</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
<view class="search-box">
<uni-icons type="search" size="16" color="#999"></uni-icons>
<input type="text" v-model="searchKeyword" placeholder="搜索预警信息" />
</view>
</view>
<scroll-view scroll-y class="alert-list">
<view
class="alert-item"
v-for="(item, index) in filteredAlerts"
:key="index"
>
<view class="alert-header">
<view class="alert-type" :class="getAlertTypeClass(item.type)">
<uni-icons :type="getAlertTypeIcon(item.type)" size="20" color="#FFFFFF"></uni-icons>
<text>{{item.title}}</text>
</view>
<text class="alert-time">{{item.time}}</text>
</view>
<view class="alert-info">
<view class="info-row">
<text class="info-label">监测点:</text>
<text class="info-value">{{item.stationName}}</text>
</view>
<view class="info-row">
<text class="info-label">参数:</text>
<text class="info-value">{{item.paramName}}</text>
</view>
<view class="info-row">
<text class="info-label">当前值:</text>
<text class="info-value" :class="getAlertTypeClass(item.type)">{{item.currentValue}} {{item.unit}}</text>
</view>
<view class="info-row">
<text class="info-label">标准值:</text>
<text class="info-value">{{item.standardValue}} {{item.unit}}</text>
</view>
</view>
<view class="alert-actions">
<view class="action-btn mark" @click="markAsRead(item.id)">
<uni-icons type="checkmarkempty" size="16" color="#666"></uni-icons>
<text>标记已读</text>
</view>
<view class="action-btn process" @click="processAlert(item.id)">
<uni-icons type="gear" size="16" color="#FFFFFF"></uni-icons>
<text>处理</text>
</view>
</view>
</view>
<view class="empty-tip" v-if="filteredAlerts.length === 0">
<image src="/static/images/empty.png" mode="aspectFit"></image>
<text>暂无预警信息</text>
</view>
</scroll-view>
<uni-popup ref="timeFilterPopup" type="bottom">
<view class="filter-popup">
<view class="popup-header">
<text>选择时间范围</text>
<view class="close-btn" @click="closeTimeFilter">
<uni-icons type="close" size="16" color="#666"></uni-icons>
</view>
</view>
<view class="filter-options">
<view
class="filter-option"
v-for="(item, index) in timeFilterOptions"
:key="index"
:class="{ active: timeFilterIndex === index }"
@click="selectTimeFilter(index)"
>
{{item}}
</view>
</view>
<view class="custom-date" v-if="timeFilterIndex === timeFilterOptions.length - 1">
<view class="date-range">
<picker mode="date" @change="onStartDateChange" :value="startDate">
<view class="date-picker">
<text>{{startDate}}</text>
<uni-icons type="calendar" size="14" color="#666"></uni-icons>
</view>
</picker>
<text class="date-separator">至</text>
<picker mode="date" @change="onEndDateChange" :value="endDate">
<view class="date-picker">
<text>{{endDate}}</text>
<uni-icons type="calendar" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<button class="confirm-btn" @click="confirmTimeFilter">确定</button>
</view>
</uni-popup>
<uni-popup ref="stationFilterPopup" type="bottom">
<view class="filter-popup">
<view class="popup-header">
<text>选择监测点</text>
<view class="close-btn" @click="closeStationFilter">
<uni-icons type="close" size="16" color="#666"></uni-icons>
</view>
</view>
<scroll-view scroll-y class="station-options">
<view
class="station-option"
v-for="(item, index) in stationOptions"
:key="index"
:class="{ active: stationIndex === index }"
@click="selectStation(index)"
>
{{item}}
</view>
</scroll-view>
<button class="confirm-btn" @click="confirmStationFilter">确定</button>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
data() {
return {
tabs: ['全部', '预警', '超标', '已处理'],
currentTab: 0,
searchKeyword: '',
timeFilterOptions: ['全部时间', '今天', '本周', '本月', '自定义'],
timeFilterIndex: 0,
startDate: '2023/05/01',
endDate: '2023/05/15',
stationOptions: ['全部监测点', '东湖监测点1号', '东湖监测点2号', '南湖监测点1号', '南湖监测点2号', '西湖监测点3号', '北湖监测点4号'],
stationIndex: 0,
alertList: [],
tempTimeFilterIndex: 0,
tempStationIndex: 0
}
},
computed: {
filteredAlerts() {
let result = this.alertList
// 根据标签筛选
if (this.currentTab === 1) {
result = result.filter(item => item.type === 'warning')
} else if (this.currentTab === 2) {
result = result.filter(item => item.type === 'exceed')
} else if (this.currentTab === 3) {
result = result.filter(item => item.status === 'resolved')
}
// 根据关键词搜索
if (this.searchKeyword) {
const keyword = this.searchKeyword.toLowerCase()
result = result.filter(item =>
item.title.toLowerCase().includes(keyword) ||
item.stationName.toLowerCase().includes(keyword) ||
item.paramName.toLowerCase().includes(keyword)
)
}
return result
}
},
onLoad() {
this.getAlertList()
},
methods: {
getAlertList() {
uni.showLoading({
title: '加载中...'
})
uni.request({
url: '/api/alert/list',
method: 'GET',
data: {
timeType: this.timeFilterIndex === this.timeFilterOptions.length - 1 ? 'custom' : this.timeFilterOptions[this.timeFilterIndex],
startDate: this.startDate,
endDate: this.endDate,
station: this.stationIndex === 0 ? '' : this.stationOptions[this.stationIndex]
},
success: (res) => {
if (res.data.code === 200) {
this.alertList = res.data.data
}
},
complete: () => {
uni.hideLoading()
}
})
},
changeTab(index) {
this.currentTab = index
},
getAlertTypeClass(type) {
if (type === 'exceed') return 'exceed'
return 'warning'
},
getAlertTypeIcon(type) {
if (type === 'exceed') return 'closeempty'
return 'info'
},
markAsRead(id) {
uni.request({
url: '/api/alert/markAsRead',
method: 'POST',
data: {
id
},
success: (res) => {
if (res.data.code === 200) {
uni.showToast({
title: '已标记为已读',
icon: 'success'
})
this.getAlertList()
}
}
})
},
processAlert(id) {
uni.navigateTo({
url: `/pages/alert/process?id=${id}`
})
},
showTimeFilter() {
this.tempTimeFilterIndex = this.timeFilterIndex
this.$refs.timeFilterPopup.open()
},
closeTimeFilter() {
this.timeFilterIndex = this.tempTimeFilterIndex
this.$refs.timeFilterPopup.close()
},
selectTimeFilter(index) {
this.tempTimeFilterIndex = index
},
confirmTimeFilter() {
this.timeFilterIndex = this.tempTimeFilterIndex
this.$refs.timeFilterPopup.close()
this.getAlertList()
},
showStationFilter() {
this.tempStationIndex = this.stationIndex
this.$refs.stationFilterPopup.open()
},
closeStationFilter() {
this.stationIndex = this.tempStationIndex
this.$refs.stationFilterPopup.close()
},
selectStation(index) {
this.tempStationIndex = index
},
confirmStationFilter() {
this.stationIndex = this.tempStationIndex
this.$refs.stationFilterPopup.close()
this.getAlertList()
},
onStartDateChange(e) {
this.startDate = e.detail.value
},
onEndDateChange(e) {
this.endDate = e.detail.value
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
.alert-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.back-btn {
position: absolute;
left: 20rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.tab-section {
display: flex;
background-color: #FFFFFF;
margin: 20rpx 20rpx 0;
border-radius: 10rpx;
.tab-item {
flex: 1;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: #4CAF50;
font-weight: bold;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 25%;
width: 50%;
height: 4rpx;
background-color: #4CAF50;
}
}
}
}
.filter-bar {
display: flex;
padding: 20rpx;
.filter-item {
height: 70rpx;
background-color: #FFFFFF;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
margin-right: 20rpx;
text {
font-size: 26rpx;
color: #333;
margin-right: 10rpx;
}
}
.search-box {
flex: 1;
height: 70rpx;
background-color: #FFFFFF;
border-radius: 8rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
input {
flex: 1;
height: 70rpx;
font-size: 26rpx;
margin-left: 10rpx;
}
}
}
.alert-list {
flex: 1;
padding: 0 20rpx 20rpx;
.alert-item {
background-color: #FFFFFF;
border-radius: 10rpx;
margin-bottom: 20rpx;
overflow: hidden;
.alert-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1px solid #EEEEEE;
.alert-type {
display: flex;
align-items: center;
&.warning {
color: #FF9800;
}
&.exceed {
color: #F44336;
}
text {
font-size: 28rpx;
font-weight: bold;
margin-left: 10rpx;
}
}
.alert-time {
font-size: 24rpx;
color: #999;
}
}
.alert-info {
padding: 20rpx;
.info-row {
display: flex;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
width: 120rpx;
font-size: 26rpx;
color: #666;
}
.info-value {
flex: 1;
font-size: 26rpx;
&.warning {
color: #FF9800;
font-weight: bold;
}
&.exceed {
color: #F44336;
font-weight: bold;
}
}
}
}
.alert-actions {
display: flex;
border-top: 1px solid #EEEEEE;
.action-btn {
flex: 1;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
&.mark {
background-color: #FFFFFF;
text {
font-size: 26rpx;
color: #666;
margin-left: 6rpx;
}
}
&.process {
background-color: #4CAF50;
text {
font-size: 26rpx;
color: #FFFFFF;
margin-left: 6rpx;
}
}
}
}
}
.empty-tip {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 100rpx 0;
image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
text {
font-size: 28rpx;
color: #999;
}
}
}
.filter-popup {
background-color: #FFFFFF;
border-radius: 20rpx 20rpx 0 0;
padding: 30rpx;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
text {
font-size: 30rpx;
font-weight: bold;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.filter-options {
display: flex;
flex-wrap: wrap;
margin-bottom: 30rpx;
.filter-option {
width: 30%;
height: 70rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 26rpx;
color: #333;
margin-right: 5%;
margin-bottom: 20rpx;
&:nth-child(3n) {
margin-right: 0;
}
&.active {
background-color: rgba(76, 175, 80, 0.1);
color: #4CAF50;
font-weight: bold;
}
}
}
.custom-date {
margin-bottom: 30rpx;
.date-range {
display: flex;
align-items: center;
.date-picker {
flex: 1;
height: 70rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 26rpx;
color: #333;
}
}
.date-separator {
margin: 0 20rpx;
font-size: 26rpx;
color: #666;
}
}
}
.station-options {
max-height: 400rpx;
margin-bottom: 30rpx;
.station-option {
height: 80rpx;
display: flex;
align-items: center;
font-size: 28rpx;
color: #333;
border-bottom: 1px solid #EEEEEE;
&:last-child {
border-bottom: none;
}
&.active {
color: #4CAF50;
font-weight: bold;
}
}
}
.confirm-btn {
background-color: #4CAF50;
color: #FFFFFF;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
}
}
}
</style>
Now let's implement the alert processing page:
<template>
<view class="process-container">
<view class="header">
<view class="back-btn" @click="goBack">
<uni-icons type="left" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="title">预警处理</text>
</view>
<scroll-view scroll-y class="content">
<view class="alert-info">
<view class="alert-type" :class="getAlertTypeClass(alertInfo.type)">
{{alertInfo.title}}
</view>
<view class="info-row">
<text class="info-label">监测点:</text>
<text class="info-value">{{alertInfo.stationName}}</text>
</view>
<view class="info-row">
<text class="info-label">参数:</text>
<text class="info-value">{{alertInfo.paramName}}</text>
</view>
</view>
<view class="process-form">
<view class="form-title">处理信息</view>
<view class="form-item">
<text class="item-label">处理类型</text>
<view class="item-content">
<picker @change="onProcessTypeChange" :value="processTypeIndex" :range="processTypeOptions">
<view class="picker-content">
<text>{{processTypeIndex === 0 ? '请选择处理类型' : processTypeOptions[processTypeIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">处理方法</text>
<view class="item-content">
<picker @change="onProcessMethodChange" :value="processMethodIndex" :range="processMethodOptions">
<view class="picker-content">
<text>{{processMethodIndex === 0 ? '请选择处理方法' : processMethodOptions[processMethodIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">处理结果</text>
<view class="item-content">
<picker @change="onProcessResultChange" :value="processResultIndex" :range="processResultOptions">
<view class="picker-content">
<text>{{processResultIndex === 0 ? '请选择处理结果' : processResultOptions[processResultIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">处理详情</text>
<view class="item-content">
<textarea
v-model="processDetail"
placeholder="请详细描述处理过程和结果..."
class="textarea-content"
/>
</view>
</view>
<view class="form-item">
<text class="item-label">后续建议</text>
<view class="item-content">
<textarea
v-model="followupSuggestion"
placeholder="请输入后续维护建议..."
class="textarea-content"
/>
</view>
</view>
</view>
</scroll-view>
<view class="footer">
<button class="submit-btn" @click="submitProcess">提交处理</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
alertId: '',
alertInfo: {
title: '溶解氧超标预警',
type: 'exceed',
stationName: '东湖监测点1号',
paramName: '溶解氧'
},
processTypeOptions: ['请选择处理类型', '现场处理', '远程处理', '委托处理', '其他'],
processTypeIndex: 0,
processMethodOptions: ['请选择处理方法', '增氧', '水质调节', '设备维修', '参数校准', '其他'],
processMethodIndex: 0,
processResultOptions: ['请选择处理结果', '已解决', '部分解决', '暂未解决', '需进一步处理'],
processResultIndex: 0,
processDetail: '',
followupSuggestion: ''
}
},
onLoad(options) {
this.alertId = options.id || ''
this.getAlertInfo()
},
methods: {
getAlertInfo() {
if (!this.alertId) return
uni.request({
url: `/api/alert/detail`,
method: 'GET',
data: {
id: this.alertId
},
success: (res) => {
if (res.data.code === 200) {
this.alertInfo = res.data.data
}
}
})
},
getAlertTypeClass(type) {
if (type === 'exceed') return 'exceed'
return 'warning'
},
onProcessTypeChange(e) {
this.processTypeIndex = e.detail.value
},
onProcessMethodChange(e) {
this.processMethodIndex = e.detail.value
},
onProcessResultChange(e) {
this.processResultIndex = e.detail.value
},
submitProcess() {
if (this.processTypeIndex === 0) {
uni.showToast({
title: '请选择处理类型',
icon: 'none'
})
return
}
if (this.processMethodIndex === 0) {
uni.showToast({
title: '请选择处理方法',
icon: 'none'
})
return
}
if (this.processResultIndex === 0) {
uni.showToast({
title: '请选择处理结果',
icon: 'none'
})
return
}
if (!this.processDetail) {
uni.showToast({
title: '请填写处理详情',
icon: 'none'
})
return
}
uni.showLoading({
title: '提交中...'
})
uni.request({
url: '/api/alert/process',
method: 'POST',
data: {
id: this.alertId,
processType: this.processTypeOptions[this.processTypeIndex],
processMethod: this.processMethodOptions[this.processMethodIndex],
processResult: this.processResultOptions[this.processResultIndex],
processDetail: this.processDetail,
followupSuggestion: this.followupSuggestion
},
success: (res) => {
if (res.data.code === 200) {
uni.showToast({
title: '处理成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.data.message || '处理失败',
icon: 'none'
})
}
},
fail: () => {
uni.showToast({
title: '网络异常,请稍后重试',
icon: 'none'
})
},
complete: () => {
uni.hideLoading()
}
})
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
.process-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #F5F5F5;
.header {
height: 90rpx;
background-color: #4CAF50;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.back-btn {
position: absolute;
left: 20rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.title {
color: #FFFFFF;
font-size: 36rpx;
font-weight: bold;
}
}
.content {
flex: 1;
padding: 20rpx;
}
.alert-info {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.alert-type {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
&.warning {
color: #FF9800;
}
&.exceed {
color: #F44336;
}
}
.info-row {
display: flex;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
width: 120rpx;
font-size: 28rpx;
color: #666;
}
.info-value {
flex: 1;
font-size: 28rpx;
font-weight: bold;
}
}
}
.process-form {
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 20rpx;
.form-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.form-item {
margin-bottom: 20rpx;
.item-label {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
display: block;
}
.item-content {
.picker-content {
height: 80rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
text {
font-size: 28rpx;
color: #333;
}
}
.textarea-content {
width: 100%;
height: 200rpx;
background-color: #F5F5F5;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
}
}
}
.footer {
padding: 20rpx;
.submit-btn {
background-color: #4CAF50;
color: #FFFFFF;
height: 90rpx;
line-height: 90rpx;
font-size: 32rpx;
}
}
}
</style>
Let's implement the lake map page:
<template>
<view class="map-container">
<view class="header">
<text class="title">湖泊地图</text>
</view>
<view class="filter-bar">
<view class="filter-item">
<picker @change="onLakeChange" :value="lakeIndex" :range="lakeOptions">
<view class="picker-content">
<text>{{lakeOptions[lakeIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
<view class="filter-item">
<picker @change="onWaterQualityChange" :value="waterQualityIndex" :range="waterQualityOptions">
<view class="picker-content">
<text>{{waterQualityOptions[waterQualityIndex]}}</text>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
</view>
</picker>
</view>
</view>
<view class="map-content">
<map
id="map"
:latitude="mapCenter.latitude"
:longitude="mapCenter.longitude"
:markers="markers"
:scale="16"
show-location
@markertap="onMarkerTap"
style="width: 100%; height: 100%;"
></map>
</view>
<view class="tab-bar">
<view class="tab-item" @click="switchTab('/pages/index/index')">
<uni-icons type="home" size="22" color="#999"></uni-icons>
<text class="tab-text">首页</text>
</view>
<view class="tab-item" @click="switchTab('/pages/monitor/monitor')">
<uni-icons type="settings" size="22" color="#999"></uni-icons>
<text class="tab-text">监测</text>
</view>
<view class="tab-item active">
<uni-icons type="map" size="22" color="#4CAF50"></uni-icons>
<text class="tab-text active">地图</text>
</view>
<view class="tab-item" @click="switchTab('/pages/report/report')">
<uni-icons type="paperplane" size="22" color="#999"></uni-icons>
<text class="tab-text">报表</text>
</view>
<view class="tab-item" @click="switchTab('/pages/user/user')">
<uni-icons type="person" size="22" color="#999"></uni-icons>
<text class="tab-text">我的</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
lakeOptions: ['全部湖泊', '东湖', '南湖', '西湖', '北湖'],
lakeIndex: 0,
waterQualityOptions: ['综合水质', '溶解氧',