Browse Source

EXCEL导出

develop
gaoguangchao 4 months ago
parent
commit
8949caa2fd
  1. 240
      ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelMultipleSheetsUtil.java
  2. 53
      ruoyi-ui/src/api/deviceManage/device.js
  3. 35
      ruoyi-ui/src/api/deviceManage/deviceOnline.js
  4. 53
      ruoyi-ui/src/api/deviceManage/product.js
  5. 185
      ruoyi-ui/src/enum/deviceEnum.js
  6. 619
      ruoyi-ui/src/views/deviceManage/device/index.vue
  7. 325
      ruoyi-ui/src/views/deviceManage/deviceOnline/index.vue
  8. 177
      ruoyi-ui/src/views/deviceManage/deviceOnline/indexLine.vue
  9. 172
      ruoyi-ui/src/views/deviceManage/lineChart.vue
  10. 56
      ruoyi-ui/src/views/deviceManage/mixins/resize.js
  11. 320
      ruoyi-ui/src/views/deviceManage/product/index.vue
  12. 229
      ruoyi-ui/src/views/deviceManage/typeTree.vue
  13. 50
      zc-business/src/main/java/com/zc/business/controller/DcDeviceOnlineController.java
  14. 12
      zc-business/src/main/java/com/zc/business/domain/OnlineSum.java

240
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelMultipleSheetsUtil.java

@ -0,0 +1,240 @@
package com.ruoyi.common.utils.poi;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.utils.DictUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class ExcelMultipleSheetsUtil {
/**
* 导出excel可多个sheet页
*
* @param data 数据Map 集合key == 每一个sheet页的名称value == sheet页数据
* @param excelFileName excel文件名
* @param suffixName 后缀名
* @param response 响应
* @throws IOException 异常
*/
public static void excelMultipleSheets(Map<String, Object> data, String excelFileName, String suffixName, HttpServletResponse response) throws IOException {
// 创建工作簿
try (Workbook workbook = new XSSFWorkbook()) {
for (Map.Entry<String, Object> entry : data.entrySet()) {
String sheetName = entry.getKey();
Object sheetData = entry.getValue();
Sheet sheet = workbook.createSheet(sheetName);
if (ObjectUtil.isNotEmpty(sheetData)) {
createSheetWithData(sheet, sheetData);
}
}
setResponseHeader(response, excelFileName, suffixName);
// 写出文件
workbook.write(response.getOutputStream());
}
}
/**
* 创建表单并填充数据
*
* @param sheet 表单
* @param data 数据
*/
private static void createSheetWithData(Sheet sheet, Object data) {
if (data instanceof List) {
createSheetWithListData(sheet, (List<?>) data);
} else {
createSheetWithObjectData(sheet, data);
}
}
/**
* 创建列表类型数据对应的Excel表单
*
* @param sheet 表单
* @param dataList 数据列表
*/
private static void createSheetWithListData(Sheet sheet, List<?> dataList) {
if (CollUtil.isNotEmpty(dataList)) {
Object firstItem = dataList.get(0);
createHeaderRow(sheet, firstItem.getClass());
int rowIndex = 1;
for (Object item : dataList) {
createDataRow(sheet, item, rowIndex++);
}
}
}
/**
* 创建对象类型数据对应的Excel表单
*
* @param sheet 表单
* @param data 数据
*/
private static void createSheetWithObjectData(Sheet sheet, Object data) {
createHeaderRow(sheet, data.getClass());
createDataRow(sheet, data, 1);
}
/**
* 创建表头行
*
* @param sheet 表单
* @param clazz 数据类
*/
private static void createHeaderRow(Sheet sheet, Class<?> clazz) {
// 创建单元格样式
CellStyle headerCellStyle = createCellStyle(sheet.getWorkbook());
// 创建标题行
Row headerRow = sheet.createRow(0);
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
createHeaderCell(sheet, headerCellStyle, fields, headerRow, i);
}
}
/**
* 创建数据行
*
* @param sheet 表单
* @param data 数据
* @param rowIndex 行号
*/
private static void createDataRow(Sheet sheet, Object data, int rowIndex) {
// 创建单元格样式
CellStyle dataCellStyle = createCellStyle(sheet.getWorkbook());
// 创建数据行
Row dataRow = sheet.createRow(rowIndex);
Field[] fields = data.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
createDataCell(dataCellStyle, fields, dataRow, i, data);
}
}
/**
* 创建单元格样式
*
* @param workbook 工作簿
* @return 单元格样式
*/
private static CellStyle createCellStyle(Workbook workbook) {
CellStyle cellStyle = workbook.createCellStyle();
// 设置 水平和垂直 居中对齐
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置 上 下 左 右 边框及颜色
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
// 设置字体
Font dataFont = workbook.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
cellStyle.setFont(dataFont);
return cellStyle;
}
/**
* 创建Excel表头单元格
*
* @param sheet 表单
* @param headerCellStyle 单元格样式
* @param fields 字段
* @param headerRow 标题行
* @param i 序号
*/
private static void createHeaderCell(Sheet sheet, CellStyle headerCellStyle, Field[] fields, Row headerRow, int i) {
// 默认宽度
double width = 16;
Excel excelAnnotation = fields[i].getAnnotation(Excel.class);
if (excelAnnotation != null && !ObjectUtil.isEmpty(excelAnnotation.width())) {
width = excelAnnotation.width();
}
// 设置宽度
sheet.setColumnWidth(i, (int) ((width + 0.72) * 256));
if (excelAnnotation != null) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(excelAnnotation.name());
cell.setCellStyle(headerCellStyle);
}
}
/**
* 创建Excel数据单元格
*
* @param dataCellStyle 单元格样式
* @param fields 字段
* @param dataRow 数据行
* @param i 序号
* @param data 数据
*/
private static void createDataCell(CellStyle dataCellStyle, Field[] fields, Row dataRow, int i, Object data) {
Cell cell = dataRow.createCell(i);
cell.setCellStyle(dataCellStyle);
try {
fields[i].setAccessible(true);
Object value = fields[i].get(data);
handleAnnotationAndSetValue(cell, fields[i], value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 处理注解并设置单元格值
*
* @param cell 单元格
* @param field 字段
* @param value
*/
private static void handleAnnotationAndSetValue(Cell cell, Field field, Object value) {
if (field.isAnnotationPresent(Excel.class) && field.getAnnotation(Excel.class).dictType().length() > 0) {
value = DictUtils.getDictLabel(field.getAnnotation(Excel.class).dictType(), String.valueOf(value));
}
if (field.isAnnotationPresent(Excel.class) && StrUtil.isNotEmpty(field.getAnnotation(Excel.class).dateFormat())) {
value = DateUtil.format(Convert.convert(Date.class, value), field.getAnnotation(Excel.class).dateFormat());
}
cell.setCellValue(ObjectUtil.isEmpty(value) ? null : value.toString());
}
/**
* 设置响应头
*
* @param response 响应
* @param excelFileName 文件名
* @param suffixName 后缀名
* @throws UnsupportedEncodingException 编码异常
*/
private static void setResponseHeader(HttpServletResponse response, String excelFileName, String suffixName) throws UnsupportedEncodingException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(excelFileName + suffixName, "utf-8"));
}
}

53
ruoyi-ui/src/api/deviceManage/device.js

@ -0,0 +1,53 @@
import request from '@/utils/request'
// 查询【请填写功能名称】列表
export function listDevice(query) {
return request({
url: '/business/device/list',
method: 'get',
params: query
})
}
// 查询【请填写功能名称】详细
export function getDevice(id) {
return request({
url: '/business/device/' + id,
method: 'get'
})
}
// 新增【请填写功能名称】
export function addDevice(data) {
return request({
url: '/business/device',
method: 'post',
data: data
})
}
// 修改【请填写功能名称】
export function updateDevice(data) {
return request({
url: '/business/device',
method: 'put',
data: data
})
}
// 删除【请填写功能名称】
export function delDevice(id) {
return request({
url: '/business/device/' + id,
method: 'delete'
})
}
// 导出【请填写功能名称】
export function exportDevice(query) {
return request({
url: '/business/device/export',
method: 'get',
params: query
})
}

35
ruoyi-ui/src/api/deviceManage/deviceOnline.js

@ -0,0 +1,35 @@
import request from '@/utils/request'
export function deviceOnlineChart(query) {
return request({
url: '/system/status/list',
method: 'get',
params: query
})
}
// 查询产品列表
export function deviceOnlineTable(query) {
return request({
url: '/system/status/tablist',
method: 'get',
params: query
})
}
export function networkLogTable(query) {
return request({
url: '/system/status//networkLogTable',
method: 'get',
params: query
})
}
export function networkLogEcharts(query){
return request({
url: '/system/status/networkLogEcharts',
method: 'get',
params: query
})
}

53
ruoyi-ui/src/api/deviceManage/product.js

@ -0,0 +1,53 @@
import request from '@/utils/request'
// 查询产品列表
export function listProduct(query) {
return request({
url: '/business/product/list',
method: 'get',
params: query
})
}
// 查询产品详细
export function getProduct(id) {
return request({
url: '/business/product/' + id,
method: 'get'
})
}
// 新增产品
export function addProduct(data) {
return request({
url: '/business/product',
method: 'post',
data: data
})
}
// 修改产品
export function updateProduct(data) {
return request({
url: '/business/product',
method: 'put',
data: data
})
}
// 删除产品
export function delProduct(id) {
return request({
url: '/business/product/' + id,
method: 'delete'
})
}
// 导出产品
export function exportProduct(query) {
return request({
url: '/business/product/export',
method: 'get',
params: query
})
}

185
ruoyi-ui/src/enum/deviceEnum.js

@ -0,0 +1,185 @@
let type =[
{
name: '摄像机',
value: '1'
},
{
name: '可变信息标志',
value: '2'
},
{
name: '气象监测器',
value: '3'
},
{
name: '出口诱导灯',
value: '4'
},
{
name: '路段语音广播',
value: '5'
},
{
name: '护栏碰撞',
value: '6'
},
{
name: '毫米波雷达',
value: '7'
},
{
name: '合流区预警',
value: '8'
},
{
name: '智慧锥桶',
value: '9'
},
{
name: '激光疲劳唤醒',
value: '10'
},
{
name: '一类交通量调查站',
value: '11'
},
{
name: '行车诱导',
value: '12'
},
{
name: '智能设备箱',
value: '13'
},
{
name: '光线在线监测',
value: '14'
}
]
let direction =[
{
name: '菏泽方向',
value: '1'
},
{
name: '双向',
value: '2'
},
{
name: '济南方向',
value: '3'
}
]
let useState = [
{
name: '停用',
value: 0
},
{
name: '在用',
value: 1
}
]
let deviceState = [
{
name: '异常',
value: '0'
},
{
name: '正常',
value: '1'
}
]
let facilitiesTypeList = [
{
label: '主干道',
value: '0'
},
{
label: '服务区',
value: '1'
},
{
label: '收费站',
value: '3'
}
]
let typeTree = [
{
label: '高清网络枪型固定摄像机',
value: '1-1'
},{
label: '高清网络球形摄像机',
value: '1-2'
}, {
label: '桥下高清网络球形摄像机',
value: '1-3'
}, {
label: '360°全景摄像机',
value: '1-4'
},{
label: '180°全景摄像机',
value: '1-5'
},{
label: '门架式可变信息标志',
value: '2-1'
}, {
label: '站前可变信息标志',
value: '2-2'
},{
label: '雨棚可变信息标志',
value: '2-3'
}, {
label: '站前悬臂式可变信息标志',
value: '2-4'
}, {
label: '气象监测器',
value: '3'
},{
label: '出口诱导灯',
value: '4'
}, {
label: '路段语音广播',
value: '5'
},{
label: '护栏碰撞',
value: '6'
}, {
label: '毫米波雷达',
value: '7'
}, {
label: '合流区预警',
value: '8'
}, {
label: '智慧锥桶',
value: '9'
}, {
label: '激光疲劳唤醒',
value: '10'
},{
label: '一类交通量调查站',
value: '11'
}, {
label: '行车诱导',
value: '12'
},{
label: '智能设备箱',
value: '13'
}, {
label: '光线在线监测',
value: '14'
},{
label: '太阳能板',
value: '15'
}, {
label: '远端机',
value: '16'
}]
export {
type,
direction,
useState,
deviceState,
facilitiesTypeList,
typeTree
}

619
ruoyi-ui/src/views/deviceManage/device/index.vue

@ -0,0 +1,619 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="设备桩号" prop="stakeMark">
<el-input
size="mini"
v-model="queryParams.stakeMark"
placeholder="请输入设备桩号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="使用状态" prop="useState">
<el-select size="mini" v-model="queryParams.useState" placeholder="请选择使用状态" clearable>
<el-option
v-for="item in useStateOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="设备状态" prop="deviceState">
<el-select size="mini" v-model="queryParams.deviceState" placeholder="请选择设备状态" clearable>
<el-option
v-for="item in deviceStateOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<span v-if="showSearch">
<el-form-item label="设备方向" prop="direction">
<el-select size="mini" v-model="queryParams.direction" placeholder="请选择设备方向" clearable>
<el-option
v-for="item in directionOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="IP地址" prop="deviceIp">
<el-input
size="mini"
v-model="queryParams.deviceIp"
placeholder="请输入IP地址"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select size="mini" v-model="queryParams.deviceType" placeholder="请选择设备类型" clearable>
<el-option
v-for="type in deviceTypeOptions"
:key="type.value"
:label="type.name"
:value="type.value"
/>
</el-select>
</el-form-item>
</span>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['deviceManage:device:add']"
>新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['deviceManage:device:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['deviceManage:device:remove']"
>删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['deviceManage:device:export']"
>导出
</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="deviceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="设备名称" align="center" prop="deviceName" width="250"/>
<el-table-column label="物联编号" align="center" prop="iotDeviceId"/>
<el-table-column label="设备桩号" align="center" prop="stakeMark"/>
<el-table-column label="设备方向" align="center" prop="direction">
<template slot-scope="scope">
<span v-if="scope.row.direction === '1'">菏泽方向</span>
<span v-if="scope.row.direction === '2'">双向</span>
<span v-if="scope.row.direction === '3'">济南方向</span>
</template>
</el-table-column>
<el-table-column label="IP地址" align="center" prop="deviceIp"/>
<el-table-column label="安装位置" align="center" prop="installationSite"/>
<el-table-column label="使用状态" align="center" prop="useState">
<template slot-scope="scope">
<el-switch
size="mini"
@change="handleUseState(scope.row)"
v-model="scope.row.useState"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0">
</el-switch>
</template>
</el-table-column>
<el-table-column label="设备状态" align="center" prop="deviceState">
<template slot-scope="scope">
<el-tag size="mini" v-if="scope.row.deviceState === '1'" type="success" effect="dark">在线</el-tag>
<el-tag size="mini" type="danger" effect="dark" v-else>异常</el-tag>
</template>
</el-table-column>
<el-table-column label="设备图片" align="center" prop="deviceImg">
<template slot-scope="scope">
<el-image
v-if="scope.row.deviceImg"
style="width: 30px; height: 30px"
:src="baseUrl+scope.row.deviceImg"
:preview-src-list="[baseUrl+scope.row.deviceImg]"
>
</el-image>
<p v-else>暂无</p>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['deviceManage:device:edit']"
>修改
</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['deviceManage:device:remove']"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改设备对话框 -->
<el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-row >
<el-col :span="8">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="物联编号" prop="iotDeviceId">
<el-input v-model="form.iotDeviceId" placeholder="请输入物联编号"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="所属单位" prop="groupId">
<el-select filterable v-model="form.groupId" clearable placeholder="请选择所属部门" class="select-width">
<el-option
v-for="dict in groupList"
:key="dict.deptId"
:label="dict.deptName"
:value="dict.deptId"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="form.deviceType" clearable placeholder="请选择设备类型" class="select-width">
<el-option
v-for="dict in deviceTypeOptions"
:key="dict.value"
:label="dict.name"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="产品型号" prop="productId">
<el-select filterable v-model="form.productId" clearable placeholder="请选择产品型号" class="select-width">
<el-option
v-for="dict in productList"
:key="dict.id"
:label="dict.productName"
:value="dict.id"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="设备桩号" prop="stakeMark">
<el-input v-model="form.stakeMark" placeholder="请输入设备桩号"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="范围桩号" prop="stakeMarkRange">
<el-input v-model="form.stakeMarkRange" placeholder="请输入范围桩号"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="设备方向" prop="direction">
<el-select class="width100" v-model="form.direction" placeholder="请选择设备方向" clearable>
<el-option
v-for="item in directionOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="安装位置" prop="facilitiesType">
<el-select v-model="form.facilitiesType" clearable placeholder="位置类型" class="select-width">
<el-option
v-for="dict in facilitiesTypeOption"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="位置描述" prop="installationSite">
<el-input v-model="form.installationSite" placeholder="请输入安装位置"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="安装日期" prop="installationDate">
<el-date-picker clearable class="width100"
v-model="form.installationDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择安装日期">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="生产日期" prop="productionDate">
<el-date-picker clearable class="width100"
v-model="form.productionDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择生产日期">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="使用年限" prop="durableYears">
<el-input v-model="form.durableYears" placeholder="请输入使用年限"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="使用状态" prop="useState">
<el-select class="width100" v-model="form.useState" placeholder="请选择使用状态" clearable>
<el-option
v-for="item in useStateOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="设备状态" prop="deviceState">
<el-select class="width100" v-model="form.deviceState" placeholder="请选择设备状态" clearable>
<el-option
v-for="item in deviceStateOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="IP地址" prop="deviceIp">
<el-input v-model="form.deviceIp" placeholder="请输入IP地址"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="其他配置" prop="otherConfig">
<el-input v-model="form.otherConfig" type="textarea" placeholder="请输入内容"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注"/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="设备图片" prop="deviceImg">
<image-upload @input="setImgUrl" :value="form.deviceImg" :limit="1"></image-upload>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {listDevice, getDevice, delDevice, addDevice, updateDevice} from "@/api/system/device";
import {type, direction, useState, deviceState, facilitiesTypeList} from "@/enum/deviceEnum";
import imageUpload from "@/components/ImageUpload";
import {listDept} from "@/api/system/dept";
import {queryProduct} from "@/api/system/product";
export default {
name: "Device",
components: {imageUpload},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: false,
//
total: 0,
//
deviceList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
iotDeviceId: null,
groupId: null,
productId: null,
stakeMark: null,
deviceCode: null,
direction: null,
deviceName: null,
deviceType: null,
installationDate: null,
productionDate: null,
durableYears: null,
installationSite: null,
useState: null,
otherConfig: null,
deviceState: null,
deviceImg: null,
deviceIp: null,
stakeMarkRange: null,
facilitiesType: null,
childType: null
},
//
form: {},
//
rules: {
stakeMark: [
{required: true, message: "设备桩号不能为空", trigger: "blur"}
],
deviceName: [
{required: true, message: "设备名称不能为空", trigger: "blur"}
],
deviceType: [
{required: true, message: "设备主类不能为空", trigger: "change"}
],
},
//
deviceTypeOptions: type,
//
directionOptions: direction,
// 使
useStateOptions: useState,
//
deviceStateOptions: deviceState,
//
facilitiesTypeOption: facilitiesTypeList,
//
baseUrl: process.env.VUE_APP_BASE_API,
//
productList:[],
//
groupList:[]
};
},
created() {
listDept().then(response => {
this.groupList = response.data
})
queryProduct().then(response => {
this.productList = response.data
})
this.getList();
},
methods: {
/** 查询设备列表 */
getList() {
this.loading = true;
listDevice(this.queryParams).then(response => {
this.deviceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
handleUseState(row) {
updateDevice(row).then(response => {
this.$modal.msgSuccess("启用成功");
this.getList();
});
},
//
reset() {
this.form = {
id: null,
iotDeviceId: null,
groupId: null,
productId: null,
stakeMark: null,
deviceCode: null,
direction: null,
deviceName: null,
deviceType: null,
installationDate: null,
productionDate: null,
durableYears: null,
installationSite: null,
useState: null,
otherConfig: null,
remark: null,
deviceState: null,
deviceImg: null,
createTime: null,
updateTime: null,
deviceIp: null,
stakeMarkRange: null,
facilitiesType: null,
childType: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加设备";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getDevice(id).then(response => {
console.log(response.data);
this.form = response.data;
console.log(this.form)
this.open = true;
this.title = "修改设备";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateDevice(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addDevice(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除设备编号为"' + ids + '"的数据项?').then(function () {
return delDevice(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
});
},
/** 导出按钮操作 */
handleExport() {
this.download('deviceManage/device/export', {
...this.queryParams
}, `device_${new Date().getTime()}.xlsx`)
},
//
setImgUrl(val) {
this.form.deviceImg = val
}
}
};
</script>
<style scoped lang="scss">
$width: 150px;
$height: 150px;
::v-deep .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: $width;
height: $height;
}
//
::v-deep .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: $width;
height: $height;
line-height: $height;
text-align: center;
}
.avatar {
width: $width;
height: $height;
display: block;
}
</style>

325
ruoyi-ui/src/views/deviceManage/deviceOnline/index.vue

@ -0,0 +1,325 @@
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="4">
<type-tree @defaultCheck="defaultCheck" @nodeCheck="nodeCheck" :filter="false" :show_checkbox="true" :default_check_first="true"></type-tree>
</el-col>
<el-col :span="20">
<el-button @click="handleExport">导出</el-button>
<line-chart height="30vh" :show-legend="true" :chart-data="chartData"></line-chart>
<el-table :data="tableData" height="45vh" style="width: 100%" @sort-change="handleSortChange">
<el-table-column prop="deviceName" label="设备名称" width="200"></el-table-column>
<el-table-column prop="stakeMark" label="桩号"></el-table-column>
<el-table-column prop="direction" label="方向">
<template slot-scope="scope">
<span v-if="scope.row.direction=='1'">菏泽方向</span>
<span v-else-if="scope.row.direction=='3'">济南方向</span>
<span v-else>双向</span>
</template>
</el-table-column>
<el-table-column prop="deviceIp" label="IP"></el-table-column>
<el-table-column prop="deviceStatus" label="状态">
<template slot-scope="scope">
<el-tag type="success" size="small" v-if="scope.row.deviceStatus=='1'">在线</el-tag>
<el-tag type="danger" size="small" v-else>离线</el-tag>
</template>
</el-table-column>
<el-table-column prop="lossRate" label="丢包率">
<template slot-scope="scope">
<span v-if="scope.row.lossRate > 0" style="color: #ff4949">{{scope.row.lossRate}}</span>
<span v-else>{{scope.row.lossRate}}</span>
</template>
</el-table-column>
<el-table-column prop="rttAvg" label="平均时延"></el-table-column>
<el-table-column prop="networkQuality" label="网络质量">
<template slot-scope="scope">
<el-tag type="success" size="small" v-if="scope.row.networkQuality=='优'"></el-tag>
<el-tag type="primary" size="small" v-if="scope.row.networkQuality=='良'"></el-tag>
<el-tag type="warning" size="small" v-if="scope.row.networkQuality=='一般'">一般</el-tag>
<el-tag type="danger" size="small" v-if="scope.row.networkQuality=='差'"></el-tag>
</template>
</el-table-column>
<!-- <el-table-column prop="statisticalDate" label="日期"></el-table-column>-->
<el-table-column prop="onlineRate" label="在线率" sortable="custom">
<template slot-scope="scope">
<el-progress :percentage="scope.row.onlineRate"></el-progress>
</template>
</el-table-column>
<el-table-column align="center">
<template slot="header" slot-scope="scope">
<el-popover placement="bottom" width="300" trigger="click">
<el-form :model="queryParams" ref="queryForm" size="small" label-width="68px">
<el-form-item label="设备桩号" prop="stakeMark">
<el-input size="mini" v-model="queryParams.stakeMark" placeholder="请输入设备桩号" clearable @keyup.enter.native="deviceOnlineTable"/>
</el-form-item>
<el-form-item label="使用状态" prop="useState">
<el-select size="mini" v-model="queryParams.useState" placeholder="请选择使用状态" clearable>
<el-option
v-for="item in useStateOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="设备状态" prop="deviceState">
<el-select size="mini" v-model="queryParams.deviceState" placeholder="请选择设备状态" clearable>
<el-option
v-for="item in deviceStateOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="IP地址" prop="deviceIp">
<el-input
size="mini"
v-model="queryParams.deviceIp"
placeholder="请输入IP地址"
clearable
@keyup.enter.native="deviceOnlineTable"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="deviceOnlineTable">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-button slot="reference" size="mini" icon="el-icon-search">搜索</el-button>
</el-popover>
</template>
<template slot-scope="scope" >
<el-button @click="seeLog(scope.row.deviceId)" type="text" size="mini" icon="el-icon-view">网络日志</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="deviceOnlineTable"
/>
</el-col>
</el-row>
<el-dialog title="设备网络日志" width="80vw" :visible.sync="logVisible">
<el-row style="position: absolute;top: 45px;right: 40px">
<el-button size="mini" @click="upDay">上一日</el-button>
<el-button style="margin-right: 10px" size="mini" @click="downDay">下一日</el-button>
<el-date-picker
style="width: 130px"
v-model="logQueryParams.queryDate"
type="date"
size="mini"
value-format="yyyy-MM-dd"
placeholder="选择日期"
@change="logQuery"
>
</el-date-picker>
</el-row>
<el-row style="width: 100%">
<indexLine height="25vh" :chartData="logChartData"></indexLine>
</el-row>
<el-row>
<el-table :data="logTableData" height="50vh" style="width: 100%" @sort-change="handleSortChange">
<el-table-column prop="deviceName" label="设备名称" width="300"></el-table-column>
<el-table-column prop="sendCount" label="发送"></el-table-column>
<el-table-column prop="receiveCount" label="返回"></el-table-column>
<el-table-column prop="lossCount" label="丢失"></el-table-column>
<el-table-column prop="deviceStatus" label="状态">
<template slot-scope="scope">
<el-tag type="success" size="small" v-if="scope.row.deviceStatus=='1'">可达</el-tag>
<el-tag type="danger" size="small" v-else>不可达</el-tag>
</template>
</el-table-column>
<el-table-column prop="lossRate" label="丢包率"></el-table-column>
<el-table-column prop="rttAvg" label="平均时延"></el-table-column>
<el-table-column prop="networkQuality" label="网络质量">
<template slot-scope="scope">
<el-tag type="success" size="small" v-if="scope.row.networkQuality=='优'"></el-tag>
<el-tag type="primary" size="small" v-if="scope.row.networkQuality=='良'"></el-tag>
<el-tag type="warning" size="small" v-if="scope.row.networkQuality=='一般'">一般</el-tag>
<el-tag type="danger" size="small" v-if="scope.row.networkQuality=='差'"></el-tag>
</template>
</el-table-column>
<el-table-column width="200" prop="monitorTime" label="检测时间"></el-table-column>
</el-table>
<!-- <pagination
v-show="logTotal>0"
:total="logTotal"
:page.sync="logQueryParams.pageNum"
:limit.sync="logQueryParams.pageSize"
@pagination="networkLogTable"
/>-->
</el-row>
</el-dialog>
</div>
</template>
<script>
import TypeTree from "@/views/deviceManage/typeTree";
import LineChart from "@/views/deviceManage/lineChart";
import indexLine from "@/views/deviceManage/deviceOnline/indexLine";
import {deviceOnlineTable,deviceOnlineChart,networkLogTable,networkLogEcharts} from "@/api/deviceManage/deviceOnline";
import {typeTree,direction, useState, deviceState} from "@/enum/deviceEnum";
export default {
name: "index",
components: { TypeTree, LineChart, indexLine},
data() {
return {
chartData:{
xData:[],
yData:[]
},
tableData:[],
//
total: 0,
//
directionOptions: direction,
// 使
useStateOptions: useState,
//
deviceStateOptions: deviceState,
logVisible:false,
deviceType:typeTree,
//
queryParams: {
pageNum: 1,
pageSize: 10,
type:'',
orderByField:'online_rate',
orderDirection:'desc',
searchValue:'',
startTime:'2024-07-01 00:00:00',
time:'2024-07-24 00:00:00',
direction:''
},
logQueryParams: {
//pageNum: 1,
//pageSize: 10,
queryDate:new Date(),
deviceId:'',
},
logChartData:{},
logTableData:[],
logTotal:0
}
},
created() {
},
methods: {
defaultCheck(types){
this.queryParams.type = types.join(',');
this.deviceOnlineTable();
this.deviceOnlineChart();
},
nodeCheck(data, checked){
this.queryParams.type = checked.checkedKeys.join(',');
this.deviceOnlineTable();
this.deviceOnlineChart();
},
deviceOnlineTable(){
deviceOnlineTable(this.queryParams).then(response => {
this.tableData = response.rows;
this.total = response.total;
})
},
deviceOnlineChart(){
deviceOnlineChart(this.queryParams).then(response => {
let data = response.data;
let xData ,yData = [];
if(Object.keys(data).length !== 0){
xData =Object.keys(data[Object.keys(data)[0]]);
for(const type in data){
let yItem = {}
yItem.name = this.findType(type).label;
yItem.unit = '%';
yItem.data = [];
let lineObj = data[type];
for(const time in lineObj){
yItem.data.push(lineObj[time].replace('%',''));
}
yData.push(yItem);
}
}
this.chartData.xData = xData;
this.chartData.yData = yData;
})
},
seeLog(deviceId){
this.logVisible = true;
this.initQueryDate();
this.logQueryParams.deviceId = deviceId;
this.logQuery();
},
networkLogEcharts(){
networkLogEcharts(this.logQueryParams).then(response => {
this.logChartData = response.data;
})
},
networkLogTable(){
networkLogTable(this.logQueryParams).then(response => {
console.log(response)
this.logTableData = response.rows;
this.logTotal = response.total;
})
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.deviceOnlineTable();
},
handleSortChange({ prop, order }){
this.queryParams.orderDirection = order === 'ascending'?'asc':'desc';
if (prop === 'onlineRate') {
this.queryParams.orderByField = 'online_rate';
}
this.deviceOnlineTable();
},
logQuery(){
if(!this.logQueryParams.queryDate) return;
this.networkLogEcharts()
this.networkLogTable()
},
initQueryDate(){
const date = new Date();
this.logQueryParams.queryDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
},
upDay(){
this.logQueryParams.queryDate = this.addOrSubtractDays(this.logQueryParams.queryDate, -1);
this.logQuery();
},
downDay(){
this.logQueryParams.queryDate = this.addOrSubtractDays(this.logQueryParams.queryDate, 1);
this.logQuery();
},
addOrSubtractDays(dateStr, numDays) {
const parts = dateStr.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1; // 1Date0
const day = parseInt(parts[2], 10);
const date = new Date(year, month, day);
date.setDate(date.getDate() + numDays);
const yearFormatted = date.getFullYear();
const monthFormatted = (date.getMonth() + 1).toString().padStart(2, '0'); // 1
const dayFormatted = date.getDate().toString().padStart(2, '0'); //
return `${yearFormatted}-${monthFormatted}-${dayFormatted}`;
},
findType(typeCode) {
return this.deviceType.find(item => item.value == typeCode)
},
handleExport() {
this.download('system/status/export', {
...this.queryParams
}, `设备在线率${new Date().getTime()}.xlsx`)
},
}
}
</script>
<style scoped>
</style>

177
ruoyi-ui/src/views/deviceManage/deviceOnline/indexLine.vue

@ -0,0 +1,177 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import * as echarts from 'echarts';
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '110px'
},
chartData: {
type: Object,
required: true
}
},
data() {
return {
chart: null
}
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el)
this.setOptions(this.chartData)
},
setOptions(data) {
this.chart.setOption({
color:['rgba(24, 144, 255, 1)','rgba(255, 114, 114, 1)'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
formatter: function (params) {
let res = params[0].name + '<br/>';
//
params.forEach(item => {
let unit = item.seriesName === '丢包率' ? '%' : 'ms';
res += `${item.seriesName} : ${item.value}${unit}<br/>`;
});
return res;
},
padding: [5, 10],
},
legend: {
data: ['丢包率', '平均往返时延'],
right:150,
top:-5,
},
grid: {
left: '1%',
right: '2%',
bottom: '3%',
top:'8%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: data.time
}
],
yAxis: [
{
name: '%',
type: 'value',
position: 'left',
splitLine: {
show: false,
lineStyle: {
color: '#F1F1F1'
}
},
axisLabel: {
color: '#656161',
formatter: function (value) {
return value + ' %';
}
}
},
{
name: 'ms',
type: 'value',
position: 'right',
splitLine: {
show: false
},
axisLabel: {
color: '#656161',
formatter: function (value) {
return value + ' ms';
}
}
}
],
series: [
{
name: '丢包率',
type: 'line',
yAxisIndex: 0,
//stack: 'Total',
smooth: true,
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 1, color: "rgba(59, 240, 240, 0)" },
{ offset: 0, color: "rgba(136, 193, 245, 1)" }
])
}
},
showSymbol: false,
/* emphasis: {
focus: 'series'
},*/
data: data.lostRate
},
{
name: '平均往返时延',
type: 'line',
yAxisIndex: 1,
stack: 'Total',
smooth: true,
showSymbol: false,
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 1, color: "rgba(254, 192, 6, 0)" },
{ offset: 0, color: "rgba(254, 192, 6, 0.38)" }
])
}
},
/* emphasis: {
focus: 'series'
},*/
data: data.rttAvg
}
]
})
}
}
}
</script>
<style scoped>
</style>

172
ruoyi-ui/src/views/deviceManage/lineChart.vue

@ -0,0 +1,172 @@
<!--曲线图-->
<template>
<div ref="echarts" :style="{height:height,width:width}"/>
</template>
<script>
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
chartData: {
type: Object,
required: true
},
showLegend:{
type:Boolean,
default:true
}
},
data() {
return {
chart: null
}
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.echarts)
this.setOptions(this.chartData)
},
setOptions({xData, yData} = {}) {
let legend = []
let series = []
let unit =''
let seriesDefault = {
name: '',
symbol: 'circle',
symbolSize: 1,
showSymbol: false,
smooth: true,
type: 'line',
itemStyle: {
color: '#06b1f5',
lineStyle: {
color: '#06b1f5',
width: 2
}
},
data: [],
//animationDuration: 2800,
//animationEasing: 'cubicInOut'
markPoint: {
symbol: 'pin', //()
symbolSize: 0, //()
itemStyle: {
color: 'rgba(18, 187, 93,1)', //
borderColor: '#000', // color
borderWidth: 0, //线 0
borderType: 'solid' //线 solid, dashed, dotted
},
data: [
{type: 'max', name: '最大值'},
{type: 'min', name: '最小值'}
],
label: {
show: true
}
},
}
yData.forEach(item => {
unit = item.unit
legend.push(item.name)
let target = JSON.parse(JSON.stringify(seriesDefault));
target.name = item.name
target.itemStyle.color = item.color
target.itemStyle.lineStyle.color = item.color
target.data = item.data
series.push(target)
})
if (series.length <= 0) series.push(seriesDefault)
this.chart.setOption({
grid: {
left: 20,
right: 10,
bottom: 0,
top: 40,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10],
},
legend: {
show:this.showLegend,
right: '1%',
top: '1%',
textStyle: {
color: '#556677'
},
data: legend
},
xAxis: {
data: xData,
boundaryGap: false,
axisLine: {
show: false,
},
axisTick: {
show: false
},
axisLabel: {
color: '#9da3a8',
width: 100,
// x
fontSize: 12,
// margin:x
margin: 15
},
},
yAxis: {
name: unit,
nameTextStyle: {
color: '#9da3a8',
fontSize: 15
},
axisTick: {
show: false
},
axisLabel: {
color: '#9da3a8'
},
},
series: series
},true)
}
}
}
</script>

56
ruoyi-ui/src/views/deviceManage/mixins/resize.js

@ -0,0 +1,56 @@
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.initListener()
},
activated() {
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener()
}
// when keep-alive chart activated, auto resize
this.resize()
},
beforeDestroy() {
this.destroyListener()
},
deactivated() {
this.destroyListener()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
initListener() {
this.$_resizeHandler = debounce(() => {
this.resize()
}, 100)
window.addEventListener('resize', this.$_resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize() {
const { chart } = this
chart && chart.resize()
}
}
}

320
ruoyi-ui/src/views/deviceManage/product/index.vue

@ -0,0 +1,320 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="产品名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入产品名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="生产厂商" prop="manufacturer">
<el-input
v-model="queryParams.manufacturer"
placeholder="请输入生产厂商"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="设备品牌" prop="brand">
<el-input
v-model="queryParams.brand"
placeholder="请输入设备品牌"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="设备型号" prop="model">
<el-input
v-model="queryParams.model"
placeholder="请输入设备型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="设备产地" prop="productionPlace">
<el-input
v-model="queryParams.productionPlace"
placeholder="请输入设备产地"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['deviceManage:product:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['deviceManage:product:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['deviceManage:product:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['deviceManage:product:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="productList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键" align="center" prop="id" />
<el-table-column label="产品名称" align="center" prop="productName" />
<el-table-column label="生产厂商" align="center" prop="manufacturer" />
<el-table-column label="设备品牌" align="center" prop="brand" />
<el-table-column label="设备型号" align="center" prop="model" />
<el-table-column label="设备产地" align="center" prop="productionPlace" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="其他配置" align="center" prop="otherConfig" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['deviceManage:product:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['deviceManage:product:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改产品对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="form.productName" placeholder="请输入产品名称" />
</el-form-item>
<el-form-item label="生产厂商" prop="manufacturer">
<el-input v-model="form.manufacturer" placeholder="请输入生产厂商" />
</el-form-item>
<el-form-item label="设备品牌" prop="brand">
<el-input v-model="form.brand" placeholder="请输入设备品牌" />
</el-form-item>
<el-form-item label="设备型号" prop="model">
<el-input v-model="form.model" placeholder="请输入设备型号" />
</el-form-item>
<el-form-item label="设备产地" prop="productionPlace">
<el-input v-model="form.productionPlace" placeholder="请输入设备产地" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="其他配置" prop="otherConfig">
<el-input v-model="form.otherConfig" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listProduct, getProduct, delProduct, addProduct, updateProduct } from "@/api/deviceManage/product";
export default {
name: "Product",
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
productList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
productName: null,
manufacturer: null,
brand: null,
model: null,
productionPlace: null,
otherConfig: null,
},
//
form: {},
//
rules: {
productName: [
{ required: true, message: "产品名称不能为空", trigger: "blur" }
],
createTime: [
{ required: true, message: "创建时间不能为空", trigger: "blur" }
],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询产品列表 */
getList() {
this.loading = true;
listProduct(this.queryParams).then(response => {
this.productList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
productName: null,
manufacturer: null,
brand: null,
model: null,
productionPlace: null,
remark: null,
otherConfig: null,
createTime: null,
updateTime: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加产品";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getProduct(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改产品";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateProduct(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addProduct(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除产品编号为"' + ids + '"的数据项?').then(function() {
return delProduct(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('deviceManage/product/export', {
...this.queryParams
}, `product_${new Date().getTime()}.xlsx`)
}
}
};
</script>

229
ruoyi-ui/src/views/deviceManage/typeTree.vue

@ -0,0 +1,229 @@
<template>
<div>
<div class="box">
<el-row v-if="filter">
<el-input
v-model="nodeName"
placeholder="请输入关键字"
clearable
size="mini"
suffix-icon="el-icon-search"
style="width: 100%"
/>
</el-row>
<!-- 级联 全选 -->
<div class="check" v-if="show_checkbox">
<el-checkbox v-model="check_strictly">级联选择</el-checkbox>
<el-checkbox v-model="check_all" @change="handleCheckedTreeNodeAll($event, 'menu')">全选</el-checkbox>
</div>
</div>
<el-scrollbar>
<el-row :style="{ height: height }">
<el-tree
class="tree"
accordion
:data="treeOptions"
:props="defaultProps"
:expand-on-click-node="true"
:check-on-click-node="true"
:show-checkbox="show_checkbox"
:check-strictly="!check_strictly"
:filter-node-method="filterNode"
ref="tree"
default-expand-all
@node-click="handleNodeClick"
@check="handleCheckChange"
node-key="value"
>
</el-tree>
</el-row>
</el-scrollbar>
</div>
</template>
<script>
import {typeTree} from "@/enum/deviceEnum"
export default {
name: "typeTree",
props: {
//
filter: {
type: Boolean,
default: true,
},
//
show_checkbox: {
type: Boolean,
default: false,
},
//
check_strictly: {
type: Boolean,
default: false,
},
//
default_check_all: {
type: Boolean,
default: false,
},
//
default_check_first: {
type: Boolean,
default: false,
},
//
default_select_first: {
type: Boolean,
default: false,
},
height: {
type: String,
default: "calc(100vh - 218px)",
},
},
data() {
return {
//
nodeName: null,
//
treeOptions: [],
defaultProps: {
value: "value",
label: "label",
children: "children",
},
check_all: this.default_check_all,
};
},
watch: {
//
nodeName(val) {
this.$refs.tree.filter(val);
},
},
mounted() {
this.treeOptions = typeTree;
this.$nextTick(() => {
this.showCheckBox();
this.selectFirstChild();
});
},
methods: {
//
handleCheckedTreeNodeAll(value, type) {
let arr = [];
if (value) {
this.getAllKeys(this.treeOptions, arr, "menuId");
}
this.$refs.tree.setCheckedKeys(arr);
this.$emit("defaultCheck", arr);
},
// true false
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
//
handleNodeClick(data) {
this.$emit("nodeClick", data);
},
//
selectFirstChild() {
if (this.default_select_first) {
let first = this.getFirstChildren(this.treeOptions);
this.$refs.tree.setCurrentKey(first);
this.$emit("defaultSelect", first, this.$refs.tree.getCurrentNode());
}
},
//--
handleCheckChange(data, checked) {
// console.log(data, checked);
let n = this.getAllKeys(this.treeOptions);
//
if (checked.checkedKeys.length === n.length) {
this.check_all = true;
} else {
this.check_all = false;
}
this.$emit("nodeCheck", data, checked);
},
//--
showCheckBox() {
if (this.show_checkbox) {
//
if (this.default_check_all) {
let all = this.getAllKeys(this.treeOptions);
this.$refs.tree.setCheckedKeys(all);
this.$emit("defaultCheck", all);
}
//
if (this.default_check_first) {
let arr = [];
let first = this.getFirstChildren(this.treeOptions);
if (first) arr.push(first);
this.$refs.tree.setCheckedKeys(arr);
this.$emit("defaultCheck", arr);
}
}
},
//
getAllKeys(node, arr = []) {
for (let item of node) {
arr.push(item.value);
let parentArr = [];
if (item.children) parentArr.push(...item.children);
if (parentArr && parentArr.length) this.getAllKeys(parentArr, arr);
}
return arr;
},
//
getFirstChildren(node) {
if (node.length) {
return node[0].children && node[0].children.length > 0
? this.getFirstChildren(node[0].children)
: node[0].value;
//return node[0].value;
}
},
},
};
</script>
<style lang="scss" scoped>
.box{
padding-bottom: 10px;
}
::v-deep .el-tree-node__content {
margin: 10px 0 !important;
}
::v-deep .el-input--small .el-input__inner{
text-align: center;
}
::v-deep .el-input--mini .el-input__inner{
text-align: center;
}
::v-deep .el-scrollbar__thumb{
display: none;
}
.check {
padding-bottom: 10px;
text-align: center;
::v-deep .el-checkbox {
width: 50%;
height: 20px;
margin: 0;
line-height: 20px;
}
::v-deep .el-checkbox__label {
font-size: 16px;
padding-left: 8px;
}
::v-deep .el-checkbox__inner {
margin-bottom: 2px;
}
}
</style>

50
zc-business/src/main/java/com/zc/business/controller/DcDeviceOnlineController.java

@ -1,5 +1,6 @@
package com.zc.business.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.tree.Tree;
import com.alibaba.excel.util.DateUtils;
import com.github.pagehelper.util.StringUtil;
@ -10,6 +11,7 @@ import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelMultipleSheetsUtil;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.zc.business.constant.RedisKeyConstants;
import com.zc.business.controller.queryParams.OnlineQueryParams;
@ -20,6 +22,7 @@ import com.zc.business.service.IOnlineLogService;
import com.zc.business.service.IOnlineSumService;
import com.zc.business.service.impl.DcDeviceServiceImpl;
import com.zc.business.utils.MathUtil;
import eu.bitwalker.useragentutils.DeviceType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.checkerframework.checker.units.qual.A;
@ -30,6 +33,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
@ -56,21 +60,35 @@ public class DcDeviceOnlineController extends BaseController {
private static final String ORDERRULE = "orderRule";//排序策略key
/*@ApiOperation("设备状态导出")
@ApiOperation("设备状态导出")
@Log(title = "【设备状态导出】", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, Status status) {
if (status.getDeviceName() != null) {
status.setDeviceName(status.getDeviceName().replaceAll(UniversalEnum.BLANK_SPACE.getValue(), UniversalEnum.PLUS_SIGN.getValue()));
public void export(HttpServletResponse response, OnlineQueryParams params) {
if(ObjectUtils.isEmpty(params.getType())) {
return;
}
String type = status.getType();
if (type != null) {
status.setTypes(type.split(UniversalEnum.COMMA.getValue()));
if(StringUtil.isEmpty(params.getOrderByField()) ||"time".equals(params.getOrderByField())) {
params.setOrderByField("online_rate");
}
List<OnlineSum> sums = onlineSumService.queryByDeviceTypesOfToday(params);
Map<String,List<OnlineSum>> sumsMap = sums.stream().collect(Collectors.groupingBy(OnlineSum::getDeviceType));
/*Map<String,Object> map = new HashMap<>();
for(String deviceType:params.getType()){
List<OnlineSum> list = sumsMap.getOrDefault(deviceType,new ArrayList<>());
String typeName = DeviceType.getDescriptionByValue(deviceType);
if(StringUtil.isNotEmpty(typeName)) {
map.put(DeviceType.getDescriptionByValue(deviceType),list);
}
}
List<Status> listStatus = statusService.export(status);
ExcelUtil<Status> util = new ExcelUtil<>(Status.class);
util.exportExcel(response, listStatus, UniversalEnum.DEVICE_STATUS_LIST.getValue());
}*/
try {
ExcelMultipleSheetsUtil.excelMultipleSheets(map,UniversalEnum.DEVICE_STATUS_LIST.getValue()+ DateUtil.format(new Date(), "yyyyMMddHHmmss"),
"xlsx", response);
} catch (IOException e) {
e.printStackTrace();
}*/
ExcelUtil<OnlineSum> util = new ExcelUtil<>(OnlineSum.class);
util.exportExcel(response, sums, UniversalEnum.DEVICE_STATUS_LIST.getValue());
}
@ApiOperation("设备状态列表按时间和类型")
@GetMapping("/tablist")
@ -182,7 +200,7 @@ public class DcDeviceOnlineController extends BaseController {
Map<String,Long> useCounts = useDeviceList.stream()
.collect(Collectors.groupingBy(DcDevice::getRealType, Collectors.counting()));
Map<String,List<OnlineSum>> typeSumGroup = onlineSums.stream().collect(Collectors.groupingBy(OnlineSum::getDeviceType));
DeviceType.toMap().forEach((k,v) -> {
DeviceType.toMap().forEach((k, v) -> {
Map<String,Object> itemMap = new HashMap<>();
itemMap.put(SUM,totalCounts.getOrDefault(k,0L).toString());//总数
itemMap.put(SUM_USE_STATE,useCounts.getOrDefault(k,0L).toString());//在用数
@ -342,6 +360,14 @@ public class DcDeviceOnlineController extends BaseController {
}
return stringBuilder.toString();
}
static String getDescriptionByValue(String value) {
for (DeviceType type : DeviceType.values()) {
if (type.value.equals(value)) {
return type.description;
}
}
return "";
}
}
}

12
zc-business/src/main/java/com/zc/business/domain/OnlineSum.java

@ -1,6 +1,7 @@
package com.zc.business.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.zc.business.enums.NetworkQuality;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
@ -13,13 +14,19 @@ public class OnlineSum implements java.io.Serializable {
private Long id;//id
private Long deviceId;//设备ID
private int totalCount;//当天总次数
@Excel(name = "在线率")
private double onlineRate;//在线率
@Excel(name = "离线率")
private double offlineRate;//离线率
@Excel(name = "丢包率")
private double lossRate;//丢包率
@Excel(name = "平均时延")
private double rttAvg;//平均往返时延
@Excel(name = "网络质量")
private String networkQuality;//网络质量
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "统计日期", width = 30, dateFormat = "yyyy-MM-dd")
private LocalDate statisticalDate;//统计日期
private int sendCount;//发送数据包总数
@ -28,12 +35,17 @@ public class OnlineSum implements java.io.Serializable {
private int totalOnlineCount;//当天总在线次数
private int totalOfflineCount;//当天总离线次数
private Long roadId;
@Excel(name = "设备名称")
private String deviceName;//设备名称
@Excel(name = "IP")
private String deviceIp;//设备IP
@Excel(name = "桩号")
private String stakeMark;//设备桩号
private String direction;//方向
private String deviceType;//设备类型
@Excel(name = "设备状态")
private String deviceStatus;//当前设备状态?
@Excel(name = "使用状态")
private String useState;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

Loading…
Cancel
Save