Commit 6f017f16daee328ad9f4ad579daeeca82575b023

Authored by xp.Huang
1 parent e6ba961f

merge Main dev main

Showing 100 changed files with 2670 additions and 327 deletions

Too many changes to show.

To preserve performance only 100 of 167 files are displayed.

... ... @@ -13,7 +13,7 @@
13 13 name="viewport"
14 14 content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
15 15 />
16   - <title> <%= title %> </title>
  16 + <title></title>
17 17 <link rel="icon" href="/favicon.ico" />
18 18 </head>
19 19
... ...
... ... @@ -126,6 +126,10 @@ export interface MasterDeviceList {
126 126 name: string;
127 127 label?: string;
128 128 value?: string;
  129 + alias?: string;
  130 + codeType?: string;
  131 + code?: string;
  132 + [key: string]: string | undefined;
129 133 }
130 134
131 135 export interface ComponentInfoDetail {
... ...
... ... @@ -10,6 +10,7 @@ export const batchImportDevice = (data: ImportDeviceParams) => {
10 10 {
11 11 url: BatchImportApi.IMPORT,
12 12 data,
  13 + timeout: 1000 * 60 * 60,
13 14 },
14 15 { joinPrefix: false }
15 16 );
... ...
... ... @@ -166,6 +166,8 @@ export interface DeviceRecord {
166 166 id: string;
167 167 creator: string;
168 168 createTime: string;
  169 + codeType?: string;
  170 + code?: string;
169 171 name: string;
170 172 tenantId: string;
171 173 transportType: string;
... ... @@ -199,6 +201,8 @@ export interface DeviceModelOfMatterAttrs {
199 201 identifier: string;
200 202 accessMode: string;
201 203 detail: StructJSON;
  204 + deviceDetail?: DeviceRecord;
  205 + extensionDesc?: any;
202 206 }
203 207
204 208 export interface DeviceStateLogModel {
... ...
... ... @@ -51,6 +51,7 @@ export interface ModelOfMatterParams {
51 51 callType?: string;
52 52 eventType?: string;
53 53 accessMode?: string;
  54 + extensionDesc?: Recordable;
54 55 }
55 56
56 57 export interface GetModelTslParams {
... ...
... ... @@ -59,8 +59,8 @@ export interface GenModbusCommandType {
59 59 crc: string;
60 60 deviceCode: string;
61 61 method: string;
62   - registerAddr: string;
63   - registerNum?: number;
  62 + registerAddress: string;
  63 + registerNumber?: number;
64 64 registerValues?: number[];
65 65 }
66 66
... ...
... ... @@ -39,6 +39,8 @@ import CustomMinMaxInput from './externalCompns/components/CustomMinMaxInput.vue
39 39 import StructForm from './externalCompns/components/StructForm/StructForm.vue';
40 40 import ApiSelectScrollLoad from './components/ApiSelectScrollLoad.vue';
41 41 import InputGroup from './components/InputGroup.vue';
  42 +import RegisterAddressInput from '/@/views/task/center/components/PollCommandInput/RegisterAddressInput.vue';
  43 +import ExtendDesc from '/@/components/Form/src/externalCompns/components/ExtendDesc/index.vue';
42 44
43 45 const componentMap = new Map<ComponentType, Component>();
44 46
... ... @@ -85,6 +87,8 @@ componentMap.set('CustomMinMaxInput', CustomMinMaxInput);
85 87 componentMap.set('StructForm', StructForm);
86 88 componentMap.set('ApiSelectScrollLoad', ApiSelectScrollLoad);
87 89 componentMap.set('InputGroup', InputGroup);
  90 +componentMap.set('RegisterAddressInput', RegisterAddressInput);
  91 +componentMap.set('ExtendDesc', ExtendDesc);
88 92
89 93 export function add(compName: ComponentType, component: Component) {
90 94 componentMap.set(compName, component);
... ...
  1 +import { findDictItemByCode } from '/@/api/system/dict';
  2 +import { FormSchema } from '/@/components/Table';
  3 +import { DictEnum } from '/@/enums/dictEnum';
  4 +
  5 +export enum FormFieldsEnum {
  6 + REGISTER_ADDRESS = 'registerAddress',
  7 + DATA_TYPE = 'dataType',
  8 + ACTION_TYPE = 'actionType',
  9 + ZOOM_FACTOR = 'zoomFactor',
  10 +}
  11 +
  12 +export const formSchemas: FormSchema[] = [
  13 + {
  14 + field: FormFieldsEnum.REGISTER_ADDRESS,
  15 + component: 'RegisterAddressInput',
  16 + label: '寄存器地址',
  17 + changeEvent: 'update:value',
  18 + valueField: 'value',
  19 + rules: [{ message: '请输入寄存器地址', required: true, type: 'number' }],
  20 + componentProps: {
  21 + placeholder: '请输入寄存器地址',
  22 + },
  23 + },
  24 + {
  25 + field: FormFieldsEnum.DATA_TYPE,
  26 + component: 'ApiSelect',
  27 + label: '数据格式',
  28 + rules: [{ message: '请选择数据格式', required: true }],
  29 + componentProps: {
  30 + api: findDictItemByCode,
  31 + params: {
  32 + dictCode: DictEnum.REGISTER_DATA_FORMAT,
  33 + },
  34 + labelField: 'itemText',
  35 + valueField: 'itemValue',
  36 + placeholder: '请选择数据格式',
  37 + getPopupContainer: () => document.body,
  38 + },
  39 + },
  40 + {
  41 + field: FormFieldsEnum.ACTION_TYPE,
  42 + component: 'Select',
  43 + label: '操作类型',
  44 + rules: [{ message: '请选择操作类型', required: true }],
  45 + componentProps: {
  46 + options: [
  47 + { label: '05 写入单个线圈寄存器', value: '05' },
  48 + { label: '06 写入单个保持寄存器', value: '06' },
  49 + // { label: '0F 写入多个线圈状态', value: '0F' },
  50 + { label: '16 写入多个保持寄存器', value: '16' },
  51 + ],
  52 + placeholder: '请选择操作类型',
  53 + getPopupContainer: () => document.body,
  54 + },
  55 + },
  56 + {
  57 + field: FormFieldsEnum.ZOOM_FACTOR,
  58 + component: 'InputNumber',
  59 + label: '缩放因子',
  60 + helpMessage: ['缩放因子不能为0,默认为1'],
  61 + defaultValue: 1,
  62 + componentProps: {
  63 + min: 1,
  64 + placeholder: '请输入缩放因子',
  65 + },
  66 + },
  67 +];
... ...
  1 +<script lang="ts" setup>
  2 + import { Button } from 'ant-design-vue';
  3 + import { nextTick, ref, watch } from 'vue';
  4 + import { BasicForm, useForm } from '/@/components/Form';
  5 + import { BasicModal } from '/@/components/Modal';
  6 + import { PlusCircleOutlined } from '@ant-design/icons-vue';
  7 + import { FormFieldsEnum, formSchemas } from './config';
  8 + import { DataTypeEnum } from '../StructForm/config';
  9 + const show = ref(false);
  10 +
  11 + const props = withDefaults(
  12 + defineProps<{
  13 + value?: object;
  14 + disabled?: boolean;
  15 + dataType?: DataTypeEnum;
  16 + }>(),
  17 + {
  18 + value: () => ({}),
  19 + }
  20 + );
  21 +
  22 + const emit = defineEmits(['update:value']);
  23 +
  24 + const [
  25 + registerForm,
  26 + { setFieldsValue, getFieldsValue, setProps, validate, resetFields, updateSchema },
  27 + ] = useForm({
  28 + schemas: formSchemas,
  29 + showActionButtonGroup: false,
  30 + });
  31 +
  32 + const handleClick = async () => {
  33 + show.value = true;
  34 + await nextTick();
  35 + resetFields();
  36 + setProps({ disabled: props.disabled });
  37 + setFieldsValue(props.value);
  38 + };
  39 +
  40 + const handleSubmit = async () => {
  41 + await validate();
  42 + const value = getFieldsValue();
  43 + emit('update:value', value);
  44 + show.value = false;
  45 + };
  46 +
  47 + watch(show, async (value) => {
  48 + if (value) {
  49 + await nextTick();
  50 + updateSchema([
  51 + {
  52 + field: FormFieldsEnum.ZOOM_FACTOR,
  53 + ifShow: props.dataType == DataTypeEnum.IS_BOOL ? false : true,
  54 + },
  55 + ]);
  56 + }
  57 + });
  58 +
  59 + watch(
  60 + () => props.value,
  61 + (value) => {
  62 + setFieldsValue(value);
  63 + }
  64 + );
  65 +</script>
  66 +
  67 +<template>
  68 + <section>
  69 + <Button type="link" @click="handleClick"><PlusCircleOutlined />新增扩展描述</Button>
  70 + <BasicModal title="扩展描述" v-model:visible="show" @ok="handleSubmit">
  71 + <BasicForm class="extension-form" @register="registerForm" />
  72 + </BasicModal>
  73 + </section>
  74 +</template>
  75 +
  76 +<style lang="less" scoped>
  77 + .extension-form {
  78 + :deep(.ant-input-number) {
  79 + width: 100%;
  80 + }
  81 + }
  82 +</style>
... ...
... ... @@ -139,6 +139,7 @@
139 139 {
140 140 required: props.required,
141 141 message: `${functionName}是必填项`,
  142 + type: 'number',
142 143 },
143 144 ],
144 145 componentProps: {
... ...
... ... @@ -28,7 +28,7 @@ export const validateJSON = (_rule, value = [] as ModelOfMatterParams[], _callba
28 28 return Promise.reject('JSON对象不能为空');
29 29 };
30 30
31   -export const formSchemas = (hasStructForm: boolean): FormSchema[] => {
  31 +export const formSchemas = (hasStructForm: boolean, isTcp = false): FormSchema[] => {
32 32 return [
33 33 {
34 34 field: FormField.FUNCTION_NAME,
... ... @@ -276,6 +276,22 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => {
276 276 ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.IS_STRING,
277 277 },
278 278 {
  279 + field: FormField.EXTENSION_DESC,
  280 + component: 'ExtendDesc',
  281 + label: '扩展描述',
  282 + valueField: 'value',
  283 + changeEvent: 'update:value',
  284 + ifShow: isTcp,
  285 + colProps: {
  286 + span: 16,
  287 + },
  288 + componentProps: ({ formModel }) => {
  289 + return {
  290 + dataType: formModel[FormField.TYPE],
  291 + };
  292 + },
  293 + },
  294 + {
279 295 field: FormField.ACCESS_MODE,
280 296 component: 'ApiRadioGroup',
281 297 label: '读写类型',
... ...
... ... @@ -129,4 +129,5 @@ export type ComponentType =
129 129 | 'RegisterAddressInput'
130 130 | 'ControlGroup'
131 131 | 'JSONEditor'
132   - | 'OrgTreeSelect';
  132 + | 'OrgTreeSelect'
  133 + | 'ExtendDesc';
... ...
... ... @@ -17,4 +17,6 @@ export enum DictEnum {
17 17 DISABLED_TENANT_AUTH = 'disabled_tenant_auth',
18 18 // 客户禁用的权限
19 19 DISABLE_CUSTOMER_AUTH = 'disabled_customer_auth',
  20 + // 寄存器数据格式
  21 + REGISTER_DATA_FORMAT = 'register_data_format',
20 22 }
... ...
... ... @@ -54,10 +54,11 @@
54 54 field: FiledKey.WEBSITE,
55 55 label: handleDecode(t('routes.aboutSoftware.websiteLabel')),
56 56 render: (val: string) => {
  57 + let joinWww = val.substring(0, 8) + 'www.' + val.substring(8);
57 58 return h(
58 59 'span',
59   - { class: 'text-blue-500 cursor-pointer', onClick: () => open(val) },
60   - val
  60 + { class: 'text-blue-500 cursor-pointer', onClick: () => open(joinWww) },
  61 + joinWww
61 62 );
62 63 },
63 64 },
... ...
... ... @@ -67,7 +67,7 @@ export function useAlarmNotify(params: UseAlarmNotifyParams = {}) {
67 67
68 68 if (items.length) {
69 69 const first = items.at(0)!;
70   - const { deviceName, id, severity } = first;
  70 + const { deviceName, id, severity, type } = first;
71 71
72 72 let key: Nullable<string> = `open-notify-${id}`;
73 73
... ... @@ -82,6 +82,10 @@ export function useAlarmNotify(params: UseAlarmNotifyParams = {}) {
82 82 h('span', { style: { marginRight: '5px' } }, '设备:'),
83 83 h('span', {}, `[${deviceName}]`),
84 84 ]),
  85 + h('div', { style: { marginRight: '5px' } }, [
  86 + h('span', { style: { marginRight: '5px' } }, '告警场景:'),
  87 + h('span', {}, `[${type}]`),
  88 + ]),
85 89 h('div', { style: { marginTop: '5px' } }, [
86 90 h('span', { style: { marginRight: '5px' } }, '告警状态:'),
87 91 h(Tag, { color }, () => `${alarmNotifyStatusMean}`),
... ...
... ... @@ -10,6 +10,9 @@ import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
10 10 import { toRaw, unref } from 'vue';
11 11 import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';
12 12 import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum';
  13 +import { TaskTypeEnum } from '/@/views/task/center/config';
  14 +import { AddressTypeEnum } from '/@/views/task/center/components/PollCommandInput';
  15 +
13 16 useComponentRegister('JSONEditor', JSONEditor);
14 17 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
15 18
... ... @@ -17,6 +20,7 @@ export enum TypeEnum {
17 20 IS_GATEWAY = 'GATEWAY',
18 21 SENSOR = 'SENSOR',
19 22 }
  23 +
20 24 export const isGateWay = (type: string) => {
21 25 return type === TypeEnum.IS_GATEWAY;
22 26 };
... ... @@ -85,6 +89,9 @@ export const step1Schemas: FormSchema[] = [
85 89 transportType,
86 90 deviceProfileId: id,
87 91 gatewayId: null,
  92 + codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null,
  93 + code: null,
  94 + addressCode: null,
88 95 });
89 96 },
90 97 showSearch: true,
... ... @@ -112,18 +119,101 @@ export const step1Schemas: FormSchema[] = [
112 119 },
113 120 },
114 121 {
  122 + field: 'codeType',
  123 + label: '标识符类型',
  124 + component: 'Select',
  125 + dynamicRules({ values }) {
  126 + return [
  127 + {
  128 + required: values?.transportType === TransportTypeEnum.TCP,
  129 + message: '请输入设备标识符',
  130 + },
  131 + ];
  132 + },
  133 + // ifShow: ({ values }) =>
  134 + // values?.transportType === TransportTypeEnum.TCP &&
  135 + // (values.deviceType === DeviceTypeEnum.SENSOR || values.deviceType === DeviceTypeEnum.GATEWAY),
  136 + ifShow: ({ values }) => values?.transportType === TransportTypeEnum.TCP,
  137 + componentProps: ({ formActionType }) => {
  138 + const { setFieldsValue } = formActionType;
  139 + return {
  140 + options: [
  141 + { label: '自定义', value: TaskTypeEnum.CUSTOM },
  142 + { label: 'ModBus', value: TaskTypeEnum.MODBUS_RTU },
  143 + ],
  144 + onChange() {
  145 + setFieldsValue({ addressCode: null });
  146 + },
  147 + };
  148 + },
  149 + },
  150 + {
  151 + field: 'addressCode',
  152 + label: '地址码',
  153 + dynamicRules({ values }) {
  154 + return [
  155 + {
  156 + required:
  157 + values?.transportType === TransportTypeEnum.TCP &&
  158 + values?.deviceType === DeviceTypeEnum.SENSOR,
  159 + message: '请输入设备地址码',
  160 + },
  161 + ];
  162 + },
  163 + component: 'RegisterAddressInput',
  164 + changeEvent: 'update:value',
  165 + valueField: 'value',
  166 + componentProps: {
  167 + type: AddressTypeEnum.DEC,
  168 + maxValue: 247,
  169 + minValue: 0,
  170 + disabledSwitch: true,
  171 + },
  172 + // ifShow: ({ values }) => {
  173 + // return (
  174 + // values?.transportType === TransportTypeEnum.TCP &&
  175 + // (values.deviceType === DeviceTypeEnum.SENSOR ||
  176 + // values.deviceType === DeviceTypeEnum.GATEWAY) &&
  177 + // values?.codeType === TaskTypeEnum.MODBUS_RTU
  178 + // );
  179 + // },
  180 + ifShow: ({ values }) => {
  181 + return (
  182 + values?.transportType === TransportTypeEnum.TCP &&
  183 + values?.codeType === TaskTypeEnum.MODBUS_RTU
  184 + );
  185 + },
  186 + },
  187 + {
115 188 field: 'code',
116 189 label: '设备标识',
117   - required: true,
  190 + dynamicRules({ values }) {
  191 + return [
  192 + {
  193 + required:
  194 + values?.transportType === TransportTypeEnum.TCP &&
  195 + values.deviceType === DeviceTypeEnum.SENSOR,
  196 + message: '请输入设备标识符',
  197 + },
  198 + ];
  199 + },
118 200 component: 'Input',
119   - componentProps: {
120   - maxLength: 255,
121   - placeholder: '请输入设备标识或设备地址码',
  201 + componentProps: () => {
  202 + return {
  203 + maxLength: 255,
  204 + placeholder: '请输入设备标识或设备地址码',
  205 + };
  206 + },
  207 + ifShow: ({ values }) => {
  208 + return (
  209 + values?.transportType === TransportTypeEnum.TCP &&
  210 + (values.deviceType === DeviceTypeEnum.SENSOR ||
  211 + values.deviceType === DeviceTypeEnum.GATEWAY) &&
  212 + values?.codeType === TaskTypeEnum.CUSTOM
  213 + );
122 214 },
123   - ifShow: ({ values }) =>
124   - values?.transportType === TransportTypeEnum.TCP &&
125   - values.deviceType === DeviceTypeEnum.SENSOR,
126 215 },
  216 +
127 217 {
128 218 field: 'brand',
129 219 component: 'ApiRadioGroup',
... ... @@ -876,8 +966,8 @@ export const CommandSchemas = (
876 966 const setValues = {
877 967 [CommandFieldsEnum.CUSTOM_TYPE]:
878 968 options.callType === ServiceCallTypeEnum.ASYNC
879   - ? CommandDeliveryWayEnum.TWO_WAY
880   - : CommandDeliveryWayEnum.ONE_WAY,
  969 + ? CommandDeliveryWayEnum.ONE_WAY
  970 + : CommandDeliveryWayEnum.TWO_WAY,
881 971 [CommandFieldsEnum.MODEL_INPUT]: null,
882 972 };
883 973
... ...
... ... @@ -43,7 +43,9 @@ export const descSchema = (emit: EmitType): DescItem[] => {
43 43 type: 'link',
44 44 style: { padding: 0 },
45 45 onClick: () =>
46   - !isCustomer ? go(PageEnum.DEVICE_PROFILE + '?name=' + String(val)) : '',
  46 + !isCustomer
  47 + ? go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(val)))
  48 + : '',
47 49 },
48 50 { default: () => val }
49 51 );
... ...
... ... @@ -109,6 +109,15 @@
109 109 unref(DeviceStep1Ref)?.resetFields();
110 110 unref(DeviceStep2Ref)?.resetFieldsValueAndStatus();
111 111 }
  112 + //验证设备名称不能超过255长度
  113 + const MAX_NAME_LENGTH = 255;
  114 + const validateNameLength = (name: string) => {
  115 + if (String(name).length > MAX_NAME_LENGTH) {
  116 + const errorText = `设备名称长度不能超过${MAX_NAME_LENGTH}`;
  117 + createMessage.error(errorText);
  118 + throw Error(errorText);
  119 + }
  120 + };
112 121 // 提交
113 122 const msg = computed(() => (unref(isUpdate) ? '更新设备成功' : '新增设备成功'));
114 123 async function handleOk() {
... ... @@ -150,6 +159,7 @@
150 159 ...DeviceStep1Ref.value?.positionState,
151 160 },
152 161 };
  162 + validateNameLength(stepRecord.name);
153 163 await createOrEditDevice(editData);
154 164 } else {
155 165 const stepRecord = unref(stepState);
... ... @@ -178,6 +188,7 @@
178 188 : null,
179 189 },
180 190 };
  191 + validateNameLength(stepRecord.name);
181 192 await createOrEditDevice(createData);
182 193 }
183 194 createMessage.success(unref(msg));
... ...
... ... @@ -127,6 +127,7 @@
127 127 import { copyTransFun } from '/@/utils/fnUtils';
128 128 import { useDrawer } from '/@/components/Drawer';
129 129 import DeptDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
  130 + import { TaskTypeEnum } from '/@/views/task/center/config';
130 131
131 132 export default defineComponent({
132 133 components: {
... ... @@ -187,7 +188,7 @@
187 188
188 189 const [register, { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema }] =
189 190 useForm({
190   - labelWidth: 120,
  191 + labelWidth: 140,
191 192 schemas: step1Schemas,
192 193 actionColOptions: {
193 194 span: 14,
... ... @@ -385,11 +386,26 @@
385 386 positionState.latitude = deviceInfo.latitude;
386 387 positionState.address = deviceInfo.address;
387 388 devicePic.value = deviceInfo.avatar;
388   - setFieldsValue(data);
  389 + setFieldsValue({
  390 + ...data,
  391 + code: data?.code,
  392 + addressCode: parseInt(data?.code || '', 16),
  393 + });
389 394 }
390 395 // 父组件调用获取字段值的方法
391 396 function parentGetFieldsValue() {
392   - return getFieldsValue();
  397 + const value = getFieldsValue();
  398 + return {
  399 + ...value,
  400 + ...(value?.code || value?.addressCode
  401 + ? {
  402 + code:
  403 + value?.codeType === TaskTypeEnum.CUSTOM
  404 + ? value?.code
  405 + : (value?.addressCode || '').toString(16).padStart(2, '0').toUpperCase(),
  406 + }
  407 + : {}),
  408 + };
393 409 }
394 410
395 411 // 父组件调用表单验证
... ...
1 1 <script lang="ts" setup>
2   - import { nextTick, onMounted, onUnmounted, reactive, ref, unref } from 'vue';
3   - import { List, Card, Tooltip, Space } from 'ant-design-vue';
  2 + import { nextTick, onMounted, onUnmounted, reactive, ref, unref, computed } from 'vue';
  3 + import { List, Card, Tooltip, Space, PaginationProps } from 'ant-design-vue';
4 4 import { PageWrapper } from '/@/components/Page';
5 5 import { BasicTable, useTable } from '/@/components/Table';
6 6 import { realTimeDataColumns } from '../../config/detail.config';
... ... @@ -14,7 +14,6 @@
14 14 import { BasicModal, useModal } from '/@/components/Modal';
15 15 import { getDeviceAttrs } from '/@/api/device/deviceManager';
16 16 import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
17   - import { computed } from '@vue/reactivity';
18 17 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
19 18 import { isArray, isObject } from '/@/utils/is';
20 19 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';
... ... @@ -50,11 +49,24 @@
50 49 const grid = {
51 50 gutter: 8,
52 51 column: 3,
53   - };
  52 + } as any;
54 53
  54 + const listElRef = ref<Nullable<ComponentElRef<HTMLDivElement>>>(null);
55 55 const token = getAuthCache(JWT_TOKEN_KEY);
56 56 const { socketUrl } = useGlobSetting();
  57 +
  58 + const pagination = reactive<PaginationProps>({
  59 + size: 'small',
  60 + showSizeChanger: true,
  61 + showQuickJumper: true,
  62 + hideOnSinglePage: false,
  63 + showTotal: (total: number) => `共${total}条数据`,
  64 + onChange: handleChange,
  65 + onShowSizeChange: handleChange,
  66 + });
  67 +
57 68 const socketInfo = reactive({
  69 + cmdId: 0,
58 70 origin: `${socketUrl}${token}`,
59 71 attr: undefined as string | undefined,
60 72 originData: [] as DataSource[],
... ... @@ -63,6 +75,22 @@
63 75 attrKeys: [] as DeviceModelOfMatterAttrs[],
64 76 });
65 77
  78 + const getPaginationAttrkey = computed<DeviceModelOfMatterAttrs[]>(() => {
  79 + const { current = 1, pageSize = 10 } = pagination;
  80 + return socketInfo.attrKeys.slice(current * pageSize - pageSize, current * pageSize);
  81 + });
  82 +
  83 + function createUnsubscribeMessage(cmdId: number) {
  84 + return {
  85 + tsSubCmds: [
  86 + {
  87 + cmdId,
  88 + unsubscribe: true,
  89 + },
  90 + ],
  91 + };
  92 + }
  93 +
66 94 const getSendValue = computed(() => {
67 95 return {
68 96 tsSubCmds: [
... ... @@ -70,8 +98,10 @@
70 98 entityType: 'DEVICE',
71 99 entityId: props.deviceDetail!.tbDeviceId,
72 100 scope: 'LATEST_TELEMETRY',
73   - cmdId: 1,
74   - keys: socketInfo.attrKeys.map((item) => item.identifier).join(','),
  101 + cmdId: socketInfo.cmdId,
  102 + keys: unref(getPaginationAttrkey)
  103 + .map((item) => item.identifier)
  104 + .join(','),
75 105 },
76 106 ],
77 107 };
... ... @@ -116,10 +146,19 @@
116 146 const [registerTable, { setTableData }] = useTable({
117 147 columns: realTimeDataColumns,
118 148 showTableSetting: true,
  149 + pagination: pagination as any,
119 150 bordered: true,
120 151 showIndexColumn: false,
121 152 });
122 153
  154 + function handleChange(page: number, pageSize: number) {
  155 + pagination.current = page;
  156 + pagination.pageSize = pageSize;
  157 + send(JSON.stringify(createUnsubscribeMessage(socketInfo.cmdId)));
  158 + socketInfo.cmdId = socketInfo.cmdId + 1;
  159 + send(JSON.stringify(unref(getSendValue)));
  160 + }
  161 +
123 162 const [registerModal, { openModal }] = useModal();
124 163
125 164 const mode = ref<EnumTableCardMode>(EnumTableCardMode.CARD);
... ... @@ -127,7 +166,7 @@
127 166 const switchMode = async (value: EnumTableCardMode) => {
128 167 mode.value = value;
129 168 await nextTick();
130   - setTableData(socketInfo.dataSource);
  169 + setTableData(socketInfo.originData);
131 170 };
132 171
133 172 const { createMessage } = useMessage();
... ... @@ -238,17 +277,28 @@
238 277 const handleSendCommandModal = (data: DataSource) => {
239 278 openSendCommandModal(true, {
240 279 mode: DataActionModeEnum.READ,
241   - record: { ...toRaw(data.detail) },
  280 + record: { ...toRaw(data.detail), deviceDetail: props.deviceDetail as any },
242 281 } as ModalParamsType);
243 282 };
244 283
  284 + onMounted(() => {
  285 + const element = unref(listElRef)?.$el;
  286 + if (!element) return;
  287 + const totalHeight = document.documentElement.clientHeight;
  288 + const { top } = element?.getBoundingClientRect() || {};
  289 + const containerEl = element.querySelector('.ant-spin-container') as HTMLDivElement;
  290 + containerEl.style.height = `${totalHeight - top - 20 - 50}px`;
  291 + containerEl.style.overflowY = 'auto';
  292 + containerEl.style.overflowX = 'hidden';
  293 + });
  294 +
245 295 onUnmounted(() => close());
246 296 </script>
247 297
248 298 <template>
249 299 <PageWrapper
250 300 dense
251   - content-class="flex flex-col bg-transparent p-4 bg-neutral-100 dark:text-gray-300 dark:bg-dark-700"
  301 + content-class="flex flex-col p-4 dark:text-gray-300 dark:bg-dark-700 bg-gray-100"
252 302 >
253 303 <section
254 304 class="flex flex-col justify-between w-full bg-light-50 pt-3 mb-4 dark:text-gray-300 dark:bg-dark-900"
... ... @@ -265,10 +315,12 @@
265 315 <ModeSwitchButton v-model:value="mode" @change="switchMode" />
266 316 </div>
267 317 <List
268   - v-if="mode === EnumTableCardMode.CARD"
  318 + v-show="mode === EnumTableCardMode.CARD"
  319 + ref="listElRef"
269 320 class="list-mode !px-2"
270   - :data-source="socketInfo.dataSource"
  321 + :data-source="socketInfo.originData"
271 322 :grid="grid"
  323 + :pagination="pagination"
272 324 >
273 325 <template #renderItem="{ item }">
274 326 <List.Item>
... ... @@ -279,11 +331,6 @@
279 331 <template #extra>
280 332 <Space>
281 333 <Tooltip title="属性下发">
282   - <!-- <MacCommandOutlined
283   - v-if="ReadAndWriteEnum.READ_AND_WRITE === item.accessMode"
284   - class="cursor-pointer text-lg svg:fill-blue-500"
285   - @click="handleSendCommandModal(item)"
286   - /> -->
287 334 <SvgIcon
288 335 name="send-command"
289 336 prefix="iconfont"
... ...
  1 +import { FormSchema } from '/@/components/Form';
  2 +
  3 +const InsertString = (t, c, n) => {
  4 + const r: string | number[] = [];
  5 +
  6 + for (let i = 0; i * 2 < t.length; i++) {
  7 + r.push(t.substr(i * 2, n));
  8 + }
  9 + return r.join(c);
  10 +};
  11 +const FillString = (t, c, n, b) => {
  12 + if (t == '' || c.length != 1 || n <= t.length) {
  13 + return t;
  14 + }
  15 + const l = t.length;
  16 +
  17 + for (let i = 0; i < n - l; i++) {
  18 + if (b == true) {
  19 + t = c + t;
  20 + } else {
  21 + t += c;
  22 + }
  23 + }
  24 + return t;
  25 +};
  26 +const SingleToHex = (t) => {
  27 + if (t == '') {
  28 + return '';
  29 + }
  30 + t = parseFloat(t);
  31 +
  32 + if (isNaN(t) == true) {
  33 + return 'Error';
  34 + }
  35 + if (t == 0) {
  36 + return '00000000';
  37 + }
  38 + let s, e, m;
  39 +
  40 + if (t > 0) {
  41 + s = 0;
  42 + } else {
  43 + s = 1;
  44 +
  45 + t = 0 - t;
  46 + }
  47 + m = t.toString(2);
  48 +
  49 + if (m >= 1) {
  50 + if (m.indexOf('.') == -1) {
  51 + m = m + '.0';
  52 + }
  53 + e = m.indexOf('.') - 1;
  54 + } else {
  55 + e = 1 - m.indexOf('1');
  56 + }
  57 + if (e >= 0) {
  58 + m = m.replace('.', '');
  59 + } else {
  60 + m = m.substring(m.indexOf('1'));
  61 + }
  62 + if (m.length > 24) {
  63 + m = m.substr(0, 24);
  64 + } else {
  65 + m = FillString(m, '0', 24, false);
  66 + }
  67 + m = m.substring(1);
  68 +
  69 + e = (e + 127).toString(2);
  70 +
  71 + e = FillString(e, '0', 8, true);
  72 +
  73 + let r = parseInt(s + e + m, 2).toString(16);
  74 +
  75 + r = FillString(r, '0', 8, true);
  76 +
  77 + return InsertString(r, ' ', 2).toUpperCase();
  78 +};
  79 +
  80 +const FormatHex = (t, n, ie) => {
  81 + const r: string[] = [];
  82 +
  83 + let s = '';
  84 +
  85 + let c = 0;
  86 +
  87 + for (let i = 0; i < t.length; i++) {
  88 + if (t.charAt(i) != ' ') {
  89 + s += t.charAt(i);
  90 +
  91 + c += 1;
  92 +
  93 + if (c == n) {
  94 + r.push(s);
  95 +
  96 + s = '';
  97 +
  98 + c = 0;
  99 + }
  100 + }
  101 + if (ie == false) {
  102 + if (i == t.length - 1 && s != '') {
  103 + r.push(s);
  104 + }
  105 + }
  106 + }
  107 + return r.join('\n');
  108 +};
  109 +const FormatHexBatch = (t, n, ie) => {
  110 + const a = t.split('\n');
  111 +
  112 + const r: string[] = [];
  113 +
  114 + for (let i = 0; i < a.length; i++) {
  115 + r[i] = FormatHex(a[i], n, ie);
  116 + }
  117 + return r.join('\n');
  118 +};
  119 +const SingleToHexBatch = (t) => {
  120 + const a = t.split('\n');
  121 +
  122 + const r: string[] = [];
  123 +
  124 + for (let i = 0; i < a.length; i++) {
  125 + r[i] = SingleToHex(a[i]);
  126 + }
  127 + return r.join('\r\n');
  128 +};
  129 +
  130 +const formSchemasConfig = (schemas, actionType): FormSchema[] => {
  131 + const { identifier, functionName } = schemas;
  132 + if (actionType == '06') {
  133 + return [
  134 + {
  135 + field: identifier,
  136 + label: functionName,
  137 + component: 'InputNumber',
  138 + rules: [{ required: true, message: '请输入正数' }],
  139 + componentProps: {
  140 + min: 0,
  141 + formatter: (e) => {
  142 + const value = `${e}`.replace('-', '').replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
  143 + return value;
  144 + },
  145 + placeholder: `请输入正数`,
  146 + },
  147 + },
  148 + ];
  149 + } else if (actionType == '05') {
  150 + return [
  151 + {
  152 + field: identifier,
  153 + label: functionName,
  154 + component: 'InputNumber',
  155 + rules: [{ required: true, message: '请输入值' }],
  156 + componentProps: {
  157 + min: 0,
  158 + max: 1,
  159 + precision: 0,
  160 + placeholder: `请输入0或1`,
  161 + },
  162 + },
  163 + ];
  164 + } else {
  165 + return [
  166 + {
  167 + field: identifier,
  168 + label: functionName,
  169 + component: 'InputNumber',
  170 + rules: [{ required: true, message: '请输入值' }],
  171 + componentProps: {
  172 + placeholder: `请输入数字`,
  173 + formatter: (e) =>
  174 + `${e}`.replace(/\B(?=(\d{3})+(?!\d))/g, '').replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3'),
  175 + },
  176 + },
  177 + ];
  178 + }
  179 +};
  180 +
  181 +export {
  182 + InsertString,
  183 + FillString,
  184 + SingleToHex,
  185 + FormatHex,
  186 + FormatHexBatch,
  187 + SingleToHexBatch,
  188 + formSchemasConfig,
  189 +};
... ...
... ... @@ -10,11 +10,14 @@
10 10 import { sendCommandOneway } from '/@/api/dataBoard';
11 11 import { useMessage } from '/@/hooks/web/useMessage';
12 12 import { unref } from 'vue';
  13 + import { genModbusCommand } from '/@/api/task';
  14 + import { TaskTypeEnum } from '/@/views/task/center/config';
  15 + import { SingleToHex, formSchemasConfig } from './config';
13 16
14 17 defineEmits(['register']);
15 18 const props = defineProps<{ deviceId: string; deviceName: string }>();
16 19
17   - const [registerForm, { setProps, getFieldsValue, resetFields }] = useForm({
  20 + const [registerForm, { setProps, getFieldsValue, resetFields, validate }] = useForm({
18 21 schemas: [],
19 22 showActionButtonGroup: false,
20 23 layout: 'vertical',
... ... @@ -24,11 +27,24 @@
24 27
25 28 const keys = ref<string[]>([]);
26 29
27   - const [register] = useModalInner((params: ModalParamsType<DeviceModelOfMatterAttrs>) => {
  30 + const modBUSForm = ref<any>({});
  31 + const isShowModBUS = ref<Boolean>(false); //用于判断标识符类型是否时自定义还是modBUS
  32 + const isShowActionType = ref<Boolean>(true); //判断设备属性标识符为modBus时没有填写扩展描述
  33 + const formField = ref(''); //存一个表单取值的field
  34 + const zoomFactorValue = ref<number>(1); //缩放因子
  35 + const isShowMultiply = ref<Boolean>(false); // 只有tcp --> int和double类型才相乘缩放因子
  36 +
  37 + const [register] = useModalInner(async (params: ModalParamsType<DeviceModelOfMatterAttrs>) => {
28 38 const { record } = params;
29   - const { name, detail, identifier } = record;
  39 + const { name, detail, identifier, deviceDetail, extensionDesc } = record;
30 40 const { dataType } = detail;
31 41 const { type } = dataType || {};
  42 + const { codeType, deviceProfile, code } = deviceDetail || {};
  43 + const { transportType } = deviceProfile || {};
  44 + const { registerAddress, actionType, zoomFactor } = extensionDesc || {}; //获取扩展描述内容
  45 + formField.value = identifier;
  46 + zoomFactorValue.value = zoomFactor ? Number(zoomFactor) : 1;
  47 + isShowMultiply.value = type == 'INT' || type == 'DOUBLE' ? true : false;
32 48
33 49 let schemas = [{ dataType: dataType, identifier, functionName: name } as StructJSON];
34 50
... ... @@ -36,33 +52,130 @@
36 52 schemas = dataType?.specs as StructJSON[];
37 53 }
38 54
39   - const formSchemas = genForm(schemas);
40   -
41 55 keys.value = schemas.map((item) => {
42 56 return item.identifier!;
43 57 });
44   - setProps({ schemas: formSchemas });
  58 + isShowActionType.value = actionType ? true : false; //判断modBUS类型时 物模型是否填写扩展描述
  59 +
  60 + //是modBUS类型的就用另外的表单
  61 + //判断是否是TCP ==> modBus的下发命令
  62 + if (codeType == TaskTypeEnum.MODBUS_RTU && transportType == 'TCP') {
  63 + isShowModBUS.value = true;
  64 + modBUSForm.value = {
  65 + crc: 'CRC_16_LOWER',
  66 + deviceCode: code,
  67 + method: actionType == '16' ? '10' : actionType,
  68 + registerAddress,
  69 + registerNumber: 1,
  70 + registerValues: [],
  71 + };
  72 + setProps({ schemas: formSchemasConfig(schemas[0], actionType) });
  73 + } else {
  74 + isShowModBUS.value = false;
  75 + const formSchemas = genForm(schemas);
  76 + setProps({ schemas: formSchemas });
  77 + }
  78 +
45 79 resetFields();
46 80 });
47 81
  82 + const getArray = (values) => {
  83 + const str = values.replace(/\s+/g, '');
  84 + const array: any = [];
  85 +
  86 + for (let i = 0; i < str.length; i += 4) {
  87 + const chunk = parseInt(str.substring(i, i + 4), 16);
  88 + array.push(chunk);
  89 + }
  90 + return array;
  91 + };
  92 +
  93 + // 获取小数
  94 + const getFloatPart = (number: string | number) => {
  95 + const isLessZero = Number(number) < 0;
  96 + number = number.toString();
  97 + const floatPartStartIndex = number.indexOf('.');
  98 + const value = ~floatPartStartIndex
  99 + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
  100 + : '0';
  101 + return Number(value);
  102 + };
  103 +
48 104 const { createMessage } = useMessage();
49 105 const loading = ref(false);
50 106 const handleSend = async () => {
51 107 try {
52 108 loading.value = true;
53 109 if (!props.deviceId) return;
54   - const _value = getFieldsValue();
55   - const value = unref(keys).reduce((prev, next) => {
56   - return { ...prev, [next]: _value[next] };
57   - }, {});
  110 +
  111 + const sendValue = ref({});
  112 + //判断tcp类型 标识符是自定义还是ModBus
  113 + if (unref(isShowModBUS)) {
  114 + if (!unref(isShowActionType)) {
  115 + createMessage.warning('当前物模型扩展描述没有填写');
  116 + return;
  117 + }
  118 + const flag = await validate();
  119 + if (!flag) return;
  120 +
  121 + const oldValue = getFieldsValue()[unref(formField)];
  122 + modBUSForm.value.registerNumber = 1;
  123 + modBUSForm.value.registerValues = [oldValue];
  124 +
  125 + if (unref(isShowMultiply) && unref(modBUSForm).method == '06') {
  126 + const newValue =
  127 + Math.trunc(oldValue) * unref(zoomFactorValue) +
  128 + getFloatPart(oldValue) * unref(zoomFactorValue);
  129 + if (newValue % 1 != 0) {
  130 + createMessage.warning(`属性下发类型必须是整数,缩放因子为${unref(zoomFactorValue)}`);
  131 + return;
  132 + }
  133 +
  134 + if (oldValue * unref(zoomFactorValue) > 65535) {
  135 + createMessage.warning(`属性下发值不能超过65535,缩放因子是${unref(zoomFactorValue)}`);
  136 + return;
  137 + }
  138 + //bool类型的就不用去乘缩放因子了
  139 + modBUSForm.value.registerValues = [newValue];
  140 + }
  141 +
  142 + if (unref(modBUSForm).method == '16' || unref(modBUSForm).method == '10') {
  143 + const regex = /^-?\d+(\.\d{0,2})?$/;
  144 + const values =
  145 + Math.trunc(oldValue) * unref(zoomFactorValue) +
  146 + getFloatPart(oldValue) * unref(zoomFactorValue);
  147 +
  148 + if (!regex.test(values as any)) {
  149 + createMessage.warning(`属性下发值精确到两位小数,缩放因子是${unref(zoomFactorValue)}`);
  150 + return;
  151 + }
  152 +
  153 + const newValue =
  154 + values == 0 ? [0, 0] : getArray(SingleToHex(unref(isShowMultiply) ? values : oldValue));
  155 + modBUSForm.value.registerValues = newValue;
  156 + modBUSForm.value.registerNumber = 2;
  157 + modBUSForm.value.method = '10';
  158 + }
  159 +
  160 + sendValue.value = await genModbusCommand(unref(modBUSForm));
  161 + } else {
  162 + const _value = getFieldsValue();
  163 +
  164 + sendValue.value = unref(keys).reduce((prev, next) => {
  165 + return { ...prev, [next]: _value[next] };
  166 + }, {});
  167 + }
  168 +
58 169 await sendCommandOneway({
59 170 deviceId: props.deviceId,
60 171 value: {
61 172 persistent: true,
62 173 method: 'methodThingskit',
63   - params: {
64   - ...value,
65   - },
  174 + params: unref(isShowModBUS)
  175 + ? unref(sendValue)
  176 + : {
  177 + ...unref(sendValue),
  178 + },
66 179 },
67 180 });
68 181 createMessage.success('属性下发成功~');
... ...
... ... @@ -20,7 +20,6 @@ export const useGenDynamicForm = () => {
20 20 const { specs, type } = dataType;
21 21 const { valueRange, step } = specs! as Partial<Specs>;
22 22 const { max, min } = valueRange || {};
23   - console.log({ max, min });
24 23 return {
25 24 field: identifier,
26 25 label: functionName,
... ...
... ... @@ -84,10 +84,10 @@
84 84 });
85 85 };
86 86 const handleRecordContent = (record) => {
87   - if (!record?.request?.body?.params) return;
88   - //如果是正常格式则返回params,否则输入什么内容则显示什么内容
89   - const jsonParams = JSON.parse(record?.request?.body?.params);
90   - commonModalInfo('命令下发内容', jsonParams?.params ? jsonParams?.params : jsonParams);
  87 + if (!record?.request?.body) return;
  88 + if (Object.prototype.toString.call(record?.request?.body) !== '[object Object]') return;
  89 + const jsonParams = record?.request?.body?.params;
  90 + commonModalInfo('命令下发内容', jsonParams);
91 91 };
92 92 const handleRecordResponseContent = (record) => {
93 93 const jsonParams = record?.response;
... ...
... ... @@ -182,7 +182,7 @@
182 182 </div>
183 183 </template>
184 184 <script lang="ts">
185   - import { defineComponent, reactive, unref, h, onMounted } from 'vue';
  185 + import { defineComponent, reactive, unref, onMounted } from 'vue';
186 186 import {
187 187 DeviceModel,
188 188 DeviceRecord,
... ... @@ -195,7 +195,6 @@
195 195 import {
196 196 deleteDevice,
197 197 devicePage,
198   - checkDeviceOccupied,
199 198 cancelDispatchCustomer,
200 199 getGATEWAY,
201 200 privateDevice,
... ... @@ -331,15 +330,8 @@
331 330 async function handleCancelDispatchCustomer(record: Recordable) {
332 331 try {
333 332 // 该设备是否正在被场景联动使用中?
334   - const isEnabled = await checkDeviceOccupied(record.id);
335   - if (!isEnabled.data) {
336   - const props = { style: { maxWidth: '600' + 'px' } };
337   - const small = h('small', '');
338   - createMessage.warn(h('h2', props, [`${isEnabled.message}`, small]));
339   - } else {
340   - await cancelDispatchCustomer(record);
341   - handleReload();
342   - }
  333 + await cancelDispatchCustomer(record);
  334 + handleReload();
343 335 } catch {}
344 336 }
345 337
... ... @@ -369,7 +361,7 @@
369 361 handleSuccess();
370 362 }
371 363 function goDeviceProfile(e) {
372   - go(PageEnum.DEVICE_PROFILE + '?name=' + String(e));
  364 + go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(e)));
373 365 }
374 366 const { copied, copy } = useClipboard({ legacy: true });
375 367 const copySN = async (snCode: string) => {
... ...
1 1 <script lang="ts" setup>
2 2 import { PageWrapper } from '/@/components/Page';
3 3 import { BasicForm, useForm } from '/@/components/Form';
4   - import { List, Button, Tooltip, Card, PaginationProps, Image } from 'ant-design-vue';
  4 + import { List, Button, Tooltip, Card, PaginationProps, Image, Popconfirm } from 'ant-design-vue';
5 5 import { ReloadOutlined } from '@ant-design/icons-vue';
6 6 import { computed, onMounted, reactive, ref, unref } from 'vue';
7 7 import {
... ... @@ -175,6 +175,9 @@
175 175
176 176 const { query: routeParams } = useRoute();
177 177 onMounted(() => {
  178 + routeParams.name = decodeURIComponent(
  179 + ((routeParams as unknown as Recordable) || {})?.name || ''
  180 + );
178 181 getDataSource(routeParams);
179 182 });
180 183 </script>
... ... @@ -204,14 +207,16 @@
204 207 {{ getSelectAllFlag ? '反选' : '全选' }}
205 208 </Button>
206 209 <Authority :value="ProductPermission.DELETE">
207   - <Button
208   - type="primary"
209   - danger
210   - :disabled="!getCheckedRecord.length"
211   - @click="handleDelete(getCheckedRecord)"
  210 + <Popconfirm
  211 + title="您确定要批量删除数据"
  212 + ok-text="确定"
  213 + cancel-text="取消"
  214 + @confirm="handleDelete(getCheckedRecord)"
212 215 >
213   - 批量删除
214   - </Button>
  216 + <Button type="primary" danger :disabled="!getCheckedRecord.length">
  217 + 批量删除
  218 + </Button>
  219 + </Popconfirm>
215 220 </Authority>
216 221
217 222 <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
... ...
... ... @@ -13,7 +13,7 @@
13 13 ? { minHeight: 55 + 'vh' }
14 14 : isMqttType == 'SNMP'
15 15 ? { minHeight: 60 + 'vh' }
16   - : isMqttType == 'TCP'
  16 + : isMqttType == 'TCP/UDP'
17 17 ? { minHeight: 15 + 'vh' }
18 18 : { minHeight: 25 + 'vh' },
19 19 ]"
... ... @@ -117,7 +117,7 @@
117 117 { label: 'CoAP', value: 'COAP' },
118 118 // { label: 'LWM2M', value: 'LWM2M' },
119 119 // { label: 'SNMP', value: 'SNMP' },
120   - { label: 'TCP', value: 'TCP' },
  120 + { label: 'TCP/UDP', value: 'TCP' },
121 121 ],
122 122 onChange(e) {
123 123 isMqttType.value = e;
... ... @@ -159,7 +159,7 @@
159 159 { label: 'CoAP', value: 'COAP' },
160 160 // { label: 'LWM2M', value: 'LWM2M' },
161 161 // { label: 'SNMP', value: 'SNMP' },
162   - { label: 'TCP', value: 'TCP' },
  162 + { label: 'TCP/UDP', value: 'TCP' },
163 163 ],
164 164 onChange(e) {
165 165 isMqttType.value = e;
... ...
... ... @@ -30,6 +30,7 @@
30 30 <Attribute
31 31 v-if="activeKey === FunctionType.PROPERTIES"
32 32 :openModalMode="openModalMode"
  33 + :transportType="record.transportType"
33 34 ref="AttrRef"
34 35 />
35 36 <Service
... ... @@ -77,8 +78,7 @@
77 78 return deviceType === DeviceTypeEnum.SENSOR && transportType === 'TCP';
78 79 });
79 80
80   - const blockContent = `属性一般是设备的运行状态,如当前温度等;服务是设备可被调用的方法,支持定义参数,如执行某项任务;事件则是设备上报的
81   -通知,如告警,需要被及时处理。`;
  81 + const blockContent = `属性一般是指设备的运行状态,如当前温度等;服务是指设备可被调用的方法,支持定义参数,如执行某项任务;事件则是指设备上报的通知,如告警,需要被及时处理。`;
82 82 const activeKey = ref<FunctionType>(FunctionType.PROPERTIES);
83 83 const size = ref('small');
84 84
... ...
... ... @@ -13,12 +13,13 @@
13 13 import { isArray } from 'lodash';
14 14 import { OpenModelMode } from '../types';
15 15 import { formSchemas } from '/@/components/Form/src/externalCompns/components/StructForm/config';
  16 + import { TransportTypeEnum } from '../../../../components/TransportDescript/const';
16 17
17   - defineProps<{ openModalMode: OpenModelMode }>();
  18 + const props = defineProps<{ openModalMode: OpenModelMode; transportType: string }>();
18 19
19 20 const [register, { validate, resetFields, setFieldsValue, setProps }] = useForm({
20 21 labelWidth: 100,
21   - schemas: formSchemas(false),
  22 + schemas: formSchemas(false, props.transportType === TransportTypeEnum.TCP),
22 23 actionColOptions: {
23 24 span: 14,
24 25 },
... ... @@ -37,13 +38,13 @@
37 38 const { functionName, remark, identifier, accessMode } = _values;
38 39 const structJSON = transfromToStructJSON(_values);
39 40 const dataType = excludeIdInStructJSON(structJSON.dataType!);
40   -
41 41 const value = {
42 42 functionName,
43 43 functionType: FunctionType.PROPERTIES,
44 44 remark,
45 45 identifier,
46 46 accessMode,
  47 + extensionDesc: _values.extensionDesc,
47 48 functionJson: {
48 49 dataType: dataType,
49 50 },
... ... @@ -67,7 +68,6 @@
67 68 ...dataType,
68 69 ...(isArray(specs) ? specs : { ...specs }),
69 70 };
70   -
71 71 setFieldsValue(value);
72 72 };
73 73
... ...
... ... @@ -89,7 +89,9 @@
89 89 functionJson: {
90 90 inputData,
91 91 outputData,
92   - ...(serviceCommand ? { inputData: [{ serviceCommand }] } : {}),
  92 + ...(serviceCommand
  93 + ? { inputData: [{ serviceCommand: serviceCommand.replaceAll(/\s/g, '').toUpperCase() }] }
  94 + : {}),
93 95 },
94 96 } as ModelOfMatterParams;
95 97
... ...
... ... @@ -24,7 +24,8 @@ export enum FormField {
24 24 EVENT_TYPE = 'eventType',
25 25 SERVICE_COMMAND = 'serviceCommand',
26 26 ACCESS_MODE = 'accessMode',
27   -
  27 + REGISTER_ADDRESS = 'registerAddress',
  28 + EXTENSION_DESC = 'extensionDesc',
28 29 STRUCT = 'struct',
29 30 }
30 31
... ... @@ -116,7 +117,18 @@ export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => {
116 117 field: FormField.SERVICE_COMMAND,
117 118 label: '输入参数',
118 119 component: 'Input',
119   - rules: [{ message: '输入参数为必填项', required: true }],
  120 + rules: [
  121 + { message: '输入参数为必填项', required: true },
  122 + {
  123 + message: '请输入ASCII或HEX服务命令(0~9/A~F)',
  124 + validator(_rule, value, _callback) {
  125 + const reg = /^[\s0-9a-fA-F]+$/;
  126 +
  127 + if (reg.test(value)) return Promise.resolve();
  128 + return Promise.reject('请输入ASCII或HEX服务命令(0~9/A~F)');
  129 + },
  130 + },
  131 + ],
120 132 ifShow: tcpDeviceFlag,
121 133 componentProps: {
122 134 placeholder: '请输入ASCII或HEX服务命令',
... ...
... ... @@ -49,7 +49,7 @@ export const useHooks = () => {
49 49 const item = ref<TSelectOption>();
50 50 if (items?.identifier !== null) {
51 51 item.value = {
52   - label: items?.identifier,
  52 + label: items?.name,
53 53 value: items?.identifier,
54 54 };
55 55 return item.value;
... ...
... ... @@ -42,7 +42,7 @@ export const modeApiForm: FormSchema[] = [
42 42 required: true,
43 43 component: 'Input',
44 44 componentProps: {
45   - maxLength: 255,
  45 + maxLength: 32,
46 46 placeholder: '请输入名称',
47 47 },
48 48 },
... ...
... ... @@ -45,7 +45,7 @@ export const modelKafkaForm: FormSchema[] = [
45 45 required: true,
46 46 component: 'Input',
47 47 componentProps: {
48   - maxLength: 255,
  48 + maxLength: 32,
49 49 placeholder: '请输入名称',
50 50 },
51 51 },
... ... @@ -152,12 +152,14 @@ export const modelKafkaForm: FormSchema[] = [
152 152 field: 'otherProperties',
153 153 label: '其他属性',
154 154 colProps: { span: 24 },
  155 + defaultValue: {},
155 156 component: 'JAddInput',
156 157 },
157 158 {
158 159 field: 'addMetadataKeyValuesAsKafkaHeaders',
159 160 label: '是否启用',
160 161 colProps: { span: 12 },
  162 + defaultValue: false,
161 163 component: 'Checkbox',
162 164 renderComponentContent: '将消息的元数据以键值对的方式添加到Kafka消息头中',
163 165 },
... ...
... ... @@ -32,9 +32,9 @@
32 32 import { modelFormPublicConfig } from '../../../dataflowmodal/config';
33 33
34 34 const credentialsFile = reactive({
35   - caCertFileName: '',
36   - certFileName: '',
37   - privateKeyFileName: '',
  35 + caCertFileName: undefined,
  36 + certFileName: undefined,
  37 + privateKeyFileName: undefined,
38 38 });
39 39
40 40 const [register, { validateFields, setFieldsValue, resetFields }] = useForm({
... ...
... ... @@ -31,7 +31,7 @@ export const modeMqttForm: FormSchema[] = [
31 31 component: 'Input',
32 32 required: true,
33 33 componentProps: {
34   - maxLength: 255,
  34 + maxLength: 32,
35 35 placeholder: '请输入名称',
36 36 },
37 37 },
... ... @@ -52,6 +52,7 @@ export const modeMqttForm: FormSchema[] = [
52 52 label: '主机',
53 53 colProps: { span: 12 },
54 54 component: 'Input',
  55 + required: true,
55 56 componentProps: {
56 57 maxLength: 255,
57 58 placeholder: '请输入Host',
... ...
... ... @@ -12,6 +12,7 @@ class RabbitMqFormPartialConfig {
12 12 static password = 'guest'; //密码
13 13 static connectionTimeout = 60000; //连接超时(毫秒)
14 14 static handshakeTimeout = 10000; //握手超时(毫秒)
  15 + static automaticRecoveryEnabled = false;
15 16
16 17 //anonymous Select options配置
17 18 static getMessageProperties() {
... ... @@ -34,7 +35,7 @@ export const modeRabbitMqForm: FormSchema[] = [
34 35 required: true,
35 36 component: 'Input',
36 37 componentProps: {
37   - maxLength: 255,
  38 + maxLength: 32,
38 39 placeholder: '请输入名称',
39 40 },
40 41 },
... ... @@ -43,6 +44,7 @@ export const modeRabbitMqForm: FormSchema[] = [
43 44 label: '交换名称模式',
44 45 colProps: { span: 12 },
45 46 component: 'Input',
  47 + defaultValue: '',
46 48 componentProps: {
47 49 maxLength: 255,
48 50 placeholder: '请输入模式',
... ... @@ -52,6 +54,7 @@ export const modeRabbitMqForm: FormSchema[] = [
52 54 field: 'routingKeyPattern',
53 55 label: '路由密钥模式',
54 56 colProps: { span: 12 },
  57 + defaultValue: '',
55 58 component: 'Input',
56 59 componentProps: {
57 60 maxLength: 255,
... ... @@ -63,6 +66,7 @@ export const modeRabbitMqForm: FormSchema[] = [
63 66 component: 'Select',
64 67 label: '消息属性',
65 68 colProps: { span: 12 },
  69 + defaultValue: null,
66 70 componentProps: {
67 71 placeholder: '请选择消息属性',
68 72 options: RabbitMqFormPartialConfig.getMessageProperties(),
... ... @@ -129,6 +133,7 @@ export const modeRabbitMqForm: FormSchema[] = [
129 133 field: 'automaticRecoveryEnabled',
130 134 label: '是否启用',
131 135 colProps: { span: 12 },
  136 + defaultValue: RabbitMqFormPartialConfig.automaticRecoveryEnabled,
132 137 component: 'Checkbox',
133 138 renderComponentContent: '自动恢复',
134 139 },
... ... @@ -159,6 +164,7 @@ export const modeRabbitMqForm: FormSchema[] = [
159 164 label: '客户端属性',
160 165 colProps: { span: 24 },
161 166 component: 'JAddInput',
  167 + defaultValue: {},
162 168 },
163 169 {
164 170 field: 'description',
... ...
... ... @@ -82,6 +82,14 @@
82 82 setValue(record);
83 83 });
84 84
  85 + // 判断转换方式
  86 + const isRabbitmq = (type) => {
  87 + return type == 'org.thingsboard.rule.engine.rabbitmq.TbRabbitMqNode';
  88 + };
  89 + const isKafka = (type) => {
  90 + return type == 'org.thingsboard.rule.engine.kafka.TbKafkaNode';
  91 + };
  92 +
85 93 const handleSubmit = async (closeModalAfterSuccess = true) => {
86 94 try {
87 95 if (closeModalAfterSuccess) {
... ... @@ -98,7 +106,30 @@
98 106 getDataFlowParams?.clientProperties
99 107 );
100 108 const data = getValue(description, name, getDataFlowMethod, getDataFlowParams);
101   - const rest = await postAddConvertApi({ ...restData.data, ...data });
  109 + const configuration = {
  110 + ...getDataFlowParams,
  111 + clientId: !isKafka(getDataFlowMethod?.type)
  112 + ? getDataFlowParams.clientId
  113 + ? getDataFlowParams.clientId
  114 + : null
  115 + : undefined,
  116 + kafkaHeadersCharset: isKafka(getDataFlowMethod?.type)
  117 + ? getDataFlowParams?.kafkaHeadersCharset
  118 + ? getDataFlowParams?.kafkaHeadersCharset
  119 + : 'UTF-8'
  120 + : undefined,
  121 + credentials: !isKafka(getDataFlowMethod?.type)
  122 + ? {
  123 + ...getDataFlowParams.credentials,
  124 + type: getDataFlowParams.type,
  125 + username: getDataFlowParams.username ? getDataFlowParams.username : undefined,
  126 + password: getDataFlowParams.password ? getDataFlowParams.password : undefined,
  127 + }
  128 + : undefined,
  129 + };
  130 + const rest = isRabbitmq(getDataFlowMethod?.type)
  131 + ? await postAddConvertApi({ ...restData.data, ...data })
  132 + : await postAddConvertApi({ ...restData.data, ...data, configuration });
102 133 if (rest) {
103 134 closeModalAfterSuccess && createMessage.success(`${businessText.value}成功`);
104 135 closeModalAfterSuccess && closeModal();
... ... @@ -120,7 +151,7 @@
120 151 ...record,
121 152 ...record?.configuration,
122 153 name: record?.name,
123   - description: record?.additionalInfo?.description,
  154 + description: record?.configuration.description || record?.additionalInfo?.description,
124 155 });
125 156 };
126 157
... ... @@ -147,10 +178,11 @@
147 178 };
148 179
149 180 //上一步
150   - const handlePrevDataFlowMethod = (value) => {
151   - currentStep.value = value;
  181 + const handlePrevDataFlowMethod = (oldData: { currentStep: number; values: Object }) => {
  182 + currentStep.value = oldData.currentStep;
152 183 setModalProps({ showOkBtn: false });
153 184 if (hasEditOrView(businessText.value)) {
  185 + restData.data = { ...restData.data, configuration: oldData.values };
154 186 setValue(restData.data);
155 187 }
156 188 };
... ...
... ... @@ -52,8 +52,9 @@
52 52
53 53 const dataFlowMethodIsRabbitMqRef = ref<InstanceType<typeof DataFlowMethodIsRabbitMq>>();
54 54
55   - const currentDataFlowParamsHanlePrevStep = () => {
56   - emit('currentDataFlowParamsEmitPrevStep', 0);
  55 + const currentDataFlowParamsHanlePrevStep = async () => {
  56 + const values = await getValue();
  57 + emit('currentDataFlowParamsEmitPrevStep', { currentStep: 0, values });
57 58 };
58 59
59 60 //表单配置项(kafka、mqtt、rabbitmq、restapi)
... ...
... ... @@ -31,6 +31,7 @@ export type TOption = {
31 31 export enum CommandTypeEnum {
32 32 CUSTOM = 0,
33 33 SERVICE = 1,
  34 + ATTRIBUTE = 2,
34 35 }
35 36
36 37 /**
... ... @@ -264,6 +265,13 @@ export const trigger_condition_schema: FormSchema[] = [
264 265 useByProductGetAttribute(res, updateSchema, options);
265 266 }
266 267 },
  268 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
  269 + let { label, value } = option;
  270 + label = label.toLowerCase();
  271 + value = value.toLowerCase();
  272 + inputValue = inputValue.toLowerCase();
  273 + return label.includes(inputValue) || value.includes(inputValue);
  274 + },
267 275 };
268 276 },
269 277 },
... ... @@ -308,6 +316,13 @@ export const trigger_condition_schema: FormSchema[] = [
308 316 },
309 317 placeholder: '请选择设备',
310 318 getPopupContainer: () => document.body,
  319 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
  320 + let { label, value } = option;
  321 + label = label.toLowerCase();
  322 + value = value.toLowerCase();
  323 + inputValue = inputValue.toLowerCase();
  324 + return label.includes(inputValue) || value.includes(inputValue);
  325 + },
311 326 };
312 327 },
313 328 ifShow: ({ values }) => isPart(values.device),
... ...
... ... @@ -103,6 +103,7 @@
103 103 beforeFetch: (params: Recordable) => {
104 104 return { ...unref(getSearchInfo), ...params };
105 105 },
  106 + resizeHeightOffset: 30,
106 107 actionColumn: {
107 108 width: 180,
108 109 title: '操作',
... ...
... ... @@ -107,7 +107,6 @@
107 107 v-else
108 108 v-model:value="scriptForm.output"
109 109 placeholder="输出参数为服务端返回的内容"
110   - :maxlength="255"
111 110 />
112 111 </a-form-item>
113 112 </a-form>
... ...
... ... @@ -48,6 +48,11 @@ export const aceEditorOptions = {
48 48 enableEmmet: true,
49 49 };
50 50
  51 +const scriptTypeInfo = {
  52 + TRANSPORT_TCP_UP: '上行数据解析',
  53 + TRANSPORT_TCP_AUTH: '设备鉴权',
  54 +};
  55 +
51 56 // 表格配置
52 57 export const columns: BasicColumn[] = [
53 58 {
... ... @@ -68,6 +73,14 @@ export const columns: BasicColumn[] = [
68 73 slots: { customRender: 'convertJs' },
69 74 },
70 75 {
  76 + title: '脚本类型',
  77 + dataIndex: 'scriptType',
  78 + width: 120,
  79 + format: (values) => {
  80 + return scriptTypeInfo[values];
  81 + },
  82 + },
  83 + {
71 84 title: '备注',
72 85 dataIndex: 'description',
73 86 width: 120,
... ...
... ... @@ -122,6 +122,7 @@
122 122 const [registerTable, { reload, setProps, setSelectedRowKeys }] = useTable({
123 123 api: ScriptPage,
124 124 ...defaultTableAttribtes,
  125 + resizeHeightOffset: 30,
125 126 beforeFetch: (params: Recordable) => {
126 127 return { ...unref(props.searchInfo), ...params };
127 128 },
... ...
... ... @@ -67,7 +67,6 @@
67 67 import { useI18n } from '/@/hooks/web/useI18n';
68 68 import { useDesign } from '/@/hooks/web/useDesign';
69 69 import { useLocaleStore } from '/@/store/modules/locale';
70   - import { useTitle } from '@vueuse/core';
71 70 import defaultBackgroundImage from '/@/assets/svg/thingskit-login-background.svg';
72 71 import { getPlatFormInfo } from '../../system/customize/hook/usePlatformInfo';
73 72
... ... @@ -85,10 +84,6 @@
85 84 const localeStore = useLocaleStore();
86 85 const showLocale = localeStore.getShowPicker;
87 86
88   - onMounted(() => {
89   - useTitle('ThingsKit 物联网平台');
90   - });
91   -
92 87 const show = ref(false);
93 88
94 89 onMounted(() => {
... ...
... ... @@ -82,10 +82,15 @@ export const usePlatform = async () => {
82 82 document.head.appendChild(styleEl);
83 83 };
84 84
  85 + const setTitle = () => {
  86 + document.title = platformInfo.name || '';
  87 + };
  88 +
85 89 const bootstrap = () => {
86 90 replaceSiteIco();
87 91 replaceLoadingEffect();
88 92 setBackgroundImage();
  93 + setTitle();
89 94 };
90 95
91 96 bootstrap();
... ...
... ... @@ -138,7 +138,7 @@ export const formSchema: FormSchema[] = [
138 138 component: 'Input',
139 139 required: true,
140 140 componentProps: {
141   - maxLength: 255,
  141 + maxLength: 100,
142 142 },
143 143 },
144 144
... ...
... ... @@ -67,6 +67,10 @@ export const searchSchedueFormSchema: FormSchema[] = [
67 67 label: '报表',
68 68 value: EJobGroup.REPORT,
69 69 },
  70 + {
  71 + label: '任务中心',
  72 + value: EJobGroup.TASK_CENTER,
  73 + },
70 74 ],
71 75 placeholder: '请选择任务组名',
72 76 },
... ...
... ... @@ -5,9 +5,9 @@
5 5 <Authority value="api:yt:schedule:post">
6 6 <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增 </a-button>
7 7 </Authority>
8   - <Authority value="api:yt:schedule:get">
  8 + <!-- <Authority value="api:yt:schedule:get">
9 9 <a-button type="primary"> 导出 </a-button>
10   - </Authority>
  10 + </Authority> -->
11 11 <Authority value="api:yt:schedule:delete">
12 12 <Popconfirm
13 13 title="您确定要批量删除数据"
... ...
... ... @@ -3,7 +3,7 @@
3 3 import { ComponentType, ColEx } from '/@/components/Form/src/types/index';
4 4 import { computed } from '@vue/reactivity';
5 5 import { isFunction } from '/@/utils/is';
6   - import { unref } from 'vue';
  6 + import { toRaw, unref } from 'vue';
7 7 import { watch } from 'vue';
8 8 import { nextTick } from 'vue';
9 9 import { ref } from 'vue';
... ... @@ -48,17 +48,13 @@
48 48 }
49 49 );
50 50
51   - const getProps = computed(() => {
52   - return props;
53   - });
54   -
55 51 const batchSetValue = (value: any): ValueItemType[] => {
56   - const { length } = unref(getProps);
  52 + const { length } = props;
57 53 return Array.from({ length }, () => ({ value }));
58 54 };
59 55
60 56 const getTotalControlItem = computed(() => {
61   - const { totalControlProps, component, showTotalControl } = unref(getProps);
  57 + const { totalControlProps, component, showTotalControl } = props;
62 58 return {
63 59 ...totalControlProps,
64 60 field: FormFieldsEnum.TOTAL_CONTROL,
... ... @@ -72,12 +68,13 @@
72 68 } as FormSchema;
73 69 });
74 70
75   - const getSchemas = computed(() => {
76   - const { itemProps, itemLabel, length, component } = unref(getProps);
  71 + const allDefaultValue = ref({});
  72 + const getSchemas = () => {
  73 + const { itemProps, itemLabel, length, component } = props;
77 74 let label = isFunction(itemLabel) ? itemLabel : (index: number) => `#${index}`;
78 75 let _itemProps = isFunction(itemProps) ? itemProps : () => ({});
79 76 const schemas = Array.from(
80   - { length },
  77 + { length: props.length },
81 78 (_item, index) =>
82 79 ({
83 80 ..._itemProps(index),
... ... @@ -93,14 +90,17 @@
93 90 } as FormSchema)
94 91 );
95 92
96   - length && schemas.unshift(unref(getTotalControlItem));
97   -
  93 + length && schemas.unshift(toRaw(getTotalControlItem.value));
  94 + allDefaultValue.value = unref(schemas).reduce(
  95 + (prev, next) => ({ ...prev, [next.field]: next.defaultValue }),
  96 + {}
  97 + );
98 98 return schemas;
99   - });
  99 + };
100 100
101 101 const [registerForm, { getFieldsValue, setProps, setFieldsValue }] = useForm({
102 102 showActionButtonGroup: false,
103   - schemas: unref(getSchemas),
  103 + schemas: toRaw(unref(getSchemas())),
104 104 // baseColProps,
105 105 baseColProps: props.itemColProps,
106 106 });
... ... @@ -111,19 +111,18 @@
111 111 return;
112 112 }
113 113 const allValue = getFieldsValue();
114   - const sortKeyList = Array.from({ length: unref(getProps).length }, (_v, key) => key);
115   - const res = sortKeyList.map((item) => ({ value: allValue[item] } as ValueItemType));
  114 + const sortKeyList = Array.from({ length: props.length }, (_v, key) => key);
  115 + const res = sortKeyList.map(
  116 + (item) => ({ value: allValue[item] ?? unref(allDefaultValue)[item] } as ValueItemType)
  117 + );
116 118
117 119 emit(EmitEventEnum.UPDATE_VALUE, res);
118 120 };
119 121
120 122 const transformValue = (value: ValueItemType[]) => {
121   - const { length } = unref(getProps);
  123 + const { length } = props;
122 124 if (value.length !== length) {
123   - value = Array.from(
124   - { length: unref(getProps).length },
125   - () => ({ value: null } as ValueItemType)
126   - );
  125 + value = Array.from({ length: props.length }, () => ({ value: null } as ValueItemType));
127 126 }
128 127 return value.reduce((prev, next, index) => ({ ...prev, [index]: next.value }), {});
129 128 };
... ... @@ -153,13 +152,11 @@
153 152
154 153 watch(
155 154 () => [props.length, props.component],
156   - (target) => {
157   - if (target !== undefined || target !== null) {
158   - setProps({
159   - schemas: unref(getSchemas),
160   - });
161   - handleUpdateValue();
162   - }
  155 + () => {
  156 + setProps({
  157 + schemas: toRaw(unref(getSchemas())),
  158 + });
  159 + handleUpdateValue();
163 160 }
164 161 );
165 162
... ...
... ... @@ -29,7 +29,6 @@
29 29 const record = composeModbusModalData(value as ModbusCommandValueType);
30 30 loading.value = true;
31 31 const result = await genModbusCommand(record);
32   - console.log(result);
33 32 commandValue.value = result;
34 33 } catch (error) {
35 34 } finally {
... ...
1 1 <script lang="ts" setup>
2   - import { InputGroup, InputNumber, Select, Input } from 'ant-design-vue';
3   - import { unref } from 'vue';
4   - import { computed } from 'vue';
5   - import { ref } from 'vue';
  2 + import { Select, InputGroup, Input } from 'ant-design-vue';
  3 + import { ref, unref, computed } from 'vue';
  4 + import { isNullOrUnDef } from '/@/utils/is';
6 5
7 6 enum AddressTypeEnum {
8 7 DEC = 'DEC',
... ... @@ -11,78 +10,154 @@
11 10
12 11 const emit = defineEmits(['update:value']);
13 12
14   - const DEC_MAX_VALUE = parseInt('0xffff', 16);
15   -
16   - withDefaults(
  13 + const props = withDefaults(
17 14 defineProps<{
18 15 value?: number | string;
19   - inputProps?: Recordable;
  16 + disabled?: boolean;
  17 + maxValue?: number | string;
  18 + minValue?: number | string;
  19 + type?: string;
  20 + toUpperCase?: boolean;
  21 + disabledSwitch?: boolean;
20 22 }>(),
21 23 {
22   - value: 0,
23 24 inputProps: () => ({}),
  25 + type: 'DEC',
  26 + maxValue: parseInt('FFFF', 16),
  27 + minValue: 0,
  28 + toUpperCase: true,
24 29 }
25 30 );
26 31
  32 + const maxHexPadLength = computed(() => {
  33 + const { maxValue, type } = props;
  34 + if (type === AddressTypeEnum.DEC) {
  35 + return Number(maxValue).toString(16).length;
  36 + } else {
  37 + return maxValue.toString().length;
  38 + }
  39 + });
  40 +
  41 + const inputType = ref(props.type);
  42 +
  43 + const getInputValue = computed(() => {
  44 + const { type, toUpperCase } = props;
  45 + let { value } = props;
  46 + if (isNaN(value as number)) value = undefined;
  47 +
  48 + if (isNullOrUnDef(value)) return value;
  49 +
  50 + if (type === AddressTypeEnum.DEC) {
  51 + if (unref(inputType) === AddressTypeEnum.DEC) {
  52 + return Number(value);
  53 + } else {
  54 + const _value = Number(value).toString(16);
  55 + return toUpperCase ? _value.toUpperCase() : _value;
  56 + }
  57 + } else {
  58 + if (unref(inputType) === AddressTypeEnum.DEC) {
  59 + return parseInt(value, 16);
  60 + } else {
  61 + return toUpperCase ? value.toString().toUpperCase() : value;
  62 + }
  63 + }
  64 + });
  65 +
  66 + const getValueOpposite = computed(() => {
  67 + if (isNullOrUnDef(unref(getInputValue))) {
  68 + if (unref(inputType) === AddressTypeEnum.DEC) {
  69 + return `0x${decToHex(props.minValue).padStart(unref(maxHexPadLength), '0').toUpperCase()}`;
  70 + } else {
  71 + return hexToDec(props.minValue);
  72 + }
  73 + }
  74 + if (unref(inputType) === AddressTypeEnum.DEC)
  75 + return `0x${decToHex(unref(getInputValue)!)
  76 + .toString()
  77 + .padStart(unref(maxHexPadLength), '0')
  78 + .toUpperCase()}`;
  79 + else return hexToDec(unref(getInputValue)!);
  80 + });
  81 +
27 82 const addressTypeOptions = [
28 83 { label: AddressTypeEnum.DEC, value: AddressTypeEnum.DEC },
29 84 { label: AddressTypeEnum.HEX, value: AddressTypeEnum.HEX },
30 85 ];
31 86
32   - const type = ref(AddressTypeEnum.DEC);
  87 + const getValueByInputType = (value: string | number) => {
  88 + let { type, toUpperCase, maxValue, minValue } = props;
33 89
34   - const inputValue = ref<number | string>(0);
  90 + if (type === AddressTypeEnum.DEC) {
  91 + if (unref(inputType) === AddressTypeEnum.HEX) {
  92 + value = hexToDec(value);
  93 + }
  94 + value = Number(value);
  95 + maxValue = Number(maxValue);
  96 + minValue = Number(minValue);
  97 + value = value > maxValue ? maxValue : value;
  98 + value = value < minValue ? minValue : value;
  99 + } else {
  100 + if (unref(inputType) === AddressTypeEnum.DEC) {
  101 + value = decToHex(value);
  102 + }
35 103
36   - const getHexValue = computed(() => {
37   - return parseInt(unref(inputValue) || 0, 16);
38   - });
  104 + const _maxValue = parseInt(maxValue, 16);
  105 + const _minValue = parseInt(minValue, 16);
39 106
40   - const getDecValue = computed(() => {
41   - let formatValue = Number(unref(inputValue) || 0).toString(16);
42   - formatValue = `0x${formatValue.padStart(4, '0')}`;
43   - return (inputValue.value as number) > DEC_MAX_VALUE ? '0x0000' : formatValue;
44   - });
  107 + value = parseInt(value, 16) > _maxValue ? maxValue : value;
  108 + value = parseInt(value, 16) < _minValue ? minValue : value;
  109 +
  110 + value = toUpperCase ? value.toString().toUpperCase() : value;
  111 + }
45 112
46   - const toDEC = (value: number | string) => {
47   - return unref(type) === AddressTypeEnum.DEC
48   - ? isNaN(value as number)
49   - ? 0
50   - : Number(value)
51   - : parseInt(value, 16);
  113 + return value;
52 114 };
53 115
54   - const handleEmit = () => {
55   - const syncValue = toDEC(unref(inputValue));
56   - emit('update:value', syncValue);
  116 + const validate = (value: string | number) => {
  117 + if (unref(inputType) === AddressTypeEnum.DEC) {
  118 + return /^[0-9]\d*$/.test(value.toString());
  119 + } else {
  120 + return /(0x)?[0-9a-fA-F]+/.test(value.toString());
  121 + }
57 122 };
58 123
59   - const handleChange = (value: AddressTypeEnum) => {
60   - const syncValue = value === AddressTypeEnum.DEC ? unref(getHexValue) : unref(getDecValue);
61   - inputValue.value = syncValue;
62   - emit('update:value', toDEC(syncValue));
  124 + const handleSyncValue = (event: ChangeEvent) => {
  125 + const value = (event.target as HTMLInputElement).value;
  126 + if (isNullOrUnDef(value) || value === '') {
  127 + emit('update:value', null);
  128 + return;
  129 + }
  130 + if (!validate(value)) return;
  131 + const syncValue = getValueByInputType(value);
  132 + emit('update:value', syncValue);
63 133 };
  134 +
  135 + function decToHex(value: number | string) {
  136 + return Number(value).toString(16);
  137 + }
  138 +
  139 + function hexToDec(value: number | string) {
  140 + return parseInt(value, 16);
  141 + }
64 142 </script>
65 143
66 144 <template>
67 145 <InputGroup compact class="!flex">
68 146 <Select
69   - v-model:value="type"
  147 + v-if="!disabledSwitch"
  148 + v-model:value="inputType"
70 149 :options="addressTypeOptions"
71   - @change="handleChange"
  150 + :disabled="disabled"
72 151 class="bg-gray-200 max-w-20"
73 152 />
74   - <InputNumber
75   - v-if="type === AddressTypeEnum.DEC"
76   - v-model:value="inputValue"
77   - :step="1"
78   - class="flex-1"
79   - v-bind="inputProps"
80   - @change="handleEmit"
  153 + <Input
  154 + :value="getInputValue"
  155 + @change="handleSyncValue"
  156 + :disabled="disabled"
  157 + :placeholder="`请输入${inputType === AddressTypeEnum.DEC ? '十进制' : '十六进制'}设备地址码`"
81 158 />
82   - <Input v-if="type === AddressTypeEnum.HEX" v-model:value="inputValue" @change="handleEmit" />
83 159 <div class="text-center h-8 leading-8 px-2 bg-gray-200 cursor-pointer rounded-1 w-20">
84   - <div v-if="type === AddressTypeEnum.DEC">{{ getDecValue }}</div>
85   - <div v-if="type === AddressTypeEnum.HEX">{{ getHexValue }}</div>
  160 + <div>{{ getValueOpposite }}</div>
86 161 </div>
87 162 </InputGroup>
88 163 </template>
... ...
1 1 import { findDictItemByCode } from '/@/api/system/dict';
2 2 import { FormSchema, useComponentRegister } from '/@/components/Form';
3 3 import { DictEnum } from '/@/enums/dictEnum';
4   -import RegisterAddressInput from './RegisterAddressInput.vue';
5 4 import { createPickerSearch } from '/@/utils/pickerSearch';
6 5 import { ControlGroup } from '../ControlGroup';
7 6
  7 +useComponentRegister('ControlGroup', ControlGroup);
  8 +
8 9 export enum FormFieldsEnum {
9 10 // 设备地址码
10 11 DEVICE_CODE = 'deviceCode',
11 12 // 功能码
12 13 METHOD = 'method',
13 14 // 寄存器地址
14   - REGISTER_ADDR = 'registerAddr',
  15 + REGISTER_ADDRESS = 'registerAddress',
15 16 // 数据校验算法
16 17 CRC = 'crc',
17 18 // 线圈个数
... ... @@ -28,9 +29,6 @@ export enum FormFieldsEnum {
28 29 REGISTER_VALUES = 'registerValues',
29 30 }
30 31
31   -useComponentRegister('RegisterAddressInput', RegisterAddressInput);
32   -useComponentRegister('ControlGroup', ControlGroup);
33   -
34 32 export enum FunctionCodeEnum {
35 33 // 读取线圈状态01
36 34 READ_COIL_STATE_01 = '01',
... ... @@ -135,7 +133,7 @@ export const formSchemas: FormSchema[] = [
135 133 },
136 134 },
137 135 {
138   - field: FormFieldsEnum.REGISTER_ADDR,
  136 + field: FormFieldsEnum.REGISTER_ADDRESS,
139 137 label: '起始寄存器地址',
140 138 component: 'RegisterAddressInput',
141 139 valueField: 'value',
... ... @@ -178,7 +176,7 @@ export const formSchemas: FormSchema[] = [
178 176 changeEvent: 'update:value',
179 177 ifShow: ({ model }) => showCoilValue(model[FormFieldsEnum.METHOD]),
180 178 defaultValue: '0',
181   - rules: [{ required: true, message: '请输入线圈值' }],
  179 + rules: [{ required: true, message: '请输入线圈值', type: 'number' }],
182 180 componentProps: {
183 181 placeholder: '请输入线圈值',
184 182 },
... ... @@ -191,7 +189,7 @@ export const formSchemas: FormSchema[] = [
191 189 changeEvent: 'update:value',
192 190 ifShow: ({ model }) => showRegisterValue(model[FormFieldsEnum.METHOD]),
193 191 defaultValue: '0',
194   - rules: [{ required: true, message: '请输入寄存器值' }],
  192 + rules: [{ required: true, message: '请输入寄存器值', type: 'number' }],
195 193 componentProps: {
196 194 placeholder: '请输入寄存器值',
197 195 },
... ... @@ -214,7 +212,8 @@ export const formSchemas: FormSchema[] = [
214 212 showTotalControl: false,
215 213 itemProps: () => {
216 214 return {
217   - defaultValue: '0',
  215 + valueField: 'value',
  216 + changeEvent: 'update:value',
218 217 } as FormSchema;
219 218 },
220 219 };
... ...
... ... @@ -3,3 +3,8 @@ export enum ModeEnum {
3 3 JSON = 'application/json',
4 4 NORMAL = 'normal',
5 5 }
  6 +
  7 +export enum AddressTypeEnum {
  8 + DEC = 'DEC',
  9 + HEX = 'HEX',
  10 +}
... ...
... ... @@ -12,23 +12,28 @@ const registerInfo = (record: ModbusCommandValueType): Partial<GenModbusCommandT
12 12 coilValue,
13 13 coilValues,
14 14 } = record;
  15 +
15 16 switch (method) {
16   - case FunctionCodeEnum.READ_COIL_STATE_01 || FunctionCodeEnum.READ_INPUT_STATE_02:
17   - return { registerNum: coilNumber };
18   - case FunctionCodeEnum.READ_KEEP_REGISTER_03 || FunctionCodeEnum.READ_INPUT_REGISTER_04:
19   - return { registerNum: registerNumber };
  17 + case FunctionCodeEnum.READ_COIL_STATE_01:
  18 + return { registerNumber: coilNumber };
  19 + case FunctionCodeEnum.READ_INPUT_STATE_02:
  20 + return { registerNumber: coilNumber };
  21 + case FunctionCodeEnum.READ_KEEP_REGISTER_03:
  22 + return { registerNumber };
  23 + case FunctionCodeEnum.READ_INPUT_REGISTER_04:
  24 + return { registerNumber };
20 25 case FunctionCodeEnum.WRITE_SINGLE_COIL_REGISTER_05:
21 26 return { registerValues: [coilValue] };
22 27 case FunctionCodeEnum.WRITE_SINGLE_KEEP_COIL_REGISTER_06:
23 28 return { registerValues: [registerValue] };
24 29 case FunctionCodeEnum.WRITE_MULTIPLE_COIL_STATE_15:
25 30 return {
26   - registerNum: coilNumber,
  31 + registerNumber: coilNumber,
27 32 registerValues: coilValues.map((item) => (item.value ? 1 : 0)),
28 33 };
29 34 case FunctionCodeEnum.WRITE_MULTIPLE_KEEP_REGISTER_16:
30 35 return {
31   - registerNum: registerNumber,
  36 + registerNumber,
32 37 registerValues: registerValues.map((item) => item.value),
33 38 };
34 39 default:
... ... @@ -37,12 +42,12 @@ const registerInfo = (record: ModbusCommandValueType): Partial<GenModbusCommandT
37 42 };
38 43
39 44 export const composeModbusModalData = (record: ModbusCommandValueType): GenModbusCommandType => {
40   - const { crc, deviceCode, method, registerAddr } = record;
  45 + const { crc, deviceCode, method, registerAddress } = record;
41 46 return {
42 47 crc,
43 48 deviceCode,
44   - method,
45   - registerAddr,
  49 + method: Number(method).toString(16).padStart(2, '0').toUpperCase(),
  50 + registerAddress,
46 51 ...registerInfo(record),
47 52 };
48 53 };
... ...
... ... @@ -122,13 +122,19 @@
122 122 });
123 123 };
124 124
125   - const handleCopy = (record: DataSourceType) => {
  125 + const handleCopy = async (record: DataSourceType) => {
  126 + const { key } = props.componentConfig || {};
  127 + if (key == 'HumidityComponent2' && props.dataSource.length >= 6) {
  128 + createMessage.warning('绑定的数据源不能超过6条~');
  129 + return;
  130 + }
  131 +
126 132 if (props.dataSource.length >= DATA_SOURCE_LIMIT_NUMBER) {
127 133 createMessage.warning('绑定的数据源不能超过10条~');
128 134 return;
129 135 }
130 136
131   - const allValues = getFormValues();
  137 + const allValues = await getFormValues();
132 138 const currentRecord = getFormValueByUUID(record.uuid);
133 139 const uuid = trackUpdate();
134 140 const raw = toRaw(record);
... ... @@ -167,7 +173,7 @@
167 173
168 174 const handleSettingOk = (data: DataSourceType) => {
169 175 const { uuid } = data;
170   - const _dataSource = cloneDeep(props.dataSource);
  176 + const _dataSource = cloneDeep(getFormValues());
171 177
172 178 const index = _dataSource.findIndex((item) => item.uuid === uuid);
173 179
... ...
... ... @@ -95,6 +95,10 @@
95 95 };
96 96 setFormValues(record);
97 97 } else {
  98 + selectWidgetKeys.value = {
  99 + componentKey: TextComponent1Config.key,
  100 + categoryKey: PackagesCategoryEnum.TEXT,
  101 + };
98 102 dataSource.value = [genNewDataSourceItem()];
99 103 }
100 104 }
... ... @@ -113,6 +117,11 @@
113 117 };
114 118
115 119 const handleNewRecord = () => {
  120 + const { componentKey } = unref(selectWidgetKeys);
  121 + if (componentKey === 'HumidityComponent2' && unref(dataSource).length >= 6) {
  122 + createMessage.warning('绑定的数据源不能超过6条~');
  123 + return;
  124 + }
116 125 if (unref(dataSource).length >= DATA_SOURCE_LIMIT_NUMBER) {
117 126 createMessage.warning('绑定的数据源不能超过10条~');
118 127 return;
... ...
  1 +import cloneDeep from 'lodash-es/cloneDeep';
  2 +import { DeviceAlarmConfig } from '.';
  3 +import {
  4 + ConfigType,
  5 + CreateComponentType,
  6 + PublicComponentOptions,
  7 + PublicPresetOptions,
  8 +} from '/@/views/visual/packages/index.type';
  9 +import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig';
  10 +import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
  11 +
  12 +export const option: PublicPresetOptions = {
  13 + multipleDataSourceComponent: true,
  14 + // componetDesign: false,
  15 + [ComponentConfigFieldEnum.OPEN_COLOR]: '#00F43D',
  16 + [ComponentConfigFieldEnum.CLOSE_COLOR]: '#FF0000',
  17 + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: true,
  18 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
  19 +};
  20 +
  21 +export default class Config extends PublicConfigClass implements CreateComponentType {
  22 + public key: string = DeviceAlarmConfig.key;
  23 +
  24 + public attr = { ...componentInitConfig };
  25 +
  26 + public componentConfig: ConfigType = cloneDeep(DeviceAlarmConfig);
  27 +
  28 + public persetOption = cloneDeep(option);
  29 +
  30 + public option: PublicComponentOptions;
  31 +
  32 + constructor(option: PublicComponentOptions) {
  33 + super();
  34 + this.option = { ...option };
  35 + }
  36 +}
... ...
  1 +<script lang="ts" setup>
  2 + import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
  3 + import { useForm, BasicForm } from '/@/components/Form';
  4 + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
  5 + import { option } from './config';
  6 +
  7 + const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
  8 + schemas: [
  9 + // {
  10 + // field: ComponentConfigFieldEnum.OPEN_COLOR,
  11 + // label: '设备正常颜色',
  12 + // component: 'ColorPicker',
  13 + // changeEvent: 'update:value',
  14 + // componentProps: {
  15 + // defaultValue: option.openColor,
  16 + // },
  17 + // },
  18 + // {
  19 + // field: ComponentConfigFieldEnum.CLOSE_COLOR,
  20 + // label: '设备告警颜色',
  21 + // component: 'ColorPicker',
  22 + // changeEvent: 'update:value',
  23 + // componentProps: {
  24 + // defaultValue: option.closeColor,
  25 + // },
  26 + // },
  27 + {
  28 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  29 + label: '显示设备名称',
  30 + component: 'Checkbox',
  31 + defaultValue: option.showDeviceName,
  32 + },
  33 + {
  34 + field: ComponentConfigFieldEnum.SHOW_TIME,
  35 + label: '显示时间',
  36 + component: 'Checkbox',
  37 + defaultValue: option.showTime,
  38 + },
  39 + ],
  40 + showActionButtonGroup: false,
  41 + labelWidth: 120,
  42 + baseColProps: {
  43 + span: 12,
  44 + },
  45 + });
  46 +
  47 + const getFormValues = () => {
  48 + return getFieldsValue();
  49 + };
  50 +
  51 + const setFormValues = (data: Recordable) => {
  52 + return setFieldsValue(data);
  53 + };
  54 +
  55 + defineExpose({
  56 + getFormValues,
  57 + setFormValues,
  58 + resetFormValues: resetFields,
  59 + } as PublicFormInstaceType);
  60 +</script>
  61 +
  62 +<template>
  63 + <BasicForm @register="register" />
  64 +</template>
... ...
  1 +<script lang="ts" setup>
  2 + // import { commonDataSourceSchemas } from '../../../config/common.config';
  3 + import { BasicForm, useForm } from '/@/components/Form';
  4 + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
  5 + import { formSchemas } from '../DeviceAlarmHistory/datasource.config';
  6 +
  7 + const [register, { getFieldsValue, setFieldsValue, validate, resetFields }] = useForm({
  8 + labelWidth: 0,
  9 + showActionButtonGroup: false,
  10 + layout: 'horizontal',
  11 + labelCol: { span: 0 },
  12 + schemas: formSchemas(),
  13 + });
  14 +
  15 + const getFormValues = () => {
  16 + return getFieldsValue();
  17 + };
  18 +
  19 + const setFormValues = (record: Recordable) => {
  20 + return setFieldsValue(record);
  21 + };
  22 +
  23 + defineExpose({
  24 + getFormValues,
  25 + setFormValues,
  26 + validate,
  27 + resetFormValues: resetFields,
  28 + } as PublicFormInstaceType);
  29 +</script>
  30 +
  31 +<template>
  32 + <BasicForm @register="register" />
  33 +</template>
... ...
  1 +// import { ComponentEnum, ComponentNameEnum } from '../index.type';
  2 +import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
  3 +import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
  4 +
  5 +const componentKeys = useComponentKeys('DeviceAlarm');
  6 +export const DeviceAlarmConfig: ConfigType = {
  7 + ...componentKeys,
  8 + title: '设备告警',
  9 + package: PackagesCategoryEnum.ALARM,
  10 +};
... ...
  1 +<script lang="ts" setup>
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  3 + import { option } from './config';
  4 + import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
  5 + import { computed, watch } from 'vue';
  6 + import { ref, onMounted, unref } from 'vue';
  7 + import { useIntervalFn } from '@vueuse/core';
  8 + import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  9 + import { useCustomDataFetch } from '../../../hook/socket/useSocket';
  10 + import { ReceiveAlarmDataCmdsMessageType } from '../../../hook/socket/useSocket.type';
  11 + import { useDataBoardContext } from '/@/views/visual/palette/hooks/useDataBoardContext';
  12 + import { useAlarmContext } from '/@/views/visual/palette/hooks/useAlarmTime';
  13 + import { getMessage } from '../config';
  14 +
  15 + interface IStatus {
  16 + text: string;
  17 + color: string;
  18 + }
  19 +
  20 + interface IAlarmStatusList {
  21 + id: string;
  22 + deviceName: string;
  23 + showDeviceName: boolean;
  24 + showTime: boolean;
  25 + status: IStatus;
  26 + time: number;
  27 + }
  28 +
  29 + const props = defineProps<{
  30 + config: ComponentPropsConfigType<typeof option>;
  31 + }>();
  32 +
  33 + const isOpenClose = ref<boolean>(true);
  34 +
  35 + const { send } = useDataBoardContext();
  36 +
  37 + const currentCmdId = ref();
  38 +
  39 + const alarmLevel = (type: string): IStatus => {
  40 + if (type === 'CRITICAL') {
  41 + return { text: '紧急', color: 'alarm_state_critical' };
  42 + } else if (type === 'MAJOR') {
  43 + return { text: '重要', color: 'alarm_state_major' };
  44 + } else if (type === 'MINOR') {
  45 + return { text: '次要', color: 'alarm_state_minor' };
  46 + } else if (type === 'WARNING') {
  47 + return { text: '警告', color: 'alarm_state_warning' };
  48 + } else {
  49 + return { text: '不确定', color: 'alarm_state_other' };
  50 + }
  51 + };
  52 +
  53 + const getDesign = computed(() => {
  54 + const { persetOption = {}, option } = props.config;
  55 + const { dataSource } = option;
  56 + const { showDeviceName: presetShowDeviceName, showTime: persetShowTime } = persetOption;
  57 +
  58 + return {
  59 + dataSource: dataSource?.map((item) => {
  60 + const { deviceId, deviceName, deviceRename, componentInfo } = item;
  61 + return {
  62 + id: deviceId,
  63 + deviceName: deviceRename || deviceName,
  64 + showDeviceName: componentInfo.showDeviceName ?? presetShowDeviceName,
  65 + showTime: componentInfo.showTime ?? persetShowTime,
  66 + };
  67 + }),
  68 + };
  69 + });
  70 +
  71 + const randomFn = () => {
  72 + useIntervalFn(() => {
  73 + isOpenClose.value = !unref(isOpenClose);
  74 + }, 4000);
  75 + };
  76 +
  77 + const alarmStatusList = ref<IAlarmStatusList[]>([
  78 + {
  79 + id: '1',
  80 + deviceName: '设备',
  81 + status: { text: '紧急', color: 'alarm_state_major' },
  82 + time: 1689574726,
  83 + showDeviceName: true,
  84 + showTime: true,
  85 + },
  86 + ]);
  87 +
  88 + const { alarmForm } = useAlarmContext();
  89 +
  90 + watch(
  91 + () => alarmForm?.value,
  92 + () => {
  93 + if (props.config.option.mode == 'SELECT_PREVIEW') return;
  94 + send?.(JSON.stringify(getMessage(unref(currentCmdId), unref(getDesign), unref(alarmForm))));
  95 + },
  96 + { immediate: false }
  97 + );
  98 +
  99 + const transformMessage = (cmdId: number) => {
  100 + if (props.config.option.mode == 'SELECT_PREVIEW') return;
  101 + currentCmdId.value = cmdId;
  102 + send?.(JSON.stringify(getMessage(cmdId, unref(getDesign), unref(alarmForm))));
  103 + };
  104 +
  105 + const getReduce = (data) => {
  106 + const list = data.reduce((acc, obj) => {
  107 + const found = acc.find((item) => item.entityId.id == obj.entityId.id);
  108 + if (!found) {
  109 + acc.push(obj);
  110 + }
  111 + return acc;
  112 + }, []);
  113 +
  114 + data.forEach((item) => {
  115 + list?.forEach((item1) => {
  116 + if (item.entityId.id == item1.entityId.id) {
  117 + item1.time = Number(item1.createdTime > item.createdTime)
  118 + ? item1.createdTime
  119 + : item.createdTime;
  120 + }
  121 + });
  122 + });
  123 +
  124 + return list;
  125 + };
  126 +
  127 + const updateFn = (message: ReceiveAlarmDataCmdsMessageType) => {
  128 + alarmStatusList.value = unref(getDesign).dataSource?.map((item) => {
  129 + return {
  130 + id: item.id,
  131 + deviceName: item.deviceName,
  132 + showDeviceName: item.showDeviceName,
  133 + showTime: item.showTime,
  134 + status: { text: '', color: '' },
  135 + time: 0,
  136 + };
  137 + }) as any;
  138 + const { data, update } = message;
  139 + const alarmList = data?.data || update;
  140 + const uniData = getReduce(alarmList); //去重得到最新的事件对象
  141 +
  142 + // if (!data?.data.length) return;
  143 + uniData.forEach((item) => {
  144 + alarmStatusList.value?.forEach((item1) => {
  145 + if (item.entityId.id == item1.id) {
  146 + item1.status = alarmLevel(item.severity);
  147 + item1.time = item.createdTime;
  148 + }
  149 + });
  150 + });
  151 + };
  152 +
  153 + onMounted(() => {
  154 + !props.config.option.uuid && randomFn();
  155 + });
  156 +
  157 + useCustomDataFetch(props, transformMessage, updateFn);
  158 +
  159 + const { getScale } = useComponentScale(props);
  160 +</script>
  161 +
  162 +<template>
  163 + <main :style="getScale" class="w-full h-full flex justify-center items-center">
  164 + <div v-for="item in alarmStatusList" :key="item.id" class="flex flex-col items-center">
  165 + <div class="flex justify-center items-center flex-col">
  166 + <div class="text-gray-500 text-sm truncate"
  167 + >{{
  168 + item.status.text
  169 + ? item.showDeviceName
  170 + ? item.deviceName + '-'
  171 + : ''
  172 + : '当前设备未发现告警'
  173 + }}{{ item.status.text }}</div
  174 + >
  175 + <div :class="item.status.color"></div>
  176 + </div>
  177 + <UpdateTime v-show="item.showTime" :time="item.time" />
  178 + </div>
  179 + </main>
  180 +</template>
  181 +<style lang="less" scoped>
  182 + .alarm_state_critical {
  183 + background: #cf1322;
  184 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #cf1322;
  185 + width: 60px;
  186 + height: 60px;
  187 + border-radius: 50%;
  188 + margin: 10px 0;
  189 + }
  190 +
  191 + .alarm_state_major {
  192 + background: #ff6e03;
  193 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #ff6e03;
  194 + width: 60px;
  195 + height: 60px;
  196 + border-radius: 50%;
  197 + margin: 10px 0;
  198 + }
  199 +
  200 + .alarm_state_minor {
  201 + background: #ff0;
  202 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #ff0;
  203 + width: 60px;
  204 + height: 60px;
  205 + border-radius: 50%;
  206 + margin: 10px 0;
  207 + }
  208 +
  209 + .alarm_state_warning {
  210 + background: #edf760;
  211 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #edf760;
  212 + width: 60px;
  213 + height: 60px;
  214 + border-radius: 50%;
  215 + margin: 10px 0;
  216 + }
  217 +
  218 + .alarm_state_other {
  219 + background: #d3adf7;
  220 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #d3adf7;
  221 + width: 60px;
  222 + height: 60px;
  223 + border-radius: 50%;
  224 + margin: 10px 0;
  225 + }
  226 +</style>
... ...
  1 +import cloneDeep from 'lodash-es/cloneDeep';
  2 +import { DeviceAlarmHistoryConfig } from '.';
  3 +import {
  4 + ConfigType,
  5 + CreateComponentType,
  6 + PublicComponentOptions,
  7 + PublicPresetOptions,
  8 +} from '../../../index.type';
  9 +import { PublicConfigClass, componentInitConfig } from '../../../publicConfig';
  10 +import { ComponentConfigFieldEnum } from '../../../enum';
  11 +
  12 +export const option: PublicPresetOptions = {
  13 + multipleDataSourceComponent: true,
  14 + componetDesign: false,
  15 + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  16 + [ComponentConfigFieldEnum.UNIT]: '℃',
  17 +};
  18 +
  19 +export default class Config extends PublicConfigClass implements CreateComponentType {
  20 + public key: string = DeviceAlarmHistoryConfig.key;
  21 +
  22 + public attr = { ...componentInitConfig };
  23 +
  24 + public componentConfig: ConfigType = cloneDeep(DeviceAlarmHistoryConfig);
  25 +
  26 + public persetOption = cloneDeep(option);
  27 +
  28 + public option: PublicComponentOptions;
  29 +
  30 + constructor(option: PublicComponentOptions) {
  31 + super();
  32 + this.option = { ...option };
  33 + }
  34 +}
... ...
  1 +<script lang="ts" setup>
  2 + import { ComponentConfigFieldEnum } from '../../../enum';
  3 + import { option } from './config';
  4 + import { useForm, BasicForm } from '/@/components/Form';
  5 + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
  6 + import { ComponentInfo } from '/@/views/visual/palette/types';
  7 +
  8 + const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
  9 + schemas: [
  10 + {
  11 + field: ComponentConfigFieldEnum.UNIT,
  12 + label: '数值单位',
  13 + component: 'Input',
  14 + defaultValue: option.unit,
  15 + },
  16 + // {
  17 + // field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  18 + // label: '显示设备名称',
  19 + // component: 'Checkbox',
  20 + // defaultValue: option.showDeviceName,
  21 + // },
  22 + ],
  23 + showActionButtonGroup: false,
  24 + labelWidth: 120,
  25 + baseColProps: {
  26 + span: 12,
  27 + },
  28 + });
  29 +
  30 + const getFormValues = () => {
  31 + // return getFieldsValue();
  32 + const item = getFieldsValue();
  33 + return {
  34 + unit: item[ComponentConfigFieldEnum.UNIT],
  35 + showDeviceName: item[ComponentConfigFieldEnum.SHOW_DEVICE_NAME],
  36 + } as ComponentInfo;
  37 + };
  38 +
  39 + const setFormValues = (data: Recordable) => {
  40 + // return setFieldsValue(data);
  41 + const { unit, fontColor, showDeviceName } = data;
  42 +
  43 + const value = {
  44 + [ComponentConfigFieldEnum.UNIT]: unit,
  45 + [ComponentConfigFieldEnum.FONT_COLOR]: fontColor,
  46 + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: showDeviceName,
  47 + };
  48 + return setFieldsValue(value);
  49 + };
  50 +
  51 + defineExpose({
  52 + getFormValues,
  53 + setFormValues,
  54 + resetFormValues: resetFields,
  55 + } as PublicFormInstaceType);
  56 +</script>
  57 +
  58 +<template>
  59 + <BasicForm @register="register" />
  60 +</template>
... ...
  1 +import { FormSchema } from '/@/components/Form';
  2 +import { DataSourceField } from '../../../config/common.config';
  3 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  4 +import { findDictItemByCode } from '/@/api/system/dict';
  5 +// import { getDeviceProfile } from '/@/api/alarm/position';
  6 +// import { useSelectWidgetMode } from '/@/views/visual/dataSourceBindPanel/useContext';
  7 +// import { DataActionModeEnum } from '/@/enums/toolEnum';
  8 +// import { unref } from 'vue';
  9 +import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
  10 +import { MasterDeviceList } from '/@/api/dataBoard/model';
  11 +
  12 +export const formSchemas = (): FormSchema[] => {
  13 + // const mode = useSelectWidgetMode();
  14 + // const isUpdate = unref(mode) === DataActionModeEnum.UPDATE;
  15 + return [
  16 + {
  17 + field: DataSourceField.DEVICE_NAME,
  18 + component: 'Input',
  19 + label: '设备名',
  20 + show: false,
  21 + },
  22 + {
  23 + field: DataSourceField.DEVICE_TYPE,
  24 + component: 'ApiSelect',
  25 + label: '设备类型',
  26 + colProps: { span: 8 },
  27 + rules: [{ message: '请选择设备类型', required: true }],
  28 + componentProps: ({ formActionType }) => {
  29 + const { setFieldsValue } = formActionType;
  30 + return {
  31 + api: findDictItemByCode,
  32 + params: {
  33 + dictCode: 'device_type',
  34 + },
  35 + valueField: 'itemValue',
  36 + labelField: 'itemText',
  37 + placeholder: '请选择设备类型',
  38 + onChange: (value: DeviceTypeEnum) => {
  39 + setFieldsValue({
  40 + [DataSourceField.IS_GATEWAY_DEVICE]: value === DeviceTypeEnum.GATEWAY,
  41 + [DataSourceField.DEVICE_PROFILE_ID]: null,
  42 + [DataSourceField.DEVICE_ID]: null,
  43 + [DataSourceField.ATTRIBUTE]: null,
  44 + [DataSourceField.ATTRIBUTE_NAME]: null,
  45 + [DataSourceField.TRANSPORT_TYPE]: null,
  46 + });
  47 + },
  48 + getPopupContainer: () => document.body,
  49 + };
  50 + },
  51 + },
  52 + // {
  53 + // field: DataSourceField.DEVICE_PROFILE_ID,
  54 + // component: 'ApiSelect',
  55 + // label: '产品',
  56 + // colProps: { span: 8 },
  57 + // rules: [{ required: true, message: '产品为必填项' }],
  58 + // componentProps: ({ formActionType, formModel }) => {
  59 + // const { setFieldsValue } = formActionType;
  60 + // const deviceType = formModel[DataSourceField.DEVICE_TYPE];
  61 + // return {
  62 + // api: async () => {
  63 + // if (!deviceType) return [];
  64 + // const list = await getDeviceProfile(deviceType);
  65 + // if (isUpdate) {
  66 + // const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
  67 + // const record = list.find((item) => item.id === deviceProfileId);
  68 + // setFieldsValue({ [DataSourceField.TRANSPORT_TYPE]: record?.transportType });
  69 + // }
  70 + // return list;
  71 + // },
  72 + // labelField: 'name',
  73 + // valueField: 'id',
  74 + // placeholder: '请选择产品',
  75 + // onChange: (_, option = {} as Record<'transportType', string>) => {
  76 + // setFieldsValue({
  77 + // [DataSourceField.DEVICE_ID]: null,
  78 + // [DataSourceField.ATTRIBUTE]: null,
  79 + // [DataSourceField.ATTRIBUTE_NAME]: null,
  80 + // [DataSourceField.TRANSPORT_TYPE]: option[DataSourceField.TRANSPORT_TYPE],
  81 + // });
  82 + // },
  83 + // getPopupContainer: () => document.body,
  84 + // };
  85 + // },
  86 + // },
  87 + {
  88 + field: DataSourceField.ORIGINATION_ID,
  89 + component: 'OrgTreeSelect',
  90 + label: '组织',
  91 + colProps: { span: 8 },
  92 + rules: [{ required: true, message: '组织为必填项' }],
  93 + componentProps({ formActionType }) {
  94 + const { setFieldsValue } = formActionType;
  95 + return {
  96 + placeholder: '请选择组织',
  97 + onChange() {
  98 + setFieldsValue({
  99 + [DataSourceField.DEVICE_ID]: null,
  100 + });
  101 + },
  102 + showCreate: false,
  103 + getPopupContainer: () => document.body,
  104 + };
  105 + },
  106 + },
  107 + {
  108 + field: DataSourceField.DEVICE_PROFILE_ID,
  109 + component: 'Input',
  110 + label: '',
  111 + show: false,
  112 + },
  113 + {
  114 + field: DataSourceField.DEVICE_ID,
  115 + component: 'ApiSelect',
  116 + label: '设备',
  117 + colProps: { span: 8 },
  118 + rules: [{ required: true, message: '设备名称为必填项' }],
  119 + componentProps({ formModel, formActionType }) {
  120 + const { setFieldsValue } = formActionType;
  121 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  122 + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
  123 + const deviceType = formModel[DataSourceField.DEVICE_TYPE];
  124 +
  125 + return {
  126 + api: async () => {
  127 + if (organizationId) {
  128 + try {
  129 + const data = await getMeetTheConditionsDevice({
  130 + organizationId,
  131 + deviceProfileId,
  132 + deviceType,
  133 + });
  134 + if (data)
  135 + return data.map((item) => ({
  136 + ...item,
  137 + label: item.alias || item.name,
  138 + value: item.tbDeviceId,
  139 + deviceType: item.deviceType,
  140 + }));
  141 + } catch (error) {}
  142 + }
  143 + return [];
  144 + },
  145 + onChange(_value, record: MasterDeviceList) {
  146 + setFieldsValue({
  147 + [DataSourceField.DEVICE_NAME]: record?.label,
  148 + });
  149 + },
  150 + placeholder: '请选择设备',
  151 + getPopupContainer: () => document.body,
  152 + };
  153 + },
  154 + },
  155 + {
  156 + field: DataSourceField.DEVICE_RENAME,
  157 + component: 'Input',
  158 + label: '设备名',
  159 + colProps: { span: 8 },
  160 + componentProps: {
  161 + placeholder: '设备重命名',
  162 + },
  163 + },
  164 + ];
  165 +};
... ...
  1 +<script lang="ts" setup>
  2 + // import { commonDataSourceSchemas } from '../../../config/common.config';
  3 + import { CreateComponentType } from '../../../index.type';
  4 + import { BasicForm, useForm } from '/@/components/Form';
  5 + import {
  6 + PublicComponentValueType,
  7 + PublicFormInstaceType,
  8 + } from '/@/views/visual/dataSourceBindPanel/index.type';
  9 +
  10 + import { formSchemas } from './datasource.config';
  11 +
  12 + defineProps<{
  13 + values: PublicComponentValueType;
  14 + componentConfig: CreateComponentType;
  15 + }>();
  16 +
  17 + const [register, { getFieldsValue, setFieldsValue, validate, resetFields }] = useForm({
  18 + labelWidth: 0,
  19 + showActionButtonGroup: false,
  20 + layout: 'horizontal',
  21 + labelCol: { span: 0 },
  22 + schemas: formSchemas(),
  23 + });
  24 +
  25 + const getFormValues = () => {
  26 + return getFieldsValue();
  27 + };
  28 +
  29 + const setFormValues = (record: Recordable) => {
  30 + return setFieldsValue(record);
  31 + };
  32 +
  33 + defineExpose({
  34 + getFormValues,
  35 + setFormValues,
  36 + validate,
  37 + resetFormValues: resetFields,
  38 + } as PublicFormInstaceType);
  39 +</script>
  40 +
  41 +<template>
  42 + <BasicForm @register="register" />
  43 +</template>
... ...
  1 +import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
  2 +import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
  3 +
  4 +const componentKeys = useComponentKeys('DeviceAlarmHistory');
  5 +
  6 +export const DeviceAlarmHistoryConfig: ConfigType = {
  7 + ...componentKeys,
  8 + title: '设备告警历史',
  9 + package: PackagesCategoryEnum.ALARM,
  10 +};
... ...
  1 +<script lang="ts" setup>
  2 + import { computed, unref, ref, watch } from 'vue';
  3 + import { ComponentPropsConfigType } from '../../../index.type';
  4 + import { option } from './config';
  5 + import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  6 + import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
  7 + import { formatToDateTime } from '/@/utils/dateUtil';
  8 + import { onMounted } from 'vue';
  9 + import { nextTick } from 'vue';
  10 + import { useComponentScale } from '../../../hook/useComponentScale';
  11 + import { h } from 'vue';
  12 + import { Tag } from 'ant-design-vue';
  13 + import { useDataBoardContext } from '/@/views/visual/palette/hooks/useDataBoardContext';
  14 + import { useCustomDataFetch } from '../../../hook/socket/useSocket';
  15 + import { ReceiveAlarmDataCmdsMessageType } from '../../../hook/socket/useSocket.type';
  16 + import { useAlarmContext } from '/@/views/visual/palette/hooks/useAlarmTime';
  17 +
  18 + import { getMessage } from '../config';
  19 +
  20 + const props = defineProps<{
  21 + config: ComponentPropsConfigType<typeof option>;
  22 + }>();
  23 +
  24 + interface IList {
  25 + [key: string]: string | number;
  26 + }
  27 +
  28 + const alarmLevel = (type: string): IList => {
  29 + if (type === 'CRITICAL') {
  30 + return { text: '紧急', color: 'red' };
  31 + } else if (type === 'MAJOR') {
  32 + return { text: '重要', color: 'pink' };
  33 + } else if (type === 'MINOR') {
  34 + return { text: '次要', color: 'warning' };
  35 + } else if (type === 'WARNING') {
  36 + return { text: '警告', color: 'warning' };
  37 + } else {
  38 + return { text: '不确定', color: 'default' };
  39 + }
  40 + };
  41 +
  42 + const statusType = (type: string): string => {
  43 + if (type === 'CLEARED_UNACK') {
  44 + return '清除未确认';
  45 + } else if (type === 'CLEARED_ACK') {
  46 + return '清除已确认';
  47 + } else if (type === 'ACTIVE_ACK') {
  48 + return '激活已确认';
  49 + } else {
  50 + return '激活未确认';
  51 + }
  52 + };
  53 +
  54 + const { send } = useDataBoardContext();
  55 +
  56 + const currentCmdId = ref();
  57 +
  58 + const alarmColumns: BasicColumn[] = [
  59 + {
  60 + title: '状态',
  61 + dataIndex: 'severity',
  62 + ellipsis: true,
  63 + width: 80,
  64 + customRender: ({ record }) => {
  65 + const { severity } = record;
  66 + const { text, color } = alarmLevel(severity);
  67 + return h(Tag, { color }, () => text);
  68 + },
  69 + },
  70 + { title: '设备', dataIndex: 'device', ellipsis: true, width: 120 },
  71 + { title: '告警场景', dataIndex: 'type', ellipsis: true, width: 80 },
  72 + {
  73 + title: '状态',
  74 + dataIndex: 'status',
  75 + ellipsis: true,
  76 + width: 80,
  77 + format: (text) => statusType(text),
  78 + },
  79 + {
  80 + title: '时间',
  81 + dataIndex: 'time',
  82 + ellipsis: true,
  83 + width: 110,
  84 + format(text) {
  85 + return formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss');
  86 + },
  87 + },
  88 + ];
  89 + const devicealarmList = ref([
  90 + { severity: 0, device: '温湿度', type: '规则', status: 50, time: 1688974276000 },
  91 + { severity: 1, device: '温湿度1', type: '规则1', status: 350, time: 1688994276000 },
  92 + { severity: 0, device: '温湿度2', type: '规则2', status: 150, time: 1688174276000 },
  93 + { severity: 0, device: '温湿度3', type: '规则2', status: 150, time: 1688174276000 },
  94 + { severity: 0, device: '温湿度4', type: '规则2', status: 150, time: 1688174276000 },
  95 + { severity: 0, device: '温湿度5', type: '规则2', status: 150, time: 1688174276000 },
  96 + ]);
  97 +
  98 + const [registerTable, { setProps, redoHeight, setTableData }] = useTable({
  99 + showIndexColumn: false,
  100 + showTableSetting: false,
  101 + canResize: true,
  102 + size: 'small',
  103 + maxHeight: 144,
  104 + dataSource: devicealarmList,
  105 + columns: alarmColumns,
  106 + });
  107 +
  108 + const getDesign = computed(() => {
  109 + const { persetOption, option } = props.config;
  110 + const { dataSource = [] } = option || {};
  111 + const { unit: presetUnit, presetShowDeviceName } = persetOption || {};
  112 + return {
  113 + dataSource: dataSource?.map((item) => {
  114 + const { unit, showDeviceName } = item.componentInfo || {};
  115 + const { attribute, attributeName, attributeRename, deviceName, deviceRename, deviceId } =
  116 + item;
  117 + return {
  118 + unit: unit ?? presetUnit,
  119 + attribute,
  120 + attributeRename,
  121 + attributeName,
  122 + showDeviceName: showDeviceName ?? presetShowDeviceName,
  123 + deviceName,
  124 + deviceRename,
  125 + id: deviceId,
  126 + };
  127 + }),
  128 + };
  129 + });
  130 +
  131 + const { alarmForm } = useAlarmContext();
  132 +
  133 + watch(
  134 + () => alarmForm?.value,
  135 + () => {
  136 + if (props.config.option.mode == 'SELECT_PREVIEW') return;
  137 + send?.(JSON.stringify(getMessage(unref(currentCmdId), unref(getDesign), unref(alarmForm))));
  138 + },
  139 + { immediate: false }
  140 + );
  141 +
  142 + const transformMessage = (cmdId: number) => {
  143 + if (props.config.option.mode == 'SELECT_PREVIEW') return;
  144 + currentCmdId.value = cmdId;
  145 + send?.(JSON.stringify(getMessage(cmdId, unref(getDesign), unref(alarmForm))));
  146 + };
  147 +
  148 + const updateFn = (message: ReceiveAlarmDataCmdsMessageType) => {
  149 + const { data } = message || {};
  150 + if (!data?.data) return;
  151 + const tableList = ref<any>(
  152 + data?.data?.map((item) => {
  153 + return {
  154 + time: item.createdTime,
  155 + device: item.originatorName,
  156 + severity: item.severity,
  157 + type: item.type,
  158 + status: item.status,
  159 + };
  160 + })
  161 + );
  162 + setTableData(unref(tableList));
  163 + };
  164 +
  165 + useCustomDataFetch(props, transformMessage, updateFn);
  166 +
  167 + onMounted(async () => {
  168 + await nextTick();
  169 + resize();
  170 + });
  171 +
  172 + const resize = async () => {
  173 + const { height } = unref(getContainerSize);
  174 + height && setProps({ maxHeight: height - 110, scroll: { x: 470, y: height - 110 } });
  175 + await nextTick();
  176 + redoHeight();
  177 + };
  178 +
  179 + const { getContainerSize } = useComponentScale(props, resize);
  180 +</script>
  181 +
  182 +<template>
  183 + <main class="flex flex-col justify-center items-center w-full h-full">
  184 + <DeviceName :config="config" />
  185 + <div class="w-full h-full">
  186 + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" />
  187 + </div>
  188 + </main>
  189 +</template>
... ...
  1 +// isRealTime是否是实时
  2 +
  3 +export const getMessage = (cmdId, getDesign, alarmForm) => {
  4 + const entityList = [...new Set(getDesign.dataSource?.map((item) => item.id))];
  5 + const message = {
  6 + alarmDataCmds: [
  7 + {
  8 + query: {
  9 + entityFilter: {
  10 + type: 'entityList',
  11 + resolveMultiple: true,
  12 + entityType: 'DEVICE',
  13 + entityList: entityList,
  14 + },
  15 + pageLink: {
  16 + page: 0,
  17 + pageSize: alarmForm?.pageSize,
  18 + textSearch: null,
  19 + searchPropagatedAlarms: false,
  20 + statusList: [],
  21 + severityList: [],
  22 + typeList: [],
  23 + sortOrder: {
  24 + key: {
  25 + key: 'createdTime',
  26 + type: 'ALARM_FIELD',
  27 + },
  28 + direction: 'DESC',
  29 + },
  30 + timeWindow: alarmForm?.time,
  31 + startTs: alarmForm?.startTs ? new Date(alarmForm?.startTs).getTime() : undefined,
  32 + endTs: alarmForm?.endTs ? new Date(alarmForm?.endTs).getTime() : undefined,
  33 + },
  34 + alarmFields: [
  35 + {
  36 + type: 'ALARM_FIELD',
  37 + key: 'createdTime',
  38 + },
  39 + {
  40 + type: 'ALARM_FIELD',
  41 + key: 'originator',
  42 + },
  43 + {
  44 + type: 'ALARM_FIELD',
  45 + key: 'type',
  46 + },
  47 + {
  48 + type: 'ALARM_FIELD',
  49 + key: 'severity',
  50 + },
  51 + {
  52 + type: 'ALARM_FIELD',
  53 + key: 'status',
  54 + },
  55 + ],
  56 + entityFields: [],
  57 + latestValues: [],
  58 + },
  59 + cmdId,
  60 + },
  61 + ],
  62 + };
  63 + return message;
  64 +};
... ...
  1 +import { DeviceAlarmHistoryConfig } from './DeviceAlarmHistory';
  2 +import { DeviceAlarmConfig } from './DeviceAlarm';
  3 +
  4 +export const AlarmList = [DeviceAlarmConfig, DeviceAlarmHistoryConfig];
... ...
... ... @@ -32,6 +32,10 @@
32 32 transportType: value.transportType,
33 33 service: value.service,
34 34 command: value.command,
  35 + openService: value.openService,
  36 + closeService: value.closeService,
  37 + openCommand: value.openCommand,
  38 + closeCommand: value.closeCommand,
35 39 commandType: value.commandType,
36 40 callType: value.callType,
37 41 },
... ... @@ -45,6 +49,10 @@
45 49 ...record,
46 50 transportType: customCommand?.transportType || (record as Recordable).transportType,
47 51 service: customCommand?.service || (record as Recordable).service,
  52 + openService: customCommand?.openService || (record as Recordable).openService,
  53 + closeService: customCommand?.closeService || (record as Recordable).closeService,
  54 + openCommand: customCommand?.openCommand || (record as Recordable).openCommand,
  55 + closeCommand: customCommand?.closeCommand || (record as Recordable).closeCommand,
48 56 command: customCommand?.command || (record as Recordable).command,
49 57 commandType: customCommand?.commandType || (record as Recordable).commandType,
50 58 callType: customCommand?.callType || (record as Recordable).callType,
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { Spin } from 'ant-design-vue';
6   - import { computed, ref } from 'vue';
  5 + import { computed, ref, unref } from 'vue';
7 6 import { useComponentScale } from '../../../hook/useComponentScale';
8 7 import { useSendCommand } from '../../../hook/useSendCommand';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  10 + import { useDataFetch } from '../../../hook/socket/useSocket';
  11 + import { getSendValues } from '../config';
10 12
11 13 const props = defineProps<{
12 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -17,10 +19,25 @@
17 19 const currentValue = ref(false);
18 20
19 21 const getDesign = computed(() => {
  22 + // console.log(props.config, 'config');
20 23 const { option } = props.config;
21   - const { attribute, attributeRename, attributeName } = option;
  24 + const {
  25 + attribute,
  26 + attributeRename,
  27 + attributeName,
  28 + commandType,
  29 + extensionDesc,
  30 + codeType,
  31 + deviceCode,
  32 + customCommand,
  33 + } = option;
22 34 return {
23 35 attribute: attributeRename || attributeName || attribute,
  36 + extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  37 + commandType,
  38 + codeType,
  39 + deviceCode,
  40 + customCommand,
24 41 };
25 42 });
26 43
... ... @@ -28,8 +45,12 @@
28 45 const handleChange = async (event: Event) => {
29 46 const target = event.target as HTMLInputElement;
30 47 const value = target.checked;
  48 + const { option } = props.config || {};
31 49
32   - const flag = await sendCommand(props.config.option, value);
  50 + const { values, isModbusCommand, sendValue } =
  51 + (await getSendValues(option, unref(getDesign), value)) || {};
  52 +
  53 + const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
33 54 if (flag) currentValue.value = value;
34 55 flag ? (currentValue.value = value) : (target.checked = !value);
35 56 };
... ...
... ... @@ -32,6 +32,10 @@
32 32 transportType: value.transportType,
33 33 service: value.service,
34 34 command: value.command,
  35 + openService: value.openService,
  36 + closeService: value.closeService,
  37 + openCommand: value.openCommand,
  38 + closeCommand: value.closeCommand,
35 39 commandType: value.commandType,
36 40 callType: value.callType,
37 41 },
... ... @@ -41,14 +45,20 @@
41 45
42 46 const setFormValues = (record: DataSource) => {
43 47 const { customCommand } = record;
44   - return setFieldsValue({
  48 + const values = {
45 49 ...record,
46 50 transportType: customCommand?.transportType || (record as Recordable).transportType,
47 51 service: customCommand?.service || (record as Recordable).service,
48 52 command: customCommand?.command || (record as Recordable).command,
  53 + openService: customCommand?.openService || (record as Recordable).openService,
  54 + closeService: customCommand?.closeService || (record as Recordable).closeService,
  55 + openCommand: customCommand?.openCommand || (record as Recordable).openCommand,
  56 + closeCommand: customCommand?.closeCommand || (record as Recordable).closeCommand,
49 57 commandType: customCommand?.commandType || (record as Recordable).commandType,
50 58 callType: customCommand?.callType || (record as Recordable).callType,
51   - } as unknown as Partial<CommonDataSourceBindValueType>);
  59 + } as unknown as Partial<CommonDataSourceBindValueType>;
  60 +
  61 + return setFieldsValue(values);
52 62 };
53 63
54 64 defineExpose({
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { SvgIcon } from '/@/components/Icon';
6 5 import { Switch } from 'ant-design-vue';
7 6 import { computed, ref } from 'vue';
... ... @@ -9,6 +8,9 @@
9 8 import { useSendCommand } from '../../../hook/useSendCommand';
10 9 import { unref } from 'vue';
11 10 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  11 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useDataFetch } from '../../../hook/socket/useSocket';
  13 + import { getSendValues } from '../config';
12 14
13 15 const props = defineProps<{
14 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -18,19 +20,45 @@
18 20
19 21 const getDesign = computed(() => {
20 22 const { option, persetOption } = props.config;
21   - const { componentInfo, attribute, attributeRename, attributeName } = option;
  23 + const {
  24 + componentInfo,
  25 + attribute,
  26 + attributeRename,
  27 + attributeName,
  28 + commandType,
  29 + extensionDesc,
  30 + codeType,
  31 + deviceCode,
  32 + customCommand,
  33 + } = option;
22 34 const { icon: presetIcon, iconColor: presetIconColor } = persetOption || {};
23 35 const { icon, iconColor } = componentInfo || {};
  36 +
24 37 return {
25 38 icon: icon ?? presetIcon,
26 39 iconColor: iconColor || presetIconColor,
27 40 attribute: attributeRename || attributeName || attribute,
  41 + extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  42 + commandType,
  43 + codeType,
  44 + deviceCode,
  45 + customCommand,
28 46 };
29 47 });
30 48
31 49 const { sendCommand, loading } = useSendCommand();
  50 +
32 51 const handleChange = async () => {
33   - const flag = await sendCommand(props.config.option, unref(checked));
  52 + const { option } = props.config || {};
  53 +
  54 + const { values, isModbusCommand, sendValue } =
  55 + (await getSendValues(option, unref(getDesign), unref(checked))) || {};
  56 +
  57 + const flag = await sendCommand(
  58 + values,
  59 + isModbusCommand ? sendValue : unref(checked),
  60 + isModbusCommand
  61 + );
34 62 if (!flag) checked.value = !unref(checked);
35 63 };
36 64
... ...
... ... @@ -32,6 +32,10 @@
32 32 transportType: value.transportType,
33 33 service: value.service,
34 34 command: value.command,
  35 + openService: value.openService,
  36 + closeService: value.closeService,
  37 + openCommand: value.openCommand,
  38 + closeCommand: value.closeCommand,
35 39 commandType: value.commandType,
36 40 callType: value.callType,
37 41 },
... ... @@ -45,6 +49,10 @@
45 49 ...record,
46 50 transportType: customCommand?.transportType || (record as Recordable).transportType,
47 51 service: customCommand?.service || (record as Recordable).service,
  52 + openService: customCommand?.openService || (record as Recordable).openService,
  53 + closeService: customCommand?.closeService || (record as Recordable).closeService,
  54 + openCommand: customCommand?.openCommand || (record as Recordable).openCommand,
  55 + closeCommand: customCommand?.closeCommand || (record as Recordable).closeCommand,
48 56 command: customCommand?.command || (record as Recordable).command,
49 57 commandType: customCommand?.commandType || (record as Recordable).commandType,
50 58 callType: customCommand?.callType || (record as Recordable).callType,
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { Spin } from 'ant-design-vue';
6   - import { computed, ref } from 'vue';
  5 + import { computed, ref, unref } from 'vue';
7 6 import { useComponentScale } from '../../../hook/useComponentScale';
8 7 import { useSendCommand } from '../../../hook/useSendCommand';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  10 + import { useDataFetch } from '../../../hook/socket/useSocket';
  11 + import { getSendValues } from '../config';
10 12
11 13 const props = defineProps<{
12 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -18,9 +20,23 @@
18 20
19 21 const getDesign = computed(() => {
20 22 const { option } = props.config;
21   - const { attribute, attributeRename, attributeName } = option;
  23 + const {
  24 + attribute,
  25 + attributeRename,
  26 + attributeName,
  27 + commandType,
  28 + extensionDesc,
  29 + codeType,
  30 + deviceCode,
  31 + customCommand,
  32 + } = option;
22 33 return {
23 34 attribute: attributeRename || attributeName || attribute,
  35 + extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  36 + commandType,
  37 + codeType,
  38 + deviceCode,
  39 + customCommand,
24 40 };
25 41 });
26 42
... ... @@ -28,7 +44,12 @@
28 44 const handleChange = async (event: Event) => {
29 45 const target = event.target as HTMLInputElement;
30 46 const value = target.checked;
31   - const flag = await sendCommand(props.config.option, value);
  47 + const { option } = props.config || {};
  48 +
  49 + const { values, isModbusCommand, sendValue } =
  50 + (await getSendValues(option, unref(getDesign), value)) || {};
  51 +
  52 + const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
32 53 flag ? (currentValue.value = value) : (target.checked = !value);
33 54 };
34 55
... ...
... ... @@ -13,6 +13,8 @@ export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
14 14 [ComponentConfigFieldEnum.CONTROL_BAR_COLOR]: '#0072ff',
15 15 [ComponentConfigFieldEnum.FONT_COLOR]: '#000000',
  16 + [ComponentConfigFieldEnum.MIN_NUMBER]: 0,
  17 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
16 18 };
17 19
18 20 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -3,6 +3,7 @@
3 3 import { useForm, BasicForm } from '/@/components/Form';
4 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 5 import { option } from './config';
  6 + import { nextTick } from 'vue';
6 7
7 8 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 9 schemas: [
... ... @@ -21,6 +22,46 @@
21 22 defaultValue: option.fontColor,
22 23 },
23 24 {
  25 + field: ComponentConfigFieldEnum.MIN_NUMBER,
  26 + label: '最小值',
  27 + component: 'InputNumber',
  28 + defaultValue: option.minNumber,
  29 + componentProps: ({ formActionType }) => {
  30 + const { setFieldsValue } = formActionType;
  31 + return {
  32 + placeholder: '请输入最小值',
  33 + onChange: async (e) => {
  34 + if ((typeof e === 'string' || typeof e === 'object') && !e) {
  35 + await nextTick();
  36 + setFieldsValue({ [ComponentConfigFieldEnum.MIN_NUMBER]: 0 });
  37 + }
  38 + },
  39 + };
  40 + },
  41 + },
  42 + {
  43 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  44 + label: '最大值',
  45 + component: 'InputNumber',
  46 + defaultValue: option.maxNumber,
  47 + componentProps: ({ formModel, formActionType }) => {
  48 + const { setFieldsValue } = formActionType;
  49 + return {
  50 + placeholder: '请输入最大值',
  51 + min: formModel[ComponentConfigFieldEnum.MIN_NUMBER] + 100,
  52 + onChange: async (e) => {
  53 + if (!e) {
  54 + await nextTick();
  55 + setFieldsValue({
  56 + [ComponentConfigFieldEnum.MAX_NUMBER]:
  57 + formModel[ComponentConfigFieldEnum.MIN_NUMBER] + 100,
  58 + });
  59 + }
  60 + },
  61 + };
  62 + },
  63 + },
  64 + {
24 65 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
25 66 label: '显示设备名称',
26 67 component: 'Checkbox',
... ...
... ... @@ -32,6 +32,10 @@
32 32 transportType: value.transportType,
33 33 service: value.service,
34 34 command: value.command,
  35 + openService: value.openService,
  36 + closeService: value.closeService,
  37 + openCommand: value.openCommand,
  38 + closeCommand: value.closeCommand,
35 39 commandType: value.commandType,
36 40 },
37 41 };
... ... @@ -42,10 +46,14 @@
42 46 const { customCommand } = record;
43 47 return setFieldsValue({
44 48 ...record,
45   - transportType: customCommand?.transportType,
46   - service: customCommand?.service,
47   - command: customCommand?.command,
48   - commandType: customCommand?.commandType,
  49 + transportType: customCommand?.transportType || (record as Recordable).transportType,
  50 + service: customCommand?.service || (record as Recordable).service,
  51 + openService: customCommand?.openService || (record as Recordable).openService,
  52 + closeService: customCommand?.closeService || (record as Recordable).closeService,
  53 + openCommand: customCommand?.openCommand || (record as Recordable).openCommand,
  54 + closeCommand: customCommand?.closeCommand || (record as Recordable).closeCommand,
  55 + command: customCommand?.command || (record as Recordable).command,
  56 + commandType: customCommand?.commandType || (record as Recordable).command,
49 57 } as unknown as Partial<CommonDataSourceBindValueType>);
50 58 };
51 59
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { Slider, Spin } from 'ant-design-vue';
6 5 import { computed, ref, unref } from 'vue';
7 6 import { useComponentScale } from '../../../hook/useComponentScale';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { useSendCommand } from '../../../hook/useSendCommand';
10 9 import { useReceiveValue } from '../../../hook/useReceiveValue';
  10 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  11 + import { useDataFetch } from '../../../hook/socket/useSocket';
  12 + import { TaskTypeEnum } from '/@/views/task/center/config';
  13 + import { useMessage } from '/@/hooks/web/useMessage';
  14 + import { SingleToHex } from '/@/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/config';
  15 + import { genModbusCommand } from '/@/api/task';
11 16
12 17 const props = defineProps<{
13 18 config: ComponentPropsConfigType<typeof option>;
... ... @@ -16,43 +21,155 @@
16 21 const sliderValue = ref<number>(33);
17 22 const oldSliderValue = ref<number>(33);
18 23 const noSendValue = ref<number>(0);
19   - const sMin = ref<number>(0);
20   - const sMax = ref<number>(100);
21 24 const sliderEl = ref<Nullable<InstanceType<typeof Slider>>>(null);
22 25
23 26 const { loading, sendCommand } = useSendCommand();
24 27
25 28 const getDesign = computed(() => {
26 29 const { option, persetOption } = props.config;
27   - const { componentInfo, attribute, attributeRename, attributeName } = option;
28   - const { controlBarColor: persetControlBarColor, fonColor: persetFontColor } =
29   - persetOption || {};
30   - const { controlBarColor, fontColor } = componentInfo || {};
  30 + const {
  31 + componentInfo,
  32 + attribute,
  33 + attributeRename,
  34 + attributeName,
  35 + commandType,
  36 + extensionDesc,
  37 + codeType,
  38 + deviceCode,
  39 + customCommand,
  40 + } = option;
  41 + const {
  42 + controlBarColor: persetControlBarColor,
  43 + fonColor: persetFontColor,
  44 + minNumber: persetMinNumber,
  45 + maxNumber: persetMaxNumber,
  46 + } = persetOption || {};
  47 + const { controlBarColor, fontColor, minNumber, maxNumber } = componentInfo || {};
31 48 return {
32 49 attribute: attributeRename || attributeName || attribute,
33 50 controlBarColor: controlBarColor ?? persetControlBarColor,
34 51 fontColor: fontColor ?? persetFontColor,
  52 + minNumber: minNumber ?? persetMinNumber,
  53 + maxNumber: maxNumber ?? persetMaxNumber,
  54 + extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  55 + commandType,
  56 + codeType,
  57 + deviceCode,
  58 + customCommand,
35 59 };
36 60 });
37 61
38 62 const index = ref<number>(0);
  63 + const afterValue = ref<number>(0); //点击Slider获取的值
39 64
40 65 const handleChange = async (e) => {
41 66 index.value++;
  67 + afterValue.value = e;
42 68 if (index.value == 1) {
43 69 oldSliderValue.value = unref(sliderValue);
  70 + return; //这个是因为设置了最大值时,当sliderValue的值大于最大值时只会显示设置的最大值,会执行一次change
44 71 }
45 72 sliderValue.value = e;
46 73 };
47 74
48 75 const handleAfterChange = async () => {
49 76 unref(sliderEl)?.blur();
  77 + if (unref(sliderValue) == unref(afterValue)) return;
  78 + sliderValue.value = afterValue.value; //这一步是因为当我们是点击不是拖动时候,会让值更新不了,所以需要赋值一次
  79 + };
  80 +
  81 + const { createMessage } = useMessage();
  82 +
  83 + // 获取小数
  84 + const getFloatPart = (number: string | number) => {
  85 + const isLessZero = Number(number) < 0;
  86 + number = number.toString();
  87 + const floatPartStartIndex = number.indexOf('.');
  88 + const value = ~floatPartStartIndex
  89 + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
  90 + : '0';
  91 + return Number(value);
  92 + };
  93 +
  94 + const getArray = (values) => {
  95 + const str = values.replace(/\s+/g, '');
  96 + const array: any = [];
  97 +
  98 + for (let i = 0; i < str.length; i += 4) {
  99 + const chunk = parseInt(str.substring(i, i + 4), 16);
  100 + array.push(chunk);
  101 + }
  102 + return array;
  103 + };
  104 +
  105 + const getSendValue = async (value: number) => {
  106 + const { extensionDesc, codeType, deviceCode, customCommand } = unref(getDesign) || {};
  107 + const { transportType } = customCommand || {};
  108 + const { registerAddress, actionType, zoomFactor } = extensionDesc || {};
  109 + const newZoomValue = zoomFactor ? Number(zoomFactor) : 1;
  110 + if (transportType == 'TCP' && codeType == TaskTypeEnum.MODBUS_RTU) {
  111 + if (!deviceCode) {
  112 + createMessage.warning('当前设备没有设置地址码');
  113 + return;
  114 + }
  115 + const modbusForm = ref({
  116 + crc: 'CRC_16_LOWER',
  117 + deviceCode: deviceCode,
  118 + method: actionType == '16' ? '10' : actionType,
  119 + registerAddress,
  120 + registerNumber: 1,
  121 + registerValues: [value],
  122 + }) as any;
  123 + if (!actionType) {
  124 + createMessage.warning('当前物模型扩展描述没有填写');
  125 + return;
  126 + }
  127 + if (actionType == '06') {
  128 + const newValue = Math.trunc(value) * newZoomValue + getFloatPart(value) * newZoomValue;
  129 + if (newValue % 1 != 0) {
  130 + createMessage.warning(`值必须是整数,缩放因子为${unref(newZoomValue)}`);
  131 + return;
  132 + }
  133 +
  134 + if (newValue > 65535) {
  135 + createMessage.warning(`值不能超过65535,缩放因子是${unref(newZoomValue)}`);
  136 + return;
  137 + }
  138 + modbusForm.value.registerValues = [newValue];
  139 + }
  140 + if (actionType == '05') {
  141 + if (Number(value) != 0 || Number(value) != 1) {
  142 + createMessage.warning(`当前物模型仅支持值为0和1`);
  143 + return;
  144 + }
  145 + }
  146 + if (actionType == '16' || actionType == '10') {
  147 + const regex = /^-?\d+(\.\d{0,2})?$/;
  148 + const values =
  149 + Math.trunc(value) * unref(newZoomValue) + getFloatPart(value) * unref(newZoomValue);
  150 + if (!regex.test(values as any)) {
  151 + createMessage.warning(`值精确到两位小数,缩放因子是${unref(newZoomValue)}`);
  152 + return;
  153 + }
  154 + const newValue = values == 0 ? [0, 0] : getArray(SingleToHex(values));
  155 + modbusForm.value.registerValues = newValue;
  156 + modbusForm.value.registerNumber = 2;
  157 + modbusForm.value.method = '10';
  158 + }
  159 + const sendValue = await genModbusCommand(unref(modbusForm));
  160 + return sendValue;
  161 + }
50 162 };
51 163
52 164 const handleBlur = async () => {
53 165 if (unref(oldSliderValue) !== unref(sliderValue)) {
54   - console.log('effect');
55   - const flag = await sendCommand(props.config.option, unref(sliderValue));
  166 + const { codeType, customCommand } = unref(getDesign) || {};
  167 + const { transportType } = customCommand || {};
  168 + const sendValue = ref<any>(unref(sliderValue));
  169 + if (transportType == 'TCP' && codeType == TaskTypeEnum.MODBUS_RTU) {
  170 + sendValue.value = await getSendValue(unref(sliderValue));
  171 + }
  172 + const flag = await sendCommand(props.config.option, unref(sendValue), true);
56 173 flag
57 174 ? ((sliderValue.value = unref(sliderValue)),
58 175 (oldSliderValue.value = sliderValue.value),
... ... @@ -90,8 +207,8 @@
90 207 :style="{ '--slider-color': getDesign.controlBarColor }"
91 208 class="no-drag"
92 209 :value="sliderValue"
93   - :min="sMin"
94   - :max="sMax"
  210 + :min="getDesign.minNumber"
  211 + :max="getDesign.maxNumber"
95 212 @change="handleChange"
96 213 @afterChange="handleAfterChange"
97 214 @blur="handleBlur"
... ...
... ... @@ -32,7 +32,12 @@
32 32 transportType: value.transportType,
33 33 service: value.service,
34 34 command: value.command,
  35 + openService: value.openService,
  36 + closeService: value.closeService,
  37 + openCommand: value.openCommand,
  38 + closeCommand: value.closeCommand,
35 39 commandType: value.commandType,
  40 + callType: value.callType,
36 41 },
37 42 };
38 43 return value;
... ... @@ -42,10 +47,15 @@
42 47 const { customCommand } = record;
43 48 return setFieldsValue({
44 49 ...record,
45   - transportType: customCommand?.transportType,
46   - service: customCommand?.service,
47   - command: customCommand?.command,
48   - commandType: customCommand?.commandType,
  50 + transportType: customCommand?.transportType || (record as Recordable).transportType,
  51 + service: customCommand?.service || (record as Recordable).service,
  52 + openService: customCommand?.openService || (record as Recordable).openService,
  53 + closeService: customCommand?.closeService || (record as Recordable).closeService,
  54 + openCommand: customCommand?.openCommand || (record as Recordable).openCommand,
  55 + closeCommand: customCommand?.closeCommand || (record as Recordable).closeCommand,
  56 + command: customCommand?.command || (record as Recordable).command,
  57 + commandType: customCommand?.commandType || (record as Recordable).commandType,
  58 + callType: customCommand?.callType || (record as Recordable).callType,
49 59 } as unknown as Partial<CommonDataSourceBindValueType>);
50 60 };
51 61
... ...
1 1 <script lang="ts" setup>
2   - import {
3   - ComponentPropsConfigType,
4   - MultipleDataFetchUpdateFn,
5   - } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
6 3 import { option } from './config';
7   - import { useMultipleDataFetch } from '/@/views/visual/packages/hook/useSocket';
8 4 import { SvgIcon } from '/@/components/Icon';
9 5 import { Switch } from 'ant-design-vue';
10 6 import { computed, ref } from 'vue';
... ... @@ -12,17 +8,22 @@
12 8 import { useSendCommand } from '../../../hook/useSendCommand';
13 9 import { unref } from 'vue';
14 10 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  11 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  13 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  14 + import { useReceiveValue } from '../../../hook/useReceiveValue';
  15 + import { DataSource } from '/@/views/visual/palette/types';
  16 + import { getSendValues, CommandTypeEnumLIst } from '../config';
15 17
16 18 const props = defineProps<{
17 19 config: ComponentPropsConfigType<typeof option>;
18 20 }>();
19 21
20   - const checked = ref(false);
21   -
22 22 const svgList = ref<any>([
23 23 {
24 24 value: 26.2,
25   - name: 'wendu',
  25 + deviceName: '光照设备',
  26 + attributeName: '光照',
26 27 icon: 'zongfushe',
27 28 unit: 'kw',
28 29 iconColor: '#367BFF',
... ... @@ -32,7 +33,8 @@
32 33 },
33 34 {
34 35 value: 53.7,
35   - name: 'shidu',
  36 + deviceName: '风机设备',
  37 + attributeName: '风机',
36 38 icon: 'guangzhaoqiangdu',
37 39 unit: '℃',
38 40 iconColor: '#FFA000',
... ... @@ -53,42 +55,77 @@
53 55 } = persetOption || {};
54 56 return {
55 57 dataSource: dataSource.map((item) => {
56   - const { fontColor, icon, iconColor, unit } = item.componentInfo;
57   - const { attribute, attributeRename } = item;
  58 + const { fontColor, icon, iconColor, unit, showDeviceName } = item.componentInfo;
  59 + const {
  60 + attribute,
  61 + attributeRename,
  62 + attributeName,
  63 + deviceId,
  64 + deviceName,
  65 + deviceRename,
  66 + commandType,
  67 + extensionDesc,
  68 + codeType,
  69 + deviceCode,
  70 + customCommand,
  71 + } = item;
58 72 return {
59 73 unit: unit ?? persetUnit,
60 74 fontColor: fontColor ?? persetFontColor,
61 75 icon: icon ?? persetIcon,
62 76 iconColor: iconColor ?? persetIconColor,
63   - attribute: attribute || attributeRename,
64   - attributeRename,
  77 + attribute: attribute,
  78 + attributeName: attributeRename || attributeName,
  79 + showDeviceName,
  80 + deviceName: deviceRename || deviceName,
  81 + id: deviceId,
  82 + extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  83 + commandType,
  84 + codeType,
  85 + deviceCode,
  86 + customCommand,
65 87 };
66 88 }),
67 89 };
68 90 });
69 91
70   - const { sendCommand, loading } = useSendCommand();
71   - const handleChange = async (index: number) => {
72   - const flag = await sendCommand(props.config.option, unref(checked));
73   - if (!flag) svgList[index].checked = !svgList[index].checked;
  92 + const { loading, sendCommand } = useSendCommand();
  93 + const handleChange = async (index: number, checked: Boolean) => {
  94 + const { heightPx, itemHeightRatio, itemWidthRatio, mode, widthPx, dataSource } =
  95 + props.config.option;
  96 + const data = {
  97 + ...dataSource?.[index],
  98 + heightPx,
  99 + itemHeightRatio,
  100 + itemWidthRatio,
  101 + mode,
  102 + widthPx,
  103 + } as DataSource;
  104 + const { values, isModbusCommand, sendValue } =
  105 + (await getSendValues(data, unref(getDesign).dataSource[index], checked)) || {};
  106 +
  107 + const flag = await sendCommand(values, isModbusCommand ? sendValue : checked, isModbusCommand);
  108 + if (!flag) controlList.value[index].checked = !checked;
74 109 };
75 110
76   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
77   - const { data = {} } = message;
78   - const { dataSource } = unref(getDesign);
79   - svgList.value.length = 0;
80   - svgList.value = dataSource.map((item) => {
81   - const { icon, iconColor, unit, fontColor, attribute } = item || {};
82   - const [latest] = data[attribute] || [];
83   - const [_timespan, value] = latest || [];
84   - return {
85   - value: Number(value),
86   - name: attribute,
87   - icon,
88   - unit,
89   - iconColor,
90   - fontColor,
91   - };
  111 + const { forEachGroupMessage } = useReceiveMessage();
  112 + const { getNumberValue } = useReceiveValue();
  113 +
  114 + const controlList = ref(
  115 + props.config.option.dataSource
  116 + ? unref(getDesign).dataSource.map((item) => {
  117 + return { ...item, checked: false };
  118 + })
  119 + : svgList
  120 + );
  121 +
  122 + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => {
  123 + forEachGroupMessage(message, deviceId, attribute, (attribute, value) => {
  124 + controlList.value.forEach((item) => {
  125 + if (item.id === deviceId && item.attribute === attribute && value) {
  126 + item.checked = Boolean(getNumberValue(value));
  127 + }
  128 + });
92 129 });
93 130 };
94 131
... ... @@ -101,7 +138,7 @@
101 138 <DeviceName :config="config" />
102 139 <div
103 140 style="width: 86%"
104   - v-for="(item, index) in svgList"
  141 + v-for="(item, index) in controlList"
105 142 :key="item.id"
106 143 class="flex justify-between items-center"
107 144 >
... ... @@ -113,10 +150,22 @@
113 150 :style="{ color: item.iconColor }"
114 151 />
115 152
116   - <div class="text-gray-500 text-lg truncate ml-6">{{ item.attribute || '温度' }}</div>
  153 + <div
  154 + class="text-gray-500 truncate ml-6"
  155 + :style="{ fontSize: getRatio ? '18px' : getRatio * 18 + 'px' }"
  156 + >{{
  157 + `${item.deviceName} - ${
  158 + item.commandType ? CommandTypeEnumLIst[item.commandType].name : item.attributeName
  159 + }`
  160 + }}</div
  161 + >
117 162 </div>
118 163
119   - <Switch v-model:checked="item.checked" :loading="loading" @change="handleChange(index)" />
  164 + <Switch
  165 + v-model:checked="item.checked"
  166 + :loading="loading"
  167 + @change="handleChange(index, item.checked)"
  168 + />
120 169 </div>
121 170 </main>
122 171 </template>
... ...
  1 +import { ref, unref } from 'vue';
  2 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  3 +import { CommandTypeEnum } from '/@/views/rule/linkedge/config/config.data';
  4 +import { TaskTypeEnum } from '/@/views/task/center/config';
  5 +import { genModbusCommand } from '/@/api/task';
  6 +import { useMessage } from '/@/hooks/web/useMessage';
  7 +import { SingleToHex } from '/@/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/config';
  8 +
  9 +const getArray = (values) => {
  10 + const str = values.replace(/\s+/g, '');
  11 + const array: any = [];
  12 +
  13 + for (let i = 0; i < str.length; i += 4) {
  14 + const chunk = parseInt(str.substring(i, i + 4), 16);
  15 + array.push(chunk);
  16 + }
  17 + return array;
  18 +};
  19 +
  20 +export const getSendValues = async (option, getDesign, checked) => {
  21 + const values: any = option;
  22 + const isModbusCommand = ref<boolean>(false); //判断是否是TCP设备-> 且设备标识符是modeBUs,切选择是下发命令类型是属性
  23 + const { extensionDesc, commandType, codeType, deviceCode, customCommand } = getDesign || {};
  24 + const { registerAddress, actionType } = extensionDesc || {};
  25 + const { openCommand, closeCommand, transportType } = customCommand || {};
  26 + const modBUS = ref<any>({});
  27 + const sendValue = ref();
  28 +
  29 + const { createMessage } = useMessage();
  30 + //判断是不是TCP类型设备
  31 + if (transportType === TransportTypeEnum.TCP) {
  32 + if (
  33 + //判断TCP下发类型是否是自定义还是服务
  34 + commandType === CommandTypeEnum.CUSTOM.toString() ||
  35 + commandType == CommandTypeEnum.SERVICE.toString()
  36 + ) {
  37 + values.customCommand.command = checked ? openCommand : closeCommand;
  38 + values.customCommand.command = values.customCommand.command
  39 + .replaceAll(/\s/g, '')
  40 + .toUpperCase();
  41 + }
  42 + if (
  43 + //判断命令下发类型是不是属性 且是modbus
  44 + commandType === CommandTypeEnum.ATTRIBUTE.toString() &&
  45 + codeType === TaskTypeEnum.MODBUS_RTU
  46 + ) {
  47 + if (!deviceCode) {
  48 + createMessage.warning('当前设备没有设置地址码');
  49 + return;
  50 + }
  51 + if (!actionType) {
  52 + createMessage.warning('当前物模型扩展描述没有填写');
  53 + return;
  54 + }
  55 + isModbusCommand.value = true;
  56 + modBUS.value = {
  57 + crc: 'CRC_16_LOWER',
  58 + deviceCode: deviceCode,
  59 + method: actionType == '16' ? '10' : actionType,
  60 + registerAddress,
  61 + registerNumber: 1,
  62 + registerValues: [Number(checked)],
  63 + };
  64 +
  65 + if (actionType == '16' || actionType == '10') {
  66 + const newValue = Number(checked) == 0 ? [0, 0] : getArray(SingleToHex(Number(checked)));
  67 + modBUS.value.registerValues = newValue;
  68 + modBUS.value.registerNumber = 2;
  69 + modBUS.value.method = '10';
  70 + }
  71 +
  72 + sendValue.value = await genModbusCommand(unref(modBUS));
  73 + }
  74 + }
  75 +
  76 + return { values, sendValue: unref(sendValue), isModbusCommand: unref(isModbusCommand) };
  77 +};
  78 +
  79 +export const CommandTypeEnumLIst = {
  80 + '0': { CUSTOM: 0, name: '自定义' },
  81 + '1': { CUSTOM: 0, name: '服务' },
  82 + '2': { CUSTOM: 0, name: '属性' },
  83 +};
... ...
... ... @@ -2,12 +2,12 @@ import { ControlComponentSlidingSwitchConfig } from './ControlComponentSlidingSw
2 2 import { ControlComponentSwitchWithIconConfig } from './ControlComponentSwitchWithIcon';
3 3 import { ControlComponentToggleSwitchConfig } from './ControlComponentToggleSwitch';
4 4 import { LateralNumericalControlConfig } from './LateralNumericalControl';
5   -// import { SwitchListConfig } from './SwitchList';
  5 +import { SwitchListConfig } from './SwitchList';
6 6
7 7 export const ControlList = [
8 8 ControlComponentSwitchWithIconConfig,
9 9 ControlComponentSlidingSwitchConfig,
10 10 ControlComponentToggleSwitchConfig,
11 11 LateralNumericalControlConfig,
12   - // SwitchListConfig,
  12 + SwitchListConfig,
13 13 ];
... ...
... ... @@ -12,8 +12,9 @@ import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
12 12 export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
14 14 [ComponentConfigFieldEnum.UNIT]: 'm',
  15 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
15 16 [ComponentConfigFieldEnum.FONT_COLOR]: '#fff',
16   - [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  17 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
17 18 [ComponentConfigFieldEnum.FLOWMETER_CONFIG]: {
18 19 [ComponentConfigFieldEnum.BACKGROUND_COLOR]: '#8badcb',
19 20 [ComponentConfigFieldEnum.WAVE_FIRST]: '#4579e2',
... ...
... ... @@ -4,7 +4,7 @@
4 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 5 import { option } from './config';
6 6 import { ComponentInfo } from '/@/views/visual/palette/types';
7   - import { toRaw } from 'vue';
  7 + import { nextTick, toRaw } from 'vue';
8 8
9 9 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
10 10 schemas: [
... ... @@ -50,11 +50,38 @@
50 50 defaultValue: option.unit,
51 51 },
52 52 {
  53 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  54 + label: '最大值',
  55 + component: 'InputNumber',
  56 + defaultValue: 100,
  57 + componentProps: ({ formActionType }) => {
  58 + const { setFieldsValue } = formActionType;
  59 + return {
  60 + placeholder: '请输入最大值',
  61 + min: 100,
  62 + onChange: async (e) => {
  63 + if (!e) {
  64 + await nextTick();
  65 + setFieldsValue({
  66 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  67 + });
  68 + }
  69 + },
  70 + };
  71 + },
  72 + },
  73 + {
53 74 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
54 75 label: '显示设备名称',
55 76 component: 'Checkbox',
56 77 defaultValue: option.showDeviceName,
57 78 },
  79 + {
  80 + field: ComponentConfigFieldEnum.SHOW_TIME,
  81 + label: '显示时间',
  82 + component: 'Checkbox',
  83 + defaultValue: option.showDeviceName,
  84 + },
58 85 ],
59 86 showActionButtonGroup: false,
60 87 labelWidth: 120,
... ...
... ... @@ -5,6 +5,6 @@ const componentKeys = useComponentKeys('CircleFlowmeter');
5 5
6 6 export const CircleFlowmeterConfig: ConfigType = {
7 7 ...componentKeys,
8   - title: '量计2',
  8 + title: '量计2',
9 9 package: PackagesCategoryEnum.FLOWMETER,
10 10 };
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { computed } from 'vue';
6 5 import { ref } from 'vue';
7 6 import { unref } from 'vue';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  9 + import { useDataFetch } from '../../../hook/socket/useSocket';
  10 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 11
11 12 const props = defineProps<{
12 13 config: ComponentPropsConfigType<typeof option>;
... ... @@ -19,12 +20,14 @@
19 20 const getDesign = computed(() => {
20 21 const { option, persetOption } = props.config;
21 22 const { componentInfo, attribute, attributeName, attributeRename } = option;
22   - const { flowmeterConfig, unit, fontColor } = componentInfo || {};
  23 + const { flowmeterConfig, unit, fontColor, showTime, maxNumber } = componentInfo || {};
23 24 const { backgroundColor, waveFirst, waveSecond, waveThird } = flowmeterConfig || {};
24 25 const {
25 26 flowmeterConfig: presetFlowmeterConfig,
26 27 unit: persetUnit,
27 28 fontColor: presetFontColor,
  29 + showTime: persetShowTime,
  30 + maxNumber: persetMaxNumber,
28 31 } = persetOption || {};
29 32 const {
30 33 backgroundColor: presetBackgroundColor,
... ... @@ -40,6 +43,8 @@
40 43 unit: unit ?? persetUnit,
41 44 fontColor: fontColor ?? presetFontColor,
42 45 attribute: attributeRename || attributeName || attribute,
  46 + showTime: showTime ?? persetShowTime,
  47 + maxNumber: maxNumber ?? persetMaxNumber,
43 48 };
44 49 });
45 50
... ... @@ -55,17 +60,26 @@
55 60
56 61 const getWaveHeight = computed(() => {
57 62 const value = unref(currentValue);
58   - return value <= 0 ? 0 : -value - 15;
  63 + const size = ref(1);
  64 + if (unref(getDesign).maxNumber > 100) {
  65 + size.value = 100 / unref(getDesign).maxNumber;
  66 + }
  67 + return value * unref(size) <= 0 ? 0 : -(value * unref(size)) - 15;
59 68 });
60 69
61 70 const getHeight = computed(() => {
62 71 const value = unref(currentValue);
63   - return value >= 100 ? 0 : 100 - value + 10;
  72 + const size = ref(1);
  73 + if (unref(getDesign).maxNumber > 100) {
  74 + size.value = 100 / unref(getDesign).maxNumber;
  75 + }
  76 + return value * unref(size) >= 100 ? 0 : 100 - value * unref(size) + 10;
64 77 });
65 78
66 79 const updateFn: DataFetchUpdateFn = (message, attribute) => {
67 80 const { data = {} } = message;
68 81 const [latest] = data[attribute] || [];
  82 + if (!latest.length) return;
69 83 const [timespan, value] = latest;
70 84 time.value = timespan;
71 85 currentValue.value = Number(value);
... ... @@ -75,7 +89,10 @@
75 89 </script>
76 90
77 91 <template>
78   - <main class="w-full h-full flex flex-col justify-center items-center relative">
  92 + <main
  93 + class="w-full h-full flex flex-col justify-center items-center relative"
  94 + :class="!getDesign.showTime && 'p-5'"
  95 + >
79 96 <DeviceName :config="config" />
80 97 <svg
81 98 class="waves-rect"
... ... @@ -120,7 +137,7 @@
120 137 <div class="text-gray-500 text-sm truncate" style="flex: 0 0 20px">{{
121 138 getDesign.attribute || '属性'
122 139 }}</div>
123   - <UpdateTime :time="time" />
  140 + <UpdateTime v-show="getDesign.showTime" :time="time" />
124 141 </main>
125 142 </template>
126 143
... ...
... ... @@ -11,7 +11,9 @@ import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
11 11
12 12 export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  14 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
14 15 [ComponentConfigFieldEnum.UNIT]: 'm',
  16 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
15 17 [ComponentConfigFieldEnum.FLOWMETER_CONFIG]: {
16 18 [ComponentConfigFieldEnum.BACKGROUND_COLOR]: '#8badcb',
17 19 [ComponentConfigFieldEnum.WAVE_FIRST]: '#4579e2',
... ...
... ... @@ -4,7 +4,7 @@
4 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 5 import { option } from './config';
6 6 import { ComponentInfo } from '/@/views/visual/palette/types';
7   - import { toRaw } from 'vue';
  7 + import { nextTick, toRaw } from 'vue';
8 8
9 9 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
10 10 schemas: [
... ... @@ -49,12 +49,45 @@
49 49 component: 'Input',
50 50 defaultValue: option.unit,
51 51 },
  52 + // {
  53 + // field: ComponentConfigFieldEnum.MIN_NUMBER,
  54 + // label: '最小值',
  55 + // component: 'InputNumber',
  56 + // defaultValue: 0,
  57 + // },
  58 + {
  59 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  60 + label: '最大值',
  61 + component: 'InputNumber',
  62 + defaultValue: 100,
  63 + componentProps: ({ formActionType }) => {
  64 + const { setFieldsValue } = formActionType;
  65 + return {
  66 + placeholder: '请输入最大值',
  67 + min: 100,
  68 + onChange: async (e) => {
  69 + if (!e) {
  70 + await nextTick();
  71 + setFieldsValue({
  72 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  73 + });
  74 + }
  75 + },
  76 + };
  77 + },
  78 + },
52 79 {
53 80 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
54 81 label: '显示设备名称',
55 82 component: 'Checkbox',
56 83 defaultValue: option.showDeviceName,
57 84 },
  85 + {
  86 + field: ComponentConfigFieldEnum.SHOW_TIME,
  87 + label: '显示时间',
  88 + component: 'Checkbox',
  89 + defaultValue: option.showTime,
  90 + },
58 91 ],
59 92 showActionButtonGroup: false,
60 93 labelWidth: 120,
... ...
... ... @@ -5,6 +5,6 @@ const componentKeys = useComponentKeys('RectFlowmeter');
5 5
6 6 export const RectFlowmeteConfig: ConfigType = {
7 7 ...componentKeys,
8   - title: '量计1',
  8 + title: '量计1',
9 9 package: PackagesCategoryEnum.FLOWMETER,
10 10 };
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { computed } from 'vue';
6 5 import { ref } from 'vue';
7 6 import { unref } from 'vue';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  9 + import { useDataFetch } from '../../../hook/socket/useSocket';
  10 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 11
11 12 const props = defineProps<{
12 13 config: ComponentPropsConfigType<typeof option>;
... ... @@ -19,12 +20,14 @@
19 20 const getDesign = computed(() => {
20 21 const { option, persetOption } = props.config;
21 22 const { componentInfo, attribute, attributeName, attributeRename } = option;
22   - const { flowmeterConfig, unit, fontColor } = componentInfo || {};
  23 + const { flowmeterConfig, unit, fontColor, showTime, maxNumber } = componentInfo || {};
23 24 const { backgroundColor, waveFirst, waveSecond, waveThird } = flowmeterConfig || {};
24 25 const {
25 26 flowmeterConfig: presetFlowmeterConfig,
26 27 unit: persetUnit,
27 28 fontColor: presetFontColor,
  29 + showTime: persetShowTime,
  30 + maxNumber: persetMaxNumber,
28 31 } = persetOption || {};
29 32 const {
30 33 backgroundColor: presetBackgroundColor,
... ... @@ -40,23 +43,34 @@
40 43 unit: unit ?? persetUnit,
41 44 fontColor: fontColor ?? presetFontColor,
42 45 attribute: attributeRename || attributeName || attribute,
  46 + showTime: showTime ?? persetShowTime,
  47 + maxNumber: maxNumber ?? persetMaxNumber,
43 48 };
44 49 });
45 50
46 51 const getWaveHeight = computed(() => {
47 52 const value = unref(currentValue);
48   - return value <= 0 ? 0 : -value - 15;
  53 + const size = ref(1);
  54 + if (unref(getDesign).maxNumber > 100) {
  55 + size.value = 100 / unref(getDesign).maxNumber;
  56 + }
  57 + return value * unref(size) <= 0 ? 0 : -(value * unref(size)) - 15;
49 58 });
50 59
51 60 const getHeight = computed(() => {
52 61 const value = unref(currentValue);
53   - return value >= 100 ? 0 : 100 - value + 10;
  62 + const size = ref(1);
  63 + if (unref(getDesign).maxNumber > 100) {
  64 + size.value = 100 / unref(getDesign).maxNumber;
  65 + }
  66 + return value * unref(size) >= 100 ? 0 : 100 - value * unref(size) + 10;
54 67 });
55 68
56 69 const updateFn: DataFetchUpdateFn = (message, attribute) => {
57 70 const { data = {} } = message;
58 71 const [latest] = data[attribute] || [];
59 72 const [timespan, value] = latest;
  73 + if (!latest.length) return;
60 74 time.value = timespan;
61 75 currentValue.value = Number(value);
62 76 };
... ... @@ -65,7 +79,10 @@
65 79 </script>
66 80
67 81 <template>
68   - <main class="w-full h-full flex flex-col justify-center items-center relative">
  82 + <main
  83 + class="w-full h-full flex flex-col justify-center items-center relative"
  84 + :class="!getDesign.showTime && 'p-5'"
  85 + >
69 86 <DeviceName :config="config" />
70 87 <svg
71 88 class="waves-rect"
... ... @@ -113,7 +130,7 @@
113 130 <div class="text-gray-500 text-sm truncate" style="flex: 0 0 20px">{{
114 131 getDesign.attribute || '属性'
115 132 }}</div>
116   - <UpdateTime :time="time" />
  133 + <UpdateTime v-show="getDesign.showTime" :time="time" />
117 134 </main>
118 135 </template>
119 136
... ...
... ... @@ -12,6 +12,7 @@ import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
12 12 export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.FONT_COLOR]: '#',
14 14 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  15 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
15 16 };
16 17
17 18 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -19,6 +19,11 @@
19 19 label: '显示设备名称',
20 20 component: 'Checkbox',
21 21 },
  22 + {
  23 + field: ComponentConfigFieldEnum.SHOW_TIME,
  24 + label: '显示时间',
  25 + component: 'Checkbox',
  26 + },
22 27 ],
23 28 showActionButtonGroup: false,
24 29 labelWidth: 120,
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { ref } from 'vue';
6 5 import { computed } from 'vue';
7 6 import { unref } from 'vue';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  9 + import { useDataFetch } from '../../../hook/socket/useSocket';
  10 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 11
11 12 const props = defineProps<{
12 13 config: ComponentPropsConfigType<typeof option>;
... ... @@ -36,18 +37,20 @@
36 37
37 38 const getDesign = computed(() => {
38 39 const { persetOption, option } = props.config;
39   - const { fontColor: presetFontColor } = persetOption || {};
  40 + const { fontColor: presetFontColor, showTime: persetShowTime } = persetOption || {};
40 41 const { componentInfo, attribute, attributeName, attributeRename } = option || {};
41   - const { fontColor } = componentInfo || {};
  42 + const { fontColor, showTime } = componentInfo || {};
42 43 return {
43 44 fontColor: fontColor ?? presetFontColor,
44 45 attribute: attributeRename || attributeName || attribute,
  46 + showTime: showTime ?? persetShowTime,
45 47 };
46 48 });
47 49
48 50 const updateFn: DataFetchUpdateFn = (message, attribute) => {
49 51 const { data = {} } = message;
50 52 const [latest] = data[attribute] || [];
  53 + if (!latest.length) return;
51 54 const [timespan, value] = latest;
52 55 time.value = timespan;
53 56
... ... @@ -58,7 +61,10 @@
58 61 </script>
59 62
60 63 <template>
61   - <main class="w-full h-full flex flex-col justify-center items-center relative">
  64 + <main
  65 + class="w-full h-full flex flex-col justify-center items-center relative"
  66 + :class="!getDesign.showTime && 'p-5'"
  67 + >
62 68 <DeviceName :config="config" />
63 69 <svg class="flowmeter-thermometer" viewBox="0 0 200 250" xmlns="http://www.w3.org/2000/svg">
64 70 <defs>
... ... @@ -255,7 +261,7 @@
255 261 <div class="text-gray-500 text-sm truncate" style="flex: 0 0 20px">{{
256 262 getDesign.attribute || '属性'
257 263 }}</div>
258   - <UpdateTime :time="time" />
  264 + <UpdateTime v-show="getDesign.showTime" :time="time" />
259 265 </main>
260 266 </template>
261 267
... ...
... ... @@ -13,6 +13,7 @@ export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.FONT_COLOR]: '#000',
14 14 [ComponentConfigFieldEnum.UNIT]: 'kw/h',
15 15 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  16 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
16 17 };
17 18
18 19 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -28,6 +28,12 @@
28 28 component: 'Checkbox',
29 29 defaultValue: option.showDeviceName,
30 30 },
  31 + {
  32 + field: ComponentConfigFieldEnum.SHOW_TIME,
  33 + label: '显示时间',
  34 + component: 'Checkbox',
  35 + defaultValue: option.showDeviceName,
  36 + },
31 37 ],
32 38 showActionButtonGroup: false,
33 39 labelWidth: 120,
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { ref, computed, unref } from 'vue';
6 5 import { Space } from 'ant-design-vue';
7 6 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
8 7 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { useDataFetch } from '../../../hook/socket/useSocket';
  10 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 11
11 12 const props = defineProps<{
12 13 config: ComponentPropsConfigType<typeof option>;
... ... @@ -24,7 +25,6 @@
24 25
25 26 if (_value.length > max) return ''.padStart(max, '9');
26 27 if (_value.length < max) return _value.padStart(max, '0');
27   -
28 28 return _value;
29 29 });
30 30
... ... @@ -37,6 +37,8 @@
37 37
38 38 let _value = number.toString().split('.')[1] || '0';
39 39
  40 + if (_value.length == 1) return _value + '0';
  41 +
40 42 if (_value.length < keepNumber) return ''.padStart(keepNumber, '0');
41 43
42 44 if (_value.length > keepNumber) return _value.slice(0, 2);
... ... @@ -47,18 +49,24 @@
47 49 const getDesign = computed(() => {
48 50 const { option, persetOption } = props.config;
49 51 const { componentInfo, attribute, attributeRename, attributeName } = option;
50   - const { fontColor: presetFontColor, unit: presetUnit } = persetOption || {};
51   - const { unit, fontColor } = componentInfo || {};
  52 + const {
  53 + fontColor: presetFontColor,
  54 + unit: presetUnit,
  55 + showTime: persetShowTime,
  56 + } = persetOption || {};
  57 + const { unit, fontColor, showTime } = componentInfo || {};
52 58 return {
53 59 unit: unit ?? presetUnit,
54 60 fontColor: fontColor ?? presetFontColor,
55 61 attribute: attributeRename || attributeName || attribute,
  62 + showTime: showTime ?? persetShowTime,
56 63 };
57 64 });
58 65
59 66 const updateFn: DataFetchUpdateFn = (message, attribute) => {
60 67 const { data = {} } = message;
61 68 const [latest] = data[attribute] || [];
  69 + if (!latest.length) return;
62 70 const [timespan, value] = latest;
63 71 time.value = timespan;
64 72 currentValue.value = isNaN(value as unknown as number) ? 0 : Number(value);
... ... @@ -70,7 +78,10 @@
70 78 </script>
71 79
72 80 <template>
73   - <main class="w-full h-full flex flex-col justify-center items-center">
  81 + <main
  82 + class="w-full h-full flex flex-col justify-center items-center"
  83 + :class="!getDesign.showTime && 'p-5'"
  84 + >
74 85 <div class="flex flex-col w-full h-full">
75 86 <DeviceName class="text-center" :config="config" />
76 87
... ... @@ -133,7 +144,7 @@
133 144 <span>{{ getDesign.attribute || '电表' }}</span>
134 145 </div>
135 146
136   - <UpdateTime :time="time" />
  147 + <UpdateTime v-show="getDesign.showTime" :time="time" />
137 148 </div>
138 149 </main>
139 150 </template>
... ...
... ... @@ -22,6 +22,8 @@ export enum GradientColor {
22 22 export const option: PublicPresetOptions = {
23 23 [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347',
24 24 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  25 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  26 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
25 27 [ComponentConfigFieldEnum.UNIT]: '℃',
26 28 [ComponentConfigFieldEnum.POINTER_COLOR]: '#15E2C6',
27 29 [ComponentConfigFieldEnum.INSTRUMENT_PANEL_COLOR]: '#61D4C5',
... ...
1 1 <script lang="ts" setup>
  2 + import { nextTick } from 'vue';
2 3 import { ComponentConfigFieldEnum } from '../../../enum';
3 4 import { option, Gradient, GradientColor } from './config';
4 5 import { useForm, BasicForm } from '/@/components/Form';
... ... @@ -22,18 +23,26 @@
22 23 defaultValue: option.fontColor,
23 24 },
24 25 {
25   - field: ComponentConfigFieldEnum.UNIT,
26   - label: '数值单位',
27   - component: 'Input',
28   - defaultValue: option.unit,
29   - },
30   - {
31 26 field: ComponentConfigFieldEnum.POINTER_COLOR,
32 27 label: '指针颜色',
33 28 component: 'ColorPicker',
34 29 changeEvent: 'update:value',
35 30 defaultValue: option.pointerColor,
36 31 },
  32 + {
  33 + field: ComponentConfigFieldEnum.FIRST_PHASE_COLOR,
  34 + label: '起始颜色',
  35 + component: 'ColorPicker',
  36 + changeEvent: 'update:value',
  37 + defaultValue: GradientColor.FIRST,
  38 + },
  39 + {
  40 + field: ComponentConfigFieldEnum.SECOND_PHASE_COLOR,
  41 + label: '结尾颜色',
  42 + component: 'ColorPicker',
  43 + changeEvent: 'update:value',
  44 + defaultValue: GradientColor.SECOND,
  45 + },
37 46 // {
38 47 // field: ComponentConfigFieldEnum.INSTRUMENT_PANEL_WIDTH,
39 48 // label: '仪表盘宽度',
... ... @@ -48,18 +57,38 @@
48 57 defaultValue: option.showDeviceName,
49 58 },
50 59 {
51   - field: ComponentConfigFieldEnum.FIRST_PHASE_COLOR,
52   - label: '起始颜色',
53   - component: 'ColorPicker',
54   - changeEvent: 'update:value',
55   - defaultValue: GradientColor.FIRST,
  60 + field: ComponentConfigFieldEnum.SHOW_TIME,
  61 + label: '显示时间',
  62 + component: 'Checkbox',
  63 + defaultValue: option.showTime,
56 64 },
  65 +
57 66 {
58   - field: ComponentConfigFieldEnum.SECOND_PHASE_COLOR,
59   - label: '结尾颜色',
60   - component: 'ColorPicker',
61   - changeEvent: 'update:value',
62   - defaultValue: GradientColor.SECOND,
  67 + field: ComponentConfigFieldEnum.UNIT,
  68 + label: '数值单位',
  69 + component: 'Input',
  70 + defaultValue: option.unit,
  71 + },
  72 + {
  73 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  74 + label: '最大值',
  75 + component: 'InputNumber',
  76 + defaultValue: 100,
  77 + componentProps: ({ formActionType }) => {
  78 + const { setFieldsValue } = formActionType;
  79 + return {
  80 + placeholder: '请输入最大值',
  81 + min: 100,
  82 + onChange: async (e) => {
  83 + if (!e) {
  84 + await nextTick();
  85 + setFieldsValue({
  86 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  87 + });
  88 + }
  89 + },
  90 + };
  91 + },
63 92 },
64 93 ],
65 94 showActionButtonGroup: false,
... ... @@ -88,13 +117,16 @@
88 117 fontColor: item[ComponentConfigFieldEnum.FONT_COLOR],
89 118 unit: item[ComponentConfigFieldEnum.UNIT],
90 119 showDeviceName: item[ComponentConfigFieldEnum.SHOW_DEVICE_NAME],
  120 + showTime: item[ComponentConfigFieldEnum.SHOW_TIME],
91 121 pointerColor: item[ComponentConfigFieldEnum.POINTER_COLOR],
  122 + maxNumber: item[ComponentConfigFieldEnum.MAX_NUMBER],
92 123 } as ComponentInfo;
93 124 };
94 125
95 126 const setFormValues = (data: Recordable) => {
96 127 // return setFieldsValue(data);
97   - const { gradientInfo, unit, fontColor, showDeviceName, pointerColor } = data;
  128 + const { gradientInfo, unit, fontColor, showDeviceName, pointerColor, showTime, maxNumber } =
  129 + data;
98 130 const firstRecord = gradientInfo.find((item) => item.key === Gradient.FIRST);
99 131 const secondRecord = gradientInfo.find((item) => item.key === Gradient.SECOND);
100 132
... ... @@ -102,11 +134,13 @@
102 134 [ComponentConfigFieldEnum.UNIT]: unit,
103 135 [ComponentConfigFieldEnum.FONT_COLOR]: fontColor,
104 136 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: showDeviceName,
  137 + [ComponentConfigFieldEnum.SHOW_TIME]: showTime,
105 138 [ComponentConfigFieldEnum.FIRST_PHASE_VALUE]: 0,
106 139 [ComponentConfigFieldEnum.FIRST_PHASE_COLOR]: firstRecord?.color,
107 140 [ComponentConfigFieldEnum.SECOND_PHASE_VALUE]: 1,
108 141 [ComponentConfigFieldEnum.SECOND_PHASE_COLOR]: secondRecord?.color,
109 142 [ComponentConfigFieldEnum.POINTER_COLOR]: pointerColor,
  143 + [ComponentConfigFieldEnum.MAX_NUMBER]: maxNumber,
110 144 };
111 145 return setFieldsValue(value);
112 146 };
... ...