目录
1 AirC首页
1.1 需求分析
- 查看网格及切换网格;
- 查看描述信息
- 查看空气质量参数
- 查看气象参数
- 查看监控设备在所有监控网点中的排名;
- 查看站点视频,点击可以播放视频;
- 查看历史记录,有变化趋势图,同时也可以导出保存。
1.2 原型设计
1.3 结果展示
- 1.查询某设备实时气象参数和空气质量参数
用户点击下拉选框选择希望查询的设备序列号,选好之后点击查询按钮,前端根据用户选择的序列号向后端发送get请求,并将获取的response中的相应值(PM2.5、PM10、SO2、CO2、NO2、O3、温度、湿度、风速、风向、光照、气压)赋值给全局变量,并更新页面显示的值,弹出获取气象参数成功的提示。
- 2.清空设备序列号输入框
前端向后端发送get请求,五请求参数。获取空气质量参数成功后,在右下角展示出PM2.5,PM10,SO2,CO,NO2这5种气体含量的变化趋势,将鼠标移至某一天的位置处,还能显示这一天这5种气体的具体含量,同时还支持导出图表下载到本地。
- 3.设备AQI排名信息
前端向后端发送get请求,无请求参数。获取排名数据成功后,在右上角的图表展示出设备排名、设备序列号、AQI以及污染详情。
- 4.近7天的空气质量参数
前端向后端发送get请求,五请求参数。获取空气质量参数成功后,在右下角展示出PM2.5,PM10,SO2,CO,NO2这5种气体含量的变化趋势,将鼠标移至某一天的位置处,还能显示这一天这5种气体的具体含量,同时还支持导出图表下载到本地。
1.4 优化思路
- 可供查询的设备应该要扩充范围到所有设备;
- 在排名表格中,增加滚动条,使用户能够下滑看到更多排名信息;
- 空气质量参数为静态数据,可以通过数据格式转换获取对应的数据值;
- 用户可自行选择数据的时间时间间隔,每小时,每天,每周,每月,每小时之类。
1.5 源代码
<template>
<div id="Index">
<!-- 占位 -->
<div>
<!-- 搜索相关开始 -->
<el-select v-model="value" placeholder="请选择设备序列号" style="width:300px;height:50px;border:1px solid #fff;margin:15px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!-- <el-button
type="primary"
style="background-color:#BFDFAF; border-color:#BFDFAF;"
@click='Search' icon="el-icon-search">查 询</el-button> -->
<el-button
type="primary"
style="background-color:#BFDFAF; border-color:#BFDFAF;"
@click='Search' icon="el-icon-search">查 询</el-button>
<el-button @click="clearText" icon="el-icon-delete">清 空</el-button>
<!-- 搜索相关结束 -->
<div style="width:100%;height:50px;text-align:center;border:1px solid #fff;font-size:15">
<h3>该设备实时气象参数和空气质量参数如下</h3>
</div>
<br>
<div style="width:100%;height:30px;border:1px solid #fff;margin:0 auto;">
<span slot="title">
PM2.5:{{PM25}}
PM10:{{PM10}}
SO2:{{SO2}}
</span>
</div>
<div style="width:100%;height:30px;border:1px solid #fff;margin:0 auto;">
<span slot="title">
CO2:{{CO2}}
NO2:{{NO2}}
O3:{{O3}}
</span>
</div>
</div>
<div>
<el-table
title="监控设备排名"
:data="tableData"
style="width: 100%;height:100%"
:header-cell-style="{'text-align':'center'}"
:cell-style="{'text-align':'center'}">
<el-table-column
prop="rank"
label="排名"
width="50">
</el-table-column>
<el-table-column
prop="serialnum"
label="设备序列号"
width="150">
</el-table-column>
<el-table-column
prop="AQI"
label="AQI"
width="200">
</el-table-column>
<el-table-column
prop="desc"
label="详情"
width="200">
</el-table-column>
</el-table>
</div>
<!-- <div id='echart3'></div> -->
<div>
<div id='temperature' style="height:30%;float:left;border:1px solid #fff;margin:5px auto">
<img src="../assets/温度计.png" alt="" align="center">
<span slot="title">{{Temperature}}</span>
</div>
<div id='windspeed' style="height:30%;float:right;border:1px solid #fff;margin:5px auto">
<img src="../assets/风.png" alt="" align="center">
<span slot="title">{{WindSpeed}}</span>
</div>
<div id='humidity' style="height:30%;float:left;border:1px solid #fff;margin:5px auto">
<img src="../assets/水滴.png" alt="" align="center">
<span slot="title">{{Humidity}}</span>
</div>
<div id='winddirection' style="height:30%;float:right;border:1px solid #fff;margin:5px auto">
<img src="../assets/风向标.png" alt="" align="center">
<span slot="title">{{WindDirection}}</span>
</div>
<div id='light' style="height:30%;float:left;border:1px solid #fff;margin:5px auto">
<img src="../assets/太阳.png" alt="" align="center">
<span slot="title">{{Light}}</span>
</div>
<div id='airpressure' style="height:30%;float:right;border:1px solid #fff;margin:5px auto">
<img src="../assets/压力.png" alt="" align="center">
<span slot="title">{{AirPressure}}</span>
</div>
</div>
<div id='echart4'></div>
</div>
</template>
<script type="text/javascript">
var echarts = require('echarts');
import china from 'echarts/map/js/china.js';
// 哪里需要请求数据 就在哪里导入
import axios from '@/http/axios.js';
export default{
data(){
return{
//show: false
tableData: [],
Temperature:'',
Humidity:'',
WindSpeed:'',
WindDirection:'',
Light:'',
AirPressure:'',
AirIndex:[],
PM25:'',
PM10:'',
SO2:'',
CO2:'',
NO2:'',
O3:'',
options: [{
value: 'ASN11000010',
label: 'ASN11000010'
}, {
value: 'ASN11000011',
label: 'ASN11000011'
}, {
value: 'ASN11000012',
label: 'ASN11000012'
}, {
value: 'ASN11000013',
label: 'ASN11000013'
}, {
value: 'ASN11000014',
label: 'ASN11000014'
}, {
value: 'ASN11000015',
label: 'ASN11000015'
}],
value: 'ASN11000010'
}
},
created(){
this.GetAllData();
},
mounted(){
this.drawEchart4();
},
methods:{
Search(){
let url2='/nowdevicedata/'+this.value+'/get_single';
axios.get(url2)
.then((response)=>{
console.log(response);
this.Temperature='温度:'+response.data.data[0]['Temperature']+'℃';
this.Humidity='湿度:'+response.data.data[0]['Humidity']+'%';
this.WindSpeed='风速:'+response.data.data[0]['WindSpeed']+'级';
this.WindDirection='风向:'+this.transfer2(response.data.data[0]['WindDirection']);
//transfer2(response.data.data[0]['WindDirection']);
this.Light='光照:'+response.data.data[0]['Light']+'lx';
this.AirPressure='气压:'+response.data.data[0]['AirPressure']+'Pa';
this.PM25=response.data.data[0]['PM25'];
this.PM10=response.data.data[0]['PM10'];
this.SO2=response.data.data[0]['SO2'];
this.CO2=response.data.data[0]['CO2'];
this.NO2=response.data.data[0]['NO2'];
this.O3=response.data.data[0]['O3'];
console.log("气象参数:",
[this.Temperature,this.Humidity,this.WindSpeed,this.WindDirection,this.Light,this.AirPressure]);
this.$notify.success({
title: '成功',
message: '获取气象参数成功'
});
}).catch((error)=>{
//错误提醒
this.$notify.error({
title: '失败',
message: '获取气象参数失败'
});
})
},
clearText(){
this.value='';
this.GetAllData();
},
GetAllData(){
this.Search();
let url1='/alldevicedata/AQI';
// 第二个参数 请求的参数
axios.get(url1)
.then((response)=>{
// 后台传来什么样的值
console.log(response);
this.tableData=response.data.data;
this.$notify.success({
title: '成功',
message: '获取排名数据成功'
});
}).catch((error)=>{
//错误提醒
this.$notify.error({
title: '失败',
message: '获取排名数据失败'
});
})
let url3='/alldevicedata/Avg';
axios.get(url3)
.then((response)=>{
console.log(response);
/*this.tableData=response.data.data.results;
this.total=response.data.data.count;
//map 数组里所有值进行统一操作 item 子的数组中单个值本身
this.tableData=response.data.data.results.map((item)=>{
//item.is_active=item.is_active?'可用':'禁用'
return item;
})
console.log("表格数据:",this.tableData);*/
this.$notify.success({
title: '成功',
message: '获取空气质量参数成功'
});
}).catch((error)=>{
//错误提醒
this.$notify.error({
title: '失败',
message: '获取空气质量参数失败'
});
})
},
transfer2(type){
if(type==='WN'){
return '西北风'
}else if(type==='ES'){
return '东南风'
}else if(type==='WS'){
return '西南风'
}else if(type==='EN'){
return '东北风'
}else if(type==='S'){
return '南风'
}else if(type==='W'){
return '西风'
}else if(type==='N'){
return '北风'
}else if(type==='E'){
return '东风'
}else{
return type
}
},
drawEchart4(){
var myChart = echarts.init(document.getElementById('echart4'));
var option = {
title: {
text: '空气质量参数'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['PM2.5', 'PM10', 'SO2', 'CO', 'NO2']
},
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['6天前', '5天前', '4天前', '3天前', '2天前', '1天前', '今天']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: 'PM2.5',
type: 'line',
stack: '总量',
areaStyle: {},
data: [114.27, 109.58, 106.23, 116.65, 119.67, 124.93, 119.77]
},
{
name: 'PM10',
type: 'line',
stack: '总量',
areaStyle: {},
data: [124.36, 109.01, 107.08, 127.70, 126.10, 114.93, 117.39]
},
{
name: 'SO2',
type: 'line',
stack: '总量',
areaStyle: {},
data: [457.39, 455.83, 483.35, 583.20, 543.11, 495.40, 486.57]
},
{
name: 'CO',
type: 'line',
stack: '总量',
areaStyle: {},
data: [49.60, 49.17, 48.55, 50.42, 50.02, 49.55, 51.33]
},
{
name: 'NO2',
type: 'line',
stack: '总量',
label: {
normal: {
show: true,
position: 'top'
}
},
areaStyle: {},
data: [1841.89, 2005.73, 2024.41, 2116.98, 1931.17, 1971.21, 2059.63]
}
]
};
myChart.setOption(option);
window.addEventListener('resize',function(){
myChart.resize()
})
},
}
}
</script>
<style type="text/css" scoped>
#Index{
height:100%;
width:100%;
/* 布局方式 */
display: flex;
/* 显示方式 空位置 不连在一起的 */
justify-content: space-around;
/* 位置操作可否换行 可以换行 */
flex-wrap: wrap;
}
#Index div{
width: calc(50% - 5px);
height:calc(50% - 5px);
border:1px solid #ccc;
}
</style>
2 网格管理子页面
2.1 需求分析
- 输入网格相关信息并添加进系统。
- 查看所有网格的信息。
- 为网格绑定设备或解绑设备。
- 删除网格的信息以及与之相绑定的设备信息。
- 修改并保存工程信息。
2.2 原型设计
- 网格列表展示
- 新增网格
- 查询网格编号
- 查询网格名称
- 修改网格
- 网格绑定/解绑设备
- 删除网格
2.3 结果展示
- 1.网格列表展示
- 2.新增网格
点击新增网格按钮,弹出新增网格模态框,填好必填字段(标红色星号)后,点击确认按钮即可通过post请求将新增网格信息提交到后端,请求参数为该表单的值;如果必填字段为空白则会有对应的提示语。
- 3.根据网格编号查询网格
在网格编号输入框内输入网格编号点击查询按钮,前端向后端提交get请求,参数为输入的网格编号。若该网格编号存在于数据库中,则表格更改为该网格的信息;若该网格编号不存在于数据库中,则提示查询失败。
- 4.清空网格编号输入
点击清空按钮,输入框中的内容全部清空,且重新获取网格列表数据。
- 5.修改网格信息
点击网格操作一栏的修改按钮,弹出修改网格模态框,模态框中的数据与该网格存在数据库中的数据对应。修改完成后,点击底部的确定按钮,就会将前端的操作提交put请求到后端,请求参数为修改后的表单值,并重新获取网格列表数据。若点击取消按钮或右上角的×号,则会取消修改操作,数据库中的数据不改变。
- 6.将网格绑定/解绑对应的设备
点击网格操作一栏的绑定/解绑按钮,弹出绑定设备/解绑设备模态框。用户输入设备序列号后,点击底部的确定按钮,前端就会根据输入的设备序列号提交put请求到后端执行数据库表的更新操作,请求参数为该网格编号,后端将该设备的gridBelong属性置为该网格编号。若点击取消按钮或右上角的×号,则会取消修改操作,数据库中的数据不改变。
- 7.删除网格
点击网格操作一栏的删除按钮,弹出是否确认删除提示框。若点击确认按钮,就会通过delete方法将前端操作提交到后端执行数据库表的delete操作,删除网格表内对应的元组。若点击取消按钮或右上角的×号,则会取消修改操作,数据库中的数据不改变。
2.4 优化思路
- 新增/修改网格模态框中,对输入经纬度、占地面积进行数值验证;
- 解绑设备增加验证功能,验证该网格是否已经与此设备绑定;
- 通过网格编号和网格名称的模糊查询;
- 提交查询时自动调整当前页为第一页。
2.5 源代码
// 两个模态框的问题 怎么区分? 网格的绑定/解绑设备 设备的详情 已解决
//修改弹出日期,是NaN
//查询函数Search
//分页参数不太懂
//数据交互是个大问题!!!
<template>
<div id="grid">
<!-- 搜索相关开始 -->
<el-button
type="primary"
icon="el-icon-circle-plus"
style="background-color:#BFDFAF; border-color:#BFDFAF; margin-right:80px;"
@click='Add'>新增网格</el-button>
<el-input id="input_id" v-model="input_id" placeholder="请输入网格编号"
style="width: 200px; margin-right:30px;" clearable></el-input>
<el-input id="input_name" v-model="input_name" placeholder="请输入网格名称"
style="width: 200px; margin-right:30px;" clearable></el-input>
<el-button
type="primary"
style="background-color:#BFDFAF; border-color:#BFDFAF;"
@click='Search' icon="el-icon-search">查 询</el-button>
<el-button @click="clearText" icon="el-icon-delete">清 空</el-button>
<!-- 搜索相关结束 -->
<!-- 表格开始 -->
<el-table
:data="tableData.slice((currentPage-1)*pageSize,currentPage*pageSize)"
style="width: 100%"
:header-cell-style="{'text-align':'center'}"
:cell-style="{'text-align':'center'}">
<el-table-column prop="gridNumber" label="网格编号" width="150"></el-table-column>
<el-table-column prop="gridName" label="网格名称" width="150"></el-table-column>
<el-table-column prop="type" label="类型" width="100">
<template slot-scope="scope">
{{transfer(scope.row.type)}}
</template>
</el-table-column>
<el-table-column prop="lastStartTime" label="时间" width='180'></el-table-column>
<el-table-column prop="gridAdminAccount" label="负责人" width='70'></el-table-column>
<el-table-column prop="longitude,latitude" label="经纬度" width='150'>
<template slot-scope="scope">
{{scope.row.longitude.toFixed(2)}},{{scope.row.latitude.toFixed(2)}}
</template>
</el-table-column>
<el-table-column prop="constructionUnits" label="施工单位" width='150'></el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button @click="Edit(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="Bind(scope.row)" type="text" size="small">绑定</el-button>
<el-button @click='Unbind(scope.row)' type="text" size="small">解绑</el-button>
<el-button @click="Delete(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 表格结束 -->
<!-- 表单开始 -->
<el-dialog :title="title" :visible.sync="dialogFormVisible" custom-class="customWidth">
<el-form :model="form" :rules='rules' ref="Form">
<!-- {{form}} -->
<el-form-item label="网格编号" prop='gridNumber' :label-width="formLabelWidth">
<el-input v-model="form.gridNumber" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="网格名称" prop='gridName' :label-width="formLabelWidth">
<el-input v-model="form.gridName" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="详细地址" prop='address' :label-width="formLabelWidth">
<el-input v-model="form.address" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="经度" prop='longitude' :label-width="formLabelWidth">
<el-input v-model="form.longitude" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="纬度" prop='latitude' :label-width="formLabelWidth">
<el-input v-model="form.latitude" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="管理员" prop='gridAdminAccount' :label-width="formLabelWidth">
<el-input v-model="form.gridAdminAccount" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="检测时间" :label-width="formLabelWidth">
<el-date-picker v-model="form.lastStartTime" type="date" placeholder="选择日期" style="width:300px">
</el-date-picker>
</el-form-item>
<el-form-item label="施工单位" prop='constructionUnits' :label-width="formLabelWidth">
<el-input v-model="form.constructionUnits" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="类型" :label-width="formLabelWidth" prop='type'>
<el-select v-model="form.type" placeholder="请选择网格类型" style="width:300px">
<el-option label="化工厂" value="1"></el-option>
<el-option label="建筑工地" value="2"></el-option>
<el-option label="发电厂" value="3"></el-option>
<el-option label="街道" value="4"></el-option>
<el-option label="搅拌站" value="5"></el-option>
</el-select>
</el-form-item>
<el-form-item label="占地面积" prop='coverSize' :label-width="formLabelWidth">
<el-input v-model="form.coverSize" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="相关单位" prop='relateUnit' :label-width="formLabelWidth">
<el-input v-model="form.relateUnit" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="工程信息" prop='projectInformation' :label-width="formLabelWidth">
<el-input v-model="form.projectInformation" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="BtnOk('Form')"
style="background-color:#BFDFAF; border-color:#BFDFAF;">确 定</el-button>
</div>
</el-dialog>
<!-- 表单结束 -->
<!-- 表单开始 -->
<el-dialog :title="title" :visible.sync="dialogFormVisible2" custom-class="customWidth">
<el-form :model="form2" :rules='rules2' ref="Form2">
<!-- {{form2}} -->
<el-form-item label="设备编号" prop='serialnum' :label-width="formLabelWidth">
<el-input v-model="form2.serialnum" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible2 = false">取 消</el-button>
<el-button type="primary" @click="BtnOk2('Form2')"
style="background-color:#BFDFAF; border-color:#BFDFAF;">确 定</el-button>
</div>
</el-dialog>
<!--表单结束 -->
<!-- 分页开始 -->
<div class="page" style="position: absolute; bottom: 40px;right: 20px;">
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page.sync="currentPage"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
<!-- 分页结束 -->
</div>
</template>
<script type="text/javascript">
import {formatDate } from '@/common/date.js'
// 哪里需要请求数据 就在哪里导入
import axios from '@/http/axios.js'
export default{
data(){
return{
input_id:'',
input_name:'',
tableData: [],
//模态框的相关数据
dialogFormVisible:false,
title:'新增网格',
form:{
gridNumber:'',
gridName:'',
address:'',
longitude:'',
latitude:'',
gridAdminAccount:'',
lastStartTime:'',
constructionUnits:'',
type:'',
coverSize:'',
relateUnit:'',
projectInformation:''
},
formLabelWidth:'100px',
id:'',
idDis:false,
rules: {
gridNumber:[{required:true,message:'设备编号不能为空'}],
gridName:[{required:true,message:'设备名称不能为空'}],
address:[{required:true,message:'地址不能为空'}],
constructionUnits:[{required:true,message:'施工单位不能为空'}],
coverSize:[{required:true,message:'占地面积不能为空'}],
relateUnit:[{required:true,message:'相关单位不能为空'}],
projectInformation:[{ required: true, message: '工程信息不能为空'}],
},
//模态框的相关数据
dialogFormVisible2:false,
title:'绑定设备',
form2:{
serialnum:''
},
formLabelWidth:'100px',
id:'',
idDis:false,
rules2: {
serialnum:[{required:true,message:'设备序列号不能为空'}],
},
//和分页相关的变量
total:undefined, // 总页数
pageSize:5,
currentPage:1,
currentPageData:[], //当前页显示内容
gridNumber:'',
gridName:'',
}
},
// search 我希望 在输框里输入数据 search 会跟着变 v-model 双向绑定
// 数据监听
// watch:{
// 'search':function(){
// this.GetAllData();
// console.log(this.search);
// }
// },
created(){
//向后台请求 用户数据
this.GetAllData();
},
methods:{
transfer(type){
if(type===1){
return '化工厂'
}else if(type===2){
return '建筑工地'
}else if(type===3){
return '发电厂'
}else if(type===4){
return '街道'
}else if(type===5){
return '搅拌站'
}
},
handleCurrentChange(val){
//筛选条件变了 重新获取值
this.GetAllData();
},
Search(){
axios.get('/grid/Grid/'+this.input_id+'/',{
gridNumber:this.input_id,
})
.then((response)=>{
console.log("如果成功:",response);
this.tableData=[response.data.data]; //不知道什么原因!!!!!表格的值不更新
//alert(response.data.data[serialnum])
console.log(response.data.data)
this.$notify.success({
title: '成功',
message: '查询成功!!!'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '查询失败!!!'
});
console.log(error)// 打印失败的信息
})
},
clearText(){
this.input_id='';
this.input_name='';
this.GetAllData();
},
GetAllData(){
// 后台会提供一个接口
//axios 访问后台接口 获取数据
//this.tableData=获取的数据
//第一个参数 url
let url='/grid/get';
// 第二个参数 请求的参数
axios.get(url,{
//分页的参数
page_size:this.pageSize,
page:this.currentPage,
gridNumber:this.gridNumber,
gridName:this.gridName,
})
.then((response)=>{
// 后台传来什么样的值
console.log(response);
//返回正确的对象,包含所有需要的属性
this.tableData=response.data.data.results;
this.total=response.data.data.count;
//map 数组里所有值进行统一操作 item 子的数组中单个值本身
this.tableData=response.data.data.results.map((item)=>{
//item.is_active=item.is_active?'可用':'禁用'
return item;
})
this.$notify.success({
title: '成功',
message: '获取数据成功'
});
console.log("表格数据:",this.tableData);
}).catch((error)=>{
//错误提醒
this.$notify.error({
title: '失败',
message: '获取数据失败'
});
})
},
Add(){
this.title='新增网格'
this.dialogFormVisible=true;
this.form={};
this.idDis=false;
},
//编辑
Edit(row){
this.title="编辑网格";
this.dialogFormVisible=true;
this.form=row;
//this.id=row.id;
this.idDis=true;
},
//确认新增的按钮
BtnOk(formName){
this.$refs[formName].validate((valid) => {
if(valid){
if(this.title=='新增网格'){
//axios 发送数据到后台
//转换时间格式
this.form.lastStartTime=formatDate(this.form.lastStartTime);
//网址根据实际要改一下
axios.post('/grid/',this.form)
.then((response)=>{
console.log("如果成功:",response);
this.GetAllData();
this.$notify.success({
title: '成功',
message: '新增成功!!!'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '新增失败!!!'
});
console.log(error)// 打印失败的信息
})
}
if(this.title=='编辑网格'){
//转换时间格式
this.form.lastStartTime=formatDate(this.form.lastStartTime);
//网址根据实际要改一下
axios.put('/grid/'+this.form.gridNumber+'/conf',this.form)
.then((response)=>{
console.log("如果成功:",response);
this.GetAllData();
this.$notify.success({
title: '成功',
message: '修改成功!!!'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '修改失败'
});
console.log(error)// 打印失败的信息
})
}
this.dialogFormVisible=false;
}else{
console.log('失败')
}
})
},
//绑定
Bind(row){
this.title="绑定设备";
this.dialogFormVisible2=true;
this.gridNumber=row.gridNumber;
this.form2={};
this.idDis=true;
},
//解绑
Unbind(row){
this.title="解绑设备";
this.dialogFormVisible2=true;
this.form2={};
this.idDis=true;
},
//确认绑定的按钮
BtnOk2(formName){
this.$refs[formName].validate((valid) => {
if(valid){
if(this.title=='绑定设备'){
//axios 发送数据到后台
//网址根据实际要改一下
axios.put('/device/'+this.form2.serialnum+'/conf3',{
//serialnum:this.form2.serialnum,
gridBelong:this.gridNumber,
//gridBelong:'2',
})
.then((response)=>{
console.log("如果成功:",response);
alert(this.gridNumber);
this.$notify.success({
title: '成功',
message: '绑定成功'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '绑定失败'
});
console.log(error)// 打印失败的信息
})
}
if(this.title=='解绑设备'){
//网址根据实际要改一下
//这里是解绑,可能需要修改一下数据库的连接关系之类的
axios.put('/device/'+this.form2.serialnum+'/conf3',{
//serialnum:this.form2.serialnum,
gridBelong:'',
})
.then((response)=>{
console.log("如果成功:",response);
this.$notify.success({
title: '成功',
message: '解绑成功'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '解绑失败'
});
console.log(error)// 打印失败的信息
})
}
this.dialogFormVisible2=false;
}else{
console.log('失败')
}
})
},
//删除
Delete(row){
this.$confirm('此操作将永久删除该网格, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
/// id ==> 后台 我们要删除 删除 谨慎
axios.delete('/grid/'+row.gridNumber+'/delete',{
gridNumber:row.gridNumber,
})
.then((response)=>{
this.$notify.success({
title: '成功',
message: '删除成功'
});
this.GetAllData();
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '删除失败'
});
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
}
}
</script>
<style type="text/css">
.customWidth{
width:500px;
}
</style>
3 设备管理子页面
3.1 需求分析
- 输入设备相关信息并添加进系统。
- 查看所有网格的信息。
- 冻结设备使之不能被绑定。
- 修改设备参数
3.2 原型设计
- 设备列表
- 新增设备
- 修改设备
- 查询设备编号
- 查询设备名称
- 删除设备
3.3 结果展示
- 1.设备列表展示
- 2.激活设备
点击激活设备按钮,弹出激活设备模态框,用户输入设备序列号,点击底部的确认按钮则提交操作到后端。设备序列号是必填字段,如果不填则会有错误提示。前端根据用户输入的设备序列号提交put请求,参数为active,在设备表里将该设备的active属性置为1.
- 3.通过设备序列号查询设备
在设备序列号输入框内输入设备序列号点击查询按钮,前端向后端提交get请求,参数为输入的设备序列号。若该设备序列号存在于数据库中,则表格更改为该设备的信息;若该设备序列号不存在于数据库中,则提示查询失败。
- 4.清空输入框
点击清空按钮,输入框中的内容全部清空,且重新获取设备列表数据。
- 5.查看设备详情
点击操作一栏的详情,弹出设备详情模态框,用户点击确认按钮不会执行任何操作。
- 6.修改设备信息
点击操作一栏的修改,弹出编辑设备模态框。用户可修改除设备序列号以外的字段,但要保证ip地址和mac地址不为空,不然会有错误提示。修改完成后点击底部的确认按钮,前端向后端提交patch请求,参数为该表单的值,更改数据库设备表中相应字段的值。
- 7.冻结设备
点击操作一栏的冻结按钮,弹出提示,向用户确认是否冻结该设备。若用户点击确认,则前端根据该设备的序列号向后端发出put请求,active参数为0,后端修改数据库中的该设备active值为0.
3.4 优化思路
- 激活设备时要求用户输入该设备密码,并验证密码是否正确。只有正确才可向后台提交激活设备操作;
- 激活设备时验证该设备是否已激活,若已经激活则提示用户相关信息;
- 查询时的分页问题,同网格子页面的问题;
- 查看设备详情时,设置字段显示形式为文本格式,而不是输入框,防止用户做了修改虽然不提交到后端但表单值已经被改的情况。
3.5 源代码
<template>
<div id="equip">
<!-- 搜索相关开始 -->
<el-button
type="primary"
icon="el-icon-circle-plus"
style="background-color:#BFDFAF; border-color:#BFDFAF; margin-right:80px;"
@click='Add'>激活设备</el-button>
<el-input id="input_id" v-model="input_id" placeholder="请输入设备序列号"
style="width: 200px; margin-right:30px;" clearable></el-input>
<el-input id="input_name" v-model="input_name" placeholder="请输入设备名称"
style="width: 200px; margin-right:30px;" clearable></el-input>
<el-button
type="primary"
style="background-color:#BFDFAF; border-color:#BFDFAF;"
@click='Search' icon="el-icon-search">查 询</el-button>
<el-button @click="clearText" icon="el-icon-delete">清 空</el-button>
<!-- 搜索相关结束 -->
<!-- 表格开始 -->
<el-table
:data="tableData.slice((currentPage-1)*pageSize,currentPage*pageSize)"
style="width: 100%"
:header-cell-style="{'text-align':'center'}"
:cell-style="{'text-align':'center'}">
<el-table-column prop="serialnum" label="设备序列号" width="150"></el-table-column>
<el-table-column prop="deviceName" label="设备名称" width="150"></el-table-column>
<el-table-column prop="is_active" label="激活情况" width="100">
<template slot-scope="scope">
{{scope.row.active?"已激活":"未激活"}}
</template>
</el-table-column>
<el-table-column prop="is_line" label="在线情况" width='180'>
<template slot-scope="scope">
{{scope.row.line?"在线":"离线"}}
</template>
</el-table-column>
<el-table-column prop="longitude,latitude" label="经纬度" width='150'>
<template slot-scope="scope">
{{scope.row.longitude}},{{scope.row.latitude}}
<!-- {{scope.row.longitude.toFixed(2)}},{{scope.row.latitude.toFixed(2)}} -->
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button @click="Detail(scope.row)" type="text" size="small">详情</el-button>
<el-button @click="Edit(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="Delete(scope.row)" type="text" size="small">冻结</el-button>
</template>
</el-table-column>
</el-table>
<!-- 表格结束 -->
<!-- 表单开始 -->
<el-dialog :title="title" :visible.sync="dialogFormVisible1" custom-class="customWidth">
<el-form :model="form1" :rules='rules1' ref="Form1">
<!-- {{form1}} -->
<el-form-item label="序列号" prop='serialnum' :label-width="formLabelWidth">
<el-input v-model="form1.serialnum" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<!-- <el-form-item label="密码" prop='devkey' :label-width="formLabelWidth">
<el-input v-model="form1.devkey" type="devkey" autocomplete="off" style="width:300px"></el-input>
</el-form-item> -->
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible2 = false">取 消</el-button>
<el-button type="primary" @click="BtnOk1('Form1')"
style="background-color:#BFDFAF; border-color:#BFDFAF;">确 定</el-button>
</div>
</el-dialog>
<!--表单结束 -->
<!-- 表单开始 -->
<el-dialog :title="title" :visible.sync="dialogFormVisible2" custom-class="customWidth">
<el-form :model="form2" :rules='rules2' ref="Form2">
<!-- {{form2}} -->
<el-form-item label="设备序列号" prop='serialnum' :label-width="formLabelWidth">
<el-input v-model="form2.serialnum" autocomplete="off" style="width:300px" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="设备名称" prop='deviceName' :label-width="formLabelWidth">
<el-input v-model="form2.deviceName" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="设备状态" :label-width="formLabelWidth" prop='class'>
<el-select v-model="form2.line" placeholder="请选择设备状态">
<el-option label="在线" value="1"></el-option>
<el-option label="离线" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="ip地址" prop='ip' :label-width="formLabelWidth">
<el-input v-model="form2.ip" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="mac地址" prop='mac' :label-width="formLabelWidth">
<el-input v-model="form2.mac" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="视频地址" prop='video' :label-width="formLabelWidth">
<el-input v-model="form2.video" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="摄像头编码" prop='camera' :label-width="formLabelWidth">
<el-input v-model="form2.camera" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="经度" prop='longitude' :label-width="formLabelWidth">
<el-input v-model="form2.longitude" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="纬度" prop='latitude' :label-width="formLabelWidth">
<el-input v-model="form2.latitude" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="PM2.5阈值" prop='PM25T' :label-width="formLabelWidth">
<el-input v-model="form2.PM25T" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="PM2.5校准" prop='PM25C' :label-width="formLabelWidth">
<el-input v-model="form2.PM25C" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="PM10阈值" prop='PM10T' :label-width="formLabelWidth">
<el-input v-model="form2.PM10T" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
<el-form-item label="PM10校准" prop='PM10C' :label-width="formLabelWidth">
<el-input v-model="form2.PM10C" autocomplete="off" style="width:300px"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible2 = false">取 消</el-button>
<el-button type="primary" @click="BtnOk2('Form2')"
style="background-color:#BFDFAF; border-color:#BFDFAF;">确 定</el-button>
</div>
</el-dialog>
<!-- 表单结束 -->
<!-- 分页开始 -->
<div class="page" style="position: absolute; bottom: 40px;right: 20px;">
<!-- <el-pagination
background
layout="prev, pager, next"
:total="1000">
</el-pagination> -->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page.sync="currentPage"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
<!-- 分页结束 -->
</div>
</template>
<script type="text/javascript">
import {formatDate } from '@/common/date.js'
// 哪里需要请求数据 就在哪里导入
import axios from '@/http/axios.js'
export default{
data(){
return{
input_id:'',
input_name:'',
tableData: [],
//模态框的相关数据
dialogFormVisible1:false,
title:'激活设备',
form1:{
serialnum:'',
//devkey:''
},
formLabelWidth:'100px',
id:'',
idDis:false,
rules1: {
serialnum:[{required:true,message:'设备编号不能为空'}]
},
//模态框的相关数据
dialogFormVisible2:false,
title:'详情',
form2:{
serialnum:'',
deviceName:'',
line:'',
ip:'',
mac:'',
video:'',
camera:'',
longitude:'',
latitude:'',
PM25T:'',
PM25C:'',
PM10T:'',
PM10C:''
},
formLabelWidth:'100px',
id:'',
idDis:false,
rules2: {
ip:[{required:true,message:'ip地址不能为空'}],
mac:[{required:true,message:'mac地址不能为空'}],
},
//和分页相关的变量
total:1, //总页数,默认为1
currentPage:1, //当前页数,默认为1
pageSize:5, //每页显示数量
currentPageData:[], //当前页显示内容
serialnum:'',
deviceName:'',
}
},
// search 我希望 在输框里输入数据 search 会跟着变 v-model 双向绑定
// 数据监听
// watch:{
// 'search':function(){
// this.GetAllData();
// console.log(this.search);
// }
// },
created(){
//向后台请求 用户数据
this.GetAllData();
},
methods:{
handleCurrentChange(val){
//筛选条件变了 重新获取值
this.GetAllData();
},
clearText(){
this.input_id='';
this.input_name='';
this.GetAllData();
},
Search(){
axios.get('/device/Device/'+this.input_id+'/',{
serialnum:this.input_id,
})
.then((response)=>{
console.log("如果成功:",response);
this.tableData=[response.data.data];
//alert(response.data.data[serialnum])
console.log(response.data.data)
this.$notify.success({
title: '成功',
message: '查询成功!!!'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '查询失败!!!'
});
console.log(error)// 打印失败的信息
})
},
GetAllData(){
// 后台会提供一个接口
//axios 访问后台接口 获取数据
//this.tableData=获取的数据
//第一个参数 url
//let url='http://175.24.65.80:8000/device/get';
let url='/device/get';
// 第二个参数 请求的参数
axios.get(url,{
//分页的参数
page_size:this.pageSize,
page:this.currentPage,
serialnum:this.serialnum,
deviceName:this.deviceName,
})
.then((response)=>{
// 后台传来什么样的值
console.log(response);
//返回正确的对象,包含所有需要的属性
this.tableData=response.data.data.results;
this.total=response.data.data.count;
//alert(this.total);
//map 数组里所有值进行统一操作 item 子的数组中单个值本身
this.tableData=response.data.data.results.map((item)=>{
//item.line=item.line?'在线':'离线'
//item.active=item.active?'已激活':'未激活'
return item;
})
console.log("表格数据:",this.tableData);
}).catch((error)=>{
//错误提醒
this.$notify.error({
title: '失败',
message: '获取数据失败'
});
})
},
Add(){
this.title='激活设备'
this.dialogFormVisible1=true;
this.form1={};
this.idDis=false;
},
//确认新增的按钮
BtnOk1(formName){
this.$refs[formName].validate((valid) => {
if(valid){
if(this.title=='激活设备'){
//axios 发送数据到后台
//转换时间格式
//this.form.date=formatDate(this.form.date);
//网址根据实际要改一下
axios.put('/device/'+this.form1.serialnum+'/active',{
active:1
})
.then((response)=>{
console.log("如果成功:",response);
this.GetAllData();
this.$notify.success({
title: '成功',
message: '激活成功!!!'
});
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '激活失败!!!'
});
console.log(error)// 打印失败的信息
})
}
this.dialogFormVisible1=false;
}else{
console.log('失败')
}
})
},
//详情
Detail(row){
this.title="设备详情";
this.dialogFormVisible2=true;
this.form2=row;
//this.id=row.id;
this.idDis=true;
},
//编辑
Edit(row){
this.title="编辑设备";
this.dialogFormVisible2=true;
this.form2=row;
//this.id=row.id;
this.idDis=true;
},
//确认修改的按钮
BtnOk2(formName){
this.$refs[formName].validate((valid) => {
if(valid){
if(this.title=='设备详情'){
//axios 发送数据到后台
//转换时间格式
//this.form.date=formatDate(this.form.date);
//网址根据实际要改一下
/*axios.post('/users/users/',this.form)
.then((response)=>{
console.log("如果成功:",response);
this.GetAllData();
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '新增失败'
});
console.log(error)// 打印失败的信息
})*/
}
if(this.title=='编辑设备'){
//转换时间格式
//this.form.date=formatDate(this.form.date);
//网址根据实际要改一下
axios.patch('/device/'+this.form2.serialnum+'/conf2',this.form2)
.then((response)=>{
this.$notify.success({
title: '成功',
message: '修改成功!!!'
});
console.log("如果成功:",response);
this.GetAllData();
}).catch((error)=>{
alert('error!!!');
this.$notify.error({
title: '失败',
message: '修改失败!!!'
});
console.log(this.form2)// 打印失败的信息
})
}
this.dialogFormVisible2=false;
}else{
console.log('失败')
}
})
},
//删除
Delete(row){
this.$confirm('此操作将永久冻结该设备, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
/// id ==> 后台 我们要删除 删除 谨慎
axios.put('/device/'+row.serialnum+'/delete',{
active:0,
})
.then((response)=>{
this.$notify.success({
title: '成功',
message: '冻结成功!!!'
});
this.GetAllData();
}).catch((error)=>{
this.$notify.error({
title: '失败',
message: '冻结失败!!!'
});
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
}
}
</script>
<style type="text/css">
.customWidth{
width:500px;
}
</style>
4 实习总结
本次实习过程中,我学习了web前端的开发知识,学会了如何用Axure做原型设计,包括界面的布局和按钮触发动作的设计;学会使用vue框架搭建项目,包括html、css、JavaScript这三种语言的用法与作用,页面渲染的方法,vue-router的路由重定向,axios向后端发送请求及接收请求的使用方法,element-ui、echarts、iconfont组件的用法。在短短的三周时间内能收获这么多知识,我感到很充实很开心很满足!
Web前端我做的界面有3个,分别是AirC首页、网格管理页面、设备管理页面。AirC首页包含设备实时气象参数和空气质量参数的查询,设备AQI排名信息和近7天空气质量参数的展示。网格管理页面包含网格的新增、查询、修改、绑定/解绑、删除操作。设备管理页面包含设备的激活、查询、修改、冻结操作。除此之外,还有列表展示、清空输入、表单验证等功能的实现。
在设计过程中我也真真切切地感受到了万事开头难这一真理!不管是原型设计还是前端开发,第一个界面永远需要花费最多的时间做出来。虽然现在看来并没有什么难度,但是最开始进行设计时,每个地方都会遇到bug,可能一上午就在解决bug中度过了,回头一看设计的页面并没有变化多少,未完成的工作只有晚上熬夜加班完成。
我认为我在这次实习过程中收获累累,不仅掌握了web前端项目的开发技能,还与团队中的队员们建立了很好的友谊!很开心能够和大家一起完成这次实习任务,过程中很快乐,就算是我们web前端三人组熬夜通宵写代码那会也是疲惫中带着快乐的!最后很想感谢老师们给予的帮助和队员们的鼓励,没有你们的支持我可能就完不成任务了。感谢学校为我们安排了一次这么好的生产实习活动,感谢实习单位的老师们细致地为我们讲解课程,还耐心地回答我们的各种奇奇怪怪的小问题!