Commit ec78503ec4f0139ce2bb3e69259c874b94008933

Authored by xp.Huang
2 parents e6ba961f 6f017f16

Merge branch 'main_dev' into 'main'

merge Main dev main

See merge request yunteng/thingskit-front!812
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 };
... ...