gy-gateway/src/views/gateway/southdirection.vue

1104 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div id="southdirection-box">
<div class="device-config">
<h2>设备配置</h2>
<div class="btn-list">
<div class="btn-list-left">
<el-button type="primary" @click="addDeviceConfig">新增配置</el-button>
<!-- <el-button type="danger" @click="getDeviceConfig">删除</el-button>-->
</div>
<div class="btn-list-right">
<el-button type="primary" style="margin-right: 10px" @click="exportDeviceConfigXlsx">导出</el-button>
<!-- <el-button type="primary" @click="getDeviceConfig">导入</el-button>-->
<el-upload action="#" :auto-upload="false" :on-change="handleFileChange" :show-file-list="false" style="margin-right: 10px">
<el-button type="primary">导入</el-button>
</el-upload>
<el-button type="primary" @click="getDeviceConfig">获取配置</el-button>
<el-button type="primary" @click="setDeviceConfig">设置配置</el-button>
</div>
</div>
<div class="table-box">
<el-table
ref="deviceConfigTableRef"
highlight-current-row
:data="deviceConfigList"
border
height="187"
:style="{ width: tabelBox + 'px', background: '#f2f2f2' }"
empty-text="配置为空"
row-key="id"
@row-click="deviceConfigClick"
>
<el-table-column type="index" label="序号" align="center" width="60" />
<el-table-column prop="equipId" label="设备ID">
<template #default="scope">
<el-input v-model="scope.row.equipId" placeholder="请输入设备ID" />
</template>
</el-table-column>
<el-table-column prop="equipName" label="设备名">
<template #default="scope">
<el-input v-model="scope.row.equipName" placeholder="请输入设备名" />
</template>
</el-table-column>
<el-table-column prop="protocol.libName" label="协议类型">
<template #default="scope">
<el-select v-model="scope.row.protocol.libName" placeholder="请选择协议类型">
<el-option v-for="item in protocolList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="interfaceParams.link" label="设备连接" />
<el-table-column prop="protocol.params.linkPar" label="设备地址">
<template #default="scope">
<el-input v-model="scope.row.protocol.params.linkPar" placeholder="请输入设备地址" />
</template>
</el-table-column>
<el-table-column label="高级选项" width="208">
<template #default="scope">
<el-button link type="primary" size="small" @click="openConnectModel(scope.row)">连接配置</el-button>
<el-button link type="primary" size="small" @click="openCollectionModel(scope.row)">采集配置</el-button>
<el-button link type="danger" size="small" @click="delDeviceConfig(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="data-config">
<h2>数据源配置 {{ activeDeviceConfig.equipId }}</h2>
<div class="btn-list">
<div class="btn-list-left">
<el-button type="primary" @click="addDeviceDataConfig">新增配置</el-button>
<!-- <el-button type="danger" @click="getDeviceConfig">删除</el-button>-->
</div>
<div class="btn-list-right">
<el-button type="primary" style="margin-right: 10px" @click="exportDeviceDataConfigXlsx">导出</el-button>
<el-upload action="#" :auto-upload="false" :on-change="handleFileChange" :show-file-list="false">
<el-button type="primary">导入</el-button>
</el-upload>
</div>
</div>
<div class="table-box">
<el-table
ref="deviceDataTableRef"
:data="activeDeviceConfig.properties"
border
size="small"
height="100%"
:style="{ width: tabelBox + 'px' }"
row-key="index"
empty-text="配置为空"
>
<el-table-column type="index" label="序号" align="center" width="60" fixed />
<el-table-column prop="id" label="数据ID" fixed align="center" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.id" placeholder="请输入数据ID" />
</template>
</el-table-column>
<el-table-column prop="dataName" label="数据名" fixed align="center" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.dataName" placeholder="请输入数据名" />
</template>
</el-table-column>
<el-table-column prop="dataAddress" label="数据地址" align="center" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.dataAddress" placeholder="请输入数据地址" />
</template>
</el-table-column>
<el-table-column prop="dataDesc" label="描述" align="center" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.dataDesc" placeholder="请输入描述" />
</template>
</el-table-column>
<el-table-column prop="dataDisplay" label="数据类型" min-width="120" align="center">
<template #default="scope">
<el-select v-model="scope.row.dataDisplay" placeholder="请选择数据类型" @change="changedataDisplay($event, scope.$index)">
<el-option v-for="(item,index) in dataTypeArray" :key="index" :value="item.type">{{ item.type }}</el-option>
<!-- <el-option value="bool">bool</el-option>-->
<!-- <el-option value="int8">int8</el-option>-->
<!-- <el-option value="uint8">uint8</el-option>-->
<!-- <el-option value="int16">int16</el-option>-->
<!-- <el-option value="uint16">uint16</el-option>-->
<!-- <el-option value="float">float</el-option>-->
<!-- <el-option value="int32">int32</el-option>-->
<!-- <el-option value="uint32">uint32</el-option>-->
<!-- <el-option value="int64">int64</el-option>-->
<!-- <el-option value="uint64">uint64</el-option>-->
<!-- <el-option value="double">double</el-option>-->
<!-- <el-option value="char">char</el-option>-->
<!-- <el-option value="string">string</el-option>-->
<!-- <el-option value="int16_AB">int16_AB</el-option>-->
<!-- <el-option value="int16_BA">int16_BA</el-option>-->
<!-- <el-option value="uint16_AB">uint16_AB</el-option>-->
<!-- <el-option value="uint16_BA">uint16_BA</el-option>-->
<!-- <el-option value="float_ABCD">float_ABCD</el-option>-->
<!-- <el-option value="float_BADC">float_BADC</el-option>-->
<!-- <el-option value="float_CDAB">float_CDAB</el-option>-->
<!-- <el-option value="float_DCBA">float_DCBA</el-option>-->
<!-- <el-option value="int32_ABCD">int32_ABCD</el-option>-->
<!-- <el-option value="int32_BADC">int32_BADC</el-option>-->
<!-- <el-option value="int32_CDAB">int32_CDAB</el-option>-->
<!-- <el-option value="int32_DCBA">int32_DCBA</el-option>-->
<!-- <el-option value="uint32_ABCD">uint32_ABCD</el-option>-->
<!-- <el-option value="uint32_BADC">uint32_BADC</el-option>-->
<!-- <el-option value="uint32_CDAB">uint32_CDAB</el-option>-->
<!-- <el-option value="uint32_DCBA">uint32_DCBA</el-option>-->
</el-select>
</template>
</el-table-column>
<el-table-column prop="datalen" label="长度(字节)" min-width="80" align="center">
<template #default="scope">
<el-input v-model="scope.row.datalen" type="number" :disabled="!scope.row.datalenShow" placeholder="请输入长度(字节)" />
</template>
</el-table-column>
<el-table-column prop="dataUnit" label="单位" min-width="70" align="center">
<template #default="scope">
<el-input v-model="scope.row.dataUnit" placeholder="请输入单位" />
</template>
</el-table-column>
<el-table-column prop="authority" label="读写权限" width="90" align="center">
<template #default="scope">
<el-select v-model="scope.row.authority" placeholder="请选择读写权限">
<el-option value="RW">RW</el-option>
<el-option value="OR">OR</el-option>
<el-option value="OW">OW</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="proportion" label="比例" min-width="80" align="center">
<template #default="scope">
<el-input v-model="scope.row.proportion" placeholder="请输入比例" />
</template>
</el-table-column>
<el-table-column prop="offset" label="偏置" min-width="60" align="center">
<template #default="scope">
<el-input v-model="scope.row.offset" placeholder="请输入偏置" />
</template>
</el-table-column>
<el-table-column prop="northCalcParam.decimal.numBits" label="小数位" min-width="60" align="center">
<template #default="scope">
<el-input v-model="scope.row.northCalcParam.decimal.numBits" placeholder="请输入小数位" />
</template>
</el-table-column>
<el-table-column prop="northCalcParam.decimal.way" label="取整" width="120" align="center">
<template #default="scope">
<el-select v-model="scope.row.northCalcParam.decimal.way" placeholder="请选择取整">
<el-option value="rounding" label="四舍五入"></el-option>
<el-option value="flooring" label="全舍"></el-option>
<el-option value="ceiling" label="全入"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="driveFlag.isConclude_R" label="块读" width="50" align="center">
<template #default="scope">
<!-- <el-select v-model="scope.row.driveFlag.isConclude_R" placeholder="请选择块读">-->
<!-- <el-option value="true">Yes</el-option>-->
<!-- <el-option value="false">No</el-option>-->
<!-- </el-select>-->
<el-switch v-model="scope.row.driveFlag.isConclude_R" />
</template>
</el-table-column>
<el-table-column prop="driveFlag.isConclude_W" label="块写" width="50" align="center">
<template #default="scope">
<!-- <el-select v-model="scope.row.driveFlag.isConclude_W" placeholder="请选择块写">-->
<!-- <el-option value="true">Yes</el-option>-->
<!-- <el-option value="false">No</el-option>-->
<!-- </el-select>-->
<el-switch v-model="scope.row.driveFlag.isConclude_W" />
</template>
</el-table-column>
<el-table-column prop="taskId" label="任务ID" align="center" min-width="80">
<template #default="scope">
<el-select v-model="scope.row.taskId" placeholder="请选择任务ID" @change="changeTaskId($event, scope.$index)">
<el-option v-for="(item, index) in taskList" :key="index" :value="item.taskId" :label="item.taskId"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="uploadMode" label="上报策略" align="center" min-width="160">
<template #default="scope">
<!-- <el-select v-model="scope.row.uploadMode" placeholder="请选择上报策略">-->
<!-- <el-option :value="item.uploadMode" v-for="(item,index) in taskList" :key="index" :label="item.uploadMode"></el-option>-->
<!-- </el-select>-->
<el-autocomplete
v-model="scope.row.uploadMode"
:fetch-suggestions="querySearch"
clearable
class="inline-input w-50"
placeholder="请选择上报策略"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="55" fixed="right" align="center">
<template #default="scope">
<el-button link type="danger" size="small" @click.stop="delDeviceDataConfig(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-dialog v-model="collectionModelShow" title="采集配置" width="500">
<el-form ref="collectionRef" :model="collectionForm" label-width="auto" status-icon>
<el-form-item label="启动">
<el-switch v-model="collectionForm.collectConf.isRunCmd" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="轮询周期(ms)">
<el-input v-model="collectionForm.collectConf.pollingCycleTime_ms" type="number" placeholder="请输入轮询周期(ms)" />
</el-form-item>
</el-col>
<el-col :span="12" class="text-center">
<el-form-item label="读通信最大时长(ms)">
<el-input v-model="collectionForm.collectConf.allowTimeOut.read" type="number" placeholder="请输入读通信最大时长(ms)" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="通信间隔(ms)">
<el-input v-model="collectionForm.collectConf.intervalTime_ms" type="number" placeholder="请输入通信间隔(ms)" />
</el-form-item>
</el-col>
<el-col :span="12" class="text-center">
<el-form-item label="写通信最大时长(ms)">
<el-input v-model="collectionForm.collectConf.allowTimeOut.write" type="number" placeholder="请输入写通信最大时长(ms)" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="异常允许最大次数">
<el-input v-model="collectionForm.collectConf.abnormalMax" type="number" placeholder="请输入异常允许最大次数" />
</el-form-item>
<el-form-item label="读归纳布尔量最大长度">
<el-input v-model="collectionForm.inductionParam.maxLenBlock_read.boolArea" type="number" placeholder="请输入读归纳布尔量最大长度" />
</el-form-item>
<el-form-item label="读归纳字节最大长度">
<el-input v-model="collectionForm.inductionParam.maxLenBlock_read.charArea" type="number" placeholder="请输入读归纳字节最大长度" />
</el-form-item>
<el-form-item label="写归纳布尔量最大长度">
<el-input v-model="collectionForm.inductionParam.maxLenBlock_write.boolArea" type="number" placeholder="请输入写归纳布尔量最大长度" />
</el-form-item>
<el-form-item label="写归纳字节最大长度">
<el-input v-model="collectionForm.inductionParam.maxLenBlock_write.charArea" type="number" placeholder="请输入写归纳字节最大长度" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="collectionModelShow = false">取消</el-button>
<el-button type="primary" @click="editCollection"> 确认 </el-button>
</div>
</template>
</el-dialog>
<!-- 串口连接配置 -->
<el-dialog v-model="dlt645ModelShow" title="连接配置" width="500">
<el-form ref="dlt645Ref" :model="dlt645Form" label-width="auto" status-icon>
<el-form-item label="端口" prop="port" required>
<el-select v-model="dlt645Form.port" placeholder="选择端口" style="flex: 1">
<el-option v-for="(item, index) in serialportList" :key="index" :label="item" :value="item" />
</el-select>
<el-button style="margin-left: 10px" type="primary" @click="getSerialPortList">获取端口列表</el-button>
</el-form-item>
<el-form-item label="波特率" prop="baudrate" required>
<el-select v-model="dlt645Form.baudrate" placeholder="选择波特率">
<el-option label="4800" value="4800" />
<el-option label="9600" value="9600" />
<el-option label="19200" value="19200" />
<el-option label="43000" value="43000" />
<el-option label="56000" value="56000" />
<el-option label="115200" value="115200" />
</el-select>
</el-form-item>
<el-form-item label="数据位" prop="databits" required>
<el-select v-model="dlt645Form.databits" placeholder="选择数据位">
<el-option label="5" value="5" />
<el-option label="6" value="6" />
<el-option label="7" value="7" />
<el-option label="8" value="8" />
</el-select>
</el-form-item>
<el-form-item label="停止位" prop="stopbits" required>
<el-select v-model="dlt645Form.stopbits" placeholder="选择停止位">
<el-option label="1" value="1" />
<el-option label="1.5" value="15" />
<el-option label="2" value="2" />
</el-select>
</el-form-item>
<el-form-item label="校验位" prop="parity" required>
<el-select v-model="dlt645Form.parity" placeholder="选择校验位">
<el-option label="None" value="N" />
<el-option label="Odd" value="O" />
<el-option label="Even" value="E" />
<!-- <el-option label="Mark" value="M" />-->
<!-- <el-option label="Space" value="S" />-->
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dlt645ModelShow = false">取消</el-button>
<el-button type="primary" @click="editDlt645(dlt645Ref)"> 确认 </el-button>
</div>
</template>
</el-dialog>
<!-- TCP连接配置 -->
<el-dialog v-model="tcpModelShow" title="连接配置" width="500">
<el-form ref="tcpRef" :model="tcpForm" :rules="tcpFormRules" label-width="auto" status-icon>
<el-form-item label="设备地址" prop="link" required>
<el-input v-model="tcpForm.link" placeholder="请输入ip地址+端口如192.168.0.1:80" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="tcpModelShow = false">取消</el-button>
<el-button type="primary" @click="editTcp(tcpRef)"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref, onMounted, onUnmounted, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { guidGenerator } from '/@/utils/function.js';
// 导入依赖
import FileSaver from 'file-saver';
import * as XLSX from 'xlsx';
import dayjs from 'dayjs';
import { getDeviceConf, getSerialPortDataList, setDeviceConf } from '/@/api/gateway/southdirection';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const dataTypeArray = ref([
{ type: 'bool', datalen: 1, show: false },
{ type: 'char', datalen: 1, show: false },
{ type: 'string', datalen: null, show: true },
{ type: 'int8', datalen: 1, show: false },
{ type: 'uint8', datalen: 1, show: false },
{ type: 'int16', datalen: 2, show: false },
{ type: 'int16_AB', datalen: 2, show: false },
{ type: 'int16_BA', datalen: 2, show: false },
{ type: 'uint16', datalen: 2, show: false },
{ type: 'uint16_AB', datalen: 2, show: false },
{ type: 'uint16_BA', datalen: 2, show: false },
{ type: 'float', datalen: 4, show: false },
{ type: 'float32_ABCD', datalen: 4, show: false },
{ type: 'float32_BADC', datalen: 4, show: false },
{ type: 'float32_CDAB', datalen: 4, show: false },
{ type: 'float32_DCBA', datalen: 4, show: false },
{ type: 'float64_ABCD', datalen: 8, show: false },
{ type: 'float64_BADC', datalen: 8, show: false },
{ type: 'float64_CDAB', datalen: 8, show: false },
{ type: 'float64_DCBA', datalen: 8, show: false },
{ type: 'int32', datalen: 4, show: false },
{ type: 'int32_ABCD', datalen: 4, show: false },
{ type: 'int32_BADC', datalen: 4, show: false },
{ type: 'int32_CDAB', datalen: 4, show: false },
{ type: 'int32_DCBA', datalen: 4, show: false },
{ type: 'uint32', datalen: 4, show: false },
{ type: 'uint32_ABCD', datalen: 4, show: false },
{ type: 'uint32_BADC', datalen: 4, show: false },
{ type: 'uint32_CDAB', datalen: 4, show: false },
{ type: 'uint32_DCBA', datalen: 4, show: false },
{ type: 'int64', datalen: 8, show: false },
{ type: 'uint64', datalen: 8, show: false },
// { type: 'double', datalen: 8, show: false },
]);
//上报策略
const uploadModeList = ref([
{ value: 'timer 10', link: 'timer 10' },
{ value: 'change 10', link: 'change 10' },
{ value: 'timer 10 || change 10', link: 'timer 10 || change 10' },
{ value: 'label LD200 up', link: 'label LD200 up' },
]);
const querySearch = (queryString, cb) => {
const results = queryString ? uploadModeList.value.filter(createFilter(queryString)) : uploadModeList.value;
cb(results);
};
const createFilter = (queryString) => {
return (restaurant) => {
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
};
};
//北向任务列表(后面换请求获取)
const taskList = ref([]);
const deviceConfigTableRef = ref();
const deviceDataTableRef = ref();
const tabelBox = ref(884);
const deviceConfigList = ref([]);
const protocolList = ref([
{ label: 'ModbusTCP', value: 'ModbusTCP' },
{ label: 'PluginTCP', value: 'PluginTCP' },
{ label: 'DLT645_2007', value: 'DLT645_2007' },
{ label: 'gycan', value: 'gycan' },
]);
//新增配置
const addDeviceConfig = () => {
let newDeviceConfig = {
id: guidGenerator(),
collectConf: {
// 数据采集配置
abnormalMax: 3, // 异常最大次数
allowTimeOut: { read: 2000, write: 2000 }, // 读写超时设置
intervalTime_ms: 100, // 采集间隔时间
isBlockRead: true, // 是否块读取
isBlockWrite: true, // 是否块写入
isRunCmd: true, // 是否运行命令
pollingCycleTime_ms: 1000, // 轮询周期时间
},
conclude: { isUse: false, libName: '' }, // 结论库设置
equipId: '', // 设备ID
equipName: '', // 设备名称
inductionParam: {
// 感应参数设置
maxLenBlock_read: { boolArea: -1, charArea: -1 }, // 读取最大长度
maxLenBlock_write: { boolArea: -1, charArea: -1 }, // 写入最大长度
},
interfaceParams: { link: '', physicalInterface: '' }, // 接口参数
properties: [],
protocol: { libName: '', params: { linkPar: '' } }, // 通信协议设置
};
console.log('deviceConfigList.value', deviceConfigTableRef.value);
deviceConfigList.value.push(newDeviceConfig);
// activeDeviceConfig.value=newDeviceConfig;
};
//删除配置
const delDeviceConfig = (index) => {
console.log('当前点击的行号', index);
ElMessageBox.confirm('确认要删除该设备配置吗?', '告警', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deviceConfigList.value.splice(index, 1);
ElMessage({
type: 'success',
message: '删除成功',
});
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
});
});
};
const getDeviceConfig = () => {
getDeviceConf({})
.then((response) => {
// 成功响应后的处理
console.log(response); // 打印响应数据
if (response.code == 0) {
//缓存上下行主题
deviceConfigList.value = response.data.equips || [];
taskList.value = response.data.tasks || [];
if(response.data.tasks && response.data.tasks.length > 0 ){
uploadModeList.value = response.data.tasks.map(item=>{
return {
value: item.uploadMode,
link: item.uploadMode,
};
});
}else{
uploadModeList.value = [];
}
if (deviceConfigList.value.length) {
if (deviceConfigList.value.length > ActiveRowIndex.value) {
activeDeviceConfig.value = deviceConfigList.value[ActiveRowIndex.value];
} else {
activeDeviceConfig.value = deviceConfigList.value[0];
}
} else {
addDeviceConfig();
}
} else {
ElMessage.error(response.message);
}
})
.catch((error) => {
// 错误处理
console.error(error); // 打印错误信息
ElMessage.error(error);
});
};
const dataFormat = (data) => {
console.log('数据格式化', deviceConfigList.value);
let nowtaskList =
taskList.value.map((item) => {
item.dataIdEntire = [];
return item;
}) || [];
if (nowtaskList.length > 0) {
deviceConfigList.value.forEach((item) => {
item.properties.forEach((property) => {
let taskId = property.taskId;
nowtaskList.forEach((task) => {
if (task.taskId === taskId) {
task.dataIdEntire.push({
equipId: item.equipId,
idInfo: [{ id: property.id, uploadMode: property.uploadMode }],
});
}
});
});
});
}
return {
equips: data,
tasks: nowtaskList,
};
};
const setDeviceConfig = () => {
console.log('提交配置', deviceConfigList.value);
let obj = dataFormat(deviceConfigList.value);
console.log('数据格式化后', obj);
setDeviceConf(obj)
.then((response) => {
// 成功响应后的处理
console.log(response); // 打印响应数据
if (response.code == 0) {
ElMessage.success('保存成功');
} else {
ElMessage.error(response.message);
}
})
.catch((error) => {
// 错误处理
console.error(error); // 打印错误信息
ElMessage.error(error);
});
};
//当选中索引
const ActiveRowIndex = ref(0);
const collectionModelShow = ref(false);
const collectionRef = ref();
const collectionForm = ref({
collectConf: {
// 数据采集配置
abnormalMax: 3, // 异常最大次数
allowTimeOut: { read: 2000, write: 2000 }, // 读写超时设置
intervalTime_ms: 100, // 采集间隔时间
isBlockRead: true, // 是否块读取
isBlockWrite: true, // 是否块写入
isRunCmd: true, // 是否运行命令
pollingCycleTime_ms: 1000, // 轮询周期时间
},
inductionParam: {
// 感应参数设置
maxLenBlock_read: { boolArea: -1, charArea: -1 }, // 读取最大长度
maxLenBlock_write: { boolArea: -1, charArea: -1 }, // 写入最大长度
},
});
//打开采集配置弹窗
const openCollectionModel = (row) => {
console.log('当前采集', row);
collectionForm.value = {
collectConf: row.collectConf,
inductionParam: row.inductionParam,
};
collectionModelShow.value = true;
};
const editCollection = () => {
deviceConfigList.value[ActiveRowIndex.value].collectConf = collectionForm.value.collectConf;
deviceConfigList.value[ActiveRowIndex.value].inductionParam = collectionForm.value.inductionParam;
collectionModelShow.value = false;
};
//连接配置
//串口配置
const dlt645ModelShow = ref(false);
const dlt645Ref = ref();
const serialportList = ref([]);
const dlt645Form = ref({
port: '',
baudrate: 115200,
databits: 8,
stopbits: 1,
parity: 'N',
});
//获取端口列表
const getSerialPortList = (msgShow = true) => {
getSerialPortDataList({})
.then((response) => {
// 成功响应后的处理
if (response.code === 0) {
serialportList.value = response.data.list || [];
if (msgShow) {
ElMessage.success('获取端口列表成功');
}
} else {
ElMessage.error(response.message);
}
})
.catch((error) => {
// 错误处理
ElMessage.error(error);
});
};
const editDlt645 = (formEl) => {
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
deviceConfigList.value[ActiveRowIndex.value].interfaceParams.link =
dlt645Form.value.port +
',' +
dlt645Form.value.baudrate +
',' +
dlt645Form.value.databits +
',' +
dlt645Form.value.stopbits +
',' +
dlt645Form.value.parity;
deviceConfigList.value[ActiveRowIndex.value].interfaceParams.physicalInterface = dlt645Form.value.port;
dlt645ModelShow.value = false;
}
});
};
//TCP配置
const tcpModelShow = ref(false);
const tcpRef = ref();
const tcpForm = ref({
link: '192.168.0.1:80',
});
const tcpFormRules = reactive({
link: [
{ required: true, message: 'ip地址+端口', trigger: 'blur' },
{ pattern: /^([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}$/, message: '格式错误格式应该是ip地址+端口如192.168.0.1:80', trigger: 'blur' },
],
});
//打开采集配置弹窗
const openConnectModel = (row) => {
console.log('当前采集', row.protocol.libName);
if (!row.protocol.libName) {
ElMessage.error('请先选择通信类型');
} else if (row.protocol.libName === 'DLT645_2007' || row.protocol.libName === 'gycan') {
if (row.interfaceParams.link) {
tcpForm.value.port = row.interfaceParams.link.split(',')[0];
tcpForm.value.baudrate = row.interfaceParams.link.split(',')[1];
tcpForm.value.databits = row.interfaceParams.link.split(',')[2];
tcpForm.value.stopbits = row.interfaceParams.link.split(',')[3];
tcpForm.value.parity = row.interfaceParams.link.split(',')[4];
} else {
tcpForm.value = {
port: '',
baudrate: 115200,
databits: 8,
stopbits: 1,
parity: 'N',
};
}
// getSerialPortList()
dlt645ModelShow.value = true;
} else if (row.protocol.libName === 'ModbusTCP' || row.protocol.libName === 'PluginTCP') {
tcpForm.value.link = row.interfaceParams.link;
tcpModelShow.value = true;
} else {
ElMessage.error('暂不支持该通信类型');
}
};
const editTcp = (formEl) => {
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
deviceConfigList.value[ActiveRowIndex.value].interfaceParams.link = tcpForm.value.link;
tcpModelShow.value = false;
}
});
};
const activeDeviceConfig = ref({
collectConf: {
// 数据采集配置
abnormalMax: 3, // 异常最大次数
allowTimeOut: { read: 2000, write: 2000 }, // 读写超时设置
intervalTime_ms: 100, // 采集间隔时间
isBlockRead: true, // 是否块读取
isBlockWrite: true, // 是否块写入
isRunCmd: true, // 是否运行命令
pollingCycleTime_ms: 1000, // 轮询周期时间
},
conclude: { isUse: false, libName: '' }, // 结论库设置
equipId: 'dev102', // 设备ID
equipName: 'dev102', // 设备名称
inductionParam: {
// 感应参数设置
maxLenBlock_read: { boolArea: -1, charArea: -1 }, // 读取最大长度
maxLenBlock_write: { boolArea: -1, charArea: -1 }, // 写入最大长度
},
interfaceParams: { link: '', physicalInterface: '' }, // 接口参数
properties: [
// 设备属性项,可包含多个属性
{
authority: 'RW', // 读写权限
dataAddress: '', // 数据地址
dataDesc: '', // 数据描述
dataDisplay: 'uint8', // 数据展示格式
dataName: '', // 数据名称
dataUnit: '', // 数据单位
driveFlag: { isConclude_R: true, isConclude_W: true }, // 驱动标志
id: '', // 属性ID
northCalcParam: {
// 北向计算参数
decimal: { numBits: 1, way: 'rounding' }, // 小数处理方式
offset: '0', // 偏移量
proportion: '1', // 比例
},
offset: '0', // 偏移量
proportion: '1', // 比例
taskId: '', // 任务ID
uploadMode: '', // 上报策略
},
],
protocol: { libName: 'ModbusTCP', params: { linkPar: '' } }, // 通信协议设置
});
//添加数据源配置
const addDeviceDataConfig = () => {
let newDeviceDataConfig = {
authority: 'RW', // 读写权限
dataAddress: '', // 数据地址
dataDesc: '', // 数据描述
dataDisplay: 'uint8', // 数据展示格式
datalen: 2, // 数据长度
datalenShow: false, // 数据长度是否可编辑
dataName: '', // 数据名称
dataUnit: '', // 数据单位
driveFlag: { isConclude_R: true, isConclude_W: true }, // 驱动标志
id: '', // 属性ID
northCalcParam: {
// 北向计算参数
decimal: { numBits: 1, way: 'rounding' }, // 小数处理方式
offset: '0', // 偏移量
proportion: '1', // 比例
},
offset: '0', // 偏移量
proportion: '1', // 比例
taskId: '', // 任务ID
uploadMode: '', // 上报策略
};
activeDeviceConfig.value.properties.push(newDeviceDataConfig);
};
const changedataDisplay = (e, index) => {
let dataTypeObj = dataTypeArray.value.filter((item) => item.type === e);
activeDeviceConfig.value.properties[index].datalen = dataTypeObj[0].datalen;
activeDeviceConfig.value.properties[index].datalenShow = dataTypeObj[0].show;
};
const changeTaskId = (e, index) => {
let taskObj = taskList.value.filter((item) => item.taskId === e);
activeDeviceConfig.value.properties[index].uploadMode = taskObj[0].uploadMode;
};
//删除数据源配置
const delDeviceDataConfig = (index) => {
ElMessageBox.confirm('确认要删除该数据源配置吗?', '告警', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
activeDeviceConfig.value.properties.splice(index, 1);
ElMessage({
type: 'success',
message: '删除成功',
});
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
});
});
};
//选择设备配置
const deviceConfigClick = (e, event, column) => {
activeDeviceConfig.value = e;
ActiveRowIndex.value = deviceConfigList.value.indexOf(e);
};
//更新表格宽度
const handleResize = () => {
deviceDataTableRef.value.doLayout();
tabelBox.value = themeConfig.value.isCollapse?window.innerWidth - 64 - 10:window.innerWidth - 180 - 10;
};
watch(
() => themeConfig.value.isCollapse,
() => {
handleResize();
},
{
deep: true,
}
);
const exportDeviceConfigXlsx = () => {
const workbook = XLSX.utils.book_new();
deviceConfigList.value.map((item) => {
let dataArr = [];
dataArr = item.properties.map((val) => {
return {
equipId: item.equipId,
...val,
isConclude_R: val.driveFlag.isConclude_R,
isConclude_W: val.driveFlag.isConclude_W,
offset: val.northCalcParam.offset,
proportion: val.northCalcParam.proportion,
numBits: val.northCalcParam.decimal.numBits,
way: val.northCalcParam.decimal.way,
equipName: item.equipName,
protocol: item.protocol.libName,
link: item.interfaceParams.link,
linkPar: item.protocol.params.linkPar,
};
});
// 将 JSON 数据转换为工作表
let worksheet = XLSX.utils.json_to_sheet(dataArr);
// 创建工作簿并添加工作表
XLSX.utils.book_append_sheet(workbook, worksheet, '南向' + item.equipId);
});
// 将工作簿写入二进制数组
const selIn = XLSX.write(workbook, { bookType: 'xlsx', bookSST: true, type: 'array' });
try {
FileSaver.saveAs(new Blob([selIn], { type: 'application/octet-stream' }), '网关南向采集配置' + dayjs().format('YYYY-MM-DD HH-mm-ss') + '.xlsx');
} catch (e) {
if (typeof console !== 'undefined') console.log(e, selIn);
}
return selIn;
};
const exportDeviceDataConfigXlsx = () => {
const workbook = XLSX.utils.book_new();
let dataArr = [];
dataArr = activeDeviceConfig.value.properties.map((val) => {
return {
equipId: activeDeviceConfig.value.equipId,
...val,
isConclude_R: val.driveFlag.isConclude_R,
isConclude_W: val.driveFlag.isConclude_W,
offset: val.northCalcParam.offset,
proportion: val.northCalcParam.proportion,
numBits: val.northCalcParam.decimal.numBits,
way: val.northCalcParam.decimal.way,
equipName: activeDeviceConfig.value.equipName,
libName: activeDeviceConfig.value.protocol.libName,
link: activeDeviceConfig.value.interfaceParams.link,
linkPar: activeDeviceConfig.value.protocol.params.linkPar,
};
});
// 将 JSON 数据转换为工作表
let worksheet = XLSX.utils.json_to_sheet(dataArr);
// 创建工作簿并添加工作表
XLSX.utils.book_append_sheet(workbook, worksheet, '南向' + activeDeviceConfig.value.equipId);
// 将工作簿写入二进制数组
const selIn = XLSX.write(workbook, { bookType: 'xlsx', bookSST: true, type: 'array' });
try {
FileSaver.saveAs(
new Blob([selIn], { type: 'application/octet-stream' }),
'网关南向采集数据源配置' + dayjs().format('YYYY-MM-DD HH-mm-ss') + '.xlsx'
);
} catch (e) {
if (typeof console !== 'undefined') console.log(e, selIn);
}
return selIn;
};
const handleFileChange = (file, fileList) => {
const rawFile = file.raw;
if (!rawFile) {
ElMessage.error('请选择一个文件');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
// 解析所有工作表
const sheets = {};
workbook.SheetNames.forEach((sheetName) => {
const sheet = workbook.Sheets[sheetName];
const json = XLSX.utils.sheet_to_json(sheet);
sheets[sheetName] = json;
});
for (var prop in sheets) {
console.log(prop, sheets[prop]);
let dataArr = sheets[prop];
if (dataArr.length > 0) {
let flag = true;
deviceConfigList.value.forEach((item) => {
if (item.equipId === dataArr[0].equipId) {
flag = false;
item.properties = dataArr.map((val) => {
return {
authority: val.authority, // 读写权限
dataAddress: val.dataAddress, // 数据地址
dataDesc: val.dataDesc, // 数据描述
dataDisplay: val.dataDisplay, // 数据展示格式
datalen: val.datalen,
datalenShow: val.datalenShow, // 数据长度是否可编辑
dataName: val.dataName, // 数据名称
dataUnit: val.dataUnit, // 数据单位
driveFlag: { isConclude_R: val.isConclude_R, isConclude_W: val.isConclude_W }, // 驱动标志
id: val.id, // 属性ID
northCalcParam: {
// 北向计算参数
decimal: { numBits: val.numBits, way: val.way }, // 小数处理方式
offset: val.offset, // 偏移量
proportion: val.proportion, // 比例
},
offset: val.offset, // 偏移量
proportion: val.proportion, // 比例
taskId: val.taskId, // 任务ID
uploadMode: val.uploadMode, // 上报策略
};
});
item.equipName = dataArr[0].equipName;
item.protocol.libName = dataArr[0].protocol;
item.interfaceParams.link = dataArr[0].link;
item.protocol.params.linkPar = dataArr[0].linkPar;
}
});
// 是否有新增
if (flag) {
let obj = {
id: guidGenerator(),
collectConf: {
// 数据采集配置
abnormalMax: 3, // 异常最大次数
allowTimeOut: { read: 2000, write: 2000 }, // 读写超时设置
intervalTime_ms: 100, // 采集间隔时间
isBlockRead: true, // 是否块读取
isBlockWrite: true, // 是否块写入
isRunCmd: true, // 是否运行命令
pollingCycleTime_ms: 1000, // 轮询周期时间
},
conclude: { isUse: false, libName: '' }, // 结论库设置
equipId: dataArr[0].equipId, // 设备ID
equipName: dataArr[0].equipName, // 设备名称
inductionParam: {
// 感应参数设置
maxLenBlock_read: { boolArea: -1, charArea: -1 }, // 读取最大长度
maxLenBlock_write: { boolArea: -1, charArea: -1 }, // 写入最大长度
},
interfaceParams: { link: dataArr[0].link, physicalInterface: '' }, // 接口参数
properties: [],
protocol: { libName: dataArr[0].protocol, params: { linkPar: dataArr[0].linkPar } }, // 通信协议设置
};
obj.properties = dataArr.map((val) => {
return {
authority: val.authority, // 读写权限
dataAddress: val.dataAddress, // 数据地址
dataDesc: val.dataDesc, // 数据描述
dataDisplay: val.dataDisplay, // 数据展示格式
datalen: val.datalen,
datalenShow: val.datalenShow, // 数据长度是否可编辑
dataName: val.dataName, // 数据名称
dataUnit: val.dataUnit, // 数据单位
driveFlag: { isConclude_R: val.isConclude_R, isConclude_W: val.isConclude_W }, // 驱动标志
id: val.id, // 属性ID
northCalcParam: {
// 北向计算参数
decimal: { numBits: val.numBits, way: val.way }, // 小数处理方式
offset: val.offset, // 偏移量
proportion: val.proportion, // 比例
},
offset: val.offset, // 偏移量
proportion: val.proportion, // 比例
taskId: val.taskId, // 任务ID
uploadMode: val.uploadMode, // 上报策略
};
});
deviceConfigList.value.push(obj);
}
}
}
// 你可以在这里处理解析后的数据
ElMessage.success('导入成功');
};
reader.readAsArrayBuffer(rawFile);
};
onMounted(() => {
window.addEventListener('resize', handleResize);
getDeviceConfig();
getSerialPortList(false);
handleResize();
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
<style scoped lang="scss">
.el-table .double-clicked-row {
background-color: #f0f9eb;
}
:deep(.el-table .el-scrollbar) {
background: #fff;
}
#southdirection-box {
position: absolute;
width: 100%;
height: 100%;
.btn-list {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
.btn-list-right {
display: flex;
align-items: center;
}
}
.device-config {
position: relative;
overflow-y: auto;
}
.data-config {
position: relative;
height: calc(100% - 278px);
.table-box {
position: absolute;
width: 100%;
height: calc(100% - 162px);
}
}
.table-box {
//position: absolute;
//width: 100%;
}
}
</style>
<style>
.selected-row {
background-color: #55e800;
}
</style>