Commit 4e42ac99002e75b4feb610c1a8b85c830dfd833f

Authored by loveumiko
1 parent e2056a64

feat: 新增看板告警实时数据柱状图等组件

Showing 33 changed files with 1714 additions and 235 deletions
  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 +};
  19 +
  20 +export default class Config extends PublicConfigClass implements CreateComponentType {
  21 + public key: string = DeviceAlarmConfig.key;
  22 +
  23 + public attr = { ...componentInitConfig };
  24 +
  25 + public componentConfig: ConfigType = cloneDeep(DeviceAlarmConfig);
  26 +
  27 + public persetOption = cloneDeep(option);
  28 +
  29 + public option: PublicComponentOptions;
  30 +
  31 + constructor(option: PublicComponentOptions) {
  32 + super();
  33 + this.option = { ...option };
  34 + }
  35 +}
... ...
  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 + showActionButtonGroup: false,
  35 + labelWidth: 120,
  36 + baseColProps: {
  37 + span: 12,
  38 + },
  39 + });
  40 +
  41 + const getFormValues = () => {
  42 + return getFieldsValue();
  43 + };
  44 +
  45 + const setFormValues = (data: Recordable) => {
  46 + return setFieldsValue(data);
  47 + };
  48 +
  49 + defineExpose({
  50 + getFormValues,
  51 + setFormValues,
  52 + resetFormValues: resetFields,
  53 + } as PublicFormInstaceType);
  54 +</script>
  55 +
  56 +<template>
  57 + <BasicForm @register="register" />
  58 +</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 +
  14 + interface IStatus {
  15 + text: string;
  16 + color: string;
  17 + }
  18 +
  19 + interface IAlarmStatusList {
  20 + id: string;
  21 + deviceName: string;
  22 + showDeviceName: boolean;
  23 + status: IStatus;
  24 + time: number;
  25 + }
  26 +
  27 + const props = defineProps<{
  28 + config: ComponentPropsConfigType<typeof option>;
  29 + }>();
  30 +
  31 + const isOpenClose = ref<boolean>(true);
  32 +
  33 + const { send } = useDataBoardContext();
  34 +
  35 + const currentCmdId = ref();
  36 +
  37 + const alarmLevel = (type: string): IStatus => {
  38 + if (type === 'CRITICAL') {
  39 + return { text: '紧急', color: 'alarm_state_critical' };
  40 + } else if (type === 'MAJOR') {
  41 + return { text: '重要', color: 'alarm_state_major' };
  42 + } else if (type === 'MINOR') {
  43 + return { text: '次要', color: 'alarm_state_minor' };
  44 + } else if (type === 'WARNING') {
  45 + return { text: '警告', color: 'alarm_state_warning' };
  46 + } else {
  47 + return { text: '不确定', color: 'alarm_state_other' };
  48 + }
  49 + };
  50 +
  51 + const getDesign = computed(() => {
  52 + const { persetOption = {}, option } = props.config;
  53 + const { dataSource } = option;
  54 + const { showDeviceName: presetShowDeviceName } = persetOption;
  55 +
  56 + return {
  57 + dataSource: dataSource?.map((item) => {
  58 + const { deviceId, deviceName, deviceRename, componentInfo } = item;
  59 + return {
  60 + id: deviceId,
  61 + deviceName: deviceRename || deviceName,
  62 + showDeviceName: componentInfo.showDeviceName ?? presetShowDeviceName,
  63 + };
  64 + }),
  65 + };
  66 + });
  67 +
  68 + const randomFn = () => {
  69 + useIntervalFn(() => {
  70 + isOpenClose.value = !unref(isOpenClose);
  71 + }, 4000);
  72 + };
  73 +
  74 + // 发送websocket的格式
  75 + const getMessage = (cmdId: number) => {
  76 + const message = {
  77 + alarmDataCmds: [
  78 + {
  79 + query: {
  80 + entityFilter: {
  81 + type: 'entityList',
  82 + resolveMultiple: true,
  83 + entityType: 'DEVICE',
  84 + entityList: unref(getDesign).dataSource?.map((item) => item.id),
  85 + },
  86 + pageLink: {
  87 + page: 0,
  88 + pageSize: unref(alarmForm)?.pageSize,
  89 + textSearch: null,
  90 + searchPropagatedAlarms: false,
  91 + statusList: [],
  92 + severityList: [],
  93 + typeList: [],
  94 + sortOrder: {
  95 + key: {
  96 + key: 'createdTime',
  97 + type: 'ALARM_FIELD',
  98 + },
  99 + direction: 'DESC',
  100 + },
  101 + timeWindow: unref(alarmForm)?.time,
  102 + },
  103 + alarmFields: [
  104 + {
  105 + type: 'ALARM_FIELD',
  106 + key: 'createdTime',
  107 + },
  108 + {
  109 + type: 'ALARM_FIELD',
  110 + key: 'originator',
  111 + },
  112 + {
  113 + type: 'ALARM_FIELD',
  114 + key: 'type',
  115 + },
  116 + {
  117 + type: 'ALARM_FIELD',
  118 + key: 'severity',
  119 + },
  120 + {
  121 + type: 'ALARM_FIELD',
  122 + key: 'status',
  123 + },
  124 + ],
  125 + entityFields: [],
  126 + latestValues: [],
  127 + },
  128 + cmdId,
  129 + },
  130 + ],
  131 + };
  132 + return message;
  133 + };
  134 +
  135 + const alarmStatusList = ref<IAlarmStatusList[]>([
  136 + {
  137 + id: '1',
  138 + deviceName: '设备',
  139 + status: { text: '紧急', color: 'alarm_state_major' },
  140 + time: 1689574726,
  141 + showDeviceName: true,
  142 + },
  143 + ]);
  144 +
  145 + const { alarmForm } = useAlarmContext();
  146 +
  147 + watch(
  148 + () => alarmForm?.value,
  149 + () => {
  150 + send?.(JSON.stringify(getMessage(unref(currentCmdId))));
  151 + },
  152 + { immediate: false }
  153 + );
  154 +
  155 + const transformMessage = (cmdId: number) => {
  156 + currentCmdId.value = cmdId;
  157 + send?.(JSON.stringify(getMessage(cmdId)));
  158 + };
  159 +
  160 + const getReduce = (data) => {
  161 + const list = data.reduce((acc, obj) => {
  162 + const found = acc.find((item) => item.entityId.id == obj.entityId.id);
  163 + if (!found) {
  164 + acc.push(obj);
  165 + }
  166 + return acc;
  167 + }, []);
  168 +
  169 + data.forEach((item) => {
  170 + list?.forEach((item1) => {
  171 + if (item.entityId.id == item1.entityId.id) {
  172 + item1.time = Number(item1.createdTime > item.createdTime)
  173 + ? item1.createdTime
  174 + : item.createdTime;
  175 + }
  176 + });
  177 + });
  178 +
  179 + return list;
  180 + };
  181 +
  182 + const updateFn = (message: ReceiveAlarmDataCmdsMessageType) => {
  183 + alarmStatusList.value = unref(getDesign).dataSource?.map((item) => {
  184 + return {
  185 + id: item.id,
  186 + deviceName: item.deviceName,
  187 + showDeviceName: item.showDeviceName,
  188 + status: { text: '', color: '' },
  189 + time: 0,
  190 + };
  191 + }) as any;
  192 + const { data } = message;
  193 + const alarmList = data?.data;
  194 + const uniData = getReduce(alarmList); //去重得到最新的事件对象
  195 +
  196 + if (!data?.data.length) return;
  197 + uniData.forEach((item) => {
  198 + alarmStatusList.value?.forEach((item1) => {
  199 + if (item.entityId.id == item1.id) {
  200 + item1.status = alarmLevel(item.severity);
  201 + item1.time = item.createdTime;
  202 + }
  203 + });
  204 + });
  205 + };
  206 +
  207 + onMounted(() => {
  208 + !props.config.option.uuid && randomFn();
  209 + });
  210 +
  211 + useCustomDataFetch(props, transformMessage, updateFn);
  212 +
  213 + const { getScale } = useComponentScale(props);
  214 +</script>
  215 +
  216 +<template>
  217 + <main :style="getScale" class="w-full h-full flex justify-center items-center">
  218 + <!-- <DeviceName :config="config" class="text-center" /> -->
  219 + <div
  220 + v-for="item in alarmStatusList"
  221 + :key="item.id"
  222 + class="flex flex-col justify-center items-center"
  223 + >
  224 + <div class="text-gray-500 text-sm truncate"
  225 + >{{
  226 + item.status.text
  227 + ? item.showDeviceName
  228 + ? item.deviceName + '-'
  229 + : ''
  230 + : '当前设备未发现告警'
  231 + }}{{ item.status.text }}</div
  232 + >
  233 + <div :class="item.status.color"></div>
  234 + <UpdateTime :time="item.time" />
  235 + </div>
  236 + </main>
  237 +</template>
  238 +<style lang="less" scoped>
  239 + .alarm_state_critical {
  240 + background: #cf1322;
  241 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #cf1322;
  242 + width: 60px;
  243 + height: 60px;
  244 + border-radius: 50%;
  245 + margin: 10px 0;
  246 + }
  247 +
  248 + .alarm_state_major {
  249 + background: #ff6e03;
  250 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #ff6e03;
  251 + width: 60px;
  252 + height: 60px;
  253 + border-radius: 50%;
  254 + margin: 10px 0;
  255 + }
  256 +
  257 + .alarm_state_minor {
  258 + background: #ff0;
  259 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #ff0;
  260 + width: 60px;
  261 + height: 60px;
  262 + border-radius: 50%;
  263 + margin: 10px 0;
  264 + }
  265 +
  266 + .alarm_state_warning {
  267 + background: #edf760;
  268 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #edf760;
  269 + width: 60px;
  270 + height: 60px;
  271 + border-radius: 50%;
  272 + margin: 10px 0;
  273 + }
  274 +
  275 + .alarm_state_other {
  276 + background: #d3adf7;
  277 + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #d3adf7;
  278 + width: 60px;
  279 + height: 60px;
  280 + border-radius: 50%;
  281 + margin: 10px 0;
  282 + }
  283 +</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 getMessage = (cmdId: number) => {
  132 + // const message = {
  133 + // alarmDataCmds: [
  134 + // {
  135 + // query: {
  136 + // entityFilter: {
  137 + // type: 'entityList',
  138 + // resolveMultiple: true,
  139 + // entityType: 'DEVICE',
  140 + // entityList: unref(getDesign).dataSource?.map((item) => item.id),
  141 + // },
  142 + // pageLink: {
  143 + // page: 0,
  144 + // pageSize: unref(alarmForm)?.pageSize,
  145 + // textSearch: null,
  146 + // searchPropagatedAlarms: false,
  147 + // statusList: [],
  148 + // severityList: [],
  149 + // typeList: [],
  150 + // sortOrder: {
  151 + // key: {
  152 + // key: 'createdTime',
  153 + // type: 'ALARM_FIELD',
  154 + // },
  155 + // direction: 'DESC',
  156 + // },
  157 + // timeWindow: unref(alarmForm)?.time,
  158 + // },
  159 + // alarmFields: [
  160 + // {
  161 + // type: 'ALARM_FIELD',
  162 + // key: 'createdTime',
  163 + // },
  164 + // {
  165 + // type: 'ALARM_FIELD',
  166 + // key: 'originator',
  167 + // },
  168 + // {
  169 + // type: 'ALARM_FIELD',
  170 + // key: 'type',
  171 + // },
  172 + // {
  173 + // type: 'ALARM_FIELD',
  174 + // key: 'severity',
  175 + // },
  176 + // {
  177 + // type: 'ALARM_FIELD',
  178 + // key: 'status',
  179 + // },
  180 + // ],
  181 + // entityFields: [],
  182 + // latestValues: [],
  183 + // },
  184 + // cmdId,
  185 + // },
  186 + // ],
  187 + // };
  188 + // return message;
  189 + // };
  190 +
  191 + const { alarmForm } = useAlarmContext();
  192 +
  193 + watch(
  194 + () => alarmForm?.value,
  195 + () => {
  196 + send?.(JSON.stringify(getMessage(unref(currentCmdId), unref(getDesign), unref(alarmForm))));
  197 + },
  198 + { immediate: false }
  199 + );
  200 +
  201 + const transformMessage = (cmdId: number) => {
  202 + currentCmdId.value = cmdId;
  203 +
  204 + send?.(JSON.stringify(getMessage(cmdId, unref(getDesign), unref(alarmForm))));
  205 + };
  206 +
  207 + const updateFn = (message: ReceiveAlarmDataCmdsMessageType) => {
  208 + const { data } = message || {};
  209 + const tableList = ref<any>(
  210 + data?.data.map((item) => {
  211 + return {
  212 + time: item.createdTime,
  213 + device: item.originatorName,
  214 + severity: item.severity,
  215 + type: item.type,
  216 + status: item.status,
  217 + };
  218 + })
  219 + );
  220 + setTableData(unref(tableList));
  221 + };
  222 +
  223 + useCustomDataFetch(props, transformMessage, updateFn);
  224 +
  225 + onMounted(async () => {
  226 + await nextTick();
  227 + resize();
  228 + });
  229 +
  230 + const resize = async () => {
  231 + const { height } = unref(getContainerSize);
  232 + height && setProps({ maxHeight: height - 100, scroll: { x: 470, y: height - 100 } });
  233 + await nextTick();
  234 + redoHeight();
  235 + };
  236 +
  237 + const { getContainerSize } = useComponentScale(props, resize);
  238 +</script>
  239 +
  240 +<template>
  241 + <main class="flex flex-col justify-center items-center w-full h-full">
  242 + <DeviceName :config="config" />
  243 + <div class="w-full h-full">
  244 + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" />
  245 + </div>
  246 + </main>
  247 +</template>
... ...
  1 +export const getMessage = (cmdId, getDesign, alarmForm) => {
  2 + const entityList = [...new Set(getDesign.dataSource?.map((item) => item.id))];
  3 + const message = {
  4 + alarmDataCmds: [
  5 + {
  6 + query: {
  7 + entityFilter: {
  8 + type: 'entityList',
  9 + resolveMultiple: true,
  10 + entityType: 'DEVICE',
  11 + entityList: entityList,
  12 + },
  13 + pageLink: {
  14 + page: 0,
  15 + pageSize: alarmForm?.pageSize,
  16 + textSearch: null,
  17 + searchPropagatedAlarms: false,
  18 + statusList: [],
  19 + severityList: [],
  20 + typeList: [],
  21 + sortOrder: {
  22 + key: {
  23 + key: 'createdTime',
  24 + type: 'ALARM_FIELD',
  25 + },
  26 + direction: 'DESC',
  27 + },
  28 + timeWindow: alarmForm?.time,
  29 + },
  30 + alarmFields: [
  31 + {
  32 + type: 'ALARM_FIELD',
  33 + key: 'createdTime',
  34 + },
  35 + {
  36 + type: 'ALARM_FIELD',
  37 + key: 'originator',
  38 + },
  39 + {
  40 + type: 'ALARM_FIELD',
  41 + key: 'type',
  42 + },
  43 + {
  44 + type: 'ALARM_FIELD',
  45 + key: 'severity',
  46 + },
  47 + {
  48 + type: 'ALARM_FIELD',
  49 + key: 'status',
  50 + },
  51 + ],
  52 + entityFields: [],
  53 + latestValues: [],
  54 + },
  55 + cmdId,
  56 + },
  57 + ],
  58 + };
  59 + return message;
  60 +};
... ...
  1 +import { DeviceAlarmHistoryConfig } from './DeviceAlarmHistory';
  2 +import { DeviceAlarmConfig } from './DeviceAlarm';
  3 +
  4 +export const AlarmList = [DeviceAlarmConfig, DeviceAlarmHistoryConfig];
... ...
... ... @@ -52,7 +52,6 @@
52 52
53 53 const handleBlur = async () => {
54 54 if (unref(oldSliderValue) !== unref(sliderValue)) {
55   - console.log('effect');
56 55 const flag = await sendCommand(props.config.option, unref(sliderValue));
57 56 flag
58 57 ? ((sliderValue.value = unref(sliderValue)),
... ...
... ... @@ -67,10 +67,8 @@
67 67 },
68 68 }))
69 69 );
70   - // console.log(unref(series), 'series');
71 70
72 71 const options = (): EChartsOption => {
73   - // getStageColor(gradientInfo);
74 72 return {
75 73 tooltip: {
76 74 trigger: 'item',
... ...
... ... @@ -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',
... ...
... ... @@ -6,6 +6,8 @@
6 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
7 7 import { useComponentScale } from '../../../hook/useComponentScale';
8 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  10 + import { useReceiveValue } from '../../../hook/useReceiveValue';
9 11 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
10 12 import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
11 13
... ... @@ -22,33 +24,45 @@
22 24 const getDesign = computed(() => {
23 25 const { persetOption, option } = props.config;
24 26 const { dataSource = [] } = option || {};
25   - const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
  27 + const {
  28 + unit: presetUnit,
  29 + fontColor: presetFontColor,
  30 + showDeviceName: persetShowDeviceName,
  31 + } = persetOption || {};
26 32 return {
27 33 dataSource: dataSource?.map((item) => {
28   - const { unit, fontColor } = item.componentInfo || {};
29   - const { attribute, attributeRename } = item;
  34 + const { unit, fontColor, showDeviceName } = item.componentInfo || {};
  35 + const { attribute, attributeName, attributeRename, deviceId, deviceName, deviceRename } =
  36 + item;
30 37 return {
31 38 unit: unit ?? presetUnit,
32 39 fontColor: fontColor ?? presetFontColor,
33 40 attribute,
  41 + attributeName,
34 42 attributeRename,
  43 + deviceName,
  44 + deviceRename,
  45 + showDeviceName: showDeviceName ?? persetShowDeviceName,
  46 + id: deviceId,
35 47 };
36 48 }),
37 49 };
38 50 });
  51 +
39 52 const seriesList = [
40 53 { value: 120, name: '123', itemStyle: { color: '#02E5F0' } },
41 54 { value: 150, name: '456', itemStyle: { color: '#028CF0' } },
42 55 { value: 40, name: '789', itemStyle: { color: '#F09202' } },
43 56 ];
44 57 const data = ['温度', '湿度', '温度1'];
  58 +
45 59 const options = (): EChartsOption => {
46   - // getStageColor(gradientInfo);
47 60 return {
48 61 color: ['#3398DB'],
49 62 tooltip: {
50 63 // 提示框
51   - trigger: 'axis',
  64 + trigger: 'item',
  65 + confine: true,
52 66 axisPointer: {
53 67 type: 'shadow',
54 68 },
... ... @@ -59,9 +73,6 @@
59 73 axisTick: {
60 74 alignWithLabel: true,
61 75 },
62   - // axisPointer: {
63   - // type: 'line',
64   - // },
65 76 },
66 77 grid: {
67 78 top: '15%',
... ... @@ -104,32 +115,40 @@
104 115 } as EChartsOption);
105 116 };
106 117
107   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
108   - const { data = {} } = message;
109   - const { dataSource } = unref(getDesign);
110   - const series = dataSource.map((item) => {
111   - const { attribute, attributeRename, fontColor, unit } = item;
112   - const [latest] = data[attribute] || [];
113   - const [_timespan, value] = latest || [];
114   -
115   - return {
116   - value,
117   - name: attributeRename ?? attribute,
118   - itemStyle: { color: fontColor },
119   - tooltip: {
120   - valueFormatter(value) {
121   - return `${value} ${unit ?? ''}`;
122   - },
  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 ?? ''}`;
123 135 },
124   - } as SeriesOption['data'];
125   - });
126   - const xAxisData = series.map((item) => {
127   - const { name } = item as any;
128   - 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) {
  144 + item.value = getNumberValue(value);
  145 + }
  146 + });
  147 + time.value = timespan;
129 148 });
130   - // }
131   - // console.log(message, 'message', series, 'series', sum);
132   - updateChart(series, xAxisData);
  149 + const xAxisData = unref(series).map((item) => item.name);
  150 +
  151 + updateChart(toRaw(unref(series)), xAxisData);
133 152 };
134 153
135 154 useMultipleDataFetch(props, updateFn);
... ...
... ... @@ -6,6 +6,8 @@
6 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
7 7 import { useComponentScale } from '../../../hook/useComponentScale';
8 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  9 + import { useReceiveMessage } from '../../../hook/useReceiveMessage';
  10 + import { useReceiveValue } from '../../../hook/useReceiveValue';
9 11 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
10 12 import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
11 13
... ... @@ -22,16 +24,26 @@
22 24 const getDesign = computed(() => {
23 25 const { persetOption, option } = props.config;
24 26 const { dataSource = [] } = option || {};
25   - const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
  27 + const {
  28 + unit: presetUnit,
  29 + fontColor: presetFontColor,
  30 + showDeviceName: persetShowDeviceName,
  31 + } = persetOption || {};
26 32 return {
27 33 dataSource: dataSource?.map((item) => {
28   - const { unit, fontColor } = item.componentInfo || {};
29   - const { attribute, attributeRename } = item;
  34 + const { unit, fontColor, showDeviceName } = item.componentInfo || {};
  35 + const { attribute, attributeName, attributeRename, deviceId, deviceName, deviceRename } =
  36 + item;
30 37 return {
31 38 unit: unit ?? presetUnit,
32 39 fontColor: fontColor ?? presetFontColor,
33 40 attribute,
  41 + attributeName,
34 42 attributeRename,
  43 + deviceName,
  44 + deviceRename,
  45 + showDeviceName: showDeviceName ?? persetShowDeviceName,
  46 + id: deviceId,
35 47 };
36 48 }),
37 49 };
... ... @@ -48,7 +60,8 @@
48 60 color: ['#3398DB'],
49 61 tooltip: {
50 62 // 提示框
51   - trigger: 'axis',
  63 + trigger: 'item',
  64 + confine: true,
52 65 axisPointer: {
53 66 type: 'shadow',
54 67 },
... ... @@ -104,32 +117,39 @@
104 117 } as EChartsOption);
105 118 };
106 119
107   - const updateFn: MultipleDataFetchUpdateFn = (message) => {
108   - const { data = {} } = message;
109   - const { dataSource } = unref(getDesign);
110   - const series = dataSource.map((item) => {
111   - const { attribute, attributeRename, fontColor, unit } = item;
112   - const [latest] = data[attribute] || [];
113   - const [_timespan, value] = latest || [];
114   -
115   - return {
116   - value,
117   - name: attributeRename ?? attribute,
118   - itemStyle: { color: fontColor },
119   - tooltip: {
120   - valueFormatter(value) {
121   - return `${value} ${unit ?? ''}`;
122   - },
  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 ?? ''}`;
123 137 },
124   - } as SeriesOption['data'];
125   - });
126   - const yAxisData = series.map((item) => {
127   - const { name } = item as any;
128   - 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) {
  146 + item.value = getNumberValue(value);
  147 + }
  148 + time.value = timespan;
  149 + });
129 150 });
130   - // }
131   - // console.log(message, 'message', series, 'series', sum);
132   - updateChart(series, yAxisData);
  151 + const xAxisData = unref(series).map((item) => item.name);
  152 + updateChart(toRaw(unref(series)), xAxisData);
133 153 };
134 154
135 155 useMultipleDataFetch(props, updateFn);
... ...
1 1 <script lang="ts" setup>
2   - import { reactive, ref } from 'vue';
  2 + import { computed, unref } from 'vue';
3 3 import { ComponentPropsConfigType } from '../../../index.type';
4 4 import { option } from './config';
5 5 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
6 6 import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
7   - import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  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';
8 14 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
  15 + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  16 +
  17 + interface IList {
  18 + [key: string]: string | number;
  19 + }
9 20
10 21 const props = defineProps<{
11 22 config: ComponentPropsConfigType<typeof option>;
12 23 }>();
13 24
14   - const realTimeList = ref<any>([
15   - { attribute: '测试', price: 2, time: '2023-06-29' },
16   - { attribute: '测试1', price: 21, time: '2023-06-29' },
17   - { attribute: '测试2', price: 213, time: '2023-06-29' },
18   - ]);
19   - const realTimeColumn = reactive<BasicColumn[]>([
20   - { title: '属性', dataIndex: 'attribute', width: 80, ellipsis: true },
21   - { title: '值', dataIndex: 'price', width: 80, ellipsis: true },
22   - { title: '时间', dataIndex: 'time', width: 80, ellipsis: true },
23   - ]);
24   -
25   - const [registerTable] = useTable({
26   - showIndexColumn: false,
27   - showTableSetting: false,
28   - dataSource: realTimeList,
29   - canResize: true,
30   - maxHeight: 144,
31   - size: 'small',
32   - columns: realTimeColumn as any,
  25 + const [registerTable, { setTableData, setColumns, getDataSource, redoHeight, setProps }] =
  26 + useTable({ showIndexColumn: false, showTableSetting: false, canResize: true, size: 'small' });
  27 +
  28 + const getDesign = computed(() => {
  29 + const { persetOption, option } = props.config;
  30 + const { dataSource = [] } = option || {};
  31 + const { unit: presetUnit, showDeviceName: presetShowDeviceName } = persetOption || {};
  32 + const columns: BasicColumn[] = dataSource.map((item) => ({
  33 + title: item.attributeRename || item.attributeName || item.attribute,
  34 + dataIndex: item.attribute,
  35 + width: 80,
  36 + ellipsis: true,
  37 + format(text, record) {
  38 + const value = text ? text + (record.unit || '') : '';
  39 + return value;
  40 + },
  41 + }));
  42 + columns.push({
  43 + title: '时间',
  44 + dataIndex: 'time',
  45 + width: 110,
  46 + ellipsis: true,
  47 + format(text) {
  48 + return formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss');
  49 + },
  50 + });
  51 + return {
  52 + columns,
  53 + dataSource: dataSource?.map((item) => {
  54 + const { unit, showDeviceName } = item.componentInfo || {};
  55 + const { attribute, attributeName, attributeRename, deviceName, deviceRename, deviceId } =
  56 + item;
  57 + return {
  58 + unit: unit ?? presetUnit,
  59 + attribute,
  60 + attributeRename,
  61 + attributeName,
  62 + showDeviceName: showDeviceName ?? presetShowDeviceName,
  63 + deviceName,
  64 + deviceRename,
  65 + id: deviceId,
  66 + };
  67 + }),
  68 + };
33 69 });
34 70
35   - const updateFn: MultipleDataFetchUpdateFn = () => {};
  71 + const { forEachGroupMessage } = useReceiveMessage();
  72 + const { getNumberValue } = useReceiveValue();
  73 +
  74 + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => {
  75 + const list: IList = {};
  76 + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  77 + list[attribute] = getNumberValue(value);
  78 + list.time = timespan || list.time;
  79 + });
  80 + await nextTick();
  81 + setTableData([list, ...toRaw(unref(getDataSource()))]);
  82 + };
36 83
37 84 useMultipleDataFetch(props, updateFn);
  85 + onMounted(async () => {
  86 + setColumns(
  87 + props.config.option.dataSource
  88 + ? unref(getDesign).columns
  89 + : [
  90 + { title: '属性', dataIndex: 'attribute', width: 80, ellipsis: true },
  91 + { title: '时间', dataIndex: 'time', width: 80, ellipsis: true },
  92 + ]
  93 + );
  94 + !props.config.option.dataSource &&
  95 + setTableData([
  96 + { attribute: '温度', time: '2023-06-29' },
  97 + { attribute: '湿度', time: '2023-06-29' },
  98 + { attribute: '湿度', time: '2023-06-29' },
  99 + ]);
  100 +
  101 + await nextTick();
  102 + resize();
  103 + });
  104 + const resize = async () => {
  105 + const { height } = unref(getContainerSize);
  106 + height && setProps({ scroll: { x: 190, y: height - 120 } });
  107 +
  108 + await nextTick();
  109 + redoHeight();
  110 + };
  111 +
  112 + const { getContainerSize } = useComponentScale(props, resize);
38 113 </script>
39 114
40 115 <template>
41   - <main class="flex flex-col justify-center items-center">
  116 + <main class="flex flex-col justify-center items-center w-full h-full">
42 117 <DeviceName :config="config" />
43   - <div>
  118 + <div class="w-full h-full">
44 119 <!-- <PageWrapper> -->
45   - <BasicTable @register="registerTable" />
  120 + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" />
46 121 <!-- </PageWrapper> -->
47 122 </div>
48 123 </main>
... ...
... ... @@ -8,13 +8,6 @@
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',
... ...
1 1 <script lang="ts" setup>
2 2 import { EChartsOption, ECharts, init } from 'echarts';
3   - import { unref, ref, onMounted, nextTick, toRaw } from 'vue';
  3 + import { unref, ref, onMounted, nextTick, toRaw, computed } from 'vue';
4 4 import { ComponentPropsConfigType } from '../../../index.type';
5 5 import { option } from './config';
6 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
7 7 import { useComponentScale } from '../../../hook/useComponentScale';
8 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9   - import { dateFormat } from '/@/utils/common/compUtils';
  9 + import { useIntervalFn } from '@vueuse/core';
10 10 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
11 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 + }
12 17
13 18 const props = defineProps<{
14 19 config: ComponentPropsConfigType<typeof option>;
15 20 }>();
16   -
17 21 const chartRefEl = ref<Nullable<HTMLDivElement>>(null);
18 22
19 23 const chartInstance = ref<Nullable<ECharts>>(null);
20   -
21 24 const time = ref<Nullable<number>>(null);
22 25
23   - function randomData() {
24   - now.value = now.value + oneDay;
25   - const newTime = dateFormat(unref(now), 'MM-dd hh:mm:ss');
26   - value = value + Math.random() * 21 - 10;
27   - return {
28   - name: newTime,
29   - value: [newTime, Math.round(value)],
30   - };
31   - }
32   - const data = ref<any>([]);
33   - const now = ref<number>(1688026367000);
34   - let oneDay = 5000; //间隔秒数
35   - let value = Math.random() * 1000;
36   - for (let i = 0; i < 10; i++) {
37   - data.value.push(randomData());
38   - }
39   - setInterval(function () {
40   - for (let i = 0; i < 1; i++) {
41   - data.value.shift();
42   - data.value.push(randomData());
43   - }
44   - unref(chartInstance)?.setOption(options());
45   - }, 1000);
46   -
47   - // const getDesign = computed(() => {
48   - // const { persetOption, option } = props.config;
49   - // const { dataSource = [] } = option || {};
50   - // const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
51   - // return {
52   - // dataSource: dataSource?.map((item) => {
53   - // const { unit, fontColor } = item.componentInfo || {};
54   - // const { attribute, attributeRename } = item;
55   - // return {
56   - // unit: unit ?? presetUnit,
57   - // fontColor: fontColor ?? presetFontColor,
58   - // attribute,
59   - // attributeRename,
60   - // };
61   - // }),
62   - // };
63   - // });
  26 + const updateInterval = ref<number>(1000); //默认每秒更新一次
  27 + const maxDataPoints = ref<number>(10); //默认每秒显示10个数据点
  28 +
  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 + };
64 40
65 41 const options = (): EChartsOption => {
66   - // getStageColor(gradientInfo);
67 42 return {
68   - trigger: 'axis',
69   - axisPointer: {
70   - lineStyle: {
71   - width: 1,
72   - color: '#019680',
73   - },
74   - },
75 43 tooltip: {
76   - show: true,
  44 + // trigger: 'axis',
77 45 },
78 46 legend: {
79 47 top: '10%',
80 48 left: 'center',
81   - data: ['温度', '湿度'],
  49 + data: ['温度'],
82 50 },
83 51 grid: {
84   - top: '45%',
85   - left: '20%',
86   - bottom: '14%',
  52 + top: '30%',
  53 + left: '6%',
  54 + right: '16%',
  55 + bottom: '1%',
87 56 containLabel: true,
88 57 },
89 58 xAxis: {
90 59 type: 'category',
91   - splitLine: {
92   - show: true,
93   - lineStyle: {
94   - width: 1,
95   - type: 'solid',
96   - color: 'rgba(226,226,226,0.5)',
97   - },
98   - },
99   - data: toRaw(unref(data)).map((item) => item.name),
  60 + // boundaryGap: false,
100 61 },
101 62 yAxis: {
102 63 type: 'value',
103   - boundaryGap: [0, '50%'],
104   - splitLine: {
105   - show: false,
106   - },
  64 + boundaryGap: [0, '100%'],
107 65 },
108   - series: [
109   - {
110   - type: 'line',
111   - name: '温度',
112   - stack: 'Total',
113   - data: unref(data).map((item) => item.value),
114   - },
115   - {
116   - type: 'line',
117   - name: '湿度',
118   - stack: 'Total',
119   - data: unref(data).map((item) => {
120   - return Number(item.value[1]) * 0.99;
121   - }),
122   - },
123   - ],
  66 + series: [{ type: 'line', name: '温度', data: [] }],
124 67 };
125 68 };
126 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 { unit: presetUnit, fontColor: presetFontColor } = persetOption || {};
  90 +
  91 + return {
  92 + dataSource: dataSource?.map((item) => {
  93 + const { unit, showDeviceName, fontColor } = item.componentInfo || {};
  94 + console.log(item, 'item');
  95 + const { attribute, attributeRename, deviceId, attributeName, deviceName, deviceRename } =
  96 + item;
  97 + return {
  98 + unit: unit ?? presetUnit,
  99 + fontColor: fontColor ?? presetFontColor,
  100 + attribute,
  101 + attributeName,
  102 + attributeRename,
  103 + showDeviceName,
  104 + deviceName,
  105 + deviceRename,
  106 + id: deviceId,
  107 + };
  108 + }),
  109 + };
  110 + });
  111 +
127 112 const initial = () => {
128 113 chartInstance.value = init(unref(chartRefEl)! as HTMLElement);
129 114 chartInstance.value.setOption(options());
130 115 };
131 116
132   - // const updateChart = (data: SeriesOption['data'], yAxisData) => {
133   - // unref(chartInstance)?.setOption({
134   - // series: [{ data }],
135   - // yAxis: { data: yAxisData },
136   - // } as EChartsOption);
137   - // };
138   -
139   - const updateFn: MultipleDataFetchUpdateFn = () => {
140   - // console.log(message, 'message');
141   - return {};
  117 + const series = ref(
  118 + unref(getDesign).dataSource.map((item) => {
  119 + return {
  120 + type: 'line',
  121 + name: `${item.showDeviceName || item.deviceRename || item.deviceName} - ${
  122 + item.attributeRename || item.attributeName
  123 + }`,
  124 + data: [] as { name: string; value: number }[],
  125 + id: item.id,
  126 + attribute: item.attribute,
  127 + };
  128 + })
  129 + );
  130 +
  131 + const { forEachGroupMessage } = useReceiveMessage();
  132 +
  133 + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => {
  134 + legendData.value = unref(getDesign).dataSource.map((item) => {
  135 + return `${item.deviceRename || item.deviceName} - ${
  136 + item.attributeRename || item.attributeName
  137 + }`;
  138 + });
  139 + const list: IList | any = {};
  140 + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => {
  141 + list.time = timespan || list.time;
  142 + series.value.forEach((item) => {
  143 + if (item.id === deviceId && item.attribute === attribute) {
  144 + item.data.push({
  145 + name: formatToDateTime(list.time, 'HH:mm:ss'),
  146 + value: value,
  147 + });
  148 + if (item.data.length > unref(maxDataPoints)) {
  149 + item.data.shift();
  150 + }
  151 + }
  152 + });
  153 + });
  154 + list.time && timeList.value.push(formatToDateTime(list.time, 'HH:mm:ss'));
  155 + if (unref(timeList).length > unref(maxDataPoints)) {
  156 + timeList.value.shift();
  157 + }
  158 + await nextTick();
  159 + unref(chartInstance)?.setOption({
  160 + xAxis: {
  161 + data: toRaw(unref(timeList)),
  162 + },
  163 + legend: {
  164 + data: toRaw(unref(legendData)),
  165 + },
  166 + series: toRaw(
  167 + unref(series).map((item) => {
  168 + const { type, name, data } = item;
  169 + return {
  170 + type,
  171 + name,
  172 + data,
  173 + };
  174 + })
  175 + ),
  176 + });
142 177 };
143 178
144 179 useMultipleDataFetch(props, updateFn);
145   -
146 180 onMounted(() => {
147 181 initial();
148   - // !props.config.option.uuid && randomFn();
149   - !props.config.option.uuid;
  182 + !props.config.option.dataSource?.length && random();
150 183 });
151 184
152 185 const resize = async () => {
153 186 await nextTick();
154 187
155 188 // 修改echarts大小
156   - unref(chartInstance)?.setOption({
157   - legend: {
158   - textStyle: {
159   - fontSize: 14 * unref(getRatio),
160   - },
161   - },
162   - } as EChartsOption);
  189 + // unref(chartInstance)?.setOption({
  190 + // legend: {
  191 + // textStyle: {
  192 + // fontSize: 14 * unref(getRatio),
  193 + // },
  194 + // },
  195 + // } as EChartsOption);
163 196 unref(chartInstance)?.resize();
164 197 };
165 198
166   - const { getRatio } = useComponentScale(props, resize);
  199 + useComponentScale(props, resize);
167 200 </script>
168 201
169 202 <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 ];
... ...
... ... @@ -180,6 +180,7 @@ class Subscriber {
180 180 }
181 181
182 182 if (isEntityDataUpdateMsg(message)) {
  183 + this.triggerEntityDataMessage(message);
183 184 return;
184 185 }
185 186
... ... @@ -248,6 +249,20 @@ class Subscriber {
248 249 return;
249 250 }
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 + }
  265 + }
251 266 }
252 267
253 268 const subscriber = new Subscriber();
... ... @@ -426,4 +441,8 @@ export const useCustomDataFetch = (
426 441 immediate: true,
427 442 }
428 443 );
  444 +
  445 + return {
  446 + getNextSubscribeId: subscriber.getNextSubscribeId,
  447 + };
429 448 };
... ...
... ... @@ -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 };
... ...
... ... @@ -21,6 +21,7 @@ export enum PackagesCategoryNameEnum {
21 21 MAP = '地图组件',
22 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,6 +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 }
... ...
... ... @@ -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,11 +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 };
21 23
22 24 /**
23 25 * @description
24 26 */
25   -export const CUSTOM_SUBSCRIBE_MESSAGE_COMPONENT_KEY_LIST: string[] = [];
  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,15 @@
88 89 return isBoolean(flag) ? flag : true;
89 90 });
90 91
  92 + const isAlarm = computed(() => {
  93 + const frontId = props.sourceInfo.frontId;
  94 + if (frontId == 'DeviceAlarm' || frontId == 'DeviceAlarmHistory') {
  95 + return true;
  96 + } else {
  97 + return false;
  98 + }
  99 + });
  100 +
91 101 async function handleCopy() {
92 102 const id = props.sourceInfo.id;
93 103 const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id);
... ... @@ -120,6 +130,10 @@
120 130 const handleOpenTrendModal = () => {
121 131 emit('openTrend', toRaw(props.sourceInfo));
122 132 };
  133 +
  134 + const handleAlarmModal = () => {
  135 + emit('openAlarm', toRaw(props.sourceInfo));
  136 + };
123 137 </script>
124 138
125 139 <template>
... ... @@ -132,8 +146,9 @@
132 146 </Tooltip>
133 147
134 148 <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" />
  149 + <Tooltip v-if="!isCustomerUser && hasTrendQueryIcon" :title="isAlarm ? '时间' : '趋势'">
  150 + <FieldTimeOutlined v-if="isAlarm" class="text-lg" @click="handleAlarmModal" />
  151 + <AreaChartOutlined v-else class="text-lg" @click="handleOpenTrendModal" />
137 152 </Tooltip>
138 153 <AuthDropDown
139 154 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 { ref, unref } from 'vue';
  3 + import { formSchema, SchemaFiled } from './config';
  4 + import { useForm } from '/@/components/Form';
  5 + import { useModalInner } from '/@/components/Modal';
  6 + import { useGridLayout } from '/@/hooks/component/useGridLayout';
  7 + import { ColEx } from '/@/components/Form/src/types';
  8 + import { BasicForm } from '/@/components/Form';
  9 + import { BasicModal } from '/@/components/Modal';
  10 + import { nextTick } from 'vue';
  11 +
  12 + const emit = defineEmits(['register', 'getAlarmForm']);
  13 + // const emit = defineEmits<{
  14 + // (event: 'getAlarmForm', data: WidgetDataType): void;
  15 + // }>();
  16 +
  17 + const [registerModal, { closeModal }] = useModalInner();
  18 +
  19 + const [register, method] = useForm({
  20 + schemas: formSchema(),
  21 + baseColProps: useGridLayout(1) as unknown as ColEx,
  22 + showSubmitButton: false,
  23 + showResetButton: false,
  24 + rowProps: {
  25 + gutter: 10,
  26 + },
  27 + labelWidth: 120,
  28 + fieldMapToTime: [
  29 + [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:ss:mm'],
  30 + ],
  31 + });
  32 +
  33 + const handleCancel = () => {
  34 + // destory()
  35 + };
  36 + const alarmForm = ref({ time: 2592000000, pageSize: 10 }); //默认30天
  37 + const handleSubmit = async () => {
  38 + const { way, pageSize, startTs, endTs } = method.getFieldsValue();
  39 + if (way == 'timePeriod') {
  40 + alarmForm.value = {
  41 + time: new Date(endTs).getTime() - new Date(startTs).getTime(),
  42 + pageSize,
  43 + };
  44 + } else {
  45 + alarmForm.value = {
  46 + time: startTs,
  47 + pageSize,
  48 + };
  49 + }
  50 + emit('getAlarmForm', unref(alarmForm));
  51 + await nextTick();
  52 + closeModal();
  53 + };
  54 +</script>
  55 +
  56 +<template>
  57 + <BasicModal
  58 + @register="registerModal"
  59 + @cancel="handleCancel"
  60 + @ok="handleSubmit"
  61 + :destroy-on-close="true"
  62 + :show-ok-btn="true"
  63 + cancel-text="关闭"
  64 + width="40%"
  65 + title="历史趋势"
  66 + >
  67 + <section
  68 + class="flex flex-col p-4 h-full w-full min-w-7/10"
  69 + style="color: #f0f2f5; background-color: #f0f2f5"
  70 + >
  71 + <section class="bg-white my-3 p-2">
  72 + <BasicForm @register="register" />
  73 + </section>
  74 + </section>
  75 + </BasicModal>
  76 +</template>
  77 +
  78 +<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 +}
  8 +
  9 +export interface AlarmContextType {
  10 + alarmForm: Ref<IAlarm>;
  11 + getAlarmForm: (value: any) => void;
  12 +}
  13 +
  14 +export const createAlarmContext = (options: AlarmContextType) => {
  15 + provide(SymbolKey, options);
  16 +};
  17 +
  18 +export const useAlarmContext = () => {
  19 + return inject<AlarmContextType>(SymbolKey) || ({} as Partial<AlarmContextType>);
  20 +};
... ...
... ... @@ -25,11 +25,13 @@
25 25 import { ModalParamsType } from '/#/utils';
26 26 import { DataActionModeEnum } from '/@/enums/toolEnum';
27 27 import { HistoryTrendModal } from './components/HistoryTrendModal';
  28 + import { alarmTimeModal } from './components/alarmTimeModal';
28 29 import { watch } from 'vue';
29 30 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
30 31 import { ThemeEnum } from '/@/enums/appEnum';
31 32 import { createDataBoardContext } from './hooks/useDataBoardContext';
32 33 import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket';
  34 + import { createAlarmContext } from './hooks/useAlarmTime';
33 35
34 36 const props = defineProps<{
35 37 value?: Recordable;
... ... @@ -73,6 +75,25 @@
73 75 openTrendModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType);
74 76 };
75 77
  78 + // 设备告警时间选择
  79 + const [registerAlarmModal, { openModal: openAlarmModal }] = useModal();
  80 + const handleOpenAlarm = (data: WidgetDataType) => {
  81 + openAlarmModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType);
  82 + };
  83 +
  84 + const alarmForm = ref({
  85 + time: 2592000000,
  86 + pageSize: 10,
  87 + });
  88 + const getAlarmForm = (value) => {
  89 + alarmForm.value = {
  90 + time: value.time,
  91 + pageSize: value.pageSize,
  92 + };
  93 + };
  94 +
  95 + createAlarmContext({ alarmForm: alarmForm, getAlarmForm });
  96 +
76 97 const { send, close } = useSocket(dataSource);
77 98
78 99 createDataBoardContext({ send, close });
... ... @@ -147,6 +168,7 @@
147 168 :source-info="item"
148 169 @update="handleUpdateWidget"
149 170 @open-trend="handleOpenTrend"
  171 + @open-alarm="handleOpenAlarm"
150 172 @ok="getDataSource"
151 173 />
152 174 </template>
... ... @@ -162,9 +184,12 @@
162 184
163 185 <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" />
164 186
  187 + <!-- 趋势 -->
165 188 <HistoryTrendModal @register="registerTrendModal" />
  189 +
  190 + <!-- 告警选择时间 -->
  191 + <alarmTimeModal @register="registerAlarmModal" @getAlarmForm="getAlarmForm" />
166 192 </section>
167 193 </template>
168 194
169 195 <style lang="less" scoped></style>
170   -../packages/hook/socket/useSocket
... ...