Merge branch 'dev' into dev-dictionary

This commit is contained in:
XieYongHong 2023-11-14 17:55:47 +08:00
commit 7e550f4558
16 changed files with 810 additions and 29 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -54,8 +54,7 @@ export const queryPointNoPaging = (data: any) =>
export const scanOpcUAList = (data: any) =>
server.get(
`/data-collect/opc/channel/${data.id}/nodes?nodeId=${
data?.nodeId || ''
`/data-collect/opc/channel/${data.id}/nodes?nodeId=${data?.nodeId || ''
}`,
);
@ -64,3 +63,7 @@ export const queryTypeList = () => server.get(`/data-collect/opc/data-types`);
export const getProviders = () => server.get('/data-collect/channel/gateway/codec/providers')
export const getStates = () => server.get('/dictionary/running-state/items')
export const getSnapTypes = () => server.get('/s7/client/s7codecs/list')
export const getArea = () => server.get('/s7/client/s7area/list')

View File

@ -57,4 +57,5 @@ export const protocolList = [
{ label: 'OPC_UA', value: 'OPC_UA', alias: 'opc-ua' },
{ label: 'MODBUS_TCP', value: 'MODBUS_TCP', alias: 'Modbus/TCP' },
{ label: 'COLLECTOR_GATEWAY', value: 'COLLECTOR_GATEWAY', alias: 'GATEWAY' },
{ label: 'S7', value: 'snap7', alias: 'snap7' },
]

View File

@ -171,6 +171,12 @@
v-model:value="formData.configuration.password"
/>
</j-form-item>
<!-- <j-form-item
v-if="formData.provider === 'snap7'"
:name="['configuration', 'connect']"
>
<j-input v-model:value="formData.configuration.connect"/>
</j-form-item> -->
<j-form-item label="说明" name="description">
<j-textarea
placeholder="请输入说明"
@ -239,6 +245,12 @@ const handleOk = async () => {
params.configuration.deviceName = formData.value.configuration.deviceName
}
if(params?.provider === 'snap7'){
params.configuration={
connect : false
}
}
params.circuitBreaker = {
type: 'Ignore'
}
@ -304,7 +316,7 @@ const getProvidersList = async () => {
if (resp.status === 200) {
const arr = resp.result
.filter(
(item: any) => ['GATEWAY', 'Modbus/TCP', 'opc-ua'].includes(item.name),
(item: any) => ['GATEWAY', 'Modbus/TCP', 'opc-ua','snap7'].includes(item.name),
)
.map((it: any) => it.name);
const providers: any = protocolList.filter((item: any) =>

View File

@ -17,6 +17,7 @@ export const FormState: FormDataType = {
password: '',
deviceId: undefined,
deviceName: undefined,
connect:false,
},
description: '',
};

View File

@ -43,7 +43,7 @@
>
<template #img>
<slot name="img">
<img :src="getImage('/channel.png')" />
<img :src="ImageMap.get(slotProps.provider)" />
</slot>
</template>
<template #content>
@ -157,6 +157,17 @@ const params = ref<Record<string, any>>({});
const visible = ref(false);
const current = ref({});
const opcImage = getImage('/DataCollect/device-opcua.png');
const modbusImage = getImage('/DataCollect/device-modbus.png');
const s7Image = getImage('/DataCollect/s7.png')
const gatewayImage = getImage('/DataCollect/gateway.png')
const ImageMap = new Map()
ImageMap.set('OPC_UA',opcImage)
ImageMap.set('MODBUS_TCP',modbusImage)
ImageMap.set('snap7',s7Image)
ImageMap.set('COLLECTOR_GATEWAY',gatewayImage)
const columns = [
{
title: '通道名称',

View File

@ -10,6 +10,7 @@ export interface ConfigurationType {
authType: string | undefined,
deviceId: string | undefined,
deviceName: string | undefined,
connect:boolean | undefined
}
export interface FormDataType {

View File

@ -0,0 +1,254 @@
<template>
<div class="top"><j-switch v-model:checked="deathArea" @change="handleDeathArea"></j-switch></div>
<div v-if="deathArea">
<div class="content">
<j-radio-group v-model:value="tag" @change="handleTag">
<j-space>
<j-radio-button value="currentValue">固定值</j-radio-button>
<j-radio-button value="this['currentValue'] - this['lastValue']">百分比</j-radio-button>
</j-space>
</j-radio-group>
<j-tooltip v-if="tag !== 'currentValue'" title="最近一次采集到的值与上一次采集值比对,数值浮动在百分比以内时将被过滤">
<AIcon type="QuestionCircleOutlined" style="margin-left: 10px;font-size: 18px;color: rgb(153, 153, 153)" />
</j-tooltip>
</div>
<j-form-item-rest>
<div v-if="tag === 'currentValue'" class="fixed">
<j-row :gutter="5" align="middle">
<j-col>
<j-input-number v-model:value="_value[0].value" style="width: 100%" placeholder="请输入值"
:max="_value[1] ? _value[1].value : 999999" :min="1" @change="handleChange" />
</j-col>
<j-col>
<j-select v-model:value="_value[0].termType" :showArrow="false" :options="termTypeOptions"
placeholder="符号" @change="handleChange" />
</j-col>
<template v-if="swap === 'range'">
<j-col>点位值</j-col>
<j-col>
<j-select :showArrow="false" v-model:value="_value[1].termType" :options="termTypeOptions"
placeholder="符号" @change="handleChange" />
</j-col>
<j-col>
<j-input-number v-model:value="_value[1].value" style="width: 100%" placeholder="请输入值"
:min="_value[0].value" @change="handleChange" />
</j-col>
</template>
<j-button @click="handleSwap">
<AIcon type="SwapOutlined" />
</j-button>
</j-row>
</div>
<div v-else class="percent">
<div class="percent-title">点位值</div>
<j-input-number v-model:value="percentValue" style="width: 200px" addon-after="%" placeholder="请输入值"
:min="1" @change="handlePercent" :max="65535" />
</div>
</j-form-item-rest>
</div>
</template>
<script setup lang='ts'>
import { Form } from 'ant-design-vue';
const formItemContext = Form.useInjectFormItemContext()
const props = defineProps({
value: {
type: Array,
default: () => [{}]
}
})
const emits = defineEmits(['update:value', 'change']);
const _value = ref<any>(props.value)
const deathArea = ref(false);
const tag = ref<string>('currentValue')
const swap = ref<string>('fix')
const percentValue = ref()
const termTypeOptions = computed(() => {
if (_value.value?.length === 1) {
return [
{ label: '=', value: 'neq' },
{ label: '>', value: 'lte' },
{ label: '<', value: 'gte' },
{ label: '≥', value: 'lt' },
{ label: '≤', value: 'gt' },
];
} else {
return [
{ label: '<', value: 'gte' },
{ label: '≤', value: 'gt' },
];
}
});
const handleDeathArea = (e: any) => {
if (e) {
_value.value = [{
column: 'currentValue',
value: undefined,
termType: undefined,
type: 'and',
}]
} else {
_value.value = []
}
handleChange()
}
const handleSwap = () => {
if (swap.value === 'fix') {
swap.value = 'range'
_value.value = [
{
column: 'currentValue',
value: '',
termType: undefined,
type: 'or',
},
{
column: 'currentValue',
value: '',
termType: undefined,
type: 'or',
},
]
} else {
swap.value = 'fix'
_value.value = [{
column: 'currentValue',
value: undefined,
termType: undefined,
type: 'and',
}]
}
handleChange()
}
const handleTag = (e: any) => {
if (e.target.value === 'currentValue') {
swap.value = 'fix'
_value.value = [{
column: 'currentValue',
value: undefined,
termType: undefined,
type: 'and',
}]
} else {
_value.value = [
{
column: `this['currentValue'] - this['lastValue']*init/100`,
value: 0,
termType: 'lt',
type: 'or',
},
{
column: `this['currentValue'] - this['lastValue']*0/100`,
value: 0,
termType: 'gt',
type: 'or',
}
]
}
handleChange()
}
const handlePercent = (e: any) => {
if (e) {
_value.value = [
{
column: `this['currentValue'] - this['lastValue'] * ${e}/100`,
value: 0,
termType: 'lt',
type: 'or',
},
{
column: `this['currentValue'] - this['lastValue'] * ${e + 100}/100`,
value: 0,
termType: 'gt',
type: 'or',
}
]
}else{
_value.value = [
{
column: `this['currentValue'] - this['lastValue'] * 1/100`,
value: 0,
termType: 'lt',
type: 'or',
},
{
column: `this['currentValue'] - this['lastValue'] * 1/100`,
value: 0,
termType: 'gt',
type: 'or',
}
]
}
handleChange()
}
const handlePercentProps = (arr: any) => {
const obj = arr.find((item: any) => item.termType === 'lt')
const val = obj.column.split('*')[1].split('/')[0]
percentValue.value = val !== 'init' ? val : undefined
}
const handleChange = () => {
emits('update:value', _value.value)
emits('change', _value.value)
formItemContext.onFieldChange()
}
watch(
() => props.value,
(val: any) => {
if (val && val.length !== 0) {
deathArea.value = true
if (val && val[0]?.column === 'currentValue') {
tag.value = 'currentValue'
_value.value = val
if (val.length === 2) {
swap.value = 'range'
}
} else {
handlePercentProps(val)
tag.value = `this['currentValue'] - this['lastValue']`
}
}
},
{ deep: true, immediate: true }
)
</script>
<style scoped lang='less'>
.top {
padding: 12px 0;
}
.fixed {
padding: 12px 0;
}
.percent {
display: flex;
align-items: center;
padding: 12px;
.percent-title {
margin-right: 10px;
}
}
</style>

View File

@ -0,0 +1,337 @@
<template >
<j-modal :title="data.id ? '编辑' : '新增'" :visible="true" width="700px" @cancel="handleCancel">
<j-form :model="form" layout="vertical" ref="formRef">
<j-form-item label="点位名称" name="name">
<j-input placeholder="请输入点位名称" v-model:value="form.name" />
</j-form-item>
<j-form-item label="地址区域" :name="['configuration', 'daveArea']" :rules="{
required: true,
message: '请选择地址区域',
trigger: 'change',
}">
<j-select v-model:value="form.configuration.daveArea" show-search placeholder="请选择地址区域"
@change="daveAreaChange">
<j-select-option v-for="item in dataAreaFilterList" :key="item.id" :value="item.id">{{
item.name }}</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="地址编号" :name="['configuration', 'areaNumber']" v-show="form.configuration.daveArea == 'DB'"
:rules="{
required: true,
message: '请输入地址编号',
trigger: 'blur',
}">
<j-input-number v-model:value="form.configuration.areaNumber" :maxlength="64" style="width: 100%"
:max="65535" autocomplete="off" :disabled="form.configuration.daveArea == 'DB' && deviceType == 'S200'"
placeholder="请输入地址编号" />
</j-form-item>
<j-form-item label="数据类型" :name="['configuration', 'type']" :rules="{
required: true,
message: '请选择数据类型',
trigger: 'change',
}">
<j-select v-model:value="form.configuration.type" show-search placeholder="请选择数据类型"
@change="chooseS7DataType">
<j-select-option v-for="item in dataTypesList" :key="item.id" :value="item.id">{{
item.name }}</j-select-option>
</j-select>
</j-form-item>
<j-form-item v-if="!disabled" label="字符串长度byte" :name="['configuration', 'bytes']" :rules="{
required: true,
message: '请输入0~65535之间的正整数',
trigger: 'blur',
}">
<j-input-number type="number" style="width: 100%" addon-after="字节" v-model:value="form.configuration.bytes"
placeholder="请输入字符串长度" :precision="0" :controls="false" :maxlength="64" :disabled="disabled" />
</j-form-item>
<j-form-item v-if="form.type == 'Bool'" label="位偏移量bit" :name="['configuration', 'bits']" :rules="{
required: true,
message: '请输入0~7之间的正整数',
trigger: 'blur',
}">
<j-input-number type="number" style="width: 100%" addon-after="" v-model:value="form.configuration.bits"
placeholder="请输入位偏移量" :precision="0" :min="0" :max="7" :controls="false" :maxlength="2" />
</j-form-item>
<j-form-item label="偏移量" :name="['configuration', 'offset']" :rules="{
required: true,
message: '请输入0~65535之间的正整数',
trigger: 'blur',
}">
<j-input-number type="number" style="width: 100%" v-model:value="form.configuration.offset"
placeholder="请输入偏移量" :precision="0" :min="0" :max="65535" :controls="false" :maxlength="64" />
</j-form-item>
<j-form-item label="缩放因子" :name="['configuration', 'scaleFactor']">
<j-input-number type="number" style="width: 100%" v-model:value="form.configuration.scaleFactor"
placeholder="缩放因子" :min="0" :max="65535" :controls="false" :maxlength="64" />
</j-form-item>
<j-form-item label="小数保留位数" :name="['configuration', 'scale']">
<j-input-number type="number" style="width: 100%" v-model:value="form.configuration.scale"
placeholder="缩放因子" :precision="0" :min="1" :max="65535" :controls="false" :maxlength="64" />
</j-form-item>
<j-form-item label="访问类型" name="accessModes" :rules="{
required: true,
message: '请选择访问类型',
}">
<j-card-select multiple :showImage="false" v-model:value="form.accessModes" :options="[
{ label: '读', value: 'read' },
{ label: '写', value: 'write' },
]
" :column="2" />
</j-form-item>
<j-form-item :name="['configuration', 'terms']" :rules="[{
validator: Area,
trigger: 'change',
}]">
<template #label>
<j-space>
<span>点位死区</span><span class="explain">点位死区范围内的异常数据将被过滤请勿配置非数值类型</span>
</j-space>
</template>
<DeathArea v-model:value="form.configuration.terms" />
</j-form-item>
<j-form-item label="轮询任务" :name="['configuration', 'interval']">
<p>
采集频率<span style="margin-left: 5px; color: #9d9ea1; font-size: 12px">采集频率为0时不执行轮询任务</span>
</p>
<j-radio-group v-model:value="form.configuration.interval">
<j-space>
<j-radio-button :value="3000">3000ms</j-radio-button>
<j-radio-button :value="6000">6000ms</j-radio-button>
<j-radio-button :value="9000">9000ms</j-radio-button>
<j-radio-button :value="Number(form.configuration.interval)" @click="intervalRef.visible = true">
{{
![3000, 6000, 9000].includes(form.configuration.interval)
? form.configuration.interval + 'ms'
: '自定义'
}}
</j-radio-button>
</j-space>
</j-radio-group>
</j-form-item>
<j-form-item name="features">
<j-checkbox-group v-model:value="form.features">
<j-checkbox value="changedOnly">只推送变化的数据</j-checkbox>
</j-checkbox-group>
</j-form-item>
</j-form>
<j-modal title="采集频率" :visible="intervalRef.visible" @cancel="handleCancelInterval" @ok="handleInterval">
<j-form ref="formRef2" name="virtual-form" layout="vertical" :model="intervalRef">
<j-form-item label="采集频率" name="interval" :rules="[
{ required: true, message: '请输入采集频率' },
]">
<j-input-number type="tel" addonAfter="ms" v-model:value="intervalRef.interval" placeholder="请输入采集频率"
:controls="false" :precision="0" :maxlength="64" />
</j-form-item>
</j-form>
</j-modal>
<template #footer>
<j-button key="back" @click="handleCancel">取消</j-button>
<PermissionButton key="submit" type="primary" :loading="loading" @click="handleOk" style="margin-left: 8px"
:hasPermission="`DataCollect/Collector:${data.id ? 'update' : 'add'}`">
确认
</PermissionButton>
</template>
</j-modal>
</template>
<script lang="ts" setup>
import {
savePoint,
updatePoint,
_validateField,
getArea,
getSnapTypes
} from '@/api/data-collect/collector';
import type { FormInstance } from 'ant-design-vue';
import DeathArea from './DeathArea.vue';
import { randomString } from '@/utils/utils';
const props = defineProps({
data: {
type: Object,
default: () => { },
}
});
const dataAreaFilter = {
S200: [
'RELAY',
'HIGH_SPEED',
'SYSTEM_FLAGS',
'ANALOG_INPUTS',
'ANALOG_OUTPUTS',
'I',
'Q',
'M',
'IEC_COUNTERS',
'IEC_TIMERS',
],
S1200: ['I', 'Q', 'M', 'DB'],
S1500: ['I', 'Q', 'M', 'DB'],
S300: ['I', 'Q', 'M', 'DB', 'C', 'T'],
S400: ['I', 'Q', 'M', 'DB', 'C', 'T'],
};
const emit = defineEmits(['change']);
const loading = ref(false);
const formRef = ref<FormInstance>();
const formRef2 = ref<FormInstance>();
const deviceType = ref<string>('S200');
const dataTypesList = ref<any[]>([]);
const daveAreaList = ref<any>([]);
const form = ref<any>({
name: props.data.name || '',
configuration: props.data.configuration || {
type: undefined,
interval: 3000,
areaNumber: undefined,
terms: []
},
accessModes: [],
features: [],
description: props.data.description || '',
// inheritBreaker: true,
// pointKey: randomString(9)
});
const intervalRef = reactive({
visible: false,
interval: 3000,
})
/**选择S7点位数据类型 */
const disabled = ref(true);
const chooseS7DataType = (val: string) => {
const result: any = dataTypesList.value.find((item: any) => item.id == val);
form.value.configuration.bytes = result.length;
disabled.value = result.length != 0;
};
const daveAreaChange = (val: string) => {
if (val === 'DB') {
form.value.configuration.areaNumber = 1;
} else {
form.value.configuration.areaNumber = 0;
}
}
/**
* 获取地区信息
*/
const getAreaList = async () => {
const res = await getArea();
if (res.success) {
daveAreaList.value = res.result;
}
};
getAreaList();
/**
* 获取数据类型
*/
const getTypes = async () => {
const res: any = await getSnapTypes();
dataTypesList.value = res.result;
};
getTypes();
const dataAreaFilterList = computed(() => {
let result = daveAreaList.value.filter((item: any) =>
dataAreaFilter[deviceType.value].includes(item.id),
);
if (deviceType.value == 'S200') {
result.push({
id: 'DB',
name: '变量存储区V',
address: '',
});
}
return result;
});
const handleInterval = async () => {
const res = await formRef2.value?.validate()
if (res) {
form.value.configuration.interval = res.interval
intervalRef.visible = false
}
};
const handleCancelInterval = () => {
intervalRef.visible = false
intervalRef.interval = 3000
}
const handleOk = async () => {
const res = await formRef.value?.validate();
const params = {
...res,
inheritBreaker: true,
pointKey: props.data.pointKey || randomString(9),
provider:props.data.provider,
collectorId:props.data.collectorId,
accessModes:res?.accessModes.filter((item:any)=>item)
}
loading.value = true;
const response = !props.data.id
? await savePoint(params).catch(() => { })
: await updatePoint(props.data.id, { ...props.data, ...params }).catch(() => { });
emit('change', response?.status === 200);
loading.value = false;
};
const handleCancel = () => {
emit('change', false);
};
const Area = (_: any, value: any): Promise<any> =>
new Promise(async (resolve, reject) => {
console.log('value',value)
if(value.length === 0){
return resolve('')
}else if(value.length === 1){
return value[0].value && value[0].termType ? resolve('') : reject('请配置点位死区');
}else{
if(value[0].column === 'currentValue'){
// value.forEach((item:any) => {
// if(item.termType && item.value){
// return resolve('')
// }else{
// return reject('')
// }
// });
const pass = value.every((item:any)=>item.termType && item.value)
return pass ? resolve(''):reject('请配置点位死区')
}else{
value.forEach((item:any) => {
if(item.column ===`this['currentValue'] - this['lastValue']*init/100`){
return reject('请配置点位死区')
}else{
return resolve('')
}
});
}
}
});
onMounted(() => {
form.value.features = props.data.features?.map((item:any)=>item.value)
if(props.data.accessModes?.length!==0){
form.value.accessModes = props.data.accessModes?.map((item:any)=>item.value)
}
})
</script>
<style lang="less" scoped>
.explain {
color: #adadad;
font-size: 12px;
}
</style>

View File

@ -20,7 +20,7 @@
<template #headerTitle>
<j-space>
<PermissionButton
v-if="['MODBUS_TCP', 'COLLECTOR_GATEWAY'].includes(data?.provider)"
v-if="['MODBUS_TCP', 'COLLECTOR_GATEWAY','snap7'].includes(data?.provider)"
type="primary"
@click="handlAdd"
hasPermission="DataCollect/Collector:add"
@ -150,13 +150,7 @@
</div>
</template>
<template #img>
<img
:src="
slotProps.provider === 'OPC_UA'
? opcImage
: modbusImage
"
/>
<img :src="ImageMap.get(slotProps.provider)"/>
</template>
<template #content>
<div class="card-box-content">
@ -319,6 +313,7 @@
:data="current"
@change="saveChange"
/>
<SaveS7 v-if="visible.saveS7" :data="current" @change="saveChange"/>
<Scan v-if="visible.scan" :data="current" @change="saveChange" />
</j-spin>
</template>
@ -345,6 +340,7 @@ import { getWebSocket } from '@/utils/websocket';
import { map } from 'rxjs/operators';
import dayjs from 'dayjs';
import { responsiveArray } from 'ant-design-vue/lib/_util/responsiveObserve';
import SaveS7 from './Save/SaveS7.vue';
const props = defineProps({
data: {
@ -357,12 +353,22 @@ const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const opcImage = getImage('/DataCollect/device-opcua.png');
const modbusImage = getImage('/DataCollect/device-modbus.png');
const s7Image = getImage('/DataCollect/s7.png')
const gatewayImage = getImage('/DataCollect/gateway.png')
const ImageMap = new Map()
ImageMap.set('OPC_UA',opcImage)
ImageMap.set('MODBUS_TCP',modbusImage)
ImageMap.set('snap7',s7Image)
ImageMap.set('COLLECTOR_GATEWAY',gatewayImage)
const visible = reactive({
saveModBus: false,
saveOPCUA: false,
writePoint: false,
batchUpdate: false,
scan: false,
saveS7:false
});
const current: any = ref({});
const accessModesOption = ref();
@ -474,15 +480,27 @@ const clickBatch = () => {
};
const handlAdd = () => {
if( props.data?.provider === 'snap7'){
console.log(props.data)
visible.saveS7 = true
current.value = {
collectorId: props.data?.id,
provider: props.data?.provider
}
}else{
visible.saveModBus = true;
current.value = {
collectorId: props.data?.id,
provider: props.data?.provider || 'MODBUS_TCP',
};
}
};
const handlEdit = (data: any) => {
if (data?.provider === 'OPC_UA') {
visible.saveOPCUA = true;
} else if(data?.provider === 'snap7'){
visible.saveS7 = true
}else{
visible.saveModBus = true;
}

View File

@ -39,6 +39,33 @@
placeholder="请输入采集器名称"
v-model:value="formData.name"
/>
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="IP" :name="['configuration', 'host']" :rules="LeftTreeRules.host" >
<j-input v-model:value="formData.configuration.host" autocomplete="off" placeholder="请输入通道IP" :disabled="false"/>
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="端口" :name="['configuration', 'port']" :rules="LeftTreeRules.port">
<j-input-number style="width: 100%" v-model:value="formData.configuration.port" autocomplete="off" placeholder="请输入通道端口"/>
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="机架号" :name="['configuration', 'rack']" :rules="LeftTreeRules.rack">
<j-input-number style="width: 100%" v-model:value="formData.configuration.rack" autocomplete="off" placeholder="请输入机架号" :maxlength="64" />
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="型号" :name="['configuration', 'type']" :rules="LeftTreeRules.type">
<j-select v-model:value="formData.configuration.type" placeholder="请选择型号" @change="typeChange">
<j-select-option v-for="item in typeOptions" :key="item.value" :value="item.value">{{ item.label }}</j-select-option>
</j-select>
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="槽位" :name="['configuration', 'slot']" :rules="LeftTreeRules.slot">
<j-input-number style="width: 100%" v-model:value="formData.configuration.slot" autocomplete="off" placeholder="请输入槽位" :maxlength="64" :disabled="formData.configuration.type == 'S200' || formData.configuration.type == 'S1200'"/>
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="超时时间(毫秒)" :name="['configuration', 'timeout']" :rules="LeftTreeRules.timeout">
<j-input-number style="width: 100%" v-model:value="formData.configuration.timeout" autocomplete="off" placeholder="请输入超时时间" :maxlength="64" />
</j-form-item>
<j-form-item v-if="provider === 'snap7'" label="数据读取方式" :name="['configuration', 'serializable']">
<j-radio-group v-model:value="formData.configuration.serializable">
<j-radio-button :value="false">并行</j-radio-button>
<j-radio-button :value="true">串行</j-radio-button>
</j-radio-group>
</j-form-item>
<j-form-item
v-if="provider === 'COLLECTOR_GATEWAY'"
@ -133,6 +160,7 @@
</p>
</div>
<j-form-item
v-if="provider !== 'snap7'"
:name="['configuration', 'requestTimeout']"
:rules="LeftTreeRules.requestTimeout"
>
@ -196,6 +224,14 @@ const props = defineProps({
const emit = defineEmits(['change']);
const typeOptions = ref([
{value: 'S200', label: 'S7-200'},
{value: 'S300', label: 'S7-300'},
{value: 'S400', label: 'S7-400'},
{value: 'S1200', label: 'S7-1200'},
{value: 'S1500', label: 'S7-1500'},
])
const id = props.data.id;
const formRef = ref<FormInstance>();
const provider = ref()
@ -248,16 +284,17 @@ const endianData = computed(() => {
}
});
const formData = ref({
const formData = ref<any>({
channelId: undefined,
name: '',
collectorProvider: undefined,
configuration: {
unitId: '',
type: 'LowerFrequency',
type: undefined,
endian: 'BIG',
endianIn: 'BIG',
requestTimeout: 2000,
serializable:false,
inheritBreakerSpec: {
type: 'LowerFrequency',
}
@ -347,10 +384,16 @@ const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const typeChange = (val:any)=>{
if(val == 'S200' || val == 'S1200') {
formData.value.configuration.slot = 1
}
}
watch(
() => formData.value.channelId,
(value) => {
const dt = _channelListAll.value.find((item) => item.id === value);
const dt:any = _channelListAll.value.find((item:any) => item.id === value);
visibleUnitId.value = visibleEndian.value =
dt?.provider && ['MODBUS_TCP', 'COLLECTOR_GATEWAY'].includes(dt?.provider);
},

View File

@ -22,6 +22,28 @@ export const getState = (record: any) => {
};
export const regOnlyNumber = new RegExp(/^\d+$/);
export const regular = {
// IPV4 IPV6 域名
IP_Domain: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))$/,
// IP地址
IP_URL: /^((2((5[0-5])|([0-4]\d)))|([0-1]?\d{1,2}))(\.((2((5[0-5])|([0-4]\d)))|([0-1]?\d{1,2}))){3}$/,
// 英文或者数字或者-或者_
EN_NUMBER: /^[A-Za-z0-9_-]+$/,
// 服务器地址
HTTP_URL: /^http:|https:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/,
// 域名
DOMAIN_NAME: /^(?:(?:(?:[a-zA-z\-]+)\:\/{1,3})?(?:[a-zA-Z0-9])(?:[a-zA-Z0-9-\.]){1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+|\[(?:(?:(?:[a-fA-F0-9]){1,4})(?::(?:[a-fA-F0-9]){1,4}){7}|::1|::)\]|(?:(?:[0-9]{1,3})(?:\.[0-9]{1,3}){3}))(?:\:[0-9]{1,5})?$/,
// 数字
// NUMBER: /^[0-9]*[1-9][0-9]*$/,
NUMBER: /^([1-9]\d*|[0]{1,1})$/,
// 正数、负数、小数
ASSIGNMENT: /^(\-)?\d+(\.\d+)?$/,
//密码验证
PASSWORD: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,}$/,
//IP子网掩码
IP_MASK: /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$/,
CronRegEx: new RegExp('^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?)*))$',)
}
export const checkProviderData = {
int8: 1,
@ -150,6 +172,73 @@ export const OPCUARules = {
description: [{ max: 200, message: '最多可输入200个字符' }],
};
const validatorUrl = (rule:any, value:any, callback:any) => {
const reg = regular.DOMAIN_NAME
const reg1 = regular.IP_URL
if(value === undefined || value === '' || value === null) {
return Promise.reject("请输入通道Ip")
} else {
if(reg.test(value) === false && reg1.test(value) === false) {
return Promise.reject("请输入正确的域名或ip地址")
}
return Promise.resolve()
}
}
/**
*
*/
const validator1 = (rule:any, value:any, callback:any) => {
if(value === undefined || value === '' || value === null) {
return Promise.reject("请输入通道端口")
} else {
if(value < 0 || value > 65535) {
return Promise.reject("请输入0~65535的整数")
}
return Promise.resolve()
}
}
/**
*
*/
const validator2 = (rule:any, value:any, callback:any) => {
if(value === undefined || value === '' || value === null) {
return Promise.reject("请输入机架号")
} else {
if(value < 0 || value > 65535) {
return Promise.reject("请输入0~65535的整数")
}
return Promise.resolve()
}
}
/**
*
*/
const validator3 = (rule:any, value:any, callback:any) => {
if(value === undefined || value === '' || value === null) {
return Promise.reject("请输入槽位")
} else {
if(value < 0 || value > 65535) {
return Promise.reject("请输入0~65535的整数")
}
return Promise.resolve()
}
}
/**
*
*/
const validator4 = (rule:any, value:any, callback:any) => {
if(value === undefined || value === '' || value === null) {
return Promise.reject("请输入超时时间")
} else {
if(value < 0 || value > 65535) {
return Promise.reject("请输入0~65535的整数")
}
return Promise.resolve()
}
}
export const LeftTreeRules = {
channelId: [{ required: true, message: '请选择所属通道', trigger: 'blur' }],
name: [
@ -163,7 +252,7 @@ export const LeftTreeRules = {
message: '请输入0-255之间的正整数',
},
],
type: [{ required: true, message: '请选择处理方式', trigger: 'blur' }],
// type: [{ required: true, message: '请选择处理方式', trigger: 'blur' }],
endian: [
{ required: true, message: '请选择双字高低位切换', trigger: 'blur' },
],
@ -172,7 +261,16 @@ export const LeftTreeRules = {
],
requestTimeout:[
{ pattern: /^\d+$/, message:'请输入2000-60000的正整数',trigger: 'change'}
]
],
host: [
{ required: true, trigger: 'blur', validator: validatorUrl, },
],
port: [{ required: true, trigger: 'blur' , validator: validator1}],
rack: [{ required: true, trigger: 'blur', validator: validator2 }],
slot: [{ required: true, trigger: 'blur', validator: validator3 }],
timeout: [{ required: true, trigger: 'blur', validator: validator4 }],
type: [{required: true, trigger: 'change', message: '请选择型号'}],
serializable: [{required: true, trigger: 'change', message: '请选择型号'}],
};
export const FormTableColumns = [
@ -216,3 +314,5 @@ export const FormTableColumns = [
width: 50,
},
];

View File

@ -99,7 +99,7 @@ const pickerTimeChange = () => {
};
const echartsOptions = computed(() => {
console.log(serverActive.value)
console.log(serverActive.value,'---')
const series = serverActive.value.length
? serverActive.value.map((key) => setOptions(serverData.data, key))
: typeDataLine
@ -173,7 +173,7 @@ const getCPUEcharts = async (val: any) => {
const setOptions = (optionsData: any, key: string) => ({
data: arrayReverse(optionsData[key]),
name: key,
name: key != 'undefined' ? key : '',
type: 'line',
smooth: true,
symbol: 'none',

View File

@ -140,7 +140,7 @@ const getJVMEcharts = async (val: any) => {
const setOptions = (optionsData: any, key: string) => ({
data: arrayReverse(optionsData[key]),
name: key,
// name: key!= 'undefined' ? key : '',
type: 'line',
smooth: true,
symbol: 'none',

View File

@ -96,8 +96,8 @@ export default defineConfig(({ mode}) => {
// target: 'http://192.168.32.244:8881',
// target: 'http://192.168.32.163:8844', //张季本地
// target: 'http://120.77.179.54:8844', // 120测试
target: 'http://192.168.33.46:8844', // 本地开发环境
// target: 'http://192.168.33.1:8845', // 社区版开发环境
// target: 'http://192.168.33.46:8844', // 本地开发环境
target: 'http://192.168.33.1:8845', // 社区版开发环境
// target: 'http://192.168.32.5:8848', // 刘本地
// target: 'http://192.168.32.187:8844', // 谭本地
ws: 'ws://192.168.33.46:8844',