Merge pull request #59 from jetlinks/dev-hub
feat: 采集器 MODBUS、OPCUA表单,读写以及批量操作 feat: 采集器 OPCUA扫描全部功能 style: 调整TitleComponent组件样式
This commit is contained in:
commit
39694d7b04
|
@ -21,3 +21,34 @@ export const _validateField = (id: string, data?: any) =>
|
||||||
server.get(`/data-collect/point/${id}/_validate`, data);
|
server.get(`/data-collect/point/${id}/_validate`, data);
|
||||||
|
|
||||||
export const queryCodecProvider = () => server.get(`/things/collector/codecs`);
|
export const queryCodecProvider = () => server.get(`/things/collector/codecs`);
|
||||||
|
|
||||||
|
export const updatePoint = (id: string, data: any) =>
|
||||||
|
server.put(`/data-collect/point/${id}`, data);
|
||||||
|
|
||||||
|
export const savePointBatch = (data: any) =>
|
||||||
|
server.patch(`/data-collect/point`, data);
|
||||||
|
|
||||||
|
export const savePoint = (data: any) =>
|
||||||
|
server.post(`/data-collect/point`, data);
|
||||||
|
|
||||||
|
export const batchDeletePoint = (data: any) =>
|
||||||
|
server.post(`/data-collect/point/batch/_delete`, data);
|
||||||
|
|
||||||
|
export const removePoint = (id: string) =>
|
||||||
|
server.remove(`/data-collect/point/${id}`);
|
||||||
|
|
||||||
|
export const readPoint = (collectorId: string, data: string[]) =>
|
||||||
|
server.post(`/data-collect/collector/${collectorId}/points/_read`, data);
|
||||||
|
|
||||||
|
export const writePoint = (collectorId: string, data: string[]) =>
|
||||||
|
server.post(`/data-collect/collector/${collectorId}/points/_write`, data);
|
||||||
|
|
||||||
|
export const queryPointNoPaging = () =>
|
||||||
|
server.post(`/data-collect/point/_query/no-paging`, { paging: false });
|
||||||
|
|
||||||
|
export const scanOpcUAList = (data: any) =>
|
||||||
|
server.get(
|
||||||
|
`/data-collect/opc/channel/${data.id}/nodes?nodeId=${
|
||||||
|
data?.nodeId || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="title" :style='style'>
|
<div class="title" :style='style'>
|
||||||
<div class="title-before"></div>
|
|
||||||
<span>{{ data }}</span>
|
<span>{{ data }}</span>
|
||||||
<slot name="extra"></slot>
|
<slot name="extra"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +22,6 @@ const props = defineProps({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -32,14 +30,14 @@ const props = defineProps({
|
||||||
color: rgba(0, 0, 0, 0.8);
|
color: rgba(0, 0, 0, 0.8);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
&::before {
|
||||||
.title-before {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: calc(100% - 2px);
|
height: 100%;
|
||||||
background-color: @primary-color;
|
background-color: @primary-color;
|
||||||
border-radius: 0 3px 3px 0;
|
content: '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -11,21 +11,10 @@
|
||||||
:model="formData"
|
:model="formData"
|
||||||
name="basic"
|
name="basic"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
:rules="ModBusRules"
|
||||||
|
ref="formRef"
|
||||||
>
|
>
|
||||||
<j-form-item
|
<j-form-item label="点位名称" name="name">
|
||||||
label="点位名称"
|
|
||||||
name="name"
|
|
||||||
:rules="[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入点位名称',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
max: 64,
|
|
||||||
message: '最多可输入64个字符',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<j-input
|
<j-input
|
||||||
placeholder="请输入点位名称"
|
placeholder="请输入点位名称"
|
||||||
v-model:value="formData.name"
|
v-model:value="formData.name"
|
||||||
|
@ -34,12 +23,7 @@
|
||||||
<j-form-item
|
<j-form-item
|
||||||
label="功能码"
|
label="功能码"
|
||||||
:name="['configuration', 'function']"
|
:name="['configuration', 'function']"
|
||||||
:rules="[
|
:rules="ModBusRules.function"
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请选择功能码',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<j-select
|
<j-select
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
@ -57,18 +41,11 @@
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
<j-form-item
|
<j-form-item
|
||||||
label="地址"
|
label="地址"
|
||||||
:name="['configuration', 'parameter', 'address']"
|
:name="['pointKey']"
|
||||||
:rules="[
|
:rules="[
|
||||||
|
...ModBusRules.pointKey,
|
||||||
{
|
{
|
||||||
required: true,
|
validator: checkPointKey,
|
||||||
message: '请输入地址',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: regOnlyNumber,
|
|
||||||
message: '请输入0-255之间的正整数',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
validator: checkAddress,
|
|
||||||
trigger: 'blur',
|
trigger: 'blur',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
@ -76,7 +53,7 @@
|
||||||
<j-input-number
|
<j-input-number
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入地址"
|
placeholder="请输入地址"
|
||||||
v-model:value="formData.configuration.parameter.address"
|
v-model:value="formData.pointKey"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="255"
|
:max="255"
|
||||||
/>
|
/>
|
||||||
|
@ -84,16 +61,7 @@
|
||||||
<j-form-item
|
<j-form-item
|
||||||
label="寄存器数量"
|
label="寄存器数量"
|
||||||
:name="['configuration', 'parameter', 'quantity']"
|
:name="['configuration', 'parameter', 'quantity']"
|
||||||
:rules="[
|
:rules="ModBusRules.quantity"
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入寄存器数量',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: regOnlyNumber,
|
|
||||||
message: '请输入1-255之间的正整数',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<j-input-number
|
<j-input-number
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
@ -104,12 +72,14 @@
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
<j-form-item
|
<j-form-item
|
||||||
|
v-if="formData.configuration.function === 'HoldingRegisters'"
|
||||||
label="数据类型"
|
label="数据类型"
|
||||||
:name="['configuration', 'codec', 'provider']"
|
:name="['configuration', 'codec', 'provider']"
|
||||||
:rules="[
|
:rules="[
|
||||||
|
...ModBusRules.provider,
|
||||||
{
|
{
|
||||||
required: true,
|
validator: checkProvider,
|
||||||
message: '请选择数据类型',
|
trigger: 'change',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
@ -131,31 +101,18 @@
|
||||||
'configuration',
|
'configuration',
|
||||||
'scaleFactor',
|
'scaleFactor',
|
||||||
]"
|
]"
|
||||||
:rules="[
|
:rules="ModBusRules.scaleFactor"
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入缩放因子',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<j-input
|
<j-input-number
|
||||||
|
style="width: 100%"
|
||||||
placeholder="请输入缩放因子"
|
placeholder="请输入缩放因子"
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.configuration.codec.configuration.scaleFactor
|
formData.configuration.codec.configuration.scaleFactor
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
<j-form-item
|
<j-form-item label="访问类型" name="accessModes">
|
||||||
label="访问类型"
|
<!-- <RadioCard
|
||||||
:name="['accessModes']"
|
|
||||||
:rules="[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请选择访问类型',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<RadioCard
|
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
:checkStyle="true"
|
:checkStyle="true"
|
||||||
:options="[
|
:options="[
|
||||||
|
@ -163,42 +120,36 @@
|
||||||
{ label: '写', value: 'write' },
|
{ label: '写', value: 'write' },
|
||||||
]"
|
]"
|
||||||
v-model="formData.accessModes"
|
v-model="formData.accessModes"
|
||||||
/>
|
/> -->
|
||||||
<!-- <j-card-select
|
<j-checkbox-group
|
||||||
v-model:value="formData.accessModes"
|
v-model:value="formData.accessModes"
|
||||||
:options="[
|
:options="[
|
||||||
{
|
{ label: '读', value: 'read' },
|
||||||
label: '读',
|
{ label: '写', value: 'write' },
|
||||||
value: 'read',
|
|
||||||
iconUrl:
|
|
||||||
'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '写',
|
|
||||||
value: 'write',
|
|
||||||
iconUrl:
|
|
||||||
'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
|
|
||||||
},
|
|
||||||
]"
|
]"
|
||||||
multiple
|
/>
|
||||||
/> -->
|
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
|
|
||||||
<!-- <j-form-item label="非标准协议写入配置" :name="['nspwc']"> -->
|
<j-form-item
|
||||||
<j-form-item :name="['nspwc']">
|
:name="['nspwc']"
|
||||||
<span>非标准协议写入配置</span>
|
v-if="
|
||||||
|
formData.accessModes?.includes('write') &&
|
||||||
|
formData.configuration.function === 'HoldingRegisters'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span style="margin-right: 10px">非标准协议写入配置</span>
|
||||||
<j-switch v-model:checked="formData.nspwc" />
|
<j-switch v-model:checked="formData.nspwc" />
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
|
|
||||||
<j-form-item
|
<j-form-item
|
||||||
|
v-if="
|
||||||
|
!!formData.nspwc &&
|
||||||
|
formData.accessModes?.includes('write') &&
|
||||||
|
formData.configuration.function === 'HoldingRegisters'
|
||||||
|
"
|
||||||
label="是否写入数据区长度"
|
label="是否写入数据区长度"
|
||||||
:name="['configuration', 'parameter', 'writeByteCount']"
|
:name="['configuration', 'parameter', 'writeByteCount']"
|
||||||
:rules="[
|
:rules="ModBusRules.writeByteCount"
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请选择是否写入数据区长度',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<RadioCard
|
<RadioCard
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
|
@ -211,14 +162,14 @@
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
<j-form-item
|
<j-form-item
|
||||||
|
v-if="
|
||||||
|
!!formData.nspwc &&
|
||||||
|
formData.accessModes?.includes('write') &&
|
||||||
|
formData.configuration.function === 'HoldingRegisters'
|
||||||
|
"
|
||||||
label="自定义数据区长度(byte)"
|
label="自定义数据区长度(byte)"
|
||||||
:name="['configuration', 'parameter', 'byteCount']"
|
:name="['configuration', 'parameter', 'byteCount']"
|
||||||
:rules="[
|
:rules="ModBusRules.byteCount"
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入自定义数据区长度(byte)',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<j-input
|
<j-input
|
||||||
placeholder="请输入自定义数据区长度(byte)"
|
placeholder="请输入自定义数据区长度(byte)"
|
||||||
|
@ -229,9 +180,10 @@
|
||||||
label="采集频率"
|
label="采集频率"
|
||||||
:name="['configuration', 'interval']"
|
:name="['configuration', 'interval']"
|
||||||
:rules="[
|
:rules="[
|
||||||
|
...ModBusRules.interval,
|
||||||
{
|
{
|
||||||
required: true,
|
validator: checkLength,
|
||||||
message: '请输入采集频率',
|
trigger: 'change',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
@ -240,6 +192,7 @@
|
||||||
placeholder="请输入采集频率"
|
placeholder="请输入采集频率"
|
||||||
v-model:value="formData.configuration.interval"
|
v-model:value="formData.configuration.interval"
|
||||||
:min="1"
|
:min="1"
|
||||||
|
addon-after="ms"
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
|
|
||||||
|
@ -279,22 +232,16 @@
|
||||||
</j-modal>
|
</j-modal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Form } from 'ant-design-vue';
|
|
||||||
import {
|
import {
|
||||||
save,
|
savePointBatch,
|
||||||
update,
|
updatePoint,
|
||||||
_validateField,
|
_validateField,
|
||||||
queryCodecProvider,
|
queryCodecProvider,
|
||||||
} from '@/api/data-collect/collector';
|
} from '@/api/data-collect/collector';
|
||||||
import { Store } from 'jetlinks-store';
|
import { ModBusRules, checkProviderData } from '../../data.ts';
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
const loading = ref(false);
|
import { Rule } from 'ant-design-vue/lib/form';
|
||||||
const useForm = Form.useForm;
|
import { cloneDeep, isArray } from 'lodash-es';
|
||||||
const channelListAll = ref();
|
|
||||||
const channelList = ref();
|
|
||||||
const visibleEndian = ref(false);
|
|
||||||
const visibleUnitId = ref(false);
|
|
||||||
const providerList = ref([]);
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -304,105 +251,72 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['change']);
|
const emit = defineEmits(['change']);
|
||||||
|
const loading = ref(false);
|
||||||
|
const providerList = ref([]);
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
const id = props.data.id;
|
const id = props.data.id;
|
||||||
const treeId = props.data.treeId;
|
const collectorId = props.data.collectorId;
|
||||||
|
const provider = props.data.provider;
|
||||||
|
const oldPointKey = props.data.pointKey;
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
name: '',
|
name: '',
|
||||||
configuration: {
|
configuration: {
|
||||||
function: undefined,
|
function: undefined,
|
||||||
interval: '',
|
interval: 3000,
|
||||||
parameter: {
|
parameter: {
|
||||||
address: '',
|
quantity: 1,
|
||||||
quantity: '',
|
|
||||||
writeByteCount: '',
|
writeByteCount: '',
|
||||||
byteCount: '',
|
byteCount: 2,
|
||||||
|
address: '',
|
||||||
},
|
},
|
||||||
codec: {
|
codec: {
|
||||||
provider: '',
|
provider: undefined,
|
||||||
configuration: {
|
configuration: {
|
||||||
scaleFactor: '',
|
scaleFactor: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
accessModes: undefined,
|
pointKey: '',
|
||||||
nspwc: '',
|
accessModes: [],
|
||||||
byte: '',
|
nspwc: false,
|
||||||
features: '',
|
features: [],
|
||||||
description: '',
|
description: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const regOnlyNumber = new RegExp(/^\d+$/);
|
const onSubmit = async () => {
|
||||||
|
const data = await formRef.value?.validate();
|
||||||
|
|
||||||
const checkAddress = (_rule: Rule, value: string): Promise<any> =>
|
delete data?.nspwc;
|
||||||
new Promise(async (resolve, reject) => {
|
const { codec } = data?.configuration;
|
||||||
if (value) {
|
|
||||||
const res = await _validateField(props.data.treeId, {
|
if (data?.configuration.function !== 'HoldingRegisters') {
|
||||||
pointKey: value,
|
codec.provider = 'int8';
|
||||||
});
|
|
||||||
return res.result.passed ? resolve('') : reject(res.result.reason);
|
|
||||||
}
|
}
|
||||||
});
|
const params = {
|
||||||
|
...props.data,
|
||||||
|
...data,
|
||||||
|
provider,
|
||||||
|
collectorId,
|
||||||
|
};
|
||||||
|
|
||||||
// const { resetFields, validate, validateInfos } = useForm(
|
// address是多余字段,但是react版本上使用到了这个字段
|
||||||
// formData,
|
params.configuration.parameter = {
|
||||||
// reactive({
|
...params.configuration.parameter,
|
||||||
// channelId: [
|
address: data?.pointKey,
|
||||||
// { required: true, message: '请选择所属通道', trigger: 'blur' },
|
};
|
||||||
// ],
|
|
||||||
// name: [
|
loading.value = true;
|
||||||
// { required: true, message: '请输入采集器名称', trigger: 'blur' },
|
const response = !id
|
||||||
// { max: 64, message: '最多可输入64个字符' },
|
? await savePointBatch(params)
|
||||||
// ],
|
: await updatePoint(id, { ...props.data, ...params });
|
||||||
// 'configuration.unitId': [
|
if (response.status === 200) {
|
||||||
// { required: true, message: '请输入从机地址', trigger: 'blur' },
|
emit('change', true);
|
||||||
// {
|
}
|
||||||
// pattern: regOnlyNumber,
|
loading.value = false;
|
||||||
// message: '请输入0-255之间的正整数',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// 'circuitBreaker.type': [
|
|
||||||
// { required: true, message: '请选择处理方式', trigger: 'blur' },
|
|
||||||
// ],
|
|
||||||
// 'configuration.endian': [
|
|
||||||
// { required: true, message: '请选择高低位切换', trigger: 'blur' },
|
|
||||||
// ],
|
|
||||||
// description: [{ max: 200, message: '最多可输入200个字符' }],
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
const onSubmit = () => {
|
|
||||||
// validate()
|
|
||||||
// .then(async (res) => {
|
|
||||||
// const { provider, name } = channelListAll.value.find(
|
|
||||||
// (item) => item.id === formData.value.channelId,
|
|
||||||
// );
|
|
||||||
// const params = {
|
|
||||||
// ...toRaw(formData.value),
|
|
||||||
// provider,
|
|
||||||
// channelName: name,
|
|
||||||
// };
|
|
||||||
// loading.value = true;
|
|
||||||
// const response = !id
|
|
||||||
// ? await save(params)
|
|
||||||
// : await update(id, { ...props.data, ...params });
|
|
||||||
// if (response.status === 200) {
|
|
||||||
// emit('change', true);
|
|
||||||
// }
|
|
||||||
// loading.value = false;
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// loading.value = false;
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTypeTooltip = (value: string) =>
|
|
||||||
value === 'LowerFrequency'
|
|
||||||
? '连续20次异常,降低连接频率至原有频率的1/10(重试间隔不超过1分钟),故障处理后自动恢复至设定连接频率'
|
|
||||||
: value === 'Break'
|
|
||||||
? '连续10分钟异常,停止采集数据进入熔断状态,设备重新启用后恢复采集状态'
|
|
||||||
: '忽略异常,保持原采集频率超时时间为5s';
|
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
onSubmit();
|
onSubmit();
|
||||||
};
|
};
|
||||||
|
@ -410,19 +324,42 @@ const handleCancel = () => {
|
||||||
emit('change', false);
|
emit('change', false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const getChannelNoPaging = async () => {
|
const checkLength = (_rule: Rule, value: string): Promise<any> =>
|
||||||
// channelListAll.value = Store.get('channelListAll');
|
new Promise(async (resolve, reject) => {
|
||||||
// channelList.value = channelListAll.value.map((item) => ({
|
if (value) {
|
||||||
// value: item.id,
|
return String(value).length > 64
|
||||||
// label: item.name,
|
? reject('最多可输入64个字符')
|
||||||
// }));
|
: resolve('');
|
||||||
// };
|
}
|
||||||
// getChannelNoPaging();
|
});
|
||||||
|
|
||||||
|
const checkProvider = (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
if (value) {
|
||||||
|
const { quantity } = formData.value.configuration.parameter;
|
||||||
|
return checkProviderData[value] > Number(quantity) * 2
|
||||||
|
? reject('数据类型长度需 <= 寄存器数量 * 2')
|
||||||
|
: resolve('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkPointKey = (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
if (value) {
|
||||||
|
if (Number(oldPointKey) === Number(value)) return resolve('');
|
||||||
|
const res = await _validateField(collectorId, {
|
||||||
|
pointKey: value,
|
||||||
|
});
|
||||||
|
return res.result.passed ? resolve('') : reject(res.result.reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
const getProviderList = async () => {
|
const getProviderList = async () => {
|
||||||
const res = await queryCodecProvider();
|
const res = await queryCodecProvider();
|
||||||
console.log(222, res.result);
|
|
||||||
|
|
||||||
providerList.value = res.result
|
providerList.value = res.result
|
||||||
.filter((i) => i.id !== 'property')
|
.filter((i) => i.id !== 'property')
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
|
@ -432,20 +369,31 @@ const getProviderList = async () => {
|
||||||
};
|
};
|
||||||
getProviderList();
|
getProviderList();
|
||||||
|
|
||||||
// watch(
|
watch(
|
||||||
// () => formData.value.channelId,
|
() => formData.value.configuration.parameter.quantity,
|
||||||
// (value) => {
|
(value) => {
|
||||||
// const dt = channelListAll.value.find((item) => item.id === value);
|
formData.value.configuration.parameter.byteCount = Number(value) * 2;
|
||||||
// visibleUnitId.value = visibleEndian.value =
|
},
|
||||||
// dt?.provider && dt?.provider === 'MODBUS_TCP';
|
{ immediate: true, deep: true },
|
||||||
// },
|
);
|
||||||
// { deep: true },
|
|
||||||
// );
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (value.id) formData.value = value;
|
if (value.id && value.provider === 'MODBUS_TCP') {
|
||||||
|
const _value = cloneDeep(value);
|
||||||
|
const { writeByteCount, byteCount } =
|
||||||
|
_value.configuration.parameter;
|
||||||
|
formData.value = _value;
|
||||||
|
if (!!_value.accessModes[0]?.value) {
|
||||||
|
formData.value.accessModes = value.accessModes.map(
|
||||||
|
(i) => i.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!!_value.features[0]?.value) {
|
||||||
|
formData.value.features = value.features.map((i) => i.value);
|
||||||
|
}
|
||||||
|
formData.value.nspwc = !!writeByteCount || !!byteCount;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true },
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
<template lang="">
|
||||||
|
<j-modal title="编辑" :visible="true" width="700px" @cancel="handleCancel">
|
||||||
|
<j-form
|
||||||
|
class="form"
|
||||||
|
layout="vertical"
|
||||||
|
:model="formData"
|
||||||
|
name="basic"
|
||||||
|
autocomplete="off"
|
||||||
|
:rules="OPCUARules"
|
||||||
|
ref="formRef"
|
||||||
|
>
|
||||||
|
<j-form-item label="点位名称" name="name">
|
||||||
|
<j-input
|
||||||
|
placeholder="请输入点位名称"
|
||||||
|
v-model:value="formData.name"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item label="数据类型" :name="['configuration', 'type']">
|
||||||
|
<j-select
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="formData.configuration.type"
|
||||||
|
:options="[
|
||||||
|
{ value: 'Number', label: '数值类型' },
|
||||||
|
{ value: 'DateTime', label: '时间类型' },
|
||||||
|
{ value: 'Array', label: '数组类型' },
|
||||||
|
{ value: 'String', label: '文本类型' },
|
||||||
|
{ value: 'Boolean', label: '布尔' },
|
||||||
|
]"
|
||||||
|
placeholder="请选择数据类型"
|
||||||
|
allowClear
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
|
||||||
|
<j-form-item label="访问类型" name="accessModes">
|
||||||
|
<j-checkbox-group
|
||||||
|
v-model:value="formData.accessModes"
|
||||||
|
:options="[
|
||||||
|
{ label: '读', value: 'read' },
|
||||||
|
{ label: '写', value: 'write' },
|
||||||
|
{ label: '订阅', value: 'subscribe' },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item
|
||||||
|
label="采集频率"
|
||||||
|
:name="['configuration', 'interval']"
|
||||||
|
:rules="[
|
||||||
|
...OPCUARules.interval,
|
||||||
|
{
|
||||||
|
validator: checkLength,
|
||||||
|
trigger: 'change',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<j-input-number
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入采集频率"
|
||||||
|
v-model:value="formData.configuration.interval"
|
||||||
|
:min="1"
|
||||||
|
addon-after="ms"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="" :name="['features']">
|
||||||
|
<a-checkbox-group v-model:value="formData.features">
|
||||||
|
<a-checkbox value="changedOnly" name="type"
|
||||||
|
>只推送变化的数据</a-checkbox
|
||||||
|
>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<j-form-item label="说明" :name="['description']">
|
||||||
|
<j-textarea
|
||||||
|
placeholder="请输入说明"
|
||||||
|
v-model:value="formData.description"
|
||||||
|
:maxlength="200"
|
||||||
|
:rows="3"
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
</j-form>
|
||||||
|
<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:${
|
||||||
|
id ? 'update' : 'add'
|
||||||
|
}`"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
savePoint,
|
||||||
|
updatePoint,
|
||||||
|
_validateField,
|
||||||
|
} from '@/api/data-collect/collector';
|
||||||
|
import { OPCUARules, checkProviderData } from '../../data.ts';
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import { Rule } from 'ant-design-vue/lib/form';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
const loading = ref(false);
|
||||||
|
const providerList = ref([]);
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const id = props.data.id;
|
||||||
|
const collectorId = props.data.collectorId;
|
||||||
|
const provider = props.data.provider;
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
name: '',
|
||||||
|
configuration: {
|
||||||
|
type: undefined,
|
||||||
|
interval: 3000,
|
||||||
|
},
|
||||||
|
accessModes: [],
|
||||||
|
features: [],
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const data = await formRef.value?.validate();
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...props.data,
|
||||||
|
...data,
|
||||||
|
provider,
|
||||||
|
collectorId,
|
||||||
|
};
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const response = !id
|
||||||
|
? await savePoint(params)
|
||||||
|
: await updatePoint(id, { ...props.data, ...params });
|
||||||
|
if (response.status === 200) {
|
||||||
|
emit('change', true);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('change', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkLength = (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
if (value) {
|
||||||
|
return String(value).length > 64
|
||||||
|
? reject('最多可输入64个字符')
|
||||||
|
: resolve('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(value) => {
|
||||||
|
if (value.id && value.provider === 'OPC_UA') {
|
||||||
|
const _value = cloneDeep(value);
|
||||||
|
formData.value = _value;
|
||||||
|
if (!!_value.accessModes[0]?.value) {
|
||||||
|
formData.value.accessModes = value.accessModes.map(
|
||||||
|
(i) => i.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!!_value.features[0]?.value) {
|
||||||
|
formData.value.features = value.features.map((i) => i.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.form {
|
||||||
|
.form-radio-button {
|
||||||
|
width: 148px;
|
||||||
|
height: 80px;
|
||||||
|
padding: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-upload-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.form-submit {
|
||||||
|
background-color: @primary-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,274 @@
|
||||||
|
<template>
|
||||||
|
<j-form style="width: 80%" ref="formTableRef" :model="modelRef">
|
||||||
|
<j-table
|
||||||
|
:dataSource="modelRef.dataSource"
|
||||||
|
:columns="FormTableColumns"
|
||||||
|
:scroll="{ x: 1100, y: 500 }"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column: { dataIndex }, record, index }">
|
||||||
|
<template v-if="dataIndex === 'name'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'name']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<j-input
|
||||||
|
v-model:value="record[dataIndex]"
|
||||||
|
placeholder="请输入"
|
||||||
|
allowClear
|
||||||
|
></j-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="dataIndex === 'id'">
|
||||||
|
<a-form-item :name="['dataSource', index, 'id']">
|
||||||
|
<j-input v-model:value="record[dataIndex]" disabled>
|
||||||
|
</j-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dataIndex === 'accessModes'">
|
||||||
|
<a-form-item
|
||||||
|
class="form-item"
|
||||||
|
:name="['dataSource', index, 'accessModes', 'value']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<j-select
|
||||||
|
style="width: 75%"
|
||||||
|
v-model:value="record[dataIndex].value"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
mode="multiple"
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:options="[
|
||||||
|
{ label: '读', value: 'read' },
|
||||||
|
{ label: '写', value: 'write' },
|
||||||
|
{ label: '订阅', value: 'subscribe' },
|
||||||
|
]"
|
||||||
|
:disabled="index !== 0 && record[dataIndex].check"
|
||||||
|
@change="changeValue(index, dataIndex)"
|
||||||
|
>
|
||||||
|
</j-select>
|
||||||
|
<j-checkbox
|
||||||
|
style="margin-left: 5px"
|
||||||
|
v-if="index !== 0"
|
||||||
|
v-model:checked="record[dataIndex].check"
|
||||||
|
@click="changeCheckbox(index, dataIndex)"
|
||||||
|
>同上</j-checkbox
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="dataIndex === 'interval'">
|
||||||
|
<a-form-item
|
||||||
|
class="form-item"
|
||||||
|
:name="[
|
||||||
|
'dataSource',
|
||||||
|
index,
|
||||||
|
'configuration',
|
||||||
|
'interval',
|
||||||
|
'value',
|
||||||
|
]"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<j-input
|
||||||
|
style="width: 70%"
|
||||||
|
v-model:value="
|
||||||
|
record.configuration[dataIndex].value
|
||||||
|
"
|
||||||
|
placeholder="请输入"
|
||||||
|
allowClear
|
||||||
|
addon-after="ms"
|
||||||
|
:disabled="
|
||||||
|
index !== 0 &&
|
||||||
|
record.configuration[dataIndex].check
|
||||||
|
"
|
||||||
|
@blur="changeValue(index, dataIndex)"
|
||||||
|
></j-input>
|
||||||
|
<j-checkbox
|
||||||
|
style="margin-left: 5px"
|
||||||
|
v-show="index !== 0"
|
||||||
|
v-model:checked="
|
||||||
|
record.configuration[dataIndex].check
|
||||||
|
"
|
||||||
|
@click="changeCheckbox(index, dataIndex)"
|
||||||
|
>同上</j-checkbox
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="dataIndex === 'features'">
|
||||||
|
<a-form-item
|
||||||
|
class="form-item"
|
||||||
|
:name="['dataSource', index, 'features', 'value']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<j-select
|
||||||
|
style="width: 50%"
|
||||||
|
v-model:value="record[dataIndex].value"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
label: '是',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '否',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:disabled="index !== 0 && record[dataIndex].check"
|
||||||
|
@change="changeValue(index, dataIndex)"
|
||||||
|
>
|
||||||
|
</j-select>
|
||||||
|
|
||||||
|
<j-checkbox
|
||||||
|
style="margin-left: 5px"
|
||||||
|
v-show="index !== 0"
|
||||||
|
v-model:checked="record[dataIndex].check"
|
||||||
|
@click="changeCheckbox(index, dataIndex)"
|
||||||
|
>同上</j-checkbox
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dataIndex === 'action'">
|
||||||
|
<a-tooltip title="删除">
|
||||||
|
<a-popconfirm
|
||||||
|
title="确认删除"
|
||||||
|
@confirm="clickDelete(record.id)"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</j-table>
|
||||||
|
</j-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { FormTableColumns } from '../../data';
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['change']);
|
||||||
|
|
||||||
|
const formTableRef = ref();
|
||||||
|
const defaultType = ['accessModes', 'interval', 'features'];
|
||||||
|
const modelRef = reactive({
|
||||||
|
dataSource: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickDelete = (value: string) => {
|
||||||
|
emits('change', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTargetData = (index: number, type: string) => {
|
||||||
|
const { dataSource } = modelRef;
|
||||||
|
const Interval = type === 'interval';
|
||||||
|
return !Interval
|
||||||
|
? dataSource[index][type]
|
||||||
|
: dataSource[index].configuration[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeValue = (index: number, type: string) => {
|
||||||
|
const { dataSource } = modelRef;
|
||||||
|
const originData = getTargetData(index, type);
|
||||||
|
for (let i = index + 1; i < dataSource.length; i++) {
|
||||||
|
const targetType = getTargetData(i, type);
|
||||||
|
if (!targetType.check) return;
|
||||||
|
targetType.value = originData.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeCheckbox = (index: number, type: string) => {
|
||||||
|
// console.log(1, getTargetData(index, type).check,getTargetData(index, type));
|
||||||
|
//如果不使用setTimeout,会导致值更新不及时
|
||||||
|
setTimeout(() => {
|
||||||
|
// console.log(2, getTargetData(index, type).check,getTargetData(index, type));
|
||||||
|
let startIndex = 0;
|
||||||
|
const { dataSource } = modelRef;
|
||||||
|
const currentCheck = getTargetData(index, type).check;
|
||||||
|
if (!currentCheck) return;
|
||||||
|
for (let i = index; i >= 0; i--) {
|
||||||
|
const preDatCheck = getTargetData(i, type).check;
|
||||||
|
if (!preDatCheck) {
|
||||||
|
startIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const originData = getTargetData(startIndex, type);
|
||||||
|
for (let i = startIndex; i < dataSource.length - 1; i++) {
|
||||||
|
const targetType = getTargetData(i + 1, type);
|
||||||
|
if (!targetType.check) return;
|
||||||
|
targetType.value = originData.value;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
formTableRef.value
|
||||||
|
.validate()
|
||||||
|
.then(() => {
|
||||||
|
res(modelRef.dataSource);
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
rej(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
defineExpose({
|
||||||
|
validate,
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(value, preValue) => {
|
||||||
|
modelRef.dataSource = value;
|
||||||
|
|
||||||
|
// 有新增时同上数据
|
||||||
|
const vlength = value.length,
|
||||||
|
plength = preValue.length;
|
||||||
|
if (plength !== 0 && plength < vlength) {
|
||||||
|
defaultType.forEach((type) => {
|
||||||
|
vlength === 2
|
||||||
|
? changeValue(0, type)
|
||||||
|
: changeCheckbox(vlength - 1, type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,240 @@
|
||||||
|
<template>
|
||||||
|
<div class="tree-content">
|
||||||
|
<div class="tree-header">
|
||||||
|
<div>数据源</div>
|
||||||
|
<j-checkbox v-model:checked="isSelected">隐藏已有节点</j-checkbox>
|
||||||
|
</div>
|
||||||
|
<j-spin :spinning="spinning">
|
||||||
|
<a-tree
|
||||||
|
v-model:checkedKeys="checkedKeys"
|
||||||
|
:tree-data="treeData"
|
||||||
|
default-expand-all
|
||||||
|
checkable
|
||||||
|
@check="onCheck"
|
||||||
|
:height="600"
|
||||||
|
>
|
||||||
|
<!-- <a-tree
|
||||||
|
:load-data="onLoadData"
|
||||||
|
:tree-data="treeData"
|
||||||
|
v-model:checkedKeys="checkedKeys"
|
||||||
|
checkable
|
||||||
|
@check="onCheck"
|
||||||
|
> -->
|
||||||
|
<template #title="{ name, key }">
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
selectKeys.includes(key)
|
||||||
|
? 'tree-selected'
|
||||||
|
: 'tree-title',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</a-tree>
|
||||||
|
</j-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { TreeProps } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
scanOpcUAList,
|
||||||
|
queryPointNoPaging,
|
||||||
|
} from '@/api/data-collect/collector';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
unSelectKeys: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['change']);
|
||||||
|
|
||||||
|
// const channelId = '1610517801347788800'; //测试
|
||||||
|
const channelId = props.data?.channelId;
|
||||||
|
|
||||||
|
const checkedKeys = ref<string[]>([]);
|
||||||
|
const selectKeys = ref<string[]>([]);
|
||||||
|
const spinning = ref(false);
|
||||||
|
|
||||||
|
const isSelected = ref(false);
|
||||||
|
const treeData = ref<TreeProps['treeData']>();
|
||||||
|
const treeAllData = ref<TreeProps['treeData']>();
|
||||||
|
|
||||||
|
const onLoadData = (node: any) =>
|
||||||
|
new Promise<void>(async (resolve) => {
|
||||||
|
if ((node?.children && node.children?.length) || !node?.folder) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resp = await scanOpcUAList({
|
||||||
|
id: channelId,
|
||||||
|
nodeId: node.key,
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const list = resp.result.map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
key: item.id,
|
||||||
|
title: item.name,
|
||||||
|
disabled: item?.folder,
|
||||||
|
isLeaf: !item?.folder,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
treeAllData.value = updateTreeData(
|
||||||
|
cloneDeep(treeAllData.value),
|
||||||
|
node.key,
|
||||||
|
[...list],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleData = (arr: any[]): any[] => {
|
||||||
|
const data = arr.filter((item) => {
|
||||||
|
return (
|
||||||
|
(isSelected && !selectKeys.value.includes(item.id)) || !isSelected
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return data.map((item) => {
|
||||||
|
if (item.children && item.children?.length) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
children: handleData(item.children),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCheck = (checkedKeys, info) => {
|
||||||
|
const one: any = { ...info.node };
|
||||||
|
const list: any = [];
|
||||||
|
const last: any = list.length ? list[list.length - 1] : undefined;
|
||||||
|
if (list.map((i: any) => i?.id).includes(one.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = {
|
||||||
|
features: {
|
||||||
|
value: last
|
||||||
|
? last?.features?.value
|
||||||
|
: (one?.features || []).includes('changedOnly'),
|
||||||
|
check: true,
|
||||||
|
},
|
||||||
|
id: one?.id || '',
|
||||||
|
name: one?.name || '',
|
||||||
|
accessModes: {
|
||||||
|
value: last ? last?.accessModes?.value : one?.accessModes || [],
|
||||||
|
check: true,
|
||||||
|
},
|
||||||
|
configuration: {
|
||||||
|
...one?.configuration,
|
||||||
|
interval: {
|
||||||
|
value: last
|
||||||
|
? last?.configuration?.interval?.value
|
||||||
|
: one?.configuration?.interval || 3000,
|
||||||
|
check: true,
|
||||||
|
},
|
||||||
|
nodeId: one?.id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
emits('change', item, info.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTreeData = (list: any[], key: string, children: any[]): any[] => {
|
||||||
|
const arr = list.map((node) => {
|
||||||
|
if (node.key === key) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (node?.children && node?.children?.length) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
children: updateTreeData(node.children, key, children),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPoint = async () => {
|
||||||
|
spinning.value = true;
|
||||||
|
const res = await queryPointNoPaging();
|
||||||
|
if (res.status === 200) {
|
||||||
|
selectKeys.value = res.result.map((item: any) => item.pointKey);
|
||||||
|
}
|
||||||
|
spinning.value = false;
|
||||||
|
};
|
||||||
|
getPoint();
|
||||||
|
|
||||||
|
const getScanOpcUAList = async () => {
|
||||||
|
const res = await scanOpcUAList({ id: channelId });
|
||||||
|
treeAllData.value = res.result.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
key: item.id,
|
||||||
|
title: item.name,
|
||||||
|
disabled: item?.folder || false,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
getScanOpcUAList();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => isSelected.value,
|
||||||
|
(value) => {
|
||||||
|
if (value) {
|
||||||
|
treeData.value = handleData(treeAllData.value);
|
||||||
|
} else {
|
||||||
|
treeData.value = treeAllData.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => treeAllData.value,
|
||||||
|
(value) => {
|
||||||
|
if (isSelected.value) {
|
||||||
|
treeData.value = handleData(value);
|
||||||
|
} else {
|
||||||
|
treeData.value = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.unSelectKeys,
|
||||||
|
(value) => {
|
||||||
|
checkedKeys.value = checkedKeys.value.filter((i) => i !== value);
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tree-content {
|
||||||
|
padding: 16px;
|
||||||
|
padding-left: 0;
|
||||||
|
.tree-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.tree-selected {
|
||||||
|
color: #1d39c4;
|
||||||
|
}
|
||||||
|
.tree-title {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,107 @@
|
||||||
|
<template lang="">
|
||||||
|
<j-modal title="扫描" :visible="true" width="90%" @cancel="handleCancel">
|
||||||
|
<div class="content">
|
||||||
|
<Tree
|
||||||
|
:data="treeData"
|
||||||
|
class="tree"
|
||||||
|
@change="changeTree"
|
||||||
|
:unSelectKeys="unSelectKeys"
|
||||||
|
></Tree>
|
||||||
|
<Table
|
||||||
|
:data="tableData"
|
||||||
|
class="table"
|
||||||
|
@change="changeTable"
|
||||||
|
ref="formTableRef"
|
||||||
|
></Table>
|
||||||
|
</div>
|
||||||
|
<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:update`"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import { savePointBatch } from '@/api/data-collect/collector';
|
||||||
|
import { Rule } from 'ant-design-vue/lib/form';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
import Table from './Table.vue';
|
||||||
|
import Tree from './Tree.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const treeData = ref(props.data);
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
const loading = ref(false);
|
||||||
|
const formTableRef = ref<FormInstance>();
|
||||||
|
const tableData = ref();
|
||||||
|
const tableDataMap = new Map();
|
||||||
|
const unSelectKeys = ref();
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const data = await formTableRef.value?.validate();
|
||||||
|
const list = data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
provider: 'OPC_UA',
|
||||||
|
collectorId: props.data?.id,
|
||||||
|
collectorName: props.data?.name,
|
||||||
|
pointKey: item.id,
|
||||||
|
configuration: {
|
||||||
|
interval: item.configuration?.interval?.value,
|
||||||
|
},
|
||||||
|
features: !item.features?.value ? [] : ['changedOnly'],
|
||||||
|
accessModes: item.accessModes?.value || [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(1112, props.data, data, list);
|
||||||
|
const resp = await savePointBatch([...list]);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
emit('change', true);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('change', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTree = (row: any, checked: boolean) => {
|
||||||
|
checked ? tableDataMap.set(row.id, row) : tableDataMap.delete(row.id);
|
||||||
|
tableData.value = [...tableDataMap.values()];
|
||||||
|
};
|
||||||
|
const changeTable = (value: string) => {
|
||||||
|
unSelectKeys.value = value;
|
||||||
|
tableDataMap.delete(value);
|
||||||
|
tableData.value = [...tableDataMap.values()];
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 600px;
|
||||||
|
.tree {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,152 @@
|
||||||
|
<template lang="">
|
||||||
|
<j-modal
|
||||||
|
title="批量编辑"
|
||||||
|
:visible="true"
|
||||||
|
width="700px"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<div class="sizeText">
|
||||||
|
将批量修改{{
|
||||||
|
data.length
|
||||||
|
}}条数据的访问类型、采集频率、只推送变化的数据
|
||||||
|
</div>
|
||||||
|
<j-form
|
||||||
|
class="form"
|
||||||
|
layout="vertical"
|
||||||
|
:model="formData"
|
||||||
|
name="basic"
|
||||||
|
autocomplete="off"
|
||||||
|
ref="formRef"
|
||||||
|
>
|
||||||
|
<j-form-item label="访问类型" name="accessModes">
|
||||||
|
<j-checkbox-group
|
||||||
|
v-model:value="formData.accessModes"
|
||||||
|
:options="[
|
||||||
|
{ label: '读', value: 'read' },
|
||||||
|
{ label: '写', value: 'write' },
|
||||||
|
{ label: '订阅', value: 'subscribe' },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item :name="['interval']">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
采集频率
|
||||||
|
<j-tooltip title="采集频率为0时不执行轮询任务">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</j-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<j-input-number
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入采集频率"
|
||||||
|
v-model:value="formData.interval"
|
||||||
|
:min="1"
|
||||||
|
addon-after="ms"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="" :name="['features']">
|
||||||
|
<a-checkbox-group v-model:value="formData.features">
|
||||||
|
<a-checkbox value="changedOnly" name="type"
|
||||||
|
>只推送变化的数据</a-checkbox
|
||||||
|
>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
</j-form>
|
||||||
|
<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:update`"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import { savePointBatch } from '@/api/data-collect/collector';
|
||||||
|
import { Rule } from 'ant-design-vue/lib/form';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
const loading = ref(false);
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
accessModes: [],
|
||||||
|
interval: '',
|
||||||
|
features: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkLength = (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
if (value) {
|
||||||
|
return String(value).length > 64
|
||||||
|
? reject('最多可输入64个字符')
|
||||||
|
: resolve('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const data = await formRef.value?.validate();
|
||||||
|
const { accessModes, features, interval } = data;
|
||||||
|
const ischange =
|
||||||
|
accessModes.length !== 0 || features.length !== 0 || !!interval;
|
||||||
|
if (ischange) {
|
||||||
|
const params = cloneDeep(props.data);
|
||||||
|
params.forEach((i: any) => {
|
||||||
|
accessModes.length !== 0 && (i.accessModes = data.accessModes);
|
||||||
|
features.length !== 0 && (i.features = data.features);
|
||||||
|
if (!!interval) {
|
||||||
|
i.interval = data.interval;
|
||||||
|
i.configuration = {
|
||||||
|
...i.configuration,
|
||||||
|
interval: data.interval,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loading.value = true;
|
||||||
|
const response = await savePointBatch(params);
|
||||||
|
if (response.status === 200) {
|
||||||
|
emit('change', true);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
} else {
|
||||||
|
emit('change', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('change', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.sizeText {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div
|
<div class="card-warp" :class="{ active: active ? 'active' : '' }">
|
||||||
class="card-warp"
|
|
||||||
:class="{ active: active ? 'active' : '' }"
|
|
||||||
@click="handleClick"
|
|
||||||
>
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-header-left">
|
<div class="card-header-left">
|
||||||
|
@ -15,7 +11,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex">
|
<div
|
||||||
|
class="card-item"
|
||||||
|
style="display: flex"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div class="card-item-avatar">
|
<div class="card-item-avatar">
|
||||||
<slot name="img"> </slot>
|
<slot name="img"> </slot>
|
||||||
|
@ -169,7 +169,7 @@ const handleClick = () => {
|
||||||
border: 1px solid #e6e6e6;
|
border: 1px solid #e6e6e6;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
box-shadow: 0 0 24px rgba(#000, 0.1);
|
box-shadow: 0 0 24px rgba(#000, 0.1);
|
||||||
|
|
||||||
.card-mask {
|
.card-mask {
|
||||||
|
@ -214,6 +214,9 @@ const handleClick = () => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.card-item-avatar {
|
.card-item-avatar {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
<template lang="">
|
||||||
|
<j-modal title="写入" :visible="true" width="500px" @cancel="handleCancel">
|
||||||
|
<j-form
|
||||||
|
class="form"
|
||||||
|
layout="vertical"
|
||||||
|
:model="formData"
|
||||||
|
name="basic"
|
||||||
|
autocomplete="off"
|
||||||
|
ref="formRef"
|
||||||
|
>
|
||||||
|
<j-form-item
|
||||||
|
:label="data.name"
|
||||||
|
name="value"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: `请输入${data.name}`,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
v-if="
|
||||||
|
data.provider === 'MODBUS_TCP' &&
|
||||||
|
data?.configuration.function === 'Coils'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<j-textarea
|
||||||
|
placeholder="请输入"
|
||||||
|
v-model:value="formData.value"
|
||||||
|
:maxlength="200"
|
||||||
|
:rows="3"
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item
|
||||||
|
:label="data.name"
|
||||||
|
name="value"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: `请输入${data.name}`,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<j-input-number
|
||||||
|
v-if="valueTypeArray.includes(valueType)"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入"
|
||||||
|
v-model:value="formData.value"
|
||||||
|
/>
|
||||||
|
<j-select
|
||||||
|
v-else-if="['boolean'].includes(valueType)"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="formData.value"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
label: '是',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '否',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
/>
|
||||||
|
<j-date-picker
|
||||||
|
v-else-if="['datetime'].includes(valueType)"
|
||||||
|
style="width: 100%"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
show-time
|
||||||
|
placeholder="请选择"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<j-input
|
||||||
|
v-else
|
||||||
|
placeholder="请输入"
|
||||||
|
v-model:value="formData.value"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
</j-form>
|
||||||
|
<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:update`"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
import { writePoint } from '@/api/data-collect/collector';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueType: string = (
|
||||||
|
props.data?.provider === 'OPC_UA'
|
||||||
|
? props?.data?.configuration?.type || 'Number'
|
||||||
|
: props.data?.configuration?.codec?.provider || 'int8'
|
||||||
|
).toLocaleLowerCase();
|
||||||
|
|
||||||
|
const valueTypeArray = [
|
||||||
|
'int8',
|
||||||
|
'int16',
|
||||||
|
'int32',
|
||||||
|
'ieee754_float',
|
||||||
|
'ieee754_double',
|
||||||
|
'hex,',
|
||||||
|
'number',
|
||||||
|
];
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
const loading = ref(false);
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const collectorId = props.data.collectorId;
|
||||||
|
const pointId = props.data.id;
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onChange = (value: Dayjs, dateString: string) => {
|
||||||
|
formData.value.value = dateString;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const data = await formRef.value?.validate();
|
||||||
|
const params = {
|
||||||
|
...data,
|
||||||
|
pointId,
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
const response = await writePoint(collectorId, [params]);
|
||||||
|
if (response.status === 200) {
|
||||||
|
emit('change', true);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('change', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<j-spin :spinning="spinning">
|
||||||
<j-advanced-search
|
<j-advanced-search
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
target="search"
|
target="search"
|
||||||
|
@ -13,9 +13,7 @@
|
||||||
:gridColumn="2"
|
:gridColumn="2"
|
||||||
:gridColumns="[1, 2]"
|
:gridColumns="[1, 2]"
|
||||||
:request="queryPoint"
|
:request="queryPoint"
|
||||||
:defaultParams="{
|
:defaultParams="defaultParams"
|
||||||
sorts: [{ name: 'id', order: 'desc' }],
|
|
||||||
}"
|
|
||||||
:params="params"
|
:params="params"
|
||||||
:rowSelection="{
|
:rowSelection="{
|
||||||
selectedRowKeys: _selectedRowKeys,
|
selectedRowKeys: _selectedRowKeys,
|
||||||
|
@ -26,12 +24,23 @@
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<j-space>
|
<j-space>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
|
v-if="data?.provider !== 'OPC_UA'"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handlAdd"
|
@click="handlAdd"
|
||||||
hasPermission="DataCollect/Collector:add"
|
hasPermission="DataCollect/Collector:add"
|
||||||
>
|
>
|
||||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||||
{{ data?.provider === 'OPC_UA' ? '扫描' : '新增点位' }}
|
新增点位
|
||||||
|
</PermissionButton>
|
||||||
|
|
||||||
|
<PermissionButton
|
||||||
|
v-if="data?.provider === 'OPC_UA'"
|
||||||
|
type="primary"
|
||||||
|
@click="handlScan"
|
||||||
|
hasPermission="DataCollect/Collector:add"
|
||||||
|
>
|
||||||
|
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||||
|
扫描
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
<j-dropdown v-if="data?.provider === 'OPC_UA'">
|
<j-dropdown v-if="data?.provider === 'OPC_UA'">
|
||||||
<j-button
|
<j-button
|
||||||
|
@ -42,6 +51,7 @@
|
||||||
<j-menu-item>
|
<j-menu-item>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
hasPermission="DataCollect/Collector:update"
|
hasPermission="DataCollect/Collector:update"
|
||||||
|
@click="handlBatchUpdate()"
|
||||||
>
|
>
|
||||||
<template #icon
|
<template #icon
|
||||||
><AIcon type="FormOutlined"
|
><AIcon type="FormOutlined"
|
||||||
|
@ -52,6 +62,10 @@
|
||||||
<j-menu-item>
|
<j-menu-item>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
hasPermission="DataCollect/Collector:delete"
|
hasPermission="DataCollect/Collector:delete"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `确定删除?`,
|
||||||
|
onConfirm: () => handlDelete(),
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<template #icon
|
<template #icon
|
||||||
><AIcon type="EditOutlined"
|
><AIcon type="EditOutlined"
|
||||||
|
@ -75,17 +89,6 @@
|
||||||
:statusText="getState(slotProps)?.text"
|
:statusText="getState(slotProps)?.text"
|
||||||
:statusNames="Object.fromEntries(colorMap.entries())"
|
:statusNames="Object.fromEntries(colorMap.entries())"
|
||||||
>
|
>
|
||||||
<!-- <PointCardBox
|
|
||||||
:showStatus="true"
|
|
||||||
:value="slotProps"
|
|
||||||
:actions="getActions(slotProps)"
|
|
||||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
|
||||||
v-bind="slotProps"
|
|
||||||
class="card-box"
|
|
||||||
:status="getState(slotProps).value"
|
|
||||||
:statusText="slotProps.runningState?.text"
|
|
||||||
:statusNames="Object.fromEntries(colorMap.entries())"
|
|
||||||
> -->
|
|
||||||
<template #title>
|
<template #title>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
<div class="card-box-title">
|
<div class="card-box-title">
|
||||||
|
@ -95,8 +98,19 @@
|
||||||
</template>
|
</template>
|
||||||
<template #action>
|
<template #action>
|
||||||
<div class="card-box-action">
|
<div class="card-box-action">
|
||||||
<a><AIcon type="DeleteOutlined" /></a>
|
<a>
|
||||||
<a><AIcon type="FormOutlined" /></a>
|
<j-popconfirm
|
||||||
|
title="确定删除?"
|
||||||
|
@confirm="handlDelete(slotProps.id)"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</j-popconfirm>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
><AIcon
|
||||||
|
@click="handlEdit(slotProps)"
|
||||||
|
type="FormOutlined"
|
||||||
|
/></a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #img>
|
<template #img>
|
||||||
|
@ -112,8 +126,26 @@
|
||||||
<div class="card-box-content">
|
<div class="card-box-content">
|
||||||
<div class="card-box-content-left">
|
<div class="card-box-content-left">
|
||||||
<span>--</span>
|
<span>--</span>
|
||||||
<a><AIcon type="EditOutlined" /></a>
|
<a
|
||||||
<a><AIcon type="RedoOutlined" /></a>
|
v-if="
|
||||||
|
getAccessModes(slotProps).includes(
|
||||||
|
'write',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
><AIcon
|
||||||
|
@click.stop="clickEdit(slotProps)"
|
||||||
|
type="EditOutlined"
|
||||||
|
/></a>
|
||||||
|
<a
|
||||||
|
v-if="
|
||||||
|
getAccessModes(slotProps).includes(
|
||||||
|
'read',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
><AIcon
|
||||||
|
@click.stop="clickRedo(slotProps)"
|
||||||
|
type="RedoOutlined"
|
||||||
|
/></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-box-content-right">
|
<div class="card-box-content-right">
|
||||||
<div
|
<div
|
||||||
|
@ -135,21 +167,46 @@
|
||||||
</template>
|
</template>
|
||||||
</j-pro-table>
|
</j-pro-table>
|
||||||
<SaveModBus
|
<SaveModBus
|
||||||
v-if="visibleSaveModBus"
|
v-if="visible.saveModBus"
|
||||||
:data="current"
|
:data="current"
|
||||||
@change="saveChange"
|
@change="saveChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
<SaveOPCUA
|
||||||
|
v-if="visible.saveOPCUA"
|
||||||
|
:data="current"
|
||||||
|
@change="saveChange"
|
||||||
|
/>
|
||||||
|
<WritePoint
|
||||||
|
v-if="visible.writePoint"
|
||||||
|
:data="current"
|
||||||
|
@change="saveChange"
|
||||||
|
/>
|
||||||
|
<BatchUpdate
|
||||||
|
v-if="visible.batchUpdate"
|
||||||
|
:data="current"
|
||||||
|
@change="saveChange"
|
||||||
|
/>
|
||||||
|
<Scan v-if="visible.scan" :data="current" @change="saveChange" />
|
||||||
|
</j-spin>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup name="PointPage">
|
<script lang="ts" setup name="PointPage">
|
||||||
import type { ActionsType } from '@/components/Table/index.vue';
|
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import { queryPoint } from '@/api/data-collect/collector';
|
import {
|
||||||
|
queryPoint,
|
||||||
|
batchDeletePoint,
|
||||||
|
removePoint,
|
||||||
|
readPoint,
|
||||||
|
} from '@/api/data-collect/collector';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { useMenuStore } from 'store/menu';
|
import { useMenuStore } from 'store/menu';
|
||||||
import PointCardBox from './components/PointCardBox/index.vue';
|
import PointCardBox from './components/PointCardBox/index.vue';
|
||||||
import { colorMap, getState } from '../data.ts';
|
import WritePoint from './components/WritePoint/index.vue';
|
||||||
|
import BatchUpdate from './components/BatchUpdate/index.vue';
|
||||||
import SaveModBus from './Save/SaveModBus.vue';
|
import SaveModBus from './Save/SaveModBus.vue';
|
||||||
|
import SaveOPCUA from './Save/SaveOPCUA.vue';
|
||||||
|
import Scan from './Scan/index.vue';
|
||||||
|
import { colorMap, getState } from '../data.ts';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -163,11 +220,35 @@ const tableRef = ref<Record<string, any>>({});
|
||||||
const params = ref<Record<string, any>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
const opcImage = getImage('/DataCollect/device-opcua.png');
|
const opcImage = getImage('/DataCollect/device-opcua.png');
|
||||||
const modbusImage = getImage('/DataCollect/device-modbus.png');
|
const modbusImage = getImage('/DataCollect/device-modbus.png');
|
||||||
const visibleSaveModBus = ref(false);
|
const visible = reactive({
|
||||||
|
saveModBus: false,
|
||||||
|
saveOPCUA: false,
|
||||||
|
writePoint: false,
|
||||||
|
batchUpdate: false,
|
||||||
|
scan: false,
|
||||||
|
});
|
||||||
const current = ref({});
|
const current = ref({});
|
||||||
const accessModesOption = ref();
|
const accessModesOption = ref();
|
||||||
const _selectedRowKeys = ref<string[]>([]);
|
const _selectedRowKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
const spinning = ref(false);
|
||||||
|
const collectorId = ref(props.data.id);
|
||||||
|
|
||||||
|
const defaultParams = ref({
|
||||||
|
sorts: [{ name: 'id', order: 'desc' }],
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'collectorId',
|
||||||
|
value: collectorId.value,
|
||||||
|
// value: '1610517928766550016', //测试
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const accessModesMODBUS_TCP = [
|
const accessModesMODBUS_TCP = [
|
||||||
{
|
{
|
||||||
label: '读',
|
label: '读',
|
||||||
|
@ -179,11 +260,6 @@ const accessModesMODBUS_TCP = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const accessModesOPC_UA = accessModesMODBUS_TCP.concat({
|
|
||||||
label: '订阅',
|
|
||||||
value: 'subscribe',
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '点位名称',
|
title: '点位名称',
|
||||||
|
@ -256,81 +332,57 @@ const columns = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|
||||||
// if (!data) return [];
|
|
||||||
// const state = data.state.value;
|
|
||||||
// const stateText = state === 'enabled' ? '禁用' : '启用';
|
|
||||||
// const actions = [
|
|
||||||
// {
|
|
||||||
// key: 'update',
|
|
||||||
// text: '编辑',
|
|
||||||
// tooltip: {
|
|
||||||
// title: '编辑',
|
|
||||||
// },
|
|
||||||
// icon: 'EditOutlined',
|
|
||||||
// onClick: () => {
|
|
||||||
// handlEdit(data.id);
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'action',
|
|
||||||
// text: stateText,
|
|
||||||
// tooltip: {
|
|
||||||
// title: stateText,
|
|
||||||
// },
|
|
||||||
// icon: state === 'enabled' ? 'StopOutlined' : 'CheckCircleOutlined',
|
|
||||||
// popConfirm: {
|
|
||||||
// title: `确认${stateText}?`,
|
|
||||||
// onConfirm: async () => {
|
|
||||||
// let res =
|
|
||||||
// state === 'enabled'
|
|
||||||
// ? await disable(data.id)
|
|
||||||
// : await enalbe(data.id);
|
|
||||||
// if (res.success) {
|
|
||||||
// message.success('操作成功');
|
|
||||||
// tableRef.value?.reload();
|
|
||||||
// } else {
|
|
||||||
// message.error('操作失败!');
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'delete',
|
|
||||||
// text: '删除',
|
|
||||||
// disabled: state === 'enabled',
|
|
||||||
// tooltip: {
|
|
||||||
// title: state === 'enabled' ? '正常的流媒体不能删除' : '删除',
|
|
||||||
// },
|
|
||||||
// popConfirm: {
|
|
||||||
// title: '确认删除?',
|
|
||||||
// onConfirm: async () => {
|
|
||||||
// const res = await remove(data.id);
|
|
||||||
// if (res.success) {
|
|
||||||
// message.success('操作成功');
|
|
||||||
// tableRef.value.reload();
|
|
||||||
// } else {
|
|
||||||
// message.error('操作失败!');
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// icon: 'DeleteOutlined',
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
// return actions;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handlAdd = () => {
|
const handlAdd = () => {
|
||||||
visibleSaveModBus.value = true;
|
visible.saveModBus = true;
|
||||||
current.value = {
|
current.value = {
|
||||||
treeId: props.data.id,
|
collectorId: collectorId.value,
|
||||||
|
provider: props.data?.provider || 'MODBUS_TCP',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const handlEdit = (id: string) => {
|
const handlEdit = (data: Object) => {
|
||||||
// menuStory.jumpPage(`media/Stream/Detail`, { id }, { view: false });
|
if (data?.provider === 'OPC_UA') {
|
||||||
|
visible.saveOPCUA = true;
|
||||||
|
} else {
|
||||||
|
visible.saveModBus = true;
|
||||||
|
}
|
||||||
|
current.value = cloneDeep(data);
|
||||||
};
|
};
|
||||||
const handlEye = (id: string) => {
|
const handlDelete = async (data: string | undefined = undefined) => {
|
||||||
// menuStory.jumpPage(`media/Stream/Detail`, { id }, { view: true });
|
spinning.value = true;
|
||||||
|
const res = !data
|
||||||
|
? await batchDeletePoint(_selectedRowKeys.value)
|
||||||
|
: await removePoint(data as string);
|
||||||
|
if (res.status === 200) {
|
||||||
|
cancelSelect();
|
||||||
|
tableRef.value?.reload();
|
||||||
|
message.success('操作成功');
|
||||||
|
}
|
||||||
|
spinning.value = false;
|
||||||
|
};
|
||||||
|
const handlBatchUpdate = () => {
|
||||||
|
const dataSet = new Set(_selectedRowKeys.value);
|
||||||
|
const dataMap = new Map();
|
||||||
|
tableRef?.value?._dataSource.forEach((i) => {
|
||||||
|
dataSet.has(i.id) && dataMap.set(i.id, i);
|
||||||
|
});
|
||||||
|
current.value = [...dataMap.values()];
|
||||||
|
visible.batchUpdate = true;
|
||||||
|
};
|
||||||
|
const handlScan = () => {
|
||||||
|
visible.scan = true;
|
||||||
|
current.value = cloneDeep(props.data);
|
||||||
|
};
|
||||||
|
const clickEdit = async (data: object) => {
|
||||||
|
visible.writePoint = true;
|
||||||
|
current.value = cloneDeep(data);
|
||||||
|
};
|
||||||
|
const clickRedo = async (data: object) => {
|
||||||
|
const res = await readPoint(data?.collectorId, [data?.id]);
|
||||||
|
if (res.status === 200) {
|
||||||
|
cancelSelect();
|
||||||
|
tableRef.value?.reload();
|
||||||
|
message.success('操作成功');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getQuantity = (item: Partial<Record<string, any>>) => {
|
const getQuantity = (item: Partial<Record<string, any>>) => {
|
||||||
|
@ -345,11 +397,9 @@ const getScaleFactor = (item: Partial<Record<string, any>>) => {
|
||||||
const { scaleFactor } = item.configuration?.codec?.configuration || '';
|
const { scaleFactor } = item.configuration?.codec?.configuration || '';
|
||||||
return !!scaleFactor ? scaleFactor + '(缩放因子)' : '';
|
return !!scaleFactor ? scaleFactor + '(缩放因子)' : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRight1 = (item: Partial<Record<string, any>>) => {
|
const getRight1 = (item: Partial<Record<string, any>>) => {
|
||||||
return !!getQuantity(item) || getAddress(item) || getScaleFactor(item);
|
return !!getQuantity(item) || getAddress(item) || getScaleFactor(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getText = (item: Partial<Record<string, any>>) => {
|
const getText = (item: Partial<Record<string, any>>) => {
|
||||||
return (item?.accessModes || []).map((i) => i?.text).join(',');
|
return (item?.accessModes || []).map((i) => i?.text).join(',');
|
||||||
};
|
};
|
||||||
|
@ -357,22 +407,18 @@ const getInterval = (item: Partial<Record<string, any>>) => {
|
||||||
const { interval } = item.configuration || '';
|
const { interval } = item.configuration || '';
|
||||||
return !!interval ? '采集频率' + interval + 'ms' : '';
|
return !!interval ? '采集频率' + interval + 'ms' : '';
|
||||||
};
|
};
|
||||||
|
const getAccessModes = (item: Partial<Record<string, any>>) => {
|
||||||
const getaccessModesOption = () => {
|
return item?.accessModes?.map((i) => i?.value);
|
||||||
return props.data?.provider !== 'MODBUS_TCP'
|
|
||||||
? accessModesMODBUS_TCP.concat({
|
|
||||||
label: '订阅',
|
|
||||||
value: 'subscribe',
|
|
||||||
})
|
|
||||||
: accessModesMODBUS_TCP;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveChange = (value: object) => {
|
const saveChange = (value: object) => {
|
||||||
visibleSaveModBus.value = false;
|
for (let key in visible) {
|
||||||
|
visible[key] = false;
|
||||||
|
}
|
||||||
current.value = {};
|
current.value = {};
|
||||||
if (value) {
|
if (value) {
|
||||||
handleSearch(params.value);
|
tableRef.value?.reload();
|
||||||
// message.success('操作成功');
|
message.success('操作成功');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -404,22 +450,12 @@ watch(
|
||||||
label: '订阅',
|
label: '订阅',
|
||||||
value: 'subscribe',
|
value: 'subscribe',
|
||||||
});
|
});
|
||||||
|
defaultParams.value.terms[0].terms[0].value = value.id;
|
||||||
params.value = {
|
// defaultParams.value.terms[0].terms[0].value = '1610517928766550016'; //测试
|
||||||
terms: [
|
tableRef?.value?.reload && tableRef?.value?.reload();
|
||||||
{
|
|
||||||
terms: [
|
|
||||||
{
|
|
||||||
column: 'collectorId',
|
|
||||||
value: value.id,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true },
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -435,9 +471,11 @@ const handleSearch = (e: any) => {
|
||||||
min-width: 480px;
|
min-width: 480px;
|
||||||
a {
|
a {
|
||||||
color: #474747;
|
color: #474747;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #315efb;
|
color: #315efb;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.card-box-title {
|
.card-box-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
@ -459,7 +497,10 @@ const handleSearch = (e: any) => {
|
||||||
height: 68px;
|
height: 68px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
|
a {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.card-box-content-right {
|
.card-box-content-right {
|
||||||
flex: 0.8;
|
flex: 0.8;
|
||||||
|
|
|
@ -201,7 +201,6 @@ const onSubmit = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const response = !id
|
const response = !id
|
||||||
? await save(params)
|
? await save(params)
|
||||||
: await update(id, { ...props.data, ...params });
|
: await update(id, { ...props.data, ...params });
|
||||||
|
@ -238,6 +237,10 @@ const getChannelNoPaging = async () => {
|
||||||
};
|
};
|
||||||
getChannelNoPaging();
|
getChannelNoPaging();
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => formData.value.channelId,
|
() => formData.value.channelId,
|
||||||
(value) => {
|
(value) => {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
新增采集器
|
新增采集器
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
</div>
|
</div>
|
||||||
<a-spin :spinning="spinning">
|
<j-spin :spinning="spinning">
|
||||||
<a-tree
|
<a-tree
|
||||||
:tree-data="defualtDataSource"
|
:tree-data="defualtDataSource"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-tree>
|
</a-tree>
|
||||||
<j-empty v-else description="暂无数据" />
|
<j-empty v-else description="暂无数据" />
|
||||||
</a-spin>
|
</j-spin>
|
||||||
<Save v-if="visible" :data="current" @change="saveChange" />
|
<Save v-if="visible" :data="current" @change="saveChange" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -16,3 +16,166 @@ export const getState = (record: any) => {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const regOnlyNumber = new RegExp(/^\d+$/);
|
||||||
|
|
||||||
|
export const checkProviderData = {
|
||||||
|
int8: 1,
|
||||||
|
int16: 2,
|
||||||
|
int32: 4,
|
||||||
|
int64: 8,
|
||||||
|
ieee754_float: 4,
|
||||||
|
ieee754_double: 8,
|
||||||
|
hex: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModBusRules = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入点位名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多可输入64个字符',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
function: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择功能码',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pointKey: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入地址',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: regOnlyNumber,
|
||||||
|
message: '请输入0-255之间的正整数',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
quantity: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入寄存器数量',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: regOnlyNumber,
|
||||||
|
message: '请输入1-255之间的正整数',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
provider: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择数据类型',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scaleFactor: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入缩放因子',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessModes: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择访问类型',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
writeByteCount: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择是否写入数据区长度',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
byteCount: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入自定义数据区长度(byte)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interval: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入采集频率',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OPCUARules = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入点位名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多可输入64个字符',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择数据类型',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessModes: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择访问类型',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interval: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入采集频率',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormTableColumns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: 200,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'nodeId',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '访问类型',
|
||||||
|
dataIndex: 'accessModes',
|
||||||
|
key: 'accessModes',
|
||||||
|
width: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '采集频率',
|
||||||
|
key: 'interval',
|
||||||
|
dataIndex: 'interval',
|
||||||
|
width: 280,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '只推送变化的数据',
|
||||||
|
key: 'features',
|
||||||
|
dataIndex: 'features',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
dataIndex: 'action',
|
||||||
|
|
||||||
|
fixed: 'right',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<Tree @change="changeTree" />
|
<Tree @change="changeTree" />
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<Point :data="data"></Point>
|
<Point v-if="!!data" :data="data" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</page-container>
|
</page-container>
|
||||||
|
@ -32,7 +32,6 @@ const changeTree = (row: any) => {
|
||||||
.left {
|
.left {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
border-right: 1px #eeeeee solid;
|
border-right: 1px #eeeeee solid;
|
||||||
// padding: 10px;
|
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
|
|
Loading…
Reference in New Issue