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 167 changed files with 4627 additions and 1090 deletions
... ... @@ -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 };
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick } from 'vue';
4   - import { useDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useIntervalFn } from '@vueuse/core';
9 8 import { useComponentScale } from '../../../hook/useComponentScale';
10 9 import { isArray } from '/@/utils/is';
11 10 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  11 + import { useDataFetch } from '../../../hook/socket/useSocket';
  12 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 13
13 14 const props = defineProps<{
14 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -29,9 +30,18 @@
29 30 pointerColor: presetPointerColor,
30 31 instrumentPanelColor: presetInstrumentPanelColor,
31 32 gradientInfo: presetGradientInfo,
  33 + showTime: persetShowTime,
  34 + maxNumber: persetMaxNumber,
32 35 } = persetOption || {};
33   - const { unit, fontColor, pointerColor, instrumentPanelColor, gradientInfo } =
34   - componentInfo || {};
  36 + const {
  37 + unit,
  38 + fontColor,
  39 + pointerColor,
  40 + instrumentPanelColor,
  41 + gradientInfo,
  42 + showTime,
  43 + maxNumber,
  44 + } = componentInfo || {};
35 45 return {
36 46 unit: unit ?? presetUnit,
37 47 fontColor: fontColor ?? presetFontColor,
... ... @@ -39,6 +49,8 @@
39 49 pointerColor: pointerColor ?? presetPointerColor,
40 50 instrumentPanelColor: instrumentPanelColor ?? presetInstrumentPanelColor,
41 51 gradientInfo: gradientInfo ?? presetGradientInfo,
  52 + showTime: showTime ?? persetShowTime,
  53 + maxNumber: maxNumber || persetMaxNumber,
42 54 };
43 55 });
44 56
... ... @@ -46,13 +58,6 @@
46 58 if (!isArray(array)) {
47 59 return;
48 60 }
49   - // const item: any = [];
50   - // array.forEach((value, index) => {
51   - // item[index] =
52   - // index == 0
53   - // ? { offset: value.value == 100 ? 1 : 0, color: value.color }
54   - // : { offset: Number((value.value / 100).toFixed(1)), color: value.color };
55   - // });
56 61 const colorList = array.map((item) => ({
57 62 offset: item.value,
58 63 color: item.color,
... ... @@ -61,7 +66,7 @@
61 66 };
62 67
63 68 const options = (): EChartsOption => {
64   - const { unit, fontColor, pointerColor, gradientInfo } = unref(getDesign);
  69 + const { unit, fontColor, pointerColor, gradientInfo, maxNumber } = unref(getDesign);
65 70
66 71 const instrumentPanelColor = getStageColor(gradientInfo);
67 72 // getStageColor(gradientInfo);
... ... @@ -69,6 +74,8 @@
69 74 series: [
70 75 {
71 76 type: 'gauge',
  77 + min: 0,
  78 + max: maxNumber,
72 79 center: ['50%', '60%'],
73 80 startAngle: 150,
74 81 endAngle: 30,
... ... @@ -159,6 +166,7 @@
159 166 const updateFn: DataFetchUpdateFn = (message, attribute) => {
160 167 const { data = {} } = message;
161 168 const [latest] = data[attribute] || [];
  169 + if (!latest.length) return;
162 170 const [timespan, value] = latest;
163 171 time.value = timespan;
164 172 updateChart(isNaN(value as unknown as number) ? 0 : Number(value));
... ... @@ -203,12 +211,18 @@
203 211 </script>
204 212
205 213 <template>
206   - <main class="w-full h-full flex flex-col justify-center items-center">
  214 + <main
  215 + class="w-full h-full flex flex-col justify-center items-center"
  216 + :class="!getDesign.showTime && 'p-5'"
  217 + >
207 218 <DeviceName :config="config" />
208   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
209   - <div class="text-gray-500 text-xs text-center truncate">{{
210   - getDesign.attribute || '湿度'
211   - }}</div>
212   - <UpdateTime :time="time" />
  219 + <div class="w-full h-full flex flex-1 flex-col justify-center items-center">
  220 + <div ref="chartRefEl" class="flex-1 w-full h-6/7 flex flex-col justify-center items-center">
  221 + </div>
  222 + <div class="text-gray-500 text-xs text-center truncate">{{
  223 + getDesign.attribute || '湿度'
  224 + }}</div>
  225 + </div>
  226 + <UpdateTime v-if="getDesign.showTime" :time="time" />
213 227 </main>
214 228 </template>
... ...
... ... @@ -20,10 +20,12 @@ export enum GradientColor {
20 20 export const option: PublicPresetOptions = {
21 21 [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347',
22 22 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  23 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
23 24 [ComponentConfigFieldEnum.PROGRESS_BAR_CIRCLE]: false,
24 25 [ComponentConfigFieldEnum.UNIT]: '℃',
25 26 [ComponentConfigFieldEnum.POINTER_COLOR]: '#15E2C6',
26 27 [ComponentConfigFieldEnum.INSTRUMENT_PANEL_COLOR]: '#61D4C5',
  28 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
27 29 [ComponentConfigFieldEnum.GRADIENT_INFO]: [
28 30 { key: Gradient.FIRST, value: 0, color: GradientColor.FIRST },
29 31 { key: Gradient.SECOND, value: 1, color: GradientColor.SECOND },
... ...
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';
... ... @@ -28,18 +29,6 @@
28 29 defaultValue: option.unit,
29 30 },
30 31 {
31   - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
32   - label: '显示设备名称',
33   - component: 'Checkbox',
34   - defaultValue: option.showDeviceName,
35   - },
36   - {
37   - field: ComponentConfigFieldEnum.PROGRESS_BAR_CIRCLE,
38   - label: '显示进度条圆形',
39   - component: 'Checkbox',
40   - defaultValue: option.progressBarCircle,
41   - },
42   - {
43 32 field: ComponentConfigFieldEnum.FIRST_PHASE_COLOR,
44 33 label: '起始颜色',
45 34 component: 'ColorPicker',
... ... @@ -53,6 +42,45 @@
53 42 changeEvent: 'update:value',
54 43 defaultValue: GradientColor.SECOND,
55 44 },
  45 + {
  46 + field: ComponentConfigFieldEnum.PROGRESS_BAR_CIRCLE,
  47 + label: '显示进度条圆形',
  48 + component: 'Checkbox',
  49 + defaultValue: option.progressBarCircle,
  50 + },
  51 + {
  52 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  53 + label: '显示设备名称',
  54 + component: 'Checkbox',
  55 + defaultValue: option.showDeviceName,
  56 + },
  57 + {
  58 + field: ComponentConfigFieldEnum.SHOW_TIME,
  59 + label: '显示时间',
  60 + component: 'Checkbox',
  61 + defaultValue: option.showTime,
  62 + },
  63 + {
  64 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  65 + label: '最大值',
  66 + component: 'InputNumber',
  67 + defaultValue: 100,
  68 + componentProps: ({ formActionType }) => {
  69 + const { setFieldsValue } = formActionType;
  70 + return {
  71 + placeholder: '请输入最大值',
  72 + min: 100,
  73 + onChange: async (e) => {
  74 + if (!e) {
  75 + await nextTick();
  76 + setFieldsValue({
  77 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  78 + });
  79 + }
  80 + },
  81 + };
  82 + },
  83 + },
56 84 ],
57 85 showActionButtonGroup: false,
58 86 labelWidth: 120,
... ... @@ -80,13 +108,23 @@
80 108 fontColor: item[ComponentConfigFieldEnum.FONT_COLOR],
81 109 unit: item[ComponentConfigFieldEnum.UNIT],
82 110 showDeviceName: item[ComponentConfigFieldEnum.SHOW_DEVICE_NAME],
  111 + showTime: item[ComponentConfigFieldEnum.SHOW_TIME],
83 112 progressBarCircle: item[ComponentConfigFieldEnum.PROGRESS_BAR_CIRCLE],
  113 + maxNumber: item[ComponentConfigFieldEnum.MAX_NUMBER],
84 114 } as ComponentInfo;
85 115 };
86 116
87 117 const setFormValues = (data: Recordable) => {
88 118 // return setFieldsValue(data);
89   - const { gradientInfo, unit, fontColor, showDeviceName, progressBarCircle } = data;
  119 + const {
  120 + gradientInfo,
  121 + unit,
  122 + fontColor,
  123 + showDeviceName,
  124 + progressBarCircle,
  125 + showTime,
  126 + maxNumber,
  127 + } = data;
90 128 const firstRecord = gradientInfo.find((item) => item.key === Gradient.FIRST);
91 129 const secondRecord = gradientInfo.find((item) => item.key === Gradient.SECOND);
92 130
... ... @@ -94,11 +132,13 @@
94 132 [ComponentConfigFieldEnum.UNIT]: unit,
95 133 [ComponentConfigFieldEnum.FONT_COLOR]: fontColor,
96 134 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: showDeviceName,
  135 + [ComponentConfigFieldEnum.SHOW_TIME]: showTime,
97 136 [ComponentConfigFieldEnum.FIRST_PHASE_VALUE]: 0,
98 137 [ComponentConfigFieldEnum.FIRST_PHASE_COLOR]: firstRecord?.color,
99 138 [ComponentConfigFieldEnum.SECOND_PHASE_VALUE]: 1,
100 139 [ComponentConfigFieldEnum.SECOND_PHASE_COLOR]: secondRecord?.color,
101 140 [ComponentConfigFieldEnum.PROGRESS_BAR_CIRCLE]: progressBarCircle,
  141 + [ComponentConfigFieldEnum.MAX_NUMBER]: maxNumber,
102 142 };
103 143 return setFieldsValue(value);
104 144 };
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick } from 'vue';
4   - import { useDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useIntervalFn } from '@vueuse/core';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10 9 import { isArray } from '/@/utils/is';
11 10 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
  11 + import { useDataFetch } from '../../../hook/socket/useSocket';
  12 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 13
13 14 const props = defineProps<{
14 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -30,9 +31,19 @@
30 31 instrumentPanelColor: presetInstrumentPanelColor,
31 32 progressBarCircle: presetProgressBarCircle,
32 33 gradientInfo: presetGradientInfo,
  34 + showTime: persetShowTime,
  35 + maxNumber: persetMaxNumber,
33 36 } = persetOption || {};
34   - const { unit, fontColor, pointerColor, gradientInfo, progressBarCircle, instrumentPanelColor } =
35   - componentInfo || {};
  37 + const {
  38 + unit,
  39 + fontColor,
  40 + pointerColor,
  41 + gradientInfo,
  42 + progressBarCircle,
  43 + instrumentPanelColor,
  44 + showTime,
  45 + maxNumber,
  46 + } = componentInfo || {};
36 47 return {
37 48 unit: unit ?? presetUnit,
38 49 fontColor: fontColor ?? presetFontColor,
... ... @@ -41,6 +52,8 @@
41 52 instrumentPanelColor: instrumentPanelColor ?? presetInstrumentPanelColor,
42 53 progressBarCircle: progressBarCircle ?? presetProgressBarCircle,
43 54 gradientInfo: gradientInfo ?? presetGradientInfo,
  55 + showTime: showTime ?? persetShowTime,
  56 + maxNumber: maxNumber || persetMaxNumber,
44 57 };
45 58 });
46 59
... ... @@ -58,7 +71,7 @@
58 71 const titleValue = ref<number>(20);
59 72
60 73 const options = (): EChartsOption => {
61   - const { unit, fontColor, progressBarCircle, gradientInfo } = unref(getDesign);
  74 + const { unit, fontColor, progressBarCircle, gradientInfo, maxNumber } = unref(getDesign);
62 75 const instrumentPanelColor = getStageColor(gradientInfo);
63 76 return {
64 77 title: {
... ... @@ -75,6 +88,8 @@
75 88 {
76 89 type: 'gauge',
77 90 center: ['50%', '50%'],
  91 + min: 0,
  92 + max: maxNumber,
78 93 startAngle: '90',
79 94 endAngle: '-270',
80 95 progress: {
... ... @@ -162,6 +177,7 @@
162 177 const updateFn: DataFetchUpdateFn = (message, attribute) => {
163 178 const { data = {} } = message;
164 179 const [latest] = data[attribute] || [];
  180 + if (!latest.length) return;
165 181 const [timespan, value] = latest;
166 182 time.value = timespan;
167 183 updateChart(isNaN(value as unknown as number) ? 0 : Number(value));
... ... @@ -205,12 +221,18 @@
205 221 </script>
206 222
207 223 <template>
208   - <main class="w-full h-full flex flex-col justify-center items-center">
  224 + <main
  225 + class="w-full h-full flex flex-col justify-center items-center"
  226 + :class="!getDesign.showTime && 'p-5'"
  227 + >
209 228 <DeviceName :config="config" />
210   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
211   - <div class="text-gray-500 text-xs text-center truncate">{{
212   - getDesign.attribute || '湿度'
213   - }}</div>
214   - <UpdateTime :time="time" />
  229 + <div class="flex w-full h-full flex-col justify-center items-center flex-1">
  230 + <div ref="chartRefEl" class="flex-1 w-full h-6/7 flex-col justify-center items-center flex">
  231 + </div>
  232 + <div class="text-gray-500 text-xs text-center truncate">{{
  233 + getDesign.attribute || '湿度'
  234 + }}</div>
  235 + </div>
  236 + <UpdateTime v-if="getDesign.showTime" :time="time" />
215 237 </main>
216 238 </template>
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick } from 'vue';
4   - import { useMultipleDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7   - import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8   - // import { useIntervalFn } from '@vueuse/core';
  6 + // import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
9 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10 8 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
11 9 import { useReceiveMessage } from '../../../hook/useReceiveMessage';
12 10 import { useReceiveValue } from '../../../hook/useReceiveValue';
  11 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
13 13
14 14 const props = defineProps<{
15 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -19,7 +19,7 @@
19 19
20 20 const chartInstance = ref<Nullable<ECharts>>(null);
21 21
22   - const time = ref<Nullable<number>>(null);
  22 + // const time = ref<Nullable<number>>(null);
23 23
24 24 const getDesign = computed(() => {
25 25 const { option, persetOption } = props.config;
... ... @@ -64,7 +64,7 @@
64 64 case 1:
65 65 return (offsetList.value = [
66 66 ['0%', '0%'],
67   - ['0%', '10%'],
  67 + ['0%', '15%'],
68 68 ]);
69 69 break;
70 70 case 2:
... ... @@ -119,7 +119,7 @@
119 119 : ['0%', '60%'],
120 120 ]);
121 121 break;
122   - case 6 || 7 || 8 || 9 || 10:
  122 + case 6:
123 123 return (offsetList.value = [
124 124 index == 0
125 125 ? ['0%', '-55%']
... ... @@ -256,31 +256,14 @@
256 256 chartInstance.value.setOption(options());
257 257 };
258 258
259   - // const randomFn = () => {
260   - // gaugeData[0].value = +(Math.random() * 100).toFixed(2);
261   - // gaugeData[1].value = +(Math.random() * 100).toFixed(2);
262   - // useIntervalFn(() => {
263   - // unref(chartInstance)?.setOption({
264   - // series: [
265   - // {
266   - // data: gaugeData,
267   - // pointer: {
268   - // show: false,
269   - // },
270   - // },
271   - // ],
272   - // });
273   - // }, 3000);
274   - // };
275   -
276 259 const { forEachGroupMessage } = useReceiveMessage();
277 260 const { getNumberValue } = useReceiveValue();
278 261 const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => {
279   - forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  262 + forEachGroupMessage(message, deviceId, attribute, (attribute, value) => {
280 263 series.value.forEach((item) => {
281   - if (item.id === deviceId && item.attribute === attribute) {
  264 + if (item.id === deviceId && item.attribute === attribute && value) {
282 265 item.value = getNumberValue(value);
283   - time.value = timespan;
  266 + // time.value = timespan;
284 267 }
285 268 });
286 269 });
... ... @@ -333,7 +316,8 @@
333 316 <template>
334 317 <main class="w-full h-full flex flex-col justify-center items-center">
335 318 <DeviceName :config="config" />
336   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
337   - <UpdateTime :time="time" />
  319 + <div ref="chartRefEl" class="flex-1 w-full h-full justify-center items-center flex-col flex">
  320 + </div>
  321 + <!-- <UpdateTime :time="time" /> -->
338 322 </main>
339 323 </template>
... ...
... ... @@ -20,7 +20,9 @@ export enum GradientColor {
20 20 export const option: PublicPresetOptions = {
21 21 [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347',
22 22 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
23   - [ComponentConfigFieldEnum.UNIT]: '℃',
  23 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
  24 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  25 + [ComponentConfigFieldEnum.UNIT]: 'kw',
24 26 };
25 27
26 28 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
1 1 <script lang="ts" setup>
  2 + import { nextTick } from 'vue';
2 3 import { ComponentConfigFieldEnum } from '../../../enum';
3 4 import { option } from './config';
4 5 import { useForm, BasicForm } from '/@/components/Form';
... ... @@ -26,6 +27,33 @@
26 27 component: 'Checkbox',
27 28 defaultValue: option.showDeviceName,
28 29 },
  30 + {
  31 + field: ComponentConfigFieldEnum.SHOW_TIME,
  32 + label: '显示时间',
  33 + component: 'Checkbox',
  34 + defaultValue: option.showTime,
  35 + },
  36 + {
  37 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  38 + label: '最大值',
  39 + component: 'InputNumber',
  40 + defaultValue: 100,
  41 + componentProps: ({ formActionType }) => {
  42 + const { setFieldsValue } = formActionType;
  43 + return {
  44 + placeholder: '请输入最大值',
  45 + min: 100,
  46 + onChange: async (e) => {
  47 + if (!e) {
  48 + await nextTick();
  49 + setFieldsValue({
  50 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  51 + });
  52 + }
  53 + },
  54 + };
  55 + },
  56 + },
29 57 ],
30 58 showActionButtonGroup: false,
31 59 labelWidth: 120,
... ... @@ -41,17 +69,21 @@
41 69 fontColor: item[ComponentConfigFieldEnum.FONT_COLOR],
42 70 unit: item[ComponentConfigFieldEnum.UNIT],
43 71 showDeviceName: item[ComponentConfigFieldEnum.SHOW_DEVICE_NAME],
  72 + showTime: item[ComponentConfigFieldEnum.SHOW_TIME],
  73 + maxNumber: item[ComponentConfigFieldEnum.MAX_NUMBER],
44 74 } as ComponentInfo;
45 75 };
46 76
47 77 const setFormValues = (data: Recordable) => {
48 78 // return setFieldsValue(data);
49   - const { unit, fontColor, showDeviceName } = data;
  79 + const { unit, fontColor, showDeviceName, showTime, maxNumber } = data;
50 80
51 81 const value = {
52 82 [ComponentConfigFieldEnum.UNIT]: unit,
53 83 [ComponentConfigFieldEnum.FONT_COLOR]: fontColor,
54 84 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: showDeviceName,
  85 + [ComponentConfigFieldEnum.SHOW_TIME]: showTime,
  86 + [ComponentConfigFieldEnum.MAX_NUMBER]: maxNumber,
55 87 };
56 88 return setFieldsValue(value);
57 89 };
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick } from 'vue';
4   - import { useDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useIntervalFn } from '@vueuse/core';
9 8 import { useComponentScale } from '../../../hook/useComponentScale';
10 9 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  10 + import { useDataFetch } from '../../../hook/socket/useSocket';
  11 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
11 12
12 13 const props = defineProps<{
13 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -28,9 +29,18 @@
28 29 pointerColor: presetPointerColor,
29 30 instrumentPanelColor: presetInstrumentPanelColor,
30 31 gradientInfo: presetGradientInfo,
  32 + showTime: persetShowTime,
  33 + maxNumber: persetMaxNumber,
31 34 } = persetOption || {};
32   - const { unit, fontColor, pointerColor, instrumentPanelColor, gradientInfo } =
33   - componentInfo || {};
  35 + const {
  36 + unit,
  37 + fontColor,
  38 + pointerColor,
  39 + instrumentPanelColor,
  40 + gradientInfo,
  41 + showTime,
  42 + maxNumber,
  43 + } = componentInfo || {};
34 44 return {
35 45 unit: unit ?? presetUnit,
36 46 fontColor: fontColor ?? presetFontColor,
... ... @@ -38,17 +48,21 @@
38 48 pointerColor: pointerColor ?? presetPointerColor,
39 49 instrumentPanelColor: instrumentPanelColor ?? presetInstrumentPanelColor,
40 50 gradientInfo: gradientInfo ?? presetGradientInfo,
  51 + showTime: showTime ?? persetShowTime,
  52 + maxNumber: maxNumber || persetMaxNumber,
41 53 };
42 54 });
43 55
44 56 const options = (): EChartsOption => {
45   - const { unit, fontColor, pointerColor } = unref(getDesign);
  57 + const { unit, fontColor, pointerColor, maxNumber } = unref(getDesign);
46 58
47 59 // getStageColor(gradientInfo);
48 60 return {
49 61 series: [
50 62 {
51 63 type: 'gauge',
  64 + min: 0,
  65 + max: maxNumber,
52 66 progress: {
53 67 show: true,
54 68 width: unref(getRatio) ? 6 * unref(getRatio) : 6,
... ... @@ -134,6 +148,7 @@
134 148 const updateFn: DataFetchUpdateFn = (message, attribute) => {
135 149 const { data = {} } = message;
136 150 const [latest] = data[attribute] || [];
  151 + if (!latest.length) return;
137 152 const [timespan, value] = latest;
138 153 time.value = timespan;
139 154 updateChart(isNaN(value as unknown as number) ? 0 : Number(value));
... ... @@ -188,12 +203,18 @@
188 203 </script>
189 204
190 205 <template>
191   - <main class="w-full h-full flex flex-col justify-center items-center">
  206 + <main
  207 + class="w-full h-full flex flex-col justify-center items-center"
  208 + :class="!getDesign.showTime && 'p-5'"
  209 + >
192 210 <DeviceName :config="config" />
193   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
194   - <div class="text-gray-500 text-xs text-center truncate">{{
195   - getDesign.attribute || '湿度'
196   - }}</div>
197   - <UpdateTime :time="time" />
  211 + <div class="flex flex-1 flex-col justify-center items-center w-full h-full">
  212 + <div ref="chartRefEl" class="flex-1 w-full h-6/7 flex flex-col justify-center items-center">
  213 + </div>
  214 + <div class="text-gray-500 text-xs text-center truncate">{{
  215 + getDesign.attribute || '速度'
  216 + }}</div>
  217 + </div>
  218 + <UpdateTime v-if="getDesign.showTime" :time="time" />
198 219 </main>
199 220 </template>
... ...
... ... @@ -12,6 +12,8 @@ import { ComponentConfigFieldEnum } from '../../../enum';
12 12 export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347',
14 14 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  15 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
  16 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
15 17 [ComponentConfigFieldEnum.UNIT]: '℃',
16 18 };
17 19
... ...
1 1 <script lang="ts" setup>
  2 + import { nextTick } from 'vue';
2 3 import { ComponentConfigFieldEnum } from '../../../enum';
3 4 import { option } from './config';
4 5 import { useForm, BasicForm } from '/@/components/Form';
... ... @@ -25,6 +26,33 @@
25 26 component: 'Checkbox',
26 27 defaultValue: option.showDeviceName,
27 28 },
  29 + {
  30 + field: ComponentConfigFieldEnum.SHOW_TIME,
  31 + label: '显示时间',
  32 + component: 'Checkbox',
  33 + defaultValue: option.showTime,
  34 + },
  35 + {
  36 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  37 + label: '最大值',
  38 + component: 'InputNumber',
  39 + defaultValue: 100,
  40 + componentProps: ({ formActionType }) => {
  41 + const { setFieldsValue } = formActionType;
  42 + return {
  43 + placeholder: '请输入最大值',
  44 + min: 100,
  45 + onChange: async (e) => {
  46 + if (!e) {
  47 + await nextTick();
  48 + setFieldsValue({
  49 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  50 + });
  51 + }
  52 + },
  53 + };
  54 + },
  55 + },
28 56 ],
29 57 showActionButtonGroup: false,
30 58 labelWidth: 120,
... ...
... ... @@ -3,8 +3,7 @@
3 3 import { onMounted } from 'vue';
4 4 import { unref } from 'vue';
5 5 import { ref } from 'vue';
6   - import { useDataFetch } from '../../../hook/useSocket';
7   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '../../../index.type';
  6 + import { ComponentPropsConfigType } from '../../../index.type';
8 7 import { option } from './config';
9 8 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
10 9 import { useIntervalFn } from '@vueuse/core';
... ... @@ -12,6 +11,8 @@
12 11 import { useComponentScale } from '../../../hook/useComponentScale';
13 12 import { nextTick } from 'vue';
14 13 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  14 + import { useDataFetch } from '../../../hook/socket/useSocket';
  15 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
15 16
16 17 const props = defineProps<{
17 18 config: ComponentPropsConfigType<typeof option>;
... ... @@ -26,27 +27,34 @@
26 27 const getDesign = computed(() => {
27 28 const { option, persetOption } = props.config;
28 29 const { componentInfo, attribute, attributeRename, attributeName } = option;
29   - const { fontColor: presetFontColor, unit: presetUnit } = persetOption || {};
30   - const { unit, fontColor } = componentInfo || {};
  30 + const {
  31 + fontColor: presetFontColor,
  32 + unit: presetUnit,
  33 + showTime: persetShowTime,
  34 + maxNumber: persetMaxNumber,
  35 + } = persetOption || {};
  36 + const { unit, fontColor, showTime, maxNumber } = componentInfo || {};
31 37 return {
32 38 unit: unit ?? presetUnit,
33 39 fontColor: fontColor ?? presetFontColor,
34 40 attribute: attributeRename || attributeName || attribute,
  41 + showTime: persetShowTime || showTime,
  42 + maxNumber: maxNumber || persetMaxNumber,
35 43 };
36 44 });
37 45
38 46 const options = (): EChartsOption => {
39   - const { unit, fontColor } = unref(getDesign);
  47 + const { unit, fontColor, maxNumber } = unref(getDesign);
40 48 return {
41 49 series: [
42 50 {
43 51 type: 'gauge',
  52 + min: 0,
  53 + max: maxNumber,
44 54 radius: '50%',
45 55 center: ['50%', '60%'],
46 56 startAngle: 200,
47 57 endAngle: -20,
48   - min: 0,
49   - max: 100,
50 58 splitNumber: 10,
51 59 itemStyle: {
52 60 color: fontColor,
... ... @@ -114,7 +122,7 @@
114 122 startAngle: 200,
115 123 endAngle: -20,
116 124 min: 0,
117   - max: 100,
  125 + max: maxNumber,
118 126 itemStyle: {
119 127 color: fontColor,
120 128 },
... ... @@ -173,6 +181,7 @@
173 181 const updateFn: DataFetchUpdateFn = (message, attribute) => {
174 182 const { data = {} } = message;
175 183 const [latest] = data[attribute] || [];
  184 + if (!latest.length) return;
176 185 const [timespan, value] = latest;
177 186 time.value = timespan;
178 187 updateChart(isNaN(value as unknown as number) ? 0 : Number(value));
... ... @@ -221,12 +230,18 @@
221 230 </script>
222 231
223 232 <template>
224   - <main class="w-full h-full flex flex-col justify-center items-center">
  233 + <main
  234 + class="w-full h-full flex flex-col justify-center items-center"
  235 + :class="!getDesign.showTime && 'p-5'"
  236 + >
225 237 <DeviceName :config="config" />
226   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
227   - <div class="text-gray-500 text-xs text-center truncate">{{
228   - getDesign.attribute || '温度'
229   - }}</div>
230   - <UpdateTime :time="time" />
  238 + <div class="w-full h-full flex justify-center items-center flex-1 flex-col">
  239 + <div ref="chartRefEl" class="flex-1 w-full h-6/7 flex justify-center items-center flex-col">
  240 + </div>
  241 + <div class="text-gray-500 text-xs text-center truncate">{{
  242 + getDesign.attribute || '温度'
  243 + }}</div>
  244 + </div>
  245 + <UpdateTime v-show="getDesign.showTime" :time="time" />
231 246 </main>
232 247 </template>
... ...
... ... @@ -29,6 +29,8 @@ export const option: PublicPresetOptions = {
29 29 ],
30 30 [ComponentConfigFieldEnum.UNIT]: 'km/h',
31 31 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  32 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
  33 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
32 34 };
33 35
34 36 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -4,6 +4,7 @@
4 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 5 import { Gradient, GradientColor, option } from './config';
6 6 import { ComponentInfo } from '/@/views/visual/palette/types';
  7 + import { nextTick } from 'vue';
7 8
8 9 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
9 10 schemas: [
... ... @@ -81,6 +82,33 @@
81 82 component: 'Checkbox',
82 83 defaultValue: option.showDeviceName,
83 84 },
  85 + {
  86 + field: ComponentConfigFieldEnum.SHOW_TIME,
  87 + label: '显示时间',
  88 + component: 'Checkbox',
  89 + defaultValue: option.showTime,
  90 + },
  91 + {
  92 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  93 + label: '最大值',
  94 + component: 'InputNumber',
  95 + defaultValue: 100,
  96 + componentProps: ({ formActionType }) => {
  97 + const { setFieldsValue } = formActionType;
  98 + return {
  99 + placeholder: '请输入最大值',
  100 + min: 100,
  101 + onChange: async (e) => {
  102 + if (!e) {
  103 + await nextTick();
  104 + setFieldsValue({
  105 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  106 + });
  107 + }
  108 + },
  109 + };
  110 + },
  111 + },
84 112 ],
85 113 showActionButtonGroup: false,
86 114 labelWidth: 120,
... ... @@ -112,11 +140,13 @@
112 140 fontColor: value[ComponentConfigFieldEnum.FONT_COLOR],
113 141 unit: value[ComponentConfigFieldEnum.UNIT],
114 142 showDeviceName: value[ComponentConfigFieldEnum.SHOW_DEVICE_NAME],
  143 + showTime: value[ComponentConfigFieldEnum.SHOW_TIME],
  144 + maxNumber: value[ComponentConfigFieldEnum.MAX_NUMBER],
115 145 } as ComponentInfo;
116 146 };
117 147
118 148 const setFormValues = (data: ComponentInfo) => {
119   - const { gradientInfo, unit, fontColor, showDeviceName } = data;
  149 + const { gradientInfo, unit, fontColor, showDeviceName, showTime, maxNumber } = data;
120 150 const firstRecord = gradientInfo.find((item) => item.key === Gradient.FIRST);
121 151 const secondRecord = gradientInfo.find((item) => item.key === Gradient.SECOND);
122 152 const thirdRecord = gradientInfo.find((item) => item.key === Gradient.THIRD);
... ... @@ -124,6 +154,8 @@
124 154 [ComponentConfigFieldEnum.UNIT]: unit,
125 155 [ComponentConfigFieldEnum.FONT_COLOR]: fontColor,
126 156 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: showDeviceName,
  157 + [ComponentConfigFieldEnum.SHOW_TIME]: showTime,
  158 + [ComponentConfigFieldEnum.MAX_NUMBER]: maxNumber,
127 159 [ComponentConfigFieldEnum.FIRST_PHASE_VALUE]: firstRecord?.value,
128 160 [ComponentConfigFieldEnum.FIRST_PHASE_COLOR]: firstRecord?.color,
129 161 [ComponentConfigFieldEnum.SECOND_PHASE_VALUE]: secondRecord?.value,
... ...
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 { Gradient, GradientColor, option } from './config';
4   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
5 4 import { ECharts, EChartsOption, init } from 'echarts';
6 5 import { ref, unref, onMounted, computed } from 'vue';
7 6 import { isArray } from '/@/utils/is';
... ... @@ -11,6 +10,8 @@
11 10 import { useIntervalFn } from '@vueuse/core';
12 11 import { nextTick } from 'vue';
13 12 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  13 + import { useDataFetch } from '../../../hook/socket/useSocket';
  14 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
14 15
15 16 const props = defineProps<{
16 17 config: ComponentPropsConfigType<typeof option>;
... ... @@ -35,14 +36,18 @@
35 36 fontColor: presetFontColor,
36 37 unit: presetUnit,
37 38 gradientInfo: presetGradientInfo,
  39 + showTime: persetShowTime,
  40 + maxNumber: persetMaxNumber,
38 41 } = persetOption || {};
39 42
40   - const { unit, fontColor, gradientInfo } = componentInfo || {};
  43 + const { unit, fontColor, gradientInfo, showTime, maxNumber } = componentInfo || {};
41 44 return {
42 45 unit: unit ?? presetUnit,
43 46 fontColor: fontColor ?? presetFontColor,
44 47 gradientInfo: gradientInfo ?? presetGradientInfo,
45 48 attribute: attributeRename || attributeName || attribute,
  49 + showTime: showTime || persetShowTime,
  50 + maxNumber: maxNumber || persetMaxNumber,
46 51 };
47 52 });
48 53
... ... @@ -52,7 +57,7 @@
52 57 };
53 58
54 59 const options = (): EChartsOption => {
55   - const { gradientInfo, unit, fontColor } = unref(getDesign);
  60 + const { gradientInfo, unit, fontColor, maxNumber } = unref(getDesign);
56 61 const firstRecord = getGradient(Gradient.FIRST, gradientInfo);
57 62 const secondRecord = getGradient(Gradient.SECOND, gradientInfo);
58 63 const thirdRecord = getGradient(Gradient.THIRD, gradientInfo);
... ... @@ -66,18 +71,18 @@
66 71 .join('')
67 72 );
68 73
69   - const firstGradient = firstRecord?.value ? firstRecord.value / max : 0.3;
70   - const secondGradient = secondRecord?.value ? secondRecord.value / max : 0.7;
  74 + const firstGradient = firstRecord?.value ? firstRecord.value / maxNumber : 0.3;
  75 + const secondGradient = secondRecord?.value ? secondRecord.value / maxNumber : 0.7;
71 76
72 77 return {
73 78 series: [
74 79 {
75 80 type: 'gauge',
76 81 min: 0,
77   - max,
  82 + max: maxNumber,
78 83 axisLine: {
79 84 lineStyle: {
80   - width: 20,
  85 + width: unref(getRatio) ? 20 * unref(getRatio) : 20,
81 86 color: [
82 87 [firstGradient, firstRecord?.color || GradientColor.FIRST],
83 88 [secondGradient, secondRecord?.color || GradientColor.SECOND],
... ... @@ -91,33 +96,33 @@
91 96 },
92 97 },
93 98 axisTick: {
94   - distance: -30,
  99 + distance: unref(getRatio) ? -30 * unref(getRatio) : -30,
95 100 length: 8,
96 101 splitNumber: max / 100,
97 102 lineStyle: {
98 103 color: '#fff',
99   - width: 2,
  104 + width: unref(getRatio) ? 2 * unref(getRatio) : 2,
100 105 },
101 106 },
102 107 splitLine: {
103   - distance: -10,
104   - length: 30,
  108 + distance: unref(getRatio) ? -10 * unref(getRatio) : -10,
  109 + length: unref(getRatio) ? unref(getRatio) * 30 : 30,
105 110 lineStyle: {
106 111 color: '#fff',
107   - width: 4,
  112 + width: unref(getRatio) ? unref(getRatio) * 4 : 4,
108 113 },
109 114 },
110 115 axisLabel: {
111 116 color: 'inherit',
112   - distance: 5,
113   - fontSize: 6,
  117 + distance: unref(getRatio) ? unref(getRatio) * 5 : 5,
  118 + fontSize: unref(getRatio) ? unref(getRatio) * 6 : 6,
114 119 },
115 120 detail: {
116 121 valueAnimation: true,
117 122 formatter: `{value} ${unit ?? ''}`,
118 123 color: fontColor || 'inherit',
119 124 offsetCenter: [0, '70%'],
120   - fontSize: 14,
  125 + fontSize: unref(getRatio) ? unref(getRatio) * 14 : 14,
121 126 },
122 127 data: [
123 128 {
... ... @@ -152,6 +157,7 @@
152 157 const updateFn: DataFetchUpdateFn = (message, attribute) => {
153 158 const { data = {} } = message;
154 159 const [latest] = data[attribute] || [];
  160 + if (!latest.length) return;
155 161 const [timespan, value] = latest;
156 162 time.value = timespan;
157 163 updateChartFn(isNaN(value as unknown as number) ? 0 : Number(value));
... ... @@ -204,12 +210,18 @@
204 210 </script>
205 211
206 212 <template>
207   - <main class="w-full h-full flex flex-col justify-center items-center">
  213 + <main
  214 + class="w-full h-full flex flex-col justify-center items-center"
  215 + :class="!getDesign.showTime && 'p-5'"
  216 + >
208 217 <DeviceName :config="config" />
209   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
210   - <div class="text-center text-gray-500 text-xs truncate">
211   - {{ getDesign.attribute || '速度' }}
  218 + <div class="w-full h-full flex justify-center items-center flex-col flex-1">
  219 + <div ref="chartRefEl" class="flex-1 w-full h-6/7 flex flex-col justify-center items-center">
  220 + </div>
  221 + <div class="text-center text-gray-500 text-xs truncate">
  222 + {{ getDesign.attribute || '速度' }}
  223 + </div>
212 224 </div>
213   - <UpdateTime :time="time" />
  225 + <UpdateTime v-show="getDesign.showTime" :time="time" />
214 226 </main>
215 227 </template>
... ...
... ... @@ -32,7 +32,7 @@
32 32 );
33 33 const options = deviceList
34 34 .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
35   - .map((item) => ({ ...item, label: item.name, value: item.tbDeviceId }));
  35 + .map((item) => ({ ...item, label: item.alias || item.name, value: item.tbDeviceId }));
36 36
37 37 const attKey = dataSource.map((item) => ({
38 38 ...item,
... ...
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 { useBaiduMapSDK } from '../../../hook/useBaiduMapSDK';
9 5 import { ref, unref } from 'vue';
10 6 import { buildUUID } from '/@/utils/uuid';
11 7 import { Spin } from 'ant-design-vue';
12 8 import { computed } from 'vue';
13 9 import { useMapTrackPlayBack } from './useMapTrackPlayback';
  10 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  11 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
14 12
15 13 const props = defineProps<{
16 14 config: ComponentPropsConfigType<typeof option>;
... ...
... ... @@ -2,8 +2,9 @@
2 2 import { ref } from 'vue';
3 3 import { Image as AntImage } from 'ant-design-vue';
4 4 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
5   - import { useDataFetch } from '../../../hook/useSocket';
6   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '../../../index.type';
  5 + import { ComponentPropsConfigType } from '../../../index.type';
  6 + import { useDataFetch } from '../../../hook/socket/useSocket';
  7 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
7 8
8 9 const props = defineProps<{
9 10 config: ComponentPropsConfigType;
... ...
... ... @@ -13,6 +13,7 @@ export const option: PublicPresetOptions = {
13 13 [ComponentConfigFieldEnum.OPEN_COLOR]: '#30f230',
14 14 [ComponentConfigFieldEnum.CLOSE_COLOR]: '#eeeeee',
15 15 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: true,
  16 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
16 17 };
17 18
18 19 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -25,10 +25,10 @@
25 25 },
26 26 },
27 27 {
28   - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
29   - label: '显示设备名称',
  28 + field: ComponentConfigFieldEnum.SHOW_TIME,
  29 + label: '显示时间',
30 30 component: 'Checkbox',
31   - defaultValue: option.showDeviceName,
  31 + defaultValue: option.showTime,
32 32 },
33 33 ],
34 34 showActionButtonGroup: false,
... ...
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 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 5 import { computed } from 'vue';
7 6 import { ref, onMounted, unref } from 'vue';
8 7 import { useIntervalFn } from '@vueuse/core';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10 9 import { useReceiveValue } from '../../../hook/useReceiveValue';
11 10 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  11 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useDataFetch } from '../../../hook/socket/useSocket';
12 13
13 14 const props = defineProps<{
14 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -23,15 +24,17 @@
23 24 openColor: persetOpenColor,
24 25 closeColor: persetCloseColor,
25 26 showDeviceName: persetShowDeviceName,
  27 + showTime: persetShowTime,
26 28 } = persetOption || {};
27 29 const { componentInfo, attribute, attributeName, attributeRename } = option;
28 30
29   - const { openColor, closeColor, showDeviceName } = componentInfo || {};
  31 + const { openColor, closeColor, showDeviceName, showTime } = componentInfo || {};
30 32 return {
31 33 openColor: openColor ?? persetOpenColor,
32 34 closeColor: closeColor ?? persetCloseColor,
33 35 showDeviceName: showDeviceName ?? persetShowDeviceName,
34 36 attribute: attributeRename || attributeName || attribute,
  37 + showTime: showTime ?? persetShowTime,
35 38 };
36 39 });
37 40
... ... @@ -44,6 +47,7 @@
44 47 const updateFn: DataFetchUpdateFn = (message, attribute) => {
45 48 const { data = {} } = message;
46 49 const [latest] = data[attribute] || [];
  50 + if (!latest.length) return;
47 51 const [timespan, value] = latest;
48 52 time.value = timespan;
49 53 isOpenClose.value = Boolean(getNumberValue(value));
... ... @@ -59,18 +63,24 @@
59 63 </script>
60 64
61 65 <template>
62   - <main :style="getScale" class="w-full h-full flex flex-col justify-center items-center">
  66 + <main
  67 + :style="getScale"
  68 + class="w-full h-full flex flex-col justify-center items-center"
  69 + :class="!getDesign.showTime && 'p-5'"
  70 + >
63 71 <DeviceName :config="config" class="text-center" />
64 72
65   - <div
66   - :style="{
67   - '--open-color': getDesign.openColor,
68   - '--close-color': getDesign.closeColor,
69   - }"
70   - :class="isOpenClose ? 'switch_open' : 'switch_close'"
71   - ></div>
72   - <div class="text-gray-500 text-sm truncate">{{ getDesign.attribute }}</div>
73   - <UpdateTime :time="time" />
  73 + <div class="flex-1 flex justify-center items-center flex-col" :style="getScale">
  74 + <div
  75 + :style="{
  76 + '--open-color': getDesign.openColor,
  77 + '--close-color': getDesign.closeColor,
  78 + }"
  79 + :class="isOpenClose ? 'switch_open' : 'switch_close'"
  80 + ></div>
  81 + <div class="text-gray-500 text-sm truncate">{{ getDesign.attribute }}</div>
  82 + </div>
  83 + <UpdateTime v-if="getDesign.showTime" :time="time" />
74 84 </main>
75 85 </template>
76 86 <style lang="less" scoped>
... ...
src/views/visual/packages/components/Other/SwitchStatus/config.ts renamed from src/views/visual/packages/components/Text/SwitchStatus/config.ts
... ... @@ -10,11 +10,12 @@ import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages
10 10 import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
11 11
12 12 export const option: PublicPresetOptions = {
13   - [ComponentConfigFieldEnum.ICON]: 'shuiwen',
14   - [ComponentConfigFieldEnum.ICON_COLOR]: '#367bff',
15   - [ComponentConfigFieldEnum.ICON_CLOSE]: 'shuiwen',
  13 + [ComponentConfigFieldEnum.ICON]: 'dianyuandianya',
  14 + [ComponentConfigFieldEnum.ICON_COLOR]: '#ffa621',
  15 + [ComponentConfigFieldEnum.ICON_CLOSE]: 'dianyuandianya',
16 16 [ComponentConfigFieldEnum.ICON_COLOR_CLOSE]: '#CCCCCC',
17 17 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  18 + [ComponentConfigFieldEnum.SHOW_TIME]: false,
18 19 };
19 20
20 21 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
src/views/visual/packages/components/Other/SwitchStatus/config.vue renamed from src/views/visual/packages/components/Text/SwitchStatus/config.vue
... ... @@ -52,6 +52,12 @@
52 52 component: 'Checkbox',
53 53 defaultValue: option.showDeviceName,
54 54 },
  55 + {
  56 + field: ComponentConfigFieldEnum.SHOW_TIME,
  57 + label: '显示时间',
  58 + component: 'Checkbox',
  59 + defaultValue: option.showTime,
  60 + },
55 61 ],
56 62 showActionButtonGroup: false,
57 63 labelWidth: 120,
... ...
src/views/visual/packages/components/Other/SwitchStatus/datasource.vue renamed from src/views/visual/packages/components/Text/SwitchStatus/datasource.vue
src/views/visual/packages/components/Other/SwitchStatus/index.ts renamed from src/views/visual/packages/components/Text/SwitchStatus/index.ts
... ... @@ -5,5 +5,5 @@ const componentKeys = useComponentKeys('SwitchStatus');
5 5 export const SwitchStatusConfig: ConfigType = {
6 6 ...componentKeys,
7 7 title: '开关量状态',
8   - package: PackagesCategoryEnum.TEXT,
  8 + package: PackagesCategoryEnum.OTHER,
9 9 };
... ...
  1 +<script lang="ts" setup>
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  3 + import { option } from './config';
  4 + import { SvgIcon } from '/@/components/Icon';
  5 + import { computed, ref } from 'vue';
  6 + import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  7 + import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  8 + import { useDataFetch } from '../../../hook/socket/useSocket';
  9 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  10 + import { useReceiveValue } from '../../../hook/useReceiveValue';
  11 +
  12 + const props = defineProps<{
  13 + config: ComponentPropsConfigType<typeof option>;
  14 + }>();
  15 +
  16 + const time = ref<Nullable<number>>(null);
  17 + const isOpenClose = ref<boolean>(true);
  18 +
  19 + const getDesign = computed(() => {
  20 + const { persetOption = {}, option } = props.config;
  21 + const {
  22 + iconColor: persetIconColor,
  23 + unit: perseUnit,
  24 + icon: persetIcon,
  25 + fontColor: persetFontColor,
  26 + iconClose: persetIconCLose,
  27 + iconColorClose: persetIconColorClose,
  28 + showTime: persetShowTime,
  29 + } = persetOption;
  30 +
  31 + const { componentInfo, attributeName, attributeRename } = option;
  32 +
  33 + const { icon, iconColor, fontColor, unit, iconClose, iconColorClose, showTime } =
  34 + componentInfo || {};
  35 + return {
  36 + iconColor: iconColor || persetIconColor,
  37 + unit: unit ?? perseUnit,
  38 + icon: icon || persetIcon,
  39 + fontColor: fontColor || persetFontColor,
  40 + attribute: attributeRename || attributeName,
  41 + iconClose: iconClose || persetIconCLose,
  42 + iconColorClose: iconColorClose || persetIconColorClose,
  43 + showTime: showTime ?? persetShowTime,
  44 + };
  45 + });
  46 +
  47 + const { getNumberValue } = useReceiveValue();
  48 +
  49 + const updateFn: DataFetchUpdateFn = (message, attribute) => {
  50 + const { data = {} } = message;
  51 + const [latest] = data[attribute] || [];
  52 + if (!latest.length) return;
  53 + const [timespan, value] = latest;
  54 + time.value = timespan;
  55 + isOpenClose.value = Boolean(getNumberValue(value));
  56 + };
  57 +
  58 + useDataFetch(props, updateFn);
  59 +</script>
  60 +
  61 +<template>
  62 + <main class="w-full h-full flex flex-col justify-center items-center">
  63 + <DeviceName :config="config" />
  64 + <div class="flex flex-1 flex-col justify-center items-center">
  65 + <SvgIcon
  66 + :name="isOpenClose ? getDesign.icon : getDesign.iconClose"
  67 + prefix="iconfont"
  68 + :size="60"
  69 + :style="{ color: isOpenClose ? getDesign.iconColor : getDesign.iconColorClose }"
  70 + />
  71 + <div class="text-gray-500 text-sm truncate m-2">{{ getDesign.attribute || '' }}</div>
  72 + </div>
  73 + <UpdateTime v-show="getDesign.showTime" :time="time" />
  74 + </main>
  75 +</template>
... ...
1 1 import { MonitorVideoConfig } from './MonitorVideo';
2 2 import { PictureConfig } from './Picture';
3 3 import { SwitchSignalLightConfig } from './SwitchSignalLight';
  4 +import { SwitchStatusConfig } from './SwitchStatus';
4 5
5   -export const OtherList = [MonitorVideoConfig, PictureConfig, SwitchSignalLightConfig];
  6 +export const OtherList = [
  7 + MonitorVideoConfig,
  8 + PictureConfig,
  9 + SwitchSignalLightConfig,
  10 + SwitchStatusConfig,
  11 +];
... ...
... ... @@ -2,8 +2,9 @@
2 2 import { ref } from 'vue';
3 3 import { Image as AntImage } from 'ant-design-vue';
4 4 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
5   - import { useDataFetch } from '../../../hook/useSocket';
6   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '../../../index.type';
  5 + import { ComponentPropsConfigType } from '../../../index.type';
  6 + import { useDataFetch } from '../../../hook/socket/useSocket';
  7 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
7 8
8 9 const props = defineProps<{
9 10 config: ComponentPropsConfigType;
... ... @@ -16,27 +17,6 @@
16 17
17 18 const url = ref<string>(fallback);
18 19
19   - // const getImagBase64 = ref(fallback);
20   -
21   - // const getBase64Image = (url: string) => {
22   - // let canvas: Nullable<HTMLCanvasElement> = document.createElement('canvas');
23   - // const ctx = canvas.getContext('2d');
24   - // let image: Nullable<HTMLImageElement> = new Image();
25   -
26   - // image.onload = function () {
27   - // canvas!.height = image!.height;
28   - // canvas!.width = image!.width;
29   - // ctx?.drawImage(image!, 0, 0);
30   - // const dataUrl = canvas!.toDataURL('image/png');
31   - // getImagBase64.value = dataUrl;
32   - // console.log(dataUrl);
33   - // image = null;
34   - // canvas = null;
35   - // };
36   - // image.setAttribute('crossOrigin', 'Anonymous');
37   - // image.src = url;
38   - // };
39   -
40 20 const updateFn: DataFetchUpdateFn = (message, attribute) => {
41 21 const { data = {} } = message;
42 22 const [latest] = data[attribute] || [];
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, SeriesOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick } from 'vue';
4   - import { useMultipleDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useComponentScale } from '../../../hook/useComponentScale';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10 9 import { useReceiveMessage } from '../../../hook/useReceiveMessage';
11 10 import { useReceiveValue } from '../../../hook/useReceiveValue';
  11 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  12 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 13
13 14 const props = defineProps<{
14 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -66,10 +67,8 @@
66 67 },
67 68 }))
68 69 );
69   - // console.log(unref(series), 'series');
70 70
71 71 const options = (): EChartsOption => {
72   - // getStageColor(gradientInfo);
73 72 return {
74 73 tooltip: {
75 74 trigger: 'item',
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, SeriesOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick } from 'vue';
4   - import { useMultipleDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useComponentScale } from '../../../hook/useComponentScale';
... ... @@ -10,6 +9,8 @@
10 9 import { useReceiveMessage } from '../../../hook/useReceiveMessage';
11 10 import { useReceiveValue } from '../../../hook/useReceiveValue';
12 11 import { toRaw } from 'vue';
  12 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  13 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
13 14
14 15 const props = defineProps<{
15 16 config: ComponentPropsConfigType<typeof option>;
... ...
... ... @@ -8,15 +8,6 @@ import {
8 8 } from '../../../index.type';
9 9 import { PublicConfigClass, componentInitConfig } from '../../../publicConfig';
10 10 import { ComponentConfigFieldEnum } from '../../../enum';
11   -
12   -export enum Gradient {
13   - FIRST = 'first',
14   - SECOND = 'second',
15   -}
16   -export enum GradientColor {
17   - FIRST = '#07ffd6',
18   - SECOND = '#5eff10',
19   -}
20 11 export const option: PublicPresetOptions = {
21 12 multipleDataSourceComponent: true,
22 13 [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347',
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, SeriesOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick, toRaw } from 'vue';
4   - import { useMultipleDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7   - import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  6 + // import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useComponentScale } from '../../../hook/useComponentScale';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  10 + import { useReceiveValue } from '../../../hook/useReceiveValue';
  11 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  12 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 13
11 14 const props = defineProps<{
12 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -21,33 +24,45 @@
21 24 const getDesign = computed(() => {
22 25 const { persetOption, option } = props.config;
23 26 const { dataSource = [] } = option || {};
24   - const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
  27 + const {
  28 + unit: presetUnit,
  29 + fontColor: presetFontColor,
  30 + showDeviceName: persetShowDeviceName,
  31 + } = persetOption || {};
25 32 return {
26 33 dataSource: dataSource?.map((item) => {
27   - const { unit, fontColor } = item.componentInfo || {};
28   - const { attribute, attributeRename } = item;
  34 + const { unit, fontColor, showDeviceName } = item.componentInfo || {};
  35 + const { attribute, attributeName, attributeRename, deviceId, deviceName, deviceRename } =
  36 + item;
29 37 return {
30 38 unit: unit ?? presetUnit,
31 39 fontColor: fontColor ?? presetFontColor,
32 40 attribute,
  41 + attributeName,
33 42 attributeRename,
  43 + deviceName,
  44 + deviceRename,
  45 + showDeviceName: showDeviceName ?? persetShowDeviceName,
  46 + id: deviceId,
34 47 };
35 48 }),
36 49 };
37 50 });
  51 +
38 52 const seriesList = [
39 53 { value: 120, name: '123', itemStyle: { color: '#02E5F0' } },
40 54 { value: 150, name: '456', itemStyle: { color: '#028CF0' } },
41 55 { value: 40, name: '789', itemStyle: { color: '#F09202' } },
42 56 ];
43 57 const data = ['温度', '湿度', '温度1'];
  58 +
44 59 const options = (): EChartsOption => {
45   - // getStageColor(gradientInfo);
46 60 return {
47 61 color: ['#3398DB'],
48 62 tooltip: {
49 63 // 提示框
50   - trigger: 'axis',
  64 + trigger: 'item',
  65 + confine: true,
51 66 axisPointer: {
52 67 type: 'shadow',
53 68 },
... ... @@ -58,9 +73,6 @@
58 73 axisTick: {
59 74 alignWithLabel: true,
60 75 },
61   - // axisPointer: {
62   - // type: 'line',
63   - // },
64 76 },
65 77 grid: {
66 78 top: '15%',
... ... @@ -103,32 +115,40 @@
103 115 } as EChartsOption);
104 116 };
105 117
106   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
107   - const { data = {} } = message;
108   - const { dataSource } = unref(getDesign);
109   - const series = dataSource.map((item) => {
110   - const { attribute, attributeRename, fontColor, unit } = item;
111   - const [latest] = data[attribute] || [];
112   - const [_timespan, value] = latest || [];
113   -
114   - return {
115   - value,
116   - name: attributeRename ?? attribute,
117   - itemStyle: { color: fontColor },
118   - tooltip: {
119   - valueFormatter(value) {
120   - return `${value} ${unit ?? ''}`;
121   - },
  118 + const { forEachGroupMessage } = useReceiveMessage();
  119 + const { getNumberValue } = useReceiveValue();
  120 +
  121 + const series = ref(
  122 + unref(getDesign).dataSource.map((item) => ({
  123 + value: 0,
  124 + name: `${item.showDeviceName ? `${item.deviceRename || item.deviceName}-` : ''}${
  125 + item.attributeRename || item.attributeName || item.attribute
  126 + }`,
  127 + attribute: item.attribute,
  128 + id: item.id,
  129 + itemStyle: {
  130 + color: item.fontColor,
  131 + },
  132 + tooltip: {
  133 + valueFormatter(value) {
  134 + return `${value} ${item.unit ?? ''}`;
122 135 },
123   - } as SeriesOption['data'];
124   - });
125   - const xAxisData = series.map((item) => {
126   - const { name } = item as any;
127   - return name;
  136 + },
  137 + }))
  138 + );
  139 +
  140 + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => {
  141 + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  142 + series.value.forEach((item) => {
  143 + if (item.id == deviceId && item.attribute === attribute && value) {
  144 + item.value = getNumberValue(value);
  145 + }
  146 + });
  147 + time.value = timespan;
128 148 });
129   - // }
130   - // console.log(message, 'message', series, 'series', sum);
131   - updateChart(series, xAxisData);
  149 + const xAxisData = unref(series).map((item) => item.name);
  150 +
  151 + updateChart(toRaw(unref(series)), xAxisData);
132 152 };
133 153
134 154 useMultipleDataFetch(props, updateFn);
... ... @@ -136,10 +156,10 @@
136 156 onMounted(() => {
137 157 initial();
138 158 // !props.config.option.uuid && randomFn();
139   - !props.config.option.uuid;
140 159 });
141 160
142 161 const resize = async () => {
  162 + console.log(123321);
143 163 await nextTick();
144 164
145 165 // 修改echarts大小
... ... @@ -159,7 +179,11 @@
159 179 <template>
160 180 <main class="w-full h-full flex flex-col justify-center items-center">
161 181 <DeviceName :config="config" />
162   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
163   - <UpdateTime :time="time" />
  182 + <div
  183 + ref="chartRefEl"
  184 + class="flex-1 w-full h-7/8 flex justify-center items-center flex-col mb-3"
  185 + >
  186 + </div>
  187 + <!-- <UpdateTime :time="time" /> -->
164 188 </main>
165 189 </template>
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, SeriesOption, ECharts, init } from 'echarts';
3 3 import { unref, ref, onMounted, computed, nextTick, toRaw } from 'vue';
4   - import { useMultipleDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7   - import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  6 + // import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useComponentScale } from '../../../hook/useComponentScale';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  10 + import { useReceiveValue } from '../../../hook/useReceiveValue';
  11 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  12 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 13
11 14 const props = defineProps<{
12 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -21,16 +24,26 @@
21 24 const getDesign = computed(() => {
22 25 const { persetOption, option } = props.config;
23 26 const { dataSource = [] } = option || {};
24   - const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
  27 + const {
  28 + unit: presetUnit,
  29 + fontColor: presetFontColor,
  30 + showDeviceName: persetShowDeviceName,
  31 + } = persetOption || {};
25 32 return {
26 33 dataSource: dataSource?.map((item) => {
27   - const { unit, fontColor } = item.componentInfo || {};
28   - const { attribute, attributeRename } = item;
  34 + const { unit, fontColor, showDeviceName } = item.componentInfo || {};
  35 + const { attribute, attributeName, attributeRename, deviceId, deviceName, deviceRename } =
  36 + item;
29 37 return {
30 38 unit: unit ?? presetUnit,
31 39 fontColor: fontColor ?? presetFontColor,
32 40 attribute,
  41 + attributeName,
33 42 attributeRename,
  43 + deviceName,
  44 + deviceRename,
  45 + showDeviceName: showDeviceName ?? persetShowDeviceName,
  46 + id: deviceId,
34 47 };
35 48 }),
36 49 };
... ... @@ -47,7 +60,8 @@
47 60 color: ['#3398DB'],
48 61 tooltip: {
49 62 // 提示框
50   - trigger: 'axis',
  63 + trigger: 'item',
  64 + confine: true,
51 65 axisPointer: {
52 66 type: 'shadow',
53 67 },
... ... @@ -103,32 +117,39 @@
103 117 } as EChartsOption);
104 118 };
105 119
106   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
107   - const { data = {} } = message;
108   - const { dataSource } = unref(getDesign);
109   - const series = dataSource.map((item) => {
110   - const { attribute, attributeRename, fontColor, unit } = item;
111   - const [latest] = data[attribute] || [];
112   - const [_timespan, value] = latest || [];
113   -
114   - return {
115   - value,
116   - name: attributeRename ?? attribute,
117   - itemStyle: { color: fontColor },
118   - tooltip: {
119   - valueFormatter(value) {
120   - return `${value} ${unit ?? ''}`;
121   - },
  120 + const { forEachGroupMessage } = useReceiveMessage();
  121 + const { getNumberValue } = useReceiveValue();
  122 +
  123 + const series = ref(
  124 + unref(getDesign).dataSource.map((item) => ({
  125 + value: 0,
  126 + name: `${item.showDeviceName ? `${item.deviceRename || item.deviceName}-` : ''}${
  127 + item.attributeRename || item.attributeName || item.attribute
  128 + }`,
  129 + attribute: item.attribute,
  130 + id: item.id,
  131 + itemStyle: {
  132 + color: item.fontColor,
  133 + },
  134 + tooltip: {
  135 + valueFormatter(value) {
  136 + return `${value} ${item.unit ?? ''}`;
122 137 },
123   - } as SeriesOption['data'];
124   - });
125   - const yAxisData = series.map((item) => {
126   - const { name } = item as any;
127   - return name;
  138 + },
  139 + }))
  140 + );
  141 +
  142 + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => {
  143 + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  144 + series.value.forEach((item) => {
  145 + if (item.id == deviceId && item.attribute === attribute && value) {
  146 + item.value = getNumberValue(value);
  147 + }
  148 + time.value = timespan;
  149 + });
128 150 });
129   - // }
130   - // console.log(message, 'message', series, 'series', sum);
131   - updateChart(series, yAxisData);
  151 + const xAxisData = unref(series).map((item) => item.name);
  152 + updateChart(toRaw(unref(series)), xAxisData);
132 153 };
133 154
134 155 useMultipleDataFetch(props, updateFn);
... ... @@ -159,7 +180,11 @@
159 180 <template>
160 181 <main class="w-full h-full flex flex-col justify-center items-center">
161 182 <DeviceName :config="config" />
162   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
163   - <UpdateTime :time="time" />
  183 + <div
  184 + ref="chartRefEl"
  185 + class="flex-1 w-full h-full flex flex-col justify-center items-center mb-3"
  186 + >
  187 + </div>
  188 + <!-- <UpdateTime :time="time" /> -->
164 189 </main>
165 190 </template>
... ...
1 1 <script lang="ts" setup>
2   - import { reactive, ref } from 'vue';
3   - import { useMultipleDataFetch } from '../../../hook/useSocket';
4   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  2 + import { computed, unref } from 'vue';
  3 + import { ComponentPropsConfigType } from '../../../index.type';
5 4 import { option } from './config';
6 5 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
7 6 import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
  7 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  8 + import { useReceiveValue } from '../../../hook/useReceiveValue';
  9 + import { formatToDateTime } from '/@/utils/dateUtil';
  10 + import { nextTick } from 'vue';
  11 + import { onMounted } from 'vue';
  12 + import { toRaw } from 'vue';
  13 + import { useComponentScale } from '../../../hook/useComponentScale';
  14 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  15 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  16 +
  17 + interface IList {
  18 + [key: string]: string | number | object;
  19 + }
8 20
9 21 const props = defineProps<{
10 22 config: ComponentPropsConfigType<typeof option>;
11 23 }>();
12 24
13   - const realTimeList = ref<any>([
14   - { attribute: '测试', price: 2, time: '2023-06-29' },
15   - { attribute: '测试1', price: 21, time: '2023-06-29' },
16   - { attribute: '测试2', price: 213, time: '2023-06-29' },
17   - ]);
18   - const realTimeColumn = reactive<BasicColumn[]>([
19   - { title: '属性', dataIndex: 'attribute', width: 80, ellipsis: true },
20   - { title: '值', dataIndex: 'price', width: 80, ellipsis: true },
21   - { title: '时间', dataIndex: 'time', width: 80, ellipsis: true },
22   - ]);
  25 + const columns: BasicColumn[] = [
  26 + {
  27 + title: '设备属性',
  28 + dataIndex: 'attribute',
  29 + width: 80,
  30 + ellipsis: true,
  31 + format(text) {
  32 + if (props.config.option.mode == 'SELECT_PREVIEW') return text;
  33 + const { uniqueArr } = unref(getDesign);
  34 + const values = uniqueArr.filter((item) => item[text])[0][text];
  35 + return values;
  36 + },
  37 + },
  38 + {
  39 + title: '值',
  40 + dataIndex: 'value',
  41 + width: 80,
  42 + ellipsis: true,
  43 + format(text, record) {
  44 + const value = text ? text + (record.unit || '') : '';
  45 + return value;
  46 + },
  47 + },
  48 + {
  49 + title: '时间',
  50 + dataIndex: 'time',
  51 + width: 110,
  52 + ellipsis: true,
  53 + format(text) {
  54 + return formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss');
  55 + },
  56 + },
  57 + ];
23 58
24   - const [registerTable] = useTable({
  59 + const [registerTable, { setTableData, getDataSource, redoHeight, setProps }] = useTable({
25 60 showIndexColumn: false,
26 61 showTableSetting: false,
27   - dataSource: realTimeList,
28 62 canResize: true,
29   - maxHeight: 144,
30 63 size: 'small',
31   - // columns: [{ title: '属性', dataIndex: 'attribute', width: 80 }],
32   - columns: realTimeColumn as any,
  64 + maxHeight: 144,
  65 + columns,
33 66 });
34 67
35   - // const getDesign = computed(() => {
36   - // const { persetOption, option } = props.config;
37   - // clearInterval;
38   - // return {};
39   - // });
  68 + const getDesign = computed(() => {
  69 + const { persetOption, option } = props.config;
  70 + const { dataSource = [] } = option || {};
  71 + const { unit: presetUnit, showDeviceName: presetShowDeviceName } = persetOption || {};
  72 + const convert = dataSource.map((item) => {
  73 + return {
  74 + [item.attribute + '-' + item.deviceId]: item.deviceName + '-' + item.attributeName,
  75 + id: item.deviceId,
  76 + };
  77 + });
  78 +
  79 + const uniqueSet = new Set();
  80 + const uniqueArr = convert.filter((obj) => {
  81 + const jsonString = JSON.stringify(obj);
  82 + const isUnique = !uniqueSet.has(jsonString);
  83 + uniqueSet.add(jsonString);
  84 + return isUnique;
  85 + });
  86 + return {
  87 + uniqueArr,
  88 + dataSource: dataSource?.map((item) => {
  89 + const { unit, showDeviceName } = item.componentInfo || {};
  90 + const { attribute, attributeName, attributeRename, deviceName, deviceRename, deviceId } =
  91 + item;
  92 + return {
  93 + unit: unit ?? presetUnit,
  94 + attribute,
  95 + attributeName: attributeRename || attributeName,
  96 + showDeviceName: showDeviceName ?? presetShowDeviceName,
  97 + deviceName: deviceRename || deviceName,
  98 + id: deviceId,
  99 + };
  100 + }),
  101 + };
  102 + });
40 103
41   - // const randomFn = () => {
42   - // useIntervalFn(() => {
43   - // const value = (Math.random() * 100).toFixed(0);
44   - // unref(chartInstance)?.setOption({
45   - // series: [{ data: [{ value }] }, { data: [{ value }] }],
46   - // } as EChartsOption);
47   - // }, 3000);
48   - // };
  104 + const { forEachGroupMessage } = useReceiveMessage();
  105 + const { getNumberValue } = useReceiveValue();
49 106
50   - const updateFn: MultipleDataFetchUpdateFn = () => {};
  107 + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => {
  108 + const list: IList = {};
  109 + const list1: IList = {};
  110 + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  111 + list[deviceId + attribute] = getNumberValue(value);
  112 + list.time = timespan || list.time;
  113 +
  114 + if (timespan && value) {
  115 + list1[attribute + '-' + deviceId] = {
  116 + attribute: attribute + '-' + deviceId,
  117 + value: value ? getNumberValue(value) : value,
  118 + time: timespan,
  119 + };
  120 + }
  121 + });
  122 + await nextTick();
  123 + setTableData([...Object.values(list1), ...toRaw(unref(getDataSource()))]);
  124 + };
51 125
52 126 useMultipleDataFetch(props, updateFn);
  127 + onMounted(async () => {
  128 + !props.config.option.dataSource &&
  129 + setTableData([
  130 + { attribute: '温度', value: 1, time: '2023-06-29' },
  131 + { attribute: '湿度', value: 1, time: '2023-06-29' },
  132 + { attribute: '湿度', value: 1, time: '2023-06-29' },
  133 + ]);
  134 +
  135 + await nextTick();
  136 + resize();
  137 + });
  138 + const resize = async () => {
  139 + const { height } = unref(getContainerSize);
  140 + height && setProps({ maxHeight: height - 110, scroll: { x: 470, y: height - 110 } });
  141 +
  142 + await nextTick();
  143 + redoHeight();
  144 + };
53 145
54   - // const { getRatio } = useComponentScale(props);
  146 + const { getContainerSize } = useComponentScale(props, resize);
55 147 </script>
56 148
57 149 <template>
58   - <main class="flex flex-col justify-center items-center">
  150 + <main class="flex flex-col justify-center items-center w-full h-full">
59 151 <DeviceName :config="config" />
60   - <div>
  152 + <div class="w-full h-full">
61 153 <!-- <PageWrapper> -->
62   - <BasicTable @register="registerTable" />
  154 + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" />
63 155 <!-- </PageWrapper> -->
64 156 </div>
65 157 </main>
... ...
... ... @@ -8,24 +8,17 @@
8 8 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
9 9 schemas: [
10 10 {
11   - field: ComponentConfigFieldEnum.FONT_COLOR,
12   - label: '数值字体颜色',
13   - component: 'ColorPicker',
14   - changeEvent: 'update:value',
15   - defaultValue: option.fontColor,
16   - },
17   - {
18 11 field: ComponentConfigFieldEnum.UNIT,
19 12 label: '数值单位',
20 13 component: 'Input',
21 14 defaultValue: option.unit,
22 15 },
23   - {
24   - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
25   - label: '显示设备名称',
26   - component: 'Checkbox',
27   - defaultValue: option.showDeviceName,
28   - },
  16 + // {
  17 + // field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  18 + // label: '显示设备名称',
  19 + // component: 'Checkbox',
  20 + // defaultValue: option.showDeviceName,
  21 + // },
29 22 ],
30 23 showActionButtonGroup: false,
31 24 labelWidth: 120,
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, ECharts, init } from 'echarts';
3   - import { unref, ref, onMounted, nextTick, toRaw } from 'vue';
4   - import { useMultipleDataFetch } from '../../../hook/useSocket';
5   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
  3 + import { unref, ref, onMounted, nextTick, toRaw, computed } from 'vue';
  4 + import { ComponentPropsConfigType } from '../../../index.type';
6 5 import { option } from './config';
7   - import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  6 + // import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { useComponentScale } from '../../../hook/useComponentScale';
9 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10   - import { dateFormat } from '/@/utils/common/compUtils';
  9 + import { useIntervalFn } from '@vueuse/core';
  10 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  11 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  13 + import { formatToDateTime } from '/@/utils/dateUtil';
  14 + interface IList {
  15 + [key: string]: string | number;
  16 + }
11 17
12 18 const props = defineProps<{
13 19 config: ComponentPropsConfigType<typeof option>;
14 20 }>();
15   -
16 21 const chartRefEl = ref<Nullable<HTMLDivElement>>(null);
17 22
18 23 const chartInstance = ref<Nullable<ECharts>>(null);
  24 + // const time = ref<Nullable<number>>(null);
19 25
20   - const time = ref<Nullable<number>>(null);
  26 + const updateInterval = ref<number>(1000); //默认每秒更新一次
  27 + const maxDataPoints = ref<number>(30); //默认每秒显示10个数据点
21 28
22   - function randomData() {
23   - now.value = now.value + oneDay;
24   - const newTime = dateFormat(unref(now), 'MM-dd hh:mm:ss');
25   - value = value + Math.random() * 21 - 10;
26   - return {
27   - name: newTime,
28   - value: [newTime, Math.round(value)],
29   - };
30   - }
31   - const data = ref<any>([]);
32   - const now = ref<number>(1688026367000);
33   - let oneDay = 5000; //间隔秒数
34   - let value = Math.random() * 1000;
35   - for (let i = 0; i < 10; i++) {
36   - data.value.push(randomData());
37   - }
38   - setInterval(function () {
39   - for (let i = 0; i < 1; i++) {
40   - data.value.shift();
41   - data.value.push(randomData());
42   - }
43   - unref(chartInstance)?.setOption(options());
44   - }, 1000);
45   -
46   - // const getDesign = computed(() => {
47   - // const { persetOption, option } = props.config;
48   - // const { dataSource = [] } = option || {};
49   - // const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
50   - // return {
51   - // dataSource: dataSource?.map((item) => {
52   - // const { unit, fontColor } = item.componentInfo || {};
53   - // const { attribute, attributeRename } = item;
54   - // return {
55   - // unit: unit ?? presetUnit,
56   - // fontColor: fontColor ?? presetFontColor,
57   - // attribute,
58   - // attributeRename,
59   - // };
60   - // }),
61   - // };
62   - // });
  29 + const chartData = ref<{ time: string | number; value: number }[]>([]);
  30 + const legendData = ref<string[]>(['温度']);
  31 + const timeList = ref<string[]>([]);
  32 +
  33 + const generateRandomData = () => {
  34 + const minValue = 0;
  35 + const maxValue = 100;
  36 + const time = new Date().toLocaleTimeString();
  37 + const value = Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
  38 + return { time, value, name: '温度' };
  39 + };
63 40
64 41 const options = (): EChartsOption => {
65   - // getStageColor(gradientInfo);
66 42 return {
67   - trigger: 'axis',
68   - axisPointer: {
69   - lineStyle: {
70   - width: 1,
71   - color: '#019680',
72   - },
73   - },
74 43 tooltip: {
75   - show: true,
  44 + // trigger: 'axis',
76 45 },
77 46 legend: {
78 47 top: '10%',
79 48 left: 'center',
80   - data: ['温度', '湿度'],
  49 + data: ['温度'],
81 50 },
82 51 grid: {
83   - top: '45%',
84   - left: '20%',
85   - bottom: '14%',
  52 + top: '30%',
  53 + left: '6%',
  54 + right: '10%',
  55 + bottom: '8%',
86 56 containLabel: true,
87 57 },
88 58 xAxis: {
89 59 type: 'category',
90   - splitLine: {
91   - show: true,
92   - lineStyle: {
93   - width: 1,
94   - type: 'solid',
95   - color: 'rgba(226,226,226,0.5)',
96   - },
97   - },
98   - data: toRaw(unref(data)).map((item) => item.name),
  60 + // boundaryGap: false,
99 61 },
100 62 yAxis: {
101 63 type: 'value',
102   - boundaryGap: [0, '50%'],
103   - splitLine: {
104   - show: false,
105   - },
  64 + boundaryGap: [0, '100%'],
106 65 },
107   - series: [
108   - {
109   - type: 'line',
110   - name: '温度',
111   - stack: 'Total',
112   - data: unref(data).map((item) => item.value),
113   - },
114   - {
115   - type: 'line',
116   - name: '湿度',
117   - stack: 'Total',
118   - data: unref(data).map((item) => {
119   - return Number(item.value[1]) * 0.99;
120   - }),
121   - },
122   - ],
  66 + series: [{ type: 'line', name: '温度', data: [] }],
123 67 };
124 68 };
125 69
  70 + const random = () => {
  71 + useIntervalFn(() => {
  72 + const newData = generateRandomData();
  73 + chartData.value.push(newData);
  74 + if (unref(chartData).length > maxDataPoints.value) {
  75 + chartData.value.shift();
  76 + }
  77 + unref(chartInstance)?.setOption({
  78 + xAxis: {
  79 + data: toRaw(unref(chartData).map((data) => data.time)),
  80 + },
  81 + series: [{ data: toRaw(unref(chartData).map((item) => item.value)) }],
  82 + });
  83 + }, updateInterval);
  84 + };
  85 +
  86 + const getDesign = computed(() => {
  87 + const { persetOption, option } = props.config;
  88 + const { dataSource = [] } = option || {};
  89 + const {
  90 + unit: presetUnit,
  91 + fontColor: presetFontColor,
  92 + // lineColor: persetLineColor,
  93 + } = persetOption || {};
  94 +
  95 + return {
  96 + dataSource: dataSource?.map((item) => {
  97 + const { unit, showDeviceName, fontColor } = item.componentInfo || {};
  98 + const { attribute, attributeRename, deviceId, attributeName, deviceName, deviceRename } =
  99 + item;
  100 + return {
  101 + unit: unit ?? presetUnit,
  102 + fontColor: fontColor ?? presetFontColor,
  103 + attribute,
  104 + attributeName: attributeRename || attributeName,
  105 + showDeviceName,
  106 + deviceName: deviceRename || deviceName,
  107 + id: deviceId,
  108 + // lineColor: lineColor || persetLineColor,
  109 + };
  110 + }),
  111 + };
  112 + });
  113 +
126 114 const initial = () => {
127 115 chartInstance.value = init(unref(chartRefEl)! as HTMLElement);
128 116 chartInstance.value.setOption(options());
129 117 };
130 118
131   - // const updateChart = (data: SeriesOption['data'], yAxisData) => {
132   - // unref(chartInstance)?.setOption({
133   - // series: [{ data }],
134   - // yAxis: { data: yAxisData },
135   - // } as EChartsOption);
136   - // };
137   -
138   - const updateFn: MultipleDataFetchUpdateFn = () => {
139   - // console.log(message, 'message');
140   - return {};
  119 + const series = ref(
  120 + unref(getDesign).dataSource.map((item) => {
  121 + return {
  122 + type: 'line',
  123 + name: `${item.deviceName} - ${item.attributeName}`,
  124 + data: [] as { name: string; value: number }[],
  125 + id: item.id,
  126 + attribute: item.attribute,
  127 + // itemStyle: {
  128 + // color: item.lineColor,
  129 + // },
  130 + // lineStyle: {
  131 + // color: item.lineColor,
  132 + // },
  133 + };
  134 + })
  135 + );
  136 +
  137 + const { forEachGroupMessage } = useReceiveMessage();
  138 +
  139 + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => {
  140 + legendData.value = unref(getDesign).dataSource.map((item) => {
  141 + return `${item.deviceName} - ${item.attributeName}`;
  142 + });
  143 + const list: IList | any = {};
  144 + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  145 + list.time = timespan || list.time;
  146 + series.value.forEach((item) => {
  147 + if (item.id === deviceId && item.attribute === attribute) {
  148 + item.data.push({
  149 + name: formatToDateTime(list.time, 'YYYY-MM-DD HH:mm:ss'),
  150 + value: value,
  151 + });
  152 + if (item.data.length > unref(maxDataPoints)) {
  153 + item.data.shift();
  154 + }
  155 + }
  156 + });
  157 + });
  158 + list.time && timeList.value.push(formatToDateTime(list.time, 'YYYY-MM-DD HH:mm:ss'));
  159 + if (unref(timeList).length > unref(maxDataPoints)) {
  160 + timeList.value.shift();
  161 + }
  162 + await nextTick();
  163 + unref(chartInstance)?.setOption({
  164 + xAxis: {
  165 + data: toRaw(unref(timeList)),
  166 + },
  167 + legend: {
  168 + data: toRaw(unref(legendData)),
  169 + },
  170 + series: toRaw(
  171 + unref(series).map((item) => {
  172 + const { type, name, data } = item;
  173 + return {
  174 + type,
  175 + name,
  176 + data,
  177 + // itemStyle,
  178 + // lineStyle,
  179 + };
  180 + })
  181 + ),
  182 + });
141 183 };
142 184
143 185 useMultipleDataFetch(props, updateFn);
144   -
145 186 onMounted(() => {
146 187 initial();
147   - // !props.config.option.uuid && randomFn();
148   - !props.config.option.uuid;
  188 + !props.config.option.dataSource?.length && random();
149 189 });
150 190
151 191 const resize = async () => {
152 192 await nextTick();
153 193
154 194 // 修改echarts大小
155   - unref(chartInstance)?.setOption({
156   - legend: {
157   - textStyle: {
158   - fontSize: 14 * unref(getRatio),
159   - },
160   - },
161   - } as EChartsOption);
  195 + // unref(chartInstance)?.setOption({
  196 + // legend: {
  197 + // textStyle: {
  198 + // fontSize: 14 * unref(getRatio),
  199 + // },
  200 + // },
  201 + // } as EChartsOption);
162 202 unref(chartInstance)?.resize();
163 203 };
164 204
165   - const { getRatio } = useComponentScale(props, resize);
  205 + useComponentScale(props, resize);
166 206 </script>
167 207
168 208 <template>
169   - <main class="w-full h-full flex flex-col justify-center items-center">
  209 + <main class="w-full h-full flex flex-col justify-center items-center flex-1">
170 210 <DeviceName :config="config" />
171   - <div ref="chartRefEl" class="flex-1 w-full h-full"> </div>
172   - <UpdateTime :time="time" />
  211 + <div
  212 + ref="chartRefEl"
  213 + class="flex-1 w-full h-7/8 flex-col justify-center items-center flex mb-3"
  214 + >
  215 + </div>
  216 + <!-- <UpdateTime :time="time" /> -->
173 217 </main>
174 218 </template>
... ...
1   -import { StatisticsComponent1Config } from './StatisticsComponent1';
2   -import { StatisticsComponent2Config } from './StatisticsComponent2';
3   -// import { StatisticsComponent3Config } from './StatisticsComponent3';
4   -// import { StatisticsComponent4Config } from './StatisticsComponent4';
5   -// import { StatisticsComponent5Config } from './StatisticsComponent5';
6   -// import { StatisticsComponent6Config } from './StatisticsComponent6';
  1 +// import { StatisticsComponent1Config } from './StatisticsComponent1';
  2 +// import { StatisticsComponent2Config } from './StatisticsComponent2';
  3 +import { StatisticsComponent3Config } from './StatisticsComponent3';
  4 +import { StatisticsComponent4Config } from './StatisticsComponent4';
  5 +import { StatisticsComponent5Config } from './StatisticsComponent5';
  6 +import { StatisticsComponent6Config } from './StatisticsComponent6';
7 7
8 8 export const STATISTICSList = [
9   - StatisticsComponent1Config,
10   - StatisticsComponent2Config,
11   - // StatisticsComponent3Config,
12   - // StatisticsComponent4Config,
13   - // StatisticsComponent5Config,
14   - // StatisticsComponent6Config,
  9 + // StatisticsComponent1Config,
  10 + // StatisticsComponent2Config,
  11 + StatisticsComponent3Config,
  12 + StatisticsComponent4Config,
  13 + StatisticsComponent5Config,
  14 + StatisticsComponent6Config,
15 15 ];
... ...
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 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 5 import { ref, onMounted, unref } from 'vue';
7 6 import { useIntervalFn } from '@vueuse/core';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { useReceiveValue } from '../../../hook/useReceiveValue';
  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>;
... ...
1   -<script lang="ts" setup>
2   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
3   - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type';
4   -
5   - import { option } from './config';
6   - import { SvgIcon } from '/@/components/Icon';
7   - import { computed } from 'vue';
8   - import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9   -
10   - const props = defineProps<{
11   - config: ComponentPropsConfigType<typeof option>;
12   - }>();
13   -
14   - const getDesign = computed(() => {
15   - const { persetOption = {}, option } = props.config;
16   -
17   - const {
18   - iconColor: persetIconColor,
19   - unit: perseUnit,
20   - icon: persetIcon,
21   - fontColor: persetFontColor,
22   - } = persetOption;
23   -
24   - const { componentInfo, attribute, attributeRename } = option;
25   -
26   - const { icon, iconColor, fontColor, unit } = componentInfo || {};
27   - return {
28   - iconColor: iconColor || persetIconColor,
29   - unit: unit ?? perseUnit,
30   - icon: icon || persetIcon,
31   - fontColor: fontColor || persetFontColor,
32   - attribute: attributeRename || attribute,
33   - };
34   - });
35   -
36   - // const svgList = ref<any>([
37   - // {
38   - // value: 26.2,
39   - // name: 'wendu',
40   - // icon: 'zongfushe',
41   - // unit: 'kw',
42   - // iconColor: '#367BFF',
43   - // fontColor: '#357CFB',
44   - // },
45   - // {
46   - // value: 53.7,
47   - // name: 'shidu',
48   - // icon: 'guangzhaoqiangdu',
49   - // unit: '℃',
50   - // iconColor: '#FFA000',
51   - // fontColor: '#FFA000',
52   - // },
53   - // ]);
54   -
55   - const updateFn: DataFetchUpdateFn = () => {};
56   -
57   - useDataFetch(props, updateFn);
58   -</script>
59   -
60   -<template>
61   - <main class="w-full h-full flex flex-col justify-evenly items-center">
62   - <DeviceName :config="config" />
63   - <div class="flex justify-center items-center">
64   - <SvgIcon
65   - :name="getDesign.icon!"
66   - prefix="iconfont"
67   - :size="60"
68   - :style="{ color: getDesign.iconColor }"
69   - />
70   - </div>
71   - </main>
72   -</template>
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 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 5 import { computed } from 'vue';
7 6 import { ref } from 'vue';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  8 + import { useDataFetch } from '../../../hook/socket/useSocket';
  9 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
9 10
10 11 const props = defineProps<{
11 12 config: ComponentPropsConfigType<typeof option>;
... ... @@ -30,6 +31,7 @@
30 31 const updateFn: DataFetchUpdateFn = (message, attribute) => {
31 32 const { data = {} } = message;
32 33 const [latest] = data[attribute] || [];
  34 + if (!latest.length) return;
33 35 const [_, value] = latest;
34 36 currentValue.value = value;
35 37 };
... ...
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 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 5 import { computed } from 'vue';
7 6 import { ref } from 'vue';
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>;
... ... @@ -33,6 +34,7 @@
33 34 const updateFn: DataFetchUpdateFn = (message, attribute) => {
34 35 const { data = {} } = message;
35 36 const [info] = data[attribute] || [];
  37 + if (!info.length) return;
36 38 const [timespan, value] = info;
37 39 currentValue.value = value;
38 40 time.value = timespan;
... ...
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 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 5 import { ref } from 'vue';
7 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 7 import { SvgIcon } from '/@/components/Icon';
9 8 import { computed } from 'vue';
10 9 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  10 + import { useDataFetch } from '../../../hook/socket/useSocket';
  11 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
11 12
12 13 const props = defineProps<{
13 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -42,6 +43,7 @@
42 43 const updateFn: DataFetchUpdateFn = (message, attribute) => {
43 44 const { data = {} } = message;
44 45 const [latest] = data[attribute] || [];
  46 + if (!latest.length) return;
45 47 const [timespan, value] = latest;
46 48 currentValue.value = value;
47 49 time.value = timespan;
... ...
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 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 5 import { computed } from 'vue';
7 6 import { ref } from 'vue';
8 7 import { SvgIcon } from '/@/components/Icon';
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>;
... ... @@ -39,6 +40,7 @@
39 40 const updateFn: DataFetchUpdateFn = (message, attribute) => {
40 41 const { data = {} } = message;
41 42 const [info] = data[attribute] || [];
  43 + if (!info.length) return;
42 44 const [_, value] = info;
43 45 currentValue.value = value;
44 46 };
... ...
... ... @@ -12,6 +12,7 @@ import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
12 12 export const option: PublicPresetOptions = {
13 13 multipleDataSourceComponent: true,
14 14 [ComponentConfigFieldEnum.FONT_COLOR]: '#000',
  15 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
15 16 [ComponentConfigFieldEnum.UNIT]: '℃',
16 17 [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
17 18 [ComponentConfigFieldEnum.BACKGROUND_COLOR]: '#377dff',
... ...
... ... @@ -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 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
7 8 schemas: [
8 9 {
... ... @@ -23,7 +24,27 @@
23 24 defaultValue: option.backgroundColor,
24 25 },
25 26 },
26   -
  27 + {
  28 + field: ComponentConfigFieldEnum.MAX_NUMBER,
  29 + label: '最大值',
  30 + component: 'InputNumber',
  31 + defaultValue: 100,
  32 + componentProps: ({ formActionType }) => {
  33 + const { setFieldsValue } = formActionType;
  34 + return {
  35 + placeholder: '请输入最大值',
  36 + min: 100,
  37 + onChange: async (e) => {
  38 + if (!e) {
  39 + await nextTick();
  40 + setFieldsValue({
  41 + [ComponentConfigFieldEnum.MAX_NUMBER]: 100,
  42 + });
  43 + }
  44 + },
  45 + };
  46 + },
  47 + },
27 48 {
28 49 field: ComponentConfigFieldEnum.UNIT,
29 50 label: '数值单位',
... ...
1 1 <script lang="ts" setup>
2 2 import { option } from './config';
3   - // import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
4   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
5   - import { useMultipleDataFetch } from '/@/views/visual/packages/hook/useSocket';
6   - import { computed } from 'vue';
7   - import { ref } from 'vue';
8   - import { Progress } from 'ant-design-vue';
  3 + import { ComponentPropsConfigType } from '../../../index.type';
  4 + import { Slider } from 'ant-design-vue';
9 5 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10   - import { unref } from 'vue';
11   - import { onMounted } from 'vue';
12   - import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
13 6 import { useReceiveMessage } from '../../../hook/useReceiveMessage';
14 7 import { buildUUID } from '/@/utils/uuid';
15 8 import { useReceiveValue } from '../../../hook/useReceiveValue';
  9 + import { ref, computed, unref, onMounted } from 'vue';
  10 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  11 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
16 12
17 13 const props = defineProps<{
18 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -32,6 +28,7 @@
32 28 deviceName?: string;
33 29 attributeName?: string;
34 30 deviceRename?: string;
  31 + maxNumber?: number;
35 32 }
36 33
37 34 const defaultValue: PercentType[] = [
... ... @@ -40,8 +37,8 @@
40 37 fontColor: '#377dff',
41 38 backgroundColor: '#377dff',
42 39 unit: '℃',
43   - deviceName: '温度',
44   - attribute: '1',
  40 + deviceName: '温湿度',
  41 + attribute: '温度',
45 42 id: buildUUID(),
46 43 },
47 44 {
... ... @@ -49,8 +46,8 @@
49 46 fontColor: '#1E8667',
50 47 backgroundColor: '#1E8667',
51 48 unit: '℃',
52   - deviceName: '温度',
53   - attribute: '3',
  49 + deviceName: '温湿度',
  50 + attribute: '湿度',
54 51 id: buildUUID(),
55 52 },
56 53 ];
... ... @@ -62,21 +59,21 @@
62 59 unit: presetUnit,
63 60 fontColor: presetFontColor,
64 61 backgroundColor: persetBackgroundColor,
  62 + maxNumber: persetMaxNumber,
65 63 } = persetOption || {};
66 64 return {
67 65 dataSource: dataSource.map((item) => {
68   - const { unit, fontColor, backgroundColor } = item.componentInfo || {};
  66 + const { unit, fontColor, backgroundColor, maxNumber } = item.componentInfo || {};
69 67 const { attribute, attributeRename, deviceName, deviceRename, deviceId, attributeName } =
70 68 item;
71 69 return {
72 70 unit: unit ?? presetUnit,
73 71 fontColor: fontColor ?? presetFontColor,
74 72 backgroundColor: backgroundColor ?? persetBackgroundColor,
75   - deviceName,
76   - deviceRename,
  73 + deviceName: deviceRename || deviceName,
77 74 attribute,
78   - attributeRename,
79   - attributeName,
  75 + attributeName: attributeRename || attributeName,
  76 + maxNumber: maxNumber || persetMaxNumber,
80 77 id: deviceId,
81 78 } as PercentType;
82 79 }),
... ... @@ -92,7 +89,7 @@
92 89 const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => {
93 90 forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
94 91 percentList.value.forEach((item) => {
95   - if (item.id === deviceId && item.attribute === attribute) {
  92 + if (item.id === deviceId && item.attribute === attribute && value) {
96 93 item.value = getNumberValue(value);
97 94 time.value = timespan;
98 95 }
... ... @@ -117,9 +114,9 @@
117 114 <div class="flex justify-between">
118 115 <div class="text-gray-500 text-sm truncate" :style="{ color: item.fontColor }">
119 116 {{
120   - `${item.deviceRename || item.deviceName}
  117 + `${item.deviceName}
121 118 -
122   - ${item.attributeRename || item.attributeName || item.attribute || '温度'}`
  119 + ${item.attributeName || item.attribute || '温度'}`
123 120 }}
124 121 </div>
125 122 <span class="text-lg" :style="{ color: item.fontColor }"
... ... @@ -127,24 +124,39 @@
127 124 >
128 125 </div>
129 126 <div>
130   - <Progress :strokeColor="item.backgroundColor" :percent="item.value" :showInfo="false" />
  127 + <!-- <Progress :strokeColor="item.backgroundColor" :percent="item.value" :showInfo="false" /> -->
  128 + <Slider
  129 + ref="sliderEl"
  130 + :style="{ '--slider-color': item.backgroundColor }"
  131 + :value="item.value"
  132 + :disabled="true"
  133 + :min="0"
  134 + :max="item.maxNumber"
  135 + />
131 136 </div>
132 137 </div>
133   - <UpdateTime :time="time" />
  138 + <!-- <UpdateTime :time="time" /> -->
134 139 </main>
135 140 </template>
136 141 <style lang="less" scoped>
137   - // :deep(.ant-progress-success-bg, .ant-progress-bg) {
138   - // background: '#2196F3' !important;
139   - // }
140   - // :deep(.ant-slider-handle) {
141   - // all: unset;
142   - // }
143   - // :deep(.ant-slider-track) {
144   - // background: var(--slider-color) !important;
145   - // height: 8px;
146   - // }
147   - // :deep(.ant-slider-rail) {
148   - // height: 8px;
149   - // }
  142 + :deep(.ant-progress-success-bg, .ant-progress-bg) {
  143 + background: #2196f3 !important;
  144 + }
  145 +
  146 + :deep(.ant-slider-handle) {
  147 + all: unset;
  148 + }
  149 +
  150 + :deep(.ant-slider-track) {
  151 + background: var(--slider-color) !important;
  152 + height: 8px;
  153 + }
  154 +
  155 + :deep(.ant-slider-rail) {
  156 + height: 8px;
  157 + }
  158 +
  159 + :deep(.ant-slider-disabled) {
  160 + cursor: auto !important;
  161 + }
150 162 </style>
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType, MultipleDataFetchUpdateFn } from '../../../index.type';
3   -
  2 + import { ComponentPropsConfigType } from '../../../index.type';
4 3 import { option } from './config';
5   - import { useMultipleDataFetch } from '/@/views/visual/packages/hook/useSocket';
6 4 import { ref, unref } from 'vue';
7 5 import { SvgIcon } from '/@/components/Icon';
8   - import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
9 6 import { computed } from 'vue';
10 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
11 8 import { useReceiveMessage } from '../../../hook/useReceiveMessage';
12 9 import { useReceiveValue } from '../../../hook/useReceiveValue';
  10 + import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  11 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
13 12
14 13 const props = defineProps<{
15 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -29,42 +28,36 @@
29 28 return {
30 29 dataSource: dataSource.map((item) => {
31 30 const { fontColor, icon, iconColor, unit } = item.componentInfo;
32   - const { attribute, attributeRename, deviceName, deviceRename, deviceId } = item;
  31 + const { attribute, attributeRename, deviceName, deviceRename, deviceId, attributeName } =
  32 + item;
33 33
34 34 return {
35 35 unit: unit ?? persetUnit,
36 36 fontColor: fontColor ?? persetFontColor,
37 37 icon: icon ?? persetIcon,
38 38 iconColor: iconColor ?? persetIconColor,
39   - attribute: attribute || attributeRename,
40   - attributeRename,
41   - deviceName,
42   - deviceRename,
  39 + attribute,
  40 + deviceName: deviceRename || deviceName,
  41 + attributeName: attributeRename || attributeName,
43 42 id: deviceId,
44 43 };
45   - // return {
46   - // unit: unit ?? persetUnit,
47   - // fontColor: fontColor ?? persetFontColor,
48   - // icon: icon ?? persetIcon,
49   - // iconColor: iconColor ?? persetIconColor,
50   - // attribute: attribute || attributeRename,
51   - // attributeRename,
52   - // };
53 44 }),
54 45 };
55 46 });
56 47 const defaultSvgList = ref<any>([
57 48 {
58 49 value: 26.2,
59   - name: 'wendu',
  50 + deviceName: '温湿度',
  51 + attributeName: '温度',
60 52 icon: 'zongfushe',
61   - unit: 'kw',
  53 + unit: '',
62 54 iconColor: '#367BFF',
63 55 fontColor: '#357CFB',
64 56 },
65 57 {
66 58 value: 53.7,
67   - name: 'shidu',
  59 + deviceName: '温湿度',
  60 + attributeName: '湿度',
68 61 icon: 'guangzhaoqiangdu',
69 62 unit: '℃',
70 63 iconColor: '#FFA000',
... ... @@ -80,33 +73,14 @@
80 73 );
81 74
82 75 const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => {
83   - // console.log(unref(getDesign).dataSource, 'dataSource');
84 76 forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
85 77 updateSvgList.value.forEach((item) => {
86   - if (item.id === deviceId && item.attribute === attribute) {
  78 + if (item.id === deviceId && item.attribute === attribute && value) {
87 79 item.value = getNumberValue(value);
88 80 time.value = timespan;
89 81 }
90 82 });
91   - // });
92 83 });
93   - // console.log(unref(svgList), 'svglist');
94   - // const { data = {} } = message;
95   - // const { dataSource } = unref(getDesign);
96   - // svgList.value.length = 0;
97   - // svgList.value = dataSource.map((item) => {
98   - // const { icon, iconColor, unit, fontColor, attribute } = item || {};
99   - // const [latest] = data[attribute] || [];
100   - // const [_timespan, value] = latest || [];
101   - // return {
102   - // value: Number(value),
103   - // name: attribute,
104   - // icon,
105   - // unit,
106   - // iconColor,
107   - // fontColor,
108   - // };
109   - // });
110 84 };
111 85
112 86 useMultipleDataFetch(props, updateFn);
... ... @@ -119,20 +93,18 @@
119 93 style="width: 86%"
120 94 v-for="item in updateSvgList"
121 95 :key="item.id"
122   - class="flex justify-between items-center"
  96 + class="flex justify-between items-center mt-2"
123 97 >
124 98 <div class="flex items-center">
125 99 <SvgIcon
126 100 :name="item.icon!"
127 101 prefix="iconfont"
128   - :size="45"
  102 + :size="40"
129 103 :style="{ color: item.iconColor }"
130 104 />
131 105
132 106 <div class="text-gray-500 text-lg truncate ml-6">{{
133   - item.deviceRename ||
134   - item.deviceName + '-' + (item.attributeRename || item.attribute) ||
135   - '温度'
  107 + item.deviceName + '-' + item.attributeName || '温度'
136 108 }}</div>
137 109 </div>
138 110
... ... @@ -141,6 +113,6 @@
141 113 <span>{{ item.unit }} </span>
142 114 </h1>
143 115 </div>
144   - <UpdateTime :time="time" />
  116 + <!-- <UpdateTime :time="time" /> -->
145 117 </main>
146 118 </template>
... ...
... ... @@ -3,6 +3,7 @@ import { TextComponent2Config } from './TextComponent2';
3 3 import { TextComponent3Config } from './TextComponent3';
4 4 import { TextComponent4Config } from './TextComponent4';
5 5 import { ValueList1Config } from './ValueList1';
  6 +import { ValueList2Config } from './ValueList2';
6 7
7 8 export const TextList = [
8 9 TextComponent1Config,
... ... @@ -10,4 +11,5 @@ export const TextList = [
10 11 TextComponent3Config,
11 12 TextComponent4Config,
12 13 ValueList1Config,
  14 + ValueList2Config,
13 15 ];
... ...
... ... @@ -14,6 +14,7 @@ import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
14 14 import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
15 15 import { CommandTypeEnum } from '/@/views/rule/linkedge/config/config.data';
16 16 import { DataActionModeEnum } from '/@/enums/toolEnum';
  17 +import { TaskTypeEnum } from '/@/views/task/center/config';
17 18
18 19 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
19 20
... ... @@ -24,6 +25,7 @@ export interface CommonDataSourceBindValueType extends Record<DataSourceField, s
24 25 command?: string;
25 26 commandType?: string;
26 27 callType?: string;
  28 + [ksy: string]: string | undefined;
27 29 };
28 30 }
29 31
... ... @@ -33,6 +35,7 @@ export enum DataSourceField {
33 35 TRANSPORT_TYPE = 'transportType',
34 36 ORIGINATION_ID = 'organizationId',
35 37 DEVICE_ID = 'deviceId',
  38 + DEVICE_CODE = 'deviceCode', //设备地址码
36 39 DEVICE_PROFILE_ID = 'deviceProfileId',
37 40 ATTRIBUTE = 'attribute',
38 41 ATTRIBUTE_NAME = 'attributeName',
... ... @@ -41,10 +44,15 @@ export enum DataSourceField {
41 44 DEVICE_RENAME = 'deviceRename',
42 45 LONGITUDE_ATTRIBUTE = 'longitudeAttribute',
43 46 LATITUDE_ATTRIBUTE = 'latitudeAttribute',
44   -
  47 + CODE_TYPE = 'codeType',
45 48 COMMAND = 'command',
  49 + OPEN_COMMAND = 'openCommand',
  50 + CLOSE_COMMAND = 'closeCommand',
46 51 COMMAND_TYPE = 'commandType',
47 52 SERVICE = 'service',
  53 + OPEN_SERVICE = 'openService',
  54 + CLOSE_SERVICE = 'closeService',
  55 + EXTENSION_DESC = 'extensionDesc',
48 56 CALL_TYPE = 'callType',
49 57 }
50 58
... ... @@ -54,7 +62,10 @@ const isControlComponent = (category?: string) => PackagesCategoryEnum.CONTROL =
54 62
55 63 const isBooleanComponent = (componentKeys: { categoryKey?: string; componentKey?: string }) => {
56 64 const { categoryKey, componentKey } = componentKeys;
57   - return categoryKey == 'OTHER' && componentKey == 'SwitchSignalLight';
  65 + return (
  66 + categoryKey == 'OTHER' &&
  67 + (componentKey == 'SwitchSignalLight' || componentKey == 'SwitchStatus')
  68 + );
58 69 };
59 70
60 71 const getDeviceService = async (deviceProfileId: string) => {
... ... @@ -69,7 +80,7 @@ const getDeviceService = async (deviceProfileId: string) => {
69 80 const getDeviceAttribute = async (params: DeviceAttributeParams) => {
70 81 try {
71 82 const data = await getDeviceAttributes(params);
72   - if (data) return data.map((item) => ({ label: item.name, value: item.identifier }));
  83 + if (data) return data.map((item) => ({ ...item, label: item.name, value: item.identifier }));
73 84 } catch (error) {}
74 85 return [];
75 86 };
... ... @@ -79,7 +90,6 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
79 90 const isUpdate = unref(mode) === DataActionModeEnum.UPDATE;
80 91 const selectWidgetKeys = useSelectWidgetKeys();
81 92 const category = unref(selectWidgetKeys).categoryKey;
82   -
83 93 return [
84 94 {
85 95 field: DataSourceField.IS_GATEWAY_DEVICE,
... ... @@ -158,6 +168,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
158 168 [DataSourceField.ATTRIBUTE]: null,
159 169 [DataSourceField.ATTRIBUTE_NAME]: null,
160 170 [DataSourceField.TRANSPORT_TYPE]: option[DataSourceField.TRANSPORT_TYPE],
  171 + [DataSourceField.COMMAND_TYPE]: null,
161 172 });
162 173 },
163 174 getPopupContainer: () => document.body,
... ... @@ -225,6 +236,9 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
225 236 onChange(_value, record: MasterDeviceList) {
226 237 setFieldsValue({
227 238 [DataSourceField.DEVICE_NAME]: record?.label,
  239 + [DataSourceField.CODE_TYPE]: record?.codeType,
  240 + [DataSourceField.DEVICE_CODE]: record?.code,
  241 + [DataSourceField.COMMAND_TYPE]: null,
228 242 });
229 243 },
230 244 placeholder: '请选择设备',
... ... @@ -233,19 +247,94 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
233 247 },
234 248 },
235 249 {
  250 + field: DataSourceField.CODE_TYPE,
  251 + component: 'Input',
  252 + label: '设备标识符',
  253 + show: false,
  254 + },
  255 +
  256 + {
  257 + field: DataSourceField.DEVICE_CODE,
  258 + component: 'Input',
  259 + show: false,
  260 + label: '设备地址码',
  261 + },
  262 +
  263 + {
236 264 field: DataSourceField.ATTRIBUTE_NAME,
237 265 component: 'Input',
238 266 label: '属性名',
239 267 show: false,
240 268 },
241 269 {
  270 + field: DataSourceField.COMMAND_TYPE,
  271 + component: 'ApiSelect',
  272 + label: '命令类型',
  273 + defaultValue: CommandTypeEnum.CUSTOM.toString(),
  274 + rules: [{ required: true, message: '请选择命令类型' }],
  275 + colProps: { span: 8 },
  276 + ifShow: ({ model }) => {
  277 + return isControlComponent(category!) && isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]);
  278 + },
  279 + componentProps: ({ formActionType, formModel }) => {
  280 + const { setFieldsValue } = formActionType;
  281 + const { codeType } = formModel || {};
  282 + return {
  283 + // api: findDictItemByCode,
  284 + api: async (params: Recordable) => {
  285 + try {
  286 + const record = await findDictItemByCode(params);
  287 + if (unref(selectWidgetKeys).componentKey == 'LateralNumericalControl') {
  288 + return record.filter(
  289 + (item) => item.itemValue == CommandTypeEnum.ATTRIBUTE.toString()
  290 + );
  291 + }
  292 + if (codeType == TaskTypeEnum.MODBUS_RTU) {
  293 + // setFieldsValue({ [DataSourceField.COMMAND_TYPE]: undefined });
  294 + return record.filter(
  295 + (item) => item.itemValue !== CommandTypeEnum.CUSTOM.toString()
  296 + );
  297 + } else {
  298 + return record.filter(
  299 + (item) => item.itemValue !== CommandTypeEnum.ATTRIBUTE.toString()
  300 + );
  301 + }
  302 + } catch (error) {
  303 + console.log(error);
  304 + return [];
  305 + }
  306 + },
  307 + params: {
  308 + dictCode: 'custom_define',
  309 + },
  310 + labelField: 'itemText',
  311 + valueField: 'itemValue',
  312 + placeholder: '请选择命令类型',
  313 + getPopupContainer: () => document.body,
  314 + onChange() {
  315 + setFieldsValue({
  316 + [DataSourceField.COMMAND]: null,
  317 + [DataSourceField.SERVICE]: null,
  318 + [DataSourceField.OPEN_COMMAND]: null,
  319 + [DataSourceField.CLOSE_COMMAND]: null,
  320 + [DataSourceField.OPEN_SERVICE]: null,
  321 + [DataSourceField.CLOSE_SERVICE]: null,
  322 + [DataSourceField.CALL_TYPE]: null,
  323 + [DataSourceField.ATTRIBUTE]: null,
  324 + });
  325 + },
  326 + };
  327 + },
  328 + },
  329 + {
242 330 field: DataSourceField.ATTRIBUTE,
243 331 component: 'ApiSelect',
244 332 label: '属性',
245 333 colProps: { span: 8 },
246 334 rules: [{ required: true, message: '请选择属性' }],
247 335 ifShow: ({ model }) =>
248   - !(isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && isControlComponent(category!)),
  336 + !(isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && isControlComponent(category!)) ||
  337 + model[DataSourceField.COMMAND_TYPE] == 2,
249 338 componentProps({ formModel, formActionType }) {
250 339 const { setFieldsValue } = formActionType;
251 340 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
... ... @@ -253,7 +342,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
253 342 api: async () => {
254 343 try {
255 344 if (deviceProfileId) {
256   - return await getDeviceAttribute({
  345 + const option = await getDeviceAttribute({
257 346 deviceProfileId,
258 347 dataType:
259 348 (isControlComponent(category!) &&
... ... @@ -262,59 +351,97 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
262 351 ? DataTypeEnum.IS_BOOL
263 352 : undefined,
264 353 });
  354 +
  355 + // 选择控制组件4的时候只能选择属性且是(int double类型)
  356 + if (unref(selectWidgetKeys).componentKey == 'LateralNumericalControl') {
  357 + setFieldsValue({
  358 + [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(),
  359 + });
  360 + return option.filter(
  361 + (item) =>
  362 + item.detail.dataType.type == 'INT' || item.detail.dataType.type == 'DOUBLE'
  363 + );
  364 + }
  365 + return option;
265 366 }
266 367 } catch (error) {}
267 368 return [];
268 369 },
269 370 placeholder: '请选择属性',
270 371 getPopupContainer: () => document.body,
271   - onChange(value: string, option: Record<'label' | 'value', string>) {
272   - setFieldsValue({ [DataSourceField.ATTRIBUTE_NAME]: value ? option.label : null });
  372 + onChange(value: string, option: Record<'label' | 'value' | any, string>) {
  373 + setFieldsValue({
  374 + [DataSourceField.ATTRIBUTE_NAME]: value ? option.label : null,
  375 + [DataSourceField.EXTENSION_DESC]: value ? JSON.stringify(option.extensionDesc) : '',
  376 + });
273 377 },
274 378 };
275 379 },
276 380 },
277 381 {
278   - field: DataSourceField.COMMAND_TYPE,
  382 + field: DataSourceField.EXTENSION_DESC,
  383 + component: 'Input',
  384 + show: false,
  385 + label: '扩展描述符',
  386 + },
  387 +
  388 + {
  389 + field: DataSourceField.CALL_TYPE,
  390 + component: 'Input',
  391 + ifShow: false,
  392 + label: 'callType',
  393 + },
  394 + {
  395 + field: DataSourceField.OPEN_SERVICE,
279 396 component: 'ApiSelect',
280   - label: '命令类型',
281   - defaultValue: CommandTypeEnum.CUSTOM.toString(),
282   - rules: [{ required: true, message: '请选择命令类型' }],
  397 + label: '开服务',
283 398 colProps: { span: 8 },
  399 + rules: [{ required: true, message: '请选择开服务' }],
284 400 ifShow: ({ model }) =>
285   - isControlComponent(category!) && isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
286   - componentProps: ({ formActionType }) => {
  401 + isControlComponent(category!) &&
  402 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE.toString() &&
  403 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
  404 + componentProps({ formModel, formActionType }) {
287 405 const { setFieldsValue } = formActionType;
  406 + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
  407 + const transportType = formModel[DataSourceField.TRANSPORT_TYPE];
  408 + if (isUpdate && ![deviceProfileId, transportType].every(Boolean))
  409 + return { placeholder: '请选择开服务', getPopupContainer: () => document.body };
288 410 return {
289   - api: findDictItemByCode,
290   - params: {
291   - dictCode: 'custom_define',
  411 + api: async () => {
  412 + try {
  413 + if (deviceProfileId) {
  414 + const services = await getDeviceService(deviceProfileId);
  415 + const value = formModel[DataSourceField.SERVICE];
  416 + if (value) {
  417 + const selected = services.find((item) => item.value === value);
  418 + selected && setFieldsValue({ [DataSourceField.CALL_TYPE]: selected.callType });
  419 + }
  420 + return services;
  421 + }
  422 + } catch (error) {}
  423 + return [];
292 424 },
293   - labelField: 'itemText',
294   - valueField: 'itemValue',
295   - placeholder: '请选择命令类型',
296   - onChange() {
  425 + placeholder: '请选择开服务',
  426 + getPopupContainer: () => document.body,
  427 + onChange(value: string, options: ModelOfMatterParams) {
  428 + const command = value
  429 + ? (options.functionJson.inputData || [])[0]?.serviceCommand
  430 + : null;
297 431 setFieldsValue({
298   - [DataSourceField.COMMAND]: null,
299   - [DataSourceField.SERVICE]: null,
300   - [DataSourceField.CALL_TYPE]: null,
  432 + [DataSourceField.OPEN_COMMAND]: command,
  433 + [DataSourceField.CALL_TYPE]: value ? options.callType : null,
301 434 });
302 435 },
303 436 };
304 437 },
305 438 },
306 439 {
307   - field: DataSourceField.CALL_TYPE,
308   - component: 'Input',
309   - ifShow: false,
310   - label: 'callType',
311   - },
312   - {
313   - field: DataSourceField.SERVICE,
  440 + field: DataSourceField.CLOSE_SERVICE,
314 441 component: 'ApiSelect',
315   - label: '服务',
  442 + label: '服务',
316 443 colProps: { span: 8 },
317   - rules: [{ required: true, message: '请选择服务' }],
  444 + rules: [{ required: true, message: '请选择服务' }],
318 445 ifShow: ({ model }) =>
319 446 isControlComponent(category!) &&
320 447 model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE.toString() &&
... ... @@ -324,7 +451,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
324 451 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
325 452 const transportType = formModel[DataSourceField.TRANSPORT_TYPE];
326 453 if (isUpdate && ![deviceProfileId, transportType].every(Boolean))
327   - return { placeholder: '请选择服务', getPopupContainer: () => document.body };
  454 + return { placeholder: '请选择服务', getPopupContainer: () => document.body };
328 455 return {
329 456 api: async () => {
330 457 try {
... ... @@ -340,14 +467,14 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
340 467 } catch (error) {}
341 468 return [];
342 469 },
343   - placeholder: '请选择服务',
  470 + placeholder: '请选择服务',
344 471 getPopupContainer: () => document.body,
345 472 onChange(value: string, options: ModelOfMatterParams) {
346 473 const command = value
347 474 ? (options.functionJson.inputData || [])[0]?.serviceCommand
348 475 : null;
349 476 setFieldsValue({
350   - [DataSourceField.COMMAND]: command,
  477 + [DataSourceField.CLOSE_COMMAND]: command,
351 478 [DataSourceField.CALL_TYPE]: value ? options.callType : null,
352 479 });
353 480 },
... ... @@ -355,11 +482,27 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
355 482 },
356 483 },
357 484 {
358   - field: DataSourceField.COMMAND,
  485 + field: DataSourceField.OPEN_COMMAND,
  486 + component: 'Input',
  487 + label: '命令',
  488 + colProps: { span: 8 },
  489 + rules: [{ required: true, message: '请输入开下发命令' }],
  490 + // 是控制组件 && 自定义命令 && 传输协议为TCP
  491 + ifShow: ({ model }) =>
  492 + isControlComponent(category!) &&
  493 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM.toString() &&
  494 + model[DataSourceField.TRANSPORT_TYPE] &&
  495 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
  496 + componentProps: {
  497 + placeholder: '请输入开下发命令',
  498 + },
  499 + },
  500 + {
  501 + field: DataSourceField.CLOSE_COMMAND,
359 502 component: 'Input',
360 503 label: '命令',
361 504 colProps: { span: 8 },
362   - rules: [{ required: true, message: '请输入下发命令' }],
  505 + rules: [{ required: true, message: '请输入下发命令' }],
363 506 // 是控制组件 && 自定义命令 && 传输协议为TCP
364 507 ifShow: ({ model }) =>
365 508 isControlComponent(category!) &&
... ... @@ -367,7 +510,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
367 510 model[DataSourceField.TRANSPORT_TYPE] &&
368 511 isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
369 512 componentProps: {
370   - placeholder: '请输入下发命令',
  513 + placeholder: '请输入下发命令',
371 514 },
372 515 },
373 516 {
... ...
... ... @@ -5,6 +5,7 @@ export enum ComponentConfigFieldEnum {
5 5 INSTRUMENT_PANEL_COLOR = 'instrumentPanelColor',
6 6 CONTROL_BAR_COLOR = 'controlBarColor',
7 7 // INSTRUMENT_PANEL_WIDTH = 'instrumentPanelWidth',
  8 + LINE_COLOR = 'lineColor',
8 9
9 10 PROGRESS_BAR_CIRCLE = 'progressBarCircle',
10 11
... ... @@ -19,6 +20,7 @@ export enum ComponentConfigFieldEnum {
19 20 SECOND_PHASE_VALUE = 'secondPhaseValue',
20 21 THIRD_PHASE_VALUE = 'thirdPhaseValue',
21 22 SHOW_DEVICE_NAME = 'showDeviceName',
  23 + SHOW_TIME = 'showTime',
22 24 GRADIENT_INFO = 'gradientInfo',
23 25
24 26 FLOWMETER_CONFIG = 'flowmeterConfig',
... ... @@ -28,4 +30,6 @@ export enum ComponentConfigFieldEnum {
28 30 BACKGROUND_COLOR = 'backgroundColor',
29 31 OPEN_COLOR = 'openColor',
30 32 CLOSE_COLOR = 'closeColor',
  33 + MIN_NUMBER = 'minNumber',
  34 + MAX_NUMBER = 'maxNumber',
31 35 }
... ...
src/views/visual/packages/hook/socket/useSocket.ts renamed from src/views/visual/packages/hook/useSocket.ts
1 1 import { Ref, computed, onUnmounted, unref, watch } from 'vue';
2   -import { WidgetDataType } from '../../palette/hooks/useDataSource';
  2 +import { WidgetDataType } from '../../../palette/hooks/useDataSource';
3 3 import { useWebSocket } from '@vueuse/core';
4 4 import { useGlobSetting } from '/@/hooks/setting';
5 5 import { isShareMode } from '/@/views/sys/share/hook';
6 6 import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
7   -import { isNullAndUnDef } from '/@/utils/is';
  7 +import { DataSource } from '../../../palette/types';
  8 +import { CUSTOM_SUBSCRIBE_MESSAGE_COMPONENT_KEY_LIST } from '../../package';
8 9 import {
9   - ComponentPropsConfigType,
  10 + CmdUpdateMsg,
  11 + CmdUpdateTypeEnum,
10 12 DataFetchUpdateFn,
11 13 EntityTypeEnum,
12 14 MultipleDataFetchUpdateFn,
13   - ReceiveGroupMessageType,
  15 + ReceiveAlarmDataCmdsMessageType,
  16 + ReceiveEntityCountMessageType,
  17 + ReceiveEntityDataMessageType,
14 18 ReceiveMessageType,
  19 + ReceiveTsSubCmdsGroupMessageType,
  20 + ReceiveTsSubCmdsMessageType,
15 21 ScopeTypeEnum,
16   - SubscribeMessageItemType,
17 22 SubscribeMessageType,
18   -} from '../index.type';
19   -import { DataSource } from '../../palette/types';
  23 + TsSubCmdsItemType,
  24 +} from './useSocket.type';
  25 +import { ComponentPropsConfigType } from '../../index.type';
  26 +import { isNullOrUnDef } from '/@/utils/is';
20 27
21 28 interface DeviceGroupMapType {
22 29 subscriptionId: number;
... ... @@ -29,11 +36,18 @@ interface ComponentUpdateFnMapValueType {
29 36 attributes: string[];
30 37 }
31 38
32   -const parseMessage = (text: string): ReceiveMessageType => {
  39 +interface CustomSubscribeMapValueType {
  40 + uuid: string;
  41 + updateFn?: Fn;
  42 +}
  43 +
  44 +export type CustomSubscribeSendMessageFnType = (cmdId: number) => void;
  45 +
  46 +const parseMessage = (text: string): ReceiveTsSubCmdsMessageType => {
33 47 try {
34 48 return JSON.parse(text);
35 49 } catch (error) {
36   - return {} as ReceiveMessageType;
  50 + return {} as ReceiveTsSubCmdsMessageType;
37 51 }
38 52 };
39 53
... ... @@ -48,6 +62,8 @@ class Subscriber {
48 62
49 63 componentGroupUpdateFnMap = new Map<string, ComponentUpdateFnMapValueType[]>();
50 64
  65 + customSubscribeMap = new Map<number, CustomSubscribeMapValueType>();
  66 +
51 67 getNextSubscribeId() {
52 68 return this.subscribeId++;
53 69 }
... ... @@ -55,9 +71,14 @@ class Subscriber {
55 71 clearSubscriber = () => {
56 72 this.deviceGroupMap.clear();
57 73 this.subscriptionMap.clear();
58   - this.componentUpdateFnMap.clear();
  74 + this.customSubscribeMap.clear();
59 75 };
60 76
  77 + clearUpdateFn() {
  78 + this.componentUpdateFnMap.clear();
  79 + this.componentGroupUpdateFnMap.clear();
  80 + }
  81 +
61 82 addSubscriber = (info: Record<'deviceId' | 'slaveDeviceId' | 'attribute' | 'uuid', string>) => {
62 83 const { deviceId, attribute, uuid } = info;
63 84 if (!this.deviceGroupMap.has(deviceId)) {
... ... @@ -81,11 +102,11 @@ class Subscriber {
81 102 return {
82 103 cmdId: subscriptionId,
83 104 entityId: deviceId,
84   - keys: Array.from(attributes.values()).join(','),
  105 + keys: Array.from(attributes.values()).filter(Boolean).join(','),
85 106 entityType: EntityTypeEnum.DEVICE,
86 107 scope: ScopeTypeEnum.LATEST_TELEMERY,
87 108 ...(unsubscribe ? { unsubscribe } : {}),
88   - } as SubscribeMessageItemType;
  109 + } as TsSubCmdsItemType;
89 110 });
90 111 return { tsSubCmds: message } as SubscribeMessageType;
91 112 }
... ... @@ -98,14 +119,14 @@ class Subscriber {
98 119 return this.genBasicMessage();
99 120 }
100 121
101   - getScopeMessage(message: ReceiveMessageType, attribute: string[]) {
  122 + getScopeMessage(message: ReceiveTsSubCmdsMessageType, attribute: string[]) {
102 123 const data = attribute.reduce((prev, next) => {
103 124 return { ...prev, [next]: (message.data || {})[next] || [[]] };
104   - }, {} as ReceiveMessageType['data']);
  125 + }, {} as ReceiveTsSubCmdsMessageType['data']);
105 126
106 127 const latestValues = attribute.reduce((prev, next) => {
107 128 return { ...prev, [next]: (message.latestValues || {})[next] || [[]] };
108   - }, {} as ReceiveMessageType['data']);
  129 + }, {} as ReceiveTsSubCmdsMessageType['data']);
109 130
110 131 return {
111 132 subscriptionId: message.subscriptionId,
... ... @@ -113,10 +134,14 @@ class Subscriber {
113 134 errorMsg: message.errorMsg,
114 135 data,
115 136 latestValues,
116   - } as ReceiveMessageType;
  137 + } as ReceiveTsSubCmdsMessageType;
117 138 }
118 139
119   - getGroupScopeMessage(message: ReceiveMessageType, attribute: string[], deviceId: string) {
  140 + getGroupScopeMessage(
  141 + message: ReceiveTsSubCmdsMessageType,
  142 + attribute: string[],
  143 + deviceId: string
  144 + ) {
120 145 const result = this.getScopeMessage(message, attribute);
121 146
122 147 return {
... ... @@ -127,7 +152,13 @@ class Subscriber {
127 152 latestValues: {
128 153 [deviceId]: result.latestValues,
129 154 },
130   - } as ReceiveGroupMessageType;
  155 + } as ReceiveTsSubCmdsGroupMessageType;
  156 + }
  157 +
  158 + customSubscribe(uuid: string, sendMessageFn: CustomSubscribeSendMessageFnType, updateFn: Fn) {
  159 + const cmdId = this.getNextSubscribeId();
  160 + this.customSubscribeMap.set(cmdId, { uuid, updateFn });
  161 + sendMessageFn(cmdId);
131 162 }
132 163
133 164 trackUpdate(uuid: string, fn: Fn) {
... ... @@ -144,8 +175,31 @@ class Subscriber {
144 175 }
145 176
146 177 triggerUpdate(message: ReceiveMessageType) {
  178 + if (message.errorCode) {
  179 + return;
  180 + }
  181 +
  182 + if (isEntityDataUpdateMsg(message)) {
  183 + this.triggerEntityDataMessage(message);
  184 + return;
  185 + }
  186 +
  187 + if (isAlarmDataUpdateMsg(message)) {
  188 + this.triggerAlarmDataMesssage(message);
  189 + return;
  190 + }
  191 +
  192 + if (isEntityCountUpdateMsg(message)) {
  193 + return;
  194 + }
  195 +
  196 + if (!isNullOrUnDef(message.subscriptionId)) {
  197 + this.triggerTsSubCmdsMessage(message);
  198 + }
  199 + }
  200 +
  201 + triggerTsSubCmdsMessage(message: ReceiveTsSubCmdsMessageType) {
147 202 const { subscriptionId } = message;
148   - if (isNullAndUnDef(subscriptionId)) return;
149 203 const deviceId = this.subscriptionMap.get(subscriptionId);
150 204 if (!deviceId) return;
151 205 const deviceGroup = this.deviceGroupMap.get(deviceId);
... ... @@ -155,29 +209,59 @@ class Subscriber {
155 209 const updateGroups = this.componentGroupUpdateFnMap.get(deviceId);
156 210
157 211 if (updateGroups) {
158   - (updateGroups || []).forEach((item) => {
  212 + for (const item of updateGroups || []) {
159 213 const { attributes, fn } = item;
160 214 try {
161   - if (!fn) return;
  215 + if (!fn) continue;
162 216 fn?.(this.getGroupScopeMessage(message, attributes, deviceId), deviceId, attributes);
163 217 } catch (error) {
164 218 console.error(`deviceId: ${deviceId}`);
165 219 throw error;
166 220 }
167   - });
  221 + }
  222 + // return;
168 223 }
169 224
170   - subscriptionGroup.forEach((item) => {
  225 + for (const item of subscriptionGroup) {
171 226 const { attribute, uuid } = item;
172 227 const updateFn = this.componentUpdateFnMap.get(uuid);
173 228 try {
174   - if (!updateFn) return;
  229 + if (!updateFn) continue;
175 230 updateFn?.(this.getScopeMessage(message, [attribute]), attribute);
176 231 } catch (error) {
177 232 console.error(`uuid: ${uuid}`);
178 233 throw error;
179 234 }
180   - });
  235 + }
  236 + }
  237 +
  238 + triggerAlarmDataMesssage(message: ReceiveAlarmDataCmdsMessageType) {
  239 + const { cmdId } = message;
  240 + const isCustomSubscribeMessage = this.customSubscribeMap.get(cmdId);
  241 + if (isCustomSubscribeMessage) {
  242 + const updateFn = isCustomSubscribeMessage.updateFn;
  243 + try {
  244 + updateFn?.(message);
  245 + } catch (error) {
  246 + console.error(`Custom subscribe message invoke update function failed`);
  247 + throw error;
  248 + }
  249 + return;
  250 + }
  251 + }
  252 + triggerEntityDataMessage(message: ReceiveEntityDataMessageType) {
  253 + const { cmdId } = message;
  254 + const isCustomSubscribeMessage = this.customSubscribeMap.get(cmdId);
  255 + if (isCustomSubscribeMessage) {
  256 + const updateFn = isCustomSubscribeMessage.updateFn;
  257 + try {
  258 + updateFn?.(message);
  259 + } catch (error) {
  260 + console.error(`Custom subscribe message invoke update function failed`);
  261 + throw error;
  262 + }
  263 + return;
  264 + }
181 265 }
182 266 }
183 267
... ... @@ -206,12 +290,14 @@ export const useSocket = (dataSourceRef: Ref<WidgetDataType[]>) => {
206 290
207 291 const initSubscribe = () => {
208 292 subscriber.clearSubscriber();
209   - unref(dataSourceRef).forEach((item) => {
210   - item.dataSource.forEach((temp) => {
  293 +
  294 + for (const item of unref(dataSourceRef)) {
  295 + if (CUSTOM_SUBSCRIBE_MESSAGE_COMPONENT_KEY_LIST.includes(item.frontId)) continue;
  296 + for (const temp of item.dataSource) {
211 297 const { deviceId, slaveDeviceId, attribute, uuid } = temp;
212 298 subscriber.addSubscriber({ deviceId, slaveDeviceId, attribute, uuid });
213   - });
214   - });
  299 + }
  300 + }
215 301 };
216 302
217 303 watch(
... ... @@ -233,9 +319,37 @@ export const useSocket = (dataSourceRef: Ref<WidgetDataType[]>) => {
233 319
234 320 onUnmounted(() => {
235 321 close();
  322 + subscriber.clearSubscriber();
  323 + subscriber.clearUpdateFn();
236 324 });
  325 +
  326 + return {
  327 + send,
  328 + close,
  329 + };
237 330 };
238 331
  332 +export function isEntityDataUpdateMsg(
  333 + message: ReceiveMessageType
  334 +): message is ReceiveEntityDataMessageType {
  335 + const updateMsg = message as CmdUpdateMsg;
  336 + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateTypeEnum.ENTITY_DATA;
  337 +}
  338 +
  339 +export function isAlarmDataUpdateMsg(
  340 + message: ReceiveMessageType
  341 +): message is ReceiveAlarmDataCmdsMessageType {
  342 + const updateMsg = message as CmdUpdateMsg;
  343 + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateTypeEnum.ALARM_DATA;
  344 +}
  345 +
  346 +export function isEntityCountUpdateMsg(
  347 + message: ReceiveMessageType
  348 +): message is ReceiveEntityCountMessageType {
  349 + const updateMsg = message as CmdUpdateMsg;
  350 + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateTypeEnum.COUNT_DATA;
  351 +}
  352 +
239 353 export const useDataFetch = (
240 354 props: { config: ComponentPropsConfigType },
241 355 updateFn: DataFetchUpdateFn
... ... @@ -288,16 +402,6 @@ export const useMultipleDataFetch = (
288 402 });
289 403
290 404 if (!Object.keys(unref(getDataSourceGroup)).length) return;
291   - // const getBindAttributes = computed(() => {
292   - // const attributes = props.config.option.dataSource?.map((item) => item.attribute);
293   - // return [...new Set(attributes)];
294   - // });
295   -
296   - // if (!unref(getBindAttributes).length) return;
297   -
298   - // const getDeviceId = computed(() => {
299   - // return props.config.option.dataSource?.at(0)?.deviceId;
300   - // });
301 405
302 406 watch(
303 407 () => getDataSourceGroup,
... ... @@ -318,3 +422,27 @@ export const useMultipleDataFetch = (
318 422
319 423 return {};
320 424 };
  425 +
  426 +export const useCustomDataFetch = (
  427 + props: { config: ComponentPropsConfigType },
  428 + transformMessage: Fn,
  429 + updateFn: Fn
  430 +) => {
  431 + const getUUID = computed(() => {
  432 + return props.config.option.uuid;
  433 + });
  434 +
  435 + watch(
  436 + () => getUUID,
  437 + () => {
  438 + subscriber.customSubscribe(props.config.option.uuid, transformMessage, updateFn);
  439 + },
  440 + {
  441 + immediate: true,
  442 + }
  443 + );
  444 +
  445 + return {
  446 + getNextSubscribeId: subscriber.getNextSubscribeId,
  447 + };
  448 +};
... ...
  1 +export interface SubscribeMessageType {
  2 + tsSubCmds?: TsSubCmdsItemType[];
  3 + alarmDataCmds?: AlarmDataCmdsItemType[];
  4 + alarmDataUnsubscribeCmds?: UnsubscribeCmdsItemType[];
  5 + attrSubCmds?: [];
  6 + entityCountCmds?: [];
  7 + entityCountUnsubscribeCmds?: UnsubscribeCmdsItemType[];
  8 + entityDataCmds?: [];
  9 + entityDataUnsubscribeCmds?: UnsubscribeCmdsItemType[];
  10 + historyCmds?: [];
  11 +}
  12 +
  13 +export interface UnsubscribeCmdsItemType {
  14 + cmdId: number;
  15 +}
  16 +
  17 +export enum CmdUpdateTypeEnum {
  18 + ENTITY_DATA = 'ENTITY_DATA',
  19 + ALARM_DATA = 'ALARM_DATA',
  20 + COUNT_DATA = 'COUNT_DATA',
  21 +}
  22 +
  23 +export interface AlarmDataCmdsItemType {
  24 + cmdId: number;
  25 +}
  26 +
  27 +export interface TsSubCmdsItemType {
  28 + cmdId: number;
  29 + entityId: string;
  30 + entityType: string;
  31 + keys?: string;
  32 + scope?: string;
  33 + unsubscribe?: boolean;
  34 +}
  35 +
  36 +export interface ReceiveTsSubCmdsMessageType {
  37 + subscriptionId: number;
  38 + errorCode: number;
  39 + errorMsg: Nullable<string>;
  40 + data: Record<string, [number, string][]>;
  41 + latestValues: Record<string, number>;
  42 +}
  43 +
  44 +export interface ReceiveTsSubCmdsGroupMessageType {
  45 + subscriptionId: number;
  46 + errorCode: number;
  47 + errorMsg: Nullable<string>;
  48 + data: {
  49 + string: Record<string, [number, string][]>;
  50 + };
  51 + latestValues: {
  52 + string: Record<string, number>;
  53 + };
  54 +}
  55 +
  56 +export interface PageData<T> {
  57 + data: T[];
  58 + totalPages: number;
  59 + totalElements: number;
  60 + hasNext: boolean;
  61 +}
  62 +
  63 +export interface CmdUpdateMsg {
  64 + cmdId: number;
  65 + errorCode: number;
  66 + errorMsg: string;
  67 + cmdUpdateType: CmdUpdateTypeEnum;
  68 +}
  69 +
  70 +export interface DataUpdateMsg<T> extends CmdUpdateMsg {
  71 + data?: PageData<T>;
  72 + update?: T[];
  73 +}
  74 +
  75 +export enum EntityType {
  76 + TENANT = 'TENANT',
  77 + TENANT_PROFILE = 'TENANT_PROFILE',
  78 + CUSTOMER = 'CUSTOMER',
  79 + USER = 'USER',
  80 + DASHBOARD = 'DASHBOARD',
  81 + ASSET = 'ASSET',
  82 + DEVICE = 'DEVICE',
  83 + DEVICE_PROFILE = 'DEVICE_PROFILE',
  84 + ASSET_PROFILE = 'ASSET_PROFILE',
  85 + ALARM = 'ALARM',
  86 + RULE_CHAIN = 'RULE_CHAIN',
  87 + RULE_NODE = 'RULE_NODE',
  88 + EDGE = 'EDGE',
  89 + ENTITY_VIEW = 'ENTITY_VIEW',
  90 + WIDGETS_BUNDLE = 'WIDGETS_BUNDLE',
  91 + WIDGET_TYPE = 'WIDGET_TYPE',
  92 + API_USAGE_STATE = 'API_USAGE_STATE',
  93 + TB_RESOURCE = 'TB_RESOURCE',
  94 + OTA_PACKAGE = 'OTA_PACKAGE',
  95 + RPC = 'RPC',
  96 + QUEUE = 'QUEUE',
  97 +}
  98 +
  99 +export enum AliasEntityType {
  100 + CURRENT_CUSTOMER = 'CURRENT_CUSTOMER',
  101 + CURRENT_TENANT = 'CURRENT_TENANT',
  102 + CURRENT_USER = 'CURRENT_USER',
  103 + CURRENT_USER_OWNER = 'CURRENT_USER_OWNER',
  104 +}
  105 +
  106 +export interface HasUUID {
  107 + id: string;
  108 +}
  109 +
  110 +export interface TsValue {
  111 + ts: number;
  112 + value: string;
  113 + count?: number;
  114 +}
  115 +
  116 +export interface EntityId extends HasUUID {
  117 + entityType: EntityType | AliasEntityType;
  118 +}
  119 +
  120 +export interface ComparisonTsValue {
  121 + current?: TsValue;
  122 + previous?: TsValue;
  123 +}
  124 +
  125 +export interface EntityData {
  126 + entityId: EntityId;
  127 + latest: { [entityKeyType: string]: { [key: string]: TsValue } };
  128 + timeseries: { [key: string]: TsValue[] };
  129 + aggLatest?: { [id: number]: ComparisonTsValue };
  130 +}
  131 +
  132 +/**
  133 + * @description entity data receive message
  134 + */
  135 +export interface ReceiveEntityDataMessageType extends DataUpdateMsg<EntityData> {
  136 + cmdUpdateType: CmdUpdateTypeEnum.ENTITY_DATA;
  137 +}
  138 +
  139 +/**
  140 + * @description 告警
  141 + */
  142 +export interface ReceiveAlarmDataCmdsMessageType extends DataUpdateMsg<Recordable> {
  143 + cmdUpdateType: CmdUpdateTypeEnum.ALARM_DATA;
  144 + allowedEntities: number;
  145 + totalEntities: number;
  146 +}
  147 +
  148 +/**
  149 + * @description entity count receive message
  150 + */
  151 +export interface ReceiveEntityCountMessageType extends CmdUpdateMsg {
  152 + cmdUpdateType: CmdUpdateTypeEnum.COUNT_DATA;
  153 + count: number;
  154 +}
  155 +
  156 +/**
  157 + * @description 接受消息
  158 + */
  159 +export type ReceiveMessageType =
  160 + | ReceiveTsSubCmdsMessageType
  161 + | ReceiveAlarmDataCmdsMessageType
  162 + | ReceiveEntityDataMessageType
  163 + | ReceiveEntityCountMessageType;
  164 +
  165 +export enum EntityTypeEnum {
  166 + DEVICE = 'DEVICE',
  167 +}
  168 +
  169 +export enum ScopeTypeEnum {
  170 + LATEST_TELEMERY = 'LATEST_TELEMERY',
  171 +}
  172 +
  173 +export enum AttributeScopeEnum {
  174 + CLIENT_SCOPE = 'CLIENT_SCOPE',
  175 + SERVER_SCOPE = 'SERVER_SCOPE',
  176 + SHARED_SCOPE = 'SHARED_SCOPE',
  177 +}
  178 +
  179 +export type DataFetchUpdateFn = (message: ReceiveTsSubCmdsMessageType, attr: string) => void;
  180 +
  181 +export type MultipleDataFetchUpdateFn = (
  182 + message: ReceiveTsSubCmdsGroupMessageType,
  183 + deviceId: string,
  184 + attr: string[]
  185 +) => void;
... ...
... ... @@ -3,6 +3,15 @@ import { ComponentPropsConfigType } from '../index.type';
3 3 import { componentOptionsInitConfig } from '../publicConfig';
4 4
5 5 export const useComponentScale = (props: { config: ComponentPropsConfigType }, onScale?: Fn) => {
  6 + const getContainerSize = computed(() => {
  7 + const { option } = props.config;
  8 + const { widthPx, heightPx } = option;
  9 + return {
  10 + width: widthPx,
  11 + height: heightPx,
  12 + };
  13 + });
  14 +
6 15 const getRatio = computed(() => {
7 16 try {
8 17 const { option, attr } = props.config;
... ... @@ -47,5 +56,5 @@ export const useComponentScale = (props: { config: ComponentPropsConfigType }, o
47 56 onScale?.();
48 57 });
49 58
50   - return { getScale, getScaleRadio, getRatio };
  59 + return { getScale, getScaleRadio, getRatio, getContainerSize };
51 60 };
... ...
1   -import { ReceiveGroupMessageType } from '../index.type';
  1 +import { ReceiveTsSubCmdsGroupMessageType } from '../index.type';
2 2
3 3 export const useReceiveMessage = () => {
4 4 const forEachGroupMessage = (
5   - message: ReceiveGroupMessageType,
  5 + message: ReceiveTsSubCmdsGroupMessageType,
6 6 deviceId: string,
7 7 attributes: string[],
8 8 Fn: (attribute: string, value: any, timespan: number) => void
... ...
... ... @@ -14,7 +14,7 @@ export function useSendCommand() {
14 14 return false;
15 15 };
16 16
17   - const sendCommand = async (record: DataSource, value: any) => {
  17 + const sendCommand = async (record: DataSource, value: any, isModbusCommand = false) => {
18 18 if (!record) return false;
19 19 const { customCommand, attribute } = record || {};
20 20 const { deviceId } = record;
... ... @@ -25,16 +25,18 @@ export function useSendCommand() {
25 25 let params: string | Recordable = {
26 26 [attribute!]: Number(value),
27 27 };
  28 + if (isModbusCommand) {
  29 + params = value;
  30 + }
28 31
29 32 let sendCommandFn = sendCommandOneway;
30 33 // 如果是TCP设备从物模型中获取下发命令(TCP网关子设备无物模型服务与事件)
31   - if (customCommand?.transportType === TransportTypeEnum.TCP) {
  34 + if (customCommand?.transportType === TransportTypeEnum.TCP && !isModbusCommand) {
32 35 params = customCommand.command!;
33   - if (customCommand.callType === ServiceCallTypeEnum.ASYNC) {
  36 + if (customCommand.callType === ServiceCallTypeEnum.SYNC) {
34 37 sendCommandFn = sendCommandTwoway;
35 38 }
36 39 }
37   -
38 40 // 控制按钮下发命令为0 或 1
39 41 await sendCommandFn({
40 42 deviceId,
... ...
... ... @@ -19,8 +19,9 @@ export enum PackagesCategoryNameEnum {
19 19 // PICTURE = '图片组件',
20 20 CONTROL = '控制组件',
21 21 MAP = '地图组件',
22   - FLOWMETER = '量计',
  22 + FLOWMETER = '量计',
23 23 STATISTICS = '统计',
  24 + ALARM = '告警',
24 25 OTHER = '其他',
25 26 }
26 27
... ... @@ -35,6 +36,7 @@ export enum PackagesCategoryEnum {
35 36 MAP = 'MAP',
36 37 FLOWMETER = 'FLOWMETER',
37 38 STATISTICS = 'STATISTICS',
  39 + ALARM = 'ALARM',
38 40 OTHER = 'OTHER',
39 41 }
40 42
... ... @@ -137,71 +139,7 @@ export interface PackagesType {
137 139 [PackagesCategoryEnum.CONTROL]: ConfigType[];
138 140 [PackagesCategoryEnum.MAP]: ConfigType[];
139 141 [PackagesCategoryEnum.FLOWMETER]: ConfigType[];
140   - // [PackagesCategoryEnum.STATISTICS]: ConfigType[];
  142 + [PackagesCategoryEnum.STATISTICS]: ConfigType[];
141 143 [PackagesCategoryEnum.OTHER]: ConfigType[];
  144 + [PackagesCategoryEnum.ALARM]: ConfigType[];
142 145 }
143   -
144   -export interface SubscribeMessageType {
145   - tsSubCmds: SubscribeMessageItemType[];
146   -}
147   -
148   -export interface SubscribeMessageItemType {
149   - cmdId: number;
150   - entityId: string;
151   - entityType: string;
152   - keys?: string;
153   - scope?: string;
154   - unsubscribe?: boolean;
155   -}
156   -
157   -export interface ReceiveMessageType {
158   - subscriptionId: number;
159   - errorCode: number;
160   - errorMsg: Nullable<string>;
161   - data: Record<string, [number, string][]>;
162   - latestValues: Record<string, number>;
163   -}
164   -
165   -export interface ReceiveGroupMessageType {
166   - subscriptionId: number;
167   - errorCode: number;
168   - errorMsg: Nullable<string>;
169   - data: {
170   - string: Record<string, [number, string][]>;
171   - };
172   - latestValues: {
173   - string: Record<string, number>;
174   - };
175   -}
176   -
177   -export enum EntityTypeEnum {
178   - DEVICE = 'DEVICE',
179   -}
180   -
181   -export enum ScopeTypeEnum {
182   - LATEST_TELEMERY = 'LATEST_TELEMERY',
183   -}
184   -
185   -export type DataFetchUpdateFn = (message: ReceiveMessageType, attr: string) => void;
186   -
187   -export type MultipleDataFetchUpdateFn = (
188   - message: ReceiveGroupMessageType,
189   - deviceId: string,
190   - attr: string[]
191   -) => void;
192   -
193   -// 旧 组件key
194   -// TEXT_COMPONENT_1 = 'text-component-1',
195   -// TEXT_COMPONENT_2 = 'text-component-2',
196   -// TEXT_COMPONENT_3 = 'text-component-3',
197   -// TEXT_COMPONENT_4 = 'text-component-4',
198   -// TEXT_COMPONENT_5 = 'text-component-5',
199   -// INSTRUMENT_COMPONENT_1 = 'instrument-component-1',
200   -// INSTRUMENT_COMPONENT_2 = 'instrument-component-2',
201   -// DIGITAL_DASHBOARD_COMPONENT = 'digital-dashboard-component',
202   -// PICTURE_COMPONENT_1 = 'picture-component-1',
203   -// CONTROL_COMPONENT_TOGGLE_SWITCH = 'control-component-toggle-switch',
204   -// CONTROL_COMPONENT_SWITCH_WITH_ICON = 'control-component-switch-with-icon',
205   -// CONTROL_COMPONENT_SLIDING_SWITCH = 'control-component-sliding-switch',
206   -// MAP_COMPONENT_TRACK_REAL = 'map-component-track-real',
207   -// MAP_COMPONENT_TRACK_HISTORY = 'map-component-track-history',
... ...
... ... @@ -5,7 +5,8 @@ import { MapList } from './components/Map';
5 5 import { OtherList } from './components/Other';
6 6 // import { PictureList } from './components/Picture';
7 7 import { TextList } from './components/Text';
8   -// import { STATISTICSList } from './components/Statistics';
  8 +import { STATISTICSList } from './components/Statistics';
  9 +import { AlarmList } from './components/Alarm';
9 10 import { PackagesCategoryEnum, PackagesType } from './index.type';
10 11
11 12 export const packageList: PackagesType = {
... ... @@ -15,6 +16,12 @@ export const packageList: PackagesType = {
15 16 [PackagesCategoryEnum.CONTROL]: ControlList,
16 17 [PackagesCategoryEnum.MAP]: MapList,
17 18 [PackagesCategoryEnum.FLOWMETER]: FlowmeterList,
18   - // [PackagesCategoryEnum.STATISTICS]: STATISTICSList,
  19 + [PackagesCategoryEnum.STATISTICS]: STATISTICSList,
  20 + [PackagesCategoryEnum.ALARM]: AlarmList,
19 21 [PackagesCategoryEnum.OTHER]: OtherList,
20 22 };
  23 +
  24 +/**
  25 + * @description
  26 + */
  27 +export const CUSTOM_SUBSCRIBE_MESSAGE_COMPONENT_KEY_LIST: string[] = ['DeviceAlarmHistory'];
... ...
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';
  4 + import { useDataFetch } from '../hook/socket/useSocket';
  5 + import { DataFetchUpdateFn } from '../hook/socket/useSocket.type';
5 6
6 7 const props = defineProps<{
7 8 config: ComponentPropsConfigType<typeof option>;
... ...
... ... @@ -6,7 +6,7 @@
6 6 import { DataActionModeEnum } from '/@/enums/toolEnum';
7 7 import { VisualComponentPermission } from '../..';
8 8 import { Tooltip } from 'ant-design-vue';
9   - import { MoreOutlined, AreaChartOutlined } from '@ant-design/icons-vue';
  9 + import { MoreOutlined, AreaChartOutlined, FieldTimeOutlined } from '@ant-design/icons-vue';
10 10 import { WidgetDataType } from '../../hooks/useDataSource';
11 11 import { useMessage } from '/@/hooks/web/useMessage';
12 12 import { addDataComponent, deleteDataComponent } from '/@/api/dataBoard';
... ... @@ -29,6 +29,7 @@
29 29 (event: 'ok'): void;
30 30 (event: 'update', data: WidgetDataType): void;
31 31 (event: 'openTrend', data: WidgetDataType): void;
  32 + (event: 'openAlarm', data: WidgetDataType): void;
32 33 }>();
33 34
34 35 const { isCustomerUser } = useRole();
... ... @@ -88,6 +89,27 @@
88 89 return isBoolean(flag) ? flag : true;
89 90 });
90 91
  92 + const isAlarmHistory = computed(() => {
  93 + const frontId = props.sourceInfo.frontId;
  94 + if (
  95 + // frontId == 'DeviceAlarm' ||
  96 + frontId == 'DeviceAlarmHistory' ||
  97 + frontId == 'StatisticsComponent7'
  98 + ) {
  99 + return true;
  100 + } else {
  101 + return false;
  102 + }
  103 + });
  104 + const isAlarm = computed(() => {
  105 + const frontId = props.sourceInfo.frontId;
  106 + if (frontId == 'DeviceAlarm') {
  107 + return false;
  108 + } else {
  109 + return true;
  110 + }
  111 + });
  112 +
91 113 async function handleCopy() {
92 114 const id = props.sourceInfo.id;
93 115 const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id);
... ... @@ -120,20 +142,30 @@
120 142 const handleOpenTrendModal = () => {
121 143 emit('openTrend', toRaw(props.sourceInfo));
122 144 };
  145 +
  146 + const handleAlarmModal = () => {
  147 + emit('openAlarm', toRaw(props.sourceInfo));
  148 + };
123 149 </script>
124 150
125 151 <template>
126 152 <section class="p-5 flex flex-col w-full">
127 153 <main class="flex w-full h-full h-7">
128 154 <Tooltip :title="sourceInfo.name">
129   - <div class="flex-1 w-full h-full flex text-lg justify-center font-semibold">
130   - {{ sourceInfo.name }}
  155 + <div class="flex-1 w-full h-full flex text-lg justify-center font-semibold truncate">
  156 + <div class="w-full truncate text-center">
  157 + {{ sourceInfo.name }}
  158 + </div>
131 159 </div>
132 160 </Tooltip>
133 161
134 162 <div v-if="!getIsSharePage" class="flex items-center w-16 justify-evenly">
135   - <Tooltip v-if="!isCustomerUser && hasTrendQueryIcon" title="趋势">
136   - <AreaChartOutlined class="text-lg" @click="handleOpenTrendModal" />
  163 + <Tooltip
  164 + v-if="!isCustomerUser && hasTrendQueryIcon && isAlarm"
  165 + :title="isAlarmHistory ? '时间' : '趋势'"
  166 + >
  167 + <FieldTimeOutlined v-if="isAlarmHistory" class="text-lg" @click="handleAlarmModal" />
  168 + <AreaChartOutlined v-else class="text-lg" @click="handleOpenTrendModal" />
137 169 </Tooltip>
138 170 <AuthDropDown
139 171 v-if="!isCustomerUser && dropMenuList.length"
... ...
  1 +import moment from 'moment';
  2 +import { FormSchema } from '/@/components/Form';
  3 +import { ColEx } from '/@/components/Form/src/types';
  4 +import { useGridLayout } from '/@/hooks/component/useGridLayout';
  5 +import { intervalOption } from '/@/views/device/localtion/cpns/TimePeriodForm/helper';
  6 +export enum QueryWay {
  7 + LATEST = 'latest',
  8 + TIME_PERIOD = 'timePeriod',
  9 +}
  10 +
  11 +export enum SchemaFiled {
  12 + DEVICE_ID = 'deviceId',
  13 + WAY = 'way',
  14 + TIME_PERIOD = 'timePeriod',
  15 + KEYS = 'keys',
  16 + DATE_RANGE = 'dataRange',
  17 + START_TS = 'startTs',
  18 + END_TS = 'endTs',
  19 + INTERVAL = 'interval',
  20 + LIMIT = 'limit',
  21 + AGG = 'agg',
  22 + ORDER_BY = 'orderBy',
  23 + PAGE_SIZE = 'pageSize',
  24 +}
  25 +
  26 +export enum AggregateDataEnum {
  27 + MIN = 'MIN',
  28 + MAX = 'MAX',
  29 + AVG = 'AVG',
  30 + SUM = 'SUM',
  31 + COUNT = 'COUNT',
  32 + NONE = 'NONE',
  33 +}
  34 +export const formSchema = (): FormSchema[] => {
  35 + return [
  36 + {
  37 + field: SchemaFiled.WAY,
  38 + label: '查询方式',
  39 + component: 'RadioGroup',
  40 + defaultValue: QueryWay.LATEST,
  41 + componentProps({ formActionType }) {
  42 + const { setFieldsValue } = formActionType;
  43 + return {
  44 + options: [
  45 + { label: '最后', value: QueryWay.LATEST },
  46 + { label: '时间段', value: QueryWay.TIME_PERIOD },
  47 + ],
  48 + onChange(event: ChangeEvent) {
  49 + (event.target as HTMLInputElement).value === QueryWay.LATEST
  50 + ? setFieldsValue({
  51 + [SchemaFiled.DATE_RANGE]: [],
  52 + [SchemaFiled.START_TS]: null,
  53 + [SchemaFiled.END_TS]: null,
  54 + })
  55 + : setFieldsValue({ [SchemaFiled.START_TS]: null });
  56 + },
  57 + getPopupContainer: () => document.body,
  58 + };
  59 + },
  60 + },
  61 +
  62 + {
  63 + field: SchemaFiled.PAGE_SIZE,
  64 + label: '分页条数',
  65 + component: 'InputNumber',
  66 + defaultValue: 20,
  67 + ifShow: true,
  68 + componentProps() {
  69 + return {
  70 + min: 10,
  71 + max: 50000,
  72 + getPopupContainer: () => document.body,
  73 + };
  74 + },
  75 + },
  76 + {
  77 + field: SchemaFiled.START_TS,
  78 + label: '最后数据',
  79 + component: 'Select',
  80 + ifShow({ values }) {
  81 + return values[SchemaFiled.WAY] === QueryWay.LATEST;
  82 + },
  83 + defaultValue: 2592000000,
  84 + componentProps({ formActionType }) {
  85 + const { setFieldsValue } = formActionType;
  86 + return {
  87 + options: intervalOption,
  88 + onChange() {
  89 + setFieldsValue({ [SchemaFiled.INTERVAL]: null });
  90 + },
  91 + getPopupContainer: () => document.body,
  92 + };
  93 + },
  94 + colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx,
  95 + rules: [{ required: true, message: '最后数据为必选项', type: 'number' }],
  96 + },
  97 + {
  98 + field: SchemaFiled.DATE_RANGE,
  99 + label: '时间段',
  100 + component: 'RangePicker',
  101 + ifShow({ values }) {
  102 + return values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD;
  103 + },
  104 + rules: [{ required: true, message: '时间段为必选项' }],
  105 + componentProps({ formActionType }) {
  106 + const { setFieldsValue } = formActionType;
  107 + return {
  108 + showTime: {
  109 + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
  110 + },
  111 + onChange() {
  112 + setFieldsValue({ [SchemaFiled.INTERVAL]: null });
  113 + },
  114 + getPopupContainer: () => document.body,
  115 + };
  116 + },
  117 + colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx,
  118 + },
  119 + {
  120 + field: SchemaFiled.LIMIT,
  121 + label: '最大条数',
  122 + component: 'InputNumber',
  123 + ifShow({ values }) {
  124 + return values[SchemaFiled.AGG] === AggregateDataEnum.NONE;
  125 + },
  126 + helpMessage: ['根据查询条件,查出的数据条数不超过这个值'],
  127 + componentProps() {
  128 + return {
  129 + max: 50000,
  130 + min: 7,
  131 + getPopupContainer: () => document.body,
  132 + };
  133 + },
  134 + },
  135 + ];
  136 +};
... ...
  1 +export { default as AlarmTimeModal } from './index.vue';
... ...
  1 +<script lang="ts" setup>
  2 + import { formSchema, SchemaFiled } from './config';
  3 + import { useForm } from '/@/components/Form';
  4 + import { useModalInner } from '/@/components/Modal';
  5 + import { useGridLayout } from '/@/hooks/component/useGridLayout';
  6 + import { ColEx } from '/@/components/Form/src/types';
  7 + import { BasicForm } from '/@/components/Form';
  8 + import { BasicModal } from '/@/components/Modal';
  9 + import { nextTick } from 'vue';
  10 + const emit = defineEmits(['register', 'getAlarmForm', 'getHistoryForm']);
  11 + // const emit = defineEmits<{
  12 + // (event: 'getAlarmForm', data: WidgetDataType): void;
  13 + // }>();
  14 +
  15 + // const fontId = ref('');
  16 + const [registerModal, { closeModal }] = useModalInner(async () => {});
  17 +
  18 + const [register, method] = useForm({
  19 + schemas: formSchema(),
  20 + baseColProps: useGridLayout(1) as unknown as ColEx,
  21 + showSubmitButton: false,
  22 + showResetButton: false,
  23 + rowProps: {
  24 + gutter: 10,
  25 + },
  26 + labelWidth: 120,
  27 + fieldMapToTime: [
  28 + [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:ss:mm'],
  29 + ],
  30 + });
  31 +
  32 + const handleCancel = () => {
  33 + // destory()
  34 + };
  35 + const handleSubmit = async () => {
  36 + const values = method.getFieldsValue();
  37 + // if (unref(fontId) == 'StatisticsComponent7') {
  38 + // emit('getHistoryForm', values);
  39 + // } else {
  40 +
  41 + // }
  42 + emit('getAlarmForm', values);
  43 + await nextTick();
  44 + closeModal();
  45 + };
  46 +</script>
  47 +
  48 +<template>
  49 + <BasicModal
  50 + @register="registerModal"
  51 + @cancel="handleCancel"
  52 + @ok="handleSubmit"
  53 + :destroy-on-close="true"
  54 + :show-ok-btn="true"
  55 + cancel-text="关闭"
  56 + width="40%"
  57 + title="历史趋势"
  58 + >
  59 + <section
  60 + class="flex flex-col p-4 h-full w-full min-w-7/10"
  61 + style="color: #f0f2f5; background-color: #f0f2f5"
  62 + >
  63 + <section class="bg-white my-3 p-2">
  64 + <BasicForm @register="register" />
  65 + </section>
  66 + </section>
  67 + </BasicModal>
  68 +</template>
  69 +
  70 +<style scoped></style>
... ...
  1 +import { Ref, inject, provide } from 'vue';
  2 +
  3 +const SymbolKey = Symbol('alarm-info');
  4 +interface IAlarm {
  5 + pageSize: number;
  6 + time?: string | number;
  7 + startTs?: string | number;
  8 + endTs?: string | number;
  9 +}
  10 +
  11 +export interface AlarmContextType {
  12 + alarmForm: Ref<IAlarm>;
  13 + getAlarmForm?: (value: any) => void;
  14 +}
  15 +
  16 +export const createAlarmContext = (options: AlarmContextType) => {
  17 + provide(SymbolKey, options);
  18 +};
  19 +
  20 +export const useAlarmContext = () => {
  21 + return inject<AlarmContextType>(SymbolKey) || ({} as Partial<AlarmContextType>);
  22 +};
... ...
  1 +import { inject, provide } from 'vue';
  2 +
  3 +const SymbolKey = Symbol('data-board');
  4 +
  5 +export interface ContextOptionsType {
  6 + send: (data: string | ArrayBuffer | Blob, useBuffer?: boolean | undefined) => boolean;
  7 + close: (code?: number | undefined, reason?: string | undefined) => void;
  8 +}
  9 +
  10 +export const createDataBoardContext = (options: ContextOptionsType) => {
  11 + provide(SymbolKey, options);
  12 +};
  13 +
  14 +export const useDataBoardContext = () => {
  15 + return inject<ContextOptionsType>(SymbolKey) || ({} as Partial<ContextOptionsType>);
  16 +};
... ...
  1 +import { Ref, inject, provide } from 'vue';
  2 +
  3 +const SymbolKey = Symbol('history-info');
  4 +interface IHistory {
  5 + [key: string]: string;
  6 +}
  7 +
  8 +export interface HistoryContextType {
  9 + historyForm: Ref<IHistory> | any;
  10 + getHistoryForm: (value: any) => void;
  11 +}
  12 +
  13 +export const createHistoryContext = (options: HistoryContextType) => {
  14 + provide(SymbolKey, options);
  15 +};
  16 +
  17 +export const useHistoryContext = () => {
  18 + return inject<HistoryContextType>(SymbolKey) || ({} as Partial<HistoryContextType>);
  19 +};
... ...
... ... @@ -25,10 +25,14 @@
25 25 import { ModalParamsType } from '/#/utils';
26 26 import { DataActionModeEnum } from '/@/enums/toolEnum';
27 27 import { HistoryTrendModal } from './components/HistoryTrendModal';
28   - import { useSocket } from '../packages/hook/useSocket';
  28 + import { AlarmTimeModal } from './components/AlarmTimeModal';
29 29 import { watch } from 'vue';
30 30 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
31 31 import { ThemeEnum } from '/@/enums/appEnum';
  32 + import { createDataBoardContext } from './hooks/useDataBoardContext';
  33 + import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket';
  34 + import { createAlarmContext } from './hooks/useAlarmTime';
  35 + import { createHistoryContext } from './hooks/useHistoryForm';
32 36
33 37 const props = defineProps<{
34 38 value?: Recordable;
... ... @@ -72,7 +76,74 @@
72 76 openTrendModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType);
73 77 };
74 78
75   - useSocket(dataSource);
  79 + // 设备告警时间选择
  80 + const [registerAlarmModal, { openModal: openAlarmModal }] = useModal();
  81 + const handleOpenAlarm = (data: WidgetDataType) => {
  82 + openAlarmModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType);
  83 + };
  84 +
  85 + const alarmForm = ref<{
  86 + time?: number | string;
  87 + pageSize: number;
  88 + startTs?: number | string;
  89 + endTs?: string | number;
  90 + }>({
  91 + time: 2592000000,
  92 + pageSize: 20,
  93 + startTs: undefined,
  94 + endTs: undefined,
  95 + });
  96 +
  97 + //告警发送数据
  98 + const getAlarmForm = (values) => {
  99 + const { way, pageSize, startTs, endTs } = values;
  100 + if (way == 'timePeriod') {
  101 + alarmForm.value = {
  102 + time: undefined,
  103 + startTs,
  104 + endTs,
  105 + pageSize,
  106 + };
  107 + } else
  108 + alarmForm.value = {
  109 + time: startTs,
  110 + pageSize,
  111 + startTs: undefined,
  112 + endTs: undefined,
  113 + };
  114 + };
  115 + createAlarmContext({ alarmForm: alarmForm });
  116 +
  117 + // 历史数据发送
  118 + const historyForm = ref({
  119 + startTs: Date.now() - 30 * 24 * 60 * 60 * 1000,
  120 + endTs: Date.now(),
  121 + agg: 'NONE',
  122 + limit: 30,
  123 + interval: undefined,
  124 + way: 'latest',
  125 + });
  126 +
  127 + const getHistoryForm = (values) => {
  128 + const { startTs, endTs, agg, limit, interval, way } = values;
  129 + historyForm.value = {
  130 + startTs,
  131 + endTs,
  132 + agg,
  133 + limit,
  134 + interval,
  135 + way,
  136 + };
  137 + if (way === 'latest') {
  138 + historyForm.value.startTs = Date.now() - startTs;
  139 + historyForm.value.endTs = Date.now();
  140 + }
  141 + };
  142 + createHistoryContext({ historyForm: historyForm, getHistoryForm });
  143 +
  144 + const { send, close } = useSocket(dataSource);
  145 +
  146 + createDataBoardContext({ send, close });
76 147
77 148 const { getDarkMode } = useRootSetting();
78 149 watch(
... ... @@ -144,6 +215,7 @@
144 215 :source-info="item"
145 216 @update="handleUpdateWidget"
146 217 @open-trend="handleOpenTrend"
  218 + @open-alarm="handleOpenAlarm"
147 219 @ok="getDataSource"
148 220 />
149 221 </template>
... ... @@ -159,7 +231,11 @@
159 231
160 232 <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" />
161 233
  234 + <!-- 趋势 -->
162 235 <HistoryTrendModal @register="registerTrendModal" />
  236 +
  237 + <!-- 选择时间 -->
  238 + <AlarmTimeModal @register="registerAlarmModal" @getAlarmForm="getAlarmForm" />
163 239 </section>
164 240 </template>
165 241
... ...
... ... @@ -28,6 +28,7 @@ export interface DataSource {
28 28 componentInfo: ComponentInfo;
29 29 customCommand: CustomCommand;
30 30 videoConfig?: VideoConfigType;
  31 + [key: string]: any;
31 32 }
32 33
33 34 export interface ExtraDataSource extends DataSource, PublicComponentOptions {
... ... @@ -53,12 +54,15 @@ export interface ComponentInfo {
53 54 icon: string;
54 55 iconColor: string;
55 56 showDeviceName: boolean;
  57 + showTime?: boolean;
56 58 gradientInfo: ComponentInfoGradientInfoType[];
57 59 flowmeterConfig: FlowmeterConfigType;
58 60 pointerColor?: string;
59 61 instrumentPanelColor?: string;
60 62 // instrumentPanelWidth?: string | number;
61 63 progressBarCircle?: Boolean;
  64 + lineColor?: string;
  65 + maxNumber?: number;
62 66 [key: string]: any;
63 67 }
64 68
... ... @@ -68,6 +72,7 @@ export interface CustomCommand {
68 72 command: string;
69 73 service: string;
70 74 callType: string;
  75 + [key: string]: string | undefined;
71 76 }
72 77
73 78 export interface ComponentLayoutType {
... ...
... ... @@ -80,12 +80,12 @@
80 80 :tab="item.title"
81 81 :forceRender="false"
82 82 >
83   - <main class="flex min-h-64 px-2 gap-8 flex-wrap justify-start">
  83 + <main class="flex min-h-64 px-2 gap-8 flex-wrap justify-start py-2">
84 84 <StageFrame
85 85 class="mt-4"
86 86 v-for="temp in item.items"
87 87 :key="temp.key"
88   - @click="handleSelected(temp)"
  88 + @click.capture="handleSelected(temp)"
89 89 :class="temp.key === props.checked?.componentKey ? '!border-2 !border-blue-500' : ''"
90 90 >
91 91 <component :is="componentMap.get(temp.key)" v-bind="getBindConfig(temp.key)" />
... ...